diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..389da54e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: osopromadze +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..dd84ea78 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..61e19e15 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,25 @@ +#### Description + +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. + +Fixes # (issue) + +#### Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +#### How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. + +#### Checklist: + +- [ ] I have performed a self-review of my own code +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings or errors +- [ ] New and existing unit tests pass locally with my changes diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..e943cfb1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,37 @@ +name: SonarCloud +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] +jobs: + build: + name: Build and analyze + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: 'zulu' + - name: Cache SonarCloud packages + uses: actions/cache@v3 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven packages + uses: actions/cache@v3 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=osopromadze_Spring-Boot-Blog-REST-API \ No newline at end of file diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml new file mode 100644 index 00000000..5e398432 --- /dev/null +++ b/.github/workflows/greetings.yml @@ -0,0 +1,13 @@ +name: Greetings + +on: [pull_request_target, issues] + +jobs: + greeting: + runs-on: ubuntu-latest + steps: + - uses: actions/first-interaction@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + issue-message: 'Welcome @${{ github.actor }} to Spring-Boot-Blog-REST-API community! Thanks so much for creating your first issue :)' + pr-message: 'Thanks so much @${{ github.actor }} for creating your first PR, the Spring-Boot-Blog-REST-API community thanks you :)' diff --git a/.gitignore b/.gitignore index 2d26c9f1..afea6911 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,12 @@ .settings .springBeans .sts4-cache +/bin/ +/build/ +*.jar +*.war +*.ear +*.db ### IntelliJ IDEA ### .idea diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index 9cc84ea9..00000000 Binary files a/.mvn/wrapper/maven-wrapper.jar and /dev/null differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index 6c8c0e08..00000000 --- a/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1 +0,0 @@ -distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..e7d65cce --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM adoptopenjdk/openjdk11:alpine-jre +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/LICENSE b/LICENSE index b2752525..0ad25db4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,661 @@ -MIT License - -Copyright (c) 2019 Omari Sopromadze - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md index 7eb05f34..2906f0fd 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.com/coma123/Spring-Boot-Blog-REST-API.svg?branch=development)](https://travis-ci.com/coma123/Spring-Boot-Blog-REST-API) [![Sonarcloud Status](https://sonarcloud.io/api/project_badges/measure?project=coma123_Spring-Boot-Blog-REST-API&metric=alert_status)](https://sonarcloud.io/dashboard?id=coma123_Spring-Boot-Blog-REST-API) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3706/badge)](https://bestpractices.coreinfrastructure.org/projects/3706) + # Spring Boot, MySQL, Spring Security, JWT, JPA, Rest API Build Restful CRUD API for a blog using Spring Boot, Mysql, JPA and Hibernate. @@ -284,5 +286,4 @@ Test them using postman or any other rest client. "completed": true } ``` - - +![segment](https://api.segment.io/v1/pixel/track?data=ewogICJ3cml0ZUtleSI6ICJwcDJuOTU4VU1NT21NR090MWJXS0JQd0tFNkcydW51OCIsCiAgInVzZXJJZCI6ICIxMjNibG9nYXBpMTIzIiwKICAiZXZlbnQiOiAiQmxvZ0FwaSB2aXNpdGVkIiwKICAicHJvcGVydGllcyI6IHsKICAgICJzdWJqZWN0IjogIkJsb2dBcGkgdmlzaXRlZCIsCiAgICAiZW1haWwiOiAiY29tcy5zcHVyc0BnbWFpbC5jb20iCiAgfQp9) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..60f6349a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,9 @@ +# Security Policy + +We take security very seriously and try to patch each discovered security vulnerability as soon as possible. Also we use +[automated tools](https://dependabot.com/) to continually monitor vulnerable dependencies. + +## Reporting a Vulnerability + +We use [full disclosure]() for reporting vulnerabilities. Therefore please +submit vulnerability reports through our [issues section](https://github.com/coma123/Spring-Boot-Blog-REST-API/issues/new?assignees=&labels=&template=bug_report.md&title=). diff --git a/data/blogapi.sql b/data/blogapi.sql new file mode 100644 index 00000000..5cc9ced8 --- /dev/null +++ b/data/blogapi.sql @@ -0,0 +1,192 @@ +USE blogapi; + +UNLOCK TABLES; + +DROP TABLE IF EXISTS `post_tag`; +DROP TABLE IF EXISTS `tags`; +DROP TABLE IF EXISTS `user_role`; +DROP TABLE IF EXISTS `roles`; +DROP TABLE IF EXISTS `comments`; +DROP TABLE IF EXISTS `posts`; +DROP TABLE IF EXISTS `photos`; +DROP TABLE IF EXISTS `albums`; +DROP TABLE IF EXISTS `todos`; +DROP TABLE IF EXISTS `users`; +DROP TABLE IF EXISTS `address`; +DROP TABLE IF EXISTS `company`; +DROP TABLE IF EXISTS `geo`; + +CREATE TABLE `tags` ( + `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created_by` bigint(19) unsigned NOT NULL, + `updated_by` bigint(19) unsigned NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `geo` ( + `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, + `lat` varchar(255), + `lng` varchar(255), + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created_by` bigint(19) unsigned DEFAULT NULL, + `updated_by` bigint(19) unsigned DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +CREATE TABLE `company` ( + `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255), + `catch_phrase` varchar(255), + `bs` varchar(255), + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created_by` bigint(19) unsigned DEFAULT NULL, + `updated_by` bigint(19) unsigned DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +CREATE TABLE `address` ( + `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, + `street` varchar(255), + `suite` varchar(255), + `city` varchar(255), + `zipcode` varchar(255), + `geo_id` bigint(19) unsigned DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created_by` bigint(19) unsigned DEFAULT NULL, + `updated_by` bigint(19) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `fk_geo` (`geo_id`), + CONSTRAINT `fk_geo` FOREIGN KEY (`geo_id`) REFERENCES `geo` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +CREATE TABLE `users` ( + `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, + `first_name` varchar(255) NOT NULL, + `last_name` varchar(255) NOT NULL, + `username` varchar(255) NOT NULL, + `password` varchar(255) NOT NULL, + `email` varchar(255) NOT NULL, + `address_id` bigint(19) unsigned DEFAULT NULL, + `phone` varchar(255), + `website` varchar(255), + `company_id` bigint(19) unsigned DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `fk_address` (`address_id`), + KEY `fk_company` (`company_id`), + CONSTRAINT `fk_address` FOREIGN KEY (`address_id`) REFERENCES `address` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_company` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +CREATE TABLE `todos` ( + `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, + `title` varchar(255) NOT NULL, + `completed` boolean default false, + `user_id` bigint(19) unsigned DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created_by` bigint(19) unsigned DEFAULT NULL, + `updated_by` bigint(19) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `fk_user_todos` (`user_id`), + CONSTRAINT `fk_user_todos` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +CREATE TABLE `albums` ( + `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, + `title` varchar(255) NOT NULL, + `user_id` bigint(19) unsigned DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created_by` bigint(19) unsigned DEFAULT NULL, + `updated_by` bigint(19) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `fk_user_album` (`user_id`), + CONSTRAINT `fk_user_album` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +CREATE TABLE `photos` ( + `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, + `title` varchar(255) NOT NULL, + `url` varchar(255) NOT NULL, + `thumbnail_url` varchar(255) NOT NULL, + `album_id` bigint(19) unsigned DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created_by` bigint(19) unsigned DEFAULT NULL, + `updated_by` bigint(19) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `fk_album` (`album_id`), + CONSTRAINT `fk_album` FOREIGN KEY (`album_id`) REFERENCES `albums` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +CREATE TABLE `posts` ( + `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, + `title` varchar(255) NOT NULL, + `body` text NOT NULL, + `user_id` bigint(19) unsigned DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created_by` bigint(19) unsigned DEFAULT NULL, + `updated_by` bigint(19) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `fk_user_post` (`user_id`), + CONSTRAINT `fk_user_post` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +CREATE TABLE `post_tag` ( + `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, + `post_id` bigint(19) unsigned NOT NULL, + `tag_id` bigint(19) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `fk_posttag_post_id` (`post_id`), + KEY `fk_posttag_tag_id` (`tag_id`), + CONSTRAINT `fk_posttag_post_id` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`), + CONSTRAINT `fk_posttag_tag_id` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `comments` ( + `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `email` varchar(255) NOT NULL, + `body` text NOT NULL, + `post_id` bigint(19) unsigned DEFAULT NULL, + `user_id` bigint(19) unsigned DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created_by` bigint(19) unsigned NOT NULL, + `updated_by` bigint(19) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `fk_comment_post` (`post_id`), + KEY `fk_comment_user` (`user_id`), + CONSTRAINT `fk_comment_post` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`), + CONSTRAINT `fk_comment_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +CREATE TABLE `roles` ( + `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +CREATE TABLE `user_role` ( + `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, + `user_id` bigint(19) unsigned NOT NULL, + `role_id` bigint(19) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `fk_security_user_id` (`user_id`), + KEY `fk_security_role_id` (`role_id`), + CONSTRAINT `fk_security_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`), + CONSTRAINT `fk_security_role_id` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +LOCK TABLES `roles` WRITE; +INSERT INTO `roles` VALUES (1,'ROLE_ADMIN'),(2,'ROLE_USER'); +UNLOCK TABLES; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..9bd9053e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +version: '3.8' +services: + db: + image: mysql + container_name: blogapi-db + restart: always + environment: + MYSQL_DATABASE: 'blogapi' + MYSQL_PASSWORD: 'root' + MYSQL_ROOT_PASSWORD: 'root' + ports: + - '3306:3306' + networks: + - blogapi-network + healthcheck: + test: "/usr/bin/mysql --user=root --password=root --execute \"SHOW DATABASES;\"" + interval: 2s + timeout: 20s + retries: 10 + volumes: + - ./data:/docker-entrypoint-initdb.d + application: + container_name: blogapi-application + build: + context: ./ + dockerfile: Dockerfile + ports: + - "8080:8080" + networks: + - blogapi-network + depends_on: + - "db" +networks: + blogapi-network: + name: blogapi-network + driver: bridge diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..f9832f18 --- /dev/null +++ b/docs/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at coma.spurs@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 00000000..5e2d16eb --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# :snowman: How to Contribute :snowman: + +We are quite glad you made it here! We can only keep our project in good health thanks to contributors like you! There are some points +we would like to highlight in terms of contributing to this repository. + +## Bug Reports and New Features + +- If you have any issue please report that using [Github Issues](https://github.com/coma123/Spring-Boot-Blog-REST-API/issues). +Currently there's two kinds; Bug Reports and New Features. + +- When writing a Bug Report or asking for a New Feature, please follow the template provided. Fill in the sections as much as you could so +we have all teh information we need. + +## Coding Contributions + +- All coding contributions should be done through [pull requests](https://help.github.com/en/articles/creating-a-pull-request-from-a-fork). We do not respond to emails or coding posted via our Gitter channel. If you have any suggessions or want to contribute please open a [issue](https://github.com/coma123/Spring-Boot-Blog-REST-API/issues) first. This way we could discuss about the change and once finalized you could do a pull request. + +- We don't have a comprehensive style guide for now, but we expect you code with due deligence with standard practices. Your code +should pass the standard unit and integration tests (automatically run by Travis). + +- Also please make sure your tests pass the [Sonar](https://sonarcloud.io/dashboard?id=coma123_Spring-Boot-Blog-REST-API) quality +analysis. We strive to maintain A grade for all sections. + +:snowman: This is all for now. Hope to update this document as we go along. If you have any suggestions please feel free to open +an issue in our [issue tracker](https://github.com/coma123/Spring-Boot-Blog-REST-API/issues). :snowman: diff --git a/pom.xml b/pom.xml index d08c5efd..4b6fd187 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - 4.0.0 @@ -14,7 +15,7 @@ org.springframework.boot spring-boot-starter-parent - 2.0.6.RELEASE + 2.1.8.RELEASE @@ -22,6 +23,8 @@ UTF-8 UTF-8 1.8 + coma123 + https://sonarcloud.io @@ -36,101 +39,126 @@ runtime - - org.springframework.boot - spring-boot-starter-web - 2.0.6.RELEASE - - - - org.springframework.boot - spring-boot-devtools - 2.0.6.RELEASE - - - - org.springframework.boot - spring-boot-starter-data-jpa - 2.0.6.RELEASE - - + + org.projectlombok + lombok + 1.18.12 + provided + - - org.springframework.boot - spring-boot-starter-test - test - + + org.springframework.boot + spring-boot-starter-web + org.springframework.boot - spring-boot-starter-security - 2.0.6.RELEASE - - - - org.springframework.security - spring-security-jwt - 1.0.9.RELEASE - - - - org.springframework.security.oauth - spring-security-oauth2 - 2.3.4.RELEASE - - - - io.jsonwebtoken - jjwt - 0.9.1 - - - - javax.xml.bind - jaxb-api - 2.3.1 - - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.9.7 + spring-boot-starter-aop - - - org.apache.commons - commons-lang3 - 3.8.1 - - - - org.modelmapper - modelmapper - 2.3.1 - - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - 2.1.0.RELEASE - - com.sopromadze.blogapi.BlogApiApplication - - - - - repackage - - - - - - - - + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.mockito + mockito-all + 2.0.2-beta + test + + + + + org.springframework.security + spring-security-test + test + + + + + + org.hamcrest + hamcrest-library + test + + + + + org.modelmapper + modelmapper + 2.3.5 + + + + org.springframework.boot + spring-boot-devtools + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.security + spring-security-jwt + 1.0.9.RELEASE + + + + org.springframework.security.oauth + spring-security-oauth2 + [2.3.5,) + + + + io.jsonwebtoken + jjwt + 0.9.1 + + + + javax.xml.bind + jaxb-api + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + org.apache.commons + commons-lang3 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + com.sopromadze.blogapi.BlogApiApplication + + + + + repackage + + + + + + diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 00000000..c11351b8 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,2 @@ +sonar.branch.name=development +sonar.sources=src/main/ diff --git a/src/main/java/com/sopromadze/blogapi/BlogApiApplication.java b/src/main/java/com/sopromadze/blogapi/BlogApiApplication.java index 2f6e2b6e..6e45ce6b 100644 --- a/src/main/java/com/sopromadze/blogapi/BlogApiApplication.java +++ b/src/main/java/com/sopromadze/blogapi/BlogApiApplication.java @@ -1,6 +1,7 @@ package com.sopromadze.blogapi; import com.sopromadze.blogapi.security.JwtAuthenticationFilter; +import org.modelmapper.ModelMapper; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; @@ -11,24 +12,27 @@ import java.util.TimeZone; @SpringBootApplication -@EntityScan(basePackageClasses = { - BlogApiApplication.class, - Jsr310Converters.class -}) +@EntityScan(basePackageClasses = { BlogApiApplication.class, Jsr310Converters.class }) + public class BlogApiApplication { - public static void main(String[] args) { - SpringApplication.run(BlogApiApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(BlogApiApplication.class, args); + } + + @PostConstruct + void init() { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + } - @PostConstruct - void init(){ - TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - } + @Bean + public JwtAuthenticationFilter jwtAuthenticationFilter() { + return new JwtAuthenticationFilter(); + } - @Bean - public JwtAuthenticationFilter jwtAuthenticationFilter() { - return new JwtAuthenticationFilter(); - } + @Bean + public ModelMapper modelMapper() { + return new ModelMapper(); + } } diff --git a/src/main/java/com/sopromadze/blogapi/config/AuditingConfig.java b/src/main/java/com/sopromadze/blogapi/config/AuditingConfig.java index 2d820eae..144a9444 100644 --- a/src/main/java/com/sopromadze/blogapi/config/AuditingConfig.java +++ b/src/main/java/com/sopromadze/blogapi/config/AuditingConfig.java @@ -15,24 +15,24 @@ @EnableJpaAuditing public class AuditingConfig { - @Bean - public AuditorAware auditorProvider(){ - return new SpringSecurityAuditAwareImpl(); - } + @Bean + public AuditorAware auditorProvider() { + return new SpringSecurityAuditAwareImpl(); + } } -class SpringSecurityAuditAwareImpl implements AuditorAware{ +class SpringSecurityAuditAwareImpl implements AuditorAware { - @Override - public Optional getCurrentAuditor() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + @Override + public Optional getCurrentAuditor() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if(authentication == null || !authentication.isAuthenticated() || authentication instanceof AnonymousAuthenticationToken) { - return Optional.empty(); - } + if (authentication == null || !authentication.isAuthenticated() || authentication instanceof AnonymousAuthenticationToken) { + return Optional.empty(); + } - UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); + UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); - return Optional.ofNullable(userPrincipal.getId()); - } + return Optional.ofNullable(userPrincipal.getId()); + } } diff --git a/src/main/java/com/sopromadze/blogapi/config/SecutiryConfig.java b/src/main/java/com/sopromadze/blogapi/config/SecutiryConfig.java index de4b402f..1a0c1175 100644 --- a/src/main/java/com/sopromadze/blogapi/config/SecutiryConfig.java +++ b/src/main/java/com/sopromadze/blogapi/config/SecutiryConfig.java @@ -3,7 +3,7 @@ import com.sopromadze.blogapi.repository.UserRepository; import com.sopromadze.blogapi.security.JwtAuthenticationEntryPoint; import com.sopromadze.blogapi.security.JwtAuthenticationFilter; -import com.sopromadze.blogapi.service.CustomUserDetailsService; +import com.sopromadze.blogapi.service.impl.CustomUserDetailsServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -23,54 +23,54 @@ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity( - securedEnabled = true, - jsr250Enabled = true, - prePostEnabled = true) + securedEnabled = true, + jsr250Enabled = true, + prePostEnabled = true) public class SecutiryConfig extends WebSecurityConfigurerAdapter { - private final CustomUserDetailsService customUserDetailsService; - private final JwtAuthenticationEntryPoint unauthorizedHandler; - private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final CustomUserDetailsServiceImpl customUserDetailsService; + private final JwtAuthenticationEntryPoint unauthorizedHandler; + private final JwtAuthenticationFilter jwtAuthenticationFilter; - @Autowired - public SecutiryConfig(UserRepository userRepository, CustomUserDetailsService customUserDetailsService, JwtAuthenticationEntryPoint unauthorizedHandler, JwtAuthenticationFilter jwtAuthenticationFilter) { - this.customUserDetailsService = customUserDetailsService; - this.unauthorizedHandler = unauthorizedHandler; - this.jwtAuthenticationFilter = jwtAuthenticationFilter; - ; - } + @Autowired + public SecutiryConfig(UserRepository userRepository, CustomUserDetailsServiceImpl customUserDetailsService, + JwtAuthenticationEntryPoint unauthorizedHandler, JwtAuthenticationFilter jwtAuthenticationFilter) { + this.customUserDetailsService = customUserDetailsService; + this.unauthorizedHandler = unauthorizedHandler; + this.jwtAuthenticationFilter = jwtAuthenticationFilter; + } - @Override - protected void configure(HttpSecurity http) throws Exception { - http.cors().and().csrf().disable() - .exceptionHandling() - .authenticationEntryPoint(unauthorizedHandler) - .and() - .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and() - .authorizeRequests() - .antMatchers(HttpMethod.GET, "/api/**").permitAll() - .antMatchers("/api/auth/**").permitAll() - .antMatchers(HttpMethod.GET, "/api/users/checkUsernameAvailability", "/api/users/checkEmailAvailability").permitAll() - .anyRequest().authenticated(); + @Override + protected void configure(HttpSecurity http) throws Exception { - http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + http.cors().and().csrf().disable() + .exceptionHandling() + .authenticationEntryPoint(unauthorizedHandler) + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests() + .antMatchers(HttpMethod.GET, "/api/**").permitAll() + .antMatchers(HttpMethod.POST, "/api/auth/**").permitAll() + .antMatchers(HttpMethod.GET, "/api/users/checkUsernameAvailability", "/api/users/checkEmailAvailability").permitAll() + .anyRequest().authenticated(); + http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - } + } - public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { - authenticationManagerBuilder.userDetailsService(customUserDetailsService) - .passwordEncoder(passwordEncoder()); - } + public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { + authenticationManagerBuilder.userDetailsService(customUserDetailsService) + .passwordEncoder(passwordEncoder()); + } - @Bean(BeanIds.AUTHENTICATION_MANAGER) - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); - } + @Bean(BeanIds.AUTHENTICATION_MANAGER) + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } } diff --git a/src/main/java/com/sopromadze/blogapi/config/WebMvcConfig.java b/src/main/java/com/sopromadze/blogapi/config/WebMvcConfig.java index eb9f2e79..301ecb17 100644 --- a/src/main/java/com/sopromadze/blogapi/config/WebMvcConfig.java +++ b/src/main/java/com/sopromadze/blogapi/config/WebMvcConfig.java @@ -1,17 +1,23 @@ package com.sopromadze.blogapi.config; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebMvcConfig implements WebMvcConfigurer { - private final long MAX_AGE_SECS = 3600; - public void addCorsMappings(CorsRegistry registry){ - registry.addMapping("/**") - .allowedOrigins("*") - .allowedMethods("HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE") - .maxAge(MAX_AGE_SECS); - } + @Value("cors.allowedOrings") + private String allowedOrigins; + + public void addCorsMappings(CorsRegistry registry) { + final long MAX_AGE_SECS = 3600; + + registry.addMapping("/**") + .allowedOrigins(allowedOrigins) + .allowedMethods("GET", "POST", "PUT", "DELETE") + .allowedHeaders("*") + .maxAge(MAX_AGE_SECS); + } } diff --git a/src/main/java/com/sopromadze/blogapi/controller/AlbumController.java b/src/main/java/com/sopromadze/blogapi/controller/AlbumController.java index f90c3e08..b5dc9ff4 100644 --- a/src/main/java/com/sopromadze/blogapi/controller/AlbumController.java +++ b/src/main/java/com/sopromadze/blogapi/controller/AlbumController.java @@ -1,67 +1,90 @@ package com.sopromadze.blogapi.controller; -import com.sopromadze.blogapi.model.album.Album; +import com.sopromadze.blogapi.exception.ResponseEntityErrorException; +import com.sopromadze.blogapi.model.Album; +import com.sopromadze.blogapi.payload.AlbumResponse; +import com.sopromadze.blogapi.payload.ApiResponse; import com.sopromadze.blogapi.payload.PagedResponse; +import com.sopromadze.blogapi.payload.PhotoResponse; +import com.sopromadze.blogapi.payload.request.AlbumRequest; import com.sopromadze.blogapi.security.CurrentUser; import com.sopromadze.blogapi.security.UserPrincipal; import com.sopromadze.blogapi.service.AlbumService; import com.sopromadze.blogapi.service.PhotoService; -import com.sopromadze.blogapi.util.AppConstants; +import com.sopromadze.blogapi.utils.AppConstants; +import com.sopromadze.blogapi.utils.AppUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; @RestController @RequestMapping("/api/albums") public class AlbumController { - private final AlbumService albumService; - private final PhotoService photoService; + @Autowired + private AlbumService albumService; - @Autowired - public AlbumController(AlbumService albumService, PhotoService photoService) { - this.albumService = albumService; - this.photoService = photoService; - } + @Autowired + private PhotoService photoService; - @GetMapping - public PagedResponse getAllAlbums( - @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, - @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size){ - return albumService.getAllAlbums(page, size); - } + @ExceptionHandler(ResponseEntityErrorException.class) + public ResponseEntity handleExceptions(ResponseEntityErrorException exception) { + return exception.getApiResponse(); + } - @PostMapping - @PreAuthorize("hasRole('USER')") - public ResponseEntity addAlbum(@Valid @RequestBody Album album, @CurrentUser UserPrincipal currentUser){ - return albumService.addAlbum(album, currentUser); - } + @GetMapping + public PagedResponse getAllAlbums( + @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, + @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { + AppUtils.validatePageNumberAndSize(page, size); - @GetMapping("/{id}") - public ResponseEntity getAlbum(@PathVariable(name = "id") Long id){ - return albumService.getAlbum(id); - } + return albumService.getAllAlbums(page, size); + } - @PutMapping("/{id}") - @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") - public ResponseEntity updateAlbum(@PathVariable(name = "id") Long id, @Valid @RequestBody Album newAlbum, @CurrentUser UserPrincipal currentUser){ - return albumService.updateAlbum(id, newAlbum, currentUser); - } + @PostMapping + @PreAuthorize("hasRole('USER')") + public ResponseEntity addAlbum(@Valid @RequestBody AlbumRequest albumRequest, @CurrentUser UserPrincipal currentUser) { + return albumService.addAlbum(albumRequest, currentUser); + } - @DeleteMapping("/{id}") - @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") - public ResponseEntity deleteAlbum(@PathVariable(name = "id") Long id, @CurrentUser UserPrincipal currentUser){ - return albumService.deleteAlbum(id, currentUser); - } + @GetMapping("/{id}") + public ResponseEntity getAlbum(@PathVariable(name = "id") Long id) { + return albumService.getAlbum(id); + } - @GetMapping("/{id}/photos") - public PagedResponse getAllPhotosByAlbum( - @PathVariable(name = "id") Long id, - @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, - @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size){ - return photoService.getAllPhotosByAlbum(id, page, size); - } + @PutMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") + public ResponseEntity updateAlbum(@PathVariable(name = "id") Long id, @Valid @RequestBody AlbumRequest newAlbum, + @CurrentUser UserPrincipal currentUser) { + return albumService.updateAlbum(id, newAlbum, currentUser); + } + + @DeleteMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") + public ResponseEntity deleteAlbum(@PathVariable(name = "id") Long id, @CurrentUser UserPrincipal currentUser) { + return albumService.deleteAlbum(id, currentUser); + } + + @GetMapping("/{id}/photos") + public ResponseEntity> getAllPhotosByAlbum(@PathVariable(name = "id") Long id, + @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, + @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { + + PagedResponse response = photoService.getAllPhotosByAlbum(id, page, size); + + return new ResponseEntity<>(response, HttpStatus.OK); + } } diff --git a/src/main/java/com/sopromadze/blogapi/controller/AuthController.java b/src/main/java/com/sopromadze/blogapi/controller/AuthController.java index d0952d1f..5d49753d 100644 --- a/src/main/java/com/sopromadze/blogapi/controller/AuthController.java +++ b/src/main/java/com/sopromadze/blogapi/controller/AuthController.java @@ -1,6 +1,7 @@ package com.sopromadze.blogapi.controller; import com.sopromadze.blogapi.exception.AppException; +import com.sopromadze.blogapi.exception.BlogapiException; import com.sopromadze.blogapi.model.role.Role; import com.sopromadze.blogapi.model.role.RoleName; import com.sopromadze.blogapi.model.user.User; @@ -33,77 +34,75 @@ @RestController @RequestMapping("/api/auth") public class AuthController { - private final AuthenticationManager authenticationManager; + private static final String USER_ROLE_NOT_SET = "User role not set"; - private final UserRepository userRepository; + @Autowired + private AuthenticationManager authenticationManager; - private final RoleRepository roleRepository; + @Autowired + private UserRepository userRepository; - private final PasswordEncoder passwordEncoder; + @Autowired + private RoleRepository roleRepository; - private final JwtTokenProvider jwtTokenProvider; + @Autowired + private PasswordEncoder passwordEncoder; - @Autowired - public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, RoleRepository roleRepository, PasswordEncoder passwordEncoder, JwtTokenProvider jwtTokenProvider) { - this.authenticationManager = authenticationManager; - this.userRepository = userRepository; - this.roleRepository = roleRepository; - this.passwordEncoder = passwordEncoder; - this.jwtTokenProvider = jwtTokenProvider; - } + @Autowired + private JwtTokenProvider jwtTokenProvider; - @PostMapping("/signin") - public ResponseEntity authenticateUser(@Valid @RequestBody LoginRequest loginRequest){ - Authentication authentication = authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken( - loginRequest.getUsernameOrEmail(), - loginRequest.getPassword() - ) - ); + @PostMapping("/signin") + public ResponseEntity authenticateUser(@Valid @RequestBody LoginRequest loginRequest) { + Authentication authentication = authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(loginRequest.getUsernameOrEmail(), loginRequest.getPassword())); - SecurityContextHolder.getContext().setAuthentication(authentication); + SecurityContextHolder.getContext().setAuthentication(authentication); - String jwt = jwtTokenProvider.generateToken(authentication); - return ResponseEntity.ok(new JwtAuthenticationResponse(jwt)); - } + String jwt = jwtTokenProvider.generateToken(authentication); + return ResponseEntity.ok(new JwtAuthenticationResponse(jwt)); + } - @PostMapping("/signup") - public ResponseEntity registerUser(@Valid @RequestBody SignUpRequest signUpRequest){ - if(userRepository.existsByUsername(signUpRequest.getUsername())){ - return new ResponseEntity<>(new ApiResponse(false, "Username is already taken"), HttpStatus.BAD_REQUEST); - } + @PostMapping("/signup") + public ResponseEntity registerUser(@Valid @RequestBody SignUpRequest signUpRequest) { + if (Boolean.TRUE.equals(userRepository.existsByUsername(signUpRequest.getUsername()))) { + throw new BlogapiException(HttpStatus.BAD_REQUEST, "Username is already taken"); + } - if(userRepository.existsByEmail(signUpRequest.getEmail())){ - return new ResponseEntity<>(new ApiResponse(false, "Email is already taken"), HttpStatus.BAD_REQUEST); - } - String firstName = signUpRequest.getFirstName().substring(0, 1).toUpperCase() + signUpRequest.getFirstName().substring(1).toLowerCase(); + if (Boolean.TRUE.equals(userRepository.existsByEmail(signUpRequest.getEmail()))) { + throw new BlogapiException(HttpStatus.BAD_REQUEST, "Email is already taken"); + } - String lastName = signUpRequest.getLastName().substring(0, 1).toUpperCase() + signUpRequest.getLastName().substring(1).toLowerCase(); + String firstName = signUpRequest.getFirstName().toLowerCase(); - String username = signUpRequest.getUsername().toLowerCase(); + String lastName = signUpRequest.getLastName().toLowerCase(); - String email = signUpRequest.getEmail().toLowerCase(); + String username = signUpRequest.getUsername().toLowerCase(); - User user = new User(firstName, lastName, username, email, signUpRequest.getPassword()); + String email = signUpRequest.getEmail().toLowerCase(); - user.setPassword(passwordEncoder.encode(user.getPassword())); + String password = passwordEncoder.encode(signUpRequest.getPassword()); - List roles = new ArrayList<>(); - if(userRepository.count() == 0){ - roles.add(roleRepository.findByName(RoleName.ROLE_USER).orElseThrow(() -> new AppException("User role not set"))); - roles.add(roleRepository.findByName(RoleName.ROLE_ADMIN).orElseThrow(() -> new AppException("User role not set"))); - } else{ - roles.add(roleRepository.findByName(RoleName.ROLE_USER).orElseThrow(() -> new AppException("User role not set"))); - } + User user = new User(firstName, lastName, username, email, password); - user.setRoles(roles); + List roles = new ArrayList<>(); - User result = userRepository.save(user); + if (userRepository.count() == 0) { + roles.add(roleRepository.findByName(RoleName.ROLE_USER) + .orElseThrow(() -> new AppException(USER_ROLE_NOT_SET))); + roles.add(roleRepository.findByName(RoleName.ROLE_ADMIN) + .orElseThrow(() -> new AppException(USER_ROLE_NOT_SET))); + } else { + roles.add(roleRepository.findByName(RoleName.ROLE_USER) + .orElseThrow(() -> new AppException(USER_ROLE_NOT_SET))); + } - URI location = ServletUriComponentsBuilder - .fromCurrentContextPath().path("/api/users/{userId}") - .buildAndExpand(result.getId()).toUri(); + user.setRoles(roles); - return ResponseEntity.created(location).body(new ApiResponse(true, "User registered successfully")); - } + User result = userRepository.save(user); + + URI location = ServletUriComponentsBuilder.fromCurrentContextPath().path("/api/users/{userId}") + .buildAndExpand(result.getId()).toUri(); + + return ResponseEntity.created(location).body(new ApiResponse(Boolean.TRUE, "User registered successfully")); + } } diff --git a/src/main/java/com/sopromadze/blogapi/controller/CategoryController.java b/src/main/java/com/sopromadze/blogapi/controller/CategoryController.java new file mode 100644 index 00000000..39b63842 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/controller/CategoryController.java @@ -0,0 +1,66 @@ +package com.sopromadze.blogapi.controller; + +import com.sopromadze.blogapi.exception.UnauthorizedException; +import com.sopromadze.blogapi.model.Category; +import com.sopromadze.blogapi.payload.ApiResponse; +import com.sopromadze.blogapi.payload.PagedResponse; +import com.sopromadze.blogapi.security.CurrentUser; +import com.sopromadze.blogapi.security.UserPrincipal; +import com.sopromadze.blogapi.service.CategoryService; +import com.sopromadze.blogapi.utils.AppConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; + +@RestController +@RequestMapping("/api/categories") +public class CategoryController { + @Autowired + private CategoryService categoryService; + + @GetMapping + public PagedResponse getAllCategories( + @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, + @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { + return categoryService.getAllCategories(page, size); + } + + @PostMapping + @PreAuthorize("hasRole('USER')") + public ResponseEntity addCategory(@Valid @RequestBody Category category, + @CurrentUser UserPrincipal currentUser) { + + return categoryService.addCategory(category, currentUser); + } + + @GetMapping("/{id}") + public ResponseEntity getCategory(@PathVariable(name = "id") Long id) { + return categoryService.getCategory(id); + } + + @PutMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") + public ResponseEntity updateCategory(@PathVariable(name = "id") Long id, + @Valid @RequestBody Category category, @CurrentUser UserPrincipal currentUser) throws UnauthorizedException { + return categoryService.updateCategory(id, category, currentUser); + } + + @DeleteMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") + public ResponseEntity deleteCategory(@PathVariable(name = "id") Long id, + @CurrentUser UserPrincipal currentUser) throws UnauthorizedException { + return categoryService.deleteCategory(id, currentUser); + } + +} diff --git a/src/main/java/com/sopromadze/blogapi/controller/CommentController.java b/src/main/java/com/sopromadze/blogapi/controller/CommentController.java index f0375010..2a402910 100644 --- a/src/main/java/com/sopromadze/blogapi/controller/CommentController.java +++ b/src/main/java/com/sopromadze/blogapi/controller/CommentController.java @@ -1,63 +1,83 @@ package com.sopromadze.blogapi.controller; -import com.sopromadze.blogapi.exception.ResourceNotFoundException; -import com.sopromadze.blogapi.model.comment.Comment; -import com.sopromadze.blogapi.model.post.Post; +import com.sopromadze.blogapi.model.Comment; +import com.sopromadze.blogapi.payload.ApiResponse; import com.sopromadze.blogapi.payload.CommentRequest; import com.sopromadze.blogapi.payload.PagedResponse; -import com.sopromadze.blogapi.repository.CommentRepository; -import com.sopromadze.blogapi.repository.PostRepository; import com.sopromadze.blogapi.security.CurrentUser; import com.sopromadze.blogapi.security.UserPrincipal; import com.sopromadze.blogapi.service.CommentService; -import com.sopromadze.blogapi.util.AppConstants; +import com.sopromadze.blogapi.utils.AppConstants; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; -import java.util.List; @RestController @RequestMapping("/api/posts/{postId}/comments") public class CommentController { - private final CommentService commentService; - - @Autowired - public CommentController(CommentService commentService) { - this.commentService = commentService; - } - - @GetMapping - public PagedResponse getAllComments( - @PathVariable(name = "postId") Long postId, - @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, - @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size){ - return commentService.getAllComments(postId, page, size); - } - - @PostMapping - @PreAuthorize("hasRole('USER')") - public ResponseEntity addComment(@Valid @RequestBody CommentRequest commentRequest, @PathVariable(name = "postId") Long postId, @CurrentUser UserPrincipal currentUser){ - return commentService.addComment(commentRequest, postId, currentUser); - } - - @GetMapping("/{id}") - public ResponseEntity getComment(@PathVariable(name = "postId") Long postId, @PathVariable(name = "id") Long id){ - return commentService.getComment(postId, id); - } - - @PutMapping("/{id}") - @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") - public ResponseEntity updateComment(@PathVariable(name = "postId") Long postId, @PathVariable(name = "id") Long id, @Valid @RequestBody CommentRequest commentRequest, @CurrentUser UserPrincipal currentUser){ - return commentService.updateComment(postId, id, commentRequest, currentUser); - } - - @DeleteMapping("/{id}") - @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") - public ResponseEntity deleteComment(@PathVariable(name = "postId") Long postId, @PathVariable(name = "id") Long id, @CurrentUser UserPrincipal currentUser){ - return commentService.deleteComment(postId, id, currentUser); - } + @Autowired + private CommentService commentService; + + @GetMapping + public ResponseEntity> getAllComments(@PathVariable(name = "postId") Long postId, + @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, + @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { + + PagedResponse allComments = commentService.getAllComments(postId, page, size); + + return new ResponseEntity< >(allComments, HttpStatus.OK); + } + + @PostMapping + @PreAuthorize("hasRole('USER')") + public ResponseEntity addComment(@Valid @RequestBody CommentRequest commentRequest, + @PathVariable(name = "postId") Long postId, @CurrentUser UserPrincipal currentUser) { + Comment newComment = commentService.addComment(commentRequest, postId, currentUser); + + return new ResponseEntity<>(newComment, HttpStatus.CREATED); + } + + @GetMapping("/{id}") + public ResponseEntity getComment(@PathVariable(name = "postId") Long postId, + @PathVariable(name = "id") Long id) { + Comment comment = commentService.getComment(postId, id); + + return new ResponseEntity<>(comment, HttpStatus.OK); + } + + @PutMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") + public ResponseEntity updateComment(@PathVariable(name = "postId") Long postId, + @PathVariable(name = "id") Long id, @Valid @RequestBody CommentRequest commentRequest, + @CurrentUser UserPrincipal currentUser) { + + Comment updatedComment = commentService.updateComment(postId, id, commentRequest, currentUser); + + return new ResponseEntity<>(updatedComment, HttpStatus.OK); + } + + @DeleteMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") + public ResponseEntity deleteComment(@PathVariable(name = "postId") Long postId, + @PathVariable(name = "id") Long id, @CurrentUser UserPrincipal currentUser) { + + ApiResponse response = commentService.deleteComment(postId, id, currentUser); + + HttpStatus status = response.getSuccess() ? HttpStatus.OK : HttpStatus.BAD_REQUEST; + + return new ResponseEntity<>(response, status); + } } diff --git a/src/main/java/com/sopromadze/blogapi/controller/PhotoController.java b/src/main/java/com/sopromadze/blogapi/controller/PhotoController.java index 24112ee9..2f0846d0 100644 --- a/src/main/java/com/sopromadze/blogapi/controller/PhotoController.java +++ b/src/main/java/com/sopromadze/blogapi/controller/PhotoController.java @@ -1,63 +1,73 @@ package com.sopromadze.blogapi.controller; -import com.sopromadze.blogapi.exception.ResourceNotFoundException; -import com.sopromadze.blogapi.model.album.Album; -import com.sopromadze.blogapi.model.photo.Photo; +import com.sopromadze.blogapi.payload.ApiResponse; import com.sopromadze.blogapi.payload.PagedResponse; import com.sopromadze.blogapi.payload.PhotoRequest; import com.sopromadze.blogapi.payload.PhotoResponse; -import com.sopromadze.blogapi.repository.AlbumRepository; -import com.sopromadze.blogapi.repository.PhotoRepository; import com.sopromadze.blogapi.security.CurrentUser; import com.sopromadze.blogapi.security.UserPrincipal; import com.sopromadze.blogapi.service.PhotoService; -import com.sopromadze.blogapi.util.AppConstants; +import com.sopromadze.blogapi.utils.AppConstants; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; -import java.util.ArrayList; -import java.util.List; @RestController @RequestMapping("/api/photos") public class PhotoController { - private final PhotoService photoService; - - @Autowired - public PhotoController(PhotoService photoService) { - this.photoService = photoService; - } - - @GetMapping - public PagedResponse getAllPhotos( - @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, - @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size){ - return photoService.getAllPhotos(page, size); - } - - @PostMapping - @PreAuthorize("hasRole('USER')") - public ResponseEntity addPhoto(@Valid @RequestBody PhotoRequest photoRequest, @CurrentUser UserPrincipal currentUser){ - return photoService.addPhoto(photoRequest, currentUser); - } - - @GetMapping("/{id}") - public ResponseEntity getPhoto(@PathVariable(name = "id") Long id){ - return photoService.getPhoto(id); - } - - @PutMapping("/{id}") - @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") - public ResponseEntity updatePhoto(@PathVariable(name = "id") Long id, @Valid @RequestBody PhotoRequest photoRequest, @CurrentUser UserPrincipal currentUser){ - return photoService.updatePhoto(id, photoRequest, currentUser); - } - - @DeleteMapping("/{id}") - @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") - public ResponseEntity deletePhoto(@PathVariable(name = "id") Long id, @CurrentUser UserPrincipal currentUser){ - return photoService.deletePhoto(id, currentUser); - } + @Autowired + private PhotoService photoService; + + @GetMapping + public PagedResponse getAllPhotos( + @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, + @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { + return photoService.getAllPhotos(page, size); + } + + @PostMapping + @PreAuthorize("hasRole('USER')") + public ResponseEntity addPhoto(@Valid @RequestBody PhotoRequest photoRequest, + @CurrentUser UserPrincipal currentUser) { + PhotoResponse photoResponse = photoService.addPhoto(photoRequest, currentUser); + + return new ResponseEntity< >(photoResponse, HttpStatus.OK); + } + + @GetMapping("/{id}") + public ResponseEntity getPhoto(@PathVariable(name = "id") Long id) { + PhotoResponse photoResponse = photoService.getPhoto(id); + + return new ResponseEntity< >(photoResponse, HttpStatus.OK); + } + + @PutMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") + public ResponseEntity updatePhoto(@PathVariable(name = "id") Long id, + @Valid @RequestBody PhotoRequest photoRequest, @CurrentUser UserPrincipal currentUser) { + + PhotoResponse photoResponse = photoService.updatePhoto(id, photoRequest, currentUser); + + return new ResponseEntity< >(photoResponse, HttpStatus.OK); + } + + @DeleteMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") + public ResponseEntity deletePhoto(@PathVariable(name = "id") Long id, @CurrentUser UserPrincipal currentUser) { + ApiResponse apiResponse = photoService.deletePhoto(id, currentUser); + + return new ResponseEntity< >(apiResponse, HttpStatus.OK); + } } diff --git a/src/main/java/com/sopromadze/blogapi/controller/PostController.java b/src/main/java/com/sopromadze/blogapi/controller/PostController.java index 1bb02f0d..5b8efcc7 100644 --- a/src/main/java/com/sopromadze/blogapi/controller/PostController.java +++ b/src/main/java/com/sopromadze/blogapi/controller/PostController.java @@ -1,56 +1,95 @@ package com.sopromadze.blogapi.controller; -import com.sopromadze.blogapi.model.post.Post; +import com.sopromadze.blogapi.model.Post; +import com.sopromadze.blogapi.payload.ApiResponse; import com.sopromadze.blogapi.payload.PagedResponse; +import com.sopromadze.blogapi.payload.PostRequest; +import com.sopromadze.blogapi.payload.PostResponse; import com.sopromadze.blogapi.security.CurrentUser; import com.sopromadze.blogapi.security.UserPrincipal; import com.sopromadze.blogapi.service.PostService; -import com.sopromadze.blogapi.util.AppConstants; +import com.sopromadze.blogapi.utils.AppConstants; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; -import java.util.Optional; @RestController @RequestMapping("/api/posts") public class PostController { - private final PostService postService; - - @Autowired - public PostController(PostService postService) { - this.postService = postService; - } - - @GetMapping - public PagedResponse getAllPosts( - @RequestParam(value = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, - @RequestParam(value = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size){ - return postService.getAllPosts(page, size); - } - - @PostMapping - @PreAuthorize("hasRole('USER')") - public ResponseEntity addPost(@Valid @RequestBody Post post, @CurrentUser UserPrincipal currentUser){ - return postService.addPost(post, currentUser); - } - - @GetMapping("/{id}") - public ResponseEntity getPost(@PathVariable(name = "id") Long id){ - return postService.getPost(id); - } - - @PutMapping("/{id}") - @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") - public ResponseEntity updatePost(@PathVariable(name = "id") Long id, @Valid @RequestBody Post newPost, @CurrentUser UserPrincipal currentUser){ - return postService.updatePost(id, newPost, currentUser); - } - - @DeleteMapping("/{id}") - @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") - public ResponseEntity deletePost(@PathVariable(name = "id") Long id, @CurrentUser UserPrincipal currentUser){ - return postService.deletePost(id, currentUser); - } + @Autowired + private PostService postService; + + @GetMapping + public ResponseEntity> getAllPosts( + @RequestParam(value = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, + @RequestParam(value = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { + PagedResponse response = postService.getAllPosts(page, size); + + return new ResponseEntity< >(response, HttpStatus.OK); + } + + @GetMapping("/category/{id}") + public ResponseEntity> getPostsByCategory( + @RequestParam(value = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, + @RequestParam(value = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size, + @PathVariable(name = "id") Long id) { + PagedResponse response = postService.getPostsByCategory(id, page, size); + + return new ResponseEntity< >(response, HttpStatus.OK); + } + + @GetMapping("/tag/{id}") + public ResponseEntity> getPostsByTag( + @RequestParam(value = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, + @RequestParam(value = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size, + @PathVariable(name = "id") Long id) { + PagedResponse response = postService.getPostsByTag(id, page, size); + + return new ResponseEntity< >(response, HttpStatus.OK); + } + + @PostMapping + @PreAuthorize("hasRole('USER')") + public ResponseEntity addPost(@Valid @RequestBody PostRequest postRequest, + @CurrentUser UserPrincipal currentUser) { + PostResponse postResponse = postService.addPost(postRequest, currentUser); + + return new ResponseEntity< >(postResponse, HttpStatus.CREATED); + } + + @GetMapping("/{id}") + public ResponseEntity getPost(@PathVariable(name = "id") Long id) { + Post post = postService.getPost(id); + + return new ResponseEntity< >(post, HttpStatus.OK); + } + + @PutMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") + public ResponseEntity updatePost(@PathVariable(name = "id") Long id, + @Valid @RequestBody PostRequest newPostRequest, @CurrentUser UserPrincipal currentUser) { + Post post = postService.updatePost(id, newPostRequest, currentUser); + + return new ResponseEntity< >(post, HttpStatus.OK); + } + + @DeleteMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") + public ResponseEntity deletePost(@PathVariable(name = "id") Long id, @CurrentUser UserPrincipal currentUser) { + ApiResponse apiResponse = postService.deletePost(id, currentUser); + + return new ResponseEntity< >(apiResponse, HttpStatus.OK); + } } diff --git a/src/main/java/com/sopromadze/blogapi/controller/TagController.java b/src/main/java/com/sopromadze/blogapi/controller/TagController.java new file mode 100644 index 00000000..9a9b45c8 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/controller/TagController.java @@ -0,0 +1,74 @@ +package com.sopromadze.blogapi.controller; + +import com.sopromadze.blogapi.model.Tag; +import com.sopromadze.blogapi.payload.ApiResponse; +import com.sopromadze.blogapi.payload.PagedResponse; +import com.sopromadze.blogapi.security.CurrentUser; +import com.sopromadze.blogapi.security.UserPrincipal; +import com.sopromadze.blogapi.service.TagService; +import com.sopromadze.blogapi.utils.AppConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; + +@RestController +@RequestMapping("/api/tags") +public class TagController { + @Autowired + private TagService tagService; + + @GetMapping + public ResponseEntity> getAllTags( + @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, + @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { + + PagedResponse response = tagService.getAllTags(page, size); + + return new ResponseEntity< >(response, HttpStatus.OK); + } + + @PostMapping + @PreAuthorize("hasRole('USER')") + public ResponseEntity addTag(@Valid @RequestBody Tag tag, @CurrentUser UserPrincipal currentUser) { + Tag newTag = tagService.addTag(tag, currentUser); + + return new ResponseEntity< >(newTag, HttpStatus.CREATED); + } + + @GetMapping("/{id}") + public ResponseEntity getTag(@PathVariable(name = "id") Long id) { + Tag tag = tagService.getTag(id); + + return new ResponseEntity< >(tag, HttpStatus.OK); + } + + @PutMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") + public ResponseEntity updateTag(@PathVariable(name = "id") Long id, @Valid @RequestBody Tag tag, @CurrentUser UserPrincipal currentUser) { + + Tag updatedTag = tagService.updateTag(id, tag, currentUser); + + return new ResponseEntity< >(updatedTag, HttpStatus.OK); + } + + @DeleteMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") + public ResponseEntity deleteTag(@PathVariable(name = "id") Long id, @CurrentUser UserPrincipal currentUser) { + ApiResponse apiResponse = tagService.deleteTag(id, currentUser); + + return new ResponseEntity< >(apiResponse, HttpStatus.OK); + } + +} diff --git a/src/main/java/com/sopromadze/blogapi/controller/TodoController.java b/src/main/java/com/sopromadze/blogapi/controller/TodoController.java index 0605ae63..aeb57789 100644 --- a/src/main/java/com/sopromadze/blogapi/controller/TodoController.java +++ b/src/main/java/com/sopromadze/blogapi/controller/TodoController.java @@ -1,72 +1,95 @@ package com.sopromadze.blogapi.controller; -import com.sopromadze.blogapi.model.todo.Todo; +import com.sopromadze.blogapi.model.Todo; +import com.sopromadze.blogapi.payload.ApiResponse; import com.sopromadze.blogapi.payload.PagedResponse; import com.sopromadze.blogapi.security.CurrentUser; import com.sopromadze.blogapi.security.UserPrincipal; import com.sopromadze.blogapi.service.TodoService; -import com.sopromadze.blogapi.util.AppConstants; +import com.sopromadze.blogapi.utils.AppConstants; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; -import java.util.Optional; @RestController @RequestMapping("/api/todos") public class TodoController { - private final TodoService todoService; - - @Autowired - public TodoController(TodoService todoService) { - this.todoService = todoService; - } - - @GetMapping - @PreAuthorize("hasRole('USER')") - public PagedResponse getAllTodos( - @CurrentUser UserPrincipal currentUser, - @RequestParam(value = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, - @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size){ - return todoService.getAllTodos(currentUser, page, size); - } - - @PostMapping - @PreAuthorize("hasRole('USER')") - public ResponseEntity addTodo(@Valid @RequestBody Todo todo, @CurrentUser UserPrincipal currentUser){ - return todoService.addTodo(todo, currentUser); - } - - @GetMapping("/{id}") - @PreAuthorize("hasRole('USER')") - public ResponseEntity getTodo(@PathVariable(value = "id") Long id, @CurrentUser UserPrincipal currentUser){ - return todoService.getTodo(id, currentUser); - } - - @PutMapping("/{id}") - @PreAuthorize("hasRole('USER')") - public ResponseEntity updateTodo(@PathVariable(value = "id") Long id, @Valid @RequestBody Todo newTodo, @CurrentUser UserPrincipal currentUser){ - return todoService.updateTodo(id, newTodo, currentUser); - } - - @DeleteMapping("/{id}") - @PreAuthorize("hasRole('USER')") - public ResponseEntity deleteTodo(@PathVariable(value = "id") Long id, @CurrentUser UserPrincipal currentUser){ - return todoService.deleteTodo(id, currentUser); - } - - @PutMapping("/{id}/complete") - @PreAuthorize("hasRole('USER')") - public ResponseEntity completeTodo(@PathVariable(value = "id") Long id, @CurrentUser UserPrincipal currentUser){ - return todoService.completeTodo(id, currentUser); - } - - @PutMapping("/{id}/unComplete") - @PreAuthorize("hasRole('USER')") - public ResponseEntity unCompleteTodo(@PathVariable(value = "id") Long id, @CurrentUser UserPrincipal currentUser){ - return todoService.unCompleteTodo(id, currentUser); - } + @Autowired + private TodoService todoService; + + @GetMapping + @PreAuthorize("hasRole('USER')") + public ResponseEntity> getAllTodos( + @CurrentUser UserPrincipal currentUser, + @RequestParam(value = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, + @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { + + PagedResponse response = todoService.getAllTodos(currentUser, page, size); + + return new ResponseEntity< >(response, HttpStatus.OK); + } + + @PostMapping + @PreAuthorize("hasRole('USER')") + public ResponseEntity addTodo(@Valid @RequestBody Todo todo, @CurrentUser UserPrincipal currentUser) { + Todo newTodo = todoService.addTodo(todo, currentUser); + + return new ResponseEntity< >(newTodo, HttpStatus.CREATED); + } + + @GetMapping("/{id}") + @PreAuthorize("hasRole('USER')") + public ResponseEntity getTodo(@PathVariable(value = "id") Long id, @CurrentUser UserPrincipal currentUser) { + Todo todo = todoService.getTodo(id, currentUser); + + return new ResponseEntity< >(todo, HttpStatus.OK); + } + + @PutMapping("/{id}") + @PreAuthorize("hasRole('USER')") + public ResponseEntity updateTodo(@PathVariable(value = "id") Long id, @Valid @RequestBody Todo newTodo, + @CurrentUser UserPrincipal currentUser) { + Todo updatedTodo = todoService.updateTodo(id, newTodo, currentUser); + + return new ResponseEntity< >(updatedTodo, HttpStatus.OK); + } + + @DeleteMapping("/{id}") + @PreAuthorize("hasRole('USER')") + public ResponseEntity deleteTodo(@PathVariable(value = "id") Long id, @CurrentUser UserPrincipal currentUser) { + ApiResponse apiResponse = todoService.deleteTodo(id, currentUser); + + return new ResponseEntity<>(apiResponse, HttpStatus.OK); + } + + @PutMapping("/{id}/complete") + @PreAuthorize("hasRole('USER')") + public ResponseEntity completeTodo(@PathVariable(value = "id") Long id, @CurrentUser UserPrincipal currentUser) { + + Todo todo = todoService.completeTodo(id, currentUser); + + return new ResponseEntity< >(todo, HttpStatus.OK); + } + + @PutMapping("/{id}/unComplete") + @PreAuthorize("hasRole('USER')") + public ResponseEntity unCompleteTodo(@PathVariable(value = "id") Long id, @CurrentUser UserPrincipal currentUser) { + + Todo todo = todoService.unCompleteTodo(id, currentUser); + + return new ResponseEntity< >(todo, HttpStatus.OK); + } } diff --git a/src/main/java/com/sopromadze/blogapi/controller/UserController.java b/src/main/java/com/sopromadze/blogapi/controller/UserController.java index 1fed5c3f..f6b717e2 100644 --- a/src/main/java/com/sopromadze/blogapi/controller/UserController.java +++ b/src/main/java/com/sopromadze/blogapi/controller/UserController.java @@ -1,108 +1,144 @@ package com.sopromadze.blogapi.controller; -import com.sopromadze.blogapi.model.album.Album; -import com.sopromadze.blogapi.model.post.Post; +import com.sopromadze.blogapi.model.Album; +import com.sopromadze.blogapi.model.Post; import com.sopromadze.blogapi.model.user.User; -import com.sopromadze.blogapi.payload.*; +import com.sopromadze.blogapi.payload.ApiResponse; +import com.sopromadze.blogapi.payload.InfoRequest; +import com.sopromadze.blogapi.payload.PagedResponse; +import com.sopromadze.blogapi.payload.UserIdentityAvailability; +import com.sopromadze.blogapi.payload.UserProfile; +import com.sopromadze.blogapi.payload.UserSummary; import com.sopromadze.blogapi.security.CurrentUser; import com.sopromadze.blogapi.security.UserPrincipal; import com.sopromadze.blogapi.service.AlbumService; import com.sopromadze.blogapi.service.PostService; import com.sopromadze.blogapi.service.UserService; -import com.sopromadze.blogapi.util.AppConstants; +import com.sopromadze.blogapi.utils.AppConstants; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; @RestController @RequestMapping("/api/users") public class UserController { - private final UserService userService; - private final PostService postService; - private final AlbumService albumService; - - @Autowired - public UserController(UserService userService, PostService postService, AlbumService albumService) { - this.userService = userService; - this.postService = postService; - this.albumService = albumService; - } - - @GetMapping("/me") - @PreAuthorize("hasRole('USER')") - public UserSummary getCurrentUser(@CurrentUser UserPrincipal currentUser){ - return userService.getCurrentUser(currentUser); - } - - @GetMapping("/checkUsernameAvailability") - public UserIdentityAvailability checkUsernameAvailability(@RequestParam(value = "username") String username){ - return userService.checkUsernameAvailability(username); - } - - @GetMapping("/checkEmailAvailability") - public UserIdentityAvailability checkEmailAvailability(@RequestParam(value = "email") String email){ - return userService.checkEmailAvailability(email); - } - - @GetMapping("/{username}/profile") - public UserProfile getUSerProfile(@PathVariable(value = "username") String username){ - return userService.getUserProfile(username); - } - - @GetMapping("/{username}/posts") - public PagedResponse getPostsCreatedBy( - @PathVariable(value = "username") String username, - @RequestParam(value = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, - @RequestParam(value = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size){ - return postService.getPostsCreatedBy(username, page, size); - } - - @GetMapping("/{username}/albums") - public PagedResponse getUserAlbums( - @PathVariable(name = "username") String username, - @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, - @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size){ - - return albumService.getUserAlbums(username, page, size); - } - - @PostMapping - @PreAuthorize("hasRole('ADMIN')") - public ResponseEntity addUser(@Valid @RequestBody User user){ - return userService.addUser(user); - } - - @PutMapping("/{username}") - @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") - public ResponseEntity updateUser(@Valid @RequestBody User newUser, @PathVariable(value = "username") String username, @CurrentUser UserPrincipal currentUser){ - return userService.updateUser(newUser, username, currentUser); - } - - @DeleteMapping("/{username}") - @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") - public ResponseEntity deleteUser(@PathVariable(value = "username") String username, @CurrentUser UserPrincipal currentUser){ - return userService.deleteUser(username, currentUser); - } - - @PutMapping("/{username}/giveAdmin") - @PreAuthorize("hasRole('ADMIN')") - public ResponseEntity giveAdmin(@PathVariable(name = "username") String username){ - return userService.giveAdmin(username); - } - - @PutMapping("/{username}/takeAdmin") - @PreAuthorize("hasRole('ADMIN')") - public ResponseEntity takeAdmin(@PathVariable(name = "username") String username){ - return userService.takeAdmin(username); - } - - @PutMapping("/setOrUpdateInfo") - @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") - public ResponseEntity setAddress(@CurrentUser UserPrincipal currentUser, @Valid @RequestBody InfoRequest infoRequest){ - return userService.setOrUpdateInfo(currentUser, infoRequest); - } + @Autowired + private UserService userService; + + @Autowired + private PostService postService; + + @Autowired + private AlbumService albumService; + + @GetMapping("/me") + @PreAuthorize("hasRole('USER')") + public ResponseEntity getCurrentUser(@CurrentUser UserPrincipal currentUser) { + UserSummary userSummary = userService.getCurrentUser(currentUser); + + return new ResponseEntity< >(userSummary, HttpStatus.OK); + } + + @GetMapping("/checkUsernameAvailability") + public ResponseEntity checkUsernameAvailability(@RequestParam(value = "username") String username) { + UserIdentityAvailability userIdentityAvailability = userService.checkUsernameAvailability(username); + + return new ResponseEntity< >(userIdentityAvailability, HttpStatus.OK); + } + + @GetMapping("/checkEmailAvailability") + public ResponseEntity checkEmailAvailability(@RequestParam(value = "email") String email) { + UserIdentityAvailability userIdentityAvailability = userService.checkEmailAvailability(email); + return new ResponseEntity< >(userIdentityAvailability, HttpStatus.OK); + } + + @GetMapping("/{username}/profile") + public ResponseEntity getUSerProfile(@PathVariable(value = "username") String username) { + UserProfile userProfile = userService.getUserProfile(username); + + return new ResponseEntity< >(userProfile, HttpStatus.OK); + } + + @GetMapping("/{username}/posts") + public ResponseEntity> getPostsCreatedBy(@PathVariable(value = "username") String username, + @RequestParam(value = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, + @RequestParam(value = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { + PagedResponse response = postService.getPostsByCreatedBy(username, page, size); + + return new ResponseEntity< >(response, HttpStatus.OK); + } + + @GetMapping("/{username}/albums") + public ResponseEntity> getUserAlbums(@PathVariable(name = "username") String username, + @RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page, + @RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) { + + PagedResponse response = albumService.getUserAlbums(username, page, size); + + return new ResponseEntity< >(response, HttpStatus.OK); + } + + @PostMapping + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity addUser(@Valid @RequestBody User user) { + User newUser = userService.addUser(user); + + return new ResponseEntity< >(newUser, HttpStatus.CREATED); + } + + @PutMapping("/{username}") + @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") + public ResponseEntity updateUser(@Valid @RequestBody User newUser, + @PathVariable(value = "username") String username, @CurrentUser UserPrincipal currentUser) { + User updatedUSer = userService.updateUser(newUser, username, currentUser); + + return new ResponseEntity< >(updatedUSer, HttpStatus.CREATED); + } + + @DeleteMapping("/{username}") + @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") + public ResponseEntity deleteUser(@PathVariable(value = "username") String username, + @CurrentUser UserPrincipal currentUser) { + ApiResponse apiResponse = userService.deleteUser(username, currentUser); + + return new ResponseEntity< >(apiResponse, HttpStatus.OK); + } + + @PutMapping("/{username}/giveAdmin") + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity giveAdmin(@PathVariable(name = "username") String username) { + ApiResponse apiResponse = userService.giveAdmin(username); + + return new ResponseEntity< >(apiResponse, HttpStatus.OK); + } + + @PutMapping("/{username}/takeAdmin") + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity takeAdmin(@PathVariable(name = "username") String username) { + ApiResponse apiResponse = userService.removeAdmin(username); + + return new ResponseEntity< >(apiResponse, HttpStatus.OK); + } + + @PutMapping("/setOrUpdateInfo") + @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") + public ResponseEntity setAddress(@CurrentUser UserPrincipal currentUser, + @Valid @RequestBody InfoRequest infoRequest) { + UserProfile userProfile = userService.setOrUpdateInfo(currentUser, infoRequest); + + return new ResponseEntity< >(userProfile, HttpStatus.OK); + } } diff --git a/src/main/java/com/sopromadze/blogapi/exception/AccessDeniedException.java b/src/main/java/com/sopromadze/blogapi/exception/AccessDeniedException.java new file mode 100644 index 00000000..ba5391e1 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/exception/AccessDeniedException.java @@ -0,0 +1,45 @@ +package com.sopromadze.blogapi.exception; + +import com.sopromadze.blogapi.payload.ApiResponse; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.UNAUTHORIZED) +public class AccessDeniedException extends RuntimeException { + private static final long serialVersionUID = 1L; + + private ApiResponse apiResponse; + + private String message; + + public AccessDeniedException(ApiResponse apiResponse) { + super(); + this.apiResponse = apiResponse; + } + + public AccessDeniedException(String message) { + super(message); + this.message = message; + } + + public AccessDeniedException(String message, Throwable cause) { + super(message, cause); + } + + public ApiResponse getApiResponse() { + return apiResponse; + } + + public void setApiResponse(ApiResponse apiResponse) { + this.apiResponse = apiResponse; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/src/main/java/com/sopromadze/blogapi/exception/AppException.java b/src/main/java/com/sopromadze/blogapi/exception/AppException.java index 2d98e737..a213876d 100644 --- a/src/main/java/com/sopromadze/blogapi/exception/AppException.java +++ b/src/main/java/com/sopromadze/blogapi/exception/AppException.java @@ -5,11 +5,13 @@ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public class AppException extends RuntimeException { - public AppException(String message) { - super(message); - } + private static final long serialVersionUID = 1L; - public AppException(String message, Throwable cause) { - super(message, cause); - } + public AppException(String message) { + super(message); + } + + public AppException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/src/main/java/com/sopromadze/blogapi/exception/BadRequestException.java b/src/main/java/com/sopromadze/blogapi/exception/BadRequestException.java index e638a1da..a35149fe 100644 --- a/src/main/java/com/sopromadze/blogapi/exception/BadRequestException.java +++ b/src/main/java/com/sopromadze/blogapi/exception/BadRequestException.java @@ -1,15 +1,29 @@ package com.sopromadze.blogapi.exception; +import com.sopromadze.blogapi.payload.ApiResponse; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(HttpStatus.BAD_REQUEST) -public class BadRequestException extends RuntimeException{ - public BadRequestException(String message) { - super(message); - } - - public BadRequestException(String message, Throwable cause) { - super(message, cause); - } +public class BadRequestException extends RuntimeException { + private static final long serialVersionUID = 1L; + + private ApiResponse apiResponse; + + public BadRequestException(ApiResponse apiResponse) { + super(); + this.apiResponse = apiResponse; + } + + public BadRequestException(String message) { + super(message); + } + + public BadRequestException(String message, Throwable cause) { + super(message, cause); + } + + public ApiResponse getApiResponse() { + return apiResponse; + } } diff --git a/src/main/java/com/sopromadze/blogapi/exception/BlogapiException.java b/src/main/java/com/sopromadze/blogapi/exception/BlogapiException.java new file mode 100644 index 00000000..4da4fd63 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/exception/BlogapiException.java @@ -0,0 +1,32 @@ +package com.sopromadze.blogapi.exception; + +import org.springframework.http.HttpStatus; + +public class BlogapiException extends RuntimeException { + + private static final long serialVersionUID = -6593330219878485669L; + + private final HttpStatus status; + private final String message; + + public BlogapiException(HttpStatus status, String message) { + super(); + this.status = status; + this.message = message; + } + + public BlogapiException(HttpStatus status, String message, Throwable exception) { + super(exception); + this.status = status; + this.message = message; + } + + public HttpStatus getStatus() { + return status; + } + + public String getMessage() { + return message; + } + +} diff --git a/src/main/java/com/sopromadze/blogapi/exception/ResourceNotFoundException.java b/src/main/java/com/sopromadze/blogapi/exception/ResourceNotFoundException.java index de5cc830..4dd90e07 100644 --- a/src/main/java/com/sopromadze/blogapi/exception/ResourceNotFoundException.java +++ b/src/main/java/com/sopromadze/blogapi/exception/ResourceNotFoundException.java @@ -1,30 +1,45 @@ package com.sopromadze.blogapi.exception; +import com.sopromadze.blogapi.payload.ApiResponse; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(value = HttpStatus.NOT_FOUND) -public class ResourceNotFoundException extends RuntimeException{ - private String resourceName; - private String fieldName; - private Object fieldValue; - - public ResourceNotFoundException(String resourceName, String fieldName, Object fieldValue) { - super(String.format("%s not found with %s: '%s'", resourceName, fieldName, fieldValue)); - this.resourceName = resourceName; - this.fieldName = fieldName; - this.fieldValue = fieldValue; - } - - public String getResourceName() { - return resourceName; - } - - public String getFieldName() { - return fieldName; - } - - public Object getFieldValue() { - return fieldValue; - } +public class ResourceNotFoundException extends RuntimeException { + private static final long serialVersionUID = 1L; + + private transient ApiResponse apiResponse; + + private String resourceName; + private String fieldName; + private Object fieldValue; + + public ResourceNotFoundException(String resourceName, String fieldName, Object fieldValue) { + super(); + this.resourceName = resourceName; + this.fieldName = fieldName; + this.fieldValue = fieldValue; + } + + public String getResourceName() { + return resourceName; + } + + public String getFieldName() { + return fieldName; + } + + public Object getFieldValue() { + return fieldValue; + } + + public ApiResponse getApiResponse() { + return apiResponse; + } + + private void setApiResponse() { + String message = String.format("%s not found with %s: '%s'", resourceName, fieldName, fieldValue); + + apiResponse = new ApiResponse(Boolean.FALSE, message); + } } diff --git a/src/main/java/com/sopromadze/blogapi/exception/ResponseEntityErrorException.java b/src/main/java/com/sopromadze/blogapi/exception/ResponseEntityErrorException.java new file mode 100644 index 00000000..f5aeaaed --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/exception/ResponseEntityErrorException.java @@ -0,0 +1,18 @@ +package com.sopromadze.blogapi.exception; + +import com.sopromadze.blogapi.payload.ApiResponse; +import org.springframework.http.ResponseEntity; + +public class ResponseEntityErrorException extends RuntimeException { + private static final long serialVersionUID = -3156815846745801694L; + + private transient ResponseEntity apiResponse; + + public ResponseEntityErrorException(ResponseEntity apiResponse) { + this.apiResponse = apiResponse; + } + + public ResponseEntity getApiResponse() { + return apiResponse; + } +} diff --git a/src/main/java/com/sopromadze/blogapi/exception/RestControllerExceptionHandler.java b/src/main/java/com/sopromadze/blogapi/exception/RestControllerExceptionHandler.java index ee0634d5..a9372e45 100644 --- a/src/main/java/com/sopromadze/blogapi/exception/RestControllerExceptionHandler.java +++ b/src/main/java/com/sopromadze/blogapi/exception/RestControllerExceptionHandler.java @@ -1,10 +1,10 @@ package com.sopromadze.blogapi.exception; +import com.sopromadze.blogapi.payload.ApiResponse; import com.sopromadze.blogapi.payload.ExceptionResponse; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -21,45 +21,98 @@ @ControllerAdvice public class RestControllerExceptionHandler { - @ExceptionHandler({MethodArgumentNotValidException.class}) - @ResponseBody - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ResponseEntity resolveException(MethodArgumentNotValidException ex){ - List fieldErrors = ex.getBindingResult().getFieldErrors(); - List messages = new ArrayList<>(); - for (FieldError error : fieldErrors){ - messages.add(error.getField() + " - " + error.getDefaultMessage()); - } - return new ResponseEntity<>(new ExceptionResponse(messages, HttpStatus.BAD_REQUEST.getReasonPhrase(), HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler({MethodArgumentTypeMismatchException.class}) - @ResponseBody - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ResponseEntity resolveException(MethodArgumentTypeMismatchException ex){ - String message = "Parameter '" + ex.getParameter().getParameterName() + "' must be '" + Objects.requireNonNull(ex.getRequiredType()).getSimpleName() + "'"; - List messages = new ArrayList<>(); - messages.add(message); - return new ResponseEntity<>(new ExceptionResponse(messages, HttpStatus.BAD_REQUEST.getReasonPhrase(), HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler({HttpRequestMethodNotSupportedException.class}) - @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) - @ResponseBody - public ResponseEntity resolveException(HttpRequestMethodNotSupportedException ex){ - String message = "Request method '" + ex.getMethod() +"' not supported. List of all supported methods - " + ex.getSupportedHttpMethods(); - List messages = new ArrayList<>(); - messages.add(message); - return new ResponseEntity<>(new ExceptionResponse(messages, HttpStatus.METHOD_NOT_ALLOWED.getReasonPhrase(), HttpStatus.METHOD_NOT_ALLOWED.value()), HttpStatus.METHOD_NOT_ALLOWED); - } - - @ExceptionHandler({HttpMessageNotReadableException.class}) - @ResponseBody - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ResponseEntity resolveException(HttpMessageNotReadableException ex){ - String message = "Please provide Request Body in valid JSON format"; - List messages = new ArrayList<>(); - messages.add(message); - return new ResponseEntity<>(new ExceptionResponse(messages, HttpStatus.BAD_REQUEST.getReasonPhrase(), HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST); - } + public ResponseEntity resolveException(BlogapiException exception) { + String message = exception.getMessage(); + HttpStatus status = exception.getStatus(); + + ApiResponse apiResponse = new ApiResponse(); + + apiResponse.setSuccess(Boolean.FALSE); + apiResponse.setMessage(message); + + return new ResponseEntity<>(apiResponse, status); + } + + @ExceptionHandler(UnauthorizedException.class) + @ResponseBody + @ResponseStatus(code = HttpStatus.UNAUTHORIZED) + public ResponseEntity resolveException(UnauthorizedException exception) { + + ApiResponse apiResponse = exception.getApiResponse(); + + return new ResponseEntity<>(apiResponse, HttpStatus.UNAUTHORIZED); + } + + @ExceptionHandler(BadRequestException.class) + @ResponseBody + public ResponseEntity resolveException(BadRequestException exception) { + ApiResponse apiResponse = exception.getApiResponse(); + + return new ResponseEntity<>(apiResponse, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(ResourceNotFoundException.class) + @ResponseBody + public ResponseEntity resolveException(ResourceNotFoundException exception) { + ApiResponse apiResponse = exception.getApiResponse(); + + return new ResponseEntity<>(apiResponse, HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(AccessDeniedException.class) + @ResponseBody + public ResponseEntity resolveException(AccessDeniedException exception) { + ApiResponse apiResponse = exception.getApiResponse(); + + return new ResponseEntity< >(apiResponse, HttpStatus.FORBIDDEN); + } + + @ExceptionHandler({ MethodArgumentNotValidException.class }) + @ResponseBody + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ResponseEntity resolveException(MethodArgumentNotValidException ex) { + List fieldErrors = ex.getBindingResult().getFieldErrors(); + List messages = new ArrayList<>(fieldErrors.size()); + for (FieldError error : fieldErrors) { + messages.add(error.getField() + " - " + error.getDefaultMessage()); + } + return new ResponseEntity<>(new ExceptionResponse(messages, HttpStatus.BAD_REQUEST.getReasonPhrase(), + HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler({ MethodArgumentTypeMismatchException.class }) + @ResponseBody + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ResponseEntity resolveException(MethodArgumentTypeMismatchException ex) { + String message = "Parameter '" + ex.getParameter().getParameterName() + "' must be '" + + Objects.requireNonNull(ex.getRequiredType()).getSimpleName() + "'"; + List messages = new ArrayList<>(1); + messages.add(message); + return new ResponseEntity<>(new ExceptionResponse(messages, HttpStatus.BAD_REQUEST.getReasonPhrase(), + HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler({ HttpRequestMethodNotSupportedException.class }) + @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) + @ResponseBody + public ResponseEntity resolveException(HttpRequestMethodNotSupportedException ex) { + String message = "Request method '" + ex.getMethod() + "' not supported. List of all supported methods - " + + ex.getSupportedHttpMethods(); + List messages = new ArrayList<>(1); + messages.add(message); + + return new ResponseEntity<>(new ExceptionResponse(messages, HttpStatus.METHOD_NOT_ALLOWED.getReasonPhrase(), + HttpStatus.METHOD_NOT_ALLOWED.value()), HttpStatus.METHOD_NOT_ALLOWED); + } + + @ExceptionHandler({ HttpMessageNotReadableException.class }) + @ResponseBody + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ResponseEntity resolveException(HttpMessageNotReadableException ex) { + String message = "Please provide Request Body in valid JSON format"; + List messages = new ArrayList<>(1); + messages.add(message); + return new ResponseEntity<>(new ExceptionResponse(messages, HttpStatus.BAD_REQUEST.getReasonPhrase(), + HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST); + } } diff --git a/src/main/java/com/sopromadze/blogapi/exception/UnauthorizedException.java b/src/main/java/com/sopromadze/blogapi/exception/UnauthorizedException.java new file mode 100644 index 00000000..1760c66b --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/exception/UnauthorizedException.java @@ -0,0 +1,45 @@ +package com.sopromadze.blogapi.exception; + +import com.sopromadze.blogapi.payload.ApiResponse; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.UNAUTHORIZED) +public class UnauthorizedException extends RuntimeException { + private static final long serialVersionUID = 1L; + + private ApiResponse apiResponse; + + private String message; + + public UnauthorizedException(ApiResponse apiResponse) { + super(); + this.apiResponse = apiResponse; + } + + public UnauthorizedException(String message) { + super(message); + this.message = message; + } + + public UnauthorizedException(String message, Throwable cause) { + super(message, cause); + } + + public ApiResponse getApiResponse() { + return apiResponse; + } + + public void setApiResponse(ApiResponse apiResponse) { + this.apiResponse = apiResponse; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/src/main/java/com/sopromadze/blogapi/model/Album.java b/src/main/java/com/sopromadze/blogapi/model/Album.java new file mode 100644 index 00000000..311d5991 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/model/Album.java @@ -0,0 +1,64 @@ +package com.sopromadze.blogapi.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.sopromadze.blogapi.model.audit.UserDateAudit; +import com.sopromadze.blogapi.model.user.User; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import javax.validation.constraints.NotBlank; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Entity +@Data +@Table(name = "albums", uniqueConstraints = { @UniqueConstraint(columnNames = { "title" }) }) +public class Album extends UserDateAudit { + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotBlank + @Column(name = "title") + private String title; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @OneToMany(mappedBy = "album", cascade = CascadeType.ALL, orphanRemoval = true) + private List photo; + + @JsonIgnore + public User getUser() { + return user; + } + + public List getPhoto() { + return this.photo == null ? null : new ArrayList<>(this.photo); + } + + public void setPhoto(List photo) { + if (photo == null) { + this.photo = null; + } else { + this.photo = Collections.unmodifiableList(photo); + } + } +} diff --git a/src/main/java/com/sopromadze/blogapi/model/Category.java b/src/main/java/com/sopromadze/blogapi/model/Category.java new file mode 100644 index 00000000..102c6e39 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/model/Category.java @@ -0,0 +1,58 @@ +package com.sopromadze.blogapi.model; + +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import com.sopromadze.blogapi.model.audit.UserDateAudit; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Entity +@Data +@NoArgsConstructor +@Table(name = "categories") +@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") +public class Category extends UserDateAudit { + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "name") + private String name; + + @OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true) + private List posts; + + public Category(String name) { + super(); + this.name = name; + } + + public List getPosts() { + return this.posts == null ? null : new ArrayList<>(this.posts); + } + + public void setPosts(List posts) { + if (posts == null) { + this.posts = null; + } else { + this.posts = Collections.unmodifiableList(posts); + } + } + +} diff --git a/src/main/java/com/sopromadze/blogapi/model/comment/Comment.java b/src/main/java/com/sopromadze/blogapi/model/Comment.java similarity index 61% rename from src/main/java/com/sopromadze/blogapi/model/comment/Comment.java rename to src/main/java/com/sopromadze/blogapi/model/Comment.java index 2080b830..d9d21972 100644 --- a/src/main/java/com/sopromadze/blogapi/model/comment/Comment.java +++ b/src/main/java/com/sopromadze/blogapi/model/Comment.java @@ -1,18 +1,33 @@ -package com.sopromadze.blogapi.model.comment; +package com.sopromadze.blogapi.model; import com.fasterxml.jackson.annotation.JsonIgnore; import com.sopromadze.blogapi.model.audit.UserDateAudit; -import com.sopromadze.blogapi.model.post.Post; import com.sopromadze.blogapi.model.user.User; - -import javax.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; +@EqualsAndHashCode(callSuper = true) @Entity +@Data +@NoArgsConstructor @Table(name = "comments") public class Comment extends UserDateAudit { + private static final long serialVersionUID = 1L; + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -41,61 +56,17 @@ public class Comment extends UserDateAudit { @JoinColumn(name = "user_id") private User user; - public Comment(){ - - } - public Comment(@NotBlank @Size(min = 10, message = "Comment body must be minimum 10 characters") String body) { this.body = body; } - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getBody() { - return body; - } - - public void setBody(String body) { - this.body = body; - } - @JsonIgnore public Post getPost() { return post; } - public void setPost(Post post) { - this.post = post; - } - @JsonIgnore public User getUser() { return user; } - - public void setUser(User user) { - this.user = user; - } } diff --git a/src/main/java/com/sopromadze/blogapi/model/Photo.java b/src/main/java/com/sopromadze/blogapi/model/Photo.java new file mode 100644 index 00000000..7aaa1d2a --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/model/Photo.java @@ -0,0 +1,60 @@ +package com.sopromadze.blogapi.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.sopromadze.blogapi.model.audit.UserDateAudit; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import javax.validation.constraints.NotBlank; + +@EqualsAndHashCode(callSuper = true) +@Entity +@Data +@NoArgsConstructor +@Table(name = "photos", uniqueConstraints = { @UniqueConstraint(columnNames = { "title" }) }) +public class Photo extends UserDateAudit { + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotBlank + @Column(name = "title") + private String title; + + @NotBlank + @Column(name = "url") + private String url; + + @NotBlank + @Column(name = "thumbnail_url") + private String thumbnailUrl; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "album_id") + private Album album; + + public Photo(@NotBlank String title, @NotBlank String url, @NotBlank String thumbnailUrl, Album album) { + this.title = title; + this.url = url; + this.thumbnailUrl = thumbnailUrl; + this.album = album; + } + + @JsonIgnore + public Album getAlbum() { + return album; + } +} diff --git a/src/main/java/com/sopromadze/blogapi/model/Post.java b/src/main/java/com/sopromadze/blogapi/model/Post.java new file mode 100644 index 00000000..6b97dbcf --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/model/Post.java @@ -0,0 +1,95 @@ +package com.sopromadze.blogapi.model; + +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import com.sopromadze.blogapi.model.audit.UserDateAudit; +import com.sopromadze.blogapi.model.user.User; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Entity +@Data +@Table(name = "posts", uniqueConstraints = { @UniqueConstraint(columnNames = { "title" }) }) +@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") +public class Post extends UserDateAudit { + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "title") + private String title; + + @Column(name = "body") + private String body; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "category_id") + private Category category; + + @JsonIgnore + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) + private List comments; + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable(name = "post_tag", joinColumns = @JoinColumn(name = "post_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "tag_id", referencedColumnName = "id")) + private List tags; + + @JsonIgnore + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public List getComments() { + return comments == null ? null : new ArrayList<>(comments); + } + + public void setComments(List comments) { + if (comments == null) { + this.comments = null; + } else { + this.comments = Collections.unmodifiableList(comments); + } + } + + public List getTags() { + return tags == null ? null : new ArrayList<>(tags); + } + + public void setTags(List tags) { + if (tags == null) { + this.tags = null; + } else { + this.tags = Collections.unmodifiableList(tags); + } + } +} diff --git a/src/main/java/com/sopromadze/blogapi/model/Tag.java b/src/main/java/com/sopromadze/blogapi/model/Tag.java new file mode 100644 index 00000000..a3cbd193 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/model/Tag.java @@ -0,0 +1,63 @@ +package com.sopromadze.blogapi.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.sopromadze.blogapi.model.audit.UserDateAudit; +import com.sopromadze.blogapi.model.Post; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.Table; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Entity +@Data +@NoArgsConstructor +@Table(name = "tags") +//@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") +public class Tag extends UserDateAudit { + + private static final long serialVersionUID = -5298707266367331514L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "name") + private String name; + + @JsonIgnore + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "post_tag", joinColumns = @JoinColumn(name = "tag_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "post_id", referencedColumnName = "id")) + private List posts; + + public Tag(String name) { + super(); + this.name = name; + } + + public List getPosts() { + return posts == null ? null : new ArrayList<>(posts); + } + + public void setPosts(List posts) { + if (posts == null) { + this.posts = null; + } else { + this.posts = Collections.unmodifiableList(posts); + } + } + +} diff --git a/src/main/java/com/sopromadze/blogapi/model/Todo.java b/src/main/java/com/sopromadze/blogapi/model/Todo.java new file mode 100644 index 00000000..7fed93d1 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/model/Todo.java @@ -0,0 +1,48 @@ +package com.sopromadze.blogapi.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.sopromadze.blogapi.model.audit.UserDateAudit; +import com.sopromadze.blogapi.model.user.User; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import javax.validation.constraints.NotBlank; + +@EqualsAndHashCode(callSuper = true) +@Entity +@Data +@Table(name = "todos", uniqueConstraints = { @UniqueConstraint(columnNames = { "title" }) }) +public class Todo extends UserDateAudit { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotBlank + @Column(name = "title") + private String title; + + @Column(name = "completed") + private Boolean completed; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @JsonIgnore + public User getUser() { + return user; + } +} diff --git a/src/main/java/com/sopromadze/blogapi/model/album/Album.java b/src/main/java/com/sopromadze/blogapi/model/album/Album.java deleted file mode 100644 index 23319d41..00000000 --- a/src/main/java/com/sopromadze/blogapi/model/album/Album.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.sopromadze.blogapi.model.album; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.sopromadze.blogapi.model.audit.UserDateAudit; -import com.sopromadze.blogapi.model.photo.Photo; -import com.sopromadze.blogapi.model.user.User; - -import javax.persistence.*; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.Size; -import java.util.List; - -@Entity -@Table(name = "albums", uniqueConstraints = { - @UniqueConstraint(columnNames = { - "title" - }) -}) -public class Album extends UserDateAudit { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @NotBlank - @Column(name = "title") - private String title; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private User user; - - @OneToMany(mappedBy = "album", cascade = CascadeType.ALL, orphanRemoval = true) - private List photo; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - @JsonIgnore - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } -} diff --git a/src/main/java/com/sopromadze/blogapi/model/audit/DateAudit.java b/src/main/java/com/sopromadze/blogapi/model/audit/DateAudit.java index 95ae07aa..c9f8abe7 100644 --- a/src/main/java/com/sopromadze/blogapi/model/audit/DateAudit.java +++ b/src/main/java/com/sopromadze/blogapi/model/audit/DateAudit.java @@ -1,6 +1,7 @@ package com.sopromadze.blogapi.model.audit; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @@ -12,34 +13,22 @@ import java.time.Instant; @MappedSuperclass +@Data @EntityListeners(AuditingEntityListener.class) @JsonIgnoreProperties( - value = {"createdAt", "updatedAt"}, - allowGetters = true + value = { "createdAt", "updatedAt" }, + allowGetters = true ) public abstract class DateAudit implements Serializable { - @CreatedDate - @Column(nullable = false, updatable = false) - private Instant createdAt; + private static final long serialVersionUID = 1L; - @LastModifiedDate - @Column(nullable = false) - private Instant updatedAt; + @CreatedDate + @Column(nullable = false, updatable = false) + private Instant createdAt; - public Instant getCreatedAt() { - return createdAt; - } + @LastModifiedDate + @Column(nullable = false) + private Instant updatedAt; - public void setCreatedAt(Instant createdAt) { - this.createdAt = createdAt; - } - - public Instant getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(Instant updatedAt) { - this.updatedAt = updatedAt; - } } diff --git a/src/main/java/com/sopromadze/blogapi/model/audit/UserDateAudit.java b/src/main/java/com/sopromadze/blogapi/model/audit/UserDateAudit.java index 525f15a1..10644bd5 100644 --- a/src/main/java/com/sopromadze/blogapi/model/audit/UserDateAudit.java +++ b/src/main/java/com/sopromadze/blogapi/model/audit/UserDateAudit.java @@ -1,38 +1,28 @@ package com.sopromadze.blogapi.model.audit; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import lombok.EqualsAndHashCode; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.LastModifiedBy; import javax.persistence.Column; import javax.persistence.MappedSuperclass; +@EqualsAndHashCode(callSuper = true) @MappedSuperclass +@Data @JsonIgnoreProperties( - value = {"createdBY", "updatedBy"}, - allowGetters = true + value = { "createdBY", "updatedBy" }, + allowGetters = true ) -public abstract class UserDateAudit extends DateAudit{ - @CreatedBy - @Column(updatable = false) - private Long createdBy; +public abstract class UserDateAudit extends DateAudit { + private static final long serialVersionUID = 1L; - @LastModifiedBy - private Long updatedBy; + @CreatedBy + @Column(updatable = false) + private Long createdBy; - public Long getCreatedBy() { - return createdBy; - } - - public void setCreatedBy(Long createdBy) { - this.createdBy = createdBy; - } - - public Long getUpdatedBy() { - return updatedBy; - } - - public void setUpdatedBy(Long updatedBy) { - this.updatedBy = updatedBy; - } + @LastModifiedBy + private Long updatedBy; } diff --git a/src/main/java/com/sopromadze/blogapi/model/photo/Photo.java b/src/main/java/com/sopromadze/blogapi/model/photo/Photo.java deleted file mode 100644 index 2e106891..00000000 --- a/src/main/java/com/sopromadze/blogapi/model/photo/Photo.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.sopromadze.blogapi.model.photo; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.sopromadze.blogapi.model.album.Album; -import com.sopromadze.blogapi.model.audit.UserDateAudit; - -import javax.persistence.*; -import javax.validation.constraints.NotBlank; - -@Entity -@Table(name = "photos", uniqueConstraints = { - @UniqueConstraint(columnNames = { - "title" - }) -}) -public class Photo extends UserDateAudit { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @NotBlank - @Column(name = "title") - private String title; - - @NotBlank - @Column(name = "url") - private String url; - - @NotBlank - @Column(name = "thumbnail_url") - private String thumbnailUrl; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "album_id") - private Album album; - - public Photo(){ - - } - - public Photo(@NotBlank String title, @NotBlank String url, @NotBlank String thumbnailUrl, Album album) { - this.title = title; - this.url = url; - this.thumbnailUrl = thumbnailUrl; - this.album = album; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getThumbnailUrl() { - return thumbnailUrl; - } - - public void setThumbnailUrl(String thumbnailUrl) { - this.thumbnailUrl = thumbnailUrl; - } - - @JsonIgnore - public Album getAlbum() { - return album; - } - - public void setAlbum(Album album) { - this.album = album; - } -} diff --git a/src/main/java/com/sopromadze/blogapi/model/post/Post.java b/src/main/java/com/sopromadze/blogapi/model/post/Post.java deleted file mode 100644 index 977b90f3..00000000 --- a/src/main/java/com/sopromadze/blogapi/model/post/Post.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.sopromadze.blogapi.model.post; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.sopromadze.blogapi.model.audit.UserDateAudit; -import com.sopromadze.blogapi.model.comment.Comment; -import com.sopromadze.blogapi.model.user.User; - -import javax.persistence.*; -import java.util.List; - -@Entity -@Table(name = "posts", uniqueConstraints = { - @UniqueConstraint(columnNames = { - "title" - }) -}) -public class Post extends UserDateAudit { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "title") - private String title; - - @Column(name = "body") - private String body; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private User user; - - @JsonIgnore - @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) - private List comments; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getBody() { - return body; - } - - public void setBody(String body) { - this.body = body; - } - - @JsonIgnore - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } - - public List getComments() { - return comments; - } - - public void setComments(List comments) { - this.comments = comments; - } -} diff --git a/src/main/java/com/sopromadze/blogapi/model/role/Role.java b/src/main/java/com/sopromadze/blogapi/model/role/Role.java index dd4b716d..00231ca9 100644 --- a/src/main/java/com/sopromadze/blogapi/model/role/Role.java +++ b/src/main/java/com/sopromadze/blogapi/model/role/Role.java @@ -1,42 +1,33 @@ package com.sopromadze.blogapi.model.role; +import lombok.Data; +import lombok.NoArgsConstructor; import org.hibernate.annotations.NaturalId; -import javax.persistence.*; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; @Entity +@Data +@NoArgsConstructor @Table(name = "roles") public class Role { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Enumerated(EnumType.STRING) - @NaturalId - @Column(name = "name") - private RoleName name; - - public Role(){ - - } - - public Role(RoleName name) { - this.name = name; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public RoleName getName() { - return name; - } - - public void setName(RoleName name) { - this.name = name; - } + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(EnumType.STRING) + @NaturalId + @Column(name = "name") + private RoleName name; + + public Role(RoleName name) { + this.name = name; + } } diff --git a/src/main/java/com/sopromadze/blogapi/model/role/RoleName.java b/src/main/java/com/sopromadze/blogapi/model/role/RoleName.java index ae05926c..f2064b2f 100644 --- a/src/main/java/com/sopromadze/blogapi/model/role/RoleName.java +++ b/src/main/java/com/sopromadze/blogapi/model/role/RoleName.java @@ -1,6 +1,6 @@ package com.sopromadze.blogapi.model.role; public enum RoleName { - ROLE_ADMIN, - ROLE_USER, + ROLE_ADMIN, + ROLE_USER, } diff --git a/src/main/java/com/sopromadze/blogapi/model/todo/Todo.java b/src/main/java/com/sopromadze/blogapi/model/todo/Todo.java deleted file mode 100644 index c23652fd..00000000 --- a/src/main/java/com/sopromadze/blogapi/model/todo/Todo.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.sopromadze.blogapi.model.todo; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.sopromadze.blogapi.model.audit.UserDateAudit; -import com.sopromadze.blogapi.model.user.User; - -import javax.persistence.*; -import javax.validation.constraints.NotBlank; - -@Entity -@Table(name = "todos", uniqueConstraints = { - @UniqueConstraint(columnNames = { - "title" - }) -}) -public class Todo extends UserDateAudit { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @NotBlank - @Column(name = "title") - private String title; - - @Column(name = "completed") - private Boolean completed; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private User user; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public Boolean getCompleted() { - return completed; - } - - public void setCompleted(Boolean completed) { - this.completed = completed; - } - - @JsonIgnore - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } -} diff --git a/src/main/java/com/sopromadze/blogapi/model/user/Address.java b/src/main/java/com/sopromadze/blogapi/model/user/Address.java index 8e97eca0..f686bbbf 100644 --- a/src/main/java/com/sopromadze/blogapi/model/user/Address.java +++ b/src/main/java/com/sopromadze/blogapi/model/user/Address.java @@ -2,142 +2,111 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.sopromadze.blogapi.model.audit.UserDateAudit; - -import javax.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; import java.time.Instant; +@EqualsAndHashCode(callSuper = true) @Entity +@Data +@NoArgsConstructor @Table(name = "address") public class Address extends UserDateAudit { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "street") - private String street; - - @Column(name = "suite") - private String suite; - - @Column(name = "city") - private String city; - - @Column(name = "zipcode") - private String zipcode; - - @OneToOne(cascade = CascadeType.ALL) - @JoinColumn(name = "geo_id") - private Geo geo; - - @OneToOne(mappedBy = "address") - private User user; - - public Address(){ - - } - - public Address(String street, String suite, String city, String zipcode, Geo geo) { - this.street = street; - this.suite = suite; - this.city = city; - this.zipcode = zipcode; - this.geo = geo; - } - - public String getStreet() { - return street; - } - - public void setStreet(String street) { - this.street = street; - } - - public String getSuite() { - return suite; - } - - public void setSuite(String suite) { - this.suite = suite; - } - - public String getCity() { - return city; - } - - public void setCity(String city) { - this.city = city; - } - - public String getZipcode() { - return zipcode; - } - - public void setZipcode(String zipcode) { - this.zipcode = zipcode; - } - - @JsonIgnore - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Geo getGeo() { - return geo; - } - - public void setGeo(Geo geo) { - this.geo = geo; - } - - @JsonIgnore - @Override - public Long getCreatedBy() { - return super.getCreatedBy(); - } - - @JsonIgnore - @Override - public void setCreatedBy(Long createdBy) { - super.setCreatedBy(createdBy); - } - - @JsonIgnore - @Override - public Long getUpdatedBy() { - return super.getUpdatedBy(); - } - - @JsonIgnore - @Override - public void setUpdatedBy(Long updatedBy) { - super.setUpdatedBy(updatedBy); - } - - @JsonIgnore - @Override - public Instant getCreatedAt() { - return super.getCreatedAt(); - } - - @JsonIgnore - @Override - public void setCreatedAt(Instant createdAt) { - super.setCreatedAt(createdAt); - } - - @JsonIgnore - @Override - public Instant getUpdatedAt() { - return super.getUpdatedAt(); - } - - @JsonIgnore - @Override - public void setUpdatedAt(Instant updatedAt) { - super.setUpdatedAt(updatedAt); - } + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "street") + private String street; + + @Column(name = "suite") + private String suite; + + @Column(name = "city") + private String city; + + @Column(name = "zipcode") + private String zipcode; + + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "geo_id") + private Geo geo; + + @OneToOne(mappedBy = "address") + private User user; + + public Address(String street, String suite, String city, String zipcode, Geo geo) { + this.street = street; + this.suite = suite; + this.city = city; + this.zipcode = zipcode; + this.geo = geo; + } + + @JsonIgnore + public Long getId() { + return id; + } + + @JsonIgnore + @Override + public Long getCreatedBy() { + return super.getCreatedBy(); + } + + @JsonIgnore + @Override + public void setCreatedBy(Long createdBy) { + super.setCreatedBy(createdBy); + } + + @JsonIgnore + @Override + public Long getUpdatedBy() { + return super.getUpdatedBy(); + } + + @JsonIgnore + @Override + public void setUpdatedBy(Long updatedBy) { + super.setUpdatedBy(updatedBy); + } + + @JsonIgnore + @Override + public Instant getCreatedAt() { + return super.getCreatedAt(); + } + + @JsonIgnore + @Override + public void setCreatedAt(Instant createdAt) { + super.setCreatedAt(createdAt); + } + + @JsonIgnore + @Override + public Instant getUpdatedAt() { + return super.getUpdatedAt(); + } + + @JsonIgnore + @Override + public void setUpdatedAt(Instant updatedAt) { + super.setUpdatedAt(updatedAt); + } } diff --git a/src/main/java/com/sopromadze/blogapi/model/user/Company.java b/src/main/java/com/sopromadze/blogapi/model/user/Company.java index 932e9660..7cc93a2f 100644 --- a/src/main/java/com/sopromadze/blogapi/model/user/Company.java +++ b/src/main/java/com/sopromadze/blogapi/model/user/Company.java @@ -2,117 +2,104 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.sopromadze.blogapi.model.audit.UserDateAudit; - -import javax.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Table; import java.time.Instant; +@EqualsAndHashCode(callSuper = true) @Entity +@Data +@NoArgsConstructor @Table(name = "company") public class Company extends UserDateAudit { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "name") - private String name; - - @Column(name = "catch_phrase") - private String catchPhrase; - - @Column(name = "bs") - private String bs; - - @OneToOne(mappedBy = "company") - private User user; - - public Company(){ - - } - - public Company(String name, String catchPhrase, String bs) { - this.name = name; - this.catchPhrase = catchPhrase; - this.bs = bs; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getCatchPhrase() { - return catchPhrase; - } - - public void setCatchPhrase(String catchPhrase) { - this.catchPhrase = catchPhrase; - } - - public String getBs() { - return bs; - } - - public void setBs(String bs) { - this.bs = bs; - } - - @JsonIgnore - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - @JsonIgnore - @Override - public Long getCreatedBy() { - return super.getCreatedBy(); - } - - @JsonIgnore - @Override - public void setCreatedBy(Long createdBy) { - super.setCreatedBy(createdBy); - } - - @JsonIgnore - @Override - public Long getUpdatedBy() { - return super.getUpdatedBy(); - } - - @JsonIgnore - @Override - public void setUpdatedBy(Long updatedBy) { - super.setUpdatedBy(updatedBy); - } - - @JsonIgnore - @Override - public Instant getCreatedAt() { - return super.getCreatedAt(); - } - - @JsonIgnore - @Override - public void setCreatedAt(Instant createdAt) { - super.setCreatedAt(createdAt); - } - - @JsonIgnore - @Override - public Instant getUpdatedAt() { - return super.getUpdatedAt(); - } - - @JsonIgnore - @Override - public void setUpdatedAt(Instant updatedAt) { - super.setUpdatedAt(updatedAt); - } + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "name") + private String name; + + @Column(name = "catch_phrase") + private String catchPhrase; + + @Column(name = "bs") + private String bs; + + @OneToOne(mappedBy = "company") + private User user; + + + public Company(String name, String catchPhrase, String bs) { + this.name = name; + this.catchPhrase = catchPhrase; + this.bs = bs; + } + + @JsonIgnore + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @JsonIgnore + @Override + public Long getCreatedBy() { + return super.getCreatedBy(); + } + + @JsonIgnore + @Override + public void setCreatedBy(Long createdBy) { + super.setCreatedBy(createdBy); + } + + @JsonIgnore + @Override + public Long getUpdatedBy() { + return super.getUpdatedBy(); + } + + @JsonIgnore + @Override + public void setUpdatedBy(Long updatedBy) { + super.setUpdatedBy(updatedBy); + } + + @JsonIgnore + @Override + public Instant getCreatedAt() { + return super.getCreatedAt(); + } + + @JsonIgnore + @Override + public void setCreatedAt(Instant createdAt) { + super.setCreatedAt(createdAt); + } + + @JsonIgnore + @Override + public Instant getUpdatedAt() { + return super.getUpdatedAt(); + } + + @JsonIgnore + @Override + public void setUpdatedAt(Instant updatedAt) { + super.setUpdatedAt(updatedAt); + } } diff --git a/src/main/java/com/sopromadze/blogapi/model/user/Geo.java b/src/main/java/com/sopromadze/blogapi/model/user/Geo.java index 8e18447c..f2845e22 100644 --- a/src/main/java/com/sopromadze/blogapi/model/user/Geo.java +++ b/src/main/java/com/sopromadze/blogapi/model/user/Geo.java @@ -2,105 +2,91 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.sopromadze.blogapi.model.audit.UserDateAudit; - -import javax.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Table; import java.time.Instant; +@EqualsAndHashCode(callSuper = true) @Entity +@Data +@NoArgsConstructor @Table(name = "geo") public class Geo extends UserDateAudit { - @JsonIgnore - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "lat") - private String lat; - - @Column(name = "lng") - private String lng; - - @OneToOne(mappedBy = "geo") - private Address address; - - public Geo(){ - - } - - public Geo(String lat, String lng) { - this.lat = lat; - this.lng = lng; - } - - public String getLat() { - return lat; - } - - public void setLat(String lat) { - this.lat = lat; - } - - public String getLng() { - return lng; - } - - public void setLng(String lng) { - this.lng = lng; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - @JsonIgnore - @Override - public Long getCreatedBy() { - return super.getCreatedBy(); - } - - @JsonIgnore - @Override - public void setCreatedBy(Long createdBy) { - super.setCreatedBy(createdBy); - } - - @JsonIgnore - @Override - public Long getUpdatedBy() { - return super.getUpdatedBy(); - } - - @JsonIgnore - @Override - public void setUpdatedBy(Long updatedBy) { - super.setUpdatedBy(updatedBy); - } - - @JsonIgnore - @Override - public Instant getCreatedAt() { - return super.getCreatedAt(); - } - - @JsonIgnore - @Override - public void setCreatedAt(Instant createdAt) { - super.setCreatedAt(createdAt); - } - - @JsonIgnore - @Override - public Instant getUpdatedAt() { - return super.getUpdatedAt(); - } - - @JsonIgnore - @Override - public void setUpdatedAt(Instant updatedAt) { - super.setUpdatedAt(updatedAt); - } + private static final long serialVersionUID = 1L; + + @JsonIgnore + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "lat") + private String lat; + + @Column(name = "lng") + private String lng; + + @OneToOne(mappedBy = "geo") + private Address address; + + public Geo(String lat, String lng) { + this.lat = lat; + this.lng = lng; + } + + @JsonIgnore + @Override + public Long getCreatedBy() { + return super.getCreatedBy(); + } + + @JsonIgnore + @Override + public void setCreatedBy(Long createdBy) { + super.setCreatedBy(createdBy); + } + + @JsonIgnore + @Override + public Long getUpdatedBy() { + return super.getUpdatedBy(); + } + + @JsonIgnore + @Override + public void setUpdatedBy(Long updatedBy) { + super.setUpdatedBy(updatedBy); + } + + @JsonIgnore + @Override + public Instant getCreatedAt() { + return super.getCreatedAt(); + } + + @JsonIgnore + @Override + public void setCreatedAt(Instant createdAt) { + super.setCreatedAt(createdAt); + } + + @JsonIgnore + @Override + public Instant getUpdatedAt() { + return super.getUpdatedAt(); + } + + @JsonIgnore + @Override + public void setUpdatedAt(Instant updatedAt) { + super.setUpdatedAt(updatedAt); + } } diff --git a/src/main/java/com/sopromadze/blogapi/model/user/User.java b/src/main/java/com/sopromadze/blogapi/model/user/User.java index bb8ecc4e..90f2e489 100644 --- a/src/main/java/com/sopromadze/blogapi/model/user/User.java +++ b/src/main/java/com/sopromadze/blogapi/model/user/User.java @@ -2,230 +2,191 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import com.sopromadze.blogapi.model.album.Album; +import com.sopromadze.blogapi.model.Album; import com.sopromadze.blogapi.model.audit.DateAudit; -import com.sopromadze.blogapi.model.comment.Comment; -import com.sopromadze.blogapi.model.post.Post; +import com.sopromadze.blogapi.model.Comment; +import com.sopromadze.blogapi.model.Post; import com.sopromadze.blogapi.model.role.Role; -import com.sopromadze.blogapi.model.todo.Todo; +import com.sopromadze.blogapi.model.Todo; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import org.hibernate.annotations.NaturalId; -import javax.persistence.*; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; -import java.time.Instant; -import java.util.Date; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +@EqualsAndHashCode(callSuper = true) @Entity -@Table(name = "users", uniqueConstraints = { - @UniqueConstraint(columnNames = { - "username" - }), - @UniqueConstraint(columnNames = { - "email" - }) -}) +@Data +@NoArgsConstructor +@Table(name = "users", uniqueConstraints = { @UniqueConstraint(columnNames = { "username" }), + @UniqueConstraint(columnNames = { "email" }) }) public class User extends DateAudit { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private Long id; - - @NotBlank - @Column(name = "first_name") - @Size(max = 40) - private String firstName; - - @NotBlank - @Column(name = "last_name") - @Size(max = 40) - private String lastName; - - @NotBlank - @Column(name = "username") - @Size(max = 15) - private String username; - - @NotBlank - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - @Size(max = 100) - @Column(name = "password") - private String password; - - @NotBlank - @NaturalId - @Size(max = 40) - @Column(name = "email") - @Email - private String email; - - @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) - @JoinColumn(name = "address_id") - private Address address; - - @Column(name = "phone") - private String phone; - - @Column(name = "website") - private String website; - - @ManyToMany(fetch = FetchType.EAGER) - @JoinTable(name = "user_role", - joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), - inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")) - private List roles; - - @JsonIgnore - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private List todos; - - @JsonIgnore - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private List albums; - - @JsonIgnore - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private List posts; - - @JsonIgnore - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private List comments; - - @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) - @JoinColumn(name = "company_id") - private Company company; - - public User(){ - - } - - public User(String firstName, String lastName, String username, String email, String password) { - this.firstName = firstName; - this.lastName = lastName; - this.username = username; - this.email = email; - this.password = password; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public Address getAddress() { - return address; - } - - public void setAddress(Address address) { - this.address = address; - } - - public String getPhone() { - return phone; - } - - public void setPhone(String phone) { - this.phone = phone; - } - - public String getWebsite() { - return website; - } - - public void setWebsite(String website) { - this.website = website; - } - - public Company getCompany() { - return company; - } + private static final long serialVersionUID = 1L; - public void setCompany(Company company) { - this.company = company; - } + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; - public List getTodos() { - return todos; - } - - public void setTodos(List todos) { - this.todos = todos; - } - - public List getAlbums() { - return albums; - } + @NotBlank + @Column(name = "first_name") + @Size(max = 40) + private String firstName; - public void setAlbums(List albums) { - this.albums = albums; - } + @NotBlank + @Column(name = "last_name") + @Size(max = 40) + private String lastName; - public String getFirstName() { - return firstName; - } + @NotBlank + @Column(name = "username") + @Size(max = 15) + private String username; - public void setFirstName(String firstName) { - this.firstName = firstName; - } + @NotBlank + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + @Size(max = 100) + @Column(name = "password") + private String password; - public String getLastName() { - return lastName; - } + @NotBlank + @NaturalId + @Size(max = 40) + @Column(name = "email") + @Email + private String email; - public void setLastName(String lastName) { - this.lastName = lastName; - } + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "address_id") + private Address address; - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public List getPosts() { - return posts; - } + @Column(name = "phone") + private String phone; - public void setPosts(List posts) { - this.posts = posts; - } + @Column(name = "website") + private String website; - public List getRoles() { - return roles; - } + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")) + private List roles; - public void setRoles(List roles) { - this.roles = roles; - } + @JsonIgnore + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List todos; - public List getComments() { - return comments; - } + @JsonIgnore + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List albums; - public void setComments(List comments) { - this.comments = comments; - } + @JsonIgnore + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List posts; + + @JsonIgnore + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List comments; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "company_id") + private Company company; + + public User(String firstName, String lastName, String username, String email, String password) { + this.firstName = firstName; + this.lastName = lastName; + this.username = username; + this.email = email; + this.password = password; + } + + + + public List getTodos() { + + return todos == null ? null : new ArrayList<>(todos); + } + + public void setTodos(List todos) { + + if (todos == null) { + this.todos = null; + } else { + this.todos = Collections.unmodifiableList(todos); + } + } + + public List getAlbums() { + + return albums == null ? null : new ArrayList<>(albums); + } + + public void setAlbums(List albums) { + + if (albums == null) { + this.albums = null; + } else { + this.albums = Collections.unmodifiableList(albums); + } + } + + + public List getPosts() { + + return posts == null ? null : new ArrayList<>(posts); + } + + public void setPosts(List posts) { + + if (posts == null) { + this.posts = null; + } else { + this.posts = Collections.unmodifiableList(posts); + } + } + + public List getRoles() { + + return roles == null ? null : new ArrayList<>(roles); + } + + public void setRoles(List roles) { + + if (roles == null) { + this.roles = null; + } else { + this.roles = Collections.unmodifiableList(roles); + } + } + + public List getComments() { + return comments == null ? null : new ArrayList<>(comments); + } + + public void setComments(List comments) { + + if (comments == null) { + this.comments = null; + } else { + this.comments = Collections.unmodifiableList(comments); + } + } } diff --git a/src/main/java/com/sopromadze/blogapi/payload/AlbumResponse.java b/src/main/java/com/sopromadze/blogapi/payload/AlbumResponse.java new file mode 100644 index 00000000..ea57f7df --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/payload/AlbumResponse.java @@ -0,0 +1,39 @@ +package com.sopromadze.blogapi.payload; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.sopromadze.blogapi.model.Photo; +import com.sopromadze.blogapi.model.user.User; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Data +@JsonInclude(Include.NON_NULL) +public class AlbumResponse extends UserDateAuditPayload { + private Long id; + + private String title; + + private User user; + + private List photo; + + public List getPhoto() { + + return photo == null ? null : new ArrayList<>(photo); + } + + public void setPhoto(List photo) { + + if (photo == null) { + this.photo = null; + } else { + this.photo = Collections.unmodifiableList(photo); + } + } +} diff --git a/src/main/java/com/sopromadze/blogapi/payload/ApiResponse.java b/src/main/java/com/sopromadze/blogapi/payload/ApiResponse.java index 94750969..4dfa14ab 100644 --- a/src/main/java/com/sopromadze/blogapi/payload/ApiResponse.java +++ b/src/main/java/com/sopromadze/blogapi/payload/ApiResponse.java @@ -1,27 +1,44 @@ package com.sopromadze.blogapi.payload; -public class ApiResponse { - private Boolean success; - private String message; - - public ApiResponse(Boolean success, String message) { - this.success = success; - this.message = message; - } - - public Boolean getSuccess() { - return success; - } - - public void setSuccess(Boolean success) { - this.success = success; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.Data; +import org.springframework.http.HttpStatus; + +import java.io.Serializable; + +@Data +@JsonPropertyOrder({ + "success", + "message" +}) +public class ApiResponse implements Serializable { + + @JsonIgnore + private static final long serialVersionUID = 7702134516418120340L; + + @JsonProperty("success") + private Boolean success; + + @JsonProperty("message") + private String message; + + @JsonIgnore + private HttpStatus status; + + public ApiResponse() { + + } + + public ApiResponse(Boolean success, String message) { + this.success = success; + this.message = message; + } + + public ApiResponse(Boolean success, String message, HttpStatus httpStatus) { + this.success = success; + this.message = message; + this.status = httpStatus; + } } diff --git a/src/main/java/com/sopromadze/blogapi/payload/CommentRequest.java b/src/main/java/com/sopromadze/blogapi/payload/CommentRequest.java index cf1b95c6..c88239ff 100644 --- a/src/main/java/com/sopromadze/blogapi/payload/CommentRequest.java +++ b/src/main/java/com/sopromadze/blogapi/payload/CommentRequest.java @@ -1,18 +1,13 @@ package com.sopromadze.blogapi.payload; +import lombok.Data; + import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; +@Data public class CommentRequest { - @NotBlank - @Size(min = 10, message = "Comment body must be minimum 10 characters") - private String body; - - public String getBody() { - return body; - } - - public void setBody(String body) { - this.body = body; - } + @NotBlank + @Size(min = 10, message = "Comment body must be minimum 10 characters") + private String body; } diff --git a/src/main/java/com/sopromadze/blogapi/payload/DateAuditPayload.java b/src/main/java/com/sopromadze/blogapi/payload/DateAuditPayload.java new file mode 100644 index 00000000..7ec74a5a --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/payload/DateAuditPayload.java @@ -0,0 +1,26 @@ +package com.sopromadze.blogapi.payload; + +import java.time.Instant; + +public abstract class DateAuditPayload { + + private Instant createdAt; + + private Instant updatedAt; + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/src/main/java/com/sopromadze/blogapi/payload/ExceptionResponse.java b/src/main/java/com/sopromadze/blogapi/payload/ExceptionResponse.java index 0fa9eb08..a243981b 100644 --- a/src/main/java/com/sopromadze/blogapi/payload/ExceptionResponse.java +++ b/src/main/java/com/sopromadze/blogapi/payload/ExceptionResponse.java @@ -1,50 +1,38 @@ package com.sopromadze.blogapi.payload; +import lombok.Data; + import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +@Data public class ExceptionResponse { - private String error; - private Integer status; - private List messages; - private Instant timestamp; - - public ExceptionResponse(List messages, String error, Integer status) { - this.messages = messages; - this.error = error; - this.status = status; - this.timestamp = Instant.now(); - } - - public List getMessages() { - return messages; - } - - public void setMessages(List messages) { - this.messages = messages; - } - - public String getError() { - return error; - } - - public void setError(String error) { - this.error = error; - } - - public Integer getStatus() { - return status; - } - - public void setStatus(Integer status) { - this.status = status; - } - - public Instant getTimestamp() { - return timestamp; - } - - public void setTimestamp(Instant timestamp) { - this.timestamp = timestamp; - } + private String error; + private Integer status; + private List messages; + private Instant timestamp; + + public ExceptionResponse(List messages, String error, Integer status) { + setMessages(messages); + this.error = error; + this.status = status; + this.timestamp = Instant.now(); + } + + public List getMessages() { + + return messages == null ? null : new ArrayList<>(messages); + } + + public final void setMessages(List messages) { + + if (messages == null) { + this.messages = null; + } else { + this.messages = Collections.unmodifiableList(messages); + } + } + } diff --git a/src/main/java/com/sopromadze/blogapi/payload/InfoRequest.java b/src/main/java/com/sopromadze/blogapi/payload/InfoRequest.java index ad80ec3c..7baf210f 100644 --- a/src/main/java/com/sopromadze/blogapi/payload/InfoRequest.java +++ b/src/main/java/com/sopromadze/blogapi/payload/InfoRequest.java @@ -1,124 +1,35 @@ package com.sopromadze.blogapi.payload; -import org.hibernate.validator.constraints.URL; -import org.modelmapper.internal.bytebuddy.agent.builder.AgentBuilder; +import lombok.Data; -import javax.validation.GroupSequence; import javax.validation.constraints.NotBlank; -import javax.validation.constraints.Size; +@Data public class InfoRequest { - @NotBlank - private String street; - @NotBlank - private String suite; + @NotBlank + private String street; - @NotBlank - private String city; + @NotBlank + private String suite; - @NotBlank - private String zipcode; + @NotBlank + private String city; - private String companyName; + @NotBlank + private String zipcode; - private String catchPhrase; + private String companyName; - private String bs; + private String catchPhrase; - private String website; + private String bs; - private String phone; + private String website; - private String lat; + private String phone; - private String lng; + private String lat; - public String getStreet() { - return street; - } - - public void setStreet(String street) { - this.street = street; - } - - public String getSuite() { - return suite; - } - - public void setSuite(String suite) { - this.suite = suite; - } - - public String getCity() { - return city; - } - - public void setCity(String city) { - this.city = city; - } - - public String getZipcode() { - return zipcode; - } - - public void setZipcode(String zipcode) { - this.zipcode = zipcode; - } - - public String getLat() { - return lat; - } - - public void setLat(String lat) { - this.lat = lat; - } - - public String getLng() { - return lng; - } - - public void setLng(String lng) { - this.lng = lng; - } - - public String getCompanyName() { - return companyName; - } - - public void setCompanyName(String companyName) { - this.companyName = companyName; - } - - public String getCatchPhrase() { - return catchPhrase; - } - - public void setCatchPhrase(String catchPhrase) { - this.catchPhrase = catchPhrase; - } - - public String getBs() { - return bs; - } - - public void setBs(String bs) { - this.bs = bs; - } - - public String getWebsite() { - return website; - } - - public void setWebsite(String website) { - this.website = website; - } - - public String getPhone() { - return phone; - } - - public void setPhone(String phone) { - this.phone = phone; - } + private String lng; } diff --git a/src/main/java/com/sopromadze/blogapi/payload/JwtAuthenticationResponse.java b/src/main/java/com/sopromadze/blogapi/payload/JwtAuthenticationResponse.java index 47f2d1bb..148aa475 100644 --- a/src/main/java/com/sopromadze/blogapi/payload/JwtAuthenticationResponse.java +++ b/src/main/java/com/sopromadze/blogapi/payload/JwtAuthenticationResponse.java @@ -1,26 +1,14 @@ package com.sopromadze.blogapi.payload; -public class JwtAuthenticationResponse { - private String accessToken; - private String tokenType = "Bearer"; - - public JwtAuthenticationResponse(String accessToken) { - this.accessToken = accessToken; - } +import lombok.Data; - public String getAccessToken() { - return accessToken; - } - - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; - } +@Data +public class JwtAuthenticationResponse { + private String accessToken; + private String tokenType = "Bearer"; - public String getTokenType() { - return tokenType; - } + public JwtAuthenticationResponse(String accessToken) { + this.accessToken = accessToken; + } - public void setTokenType(String tokenType) { - this.tokenType = tokenType; - } } diff --git a/src/main/java/com/sopromadze/blogapi/payload/LoginRequest.java b/src/main/java/com/sopromadze/blogapi/payload/LoginRequest.java index 4a04b5cb..ac1b0ee8 100644 --- a/src/main/java/com/sopromadze/blogapi/payload/LoginRequest.java +++ b/src/main/java/com/sopromadze/blogapi/payload/LoginRequest.java @@ -1,27 +1,14 @@ package com.sopromadze.blogapi.payload; +import lombok.Data; + import javax.validation.constraints.NotBlank; +@Data public class LoginRequest { - @NotBlank - private String usernameOrEmail; - - @NotBlank - private String password; - - public String getUsernameOrEmail() { - return usernameOrEmail; - } - - public void setUsernameOrEmail(String usernameOrEmail) { - this.usernameOrEmail = usernameOrEmail; - } - - public String getPassword() { - return password; - } + @NotBlank + private String usernameOrEmail; - public void setPassword(String password) { - this.password = password; - } + @NotBlank + private String password; } diff --git a/src/main/java/com/sopromadze/blogapi/payload/PagedResponse.java b/src/main/java/com/sopromadze/blogapi/payload/PagedResponse.java index 651ad5aa..9e73e40e 100644 --- a/src/main/java/com/sopromadze/blogapi/payload/PagedResponse.java +++ b/src/main/java/com/sopromadze/blogapi/payload/PagedResponse.java @@ -1,73 +1,48 @@ package com.sopromadze.blogapi.payload; +import lombok.Data; + +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +@Data public class PagedResponse { - private List content; - private int page; - private int size; - private long totalElements; - private int totalPages; - private boolean last; - - public PagedResponse(){ - - } - - public PagedResponse(List content, int page, int size, long totalElements, int totalPages, boolean last) { - this.content = content; - this.page = page; - this.size = size; - this.totalElements = totalElements; - this.totalPages = totalPages; - this.last = last; - } - - public List getContent() { - return content; - } - - public void setContent(List posts) { - this.content = posts; - } - - public int getPage() { - return page; - } - - public void setPage(int page) { - this.page = page; - } - - public int getSize() { - return size; - } - - public void setSize(int size) { - this.size = size; - } - - public long getTotalElements() { - return totalElements; - } - - public void setTotalElements(long totalElements) { - this.totalElements = totalElements; - } - - public int getTotalPages() { - return totalPages; - } - - public void setTotalPages(int totalPages) { - this.totalPages = totalPages; - } - - public boolean isLast() { - return last; - } - - public void setLast(boolean last) { - this.last = last; - } + private List content; + private int page; + private int size; + private long totalElements; + private int totalPages; + private boolean last; + + public PagedResponse() { + + } + + public PagedResponse(List content, int page, int size, long totalElements, int totalPages, boolean last) { + setContent(content); + this.page = page; + this.size = size; + this.totalElements = totalElements; + this.totalPages = totalPages; + this.last = last; + } + + public List getContent() { + return content == null ? null : new ArrayList<>(content); + } + + public final void setContent(List content) { + if (content == null) { + this.content = null; + } else { + this.content = Collections.unmodifiableList(content); + } + } + + + + public boolean isLast() { + return last; + } } diff --git a/src/main/java/com/sopromadze/blogapi/payload/PhotoRequest.java b/src/main/java/com/sopromadze/blogapi/payload/PhotoRequest.java index 6a7ac57d..74950489 100644 --- a/src/main/java/com/sopromadze/blogapi/payload/PhotoRequest.java +++ b/src/main/java/com/sopromadze/blogapi/payload/PhotoRequest.java @@ -1,55 +1,26 @@ package com.sopromadze.blogapi.payload; +import lombok.Data; + import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +@Data public class PhotoRequest { - @NotBlank - @Size(min = 3) - private String title; - - @NotBlank - @Size(min = 10) - private String url; - - @NotBlank - @Size(min = 10) - private String thumbnailUrl; - - @NotNull - private Long albumId; - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getThumbnailUrl() { - return thumbnailUrl; - } + @NotBlank + @Size(min = 3) + private String title; - public void setThumbnailUrl(String thumbnailUrl) { - this.thumbnailUrl = thumbnailUrl; - } + @NotBlank + @Size(min = 10) + private String url; - public Long getAlbumId() { - return albumId; - } + @NotBlank + @Size(min = 10) + private String thumbnailUrl; - public void setAlbumId(Long albumId) { - this.albumId = albumId; - } + @NotNull + private Long albumId; } diff --git a/src/main/java/com/sopromadze/blogapi/payload/PhotoResponse.java b/src/main/java/com/sopromadze/blogapi/payload/PhotoResponse.java index 2038142f..e695b977 100644 --- a/src/main/java/com/sopromadze/blogapi/payload/PhotoResponse.java +++ b/src/main/java/com/sopromadze/blogapi/payload/PhotoResponse.java @@ -1,57 +1,21 @@ package com.sopromadze.blogapi.payload; -public class PhotoResponse { - private Long id; - private String title; - private String url; - private String thumbnailUrl; - private Long albumId; - - public PhotoResponse(Long id, String title, String url, String thumbnailUrl, Long albumId) { - this.id = id; - this.title = title; - this.url = url; - this.thumbnailUrl = thumbnailUrl; - this.albumId = albumId; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } +import lombok.Data; - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getThumbnailUrl() { - return thumbnailUrl; - } - - public void setThumbnailUrl(String thumbnailUrl) { - this.thumbnailUrl = thumbnailUrl; - } - - public Long getAlbumId() { - return albumId; - } +@Data +public class PhotoResponse { + private Long id; + private String title; + private String url; + private String thumbnailUrl; + private Long albumId; + + public PhotoResponse(Long id, String title, String url, String thumbnailUrl, Long albumId) { + this.id = id; + this.title = title; + this.url = url; + this.thumbnailUrl = thumbnailUrl; + this.albumId = albumId; + } - public void setAlbumId(Long albumId) { - this.albumId = albumId; - } } diff --git a/src/main/java/com/sopromadze/blogapi/payload/PostRequest.java b/src/main/java/com/sopromadze/blogapi/payload/PostRequest.java new file mode 100644 index 00000000..28d7ed13 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/payload/PostRequest.java @@ -0,0 +1,41 @@ +package com.sopromadze.blogapi.payload; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Data +public class PostRequest { + + @NotBlank + @Size(min = 10) + private String title; + + @NotBlank + @Size(min = 50) + private String body; + + @NotNull + private Long categoryId; + + private List tags; + + public List getTags() { + + return tags == null ? Collections.emptyList() : new ArrayList<>(tags); + } + + public void setTags(List tags) { + + if (tags == null) { + this.tags = null; + } else { + this.tags = Collections.unmodifiableList(tags); + } + } +} diff --git a/src/main/java/com/sopromadze/blogapi/payload/PostResponse.java b/src/main/java/com/sopromadze/blogapi/payload/PostResponse.java index 7298316b..f054e518 100644 --- a/src/main/java/com/sopromadze/blogapi/payload/PostResponse.java +++ b/src/main/java/com/sopromadze/blogapi/payload/PostResponse.java @@ -1,51 +1,31 @@ package com.sopromadze.blogapi.payload; -import java.time.Instant; +import lombok.Data; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Data public class PostResponse { - private Long id; - private String title; - private String body; - private UserSummary createdBy; - private Instant updatedDateTime; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getBody() { - return body; - } - - public void setBody(String body) { - this.body = body; - } - - public UserSummary getCreatedBy() { - return createdBy; - } - - public void setCreatedBy(UserSummary createdBy) { - this.createdBy = createdBy; - } - - public Instant getUpdatedDateTime() { - return updatedDateTime; - } - - public void setUpdatedDateTime(Instant updatedDateTime) { - this.updatedDateTime = updatedDateTime; - } + private String title; + private String body; + private String category; + private List tags; + + + + public List getTags() { + + return tags == null ? null : new ArrayList<>(tags); + } + + public void setTags(List tags) { + + if (tags == null) { + this.tags = null; + } else { + this.tags = Collections.unmodifiableList(tags); + } + } } diff --git a/src/main/java/com/sopromadze/blogapi/payload/SignUpRequest.java b/src/main/java/com/sopromadze/blogapi/payload/SignUpRequest.java index 46eeb955..3f993f13 100644 --- a/src/main/java/com/sopromadze/blogapi/payload/SignUpRequest.java +++ b/src/main/java/com/sopromadze/blogapi/payload/SignUpRequest.java @@ -1,68 +1,31 @@ package com.sopromadze.blogapi.payload; +import lombok.Data; + import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; +@Data public class SignUpRequest { - @NotBlank - @Size(min = 4, max = 40) - private String firstName; - - @NotBlank - @Size(min = 4, max = 40) - private String lastName; - - @NotBlank - @Size(min = 3, max = 15) - private String username; - - @NotBlank - @Size(max = 40) - @Email - private String email; - - @NotBlank - @Size(min = 6, max = 20) - private String password; - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } + @NotBlank + @Size(min = 4, max = 40) + private String firstName; + + @NotBlank + @Size(min = 4, max = 40) + private String lastName; + + @NotBlank + @Size(min = 3, max = 15) + private String username; + + @NotBlank + @Size(max = 40) + @Email + private String email; + + @NotBlank + @Size(min = 6, max = 20) + private String password; } diff --git a/src/main/java/com/sopromadze/blogapi/payload/UserDateAuditPayload.java b/src/main/java/com/sopromadze/blogapi/payload/UserDateAuditPayload.java new file mode 100644 index 00000000..834f095d --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/payload/UserDateAuditPayload.java @@ -0,0 +1,13 @@ +package com.sopromadze.blogapi.payload; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public abstract class UserDateAuditPayload extends DateAuditPayload { + private Long createdBy; + + private Long updatedBy; + +} diff --git a/src/main/java/com/sopromadze/blogapi/payload/UserIdentityAvailability.java b/src/main/java/com/sopromadze/blogapi/payload/UserIdentityAvailability.java index d2dffb93..c5d09fc2 100644 --- a/src/main/java/com/sopromadze/blogapi/payload/UserIdentityAvailability.java +++ b/src/main/java/com/sopromadze/blogapi/payload/UserIdentityAvailability.java @@ -1,17 +1,11 @@ package com.sopromadze.blogapi.payload; -public class UserIdentityAvailability { - private Boolean available; - - public UserIdentityAvailability(Boolean available) { - this.available = available; - } +import lombok.AllArgsConstructor; +import lombok.Data; - public Boolean getAvailable() { - return available; - } +@Data +@AllArgsConstructor +public class UserIdentityAvailability { + private Boolean available; - public void setAvailable(Boolean available) { - this.available = available; - } } diff --git a/src/main/java/com/sopromadze/blogapi/payload/UserProfile.java b/src/main/java/com/sopromadze/blogapi/payload/UserProfile.java index 968ede71..b428ea12 100644 --- a/src/main/java/com/sopromadze/blogapi/payload/UserProfile.java +++ b/src/main/java/com/sopromadze/blogapi/payload/UserProfile.java @@ -2,125 +2,25 @@ import com.sopromadze.blogapi.model.user.Address; import com.sopromadze.blogapi.model.user.Company; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; import java.time.Instant; +@Data +@AllArgsConstructor +@NoArgsConstructor public class UserProfile { - private Long id; - private String username; - private String firstName; - private String lastName; - private Instant joinedAt; - private String email; - private Address address; - private String phone; - private String website; - private Company company; - private Long postCount; - - public UserProfile(){ - - } - - public UserProfile(Long id, String username, String firstName, String lastName, Instant joinedAt, String email, Address address, String phone, String website, Company company, Long postCount) { - this.id = id; - this.username = username; - this.firstName = firstName; - this.lastName = lastName; - this.joinedAt = joinedAt; - this.email = email; - this.address = address; - this.phone = phone; - this.website = website; - this.company = company; - this.postCount = postCount; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public Instant getJoinedAt() { - return joinedAt; - } - - public void setJoinedAt(Instant joinedAt) { - this.joinedAt = joinedAt; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public Address getAddress() { - return address; - } - - public void setAddress(Address address) { - this.address = address; - } - - public String getPhone() { - return phone; - } - - public void setPhone(String phone) { - this.phone = phone; - } - - public String getWebsite() { - return website; - } - - public void setWebsite(String website) { - this.website = website; - } - - public Company getCompany() { - return company; - } - - public void setCompany(Company company) { - this.company = company; - } - - public Long getPostCount() { - return postCount; - } - - public void setPostCount(Long postCount) { - this.postCount = postCount; - } + private Long id; + private String username; + private String firstName; + private String lastName; + private Instant joinedAt; + private String email; + private Address address; + private String phone; + private String website; + private Company company; + private Long postCount; } diff --git a/src/main/java/com/sopromadze/blogapi/payload/UserSummary.java b/src/main/java/com/sopromadze/blogapi/payload/UserSummary.java index f1a1c3c3..7fe8fe92 100644 --- a/src/main/java/com/sopromadze/blogapi/payload/UserSummary.java +++ b/src/main/java/com/sopromadze/blogapi/payload/UserSummary.java @@ -1,47 +1,13 @@ package com.sopromadze.blogapi.payload; -public class UserSummary { - private Long id; - private String username; - private String firstName; - private String lastName; - - public UserSummary(Long id, String username, String firstName, String lastName) { - this.id = id; - this.username = username; - this.firstName = firstName; - this.lastName = lastName; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getUsername() { - return username; - } +import lombok.AllArgsConstructor; +import lombok.Data; - public void setUsername(String username) { - this.username = username; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } +@Data +@AllArgsConstructor +public class UserSummary { + private Long id; + private String username; + private String firstName; + private String lastName; } diff --git a/src/main/java/com/sopromadze/blogapi/payload/request/AlbumRequest.java b/src/main/java/com/sopromadze/blogapi/payload/request/AlbumRequest.java new file mode 100644 index 00000000..83e4f1eb --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/payload/request/AlbumRequest.java @@ -0,0 +1,36 @@ +package com.sopromadze.blogapi.payload.request; + +import com.sopromadze.blogapi.model.Photo; +import com.sopromadze.blogapi.model.user.User; +import com.sopromadze.blogapi.payload.UserDateAuditPayload; +import lombok.Data; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Data +public class AlbumRequest extends UserDateAuditPayload { + + private Long id; + + private String title; + + private User user; + + private List photo; + + public List getPhoto() { + + return photo == null ? null : new ArrayList<>(photo); + } + + public void setPhoto(List photo) { + + if (photo == null) { + this.photo = null; + } else { + this.photo = Collections.unmodifiableList(photo); + } + } +} diff --git a/src/main/java/com/sopromadze/blogapi/repository/AlbumRepository.java b/src/main/java/com/sopromadze/blogapi/repository/AlbumRepository.java index af696383..12d6837f 100644 --- a/src/main/java/com/sopromadze/blogapi/repository/AlbumRepository.java +++ b/src/main/java/com/sopromadze/blogapi/repository/AlbumRepository.java @@ -1,13 +1,12 @@ package com.sopromadze.blogapi.repository; -import com.sopromadze.blogapi.model.album.Album; +import com.sopromadze.blogapi.model.Album; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository -public interface AlbumRepository extends PagingAndSortingRepository { - Page findByCreatedBy(Long userId, Pageable pageable); +public interface AlbumRepository extends JpaRepository { + Page findByCreatedBy(Long userId, Pageable pageable); } diff --git a/src/main/java/com/sopromadze/blogapi/repository/CategoryRepository.java b/src/main/java/com/sopromadze/blogapi/repository/CategoryRepository.java new file mode 100644 index 00000000..55c00914 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/repository/CategoryRepository.java @@ -0,0 +1,10 @@ +package com.sopromadze.blogapi.repository; + +import com.sopromadze.blogapi.model.Category; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CategoryRepository extends JpaRepository { + +} diff --git a/src/main/java/com/sopromadze/blogapi/repository/CommentRepository.java b/src/main/java/com/sopromadze/blogapi/repository/CommentRepository.java index 0465ee4f..b4fb3be3 100644 --- a/src/main/java/com/sopromadze/blogapi/repository/CommentRepository.java +++ b/src/main/java/com/sopromadze/blogapi/repository/CommentRepository.java @@ -1,13 +1,12 @@ package com.sopromadze.blogapi.repository; -import com.sopromadze.blogapi.model.comment.Comment; +import com.sopromadze.blogapi.model.Comment; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository -public interface CommentRepository extends PagingAndSortingRepository { - Page findByPostId(Long postId, Pageable pageable); +public interface CommentRepository extends JpaRepository { + Page findByPostId(Long postId, Pageable pageable); } diff --git a/src/main/java/com/sopromadze/blogapi/repository/PhotoRepository.java b/src/main/java/com/sopromadze/blogapi/repository/PhotoRepository.java index 4f461e32..a6f52698 100644 --- a/src/main/java/com/sopromadze/blogapi/repository/PhotoRepository.java +++ b/src/main/java/com/sopromadze/blogapi/repository/PhotoRepository.java @@ -1,13 +1,12 @@ package com.sopromadze.blogapi.repository; -import com.sopromadze.blogapi.model.photo.Photo; +import com.sopromadze.blogapi.model.Photo; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository -public interface PhotoRepository extends PagingAndSortingRepository { - Page findByAlbumId(Long albumId, Pageable pageable); +public interface PhotoRepository extends JpaRepository { + Page findByAlbumId(Long albumId, Pageable pageable); } diff --git a/src/main/java/com/sopromadze/blogapi/repository/PostRepository.java b/src/main/java/com/sopromadze/blogapi/repository/PostRepository.java index c3443d3b..a748afb8 100644 --- a/src/main/java/com/sopromadze/blogapi/repository/PostRepository.java +++ b/src/main/java/com/sopromadze/blogapi/repository/PostRepository.java @@ -1,14 +1,21 @@ package com.sopromadze.blogapi.repository; -import com.sopromadze.blogapi.model.post.Post; +import com.sopromadze.blogapi.model.Post; +import com.sopromadze.blogapi.model.Tag; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository -public interface PostRepository extends PagingAndSortingRepository { - Page findByCreatedBy(Long userId, Pageable pageable); - Long countByCreatedBy(Long userId); +public interface PostRepository extends JpaRepository { + Page findByCreatedBy(Long userId, Pageable pageable); + + Page findByCategory(Long categoryId, Pageable pageable); + + Page findByTags(List tags, Pageable pageable); + + Long countByCreatedBy(Long userId); } diff --git a/src/main/java/com/sopromadze/blogapi/repository/RoleRepository.java b/src/main/java/com/sopromadze/blogapi/repository/RoleRepository.java index 6c666d0f..067294c6 100644 --- a/src/main/java/com/sopromadze/blogapi/repository/RoleRepository.java +++ b/src/main/java/com/sopromadze/blogapi/repository/RoleRepository.java @@ -2,10 +2,10 @@ import com.sopromadze.blogapi.model.role.Role; import com.sopromadze.blogapi.model.role.RoleName; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; -public interface RoleRepository extends CrudRepository { - Optional findByName(RoleName name); +public interface RoleRepository extends JpaRepository { + Optional findByName(RoleName name); } diff --git a/src/main/java/com/sopromadze/blogapi/repository/TagRepository.java b/src/main/java/com/sopromadze/blogapi/repository/TagRepository.java new file mode 100644 index 00000000..e9620636 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/repository/TagRepository.java @@ -0,0 +1,10 @@ +package com.sopromadze.blogapi.repository; + +import com.sopromadze.blogapi.model.Tag; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TagRepository extends JpaRepository { + Tag findByName(String name); +} diff --git a/src/main/java/com/sopromadze/blogapi/repository/TodoRepository.java b/src/main/java/com/sopromadze/blogapi/repository/TodoRepository.java index f619980e..3cb55e71 100644 --- a/src/main/java/com/sopromadze/blogapi/repository/TodoRepository.java +++ b/src/main/java/com/sopromadze/blogapi/repository/TodoRepository.java @@ -1,12 +1,12 @@ package com.sopromadze.blogapi.repository; -import com.sopromadze.blogapi.model.todo.Todo; +import com.sopromadze.blogapi.model.Todo; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository -public interface TodoRepository extends CrudRepository { - Page findByCreatedBy(Long userId, Pageable pageable); +public interface TodoRepository extends JpaRepository { + Page findByCreatedBy(Long userId, Pageable pageable); } diff --git a/src/main/java/com/sopromadze/blogapi/repository/UserRepository.java b/src/main/java/com/sopromadze/blogapi/repository/UserRepository.java index fd46b823..94290248 100644 --- a/src/main/java/com/sopromadze/blogapi/repository/UserRepository.java +++ b/src/main/java/com/sopromadze/blogapi/repository/UserRepository.java @@ -1,20 +1,32 @@ package com.sopromadze.blogapi.repository; +import com.sopromadze.blogapi.exception.ResourceNotFoundException; import com.sopromadze.blogapi.model.user.User; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.PagingAndSortingRepository; +import com.sopromadze.blogapi.security.UserPrincipal; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import javax.swing.text.html.Option; import javax.validation.constraints.NotBlank; -import java.util.List; import java.util.Optional; @Repository -public interface UserRepository extends PagingAndSortingRepository { - Optional findByUsername(@NotBlank String username); - Optional findByEmail(@NotBlank String email); - Boolean existsByUsername(@NotBlank String username); - Boolean existsByEmail(@NotBlank String email); - Optional findByUsernameOrEmail(String username, String email); +public interface UserRepository extends JpaRepository { + Optional findByUsername(@NotBlank String username); + + Optional findByEmail(@NotBlank String email); + + Boolean existsByUsername(@NotBlank String username); + + Boolean existsByEmail(@NotBlank String email); + + Optional findByUsernameOrEmail(String username, String email); + + default User getUser(UserPrincipal currentUser) { + return getUserByName(currentUser.getUsername()); + } + + default User getUserByName(String username) { + return findByUsername(username) + .orElseThrow(() -> new ResourceNotFoundException("User", "username", username)); + } } diff --git a/src/main/java/com/sopromadze/blogapi/security/CurrentUser.java b/src/main/java/com/sopromadze/blogapi/security/CurrentUser.java index 2833d6a4..400b4a48 100644 --- a/src/main/java/com/sopromadze/blogapi/security/CurrentUser.java +++ b/src/main/java/com/sopromadze/blogapi/security/CurrentUser.java @@ -2,9 +2,13 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; -@Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) +@Target({ ElementType.PARAMETER, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @AuthenticationPrincipal diff --git a/src/main/java/com/sopromadze/blogapi/security/JwtAuthenticationEntryPoint.java b/src/main/java/com/sopromadze/blogapi/security/JwtAuthenticationEntryPoint.java index bc0913eb..b9f644ab 100644 --- a/src/main/java/com/sopromadze/blogapi/security/JwtAuthenticationEntryPoint.java +++ b/src/main/java/com/sopromadze/blogapi/security/JwtAuthenticationEntryPoint.java @@ -13,10 +13,12 @@ @Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { - private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class); - @Override - public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { - LOGGER.error("Responding with unauthorized error. Message - {}", e.getMessage()); - httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Sorry, You're not authorized to access this resource."); - } + private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class); + + @Override + public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) + throws IOException, ServletException { + LOGGER.error("Responding with unauthorized error. Message - {}", e.getMessage()); + httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Sorry, You're not authorized to access this resource."); + } } diff --git a/src/main/java/com/sopromadze/blogapi/security/JwtAuthenticationFilter.java b/src/main/java/com/sopromadze/blogapi/security/JwtAuthenticationFilter.java index 8e09deb7..3c162554 100644 --- a/src/main/java/com/sopromadze/blogapi/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/sopromadze/blogapi/security/JwtAuthenticationFilter.java @@ -18,40 +18,40 @@ import java.io.IOException; public class JwtAuthenticationFilter extends OncePerRequestFilter { - @Autowired - private JwtTokenProvider tokenProvider; - - @Autowired - private CustomUserDetailsService customUserDetailsService; - - private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationFilter.class); - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - try{ - String jwt = getJwtFromRequest(request); - - if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)){ - Long userId = tokenProvider.getUserIdFromJWT(jwt); - - UserDetails userDetails = customUserDetailsService.loadUserById(userId); - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - - SecurityContextHolder.getContext().setAuthentication(authenticationToken); - } - } catch (Exception ex){ - LOGGER.error("Could not set user authentication in security context", ex); - } - - filterChain.doFilter(request, response); - } - - private String getJwtFromRequest(HttpServletRequest request){ - String bearerToken = request.getHeader("Authorization"); - if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")){ - return bearerToken.substring(7, bearerToken.length()); - } - return null; - } + private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationFilter.class); + @Autowired + private JwtTokenProvider tokenProvider; + @Autowired + private CustomUserDetailsService customUserDetailsService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + try { + String jwt = getJwtFromRequest(request); + + if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) { + Long userId = tokenProvider.getUserIdFromJWT(jwt); + + UserDetails userDetails = customUserDetailsService.loadUserById(userId); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, + userDetails.getAuthorities()); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + } catch (Exception ex) { + LOGGER.error("Could not set user authentication in security context", ex); + } + + filterChain.doFilter(request, response); + } + + private String getJwtFromRequest(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7, bearerToken.length()); + } + return null; + } } diff --git a/src/main/java/com/sopromadze/blogapi/security/JwtTokenProvider.java b/src/main/java/com/sopromadze/blogapi/security/JwtTokenProvider.java index 3472d79a..77fad327 100644 --- a/src/main/java/com/sopromadze/blogapi/security/JwtTokenProvider.java +++ b/src/main/java/com/sopromadze/blogapi/security/JwtTokenProvider.java @@ -1,6 +1,12 @@ package com.sopromadze.blogapi.security; -import io.jsonwebtoken.*; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.SignatureException; +import io.jsonwebtoken.UnsupportedJwtException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -11,52 +17,52 @@ @Component public class JwtTokenProvider { - private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenProvider.class); - - @Value(value = "${app.jwtSecret}") - private String jwtSecret; - - @Value(value = "${app.jwtExpirationInMs}") - private int jwtExpirationInMs; - - public String generateToken(Authentication authentication){ - UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); - - Date now = new Date(); - Date expiryDate = new Date(now.getTime() + jwtExpirationInMs); - - return Jwts.builder() - .setSubject(Long.toString(userPrincipal.getId())) - .setIssuedAt(new Date()) - .setExpiration(expiryDate) - .signWith(SignatureAlgorithm.HS512, jwtSecret) - .compact(); - } - - public Long getUserIdFromJWT(String token){ - Claims claims = Jwts.parser() - .setSigningKey(jwtSecret) - .parseClaimsJws(token) - .getBody(); - - return Long.parseLong(claims.getSubject()); - } - - public boolean validateToken(String authToken){ - try{ - Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken); - return true; - } catch (SignatureException ex){ - LOGGER.error("Invalid JWT signature"); - } catch (MalformedJwtException ex){ - LOGGER.error("Invalid JWT token"); - } catch (ExpiredJwtException ex){ - LOGGER.error("Expired JWT token"); - } catch (UnsupportedJwtException ex){ - LOGGER.error("Unsupported JWT token"); - } catch (IllegalArgumentException ex){ - LOGGER.error("JWT claims string is empty"); - } - return false; - } + private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenProvider.class); + + @Value(value = "${app.jwtSecret}") + private String jwtSecret; + + @Value(value = "${app.jwtExpirationInMs}") + private int jwtExpirationInMs; + + public String generateToken(Authentication authentication) { + UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); + + Date now = new Date(); + Date expiryDate = new Date(now.getTime() + jwtExpirationInMs); + + return Jwts.builder() + .setSubject(Long.toString(userPrincipal.getId())) + .setIssuedAt(new Date()) + .setExpiration(expiryDate) + .signWith(SignatureAlgorithm.HS512, jwtSecret) + .compact(); + } + + public Long getUserIdFromJWT(String token) { + Claims claims = Jwts.parser() + .setSigningKey(jwtSecret) + .parseClaimsJws(token) + .getBody(); + + return Long.valueOf(claims.getSubject()); + } + + public boolean validateToken(String authToken) { + try { + Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken); + return true; + } catch (SignatureException ex) { + LOGGER.error("Invalid JWT signature"); + } catch (MalformedJwtException ex) { + LOGGER.error("Invalid JWT token"); + } catch (ExpiredJwtException ex) { + LOGGER.error("Expired JWT token"); + } catch (UnsupportedJwtException ex) { + LOGGER.error("Unsupported JWT token"); + } catch (IllegalArgumentException ex) { + LOGGER.error("JWT claims string is empty"); + } + return false; + } } diff --git a/src/main/java/com/sopromadze/blogapi/security/UserPrincipal.java b/src/main/java/com/sopromadze/blogapi/security/UserPrincipal.java index 40cd9028..3402459f 100644 --- a/src/main/java/com/sopromadze/blogapi/security/UserPrincipal.java +++ b/src/main/java/com/sopromadze/blogapi/security/UserPrincipal.java @@ -6,112 +6,116 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; public class UserPrincipal implements UserDetails { - private Long id; - - private String firstName; - - private String lastName; - - private String username; - - @JsonIgnore - private String email; - - @JsonIgnore - private String password; - - private Collection authorities; - - public UserPrincipal(Long id, String firstName, String lastName, String username, String email, String password, Collection authorities) { - this.id = id; - this.firstName = firstName; - this.lastName = lastName; - this.username = username; - this.email = email; - this.password = password; - this.authorities = authorities; - } - - public static UserPrincipal create(User user){ - List authorities = user.getRoles().stream().map(role -> - new SimpleGrantedAuthority(role.getName().name())).collect(Collectors.toList()); - - return new UserPrincipal( - user.getId(), - user.getFirstName(), - user.getLastName(), - user.getUsername(), - user.getEmail(), - user.getPassword(), - authorities - ); - } - - public Long getId(){ - return id; - } - - public String getEmail(){ - return email; - } - - @Override - public Collection getAuthorities() { - return authorities; - } - - @Override - public String getPassword() { - return password; - } - - @Override - public String getUsername() { - return username; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } - - public boolean equals(Object object){ - if(this == object) return true; - if(object == null || getClass() != object.getClass()) return false; - UserPrincipal that = (UserPrincipal) object; - return Objects.equals(id, that.id); - } - - public int hashCode(){ - return Objects.hash(id); - } - - public String getFirstName() { - return firstName; - } - - public String getLastName() { - return lastName; - } + private static final long serialVersionUID = 1L; + + private Long id; + + private String firstName; + + private String lastName; + + private String username; + + @JsonIgnore + private String email; + + @JsonIgnore + private String password; + + private Collection authorities; + + public UserPrincipal(Long id, String firstName, String lastName, String username, String email, String password, + Collection authorities) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.username = username; + this.email = email; + this.password = password; + + if (authorities == null) { + this.authorities = null; + } else { + this.authorities = new ArrayList<>(authorities); + } + } + + public static UserPrincipal create(User user) { + List authorities = user.getRoles().stream() + .map(role -> new SimpleGrantedAuthority(role.getName().name())).collect(Collectors.toList()); + + return new UserPrincipal(user.getId(), user.getFirstName(), user.getLastName(), user.getUsername(), + user.getEmail(), user.getPassword(), authorities); + } + + public Long getId() { + return id; + } + + public String getEmail() { + return email; + } + + @Override + public Collection getAuthorities() { + return authorities == null ? null : new ArrayList<>(authorities); + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + public boolean equals(Object object) { + if (this == object) + return true; + if (object == null || getClass() != object.getClass()) + return false; + UserPrincipal that = (UserPrincipal) object; + return Objects.equals(id, that.id); + } + + public int hashCode() { + return Objects.hash(id); + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } } diff --git a/src/main/java/com/sopromadze/blogapi/service/AlbumService.java b/src/main/java/com/sopromadze/blogapi/service/AlbumService.java index 91e26d88..ef4a3621 100644 --- a/src/main/java/com/sopromadze/blogapi/service/AlbumService.java +++ b/src/main/java/com/sopromadze/blogapi/service/AlbumService.java @@ -1,108 +1,25 @@ package com.sopromadze.blogapi.service; -import com.sopromadze.blogapi.exception.BadRequestException; -import com.sopromadze.blogapi.exception.ResourceNotFoundException; -import com.sopromadze.blogapi.model.album.Album; -import com.sopromadze.blogapi.model.role.RoleName; -import com.sopromadze.blogapi.model.user.User; +import com.sopromadze.blogapi.model.Album; +import com.sopromadze.blogapi.payload.AlbumResponse; import com.sopromadze.blogapi.payload.ApiResponse; import com.sopromadze.blogapi.payload.PagedResponse; -import com.sopromadze.blogapi.repository.AlbumRepository; -import com.sopromadze.blogapi.repository.UserRepository; +import com.sopromadze.blogapi.payload.request.AlbumRequest; import com.sopromadze.blogapi.security.UserPrincipal; -import com.sopromadze.blogapi.util.AppConstants; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.stereotype.Service; -import java.util.Collections; +public interface AlbumService { -@Service -public class AlbumService { - private final AlbumRepository albumRepository; - private final UserRepository userRepository; + PagedResponse getAllAlbums(int page, int size); - @Autowired - public AlbumService(AlbumRepository albumRepository, UserRepository userRepository) { - this.albumRepository = albumRepository; - this.userRepository = userRepository; - } + ResponseEntity addAlbum(AlbumRequest albumRequest, UserPrincipal currentUser); - public PagedResponse getAllAlbums(int page, int size){ - validatePageNumberAndSize(page, size); + ResponseEntity getAlbum(Long id); - Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdAt"); + ResponseEntity updateAlbum(Long id, AlbumRequest newAlbum, UserPrincipal currentUser); - Page albums = albumRepository.findAll(pageable); + ResponseEntity deleteAlbum(Long id, UserPrincipal currentUser); - if (albums.getNumberOfElements() == 0){ - return new PagedResponse<>(Collections.emptyList(), albums.getNumber(), albums.getSize(), albums.getTotalElements(), albums.getTotalPages(), albums.isLast()); - } - return new PagedResponse<>(albums.getContent(), albums.getNumber(), albums.getSize(), albums.getTotalElements(), albums.getTotalPages(), albums.isLast()); - } + PagedResponse getUserAlbums(String username, int page, int size); - public ResponseEntity addAlbum(Album album, UserPrincipal currentUser){ - User user = userRepository.findByUsername(currentUser.getUsername()).orElseThrow(() -> new ResourceNotFoundException("User", "username", currentUser.getUsername())); - album.setUser(user); - Album newAlbum = albumRepository.save(album); - return new ResponseEntity<>(newAlbum, HttpStatus.CREATED); - } - - public ResponseEntity getAlbum(Long id){ - Album album = albumRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Album", "id", id)); - return new ResponseEntity<>(album, HttpStatus.OK); - } - - public ResponseEntity updateAlbum(Long id, Album newAlbum, UserPrincipal currentUser){ - Album album = albumRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Album", "id", id)); - User user = userRepository.findByUsername(currentUser.getUsername()).orElseThrow(() -> new ResourceNotFoundException("User", "username", currentUser.getUsername())); - if (album.getUser().getId().equals(user.getId()) || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))){ - album.setTitle(newAlbum.getTitle()); - Album updatedAlbum = albumRepository.save(album); - return new ResponseEntity<>(updatedAlbum, HttpStatus.OK); - } - return new ResponseEntity<>(new ApiResponse(false, "You don't have permission to make this operation"), HttpStatus.UNAUTHORIZED); - } - - public ResponseEntity deleteAlbum(Long id, UserPrincipal currentUser){ - Album album = albumRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Album", "id", id)); - User user = userRepository.findByUsername(currentUser.getUsername()).orElseThrow(() -> new ResourceNotFoundException("User", "username", currentUser.getUsername())); - if (album.getUser().getId().equals(user.getId()) || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))){ - albumRepository.deleteById(id); - return new ResponseEntity<>(new ApiResponse(true, "You successfully deleted album"), HttpStatus.OK); - } - return new ResponseEntity<>(new ApiResponse(false, "You don't have permission to make this operation"), HttpStatus.UNAUTHORIZED); - } - - public PagedResponse getUserAlbums(String username, int page, int size){ - User user = userRepository.findByUsername(username).orElseThrow(() -> new ResourceNotFoundException("User", "username", username)); - Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdAt"); - - Page albums = albumRepository.findByCreatedBy(user.getId(), pageable); - - if (albums.getNumberOfElements() == 0){ - return new PagedResponse<>(Collections.emptyList(), albums.getNumber(), albums.getSize(), albums.getTotalElements(), albums.getTotalPages(), albums.isLast()); - } - return new PagedResponse<>(albums.getContent(), albums.getNumber(), albums.getSize(), albums.getTotalElements(), albums.getTotalPages(), albums.isLast()); - } - - private void validatePageNumberAndSize(int page, int size) { - if(page < 0) { - throw new BadRequestException("Page number cannot be less than zero."); - } - - if(size < 0) { - throw new BadRequestException("Size number cannot be less than zero."); - } - - if(size > AppConstants.MAX_PAGE_SIZE) { - throw new BadRequestException("Page size must not be greater than " + AppConstants.MAX_PAGE_SIZE); - } - } } diff --git a/src/main/java/com/sopromadze/blogapi/service/CategoryService.java b/src/main/java/com/sopromadze/blogapi/service/CategoryService.java new file mode 100644 index 00000000..65734a3e --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/service/CategoryService.java @@ -0,0 +1,23 @@ +package com.sopromadze.blogapi.service; + +import com.sopromadze.blogapi.exception.UnauthorizedException; +import com.sopromadze.blogapi.model.Category; +import com.sopromadze.blogapi.payload.ApiResponse; +import com.sopromadze.blogapi.payload.PagedResponse; +import com.sopromadze.blogapi.security.UserPrincipal; +import org.springframework.http.ResponseEntity; + +public interface CategoryService { + + PagedResponse getAllCategories(int page, int size); + + ResponseEntity getCategory(Long id); + + ResponseEntity addCategory(Category category, UserPrincipal currentUser); + + ResponseEntity updateCategory(Long id, Category newCategory, UserPrincipal currentUser) + throws UnauthorizedException; + + ResponseEntity deleteCategory(Long id, UserPrincipal currentUser) throws UnauthorizedException; + +} diff --git a/src/main/java/com/sopromadze/blogapi/service/CommentService.java b/src/main/java/com/sopromadze/blogapi/service/CommentService.java index 1940b969..cdbc34de 100644 --- a/src/main/java/com/sopromadze/blogapi/service/CommentService.java +++ b/src/main/java/com/sopromadze/blogapi/service/CommentService.java @@ -1,117 +1,21 @@ package com.sopromadze.blogapi.service; -import com.sopromadze.blogapi.exception.BadRequestException; -import com.sopromadze.blogapi.exception.ResourceNotFoundException; -import com.sopromadze.blogapi.model.comment.Comment; -import com.sopromadze.blogapi.model.post.Post; -import com.sopromadze.blogapi.model.role.RoleName; -import com.sopromadze.blogapi.model.user.User; +import com.sopromadze.blogapi.model.Comment; import com.sopromadze.blogapi.payload.ApiResponse; import com.sopromadze.blogapi.payload.CommentRequest; import com.sopromadze.blogapi.payload.PagedResponse; -import com.sopromadze.blogapi.repository.CommentRepository; -import com.sopromadze.blogapi.repository.PostRepository; -import com.sopromadze.blogapi.repository.UserRepository; import com.sopromadze.blogapi.security.UserPrincipal; -import com.sopromadze.blogapi.util.AppConstants; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.stereotype.Service; -@Service -public class CommentService { - private final CommentRepository commentRepository; - private final PostRepository postRepository; - private final UserRepository userRepository; +public interface CommentService { - @Autowired - public CommentService(CommentRepository commentRepository, PostRepository postRepository, UserRepository userRepository) { - this.commentRepository = commentRepository; - this.postRepository = postRepository; - this.userRepository = userRepository; - } + PagedResponse getAllComments(Long postId, int page, int size); - public PagedResponse getAllComments(Long postId, int page, int size){ - validatePageNumberAndSize(page, size); - Post post = postRepository.findById(postId).orElseThrow(() -> new ResourceNotFoundException("Post", "id", postId)); + Comment addComment(CommentRequest commentRequest, Long postId, UserPrincipal currentUser); - Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdAt"); + Comment getComment(Long postId, Long id); - Page comments = commentRepository.findByPostId(postId, pageable); + Comment updateComment(Long postId, Long id, CommentRequest commentRequest, UserPrincipal currentUser); - return new PagedResponse<>(comments.getContent(), comments.getNumber(), comments.getSize(), comments.getTotalElements(), comments.getTotalPages(), comments.isLast()); - } + ApiResponse deleteComment(Long postId, Long id, UserPrincipal currentUser); - public ResponseEntity addComment(CommentRequest commentRequest, Long postId, UserPrincipal currentUser){ - Post post = postRepository.findById(postId).orElseThrow(() -> new ResourceNotFoundException("Post", "id", postId)); - User user = userRepository.findByUsername(currentUser.getUsername()).orElseThrow(() -> new ResourceNotFoundException("User", "username", currentUser.getUsername())); - Comment comment = new Comment(commentRequest.getBody()); - comment.setUser(user); - comment.setPost(post); - comment.setName(currentUser.getUsername()); - comment.setEmail(currentUser.getEmail()); - Comment newComment = commentRepository.save(comment); - return new ResponseEntity<>(newComment, HttpStatus.CREATED); - } - - public ResponseEntity getComment(Long postId, Long id){ - Post post = postRepository.findById(postId).orElseThrow(() -> new ResourceNotFoundException("Post", "id", postId)); - Comment comment = commentRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Comment", "id", id)); - if (comment.getPost().getId().equals(post.getId())){ - return new ResponseEntity<>(comment, HttpStatus.OK); - } - return new ResponseEntity<>(new ApiResponse(false, "Comment does not belong to post"), HttpStatus.BAD_REQUEST); - } - - public ResponseEntity updateComment(Long postId, Long id, CommentRequest commentRequest, UserPrincipal currentUser){ - Post post = postRepository.findById(postId).orElseThrow(() -> new ResourceNotFoundException("Post", "id", postId)); - Comment comment = commentRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Comment", "id", id)); - - if (!comment.getPost().getId().equals(post.getId())){ - return new ResponseEntity<>(new ApiResponse(false, "Comment does not belong to post"), HttpStatus.BAD_REQUEST); - } - - if (comment.getUser().getId().equals(currentUser.getId()) || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))){ - comment.setBody(commentRequest.getBody()); - Comment updatedComment = commentRepository.save(comment); - return new ResponseEntity<>(updatedComment, HttpStatus.OK); - } - return new ResponseEntity<>(new ApiResponse(false, "You don't have permission to update this comment"), HttpStatus.UNAUTHORIZED); - } - - public ResponseEntity deleteComment(Long postId, Long id, UserPrincipal currentUser){ - Post post = postRepository.findById(postId).orElseThrow(() -> new ResourceNotFoundException("Post", "id", postId)); - Comment comment = commentRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Comment", "id", id)); - - if (!comment.getPost().getId().equals(post.getId())){ - return new ResponseEntity<>(new ApiResponse(false, "Comment does not belong to post"), HttpStatus.BAD_REQUEST); - } - - if (comment.getUser().getId().equals(currentUser.getId()) || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))){ - commentRepository.deleteById(comment.getId()); - return new ResponseEntity<>(new ApiResponse(true, "You successfully deleted comment"), HttpStatus.OK); - } - - return new ResponseEntity<>(new ApiResponse(false, "You don't have permission to delete this comment"), HttpStatus.BAD_REQUEST); - } - - private void validatePageNumberAndSize(int page, int size) { - if(page < 0) { - throw new BadRequestException("Page number cannot be less than zero."); - } - - if(size < 0) { - throw new BadRequestException("Size number cannot be less than zero."); - } - - if(size > AppConstants.MAX_PAGE_SIZE) { - throw new BadRequestException("Page size must not be greater than " + AppConstants.MAX_PAGE_SIZE); - } - } } diff --git a/src/main/java/com/sopromadze/blogapi/service/CustomUserDetailsService.java b/src/main/java/com/sopromadze/blogapi/service/CustomUserDetailsService.java index 357b880b..55084feb 100644 --- a/src/main/java/com/sopromadze/blogapi/service/CustomUserDetailsService.java +++ b/src/main/java/com/sopromadze/blogapi/service/CustomUserDetailsService.java @@ -1,36 +1,12 @@ package com.sopromadze.blogapi.service; -import com.sopromadze.blogapi.model.user.User; -import com.sopromadze.blogapi.repository.UserRepository; -import com.sopromadze.blogapi.security.UserPrincipal; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; -import javax.transaction.Transactional; +public interface CustomUserDetailsService { -@Service -public class CustomUserDetailsService implements UserDetailsService { - private final UserRepository userRepository; + UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException; - @Autowired - public CustomUserDetailsService(UserRepository userRepository) { - this.userRepository = userRepository; - } + UserDetails loadUserById(Long id); - @Override - @Transactional - public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException { - User user = userRepository.findByUsernameOrEmail(usernameOrEmail, usernameOrEmail).orElseThrow(() -> new UsernameNotFoundException(String.format("User not found with this username or email: %s", usernameOrEmail))); - return UserPrincipal.create(user); - } - - @Transactional - public UserDetails loadUserById(Long id){ - User user = userRepository.findById(id).orElseThrow(() -> new UsernameNotFoundException(String.format("User not found with id: %s", id))); - - return UserPrincipal.create(user); - } -} +} \ No newline at end of file diff --git a/src/main/java/com/sopromadze/blogapi/service/PhotoService.java b/src/main/java/com/sopromadze/blogapi/service/PhotoService.java index 5238b2a1..b59c767f 100644 --- a/src/main/java/com/sopromadze/blogapi/service/PhotoService.java +++ b/src/main/java/com/sopromadze/blogapi/service/PhotoService.java @@ -1,125 +1,23 @@ package com.sopromadze.blogapi.service; -import com.sopromadze.blogapi.exception.BadRequestException; -import com.sopromadze.blogapi.exception.ResourceNotFoundException; -import com.sopromadze.blogapi.model.album.Album; -import com.sopromadze.blogapi.model.photo.Photo; -import com.sopromadze.blogapi.model.role.RoleName; import com.sopromadze.blogapi.payload.ApiResponse; import com.sopromadze.blogapi.payload.PagedResponse; import com.sopromadze.blogapi.payload.PhotoRequest; import com.sopromadze.blogapi.payload.PhotoResponse; -import com.sopromadze.blogapi.repository.AlbumRepository; -import com.sopromadze.blogapi.repository.PhotoRepository; import com.sopromadze.blogapi.security.UserPrincipal; -import com.sopromadze.blogapi.util.AppConstants; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +public interface PhotoService { -@Service -public class PhotoService { - private final PhotoRepository photoRepository; - private final AlbumRepository albumRepository; + PagedResponse getAllPhotos(int page, int size); - @Autowired - public PhotoService(PhotoRepository photoRepository, AlbumRepository albumRepository) { - this.photoRepository = photoRepository; - this.albumRepository = albumRepository; - } + PhotoResponse getPhoto(Long id); - public PagedResponse getAllPhotos(int page, int size){ - validatePageNumberAndSize(page, size); + PhotoResponse updatePhoto(Long id, PhotoRequest photoRequest, UserPrincipal currentUser); - Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdAt"); - Page photos = photoRepository.findAll(pageable); + PhotoResponse addPhoto(PhotoRequest photoRequest, UserPrincipal currentUser); - List photoResponses = new ArrayList<>(); - for (Photo photo : photos.getContent()){ - photoResponses.add(new PhotoResponse(photo.getId(), photo.getTitle(), photo.getUrl(), photo.getThumbnailUrl(), photo.getAlbum().getId())); - } + ApiResponse deletePhoto(Long id, UserPrincipal currentUser); - if (photos.getNumberOfElements() == 0){ - return new PagedResponse<>(Collections.emptyList(), photos.getNumber(), photos.getSize(), photos.getTotalElements(), photos.getTotalPages(), photos.isLast()); - } - return new PagedResponse<>(photoResponses, photos.getNumber(), photos.getSize(), photos.getTotalElements(), photos.getTotalPages(), photos.isLast()); + PagedResponse getAllPhotosByAlbum(Long albumId, int page, int size); - } - - public ResponseEntity getPhoto(Long id){ - Photo photo = photoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Photo", "id", id)); - return new ResponseEntity<>(new PhotoResponse(photo.getId(), photo.getTitle(), photo.getUrl(), photo.getThumbnailUrl(), photo.getAlbum().getId()), HttpStatus.OK); - } - - public ResponseEntity updatePhoto(Long id, PhotoRequest photoRequest, UserPrincipal currentUser){ - Album album = albumRepository.findById(photoRequest.getAlbumId()).orElseThrow(() -> new ResourceNotFoundException("Album", "id", photoRequest.getAlbumId())); - Photo photo = photoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Photo", "id", id)); - if(photo.getAlbum().getUser().getId().equals(currentUser.getId()) || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))){ - photo.setTitle(photoRequest.getTitle()); - photo.setThumbnailUrl(photoRequest.getThumbnailUrl()); - photo.setAlbum(album); - Photo updatedPhoto = photoRepository.save(photo); - return new ResponseEntity<>(new PhotoResponse(updatedPhoto.getId(), updatedPhoto.getTitle(), updatedPhoto.getUrl(), updatedPhoto.getThumbnailUrl(), updatedPhoto.getAlbum().getId()), HttpStatus.OK); - } - return new ResponseEntity<>(new ApiResponse(false, "You don't have permission to update this photo"), HttpStatus.UNAUTHORIZED); - } - - public ResponseEntity addPhoto(PhotoRequest photoRequest, UserPrincipal currentUser){ - Album album = albumRepository.findById(photoRequest.getAlbumId()).orElseThrow(() -> new ResourceNotFoundException("Album", "id", photoRequest.getAlbumId())); - if (album.getUser().getId().equals(currentUser.getId())){ - Photo photo = new Photo(photoRequest.getTitle(), photoRequest.getUrl(), photoRequest.getThumbnailUrl(), album); - Photo newPhoto = photoRepository.save(photo); - return new ResponseEntity<>(new PhotoResponse(newPhoto.getId(), newPhoto.getTitle(), newPhoto.getUrl(), newPhoto.getThumbnailUrl(), newPhoto.getAlbum().getId()), HttpStatus.CREATED); - } - return new ResponseEntity<>(new ApiResponse(false, "You don't have permission to add photo in this album"), HttpStatus.UNAUTHORIZED); - } - - public ResponseEntity deletePhoto(Long id, UserPrincipal currentUser){ - Photo photo = photoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Photo", "id", id)); - if(photo.getAlbum().getUser().getId().equals(currentUser.getId()) || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))){ - photoRepository.deleteById(id); - return new ResponseEntity<>(new ApiResponse(true, "Photo deleted successfully"), HttpStatus.OK); - } - return new ResponseEntity<>(new ApiResponse(false, "You don't have permission to delete this photo"), HttpStatus.UNAUTHORIZED); - } - - public PagedResponse getAllPhotosByAlbum(Long albumId, int page, int size){ - Album album = albumRepository.findById(albumId).orElseThrow(() -> new ResourceNotFoundException("Album", "id", albumId)); - validatePageNumberAndSize(page, size); - - Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdAt"); - - Page photos = photoRepository.findByAlbumId(albumId, pageable); - - List photoResponses = new ArrayList<>(); - for(Photo photo : photos.getContent()){ - photoResponses.add(new PhotoResponse(photo.getId(), photo.getTitle(), photo.getUrl(), photo.getThumbnailUrl(), photo.getAlbum().getId())); - } - - return new PagedResponse<>(photoResponses, photos.getNumber(), photos.getSize(), photos.getTotalElements(), photos.getTotalPages(), photos.isLast()); - } - - private void validatePageNumberAndSize(int page, int size) { - if(page < 0) { - throw new BadRequestException("Page number cannot be less than zero."); - } - - if(size < 0) { - throw new BadRequestException("Size number cannot be less than zero."); - } - - if(size > AppConstants.MAX_PAGE_SIZE) { - throw new BadRequestException("Page size must not be greater than " + AppConstants.MAX_PAGE_SIZE); - } - } -} +} \ No newline at end of file diff --git a/src/main/java/com/sopromadze/blogapi/service/PostService.java b/src/main/java/com/sopromadze/blogapi/service/PostService.java index 115c6554..d4754205 100644 --- a/src/main/java/com/sopromadze/blogapi/service/PostService.java +++ b/src/main/java/com/sopromadze/blogapi/service/PostService.java @@ -1,113 +1,28 @@ package com.sopromadze.blogapi.service; -import com.sopromadze.blogapi.exception.BadRequestException; -import com.sopromadze.blogapi.exception.ResourceNotFoundException; -import com.sopromadze.blogapi.model.post.Post; -import com.sopromadze.blogapi.model.role.RoleName; -import com.sopromadze.blogapi.model.user.User; +import com.sopromadze.blogapi.model.Post; import com.sopromadze.blogapi.payload.ApiResponse; import com.sopromadze.blogapi.payload.PagedResponse; -import com.sopromadze.blogapi.repository.PostRepository; -import com.sopromadze.blogapi.repository.UserRepository; +import com.sopromadze.blogapi.payload.PostRequest; +import com.sopromadze.blogapi.payload.PostResponse; import com.sopromadze.blogapi.security.UserPrincipal; -import com.sopromadze.blogapi.util.AppConstants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.stereotype.Service; -import java.util.Collections; +public interface PostService { -@Service -public class PostService { - private final PostRepository postRepository; + PagedResponse getAllPosts(int page, int size); - private final UserRepository userRepository; + PagedResponse getPostsByCreatedBy(String username, int page, int size); - private static final Logger logger = LoggerFactory.getLogger(PostService.class); + PagedResponse getPostsByCategory(Long id, int page, int size); - @Autowired - public PostService(PostRepository postRepository, UserRepository userRepository) { - this.postRepository = postRepository; - this.userRepository = userRepository; - } + PagedResponse getPostsByTag(Long id, int page, int size); - public PagedResponse getAllPosts(int page, int size){ - validatePageNumberAndSize(page, size); + Post updatePost(Long id, PostRequest newPostRequest, UserPrincipal currentUser); - Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdAt"); + ApiResponse deletePost(Long id, UserPrincipal currentUser); - Page posts = postRepository.findAll(pageable); + PostResponse addPost(PostRequest postRequest, UserPrincipal currentUser); - if (posts.getNumberOfElements() == 0){ - return new PagedResponse<>(Collections.emptyList(), posts.getNumber(), posts.getSize(), posts.getTotalElements(), posts.getTotalPages(), posts.isLast()); - } + Post getPost(Long id); - return new PagedResponse<>(posts.getContent(), posts.getNumber(), posts.getSize(), posts.getTotalElements(), posts.getTotalPages(), posts.isLast()); - } - - public PagedResponse getPostsCreatedBy(String username, int page, int size){ - validatePageNumberAndSize(page, size); - User user = userRepository.findByUsername(username).orElseThrow(() -> new ResourceNotFoundException("User", "username", username)); - Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdAt"); - Page posts = postRepository.findByCreatedBy(user.getId(), pageable); - - if(posts.getNumberOfElements() == 0){ - return new PagedResponse<>(Collections.emptyList(), posts.getNumber(), posts.getSize(), posts.getTotalElements(), posts.getTotalPages(), posts.isLast()); - } - return new PagedResponse<>(posts.getContent(), posts.getNumber(), posts.getSize(), posts.getTotalElements(), posts.getTotalPages(), posts.isLast()); - } - - public ResponseEntity updatePost(Long id, Post newPost, UserPrincipal currentUser){ - Post post = postRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Post", "id", id)); - if (post.getUser().getId().equals(currentUser.getId()) || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))){ - post.setTitle(newPost.getTitle()); - post.setBody(newPost.getBody()); - Post updatedPost = postRepository.save(post); - return new ResponseEntity<>(updatedPost, HttpStatus.OK); - } - return new ResponseEntity<>(new ApiResponse(false, "You don't have permission to edit this post"), HttpStatus.UNAUTHORIZED); - } - - public ResponseEntity deletePost(Long id, UserPrincipal currentUser){ - Post post = postRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Post", "id", id)); - if (post.getUser().getId().equals(currentUser.getId()) || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))){ - postRepository.deleteById(id); - return new ResponseEntity<>(new ApiResponse(true, "You successfully deleted post"), HttpStatus.OK); - } - return new ResponseEntity<>(new ApiResponse(true, "You don't have permission to delete this post"), HttpStatus.UNAUTHORIZED); - } - - public ResponseEntity addPost(Post post, UserPrincipal currentUser){ - User user = userRepository.findById(currentUser.getId()).orElseThrow(() -> new ResourceNotFoundException("User", "id", 1L)); - post.setUser(user); - Post newPost = postRepository.save(post); - return new ResponseEntity<>(newPost, HttpStatus.CREATED); - } - - public ResponseEntity getPost(Long id){ - Post post = postRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Post", "id", id)); - return new ResponseEntity<>(post, HttpStatus.OK); - } - - private void validatePageNumberAndSize(int page, int size) { - if(page < 0) { - throw new BadRequestException("Page number cannot be less than zero."); - } - - if(size < 0) { - throw new BadRequestException("Size number cannot be less than zero."); - } - - if(size > AppConstants.MAX_PAGE_SIZE) { - throw new BadRequestException("Page size must not be greater than " + AppConstants.MAX_PAGE_SIZE); - } - } } diff --git a/src/main/java/com/sopromadze/blogapi/service/TagService.java b/src/main/java/com/sopromadze/blogapi/service/TagService.java new file mode 100644 index 00000000..db321cca --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/service/TagService.java @@ -0,0 +1,20 @@ +package com.sopromadze.blogapi.service; + +import com.sopromadze.blogapi.model.Tag; +import com.sopromadze.blogapi.payload.ApiResponse; +import com.sopromadze.blogapi.payload.PagedResponse; +import com.sopromadze.blogapi.security.UserPrincipal; + +public interface TagService { + + PagedResponse getAllTags(int page, int size); + + Tag getTag(Long id); + + Tag addTag(Tag tag, UserPrincipal currentUser); + + Tag updateTag(Long id, Tag newTag, UserPrincipal currentUser); + + ApiResponse deleteTag(Long id, UserPrincipal currentUser); + +} diff --git a/src/main/java/com/sopromadze/blogapi/service/TodoService.java b/src/main/java/com/sopromadze/blogapi/service/TodoService.java index e02a6a46..23e7903d 100644 --- a/src/main/java/com/sopromadze/blogapi/service/TodoService.java +++ b/src/main/java/com/sopromadze/blogapi/service/TodoService.java @@ -1,121 +1,24 @@ package com.sopromadze.blogapi.service; -import com.sopromadze.blogapi.exception.BadRequestException; -import com.sopromadze.blogapi.exception.ResourceNotFoundException; -import com.sopromadze.blogapi.model.todo.Todo; -import com.sopromadze.blogapi.model.user.User; +import com.sopromadze.blogapi.model.Todo; import com.sopromadze.blogapi.payload.ApiResponse; import com.sopromadze.blogapi.payload.PagedResponse; -import com.sopromadze.blogapi.repository.TodoRepository; -import com.sopromadze.blogapi.repository.UserRepository; import com.sopromadze.blogapi.security.UserPrincipal; -import com.sopromadze.blogapi.util.AppConstants; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; -import java.util.Collections; +public interface TodoService { -@Service -public class TodoService { - private final TodoRepository todoRepository; - private final UserRepository userRepository; + Todo completeTodo(Long id, UserPrincipal currentUser); - @Autowired - public TodoService(TodoRepository todoRepository, UserRepository userRepository) { - this.todoRepository = todoRepository; - this.userRepository = userRepository; - } + Todo unCompleteTodo(Long id, UserPrincipal currentUser); - public ResponseEntity completeTodo(Long id, UserPrincipal currentUser) { - Todo todo = todoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Todo", "id", id)); - User user = userRepository.findByUsername(currentUser.getUsername()).orElseThrow(() -> new ResourceNotFoundException("User", "username", currentUser.getUsername())); - if(todo.getUser().getId().equals(user.getId())){ - todo.setCompleted(true); - Todo result = todoRepository.save(todo); - return new ResponseEntity<>(todo, HttpStatus.OK); - } - return todo.getUser().getId().equals(user.getId()) ? new ResponseEntity<>(todo, HttpStatus.OK) : new ResponseEntity<>(new ApiResponse(false, "You don't have permission to make this operation"), HttpStatus.UNAUTHORIZED); - } + PagedResponse getAllTodos(UserPrincipal currentUser, int page, int size); - public ResponseEntity unCompleteTodo(Long id, UserPrincipal currentUser) { - Todo todo = todoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Todo", "id", id)); - User user = userRepository.findByUsername(currentUser.getUsername()).orElseThrow(() -> new ResourceNotFoundException("User", "username", currentUser.getUsername())); - if(todo.getUser().getId().equals(user.getId())){ - todo.setCompleted(false); - Todo result = todoRepository.save(todo); - return new ResponseEntity<>(todo, HttpStatus.OK); - } - return todo.getUser().getId().equals(user.getId()) ? new ResponseEntity<>(todo, HttpStatus.OK) : new ResponseEntity<>(new ApiResponse(false, "You don't have permission to make this operation"), HttpStatus.UNAUTHORIZED); - } + Todo addTodo(Todo todo, UserPrincipal currentUser); - public PagedResponse getAllTodos(UserPrincipal currentUser, int page, int size){ - validatePageNumberAndSize(page, size); - Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdAt"); + Todo getTodo(Long id, UserPrincipal currentUser); - Page todos = todoRepository.findByCreatedBy(currentUser.getId(), pageable); + Todo updateTodo(Long id, Todo newTodo, UserPrincipal currentUser); - if (todos.getNumberOfElements() == 0){ - return new PagedResponse<>(Collections.emptyList(), todos.getNumber(), todos.getSize(), todos.getTotalElements(), todos.getTotalPages(), todos.isLast()); - } + ApiResponse deleteTodo(Long id, UserPrincipal currentUser); - return new PagedResponse<>(todos.getContent(), todos.getNumber(), todos.getSize(), todos.getTotalElements(), todos.getTotalPages(), todos.isLast()); - } - - public ResponseEntity addTodo(Todo todo, UserPrincipal currentUser){ - User user = userRepository.findByUsername(currentUser.getUsername()).orElseThrow(() -> new ResourceNotFoundException("User", "username", currentUser.getUsername())); - todo.setUser(user); - Todo result = todoRepository.save(todo); - return new ResponseEntity<>(result, HttpStatus.CREATED); - } - - public ResponseEntity getTodo(Long id, UserPrincipal currentUser){ - User user = userRepository.findByUsername(currentUser.getUsername()).orElseThrow(() -> new ResourceNotFoundException("User", "username", currentUser.getUsername())); - Todo todo = todoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Todo", "id", id)); - - return todo.getUser().getId().equals(user.getId()) ? new ResponseEntity<>(todo, HttpStatus.OK) : new ResponseEntity<>(new ApiResponse(false, "You don't have permission to make this operation"), HttpStatus.UNAUTHORIZED); - } - - public ResponseEntity updateTodo(Long id, Todo newTodo, UserPrincipal currentUser){ - User user = userRepository.findByUsername(currentUser.getUsername()).orElseThrow(() -> new ResourceNotFoundException("User", "username", currentUser.getUsername())); - Todo todo = todoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Todo", "id", id)); - if(todo.getUser().getId().equals(user.getId())){ - todo.setTitle(newTodo.getTitle()); - todo.setCompleted(newTodo.getCompleted()); - Todo result = todoRepository.save(todo); - return new ResponseEntity<>(result, HttpStatus.OK); - } - return new ResponseEntity<>(new ApiResponse(false, "You don't have permission to make this operation"), HttpStatus.UNAUTHORIZED); - } - - public ResponseEntity deleteTodo(Long id, UserPrincipal currentUser){ - User user = userRepository.findByUsername(currentUser.getUsername()).orElseThrow(() -> new ResourceNotFoundException("User", "username", currentUser.getUsername())); - Todo todo = todoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Todo", "id", id)); - - if(todo.getUser().getId().equals(user.getId())){ - todoRepository.deleteById(id); - return new ResponseEntity<>(new ApiResponse(true, "You successfully deleted todo"), HttpStatus.OK); - } - return new ResponseEntity<>(new ApiResponse(false, "You don't have permission to make this operation"), HttpStatus.UNAUTHORIZED); - } - - - private void validatePageNumberAndSize(int page, int size) { - if(page < 0) { - throw new BadRequestException("Page number cannot be less than zero."); - } - - if(size < 0) { - throw new BadRequestException("Size number cannot be less than zero."); - } - - if(size > AppConstants.MAX_PAGE_SIZE) { - throw new BadRequestException("Page size must not be greater than " + AppConstants.MAX_PAGE_SIZE); - } - } } diff --git a/src/main/java/com/sopromadze/blogapi/service/UserService.java b/src/main/java/com/sopromadze/blogapi/service/UserService.java index 0d3dad28..a134a637 100644 --- a/src/main/java/com/sopromadze/blogapi/service/UserService.java +++ b/src/main/java/com/sopromadze/blogapi/service/UserService.java @@ -1,150 +1,33 @@ package com.sopromadze.blogapi.service; -import com.sopromadze.blogapi.exception.AppException; -import com.sopromadze.blogapi.exception.ResourceNotFoundException; -import com.sopromadze.blogapi.model.role.Role; -import com.sopromadze.blogapi.model.role.RoleName; -import com.sopromadze.blogapi.model.user.Address; -import com.sopromadze.blogapi.model.user.Company; -import com.sopromadze.blogapi.model.user.Geo; import com.sopromadze.blogapi.model.user.User; -import com.sopromadze.blogapi.payload.*; -import com.sopromadze.blogapi.repository.PostRepository; -import com.sopromadze.blogapi.repository.RoleRepository; -import com.sopromadze.blogapi.repository.UserRepository; +import com.sopromadze.blogapi.payload.ApiResponse; +import com.sopromadze.blogapi.payload.InfoRequest; +import com.sopromadze.blogapi.payload.UserIdentityAvailability; +import com.sopromadze.blogapi.payload.UserProfile; +import com.sopromadze.blogapi.payload.UserSummary; import com.sopromadze.blogapi.security.UserPrincipal; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.List; +public interface UserService { -@Service -public class UserService { - private final UserRepository userRepository; - private final PostRepository postRepository; - private final RoleRepository roleRepository; - private final PasswordEncoder passwordEncoder; + UserSummary getCurrentUser(UserPrincipal currentUser); - @Autowired - public UserService(UserRepository userRepository, PostRepository postRepository, RoleRepository roleRepository, PasswordEncoder passwordEncoder) { - this.userRepository = userRepository; - this.postRepository = postRepository; - this.roleRepository = roleRepository; - this.passwordEncoder = passwordEncoder; - } + UserIdentityAvailability checkUsernameAvailability(String username); - public UserSummary getCurrentUser(UserPrincipal currentUser){ - return new UserSummary(currentUser.getId(), currentUser.getUsername(), currentUser.getFirstName(), currentUser.getLastName()); - } + UserIdentityAvailability checkEmailAvailability(String email); - public UserIdentityAvailability checkUsernameAvailability(String username){ - Boolean isAvailable = !userRepository.existsByUsername(username); - return new UserIdentityAvailability(isAvailable); - } + UserProfile getUserProfile(String username); - public UserIdentityAvailability checkEmailAvailability(String email){ - Boolean isAvailable = !userRepository.existsByEmail(email); - return new UserIdentityAvailability(isAvailable); - } + User addUser(User user); - public UserProfile getUserProfile(String username){ - User user = userRepository.findByUsername(username).orElseThrow(() -> new ResourceNotFoundException("User", "username", username)); + User updateUser(User newUser, String username, UserPrincipal currentUser); - Long postCount = postRepository.countByCreatedBy(user.getId()); + ApiResponse deleteUser(String username, UserPrincipal currentUser); - return new UserProfile(user.getId(), user.getUsername(), user.getFirstName(), user.getLastName(), user.getCreatedAt(), user.getEmail(), user.getAddress(), user.getPhone(), user.getWebsite(), user.getCompany(), postCount); - } + ApiResponse giveAdmin(String username); - public ResponseEntity addUser(User user){ - if(userRepository.existsByUsername(user.getUsername())){ - return new ResponseEntity<>(new ApiResponse(false, "Username is already taken"), HttpStatus.BAD_REQUEST); - } + ApiResponse removeAdmin(String username); - if(userRepository.existsByEmail(user.getEmail())){ - return new ResponseEntity<>(new ApiResponse(false, "Email is already taken"), HttpStatus.BAD_REQUEST); - } + UserProfile setOrUpdateInfo(UserPrincipal currentUser, InfoRequest infoRequest); - List roles = new ArrayList<>(); - roles.add(roleRepository.findByName(RoleName.ROLE_USER).orElseThrow(() -> new AppException("User role not set"))); - user.setRoles(roles); - - user.setPassword(passwordEncoder.encode(user.getPassword())); - User result = userRepository.save(user); - return new ResponseEntity<>(result, HttpStatus.CREATED); - } - - public ResponseEntity updateUser(User newUser, String username, UserPrincipal currentUser){ - User user = userRepository.findByUsername(username).orElseThrow(() -> new ResourceNotFoundException("User", "username", username)); - if(user.getId().equals(currentUser.getId()) || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))){ - user.setFirstName(newUser.getFirstName()); - user.setLastName(newUser.getLastName()); - user.setPassword(passwordEncoder.encode(newUser.getPassword())); - user.setAddress(newUser.getAddress()); - user.setPhone(newUser.getPhone()); - user.setWebsite(newUser.getWebsite()); - user.setCompany(newUser.getCompany()); - - User updatedUser = userRepository.save(user); - return new ResponseEntity<>(updatedUser, HttpStatus.OK); - - } - - return new ResponseEntity<>(new ApiResponse(false, "You don't have permission to update profile of: " + username), HttpStatus.UNAUTHORIZED); - - } - - public ResponseEntity deleteUser(String username, UserPrincipal currentUser){ - User user = userRepository.findByUsername(username).orElseThrow(() -> new ResourceNotFoundException("User", "id", username)); - if(!user.getId().equals(currentUser.getId())){ - return new ResponseEntity<>(new ApiResponse(false, "You don't have permission to delete profile of: " + username), HttpStatus.UNAUTHORIZED); - } - userRepository.deleteById(user.getId()); - - return new ResponseEntity<>(new ApiResponse(true, "You successfully deleted profile of: " + username), HttpStatus.OK); - } - - public ResponseEntity giveAdmin(String username){ - User user = userRepository.findByUsername(username).orElseThrow(() -> new ResourceNotFoundException("User", "username", username)); - List roles = new ArrayList<>(); - roles.add(roleRepository.findByName(RoleName.ROLE_ADMIN).orElseThrow(() -> new AppException("User role not set"))); - roles.add(roleRepository.findByName(RoleName.ROLE_USER).orElseThrow(() -> new AppException("User role not set"))); - user.setRoles(roles); - userRepository.save(user); - return new ResponseEntity<>(new ApiResponse(true, "You gave ADMIN role to user: " + username), HttpStatus.OK); - } - - public ResponseEntity takeAdmin(String username){ - User user = userRepository.findByUsername(username).orElseThrow(() -> new ResourceNotFoundException("User", "username", username)); - List roles = new ArrayList<>(); - roles.add(roleRepository.findByName(RoleName.ROLE_USER).orElseThrow(() -> new AppException("User role not set"))); - user.setRoles(roles); - userRepository.save(user); - return new ResponseEntity<>(new ApiResponse(true, "You took ADMIN role from user: " + username), HttpStatus.OK); - } - - public ResponseEntity setOrUpdateInfo(UserPrincipal currentUser, InfoRequest infoRequest) { - User user = userRepository.findByUsername(currentUser.getUsername()).orElseThrow(() -> new ResourceNotFoundException("User", "username", currentUser.getUsername())); - Geo geo = new Geo(infoRequest.getLat(), infoRequest.getLng()); - Address address = new Address(infoRequest.getStreet(), infoRequest.getSuite(), infoRequest.getCity(), infoRequest.getZipcode(), geo); - Company company = new Company(infoRequest.getCompanyName(), infoRequest.getCatchPhrase(), infoRequest.getBs()); - if (user.getId().equals(currentUser.getId()) || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))){ - user.setAddress(address); - user.setCompany(company); - user.setWebsite(infoRequest.getWebsite()); - user.setPhone(infoRequest.getPhone()); - User updatedUser = userRepository.save(user); - - Long postCount = postRepository.countByCreatedBy(updatedUser.getId()); - - - UserProfile userProfile = new UserProfile(updatedUser.getId(), updatedUser.getUsername(), updatedUser.getFirstName(), updatedUser.getLastName(), updatedUser.getCreatedAt(), updatedUser.getEmail(), updatedUser.getAddress(), updatedUser.getPhone(), updatedUser.getWebsite(), updatedUser.getCompany(), postCount); - return new ResponseEntity<>(userProfile, HttpStatus.OK); - } - return new ResponseEntity<>(new ApiResponse(false, "You don't have permission to update users profile"), HttpStatus.OK); - } -} +} \ No newline at end of file diff --git a/src/main/java/com/sopromadze/blogapi/service/impl/AlbumServiceImpl.java b/src/main/java/com/sopromadze/blogapi/service/impl/AlbumServiceImpl.java new file mode 100644 index 00000000..6964008e --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/service/impl/AlbumServiceImpl.java @@ -0,0 +1,133 @@ +package com.sopromadze.blogapi.service.impl; + +import com.sopromadze.blogapi.exception.BlogapiException; +import com.sopromadze.blogapi.exception.ResourceNotFoundException; +import com.sopromadze.blogapi.model.Album; +import com.sopromadze.blogapi.model.role.RoleName; +import com.sopromadze.blogapi.model.user.User; +import com.sopromadze.blogapi.payload.AlbumResponse; +import com.sopromadze.blogapi.payload.ApiResponse; +import com.sopromadze.blogapi.payload.PagedResponse; +import com.sopromadze.blogapi.payload.request.AlbumRequest; +import com.sopromadze.blogapi.repository.AlbumRepository; +import com.sopromadze.blogapi.repository.UserRepository; +import com.sopromadze.blogapi.security.UserPrincipal; +import com.sopromadze.blogapi.service.AlbumService; +import com.sopromadze.blogapi.utils.AppUtils; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static com.sopromadze.blogapi.utils.AppConstants.ID; + +@Service +public class AlbumServiceImpl implements AlbumService { + private static final String CREATED_AT = "createdAt"; + + private static final String ALBUM_STR = "Album"; + + private static final String YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION = "You don't have permission to make this operation"; + + @Autowired + private AlbumRepository albumRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private ModelMapper modelMapper; + + @Override + public PagedResponse getAllAlbums(int page, int size) { + AppUtils.validatePageNumberAndSize(page, size); + + Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, CREATED_AT); + + Page albums = albumRepository.findAll(pageable); + + if (albums.getNumberOfElements() == 0) { + return new PagedResponse<>(Collections.emptyList(), albums.getNumber(), albums.getSize(), albums.getTotalElements(), + albums.getTotalPages(), albums.isLast()); + } + + List albumResponses = Arrays.asList(modelMapper.map(albums.getContent(), AlbumResponse[].class)); + + return new PagedResponse<>(albumResponses, albums.getNumber(), albums.getSize(), albums.getTotalElements(), albums.getTotalPages(), + albums.isLast()); + } + + @Override + public ResponseEntity addAlbum(AlbumRequest albumRequest, UserPrincipal currentUser) { + User user = userRepository.getUser(currentUser); + + Album album = new Album(); + + modelMapper.map(albumRequest, album); + + album.setUser(user); + Album newAlbum = albumRepository.save(album); + return new ResponseEntity<>(newAlbum, HttpStatus.CREATED); + } + + @Override + public ResponseEntity getAlbum(Long id) { + Album album = albumRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(ALBUM_STR, ID, id)); + return new ResponseEntity<>(album, HttpStatus.OK); + } + + @Override + public ResponseEntity updateAlbum(Long id, AlbumRequest newAlbum, UserPrincipal currentUser) { + Album album = albumRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(ALBUM_STR, ID, id)); + User user = userRepository.getUser(currentUser); + if (album.getUser().getId().equals(user.getId()) || currentUser.getAuthorities() + .contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { + album.setTitle(newAlbum.getTitle()); + Album updatedAlbum = albumRepository.save(album); + + AlbumResponse albumResponse = new AlbumResponse(); + + modelMapper.map(updatedAlbum, albumResponse); + + return new ResponseEntity<>(albumResponse, HttpStatus.OK); + } + + throw new BlogapiException(HttpStatus.UNAUTHORIZED, YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION); + } + + @Override + public ResponseEntity deleteAlbum(Long id, UserPrincipal currentUser) { + Album album = albumRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(ALBUM_STR, ID, id)); + User user = userRepository.getUser(currentUser); + if (album.getUser().getId().equals(user.getId()) || currentUser.getAuthorities() + .contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { + albumRepository.deleteById(id); + return new ResponseEntity<>(new ApiResponse(Boolean.TRUE, "You successfully deleted album"), HttpStatus.OK); + } + + throw new BlogapiException(HttpStatus.UNAUTHORIZED, YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION); + } + + @Override + public PagedResponse getUserAlbums(String username, int page, int size) { + User user = userRepository.getUserByName(username); + + Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, CREATED_AT); + + Page albums = albumRepository.findByCreatedBy(user.getId(), pageable); + + List content = albums.getNumberOfElements() > 0 ? albums.getContent() : Collections.emptyList(); + + return new PagedResponse<>(content, albums.getNumber(), albums.getSize(), albums.getTotalElements(), albums.getTotalPages(), albums.isLast()); + } +} diff --git a/src/main/java/com/sopromadze/blogapi/service/impl/CategoryServiceImpl.java b/src/main/java/com/sopromadze/blogapi/service/impl/CategoryServiceImpl.java new file mode 100644 index 00000000..a98064b3 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/service/impl/CategoryServiceImpl.java @@ -0,0 +1,103 @@ +package com.sopromadze.blogapi.service.impl; + +import com.sopromadze.blogapi.exception.ResourceNotFoundException; +import com.sopromadze.blogapi.exception.UnauthorizedException; +import com.sopromadze.blogapi.model.Category; +import com.sopromadze.blogapi.model.role.RoleName; +import com.sopromadze.blogapi.payload.ApiResponse; +import com.sopromadze.blogapi.payload.PagedResponse; +import com.sopromadze.blogapi.repository.CategoryRepository; +import com.sopromadze.blogapi.security.UserPrincipal; +import com.sopromadze.blogapi.service.CategoryService; +import com.sopromadze.blogapi.utils.AppUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; + +@Service +public class CategoryServiceImpl implements CategoryService { + + @Autowired + private CategoryRepository categoryRepository; + + @Override + public PagedResponse getAllCategories(int page, int size) { + AppUtils.validatePageNumberAndSize(page, size); + + Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdAt"); + + Page categories = categoryRepository.findAll(pageable); + + List content = categories.getNumberOfElements() == 0 ? Collections.emptyList() : categories.getContent(); + + return new PagedResponse<>(content, categories.getNumber(), categories.getSize(), categories.getTotalElements(), + categories.getTotalPages(), categories.isLast()); + } + + @Override + public ResponseEntity getCategory(Long id) { + Category category = categoryRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Category", "id", id)); + return new ResponseEntity<>(category, HttpStatus.OK); + } + + @Override + public ResponseEntity addCategory(Category category, UserPrincipal currentUser) { + Category newCategory = categoryRepository.save(category); + return new ResponseEntity<>(newCategory, HttpStatus.CREATED); + } + + @Override + public ResponseEntity updateCategory(Long id, Category newCategory, UserPrincipal currentUser) { + Category category = categoryRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Category", "id", id)); + if (category.getCreatedBy().equals(currentUser.getId()) || currentUser.getAuthorities() + .contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { + category.setName(newCategory.getName()); + Category updatedCategory = categoryRepository.save(category); + return new ResponseEntity<>(updatedCategory, HttpStatus.OK); + } + + throw new UnauthorizedException("You don't have permission to edit this category"); + } + + @Override + public ResponseEntity deleteCategory(Long id, UserPrincipal currentUser) { + Category category = categoryRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("category", "id", id)); + if (category.getCreatedBy().equals(currentUser.getId()) || currentUser.getAuthorities() + .contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { + categoryRepository.deleteById(id); + return new ResponseEntity<>(new ApiResponse(Boolean.TRUE, "You successfully deleted category"), HttpStatus.OK); + } + throw new UnauthorizedException("You don't have permission to delete this category"); + } +} + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/sopromadze/blogapi/service/impl/CommentServiceImpl.java b/src/main/java/com/sopromadze/blogapi/service/impl/CommentServiceImpl.java new file mode 100644 index 00000000..00258de7 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/service/impl/CommentServiceImpl.java @@ -0,0 +1,127 @@ +package com.sopromadze.blogapi.service.impl; + +import com.sopromadze.blogapi.exception.BlogapiException; +import com.sopromadze.blogapi.exception.ResourceNotFoundException; +import com.sopromadze.blogapi.model.Comment; +import com.sopromadze.blogapi.model.Post; +import com.sopromadze.blogapi.model.role.RoleName; +import com.sopromadze.blogapi.model.user.User; +import com.sopromadze.blogapi.payload.ApiResponse; +import com.sopromadze.blogapi.payload.CommentRequest; +import com.sopromadze.blogapi.payload.PagedResponse; +import com.sopromadze.blogapi.repository.CommentRepository; +import com.sopromadze.blogapi.repository.PostRepository; +import com.sopromadze.blogapi.repository.UserRepository; +import com.sopromadze.blogapi.security.UserPrincipal; +import com.sopromadze.blogapi.service.CommentService; +import com.sopromadze.blogapi.utils.AppUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Service; + +@Service +public class CommentServiceImpl implements CommentService { + private static final String THIS_COMMENT = " this comment"; + + private static final String YOU_DON_T_HAVE_PERMISSION_TO = "You don't have permission to "; + + private static final String ID_STR = "id"; + + private static final String COMMENT_STR = "Comment"; + + private static final String POST_STR = "Post"; + + private static final String COMMENT_DOES_NOT_BELONG_TO_POST = "Comment does not belong to post"; + + @Autowired + private CommentRepository commentRepository; + + @Autowired + private PostRepository postRepository; + + @Autowired + private UserRepository userRepository; + + @Override + public PagedResponse getAllComments(Long postId, int page, int size) { + AppUtils.validatePageNumberAndSize(page, size); + Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdAt"); + + Page comments = commentRepository.findByPostId(postId, pageable); + + return new PagedResponse<>(comments.getContent(), comments.getNumber(), comments.getSize(), + comments.getTotalElements(), comments.getTotalPages(), comments.isLast()); + } + + @Override + public Comment addComment(CommentRequest commentRequest, Long postId, UserPrincipal currentUser) { + Post post = postRepository.findById(postId) + .orElseThrow(() -> new ResourceNotFoundException(POST_STR, ID_STR, postId)); + User user = userRepository.getUser(currentUser); + Comment comment = new Comment(commentRequest.getBody()); + comment.setUser(user); + comment.setPost(post); + comment.setName(currentUser.getUsername()); + comment.setEmail(currentUser.getEmail()); + return commentRepository.save(comment); + } + + @Override + public Comment getComment(Long postId, Long id) { + Post post = postRepository.findById(postId) + .orElseThrow(() -> new ResourceNotFoundException(POST_STR, ID_STR, postId)); + Comment comment = commentRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException(COMMENT_STR, ID_STR, id)); + if (comment.getPost().getId().equals(post.getId())) { + return comment; + } + + throw new BlogapiException(HttpStatus.BAD_REQUEST, COMMENT_DOES_NOT_BELONG_TO_POST); + } + + @Override + public Comment updateComment(Long postId, Long id, CommentRequest commentRequest, + UserPrincipal currentUser) { + Post post = postRepository.findById(postId) + .orElseThrow(() -> new ResourceNotFoundException(POST_STR, ID_STR, postId)); + Comment comment = commentRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException(COMMENT_STR, ID_STR, id)); + + if (!comment.getPost().getId().equals(post.getId())) { + throw new BlogapiException(HttpStatus.BAD_REQUEST, COMMENT_DOES_NOT_BELONG_TO_POST); + } + + if (comment.getUser().getId().equals(currentUser.getId()) + || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { + comment.setBody(commentRequest.getBody()); + return commentRepository.save(comment); + } + + throw new BlogapiException(HttpStatus.UNAUTHORIZED, YOU_DON_T_HAVE_PERMISSION_TO + "update" + THIS_COMMENT); + } + + @Override + public ApiResponse deleteComment(Long postId, Long id, UserPrincipal currentUser) { + Post post = postRepository.findById(postId) + .orElseThrow(() -> new ResourceNotFoundException(POST_STR, ID_STR, postId)); + Comment comment = commentRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException(COMMENT_STR, ID_STR, id)); + + if (!comment.getPost().getId().equals(post.getId())) { + return new ApiResponse(Boolean.FALSE, COMMENT_DOES_NOT_BELONG_TO_POST); + } + + if (comment.getUser().getId().equals(currentUser.getId()) + || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { + commentRepository.deleteById(comment.getId()); + return new ApiResponse(Boolean.TRUE, "You successfully deleted comment"); + } + + throw new BlogapiException(HttpStatus.UNAUTHORIZED, YOU_DON_T_HAVE_PERMISSION_TO + "delete" + THIS_COMMENT); + } +} diff --git a/src/main/java/com/sopromadze/blogapi/service/impl/CustomUserDetailsServiceImpl.java b/src/main/java/com/sopromadze/blogapi/service/impl/CustomUserDetailsServiceImpl.java new file mode 100644 index 00000000..27451513 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/service/impl/CustomUserDetailsServiceImpl.java @@ -0,0 +1,35 @@ +package com.sopromadze.blogapi.service.impl; + +import com.sopromadze.blogapi.model.user.User; +import com.sopromadze.blogapi.repository.UserRepository; +import com.sopromadze.blogapi.security.UserPrincipal; +import com.sopromadze.blogapi.service.CustomUserDetailsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; + +@Service +public class CustomUserDetailsServiceImpl implements UserDetailsService, CustomUserDetailsService { + @Autowired + private UserRepository userRepository; + + @Override + @Transactional + public UserDetails loadUserByUsername(String usernameOrEmail) { + User user = userRepository.findByUsernameOrEmail(usernameOrEmail, usernameOrEmail) + .orElseThrow(() -> new UsernameNotFoundException(String.format("User not found with this username or email: %s", usernameOrEmail))); + return UserPrincipal.create(user); + } + + @Override + @Transactional + public UserDetails loadUserById(Long id) { + User user = userRepository.findById(id).orElseThrow(() -> new UsernameNotFoundException(String.format("User not found with id: %s", id))); + + return UserPrincipal.create(user); + } +} diff --git a/src/main/java/com/sopromadze/blogapi/service/impl/PhotoServiceImpl.java b/src/main/java/com/sopromadze/blogapi/service/impl/PhotoServiceImpl.java new file mode 100644 index 00000000..dfa90b86 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/service/impl/PhotoServiceImpl.java @@ -0,0 +1,142 @@ +package com.sopromadze.blogapi.service.impl; + +import com.sopromadze.blogapi.exception.ResourceNotFoundException; +import com.sopromadze.blogapi.exception.UnauthorizedException; +import com.sopromadze.blogapi.model.Album; +import com.sopromadze.blogapi.model.Photo; +import com.sopromadze.blogapi.model.role.RoleName; +import com.sopromadze.blogapi.payload.ApiResponse; +import com.sopromadze.blogapi.payload.PagedResponse; +import com.sopromadze.blogapi.payload.PhotoRequest; +import com.sopromadze.blogapi.payload.PhotoResponse; +import com.sopromadze.blogapi.repository.AlbumRepository; +import com.sopromadze.blogapi.repository.PhotoRepository; +import com.sopromadze.blogapi.security.UserPrincipal; +import com.sopromadze.blogapi.service.PhotoService; +import com.sopromadze.blogapi.utils.AppConstants; +import com.sopromadze.blogapi.utils.AppUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static com.sopromadze.blogapi.utils.AppConstants.ALBUM; +import static com.sopromadze.blogapi.utils.AppConstants.CREATED_AT; +import static com.sopromadze.blogapi.utils.AppConstants.ID; +import static com.sopromadze.blogapi.utils.AppConstants.PHOTO; + +@Service +public class PhotoServiceImpl implements PhotoService { + + @Autowired + private PhotoRepository photoRepository; + + @Autowired + private AlbumRepository albumRepository; + + @Override + public PagedResponse getAllPhotos(int page, int size) { + AppUtils.validatePageNumberAndSize(page, size); + + Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, CREATED_AT); + Page photos = photoRepository.findAll(pageable); + + List photoResponses = new ArrayList<>(photos.getContent().size()); + for (Photo photo : photos.getContent()) { + photoResponses.add(new PhotoResponse(photo.getId(), photo.getTitle(), photo.getUrl(), + photo.getThumbnailUrl(), photo.getAlbum().getId())); + } + + if (photos.getNumberOfElements() == 0) { + return new PagedResponse<>(Collections.emptyList(), photos.getNumber(), photos.getSize(), + photos.getTotalElements(), photos.getTotalPages(), photos.isLast()); + } + return new PagedResponse<>(photoResponses, photos.getNumber(), photos.getSize(), photos.getTotalElements(), + photos.getTotalPages(), photos.isLast()); + + } + + @Override + public PhotoResponse getPhoto(Long id) { + Photo photo = photoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(PHOTO, ID, id)); + + return new PhotoResponse(photo.getId(), photo.getTitle(), photo.getUrl(), + photo.getThumbnailUrl(), photo.getAlbum().getId()); + } + + @Override + public PhotoResponse updatePhoto(Long id, PhotoRequest photoRequest, UserPrincipal currentUser) { + Album album = albumRepository.findById(photoRequest.getAlbumId()) + .orElseThrow(() -> new ResourceNotFoundException(ALBUM, ID, photoRequest.getAlbumId())); + Photo photo = photoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(PHOTO, ID, id)); + if (photo.getAlbum().getUser().getId().equals(currentUser.getId()) + || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { + photo.setTitle(photoRequest.getTitle()); + photo.setThumbnailUrl(photoRequest.getThumbnailUrl()); + photo.setAlbum(album); + Photo updatedPhoto = photoRepository.save(photo); + return new PhotoResponse(updatedPhoto.getId(), updatedPhoto.getTitle(), + updatedPhoto.getUrl(), updatedPhoto.getThumbnailUrl(), updatedPhoto.getAlbum().getId()); + } + + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to update this photo"); + + throw new UnauthorizedException(apiResponse); + } + + @Override + public PhotoResponse addPhoto(PhotoRequest photoRequest, UserPrincipal currentUser) { + Album album = albumRepository.findById(photoRequest.getAlbumId()) + .orElseThrow(() -> new ResourceNotFoundException(ALBUM, ID, photoRequest.getAlbumId())); + if (album.getUser().getId().equals(currentUser.getId())) { + Photo photo = new Photo(photoRequest.getTitle(), photoRequest.getUrl(), photoRequest.getThumbnailUrl(), + album); + Photo newPhoto = photoRepository.save(photo); + return new PhotoResponse(newPhoto.getId(), newPhoto.getTitle(), newPhoto.getUrl(), + newPhoto.getThumbnailUrl(), newPhoto.getAlbum().getId()); + } + + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to add photo in this album"); + + throw new UnauthorizedException(apiResponse); + } + + @Override + public ApiResponse deletePhoto(Long id, UserPrincipal currentUser) { + Photo photo = photoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(PHOTO, ID, id)); + if (photo.getAlbum().getUser().getId().equals(currentUser.getId()) + || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { + photoRepository.deleteById(id); + return new ApiResponse(Boolean.TRUE, "Photo deleted successfully"); + } + + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to delete this photo"); + + throw new UnauthorizedException(apiResponse); + } + + @Override + public PagedResponse getAllPhotosByAlbum(Long albumId, int page, int size) { + AppUtils.validatePageNumberAndSize(page, size); + + Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, AppConstants.CREATED_AT); + + Page photos = photoRepository.findByAlbumId(albumId, pageable); + + List photoResponses = new ArrayList<>(photos.getContent().size()); + for (Photo photo : photos.getContent()) { + photoResponses.add(new PhotoResponse(photo.getId(), photo.getTitle(), photo.getUrl(), + photo.getThumbnailUrl(), photo.getAlbum().getId())); + } + + return new PagedResponse<>(photoResponses, photos.getNumber(), photos.getSize(), photos.getTotalElements(), + photos.getTotalPages(), photos.isLast()); + } +} diff --git a/src/main/java/com/sopromadze/blogapi/service/impl/PostServiceImpl.java b/src/main/java/com/sopromadze/blogapi/service/impl/PostServiceImpl.java new file mode 100644 index 00000000..8666ec58 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/service/impl/PostServiceImpl.java @@ -0,0 +1,205 @@ +package com.sopromadze.blogapi.service.impl; + +import com.sopromadze.blogapi.exception.BadRequestException; +import com.sopromadze.blogapi.exception.ResourceNotFoundException; +import com.sopromadze.blogapi.exception.UnauthorizedException; +import com.sopromadze.blogapi.model.Category; +import com.sopromadze.blogapi.model.Post; +import com.sopromadze.blogapi.model.Tag; +import com.sopromadze.blogapi.model.role.RoleName; +import com.sopromadze.blogapi.model.user.User; +import com.sopromadze.blogapi.payload.ApiResponse; +import com.sopromadze.blogapi.payload.PagedResponse; +import com.sopromadze.blogapi.payload.PostRequest; +import com.sopromadze.blogapi.payload.PostResponse; +import com.sopromadze.blogapi.repository.CategoryRepository; +import com.sopromadze.blogapi.repository.PostRepository; +import com.sopromadze.blogapi.repository.TagRepository; +import com.sopromadze.blogapi.repository.UserRepository; +import com.sopromadze.blogapi.security.UserPrincipal; +import com.sopromadze.blogapi.service.PostService; +import com.sopromadze.blogapi.utils.AppConstants; +import com.sopromadze.blogapi.utils.AppUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static com.sopromadze.blogapi.utils.AppConstants.CATEGORY; +import static com.sopromadze.blogapi.utils.AppConstants.CREATED_AT; +import static com.sopromadze.blogapi.utils.AppConstants.ID; +import static com.sopromadze.blogapi.utils.AppConstants.POST; +import static com.sopromadze.blogapi.utils.AppConstants.TAG; +import static com.sopromadze.blogapi.utils.AppConstants.USER; + +@Service +public class PostServiceImpl implements PostService { + @Autowired + private PostRepository postRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private CategoryRepository categoryRepository; + + @Autowired + private TagRepository tagRepository; + + @Override + public PagedResponse getAllPosts(int page, int size) { + validatePageNumberAndSize(page, size); + + Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, CREATED_AT); + + Page posts = postRepository.findAll(pageable); + + List content = posts.getNumberOfElements() == 0 ? Collections.emptyList() : posts.getContent(); + + return new PagedResponse<>(content, posts.getNumber(), posts.getSize(), posts.getTotalElements(), + posts.getTotalPages(), posts.isLast()); + } + + @Override + public PagedResponse getPostsByCreatedBy(String username, int page, int size) { + validatePageNumberAndSize(page, size); + User user = userRepository.getUserByName(username); + Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, CREATED_AT); + Page posts = postRepository.findByCreatedBy(user.getId(), pageable); + + List content = posts.getNumberOfElements() == 0 ? Collections.emptyList() : posts.getContent(); + + return new PagedResponse<>(content, posts.getNumber(), posts.getSize(), posts.getTotalElements(), + posts.getTotalPages(), posts.isLast()); + } + + @Override + public PagedResponse getPostsByCategory(Long id, int page, int size) { + AppUtils.validatePageNumberAndSize(page, size); + Category category = categoryRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException(CATEGORY, ID, id)); + + Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, CREATED_AT); + Page posts = postRepository.findByCategory(category.getId(), pageable); + + List content = posts.getNumberOfElements() == 0 ? Collections.emptyList() : posts.getContent(); + + return new PagedResponse<>(content, posts.getNumber(), posts.getSize(), posts.getTotalElements(), + posts.getTotalPages(), posts.isLast()); + } + + @Override + public PagedResponse getPostsByTag(Long id, int page, int size) { + AppUtils.validatePageNumberAndSize(page, size); + + Tag tag = tagRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(TAG, ID, id)); + + Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, CREATED_AT); + + Page posts = postRepository.findByTags(Collections.singletonList(tag), pageable); + + List content = posts.getNumberOfElements() == 0 ? Collections.emptyList() : posts.getContent(); + + return new PagedResponse<>(content, posts.getNumber(), posts.getSize(), posts.getTotalElements(), + posts.getTotalPages(), posts.isLast()); + } + + @Override + public Post updatePost(Long id, PostRequest newPostRequest, UserPrincipal currentUser) { + Post post = postRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(POST, ID, id)); + Category category = categoryRepository.findById(newPostRequest.getCategoryId()) + .orElseThrow(() -> new ResourceNotFoundException(CATEGORY, ID, newPostRequest.getCategoryId())); + if (post.getUser().getId().equals(currentUser.getId()) + || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { + post.setTitle(newPostRequest.getTitle()); + post.setBody(newPostRequest.getBody()); + post.setCategory(category); + return postRepository.save(post); + } + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to edit this post"); + + throw new UnauthorizedException(apiResponse); + } + + @Override + public ApiResponse deletePost(Long id, UserPrincipal currentUser) { + Post post = postRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(POST, ID, id)); + if (post.getUser().getId().equals(currentUser.getId()) + || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { + postRepository.deleteById(id); + return new ApiResponse(Boolean.TRUE, "You successfully deleted post"); + } + + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to delete this post"); + + throw new UnauthorizedException(apiResponse); + } + + @Override + public PostResponse addPost(PostRequest postRequest, UserPrincipal currentUser) { + User user = userRepository.findById(currentUser.getId()) + .orElseThrow(() -> new ResourceNotFoundException(USER, ID, 1L)); + Category category = categoryRepository.findById(postRequest.getCategoryId()) + .orElseThrow(() -> new ResourceNotFoundException(CATEGORY, ID, postRequest.getCategoryId())); + + List tags = new ArrayList<>(postRequest.getTags().size()); + + for (String name : postRequest.getTags()) { + Tag tag = tagRepository.findByName(name); + tag = tag == null ? tagRepository.save(new Tag(name)) : tag; + + tags.add(tag); + } + + Post post = new Post(); + post.setBody(postRequest.getBody()); + post.setTitle(postRequest.getTitle()); + post.setCategory(category); + post.setUser(user); + post.setTags(tags); + + Post newPost = postRepository.save(post); + + PostResponse postResponse = new PostResponse(); + + postResponse.setTitle(newPost.getTitle()); + postResponse.setBody(newPost.getBody()); + postResponse.setCategory(newPost.getCategory().getName()); + + List tagNames = new ArrayList<>(newPost.getTags().size()); + + for (Tag tag : newPost.getTags()) { + tagNames.add(tag.getName()); + } + + postResponse.setTags(tagNames); + + return postResponse; + } + + @Override + public Post getPost(Long id) { + return postRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(POST, ID, id)); + } + + private void validatePageNumberAndSize(int page, int size) { + if (page < 0) { + throw new BadRequestException("Page number cannot be less than zero."); + } + + if (size < 0) { + throw new BadRequestException("Size number cannot be less than zero."); + } + + if (size > AppConstants.MAX_PAGE_SIZE) { + throw new BadRequestException("Page size must not be greater than " + AppConstants.MAX_PAGE_SIZE); + } + } +} diff --git a/src/main/java/com/sopromadze/blogapi/service/impl/TagServiceImpl.java b/src/main/java/com/sopromadze/blogapi/service/impl/TagServiceImpl.java new file mode 100644 index 00000000..18488a23 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/service/impl/TagServiceImpl.java @@ -0,0 +1,101 @@ +package com.sopromadze.blogapi.service.impl; + +import com.sopromadze.blogapi.exception.ResourceNotFoundException; +import com.sopromadze.blogapi.exception.UnauthorizedException; +import com.sopromadze.blogapi.model.Tag; +import com.sopromadze.blogapi.model.role.RoleName; +import com.sopromadze.blogapi.payload.ApiResponse; +import com.sopromadze.blogapi.payload.PagedResponse; +import com.sopromadze.blogapi.repository.TagRepository; +import com.sopromadze.blogapi.security.UserPrincipal; +import com.sopromadze.blogapi.service.TagService; +import com.sopromadze.blogapi.utils.AppUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; + +@Service +public class TagServiceImpl implements TagService { + + @Autowired + private TagRepository tagRepository; + + @Override + public PagedResponse getAllTags(int page, int size) { + AppUtils.validatePageNumberAndSize(page, size); + + Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, "createdAt"); + + Page tags = tagRepository.findAll(pageable); + + List content = tags.getNumberOfElements() == 0 ? Collections.emptyList() : tags.getContent(); + + return new PagedResponse<>(content, tags.getNumber(), tags.getSize(), tags.getTotalElements(), tags.getTotalPages(), tags.isLast()); + } + + @Override + public Tag getTag(Long id) { + return tagRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Tag", "id", id)); + } + + @Override + public Tag addTag(Tag tag, UserPrincipal currentUser) { + return tagRepository.save(tag); + } + + @Override + public Tag updateTag(Long id, Tag newTag, UserPrincipal currentUser) { + Tag tag = tagRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Tag", "id", id)); + if (tag.getCreatedBy().equals(currentUser.getId()) || currentUser.getAuthorities() + .contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { + tag.setName(newTag.getName()); + return tagRepository.save(tag); + } + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to edit this tag"); + + throw new UnauthorizedException(apiResponse); + } + + @Override + public ApiResponse deleteTag(Long id, UserPrincipal currentUser) { + Tag tag = tagRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Tag", "id", id)); + if (tag.getCreatedBy().equals(currentUser.getId()) || currentUser.getAuthorities() + .contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { + tagRepository.deleteById(id); + return new ApiResponse(Boolean.TRUE, "You successfully deleted tag"); + } + + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to delete this tag"); + + throw new UnauthorizedException(apiResponse); + } +} + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/sopromadze/blogapi/service/impl/TodoServiceImpl.java b/src/main/java/com/sopromadze/blogapi/service/impl/TodoServiceImpl.java new file mode 100644 index 00000000..9ef3b2e8 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/service/impl/TodoServiceImpl.java @@ -0,0 +1,146 @@ +package com.sopromadze.blogapi.service.impl; + +import com.sopromadze.blogapi.exception.BadRequestException; +import com.sopromadze.blogapi.exception.ResourceNotFoundException; +import com.sopromadze.blogapi.exception.UnauthorizedException; +import com.sopromadze.blogapi.model.Todo; +import com.sopromadze.blogapi.model.user.User; +import com.sopromadze.blogapi.payload.ApiResponse; +import com.sopromadze.blogapi.payload.PagedResponse; +import com.sopromadze.blogapi.repository.TodoRepository; +import com.sopromadze.blogapi.repository.UserRepository; +import com.sopromadze.blogapi.security.UserPrincipal; +import com.sopromadze.blogapi.service.TodoService; +import com.sopromadze.blogapi.utils.AppConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; + +import static com.sopromadze.blogapi.utils.AppConstants.CREATED_AT; +import static com.sopromadze.blogapi.utils.AppConstants.ID; +import static com.sopromadze.blogapi.utils.AppConstants.TODO; +import static com.sopromadze.blogapi.utils.AppConstants.YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION; + +@Service +public class TodoServiceImpl implements TodoService { + + @Autowired + private TodoRepository todoRepository; + + @Autowired + private UserRepository userRepository; + + @Override + public Todo completeTodo(Long id, UserPrincipal currentUser) { + Todo todo = todoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(TODO, ID, id)); + + User user = userRepository.getUser(currentUser); + + if (todo.getUser().getId().equals(user.getId())) { + todo.setCompleted(Boolean.TRUE); + return todoRepository.save(todo); + } + + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION); + + throw new UnauthorizedException(apiResponse); + } + + @Override + public Todo unCompleteTodo(Long id, UserPrincipal currentUser) { + Todo todo = todoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(TODO, ID, id)); + User user = userRepository.getUser(currentUser); + if (todo.getUser().getId().equals(user.getId())) { + todo.setCompleted(Boolean.FALSE); + return todoRepository.save(todo); + } + + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION); + + throw new UnauthorizedException(apiResponse); + } + + @Override + public PagedResponse getAllTodos(UserPrincipal currentUser, int page, int size) { + validatePageNumberAndSize(page, size); + Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, CREATED_AT); + + Page todos = todoRepository.findByCreatedBy(currentUser.getId(), pageable); + + List content = todos.getNumberOfElements() == 0 ? Collections.emptyList() : todos.getContent(); + + return new PagedResponse<>(content, todos.getNumber(), todos.getSize(), todos.getTotalElements(), + todos.getTotalPages(), todos.isLast()); + } + + @Override + public Todo addTodo(Todo todo, UserPrincipal currentUser) { + User user = userRepository.getUser(currentUser); + todo.setUser(user); + return todoRepository.save(todo); + } + + @Override + public Todo getTodo(Long id, UserPrincipal currentUser) { + User user = userRepository.getUser(currentUser); + Todo todo = todoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(TODO, ID, id)); + + if (todo.getUser().getId().equals(user.getId())) { + return todo; + } + + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION); + + throw new UnauthorizedException(apiResponse); + } + + @Override + public Todo updateTodo(Long id, Todo newTodo, UserPrincipal currentUser) { + User user = userRepository.getUser(currentUser); + Todo todo = todoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(TODO, ID, id)); + if (todo.getUser().getId().equals(user.getId())) { + todo.setTitle(newTodo.getTitle()); + todo.setCompleted(newTodo.getCompleted()); + return todoRepository.save(todo); + } + + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION); + + throw new UnauthorizedException(apiResponse); + } + + @Override + public ApiResponse deleteTodo(Long id, UserPrincipal currentUser) { + User user = userRepository.getUser(currentUser); + Todo todo = todoRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(TODO, ID, id)); + + if (todo.getUser().getId().equals(user.getId())) { + todoRepository.deleteById(id); + return new ApiResponse(Boolean.TRUE, "You successfully deleted todo"); + } + + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION); + + throw new UnauthorizedException(apiResponse); + } + + private void validatePageNumberAndSize(int page, int size) { + if (page < 0) { + throw new BadRequestException("Page number cannot be less than zero."); + } + + if (size < 0) { + throw new BadRequestException("Size number cannot be less than zero."); + } + + if (size > AppConstants.MAX_PAGE_SIZE) { + throw new BadRequestException("Page size must not be greater than " + AppConstants.MAX_PAGE_SIZE); + } + } +} diff --git a/src/main/java/com/sopromadze/blogapi/service/impl/UserServiceImpl.java b/src/main/java/com/sopromadze/blogapi/service/impl/UserServiceImpl.java new file mode 100644 index 00000000..d740f27b --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/service/impl/UserServiceImpl.java @@ -0,0 +1,185 @@ +package com.sopromadze.blogapi.service.impl; + +import com.sopromadze.blogapi.exception.AccessDeniedException; +import com.sopromadze.blogapi.exception.AppException; +import com.sopromadze.blogapi.exception.BadRequestException; +import com.sopromadze.blogapi.exception.ResourceNotFoundException; +import com.sopromadze.blogapi.exception.UnauthorizedException; +import com.sopromadze.blogapi.model.role.Role; +import com.sopromadze.blogapi.model.role.RoleName; +import com.sopromadze.blogapi.model.user.Address; +import com.sopromadze.blogapi.model.user.Company; +import com.sopromadze.blogapi.model.user.Geo; +import com.sopromadze.blogapi.model.user.User; +import com.sopromadze.blogapi.payload.ApiResponse; +import com.sopromadze.blogapi.payload.InfoRequest; +import com.sopromadze.blogapi.payload.UserIdentityAvailability; +import com.sopromadze.blogapi.payload.UserProfile; +import com.sopromadze.blogapi.payload.UserSummary; +import com.sopromadze.blogapi.repository.PostRepository; +import com.sopromadze.blogapi.repository.RoleRepository; +import com.sopromadze.blogapi.repository.UserRepository; +import com.sopromadze.blogapi.security.UserPrincipal; +import com.sopromadze.blogapi.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class UserServiceImpl implements UserService { + @Autowired + private UserRepository userRepository; + + @Autowired + private PostRepository postRepository; + + @Autowired + private RoleRepository roleRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Override + public UserSummary getCurrentUser(UserPrincipal currentUser) { + return new UserSummary(currentUser.getId(), currentUser.getUsername(), currentUser.getFirstName(), + currentUser.getLastName()); + } + + @Override + public UserIdentityAvailability checkUsernameAvailability(String username) { + Boolean isAvailable = !userRepository.existsByUsername(username); + return new UserIdentityAvailability(isAvailable); + } + + @Override + public UserIdentityAvailability checkEmailAvailability(String email) { + Boolean isAvailable = !userRepository.existsByEmail(email); + return new UserIdentityAvailability(isAvailable); + } + + @Override + public UserProfile getUserProfile(String username) { + User user = userRepository.getUserByName(username); + + Long postCount = postRepository.countByCreatedBy(user.getId()); + + return new UserProfile(user.getId(), user.getUsername(), user.getFirstName(), user.getLastName(), + user.getCreatedAt(), user.getEmail(), user.getAddress(), user.getPhone(), user.getWebsite(), + user.getCompany(), postCount); + } + + @Override + public User addUser(User user) { + if (userRepository.existsByUsername(user.getUsername())) { + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "Username is already taken"); + throw new BadRequestException(apiResponse); + } + + if (userRepository.existsByEmail(user.getEmail())) { + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "Email is already taken"); + throw new BadRequestException(apiResponse); + } + + List roles = new ArrayList<>(); + roles.add( + roleRepository.findByName(RoleName.ROLE_USER).orElseThrow(() -> new AppException("User role not set"))); + user.setRoles(roles); + + user.setPassword(passwordEncoder.encode(user.getPassword())); + return userRepository.save(user); + } + + @Override + public User updateUser(User newUser, String username, UserPrincipal currentUser) { + User user = userRepository.getUserByName(username); + if (user.getId().equals(currentUser.getId()) + || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { + user.setFirstName(newUser.getFirstName()); + user.setLastName(newUser.getLastName()); + user.setPassword(passwordEncoder.encode(newUser.getPassword())); + user.setAddress(newUser.getAddress()); + user.setPhone(newUser.getPhone()); + user.setWebsite(newUser.getWebsite()); + user.setCompany(newUser.getCompany()); + + return userRepository.save(user); + + } + + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to update profile of: " + username); + throw new UnauthorizedException(apiResponse); + + } + + @Override + public ApiResponse deleteUser(String username, UserPrincipal currentUser) { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new ResourceNotFoundException("User", "id", username)); + if (!user.getId().equals(currentUser.getId()) || !currentUser.getAuthorities() + .contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to delete profile of: " + username); + throw new AccessDeniedException(apiResponse); + } + + userRepository.deleteById(user.getId()); + + return new ApiResponse(Boolean.TRUE, "You successfully deleted profile of: " + username); + } + + @Override + public ApiResponse giveAdmin(String username) { + User user = userRepository.getUserByName(username); + List roles = new ArrayList<>(); + roles.add(roleRepository.findByName(RoleName.ROLE_ADMIN) + .orElseThrow(() -> new AppException("User role not set"))); + roles.add( + roleRepository.findByName(RoleName.ROLE_USER).orElseThrow(() -> new AppException("User role not set"))); + user.setRoles(roles); + userRepository.save(user); + return new ApiResponse(Boolean.TRUE, "You gave ADMIN role to user: " + username); + } + + @Override + public ApiResponse removeAdmin(String username) { + User user = userRepository.getUserByName(username); + List roles = new ArrayList<>(); + roles.add( + roleRepository.findByName(RoleName.ROLE_USER).orElseThrow(() -> new AppException("User role not set"))); + user.setRoles(roles); + userRepository.save(user); + return new ApiResponse(Boolean.TRUE, "You took ADMIN role from user: " + username); + } + + @Override + public UserProfile setOrUpdateInfo(UserPrincipal currentUser, InfoRequest infoRequest) { + User user = userRepository.findByUsername(currentUser.getUsername()) + .orElseThrow(() -> new ResourceNotFoundException("User", "username", currentUser.getUsername())); + Geo geo = new Geo(infoRequest.getLat(), infoRequest.getLng()); + Address address = new Address(infoRequest.getStreet(), infoRequest.getSuite(), infoRequest.getCity(), + infoRequest.getZipcode(), geo); + Company company = new Company(infoRequest.getCompanyName(), infoRequest.getCatchPhrase(), infoRequest.getBs()); + if (user.getId().equals(currentUser.getId()) + || currentUser.getAuthorities().contains(new SimpleGrantedAuthority(RoleName.ROLE_ADMIN.toString()))) { + user.setAddress(address); + user.setCompany(company); + user.setWebsite(infoRequest.getWebsite()); + user.setPhone(infoRequest.getPhone()); + User updatedUser = userRepository.save(user); + + Long postCount = postRepository.countByCreatedBy(updatedUser.getId()); + + return new UserProfile(updatedUser.getId(), updatedUser.getUsername(), + updatedUser.getFirstName(), updatedUser.getLastName(), updatedUser.getCreatedAt(), + updatedUser.getEmail(), updatedUser.getAddress(), updatedUser.getPhone(), updatedUser.getWebsite(), + updatedUser.getCompany(), postCount); + } + + ApiResponse apiResponse = new ApiResponse(Boolean.FALSE, "You don't have permission to update users profile", HttpStatus.FORBIDDEN); + throw new AccessDeniedException(apiResponse); + } +} diff --git a/src/main/java/com/sopromadze/blogapi/util/AppConstants.java b/src/main/java/com/sopromadze/blogapi/util/AppConstants.java deleted file mode 100644 index 8975f56a..00000000 --- a/src/main/java/com/sopromadze/blogapi/util/AppConstants.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.sopromadze.blogapi.util; - -public interface AppConstants { - String DEFAULT_PAGE_NUMBER = "0"; - String DEFAULT_PAGE_SIZE = "30"; - - int MAX_PAGE_SIZE = 30; -} diff --git a/src/main/java/com/sopromadze/blogapi/utils/AppConstants.java b/src/main/java/com/sopromadze/blogapi/utils/AppConstants.java new file mode 100644 index 00000000..19df8f75 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/utils/AppConstants.java @@ -0,0 +1,31 @@ +package com.sopromadze.blogapi.utils; + +public class AppConstants { + public static final String DEFAULT_PAGE_NUMBER = "0"; + + public static final String DEFAULT_PAGE_SIZE = "30"; + + public static final int MAX_PAGE_SIZE = 30; + + public static final String CREATED_AT = "createdAt"; + + public static final String ID = "id"; + + public static final String PHOTO = "Photo"; + + public static final String ALBUM = "Album"; + + public static final String USERNAME = "username"; + + public static final String USER = "User"; + + public static final String CATEGORY = "Category"; + + public static final String TAG = "Tag"; + + public static final String POST = "Post"; + + public static final String TODO = "ToDo"; + + public static final String YOU_DON_T_HAVE_PERMISSION_TO_MAKE_THIS_OPERATION = "You don't have permission to make this operation"; +} diff --git a/src/main/java/com/sopromadze/blogapi/utils/AppUtils.java b/src/main/java/com/sopromadze/blogapi/utils/AppUtils.java new file mode 100644 index 00000000..fccdf832 --- /dev/null +++ b/src/main/java/com/sopromadze/blogapi/utils/AppUtils.java @@ -0,0 +1,20 @@ +package com.sopromadze.blogapi.utils; + +import com.sopromadze.blogapi.exception.BlogapiException; +import org.springframework.http.HttpStatus; + +public class AppUtils { + public static void validatePageNumberAndSize(int page, int size) { + if (page < 0) { + throw new BlogapiException(HttpStatus.BAD_REQUEST, "Page number cannot be less than zero."); + } + + if (size < 0) { + throw new BlogapiException(HttpStatus.BAD_REQUEST, "Size number cannot be less than zero."); + } + + if (size > AppConstants.MAX_PAGE_SIZE) { + throw new BlogapiException(HttpStatus.BAD_REQUEST, "Page size must not be greater than " + AppConstants.MAX_PAGE_SIZE); + } + } +} diff --git a/src/main/resources/_application.properties b/src/main/resources/_application.properties new file mode 100644 index 00000000..d7d7b14c --- /dev/null +++ b/src/main/resources/_application.properties @@ -0,0 +1,12 @@ +spring.jpa.hibernate.ddl-auto=none +spring.datasource.url=jdbc:mysql://localhost:3306/blogapi?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true +spring.datasource.username=root +spring.datasource.password=root +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect +app.jwtSecret=secret +# Expiration in milliseconds - 1 Hour +app.jwtExpirationInMs=3600000 +spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false +spring.jackson.time-zone=UTC +cors.allowedOrings=* diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index cd684791..00000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,17 +0,0 @@ -spring.jpa.hibernate.ddl-auto = none -spring.datasource.url = jdbc:mysql://localhost:3306/blogapi?useSSL=false -spring.datasource.username = DB_USERNAME -spring.datasource.password = DB_PASSWORD - -spring.jpa.show-sql = true - -spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect - -app.jwtSecret = secret - -# Expiration in milliseconds - 1 Hour -app.jwtExpirationInMs = 3600000 - - -spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false -spring.jackson.time-zone = UTC \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 00000000..ef6d7e3d --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,23 @@ +spring: + datasource: + url: jdbc:mysql://blogapi-db:3306/blogapi?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true + username: root + password: root + jpa: + hibernate: + ddl-auto: none + show-sql: true + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL5Dialect + jackson: + serialization: + WRITE_DATES_AS_TIMESTAMPS: false + time-zone: UTC + +app: + jwtSecret: secret + jwtExpirationInMs: 3600000 + +cors: + allowedOrings: '*' diff --git a/src/main/resources/blogapi.sql b/src/main/resources/blogapi.sql index 58786e00..3a85baec 100644 --- a/src/main/resources/blogapi.sql +++ b/src/main/resources/blogapi.sql @@ -1,3 +1,7 @@ +UNLOCK TABLES; + +DROP TABLE IF EXISTS `post_tag`; +DROP TABLE IF EXISTS `tags`; DROP TABLE IF EXISTS `user_role`; DROP TABLE IF EXISTS `roles`; DROP TABLE IF EXISTS `comments`; @@ -10,12 +14,22 @@ DROP TABLE IF EXISTS `address`; DROP TABLE IF EXISTS `company`; DROP TABLE IF EXISTS `geo`; +CREATE TABLE `tags` ( + `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created_by` bigint(19) unsigned NOT NULL, + `updated_by` bigint(19) unsigned NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + CREATE TABLE `geo` ( `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, `lat` varchar(255), `lng` varchar(255), - `created_at` timestamp, - `updated_at` timestamp, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `created_by` bigint(19) unsigned DEFAULT NULL, `updated_by` bigint(19) unsigned DEFAULT NULL, PRIMARY KEY (`id`) @@ -26,8 +40,8 @@ CREATE TABLE `company` ( `name` varchar(255), `catch_phrase` varchar(255), `bs` varchar(255), - `created_at` timestamp, - `updated_at` timestamp, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `created_by` bigint(19) unsigned DEFAULT NULL, `updated_by` bigint(19) unsigned DEFAULT NULL, PRIMARY KEY (`id`) @@ -40,8 +54,8 @@ CREATE TABLE `address` ( `city` varchar(255), `zipcode` varchar(255), `geo_id` bigint(19) unsigned DEFAULT NULL, - `created_at` timestamp, - `updated_at` timestamp, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `created_by` bigint(19) unsigned DEFAULT NULL, `updated_by` bigint(19) unsigned DEFAULT NULL, PRIMARY KEY (`id`), @@ -60,8 +74,8 @@ CREATE TABLE `users` ( `phone` varchar(255), `website` varchar(255), `company_id` bigint(19) unsigned DEFAULT NULL, - `created_at` timestamp, - `updated_at` timestamp, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `fk_address` (`address_id`), KEY `fk_company` (`company_id`), @@ -74,8 +88,8 @@ CREATE TABLE `todos` ( `title` varchar(255) NOT NULL, `completed` boolean default false, `user_id` bigint(19) unsigned DEFAULT NULL, - `created_at` timestamp, - `updated_at` timestamp, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `created_by` bigint(19) unsigned DEFAULT NULL, `updated_by` bigint(19) unsigned DEFAULT NULL, PRIMARY KEY (`id`), @@ -87,8 +101,8 @@ CREATE TABLE `albums` ( `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, `title` varchar(255) NOT NULL, `user_id` bigint(19) unsigned DEFAULT NULL, - `created_at` timestamp, - `updated_at` timestamp, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `created_by` bigint(19) unsigned DEFAULT NULL, `updated_by` bigint(19) unsigned DEFAULT NULL, PRIMARY KEY (`id`), @@ -102,8 +116,8 @@ CREATE TABLE `photos` ( `url` varchar(255) NOT NULL, `thumbnail_url` varchar(255) NOT NULL, `album_id` bigint(19) unsigned DEFAULT NULL, - `created_at` timestamp, - `updated_at` timestamp, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `created_by` bigint(19) unsigned DEFAULT NULL, `updated_by` bigint(19) unsigned DEFAULT NULL, PRIMARY KEY (`id`), @@ -116,8 +130,8 @@ CREATE TABLE `posts` ( `title` varchar(255) NOT NULL, `body` text NOT NULL, `user_id` bigint(19) unsigned DEFAULT NULL, - `created_at` timestamp, - `updated_at` timestamp, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `created_by` bigint(19) unsigned DEFAULT NULL, `updated_by` bigint(19) unsigned DEFAULT NULL, PRIMARY KEY (`id`), @@ -125,6 +139,17 @@ CREATE TABLE `posts` ( CONSTRAINT `fk_user_post` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; +CREATE TABLE `post_tag` ( + `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, + `post_id` bigint(19) unsigned NOT NULL, + `tag_id` bigint(19) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `fk_posttag_post_id` (`post_id`), + KEY `fk_posttag_tag_id` (`tag_id`), + CONSTRAINT `fk_posttag_post_id` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`), + CONSTRAINT `fk_posttag_tag_id` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + CREATE TABLE `comments` ( `id` bigint(19) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, @@ -132,8 +157,8 @@ CREATE TABLE `comments` ( `body` text NOT NULL, `post_id` bigint(19) unsigned DEFAULT NULL, `user_id` bigint(19) unsigned DEFAULT NULL, - `created_at` timestamp, - `updated_at` timestamp, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `created_by` bigint(19) unsigned NOT NULL, `updated_by` bigint(19) unsigned NOT NULL, PRIMARY KEY (`id`), @@ -158,4 +183,8 @@ CREATE TABLE `user_role` ( KEY `fk_security_role_id` (`role_id`), CONSTRAINT `fk_security_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`), CONSTRAINT `fk_security_role_id` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; \ No newline at end of file +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +LOCK TABLES `roles` WRITE; +INSERT INTO `roles` VALUES (1,'ROLE_ADMIN'),(2,'ROLE_USER'); +UNLOCK TABLES; diff --git a/src/test/java/com/sopromadze/blogapi/BlogApiApplicationTests.java b/src/test/java/com/sopromadze/blogapi/BlogApiApplicationTests.java index f3c607b9..b1247c5c 100644 --- a/src/test/java/com/sopromadze/blogapi/BlogApiApplicationTests.java +++ b/src/test/java/com/sopromadze/blogapi/BlogApiApplicationTests.java @@ -6,11 +6,11 @@ import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) -@SpringBootTest +@SpringBootTest(classes = BlogApiApplication.class) public class BlogApiApplicationTests { - @Test - public void contextLoads() { - } + @Test + public void contextLoads() { + } }