├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.md ├── assets ├── main.png └── photo.png ├── insomnia └── Insomnia_2024-03-08.json ├── mvnw ├── mvnw.cmd ├── pom.xml ├── src ├── main │ ├── java │ │ └── com │ │ │ └── spring │ │ │ └── myfood │ │ │ ├── MyfoodApplication.java │ │ │ ├── config │ │ │ ├── CorsConfig.java │ │ │ ├── MongoConfig.java │ │ │ └── SwaggerConfiguration.java │ │ │ ├── controller │ │ │ ├── MainController.java │ │ │ ├── ProductController.java │ │ │ └── RankingController.java │ │ │ ├── dtos │ │ │ ├── request │ │ │ │ └── RequestProductDTO.java │ │ │ └── response │ │ │ │ └── ResponseSearchFoodDTO.java │ │ │ ├── enums │ │ │ ├── FoodCategoryEnum.java │ │ │ └── RankingTypeEnum.java │ │ │ ├── model │ │ │ ├── Product.java │ │ │ └── Ranking.java │ │ │ ├── mongo │ │ │ └── MyFoodMongo.java │ │ │ ├── repository │ │ │ ├── ProductRepository.java │ │ │ └── RankingRepository.java │ │ │ ├── service │ │ │ ├── ProductService.java │ │ │ └── RankingService.java │ │ │ └── utils │ │ │ └── GlobalExceptionHandler.java │ └── resources │ │ └── application.properties └── test │ └── java │ └── com │ └── spring │ └── myfood │ └── MyfoodApplicationTests.java └── todo.todo /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LadyJessie19/MyFood/7dce8c6d131eb4498e4a9ff366ba29dffaf78bde/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MyFood - Food Ranking 🍔🍕🌭 2 | 3 | ## Discover & Rate Your Favorite Foods 4 | 5 | ### MyFood is a platform that allows users to explore trending foods and rate their favorites, creating a dynamic food ranking system. 6 | 7 | MyFood 8 | 9 | **Project Name:** MyFood 🍔🍕🌭 10 | 11 | **Development Start Date:** 03/02/2024 ⏩ 12 | 13 | **Development End Date:** 11/03/2024 🏁 14 | 15 | **Status:** Completed ✅ 16 | 17 | **Technologies used:** ☕ Spring Boot | 🍃 MongoDB | 🚈 Railway | 🪂 Swagger | 🟣 Insomnia 18 | 19 | 🚈 **Railway Deploy Link:** [MyFood Deploy](https://app-myfood-production.up.railway.app) 20 | 21 | 🪂 **Swagger Link:** [MyFood Swagger](https://app-myfood-production.up.railway.app/swagger-ui/index.html#/) 22 | 23 | 🟣 **Insomnia Import File Link:** [MyFood Insomnia](https://github.com/LadyJessie19/MyFood/tree/main/insomnia/Insomnia_2024-03-08.json) 24 | 25 | ## Project Description 📝 26 | 27 | MyFood is a fictional app that implements a ranking system. The purpose of this project is to showcase the most searched foods and their associated scores within the fictitious app named MyFood. 28 | 29 | ## Key Features 🔧 30 | 31 | - Implementation of a food ranking system. 32 | - Display of most searched foods. 33 | - Integration with MongoDB for data storage. 34 | 35 | ## Railway Deploy/MongoDB Notice ⚠️ 36 | 37 | Please note that MongoDB or Railway services may be temporarily unavailable due to inactivity or other technical reasons. If this happens, you may need to clone the repository to your local machine and connect to another MongoDB database. 38 | 39 | ## Installing the Project 🛠️ 40 | 41 | Clone the repository: 42 | 43 | ```bash 44 | git clone https://github.com/LadyJessie19/MyFood.git 45 | ``` 46 | 47 | ## Running the Application 🚀 48 | 49 | To start the application: 50 | 51 | 1. Navigate to the project directory. 52 | 2. Run the following command to build and start the application: 53 | 54 | ```bash 55 | mvn spring-boot:run 56 | ``` 57 | 58 | This will start the application on the default port, typically `8080`. For development purposes, I used the port `8081`. 59 | 60 | ## How to import Insomnia File 📥 61 | 62 | 1. **Download the Insomnia file;** 63 | 2. **Open Insomnia;** 64 | 3. **Import the File:** 65 | - Click on the **`Import/Export`** button in the top-right corner of the Insomnia window. 66 | - Select **`Import Data`** from the dropdown menu. 67 | - Choose **`From File`** and browse to the location where you downloaded the Insomnia file. 68 | - Select the file and click **`Open`** to import it. 69 | 4. **Start Testing;** 70 | 71 | Once imported, you will see the collection of pre-configured requests in Insomnia. You can now start using them to test MyFood API. 72 | 73 | > **Note:** Make sure to set up the environment variable `base_url`. If you're running the project locally the default value is `http://localhost:8080`, but if you're running the project on Railway the default value is `https://app-myfood-production.up.railway.app`. 74 | 75 | ## Development Team 🙋‍♀️ 76 | 77 | Jessie 78 | 79 | - Developer: [Jessie M Bentes](https://github.com/LadyJessie19) 80 | 81 | ## How to Contribute 🤝 82 | 83 | If you want to contribute to the project, follow these steps: 84 | 85 | 1. Fork the project. 86 | 2. Create a new branch (`git checkout -b feature/new-feature`). 87 | 3. Commit your changes (`git commit -am 'Add new feature'`). 88 | 4. Push to the branch (`git push origin feature/new-feature`). 89 | 5. Create a new Pull Request. 90 | 91 | ## License 🧐 92 | 93 | This project is licensed under the [MIT License](https://opensource.org/licenses/MIT). 94 | -------------------------------------------------------------------------------- /assets/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LadyJessie19/MyFood/7dce8c6d131eb4498e4a9ff366ba29dffaf78bde/assets/main.png -------------------------------------------------------------------------------- /assets/photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LadyJessie19/MyFood/7dce8c6d131eb4498e4a9ff366ba29dffaf78bde/assets/photo.png -------------------------------------------------------------------------------- /insomnia/Insomnia_2024-03-08.json: -------------------------------------------------------------------------------- 1 | {"_type":"export","__export_format":4,"__export_date":"2024-03-08T15:48:07.451Z","__export_source":"insomnia.desktop.app:v2023.5.8","resources":[{"_id":"req_8cfc65166ef14c848eb1ff89fb1069f7","parentId":"fld_de3307efbe8d4c7cbfd86e8a4ef3b341","modified":1709912747173,"created":1709912677628,"url":"{{ _.base_url }}/health","name":"Health Check","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/2023.5.8"}],"authentication":{},"metaSortKey":-1709912746945,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_de3307efbe8d4c7cbfd86e8a4ef3b341","parentId":"wrk_9fc76dd77d0a4c34bc9f93dab8300b4b","modified":1709912674252,"created":1709912674252,"name":"Health","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1709912674252,"_type":"request_group"},{"_id":"wrk_9fc76dd77d0a4c34bc9f93dab8300b4b","parentId":null,"modified":1708548826518,"created":1708548826518,"name":"MyFood","description":"","scope":"collection","_type":"workspace"},{"_id":"req_ce7c673aa7ca420b90a645c7dfa9203b","parentId":"fld_2a55ad4354424cb197b47c4eb253e3b9","modified":1709750763264,"created":1708549115422,"url":"{{ _['base_url'] }}/products/all","name":"List All Foods","description":"","method":"GET","body":{},"parameters":[{"id":"pair_ab9b10e8ea494744bd7909a21a4b2fd7","name":"category","value":"FRUIT","description":"","disabled":true}],"headers":[{"name":"User-Agent","value":"insomnia/2023.5.8"}],"authentication":{},"metaSortKey":-1708549203830,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_2a55ad4354424cb197b47c4eb253e3b9","parentId":"wrk_9fc76dd77d0a4c34bc9f93dab8300b4b","modified":1708549112053,"created":1708549112053,"name":"Products","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1708549112053,"_type":"request_group"},{"_id":"req_5bb2e7268148451091c804f163115a04","parentId":"fld_2a55ad4354424cb197b47c4eb253e3b9","modified":1709910246724,"created":1708549223434,"url":"{{ _['base_url'] }}/products/register","name":"Register New Food","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"name\": \"Mussarela\",\n\t\"description\": \"Lonelly people pizza\",\n\t\"price\": 26,\n\t\"image\": \"https://media.istockphoto.com/id/1168754685/pt/foto/pizza-margarita-with-cheese-top-view-isolated-on-white-background.jpg?s=612x612&w=0&k=20&c=zOBq4jmDcRY9zauo-sfrBwOPbcTJhnFgavKq5CXmLCo=\",\n\t\"category\": \"PIZZA\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"User-Agent","value":"insomnia/2023.5.8"}],"authentication":{},"metaSortKey":-1708549203730,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_de7ef79a7b75409c9a47d0980beac47d","parentId":"fld_2a55ad4354424cb197b47c4eb253e3b9","modified":1709909499319,"created":1708553514654,"url":"{{ _['base_url'] }}/products/65eb262c8b90dd3be2ea7c9b","name":"Find Food By Id","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/2023.5.8"}],"authentication":{},"metaSortKey":-1708549203630,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_ae9834a730e34dac86d0463e85728170","parentId":"fld_4b3bd3b27bce4ac3a3c581d6481c8272","modified":1709910365358,"created":1708549058793,"url":"{{ _['base_url'] }}/rankings/search","name":"Search Foods","description":"","method":"GET","body":{},"parameters":[{"id":"pair_a6ebc7fdea744e18a9f0034f4f087e87","name":"foodTitle","value":"mussarela","description":""}],"headers":[{"name":"User-Agent","value":"insomnia/2023.5.8"}],"authentication":{},"metaSortKey":-1708549058993,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_4b3bd3b27bce4ac3a3c581d6481c8272","parentId":"wrk_9fc76dd77d0a4c34bc9f93dab8300b4b","modified":1708549018452,"created":1708549018452,"name":"Rankings","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1708549018452,"_type":"request_group"},{"_id":"req_9694503a22224adb9a5035733e8e08c9","parentId":"fld_4b3bd3b27bce4ac3a3c581d6481c8272","modified":1709739037949,"created":1708548927389,"url":"{{ _['base_url'] }}/rankings/top-foods","name":"Most Searched Foods","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/2023.5.8"}],"authentication":{},"metaSortKey":-1708549058893,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_a3625288aeaa44fc901c35071db036d3","parentId":"fld_4b3bd3b27bce4ac3a3c581d6481c8272","modified":1709739044896,"created":1708983188697,"url":"{{ _['base_url'] }}/rankings/top-categories","name":"Most Searched Categories","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/2023.5.8"}],"authentication":{},"metaSortKey":-1708549058818,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_40d8b2bb692e4a669c4f52e02b8023d1","parentId":"fld_4b3bd3b27bce4ac3a3c581d6481c8272","modified":1709909538111,"created":1708549296723,"url":"{{ _['base_url'] }}/rankings/65eb26488b90dd3be2ea7c9c","name":"Check Ranking Score By Id","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/2023.5.8"}],"authentication":{},"metaSortKey":-1708549058743,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"env_09ceb19816ae6c59e40195e08ab0cf8881e7e305","parentId":"wrk_9fc76dd77d0a4c34bc9f93dab8300b4b","modified":1708548837362,"created":1708548826719,"name":"Base Environment","data":{},"dataPropertyOrder":{},"color":null,"isPrivate":false,"metaSortKey":1708548826719,"_type":"environment"},{"_id":"jar_09ceb19816ae6c59e40195e08ab0cf8881e7e305","parentId":"wrk_9fc76dd77d0a4c34bc9f93dab8300b4b","modified":1709581028936,"created":1708548826721,"name":"Default Jar","cookies":[{"key":"JSESSIONID","value":"659DD62F7E0628104D4D6E6B61ED4B4E","domain":"localhost","path":"/","httpOnly":true,"hostOnly":true,"creation":"2024-03-04T19:37:08.935Z","lastAccessed":"2024-03-04T19:37:08.935Z","id":"8693064594501603"}],"_type":"cookie_jar"},{"_id":"env_ce2b9d51216e496683b00fdaa93d644e","parentId":"env_09ceb19816ae6c59e40195e08ab0cf8881e7e305","modified":1709739005378,"created":1708548842155,"name":"Local","data":{"base_url":"http://localhost:8081"},"dataPropertyOrder":{"&":["base_url"]},"color":"#ec0404","isPrivate":false,"metaSortKey":1708548842155,"_type":"environment"},{"_id":"env_ced63c56361d4db4b5689b210f6ff566","parentId":"env_09ceb19816ae6c59e40195e08ab0cf8881e7e305","modified":1709912722879,"created":1709738922079,"name":"Deploy","data":{"base_url":"https://app-myfood-production.up.railway.app"},"dataPropertyOrder":{"&":["base_url"]},"color":"#7d69cb","isPrivate":false,"metaSortKey":1709738922079,"_type":"environment"}]} -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.2.0 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "$(uname)" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=$(java-config --jre-home) 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && 89 | JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="$(which javac)" 94 | if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=$(which readlink) 97 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then 98 | if $darwin ; then 99 | javaHome="$(dirname "\"$javaExecutable\"")" 100 | javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" 101 | else 102 | javaExecutable="$(readlink -f "\"$javaExecutable\"")" 103 | fi 104 | javaHome="$(dirname "\"$javaExecutable\"")" 105 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=$(cd "$wdir/.." || exit 1; pwd) 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir" || exit 1; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | # Remove \r in case we run on Windows within Git Bash 164 | # and check out the repository with auto CRLF management 165 | # enabled. Otherwise, we may read lines that are delimited with 166 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word 167 | # splitting rules. 168 | tr -s '\r\n' ' ' < "$1" 169 | fi 170 | } 171 | 172 | log() { 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | printf '%s\n' "$1" 175 | fi 176 | } 177 | 178 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") 179 | if [ -z "$BASE_DIR" ]; then 180 | exit 1; 181 | fi 182 | 183 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 184 | log "$MAVEN_PROJECTBASEDIR" 185 | 186 | ########################################################################################## 187 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 188 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 189 | ########################################################################################## 190 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" 191 | if [ -r "$wrapperJarPath" ]; then 192 | log "Found $wrapperJarPath" 193 | else 194 | log "Couldn't find $wrapperJarPath, downloading it ..." 195 | 196 | if [ -n "$MVNW_REPOURL" ]; then 197 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 198 | else 199 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 200 | fi 201 | while IFS="=" read -r key value; do 202 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) 203 | safeValue=$(echo "$value" | tr -d '\r') 204 | case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; 205 | esac 206 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 207 | log "Downloading from: $wrapperUrl" 208 | 209 | if $cygwin; then 210 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 211 | fi 212 | 213 | if command -v wget > /dev/null; then 214 | log "Found wget ... using wget" 215 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" 216 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 217 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 218 | else 219 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 220 | fi 221 | elif command -v curl > /dev/null; then 222 | log "Found curl ... using curl" 223 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 228 | fi 229 | else 230 | log "Falling back to using Java to download" 231 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" 232 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" 233 | # For Cygwin, switch paths to Windows format before running javac 234 | if $cygwin; then 235 | javaSource=$(cygpath --path --windows "$javaSource") 236 | javaClass=$(cygpath --path --windows "$javaClass") 237 | fi 238 | if [ -e "$javaSource" ]; then 239 | if [ ! -e "$javaClass" ]; then 240 | log " - Compiling MavenWrapperDownloader.java ..." 241 | ("$JAVA_HOME/bin/javac" "$javaSource") 242 | fi 243 | if [ -e "$javaClass" ]; then 244 | log " - Running MavenWrapperDownloader.java ..." 245 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" 246 | fi 247 | fi 248 | fi 249 | fi 250 | ########################################################################################## 251 | # End of extension 252 | ########################################################################################## 253 | 254 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file 255 | wrapperSha256Sum="" 256 | while IFS="=" read -r key value; do 257 | case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; 258 | esac 259 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 260 | if [ -n "$wrapperSha256Sum" ]; then 261 | wrapperSha256Result=false 262 | if command -v sha256sum > /dev/null; then 263 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then 264 | wrapperSha256Result=true 265 | fi 266 | elif command -v shasum > /dev/null; then 267 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then 268 | wrapperSha256Result=true 269 | fi 270 | else 271 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." 272 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." 273 | exit 1 274 | fi 275 | if [ $wrapperSha256Result = false ]; then 276 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 277 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 278 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 279 | exit 1 280 | fi 281 | fi 282 | 283 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 284 | 285 | # For Cygwin, switch paths to Windows format before running java 286 | if $cygwin; then 287 | [ -n "$JAVA_HOME" ] && 288 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 289 | [ -n "$CLASSPATH" ] && 290 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 291 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 292 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 293 | fi 294 | 295 | # Provide a "standardized" way to retrieve the CLI args that will 296 | # work with both Windows and non-Windows executions. 297 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" 298 | export MAVEN_CMD_LINE_ARGS 299 | 300 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 301 | 302 | # shellcheck disable=SC2086 # safe args 303 | exec "$JAVACMD" \ 304 | $MAVEN_OPTS \ 305 | $MAVEN_DEBUG_OPTS \ 306 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 307 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 308 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 309 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.2.0 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file 157 | SET WRAPPER_SHA_256_SUM="" 158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B 160 | ) 161 | IF NOT %WRAPPER_SHA_256_SUM%=="" ( 162 | powershell -Command "&{"^ 163 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ 164 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ 165 | " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ 166 | " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ 167 | " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ 168 | " exit 1;"^ 169 | "}"^ 170 | "}" 171 | if ERRORLEVEL 1 goto error 172 | ) 173 | 174 | @REM Provide a "standardized" way to retrieve the CLI args that will 175 | @REM work with both Windows and non-Windows executions. 176 | set MAVEN_CMD_LINE_ARGS=%* 177 | 178 | %MAVEN_JAVA_EXE% ^ 179 | %JVM_CONFIG_MAVEN_PROPS% ^ 180 | %MAVEN_OPTS% ^ 181 | %MAVEN_DEBUG_OPTS% ^ 182 | -classpath %WRAPPER_JAR% ^ 183 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 184 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 185 | if ERRORLEVEL 1 goto error 186 | goto end 187 | 188 | :error 189 | set ERROR_CODE=1 190 | 191 | :end 192 | @endlocal & set ERROR_CODE=%ERROR_CODE% 193 | 194 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 195 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 196 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 197 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 198 | :skipRcPost 199 | 200 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 201 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 202 | 203 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 204 | 205 | cmd /C exit /B %ERROR_CODE% 206 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 3.3.0-SNAPSHOT 10 | 11 | 12 | com.spring 13 | myfood 14 | 0.0.1-SNAPSHOT 15 | myfood 16 | This project is meant to demostrate the ranking system of a delivery app 17 | 18 | 17 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-actuator 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-data-mongodb 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-validation 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-web 36 | 37 | 38 | org.projectlombok 39 | lombok 40 | true 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-test 45 | test 46 | 47 | 48 | org.springdoc 49 | springdoc-openapi-starter-webmvc-ui 50 | 2.1.0 51 | 52 | 53 | org.springdoc 54 | springdoc-openapi-starter-webflux-ui 55 | 2.1.0 56 | 57 | 58 | io.springfox 59 | springfox-boot-starter 60 | 3.0.0 61 | 62 | 63 | 64 | 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-maven-plugin 69 | 70 | 71 | 72 | org.projectlombok 73 | lombok 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | spring-milestones 83 | Spring Milestones 84 | https://repo.spring.io/milestone 85 | 86 | false 87 | 88 | 89 | 90 | spring-snapshots 91 | Spring Snapshots 92 | https://repo.spring.io/snapshot 93 | 94 | false 95 | 96 | 97 | 98 | 99 | 100 | spring-milestones 101 | Spring Milestones 102 | https://repo.spring.io/milestone 103 | 104 | false 105 | 106 | 107 | 108 | spring-snapshots 109 | Spring Snapshots 110 | https://repo.spring.io/snapshot 111 | 112 | false 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/MyfoodApplication.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.ComponentScan; 6 | 7 | @SpringBootApplication 8 | @ComponentScan(basePackages = { "com.spring.myfood.config", "com.spring.myfood.controller", "com.spring.myfood.service", 9 | "com.spring.myfood.model", "com.spring.myfood.repository", "com.spring.myfood.enums", 10 | "com.spring.myfood.mongo" }) 11 | public class MyfoodApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(MyfoodApplication.class, args); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/config/CorsConfig.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | @Configuration 9 | @EnableWebMvc 10 | public class CorsConfig implements WebMvcConfigurer { 11 | @Override 12 | public void addCorsMappings(CorsRegistry registry) { 13 | registry.addMapping("/**") 14 | .allowedOrigins("*") 15 | .allowedMethods("GET", "POST", "PUT", "DELETE") 16 | .allowedHeaders("*") 17 | .maxAge(3600); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/config/MongoConfig.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.config; 2 | 3 | import java.util.Collection; 4 | import java.util.Collections; 5 | 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration; 8 | import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; 9 | 10 | import com.mongodb.ConnectionString; 11 | import com.mongodb.MongoClientSettings; 12 | import com.mongodb.client.MongoClient; 13 | import com.mongodb.client.MongoClients; 14 | 15 | @Configuration 16 | @EnableMongoRepositories("com.spring.myfood.repository") 17 | public class MongoConfig extends AbstractMongoClientConfiguration { 18 | 19 | @Override 20 | protected String getDatabaseName() { 21 | return "my-food"; 22 | } 23 | 24 | @Override 25 | public MongoClient mongoClient() { 26 | ConnectionString connectionString = new ConnectionString( 27 | "mongodb+srv://jaime:march@second.sreaptl.mongodb.net/my-food"); 28 | MongoClientSettings mongoClientSettings = MongoClientSettings.builder() 29 | .applyConnectionString(connectionString) 30 | .build(); 31 | 32 | return MongoClients.create(mongoClientSettings); 33 | } 34 | 35 | @Override 36 | public Collection getMappingBasePackages() { 37 | return Collections.singleton("com.spring.myfood.model"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/config/SwaggerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.config; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.web.servlet.config.annotation.*; 8 | import springfox.documentation.swagger.web.UiConfiguration; 9 | import springfox.documentation.swagger.web.UiConfigurationBuilder; 10 | 11 | import io.swagger.v3.oas.models.OpenAPI; 12 | import io.swagger.v3.oas.models.info.Contact; 13 | import io.swagger.v3.oas.models.info.Info; 14 | import io.swagger.v3.oas.models.servers.Server; 15 | 16 | @Configuration 17 | public class SwaggerConfiguration implements WebMvcConfigurer { 18 | 19 | @Bean 20 | public OpenAPI customOpenAPI() { 21 | return new OpenAPI() 22 | .servers(List.of(new Server().url("https://app-myfood-production.up.railway.app/"), 23 | new Server().url("http://localhost:8080/"))) 24 | .info(new Info() 25 | .title("My Food - Ranking System") 26 | .description("This documentation comprises all endpoints for the My Food application. My Food is a fictional app that implements a ranking system. The purpose of this project is to showcase the most searched foods and their associated scores within the fictitious app named MyFood.") 27 | .version("1.1") 28 | .contact(new Contact().name("GitHub Repository") 29 | .url("https://github.com/LadyJessie19/MyFood"))); 30 | } 31 | 32 | @Bean 33 | public UiConfiguration uiConfig() { 34 | return UiConfigurationBuilder.builder().build(); 35 | } 36 | 37 | @Bean 38 | public WebMvcConfigurer corsConfigurer() { 39 | return new WebMvcConfigurer() { 40 | @Override 41 | public void addCorsMappings(CorsRegistry registry) { 42 | registry.addMapping("/swagger-ui/**").allowedOrigins("*"); 43 | } 44 | }; 45 | } 46 | 47 | @Override 48 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 49 | registry.addResourceHandler("/swagger-ui/**") 50 | .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/") 51 | .resourceChain(false); 52 | registry.addResourceHandler("/swagger-ui.html") 53 | .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/") 54 | .resourceChain(false); 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/controller/MainController.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.controller; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | import io.swagger.v3.oas.annotations.Operation; 8 | import io.swagger.v3.oas.annotations.media.Content; 9 | import io.swagger.v3.oas.annotations.media.Schema; 10 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 11 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 12 | import io.swagger.v3.oas.annotations.tags.Tag; 13 | 14 | @RestController 15 | @RequestMapping("/") 16 | @Tag(name = "Main - Home and Health", description = "Home and Health endpoints") 17 | public class MainController { 18 | 19 | @Operation(summary = "This is the landing route to the application") 20 | @ApiResponses(value = { 21 | @ApiResponse(responseCode = "200", description = "Home is ok", content = { 22 | @Content(mediaType = "text/plain", schema = @Schema(implementation = String.class)) }), 23 | @ApiResponse(responseCode = "404", description = "Error - Not found", content = @Content), 24 | @ApiResponse(responseCode = "500", description = "Internal server error", content = @Content) }) 25 | @GetMapping 26 | public String home() { 27 | return "Hi! This is the MyFood API. Check out the docs at https://app-myfood-production.up.railway.app/swagger-ui/index.html#/"; 28 | } 29 | 30 | @Operation(summary = "Check if MyFood is running") 31 | @ApiResponses(value = { 32 | @ApiResponse(responseCode = "200", description = "Api is ok", content = { 33 | @Content(mediaType = "text/plain", schema = @Schema(implementation = String.class)) }), 34 | @ApiResponse(responseCode = "404", description = "Api is not ok", content = @Content) }) 35 | @GetMapping("health") 36 | public String health() { 37 | return "Is MyFood running? I'm thumbs up!"; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/controller/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.controller; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RequestParam; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | import com.spring.myfood.dtos.request.RequestProductDTO; 8 | import com.spring.myfood.dtos.response.ResponseSearchFoodDTO; 9 | import com.spring.myfood.enums.FoodCategoryEnum; 10 | import com.spring.myfood.model.Product; 11 | import com.spring.myfood.service.ProductService; 12 | 13 | import io.swagger.v3.oas.annotations.Operation; 14 | import io.swagger.v3.oas.annotations.Parameter; 15 | import io.swagger.v3.oas.annotations.media.Content; 16 | import io.swagger.v3.oas.annotations.media.ExampleObject; 17 | import io.swagger.v3.oas.annotations.media.Schema; 18 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 19 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 20 | import io.swagger.v3.oas.annotations.tags.Tag; 21 | 22 | import java.net.URI; 23 | import java.util.Arrays; 24 | import java.util.List; 25 | 26 | import org.apache.coyote.BadRequestException; 27 | import org.springframework.beans.factory.annotation.Autowired; 28 | import org.springframework.data.domain.Page; 29 | import org.springframework.data.domain.PageRequest; 30 | import org.springframework.data.domain.Pageable; 31 | import org.springframework.http.ResponseEntity; 32 | import org.springframework.web.bind.annotation.GetMapping; 33 | import org.springframework.web.bind.annotation.PathVariable; 34 | import org.springframework.web.bind.annotation.PostMapping; 35 | import org.springframework.web.bind.annotation.RequestBody; 36 | 37 | @RestController 38 | @RequestMapping("/products") 39 | @Tag(name = "Products", description = "The Products endpoints to create and list products") 40 | public class ProductController { 41 | 42 | @Autowired 43 | public ProductService productService; 44 | 45 | @PostMapping("register") 46 | @Operation(summary = "Register a new product") 47 | @ApiResponses(value = { 48 | @ApiResponse(responseCode = "201", description = "Product created", content = { 49 | @Content(mediaType = "application/json", schema = @Schema(implementation = Product.class)) }), 50 | @ApiResponse(responseCode = "404", description = "Product not created", content = @Content), 51 | @ApiResponse(responseCode = "400", description = "Bad request", content = @Content), 52 | @ApiResponse(responseCode = "500", description = "Internal server error", content = @Content) }) 53 | public ResponseEntity registerProduct( 54 | @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Product to register", required = true, content = @Content(mediaType = "application/json", schema = @Schema(implementation = RequestProductDTO.class), examples = @ExampleObject(value = "{\n" 55 | + 56 | " \"name\": \"The product name goes here\",\n" + 57 | " \"description\": \"The product description goes here\",\n" + 58 | " \"price\": 10.0,\n" + 59 | " \"image\": \"An image url\",\n" + 60 | " \"category\": \"FoodCategoryEnum (PIZZA - HAMBURGER - SANDWICH - PASTA - SALAD - SUSHI - BBQ - DESSERT - VEGETARIAN - VEGAN - FRUIT - BEVERAGE )\"\n" 61 | + 62 | "}"))) @RequestBody(required = true) RequestProductDTO product) 63 | throws BadRequestException { 64 | 65 | if (product == null) { 66 | return ResponseEntity.badRequest().body(null); 67 | } 68 | 69 | return ResponseEntity.created(URI.create("/" + product.getName())) 70 | .body(productService.registerNewProduct(product)); 71 | } 72 | 73 | @GetMapping("/all") 74 | @Operation(summary = "List all products") 75 | @ApiResponses(value = { 76 | @ApiResponse(responseCode = "200", description = "List of products", content = { 77 | @Content(mediaType = "application/json", schema = @Schema(implementation = Product.class)) }), 78 | @ApiResponse(responseCode = "404", description = "Products not found", content = @Content), 79 | @ApiResponse(responseCode = "400", description = "Bad request / Invalid category", content = @Content), 80 | @ApiResponse(responseCode = "500", description = "Internal server error", content = @Content) 81 | }) 82 | public ResponseEntity> listAllProducts( 83 | @Parameter(description = "Page number.") @RequestParam(defaultValue = "0") int page, 84 | @Parameter(description = "Number of products per page.") @RequestParam(defaultValue = "5") int size, 85 | @Parameter(description = "Optional parameter to filter products by category.") @RequestParam(required = false) FoodCategoryEnum category) { 86 | 87 | Pageable pageable = PageRequest.of(page, size); 88 | 89 | if (category != null && this.isValidCategory(category.toString())) { 90 | Page products = productService.findProductsByCategory(category, pageable); 91 | return ResponseEntity.ok().body(products); 92 | } else { 93 | Page products = productService.findAllProducts(pageable); 94 | return ResponseEntity.ok().body(products); 95 | } 96 | } 97 | 98 | @GetMapping("/{id}") 99 | @Operation(summary = "Find a product by id (MongoDB ObjectId)") 100 | @ApiResponses(value = { 101 | @ApiResponse(responseCode = "200", description = "Product found", content = { 102 | @Content(mediaType = "application/json", schema = @Schema(implementation = Product.class)) }), 103 | @ApiResponse(responseCode = "404", description = "Product not found", content = @Content), 104 | @ApiResponse(responseCode = "500", description = "Internal server error", content = @Content) }) 105 | public ResponseEntity findProductById( 106 | @Parameter(description = "Product id", required = true) @PathVariable String id) { 107 | return ResponseEntity.ok().body(productService.findProductById(id)); 108 | } 109 | 110 | private boolean isValidCategory(String category) { 111 | return Arrays.stream(FoodCategoryEnum.values()) 112 | .anyMatch(enumValue -> enumValue.name().equalsIgnoreCase(category)); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/controller/RankingController.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.controller; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.data.domain.PageRequest; 8 | import org.springframework.data.domain.Pageable; 9 | import org.springframework.data.web.PageableDefault; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestParam; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | import com.spring.myfood.model.Product; 18 | import com.spring.myfood.model.Ranking; 19 | import com.spring.myfood.service.RankingService; 20 | 21 | import io.swagger.v3.oas.annotations.Operation; 22 | import io.swagger.v3.oas.annotations.Parameter; 23 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 24 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 25 | import io.swagger.v3.oas.annotations.media.Content; 26 | import io.swagger.v3.oas.annotations.tags.Tag; 27 | import io.swagger.v3.oas.annotations.media.Schema; 28 | 29 | @RestController 30 | @RequestMapping("/rankings") 31 | @Tag(name = "Ranking", description = "The Ranking endpoints to search and view products and categories scores") 32 | public class RankingController { 33 | 34 | @Autowired 35 | private RankingService rankingService; 36 | 37 | @GetMapping("/search") 38 | @Operation(summary = "Search for products at the database") 39 | @ApiResponses(value = { 40 | @ApiResponse(responseCode = "200", description = "List of products", content = { 41 | @Content(mediaType = "application/json", schema = @Schema(implementation = Product.class)) }), 42 | @ApiResponse(responseCode = "404", description = "Products not found", content = @Content), 43 | @ApiResponse(responseCode = "500", description = "Internal server error", content = @Content) 44 | }) 45 | public ResponseEntity> searchFood( 46 | @Parameter(description = "The name of the food", required = true) @RequestParam String foodTitle) { 47 | 48 | if (foodTitle == null || foodTitle.isEmpty()) { 49 | return ResponseEntity.ok().body(Collections.emptyList()); 50 | } 51 | 52 | return ResponseEntity.ok().body(rankingService.searchingFoods(foodTitle)); 53 | } 54 | 55 | @GetMapping("/top-foods") 56 | @Operation(summary = "List of the 3 top most searched foods") 57 | @ApiResponses(value = { 58 | @ApiResponse(responseCode = "200", description = "List of rankings", content = { 59 | @Content(mediaType = "application/json", schema = @Schema(implementation = Ranking.class)) }), 60 | @ApiResponse(responseCode = "404", description = "The rankings were not found", content = @Content), 61 | @ApiResponse(responseCode = "500", description = "Internal server error", content = @Content) 62 | }) 63 | public ResponseEntity> findMostScoredFoods() { 64 | Pageable pageable = PageRequest.of(0, 3); 65 | return ResponseEntity.ok().body(rankingService.mostScoredFoods(pageable)); 66 | } 67 | 68 | @GetMapping("/top-categories") 69 | @Operation(summary = "List of the 3 top most searched categories") 70 | @ApiResponses(value = { 71 | @ApiResponse(responseCode = "200", description = "List of rankings", content = { 72 | @Content(mediaType = "application/json", schema = @Schema(implementation = Ranking.class)) }), 73 | @ApiResponse(responseCode = "404", description = "The rankings were not found", content = @Content), 74 | @ApiResponse(responseCode = "500", description = "Internal server error", content = @Content) 75 | }) 76 | public ResponseEntity> findMostSearchedCategories() { 77 | Pageable pageable = PageRequest.of(0, 3); 78 | return ResponseEntity.ok().body(rankingService.findMostSearchedCategories(pageable)); 79 | } 80 | 81 | @GetMapping("/{id}") 82 | @Operation(summary = "Find a ranking by id (MongoDB ObjectId)") 83 | @ApiResponses(value = { 84 | @ApiResponse(responseCode = "200", description = "Ranking found", content = { 85 | @Content(mediaType = "application/json", schema = @Schema(implementation = Ranking.class)) }), 86 | @ApiResponse(responseCode = "404", description = "Ranking not found", content = @Content), 87 | @ApiResponse(responseCode = "500", description = "Internal server error", content = @Content) 88 | }) 89 | public ResponseEntity findRankingById( 90 | @Parameter(description = "The Ranking id", required = true) @PathVariable String id) { 91 | return ResponseEntity.ok().body(rankingService.findRankingById(id)); 92 | } 93 | } -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/dtos/request/RequestProductDTO.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.dtos.request; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @Getter 10 | @Setter 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class RequestProductDTO { 14 | @NotBlank(message = "Name is required") 15 | private String name; 16 | 17 | @NotBlank(message = "Description is required") 18 | private String description; 19 | 20 | @NotBlank(message = "Price is required") 21 | private int price; 22 | 23 | private String image; 24 | 25 | @NotBlank(message = "Category is required") 26 | private String category; 27 | 28 | @Override 29 | public String toString() { 30 | return "RequestProductDTO [ name=" + name + ", price=" + price + ", category=" + category + ", description=" 31 | + description + ", image=" + image; 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/dtos/response/ResponseSearchFoodDTO.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.dtos.response; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | 6 | import com.spring.myfood.enums.FoodCategoryEnum; 7 | import com.spring.myfood.enums.RankingTypeEnum; 8 | import com.spring.myfood.model.Product; 9 | import com.spring.myfood.model.Ranking; 10 | import com.spring.myfood.repository.RankingRepository; 11 | 12 | import jakarta.annotation.PostConstruct; 13 | import lombok.AllArgsConstructor; 14 | import lombok.NoArgsConstructor; 15 | import lombok.Setter; 16 | import lombok.Getter; 17 | 18 | @Getter 19 | @Setter 20 | @NoArgsConstructor 21 | @AllArgsConstructor 22 | @Component 23 | public class ResponseSearchFoodDTO { 24 | @Autowired 25 | private RankingRepository rankingRepository; 26 | 27 | public ResponseSearchFoodDTO(Product product) { 28 | this.name = product.getName(); 29 | this.description = product.getDescription(); 30 | this.price = product.getPrice(); 31 | this.image = product.getImage(); 32 | this.category = product.getCategory(); 33 | } 34 | 35 | private String name; 36 | private String description; 37 | private int price; 38 | private String image; 39 | private FoodCategoryEnum category; 40 | private int score = 0; 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/enums/FoodCategoryEnum.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor 8 | public enum FoodCategoryEnum { 9 | PIZZA("Pizza"), 10 | HAMBURGER("Hamburger"), 11 | SANDWICH("Sandwich"), 12 | PASTA("Pasta"), 13 | SALAD("Salad"), 14 | SUSHI("Sushi"), 15 | BBQ("BBQ"), 16 | DESSERT("Dessert"), 17 | VEGETARIAN("Vegetarian"), 18 | VEGAN("Vegan"), 19 | FRUIT("Fruit"), 20 | BEVERAGE("Beverage"); 21 | 22 | private final String category; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/enums/RankingTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @AllArgsConstructor 7 | @Getter 8 | public enum RankingTypeEnum { 9 | FOOD("Food"), 10 | CATEGORY("Category"); 11 | 12 | private final String type; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/model/Product.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.model; 2 | 3 | import java.util.Date; 4 | 5 | import org.springframework.data.annotation.CreatedDate; 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.annotation.LastModifiedDate; 8 | import org.springframework.data.mongodb.core.mapping.Field; 9 | 10 | import com.fasterxml.jackson.annotation.JsonIgnore; 11 | import com.spring.myfood.enums.FoodCategoryEnum; 12 | 13 | import lombok.AllArgsConstructor; 14 | import lombok.Getter; 15 | import lombok.NoArgsConstructor; 16 | import lombok.Setter; 17 | 18 | @Getter 19 | @AllArgsConstructor 20 | @NoArgsConstructor 21 | public class Product { 22 | @Id 23 | private String id; 24 | 25 | @Setter 26 | @Field 27 | private String name; 28 | 29 | @Setter 30 | @Field 31 | private String description; 32 | 33 | @Setter 34 | @Field 35 | private int price; 36 | 37 | @Setter 38 | @Field 39 | private String image; 40 | 41 | @Setter 42 | @Field 43 | private FoodCategoryEnum category; 44 | 45 | @JsonIgnore 46 | @CreatedDate 47 | private Date createdAt; 48 | 49 | @JsonIgnore 50 | @LastModifiedDate 51 | private Date updatedAt; 52 | 53 | @Override 54 | public String toString() { 55 | return "Product{" + 56 | "id=" + id + 57 | ", name='" + name + '\'' + 58 | ", description='" + description + '\'' + 59 | ", price=" + price + 60 | ", image='" + image + '\''; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/model/Ranking.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.model; 2 | 3 | import java.util.Date; 4 | 5 | import org.springframework.data.annotation.CreatedDate; 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.annotation.LastModifiedDate; 8 | import org.springframework.data.mongodb.core.mapping.Field; 9 | 10 | import com.fasterxml.jackson.annotation.JsonIgnore; 11 | import com.spring.myfood.enums.RankingTypeEnum; 12 | 13 | import lombok.AllArgsConstructor; 14 | import lombok.Getter; 15 | import lombok.NoArgsConstructor; 16 | import lombok.Setter; 17 | 18 | @Getter 19 | @AllArgsConstructor 20 | @NoArgsConstructor 21 | public class Ranking { 22 | 23 | public Ranking(String title, RankingTypeEnum type, int score) { 24 | this.title = title; 25 | this.type = type; 26 | this.score = score; 27 | } 28 | 29 | @Id 30 | private String id; 31 | 32 | @Setter 33 | @Field 34 | private String title; 35 | 36 | @Setter 37 | @Field 38 | private int score = 1; 39 | 40 | @Setter 41 | @Field 42 | private RankingTypeEnum type; 43 | 44 | @JsonIgnore 45 | @CreatedDate 46 | private Date createdAt; 47 | 48 | @JsonIgnore 49 | @LastModifiedDate 50 | private Date updatedAt; 51 | 52 | @Override 53 | public String toString() { 54 | return "Ranking{" + 55 | "id=" + id + 56 | ", title='" + title + '\'' + 57 | ", score=" + score + 58 | ", type='" + type + '\'' + 59 | ", createdAt=" + createdAt + 60 | ", updatedAt=" + updatedAt + 61 | '}'; 62 | } 63 | } -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/mongo/MyFoodMongo.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.mongo; 2 | 3 | import java.util.List; 4 | import java.util.regex.Pattern; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.data.domain.Page; 8 | import org.springframework.data.domain.Pageable; 9 | import org.springframework.data.domain.Sort; 10 | import org.springframework.data.mongodb.core.MongoTemplate; 11 | import org.springframework.data.mongodb.core.query.Criteria; 12 | import org.springframework.data.mongodb.core.query.Query; 13 | import org.springframework.data.support.PageableExecutionUtils; 14 | import org.springframework.stereotype.Component; 15 | 16 | import com.spring.myfood.enums.FoodCategoryEnum; 17 | import com.spring.myfood.enums.RankingTypeEnum; 18 | import com.spring.myfood.model.Product; 19 | import com.spring.myfood.model.Ranking; 20 | 21 | @Component 22 | public class MyFoodMongo { 23 | 24 | @Autowired 25 | private MongoTemplate mongoTemplate; 26 | 27 | public Ranking findRankingByTitleAndType(String title, RankingTypeEnum type) { 28 | Query query = new Query(); 29 | query.addCriteria(Criteria.where("title").is(title) 30 | .regex(Pattern.compile("^" + title + "$", Pattern.CASE_INSENSITIVE)).and("type").is(type)); 31 | return mongoTemplate.findOne(query, Ranking.class); 32 | } 33 | 34 | public List searchFoods(String foodTitle) { 35 | Query query = new Query(); 36 | Criteria criteria = Criteria.where("name") 37 | .regex(Pattern.compile(".*" + foodTitle + ".*", Pattern.CASE_INSENSITIVE)); 38 | query.addCriteria(criteria); 39 | List products = mongoTemplate.find(query, Product.class); 40 | 41 | return products; 42 | } 43 | 44 | public List findMostScoredFoods(Pageable pageable) { 45 | Query query = new Query(Criteria.where("type").is("FOOD")) 46 | .with(Sort.by(Sort.Order.desc("score"))) 47 | .with(pageable); 48 | return mongoTemplate.find(query, Ranking.class); 49 | } 50 | 51 | public List findMostSearchedCategories(Pageable pageable) { 52 | Query query = new Query(Criteria.where("type").is("CATEGORY")) 53 | .with(Sort.by(Sort.Order.desc("score"))) 54 | .with(pageable); 55 | return mongoTemplate.find(query, Ranking.class); 56 | } 57 | 58 | public Page findProductsByCategory(FoodCategoryEnum category, Pageable pageable) { 59 | Query query = new Query(); 60 | query.addCriteria(Criteria.where("category").is(category)); 61 | query.with(pageable); 62 | 63 | // Nova instância de Query apenas para a contagem 64 | Query countQuery = new Query(); 65 | countQuery.addCriteria(Criteria.where("category").is(category)); 66 | 67 | long total = mongoTemplate.count(countQuery, Product.class); 68 | 69 | return PageableExecutionUtils.getPage( 70 | mongoTemplate.find(query, Product.class), 71 | pageable, 72 | () -> total); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/repository/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.repository; 2 | 3 | import org.springframework.data.mongodb.repository.MongoRepository; 4 | 5 | import com.spring.myfood.model.Product; 6 | 7 | public interface ProductRepository extends MongoRepository { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/repository/RankingRepository.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.repository; 2 | 3 | import org.springframework.data.mongodb.repository.MongoRepository; 4 | 5 | import com.spring.myfood.model.Ranking; 6 | 7 | public interface RankingRepository extends MongoRepository { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/service/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.service; 2 | 3 | import java.util.List; 4 | 5 | import org.apache.coyote.BadRequestException; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.data.domain.Page; 8 | import org.springframework.data.domain.Pageable; 9 | import org.springframework.stereotype.Service; 10 | 11 | import com.spring.myfood.dtos.request.RequestProductDTO; 12 | import com.spring.myfood.dtos.response.ResponseSearchFoodDTO; 13 | import com.spring.myfood.enums.FoodCategoryEnum; 14 | import com.spring.myfood.enums.RankingTypeEnum; 15 | import com.spring.myfood.model.Product; 16 | import com.spring.myfood.model.Ranking; 17 | import com.spring.myfood.mongo.MyFoodMongo; 18 | import com.spring.myfood.repository.ProductRepository; 19 | 20 | @Service 21 | public class ProductService { 22 | 23 | @Autowired 24 | public ProductRepository productRepository; 25 | 26 | @Autowired 27 | private MyFoodMongo myFoodMongo; 28 | 29 | public Product registerNewProduct(RequestProductDTO product) throws BadRequestException { 30 | 31 | String upperCategory = product.getCategory().toUpperCase(); 32 | 33 | try { 34 | FoodCategoryEnum.valueOf(upperCategory); 35 | } catch (IllegalArgumentException e) { 36 | throw new BadRequestException("Category not found"); 37 | } 38 | 39 | FoodCategoryEnum category = FoodCategoryEnum.valueOf(upperCategory); 40 | 41 | if ((product.getName() == null) && (product.getDescription() == null) && (product.getPrice() == 0) 42 | && (product.getImage() == null)) { 43 | throw new BadRequestException("All fields must be filled"); 44 | } 45 | 46 | Product newProduct = new Product(); 47 | newProduct.setName(product.getName()); 48 | newProduct.setDescription(product.getDescription()); 49 | newProduct.setPrice(product.getPrice()); 50 | newProduct.setImage(product.getImage()); 51 | newProduct.setCategory(category); 52 | 53 | return productRepository.save(newProduct); 54 | } 55 | 56 | public Page findAllProducts(Pageable pageable) { 57 | return productRepository.findAll(pageable); 58 | } 59 | 60 | public Page findProductsByCategory(FoodCategoryEnum category, Pageable pageable) { 61 | return myFoodMongo.findProductsByCategory(category, pageable); 62 | } 63 | 64 | public ResponseSearchFoodDTO findProductById(String id) { 65 | if (id == null) { 66 | throw new IllegalArgumentException("Id cannot be null"); 67 | } 68 | Product product = productRepository.findById(id).get(); 69 | 70 | if (product == null) { 71 | throw new IllegalArgumentException("Product not found"); 72 | } 73 | 74 | ResponseSearchFoodDTO response = new ResponseSearchFoodDTO(); 75 | response.setName(product.getName()); 76 | response.setDescription(product.getDescription()); 77 | response.setPrice(product.getPrice()); 78 | response.setImage(product.getImage()); 79 | response.setCategory(product.getCategory()); 80 | 81 | Ranking foundRanking = myFoodMongo.findRankingByTitleAndType(product.getName(), RankingTypeEnum.FOOD); 82 | 83 | if (foundRanking != null) { 84 | response.setScore(foundRanking.getScore()); 85 | } else { 86 | response.setScore(0); 87 | } 88 | 89 | return response; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/service/RankingService.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.service; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.data.domain.Pageable; 8 | import org.springframework.stereotype.Service; 9 | 10 | import com.spring.myfood.enums.FoodCategoryEnum; 11 | import com.spring.myfood.enums.RankingTypeEnum; 12 | import com.spring.myfood.model.Product; 13 | import com.spring.myfood.model.Ranking; 14 | import com.spring.myfood.mongo.MyFoodMongo; 15 | import com.spring.myfood.repository.ProductRepository; 16 | import com.spring.myfood.repository.RankingRepository; 17 | 18 | @Service 19 | public class RankingService { 20 | 21 | @Autowired 22 | public RankingRepository rankingRepository; 23 | 24 | @Autowired 25 | public ProductRepository productRepository; 26 | 27 | @Autowired 28 | private MyFoodMongo myFoodMongo; 29 | 30 | public List searchingFoods(String foodTitle) { 31 | List foundProducts = searchFoods(foodTitle.toLowerCase()); 32 | 33 | for (Product product : foundProducts) { 34 | updateFoodRanking(product, RankingTypeEnum.FOOD); 35 | } 36 | 37 | return foundProducts; 38 | } 39 | 40 | public void updateFoodRanking(Product product, RankingTypeEnum type) { 41 | 42 | if (product != null) { 43 | 44 | Ranking existsRanking = myFoodMongo.findRankingByTitleAndType(product.getName(), type); 45 | 46 | if (existsRanking != null) { 47 | updateCategoryRanking(existsRanking.getTitle()); 48 | existsRanking.setScore(existsRanking.getScore() + 1); 49 | rankingRepository.save(existsRanking); 50 | } else { 51 | Ranking newRanking = new Ranking(product.getName(), type, 1); 52 | updateCategoryRanking(newRanking.getTitle()); 53 | rankingRepository.save(newRanking); 54 | } 55 | } else { 56 | throw new IllegalArgumentException("Product cannot be null"); 57 | } 58 | } 59 | 60 | public void updateCategoryRanking(String foodTitle) { 61 | 62 | List foundProduct = myFoodMongo.searchFoods(foodTitle); 63 | 64 | if (foundProduct.isEmpty()) { 65 | return; 66 | } 67 | 68 | String category = foundProduct.get(0).getCategory().toString().toUpperCase(); 69 | 70 | if (isValidCategory(category)) { 71 | Ranking existsRanking = myFoodMongo.findRankingByTitleAndType(category, RankingTypeEnum.CATEGORY); 72 | 73 | if (existsRanking != null) { 74 | existsRanking.setScore(existsRanking.getScore() + 1); 75 | rankingRepository.save(existsRanking); 76 | } else { 77 | Ranking newRanking = new Ranking(category, RankingTypeEnum.CATEGORY, 1); 78 | rankingRepository.save(newRanking); 79 | } 80 | } else { 81 | return; 82 | } 83 | } 84 | 85 | private boolean isValidCategory(String category) { 86 | try { 87 | FoodCategoryEnum.valueOf(category); 88 | return true; 89 | } catch (IllegalArgumentException e) { 90 | return false; 91 | } 92 | } 93 | 94 | private List searchFoods(String lowerFoodTitle) { 95 | return myFoodMongo.searchFoods(lowerFoodTitle); 96 | } 97 | 98 | public Ranking findRankingById(String id) { 99 | if (id == null) { 100 | throw new IllegalArgumentException("Id cannot be null"); 101 | } 102 | Optional foundRanking = rankingRepository.findById(id); 103 | 104 | if (foundRanking.isEmpty()) { 105 | throw new IllegalArgumentException("Ranking not found"); 106 | } 107 | 108 | return foundRanking.get(); 109 | } 110 | 111 | public List mostScoredFoods(Pageable pageable) { 112 | return myFoodMongo.findMostScoredFoods(pageable); 113 | } 114 | 115 | public List findMostSearchedCategories(Pageable pageable) { 116 | return myFoodMongo.findMostSearchedCategories(pageable); 117 | } 118 | } -------------------------------------------------------------------------------- /src/main/java/com/spring/myfood/utils/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood.utils; 2 | 3 | import org.springframework.data.crossstore.ChangeSetPersister.NotFoundException; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.ControllerAdvice; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | 9 | import jakarta.validation.ConstraintViolationException; 10 | 11 | import org.springframework.web.bind.MethodArgumentNotValidException; 12 | import org.springframework.validation.FieldError; 13 | 14 | import java.nio.file.AccessDeniedException; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | @ControllerAdvice 19 | public class GlobalExceptionHandler { 20 | 21 | @ExceptionHandler(Exception.class) 22 | public ResponseEntity handleException(Exception e) { 23 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) 24 | .body("Ocorreu um erro no servidor: " + e.getMessage()); 25 | } 26 | 27 | @ExceptionHandler(NotFoundException.class) 28 | public ResponseEntity handleResourceNotFoundException(NotFoundException e) { 29 | return ResponseEntity.status(HttpStatus.NOT_FOUND) 30 | .body("Recurso não encontrado: " + e.getMessage()); 31 | } 32 | 33 | @ExceptionHandler(MethodArgumentNotValidException.class) 34 | public ResponseEntity> handleValidationExceptions(MethodArgumentNotValidException e) { 35 | Map errors = new HashMap<>(); 36 | e.getBindingResult().getAllErrors().forEach(error -> { 37 | String fieldName = ((FieldError) error).getField(); 38 | String errorMessage = error.getDefaultMessage(); 39 | errors.put(fieldName, errorMessage); 40 | }); 41 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors); 42 | } 43 | 44 | @ExceptionHandler(AccessDeniedException.class) 45 | public ResponseEntity handleAccessDeniedException(AccessDeniedException e) { 46 | return ResponseEntity.status(HttpStatus.FORBIDDEN) 47 | .body("Acesso negado: " + e.getMessage()); 48 | } 49 | 50 | @ExceptionHandler(ConstraintViolationException.class) 51 | public ResponseEntity handleConstraintViolationException(ConstraintViolationException e) { 52 | return ResponseEntity.status(HttpStatus.BAD_REQUEST) 53 | .body("Violação de restrição: " + e.getMessage()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.data.mongodb.uri=mongodb://localhost:27017/test 2 | -------------------------------------------------------------------------------- /src/test/java/com/spring/myfood/MyfoodApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.spring.myfood; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class MyfoodApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /todo.todo: -------------------------------------------------------------------------------- 1 | ✔ Add o código @done(24-02-16 16:59) 2 | ✔ Model @done(24-02-16 16:59) 3 | ✔ Repository @done(24-02-16 16:59) 4 | ✔ Service @done(24-03-05 17:25) 5 | ✔ Controller @done(24-02-16 16:59) 6 | ✔ Criar entidade Product @done(24-02-16 16:59) 7 | ✔ Model @done(24-02-16 16:59) 8 | ✔ Repository @done(24-02-16 16:59) 9 | ✔ Service @done(24-02-21 17:22) 10 | ✔ Controller @done(24-02-16 17:16) 11 | ✔ Conectar no banco @done(24-02-21 16:51) 12 | ✔ Criar docs swagger @done(24-02-16 17:21) 13 | ✔ Testar @done(24-03-05 17:26) 14 | 15 | ✔ Add o pageable na rota de listar todos os produtos @done(24-03-06 14:38) 16 | ✔ Add a opção de filtrar pela categorias dos produtos @done(24-03-06 14:38) 17 | ✔ Criar um bom swagger (bem detalhado) @done(24-03-06 13:54) 18 | ✔ Fazer o deploy do projeto @done(24-03-06 13:05) 19 | ✔ Criar md explicando o projeto @done(24-03-08 12:12) --------------------------------------------------------------------------------