├── .gitignore ├── README.md ├── build.gradle.kts ├── docker-compose.yml ├── docs ├── .swagger-codegen-ignore ├── .swagger-codegen │ └── VERSION └── index.html ├── exercises.json ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── 01-architecture-atlas_application.png ├── 02-architecture-kotlin_application.png ├── 03-creating_exercises_collection.png ├── 04-creating_trigger_details.png ├── 05-creating_trigger_details.png ├── 06-creating_function.png ├── 07-app_services.png ├── 08-rename_function.png ├── 09-rename_function.png ├── 10-huggingFace_token.png ├── 11-creating_application_new_value.png ├── 12-creating_application_new_value_secret.png ├── 13-creating_application_new_value_Value.png ├── 14-values_list.png ├── 15-exercises_document_sample.png ├── 16-mongo_import.png ├── 17-mongo_import_success.png ├── 18-app_service_logs.png ├── 19-collection_exercises_with_embedded_imported.png ├── 20-postman_final_demonstration.png ├── 21-creating_atlas_vector_index.png ├── 22-creating_atlas_vector_index.png ├── 23-creating_atlas_vector_index.png ├── 24-creating_atlas_vector_index.png ├── 25-running_application.png └── 26-postman_request.png ├── settings.gradle.kts └── src ├── main ├── kotlin │ └── com │ │ └── mongodb │ │ ├── Application.kt │ │ ├── application │ │ ├── request │ │ │ ├── FitnessRequest.kt │ │ │ └── SentenceRequest.kt │ │ ├── response │ │ │ ├── ExercisesResponse.kt │ │ │ └── FitnessResponse.kt │ │ └── routes │ │ │ ├── ExercisesRoutes.kt │ │ │ └── FitnessRoute.kt │ │ ├── domain │ │ ├── entity │ │ │ ├── Exercises.kt │ │ │ └── Fitness.kt │ │ └── ports │ │ │ ├── ExerciseRepository.kt │ │ │ └── FitnessRepository.kt │ │ └── infrastructure │ │ ├── ExerciseRepositoryImpl.kt │ │ └── FitnessRepositoryImpl.kt └── resources │ ├── application.conf │ ├── logback.xml │ └── openapi │ └── documentation.yaml └── test └── kotlin └── com └── mongodb └── ApplicationTest.kt /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | bin/ 16 | !**/src/main/**/bin/ 17 | !**/src/test/**/bin/ 18 | 19 | ### IntelliJ IDEA ### 20 | .idea 21 | *.iws 22 | *.iml 23 | *.ipr 24 | out/ 25 | !**/src/main/**/out/ 26 | !**/src/test/**/out/ 27 | 28 | ### NetBeans ### 29 | /nbproject/private/ 30 | /nbbuild/ 31 | /dist/ 32 | /nbdist/ 33 | /.nb-gradle/ 34 | 35 | ### VS Code ### 36 | .vscode/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kotlin-ktor-mongodb-vector-search 2 | 3 | This is the source code about my MongoDB articles. 4 | - [`Mastering Kotlin: Creating an API With Ktor and MongoDB Atlas`](https://www.mongodb.com/developer/languages/kotlin/mastering-kotlin-creating-api-ktor-mongodb-atlas/) 5 | - [`Beyond Basics: Enhancing Kotlin Ktor API With Vector Search`](https://www.mongodb.com/developer/products/atlas/beyond-basics-enhancing-kotlin-ktor-api-vector-search/) 6 | 7 | 8 | 9 | Kotlin's simplicity, Java interoperability, and Ktor's user-friendly framework combined with MongoDB Atlas' flexible cloud database provide a robust stack for modern software development. 10 | Together, we'll demonstrate and set up the Ktor project, implement CRUD operations, define API route endpoints, and run the application. By the end, you'll have a solid understanding of Kotlin's capabilities in API development and the tools needed to succeed in modern software development. 11 | 12 | ## MongoDB Atlas Flow 13 | ![Generating Embedded Flow](https://i.ibb.co/SQGKK25/first-flow.png) 14 | 15 | ## Kotlin Ktor Flow 16 | ![Application Flow](https://i.ibb.co/JHSFdZc/second-flow.png) 17 | 18 | ## Built with 19 | 20 | - [Kotlin - Programming Language](https://kotlinlang.org/docs/coroutines-overview.html) 21 | - [Ktor - Asynchronous framework](https://ktor.io/) 22 | - [Koin - Dependency Injection framework](https://insert-koin.io/) 23 | - [MongoDB Kotlin Driver — Kotlin Coroutine](https://www.mongodb.com/docs/drivers/kotlin/coroutine/current/) 24 | 25 | ## Running 26 | 27 | Follow the steps below to get the Fitness Tracker App up and running local / MongoDB Atlas. 28 | 29 | ### Local 30 | 31 | 1. Clone the repository to your local machine: 32 | 33 | ```bash 34 | git clone https://github.com/your-username/fitness-tracker-app.git 35 | cd fitness-tracker 36 | ``` 37 | 38 | 2. Start the application using Docker Compose: 39 | 40 | ```bash 41 | docker-compose up -d 42 | ``` 43 | 44 | 3. Compile the application jar using Gradle: 45 | 46 | ```bash 47 | ./gradlew shadowJar 48 | ``` 49 | 50 | 4. Run the application 51 | 52 | ```bash 53 | java -jar -DMONGO_URI="mongodb://localhost:27017/fitness/" -DMONGO_DATABASE="my_database" build/libs 54 | ``` 55 | 56 | ### MongoDB Atlas 57 | 58 | 1. Clone the repository to your local machine: 59 | 60 | ```bash 61 | git clone https://github.com/your-username/fitness-tracker-app.git 62 | cd fitness-tracker 63 | ``` 64 | 65 | 2. Compile the application jar using Gradle: 66 | 67 | ```bash 68 | ./gradlew shadowJar 69 | ``` 70 | 71 | 4. Run the application 72 | 73 | ```bash 74 | java -jar -DMONGO_URI="mongodb+srv://:@/?retryWrites=true&w=majority" -DMONGO_DATABASE="my_database" build/libs 75 | 76 | ``` 77 | ### Swagger UI 78 | 79 | To explore the API documentation and interact with the Fitness Tracker App, you can use Swagger. Open your web browser and navigate to: 80 | 81 | http://localhost:8080/swagger-ui/ 82 | 83 | 84 | ![OpenAPI](https://i.ibb.co/r0vm3FL/swagger-git.png) 85 | 86 | 87 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | val ktor_version: String by project 2 | val kotlin_version: String by project 3 | val logback_version: String by project 4 | 5 | plugins { 6 | kotlin("jvm") version "1.9.22" 7 | id("io.ktor.plugin") version "2.3.7" 8 | id("org.jetbrains.kotlin.plugin.serialization") version "1.9.22" 9 | } 10 | 11 | group = "com.mongodb" 12 | version = "0.0.1" 13 | 14 | application { 15 | mainClass.set("com.mongodb.ApplicationKt") 16 | 17 | val isDevelopment: Boolean = project.ext.has("development") 18 | applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") 19 | } 20 | 21 | repositories { 22 | mavenCentral() 23 | } 24 | 25 | dependencies { 26 | implementation("io.ktor:ktor-server-core-jvm") 27 | implementation("io.ktor:ktor-server-swagger-jvm") 28 | implementation("io.ktor:ktor-server-content-negotiation-jvm") 29 | implementation("io.ktor:ktor-serialization-gson-jvm") 30 | implementation("io.ktor:ktor-server-tomcat-jvm") 31 | implementation("ch.qos.logback:logback-classic:$logback_version") 32 | implementation("io.ktor:ktor-server-config-yaml:2.3.8") 33 | 34 | //MongoDB 35 | implementation("org.mongodb:mongodb-driver-kotlin-coroutine:4.10.1") 36 | 37 | //Koin 38 | implementation("io.insert-koin:koin-ktor:3.5.3") 39 | implementation("io.insert-koin:koin-logger-slf4j:3.5.3") 40 | 41 | //Client 42 | implementation("io.ktor:ktor-client-core:$ktor_version") 43 | implementation("io.ktor:ktor-client-cio:$ktor_version") 44 | 45 | // tests 46 | testImplementation("io.ktor:ktor-server-tests-jvm") 47 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") 48 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | services: 4 | mongodb: 5 | image: mongo 6 | container_name: mongodb 7 | restart: always 8 | ports: 9 | - "27017:27017" 10 | volumes: 11 | - mongodb_data:/data/db 12 | 13 | volumes: 14 | mongodb_data: 15 | -------------------------------------------------------------------------------- /docs/.swagger-codegen-ignore: -------------------------------------------------------------------------------- 1 | # Swagger Codegen Ignore 2 | # Generated by swagger-codegen https://github.com/swagger-api/swagger-codegen 3 | 4 | # Use this file to prevent files from being overwritten by the generator. 5 | # The patterns follow closely to .gitignore or .dockerignore. 6 | 7 | # As an example, the C# client generator defines ApiClient.cs. 8 | # You can make changes and tell Swagger Codgen to ignore just this file by uncommenting the following line: 9 | #ApiClient.cs 10 | 11 | # You can match any string of characters against a directory, file or extension with a single asterisk (*): 12 | #foo/*/qux 13 | # The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux 14 | 15 | # You can recursively match patterns against a directory, file or extension with a double asterisk (**): 16 | #foo/**/qux 17 | # This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux 18 | 19 | # You can also negate patterns with an exclamation (!). 20 | # For example, you can ignore all files in a docs folder with the file extension .md: 21 | #docs/*.md 22 | # Then explicitly reverse the ignore rule for a single file: 23 | #!docs/README.md 24 | -------------------------------------------------------------------------------- /docs/.swagger-codegen/VERSION: -------------------------------------------------------------------------------- 1 | 3.0.52 -------------------------------------------------------------------------------- /exercises.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "exerciseNumber": 1, 4 | "title": "Banded crunch isometric hold", 5 | "description": "The banded crunch isometric hold is an exercise targeting the abdominal muscles, particularly the rectus abdominis or \"six-pack\" muscles. The band adds resistance and continuous tension to this popular exercise.", 6 | "type": "Strength", 7 | "bodyPart": "Abdominals", 8 | "equipment": "Bands", 9 | "level": "Intermediate", 10 | "rating": 5.4, 11 | "ratingDesc": "" 12 | }, 13 | { 14 | "exerciseNumber": 2, 15 | "title": "FYR Banded Plank Jack", 16 | "description": "The banded plank jack is a variation on the plank that involves moving the legs in and out for repetitions. Having a band around the thighs forces the lower body to work harder, particularly the hips and glutes. The plank jack is commonly performed as part of a bodyweight circuit, or as part of a dynamic warm-up.", 17 | "type": "Strength", 18 | "bodyPart": "Abdominals", 19 | "equipment": "Bands", 20 | "level": "Intermediate", 21 | "rating": 5.5, 22 | "ratingDesc": "" 23 | }, 24 | 25 | 26 | { 27 | "exerciseNumber": 3, 28 | "title": "Banded crunch", 29 | "description": "The banded crunch is an exercise targeting the abdominal muscles, particularly the rectus abdominis or \"six-pack\" muscles. The band adds resistance and continuous tension to this popular exercise.", 30 | "type": "Strength", 31 | "bodyPart": "Abdominals", 32 | "equipment": "Bands", 33 | "level": "Intermediate", 34 | "rating": 5.2, 35 | "ratingDesc": "" 36 | }, 37 | { 38 | "exerciseNumber": 4, 39 | "title": "Crunch", 40 | "description": "The crunch is a popular core exercise targeting the rectus abdominis, or \"six-pack\" muscles, as well as the obliques. It has been the centerpiece of many ab-focused workouts, due to both its simplicity and the intense burn and mind-muscle connection it produces. It can be performed for time or reps as part of the ab-focused portion of any workout.", 41 | "type": "Strength", 42 | "bodyPart": "Abdominals", 43 | "equipment": "Bands", 44 | "level": "Intermediate", 45 | "rating": 5.8, 46 | "ratingDesc": "" 47 | }, 48 | { 49 | "exerciseNumber": 5, 50 | "title": "Iliotibial band SMR", 51 | "description": "Iliotibial band self-myofascial release (SMR) is a self-administered soft-tissue treatment for the lower body, usually using a foam roller or other \"trigger point\" massage tool. By applying pressure strategically to the IT band along the side of the upper leg, some people find they can cause this often painful and tight area to relax or \"release.\" IT band SMR is often accompanied by SMR on the glute or quad muscles, or by stretches that target the glutes or quads.", 52 | "type": "Stretching", 53 | "bodyPart": "Abductors", 54 | "equipment": "Foam Roll", 55 | "level": "Intermediate", 56 | "rating": 8.2, 57 | "ratingDesc": "Average" 58 | }, 59 | { 60 | "exerciseNumber": 6, 61 | "title": "Adductor SMR", 62 | "description": "Adductor self-myofascial release (SMR) is a self-administered soft-tissue treatment for the lower body, usually using a foam roller, lacrosse ball, or other \"trigger point\" massage tool. By applying pressure strategically to the adductor muscles in the inner thigh region, some people find they can cause this often painful and tight muscle to relax or \"release.\" Adductor SMR is often accompanied by SMR on the iliotibial (IT) band or glutes (TFL muscles), or by stretches that target the adductors.", 63 | "type": "Stretching", 64 | "bodyPart": "Adductors", 65 | "equipment": "Foam Roll", 66 | "level": "Beginner", 67 | "rating": 4, 68 | "ratingDesc": "Average" 69 | }, 70 | 71 | { 72 | "exerciseNumber": 7, 73 | "title": "Incline dumbbell biceps curl", 74 | "description": "The incline dumbbell biceps curl is an exercise targeting the biceps and performed face up on an incline bench. This creates a vertical arm angle, which helps to isolate the biceps and limit involvement of the shoulder muscles. This curl variation is usually performed for moderate to high reps, such as 8-12 reps per set or more, as part of upper-body or arm-focused training.", 75 | "type": "Strength", 76 | "bodyPart": "Biceps", 77 | "equipment": "Bands", 78 | "level": "Intermediate", 79 | "rating": 7.0, 80 | "ratingDesc": "" 81 | }, 82 | { 83 | "exerciseNumber": 8, 84 | "title": "Banded biceps curl in squat position", 85 | "description": "The banded biceps curl in squat position is an exercise targeting the biceps, although it also works the core and lower body.", 86 | "type": "Strength", 87 | "bodyPart": "Biceps", 88 | "equipment": "Bands", 89 | "level": "Intermediate", 90 | "rating": 7.0, 91 | "ratingDesc": "" 92 | }, 93 | { 94 | "exerciseNumber": 9, 95 | "title": "Banded Hammer Press - Gethin Variation", 96 | "description": "The machine chest press is an upper body exercise targeting the pectoral muscles.", 97 | "type": "Strength", 98 | "bodyPart": "Biceps", 99 | "equipment": "Bands", 100 | "level": "Intermediate", 101 | "rating": 7.0, 102 | "ratingDesc": "" 103 | }, 104 | { 105 | "exerciseNumber": 10, 106 | "title": "Band standing concentration curl", 107 | "description": "The band standing concentration curl is a variation of a popular biceps-focused exercise utilizing a band instead of a dumbbell. It allows you to focus on one biceps at a time and really squeeze out intense peak contractions. Band concentration curls are usually performed for moderate to high reps, such as 8-12 reps per set or more, as part of an upper-body or arm-focused workout.", 108 | "type": "Strength", 109 | "bodyPart": "Biceps", 110 | "equipment": "Bands", 111 | "level": "Intermediate", 112 | "rating": 7.0, 113 | "ratingDesc": "" 114 | }, 115 | { 116 | "exerciseNumber": 11, 117 | "title": "Brachialis SMR", 118 | "description": "Brachialis self-myofascial release (SMR) is a self-administered soft-tissue treatment for the upper body, usually using a foam roller, lacrosse ball, or other \"trigger point\" massage tool. By applying pressure strategically to the biceps or brachialis muscles of the upper arm, some people find they can cause this often painful and tight muscle to relax or \"release.\" Brachialis SMR is often accompanied by SMR on the triceps muscles or pectorals, or by stretches that target the biceps or pecs.", 119 | "type": "Stretching", 120 | "bodyPart": "Biceps", 121 | "equipment": "Foam Roll", 122 | "level": "Beginner", 123 | "rating": 3.2, 124 | "ratingDesc": "Average" 125 | }, 126 | 127 | 128 | { 129 | "exerciseNumber": 12, 130 | "title": "Calf SMR", 131 | "description": "Calf self-myofascial release (SMR) is a self-administered soft-tissue treatment for the lower body, usually using a foam roller, lacrosse ball, or other \"trigger point\" massage tool. By applying pressure strategically to the calf muscles of the lower leg, some people find they can cause this often painful and tight muscle to relax or \"release.\" Calf SMR is often accompanied by SMR on the quadriceps muscles, or by stretches that target the calves.", 132 | "type": "Stretching", 133 | "bodyPart": "Calves", 134 | "equipment": "Foam Roll", 135 | "level": "Beginner", 136 | "rating": 6, 137 | "ratingDesc": "Average" 138 | }, 139 | 140 | { 141 | "exerciseNumber": 13, 142 | "title": "Standing barbell calf raise", 143 | "description": "The standing barbell calf raise is a popular exercise to target the calf muscles of the lower leg, particularly the gastrocnemius muscle. It can be performed for time or for reps, either using a barbell or Smith machine bar if balance is a problem.", 144 | "type": "Strength", 145 | "bodyPart": "Calves", 146 | "equipment": "Barbell", 147 | "level": "Beginner", 148 | "rating": 8.2, 149 | "ratingDesc": "Average" 150 | }, 151 | 152 | 153 | { 154 | "exerciseNumber": 14, 155 | "title": "Band-suspended kettlebell bench press", 156 | "description": "The band-suspended kettlebell bench press is a bench press variation where kettlebells or weight plates are hung from the ends of a barbell by looped bands. The suspended weights can be in addition to normally loaded weight plates, or in the place of them. Because the instability of the weights makes the lift difficult, this bench press variety is a popular accessory movement for building the traditional bench press in powerlifting training.", 157 | "type": "Strength", 158 | "bodyPart": "Chest", 159 | "equipment": "Bands", 160 | "level": "Intermediate", 161 | "rating": 7.0, 162 | "ratingDesc": "" 163 | }, 164 | 165 | { 166 | "exerciseNumber": 15, 167 | "title": "Incline band bench press", 168 | "description": "The incline band bench press is variation of the incline press, a popular upper-body exercise targeting the upper pectoral muscles. While it can be a strength-focused movement, it is usually performed for moderate to high reps, such as 8-12 reps per set or more, as part of upper-body or chest-focused training.", 169 | "type": "Strength", 170 | "bodyPart": "Chest", 171 | "equipment": "Bands", 172 | "level": "Intermediate", 173 | "rating": 7.0, 174 | "ratingDesc": "" 175 | }, 176 | { 177 | "exerciseNumber": 16, 178 | "title": "Band push-up", 179 | "description": "The band push-up is a progression of the popular bodyweight version of the exercise commonly used in military and tactical physical fitness tests. Adding the dynamic resistance of the band matches the strength curve of the push-up and helps build upper-body muscle and strength, emphasizing the chest, triceps, and shoulders, but also working the upper back and core.", 180 | "type": "Strength", 181 | "bodyPart": "Chest", 182 | "equipment": "Bands", 183 | "level": "Intermediate", 184 | "rating": 7.0, 185 | "ratingDesc": "" 186 | }, 187 | { 188 | "exerciseNumber": 17, 189 | "title": "Barbell Bench Press - Medium Grip", 190 | "description": "The bench press is a compound exercise that builds strength and muscle in the chest and triceps. When many people think of listing, the bench press is often the first exercise that comes to mind", 191 | "type": "Strength", 192 | "bodyPart": "Chest", 193 | "equipment": "Barbell", 194 | "level": "Intermediate", 195 | "rating": 9, 196 | "ratingDesc": "Average" 197 | }, 198 | 199 | { 200 | "exerciseNumber": 18, 201 | "title": "Standing alternating band speed row", 202 | "description": "The standing alternating band speed row is an explosive back exercise using exercise bands. It targets the muscles of the middle and upper back, including the lats (latissimus dorsi), rhomboids, and trapezius, as well as the biceps. It can be performed for time or reps either as part of a power or speed-focused portion of a workout or as a burnout at the end of a workout.", 203 | "type": "Strength", 204 | "bodyPart": "Forearms", 205 | "equipment": "Bands", 206 | "level": "Intermediate", 207 | "rating": 7.0, 208 | "ratingDesc": "" 209 | }, 210 | { 211 | "exerciseNumber": 19, 212 | "title": "Band roundhouse elbow", 213 | "description": "The band roundhouse elbow is an explosive rotational power exercise using exercise bands. It targets the muscles of the obliques and deep core, but also engrains full-body coordination and striking ability. It can work in an ab workout, in martial arts training, or in general strength and muscle training.", 214 | "type": "Strength", 215 | "bodyPart": "Forearms", 216 | "equipment": "Bands", 217 | "level": "Intermediate", 218 | "rating": 7.0, 219 | "ratingDesc": "" 220 | }, 221 | { 222 | "exerciseNumber": 20, 223 | "title": "Palms-down wrist curl over bench", 224 | "description": "The palms-down wrist curl over bench is an exercise targeting the forearms. It is usually performed for high reps, such as 10-15 reps per set or more, as part of a grip or arm-focused workout.", 225 | "type": "Strength", 226 | "bodyPart": "Forearms", 227 | "equipment": "Barbell", 228 | "level": "Intermediate", 229 | "rating": 9.5, 230 | "ratingDesc": "Average" 231 | }, 232 | { 233 | "exerciseNumber": 21, 234 | "title": "Palms-up wrist curl over bench", 235 | "description": "The palms-up wrist curl over bench is an exercise targeting the forearms. It is usually performed for high reps, such as 10-15 reps per set or more, as part of a grip or arm-focused workout.", 236 | "type": "Strength", 237 | "bodyPart": "Forearms", 238 | "equipment": "Barbell", 239 | "level": "Intermediate", 240 | "rating": 9.4, 241 | "ratingDesc": "Average" 242 | }, 243 | { 244 | "exerciseNumber": 22, 245 | "title": "Standing behind-the-back wrist curl", 246 | "description": "The standing behind-the-back wrist curl is a popular exercise to target the muscles in the forearms, wrists, and fingers. It is generally performed for moderate to high reps, such as 8-15 reps per set, and can work as isolated forearm training or as part of an arms-focused workout.", 247 | "type": "Strength", 248 | "bodyPart": "Forearms", 249 | "equipment": "Barbell", 250 | "level": "Beginner", 251 | "rating": 9.3, 252 | "ratingDesc": "Average" 253 | }, 254 | 255 | 256 | { 257 | "exerciseNumber": 23, 258 | "title": "Piriformis SMR", 259 | "description": "Piriformis self-myofascial release (SMR) is a self-administered soft-tissue treatment for the lower body, usually using a foam roller, lacrosse ball, or other \"trigger point\" massage tool. By applying pressure strategically to the piriformis muscles in the upper gluteal region, some people find they can cause this often painful and tight muscle to relax or \"release.\" Piriformis SMR is often accompanied by SMR on the iliotibial (IT) band or tensor fascia lata (TFL muscles), or by stretches that target the glutes and hips.", 260 | "type": "Stretching", 261 | "bodyPart": "Glutes", 262 | "equipment": "Foam Roll", 263 | "level": "Intermediate", 264 | "rating": 6.6, 265 | "ratingDesc": "Average" 266 | }, 267 | { 268 | "exerciseNumber": 24, 269 | "title": "Barbell glute bridge", 270 | "description": "The barbell glute bridge is a popular exercise targeting the muscles of the glutes and hamstrings. It can be done as a strength movement on its own, as an activation drill or warm-up for lower-body training, or as a burnout at the end of a lower-body workout.", 271 | "type": "Powerlifting", 272 | "bodyPart": "Glutes", 273 | "equipment": "Barbell", 274 | "level": "Intermediate", 275 | "rating": 9.4, 276 | "ratingDesc": "Average" 277 | }, 278 | { 279 | "exerciseNumber": 25, 280 | "title": "Barbell Hip Thrust", 281 | "description": "The barbell hip thrust is an exercise that engages the posterior chain, particular the glutes. It involves fully extending your hips while holding a weighted barbell across the midsection.", 282 | "type": "Powerlifting", 283 | "bodyPart": "Glutes", 284 | "equipment": "Barbell", 285 | "level": "Intermediate", 286 | "rating": 9.1, 287 | "ratingDesc": "Average" 288 | }, 289 | 290 | { 291 | "exerciseNumber": 26, 292 | "title": "Kettlebell thruster", 293 | "description": "The kettlebell thruster is a popular kettlebell movement that combines a kettlebell front squat with a kettlebell press, while holding the bell in a goblet or bottoms-up grip. It is preceded by a clean at the start of each set, and you can perform a clean between each rep if you choose. It can be trained in traditional strength or muscle-building rep ranges, in circuit or fat-loss training, or as part of a larger kettlebell combination or complex.", 294 | "type": "Strength", 295 | "bodyPart": "Glutes", 296 | "equipment": "Kettlebells", 297 | "level": "Intermediate", 298 | "rating": 8.6, 299 | "ratingDesc": "Average" 300 | }, 301 | 302 | { 303 | "exerciseNumber": 27, 304 | "title": "Single-arm triceps kick-back", 305 | "description": "The single-arm triceps kick-back is a popular movement to increase size and strength of the triceps. It is usually performed for moderate to high reps, at least 8-12 reps, as part of an upper-body or arm-focused workout.", 306 | "type": "Strength", 307 | "bodyPart": "Glutes", 308 | "equipment": "Dumbbell", 309 | "level": "Intermediate", 310 | "rating": 7.0, 311 | "ratingDesc": "" 312 | }, 313 | 314 | { 315 | "exerciseNumber": 28, 316 | "title": "World's greatest stretch", 317 | "description": "The world's greatest stretch is a popular movement that earned its name because it targets multiple parts of the body, including the hips, shoulders, and thoracic spine, in a single sequence. This makes it a great dynamic warm-up, post-workout stretch, or mobility work you can do anytime.", 318 | "type": "Stretching", 319 | "bodyPart": "Hamstrings", 320 | "equipment": "Body Only", 321 | "level": "Intermediate", 322 | "rating": 4.4, 323 | "ratingDesc": "Average" 324 | }, 325 | 326 | { 327 | "exerciseNumber": 29, 328 | "title": "Single-leg balance and reach", 329 | "description": "The single-leg balance and reach is an exercise that focuses on lower-body strength and stability.", 330 | "type": "Stretching", 331 | "bodyPart": "Hamstrings", 332 | "equipment": "Body Only", 333 | "level": "Beginner", 334 | "rating": 0, 335 | "ratingDesc": "" 336 | }, 337 | 338 | { 339 | "exerciseNumber": 30, 340 | "title": "Band-assisted pull-up", 341 | "description": "The band-assisted pull-up is a variation of the pull-up exercise in which the reps are performed with an elastic band looped around the feet or knees. This reduces weight and resistance at the bottom of the rep, while increasing it toward the top. Like other pull-up variations, it builds strength and muscle in the upper back, biceps, and core, and is often used as a way to help people boost their pull-up numbers or perform their first rep.", 342 | "type": "Strength", 343 | "bodyPart": "Lats", 344 | "equipment": "Bands", 345 | "level": "Intermediate", 346 | "rating": 7.6, 347 | "ratingDesc": "Average" 348 | }, 349 | { 350 | "exerciseNumber": 31, 351 | "title": "Assisted Chin-Up", 352 | "description": "The reverse-grip chin-up is a variation of the pull-up exercise in which the reps are performed with the palms facing toward the body, in an underhand position, with a band looped around the feet or knees. This reduces weight and resistance at the bottom of the rep, while increasing it toward the top. Like other pull-up variations, it builds strength and muscle in the upper back, biceps, and core, but it utilizes the biceps slightly more than overhand band-assisted pull-ups.", 353 | "type": "Strength", 354 | "bodyPart": "Lats", 355 | "equipment": "Bands", 356 | "level": "Beginner", 357 | "rating": 0, 358 | "ratingDesc": "Average" 359 | }, 360 | 361 | { 362 | "exerciseNumber": 32, 363 | "title": "Band good morning-", 364 | "description": "The band good morning is an exercise targeting the hamstrings and glutes. It is often performed in powerlifting-style training for high reps, or in the place of barbell good mornings.", 365 | "type": "Strength", 366 | "bodyPart": "Lower Back", 367 | "equipment": "Bands", 368 | "level": "Intermediate", 369 | "rating": 7.0, 370 | "ratingDesc": "" 371 | }, 372 | 373 | { 374 | "exerciseNumber": 33, 375 | "title": "Band deadlift", 376 | "description": "The band deadlift is a lower-body exercise that mainly targets the hamstrings while also benefiting the glutes and lats using a proper hip-hinging motion.", 377 | "type": "Strength", 378 | "bodyPart": "Lower Back", 379 | "equipment": "Bands", 380 | "level": "Intermediate", 381 | "rating": 7.0, 382 | "ratingDesc": "" 383 | }, 384 | { 385 | "exerciseNumber": 34, 386 | "title": "Lower back SMR", 387 | "description": "Lower back self-myofascial release (SMR) is a self-administered soft-tissue treatment, usually using a foam roller, lacrosse ball, or other \"trigger point\" massage tool. By applying pressure strategically to the quadratus lumborum or spinal erector (erector spinae) muscles of the lower back, some people find they can cause this often painful and tight area to relax or \"release.\" Lower back SMR is often accompanied by SMR on the glutes or piriformis muscles, or by stretches that target the piriformis or hips. However, some people find that SMR on the lower back can aggravate existing pain or injury, so be cautious when performing it.", 388 | "type": "Stretching", 389 | "bodyPart": "Lower Back", 390 | "equipment": "Foam Roll", 391 | "level": "Intermediate", 392 | "rating": 4.5, 393 | "ratingDesc": "Average" 394 | }, 395 | 396 | 397 | { 398 | "exerciseNumber": 35, 399 | "title": "Partner side plank band row", 400 | "description": "The partner side plank band row is an abdominal exercise where two partners perform side planks while pulling on the opposite ends of an exercise band. This technique can be done for time or reps in any ab-focused workout.", 401 | "type": "Strength", 402 | "bodyPart": "Middle Back", 403 | "equipment": "Bands", 404 | "level": "Intermediate", 405 | "rating": 0, 406 | "ratingDesc": "" 407 | }, 408 | { 409 | "exerciseNumber": 36, 410 | "title": "Band-assisted chin-up", 411 | "description": "The band-assisted chin-up is a variation of the pull-up exercise in which the reps are performed with the palms facing toward the body in an underhand position, with a band looped around the feet or knees. This reduces weight and resistance at the bottom of the rep, while increasing it toward the top. Like other pull-up variations, it builds strength and muscle in the upper back, biceps, and core, but it utilizes the biceps slightly more than overhand band-assisted pull-ups.", 412 | "type": "Strength", 413 | "bodyPart": "Middle Back", 414 | "equipment": "Bands", 415 | "level": "Intermediate", 416 | "rating": 7.0, 417 | "ratingDesc": "" 418 | }, 419 | { 420 | "exerciseNumber": 37, 421 | "title": "Band seated row", 422 | "description": "The band seated row is a band alternative to the popular cable exercise and trains the muscles of the upper back, including the lats (latissimus dorsi), traps, rhomboids, and rear deltoids. It also targets the biceps to a lesser degree. The band row can work well in a variety of rep ranges, requiring a heavier band for muscle-building and strength workouts and a lighter band for weight-loss work and as an accessory movement.", 423 | "type": "Strength", 424 | "bodyPart": "Traps", 425 | "equipment": "Bands", 426 | "level": "Intermediate", 427 | "rating": 7.0, 428 | "ratingDesc": "" 429 | }, 430 | { 431 | "exerciseNumber": 38, 432 | "title": "Barbell shrug", 433 | "description": "The barbell shrug is an exercise targeting the traps (trapezius muscles). It is popular in strength and muscle-focused upper-body training, and is often trained on a shoulder day. With the assistance of straps, it can be loaded heavily, but it is still usually performed for moderate to high reps, such as 8-10 reps per set.", 434 | "type": "Strength", 435 | "bodyPart": "Traps", 436 | "equipment": "Barbell", 437 | "level": "Intermediate", 438 | "rating": 8.4, 439 | "ratingDesc": "Average" 440 | }, 441 | { 442 | "exerciseNumber": 39, 443 | "title": "Barbell behind-the-back shrug", 444 | "description": "The barbell behind-the-back shrug is an exercise targeting the traps. Having the bar behind you helps keep your shoulders pulled up and back, rather than rounded forward. It is popular in strength and muscle-focused upper-body training, and is often trained on a shoulder day. With the assistance of straps, it can be loaded heavily, but it is still usually performed for moderate to high reps, such as 8-10 reps per set.", 445 | "type": "Strength", 446 | "bodyPart": "Traps", 447 | "equipment": "Barbell", 448 | "level": "Intermediate", 449 | "rating": 8.3, 450 | "ratingDesc": "Average" 451 | }, 452 | 453 | { 454 | "exerciseNumber": 40, 455 | "title": "Single-arm inverted row", 456 | "description": "The single-arm inverted row is a bodyweight exercise targeting the muscles of the lats (latissimus dorsi) and upper back, using the bar of the Smith machine to hang and pull from. You can change the bar height to customize the difficulty level and desired angle of the row.", 457 | "type": "Strength", 458 | "bodyPart": "Traps", 459 | "equipment": "Barbell", 460 | "level": "Intermediate", 461 | "rating": 7.0, 462 | "ratingDesc": "" 463 | }, 464 | 465 | 466 | { 467 | "exerciseNumber": 41, 468 | "title": "Single-leg depth squat", 469 | "description": "The single-leg depth squat is an exercise targeting the muscles of the lower body, including the quads, glutes, and hamstrings. It is sometimes used as a substitute for the pistol squat, since it doesn't demand the same level of flexibility to perform. It is usually performed for low to moderate reps with a focus on proper form and control.", 470 | "type": "Strength", 471 | "bodyPart": "Quadriceps", 472 | "equipment": "Bands", 473 | "level": "Intermediate", 474 | "rating": 9, 475 | "ratingDesc": "Average" 476 | }, 477 | 478 | { 479 | "exerciseNumber": 42, 480 | "title": "Banded jump squat", 481 | "description": "The banded jump squat is an explosive bodyweight squat variation performed with a band around the thighs just above the knees. This increases muscle activation in the hips and glutes, while the squat targets the quads, hamstrings, and glutes. The banded jump squat can be performed for low reps as a power-focused exercise, or for higher reps to build muscle in the lower body, challenge conditioning, and burn fat.", 482 | "type": "Strength", 483 | "bodyPart": "Quadriceps", 484 | "equipment": "Bands", 485 | "level": "Intermediate", 486 | "rating": 7.0, 487 | "ratingDesc": "" 488 | }, 489 | { 490 | "exerciseNumber": 43, 491 | "title": "Speed Box Squat", 492 | "description": "The banded barbell box squat is a strength and power exercise targeting the lower body. It is popular in powerlifting training, either performed with light weights as a power and speed exercise, or heavier for strength. It is usually performed to a box of a height that demands the squatter to decent to around parallel.", 493 | "type": "Powerlifting", 494 | "bodyPart": "Quadriceps", 495 | "equipment": "Bands", 496 | "level": "Intermediate", 497 | "rating": 7.0, 498 | "ratingDesc": "" 499 | }, 500 | { 501 | "exerciseNumber": 44, 502 | "title": "Band Pull Apart", 503 | "description": "The band pull-apart is an exercise targeting the upper back muscles, including the trapezius, rear delts, and rhomboids. The band creates dynamic tension, allowing you to really focus on contracting these muscles, making it a great activation drill prior to upper-body lifting.", 504 | "type": "Strength", 505 | "bodyPart": "Shoulders", 506 | "equipment": "Bands", 507 | "level": "Intermediate", 508 | "rating": 8.2, 509 | "ratingDesc": "Average" 510 | }, 511 | { 512 | "exerciseNumber": 45, 513 | "title": "Band shoulder press", 514 | "description": "The band shoulder press is a deltoid exercise and an alternative to the classic dumbbell shoulder press. The dynamic tension of the band forces the core to stabilize the body and more closely matches the strength curve of the shoulder press. It can be performed in low reps, such as 5-8 reps per set, to build shoulder strength, or for higher reps to build muscle and for conditioning. It can work as the main focus of a shoulder day but is also popular as an accessory movement to the bench press or barbell military press.", 515 | "type": "Strength", 516 | "bodyPart": "Shoulders", 517 | "equipment": "Bands", 518 | "level": "Intermediate", 519 | "rating": 7.0, 520 | "ratingDesc": "" 521 | }, 522 | { 523 | "exerciseNumber": 46, 524 | "title": "Band lateral raise", 525 | "description": "The band lateral raise is a shoulder exercise that targets the medial or middle head of the deltoid muscle. It's a variation of the dumbbell lateral raise, a staple strength-training move. The band lateral raise is a great option for warm-up or accessory work on upper-body training days. The dynamic tension of the band provides constant tension and matches the strength curve of the movement. It is usually performed for moderate to high reps, at least 8-12 reps, as part of the upper-body or shoulder-focused portion of a workout.", 526 | "type": "Strength", 527 | "bodyPart": "Shoulders", 528 | "equipment": "Bands", 529 | "level": "Intermediate", 530 | "rating": 7.0, 531 | "ratingDesc": "" 532 | }, 533 | { 534 | "exerciseNumber": 47, 535 | "title": "Band upright row", 536 | "description": "The band upright row is a popular movement for building stronger and bigger traps and shoulders and similar to the dumbbell upright row. Many lifters combine this move with either their back or shoulder workout, since it involves both body parts. Using the band provides dynamic resistance and is easier on the shoulder than using a barbell. It is generally performed for moderate to high reps, such as 8-12 reps per set or more.", 537 | "type": "Strength", 538 | "bodyPart": "Shoulders", 539 | "equipment": "Bands", 540 | "level": "Intermediate", 541 | "rating": 7.0, 542 | "ratingDesc": "" 543 | }, 544 | 545 | { 546 | "exerciseNumber": 48, 547 | "title": "Cable V-bar push-down", 548 | "description": "The cable V-bar push-down is a popular gym exercise for targeting the triceps. It utilizes an angled bar, which can allow you to move heavier weights more comfortably than a straight bar or rope. It is usually performed for moderate to high reps, such as 8-12 reps or more per set, as part of an upper-body or arm-focused workout.", 549 | "type": "Strength", 550 | "bodyPart": "Triceps", 551 | "equipment": "Cable", 552 | "level": "Intermediate", 553 | "rating": 9.1, 554 | "ratingDesc": "Average" 555 | }, 556 | { 557 | "exerciseNumber": 49, 558 | "title": "Reverse Grip Triceps Pushdown", 559 | "description": "The reverse-grip cable straight-bar push-down is a twist on the popular cable straight-bar push-down. The difference has to do with how the hands are positioned holding the bar: the palms facing up rather than down. Because grip will be a limiting factor, this movement is usually performed for moderate to high reps, such as 8-12 reps per set or higher.", 560 | "type": "Strength", 561 | "bodyPart": "Triceps", 562 | "equipment": "Cable", 563 | "level": "Intermediate", 564 | "rating": 9, 565 | "ratingDesc": "Average" 566 | }, 567 | { 568 | "exerciseNumber": 50, 569 | "title": "Kneeling cable triceps extension", 570 | "description": "The kneeling cable triceps extension is a single-joint exercise meant to isolate the triceps muscles using a high pully on a cable stack and a bench. It can also be performed in a tall-kneeling stance from a low pully. It is most commonly used in muscle-building triceps or arm workouts.", 571 | "type": "Strength", 572 | "bodyPart": "Triceps", 573 | "equipment": "Cable", 574 | "level": "Intermediate", 575 | "rating": 9, 576 | "ratingDesc": "Average" 577 | } 578 | ] -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ktor_version=2.3.7 2 | kotlin_version=1.9.22 3 | logback_version=1.4.14 4 | kotlin.code.style=official 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /images/01-architecture-atlas_application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/01-architecture-atlas_application.png -------------------------------------------------------------------------------- /images/02-architecture-kotlin_application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/02-architecture-kotlin_application.png -------------------------------------------------------------------------------- /images/03-creating_exercises_collection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/03-creating_exercises_collection.png -------------------------------------------------------------------------------- /images/04-creating_trigger_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/04-creating_trigger_details.png -------------------------------------------------------------------------------- /images/05-creating_trigger_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/05-creating_trigger_details.png -------------------------------------------------------------------------------- /images/06-creating_function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/06-creating_function.png -------------------------------------------------------------------------------- /images/07-app_services.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/07-app_services.png -------------------------------------------------------------------------------- /images/08-rename_function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/08-rename_function.png -------------------------------------------------------------------------------- /images/09-rename_function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/09-rename_function.png -------------------------------------------------------------------------------- /images/10-huggingFace_token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/10-huggingFace_token.png -------------------------------------------------------------------------------- /images/11-creating_application_new_value.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/11-creating_application_new_value.png -------------------------------------------------------------------------------- /images/12-creating_application_new_value_secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/12-creating_application_new_value_secret.png -------------------------------------------------------------------------------- /images/13-creating_application_new_value_Value.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/13-creating_application_new_value_Value.png -------------------------------------------------------------------------------- /images/14-values_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/14-values_list.png -------------------------------------------------------------------------------- /images/15-exercises_document_sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/15-exercises_document_sample.png -------------------------------------------------------------------------------- /images/16-mongo_import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/16-mongo_import.png -------------------------------------------------------------------------------- /images/17-mongo_import_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/17-mongo_import_success.png -------------------------------------------------------------------------------- /images/18-app_service_logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/18-app_service_logs.png -------------------------------------------------------------------------------- /images/19-collection_exercises_with_embedded_imported.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/19-collection_exercises_with_embedded_imported.png -------------------------------------------------------------------------------- /images/20-postman_final_demonstration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/20-postman_final_demonstration.png -------------------------------------------------------------------------------- /images/21-creating_atlas_vector_index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/21-creating_atlas_vector_index.png -------------------------------------------------------------------------------- /images/22-creating_atlas_vector_index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/22-creating_atlas_vector_index.png -------------------------------------------------------------------------------- /images/23-creating_atlas_vector_index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/23-creating_atlas_vector_index.png -------------------------------------------------------------------------------- /images/24-creating_atlas_vector_index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/24-creating_atlas_vector_index.png -------------------------------------------------------------------------------- /images/25-running_application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/25-running_application.png -------------------------------------------------------------------------------- /images/26-postman_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardohsmello/kotlin-ktor-mongodb-vector-search/99367f7248224b90e98a010980dd4909fc09c900/images/26-postman_request.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "com.mongodb.fitnesstracker" 2 | -------------------------------------------------------------------------------- /src/main/kotlin/com/mongodb/Application.kt: -------------------------------------------------------------------------------- 1 | package com.mongodb 2 | 3 | import com.mongodb.application.routes.exercisesRoutes 4 | import com.mongodb.application.routes.fitnessRoutes 5 | import com.mongodb.domain.ports.ExercisesRepository 6 | import com.mongodb.domain.ports.FitnessRepository 7 | import com.mongodb.infrastructure.ExercisesRepositoryImpl 8 | import com.mongodb.infrastructure.FitnessRepositoryImpl 9 | import com.mongodb.kotlin.client.coroutine.MongoClient 10 | import io.ktor.serialization.gson.* 11 | import io.ktor.server.application.* 12 | import io.ktor.server.plugins.contentnegotiation.* 13 | import io.ktor.server.plugins.swagger.* 14 | import io.ktor.server.routing.* 15 | import io.ktor.server.tomcat.* 16 | import org.koin.dsl.module 17 | import org.koin.ktor.plugin.Koin 18 | import org.koin.logger.slf4jLogger 19 | 20 | fun main(args: Array): Unit = EngineMain.main(args) 21 | fun Application.module() { 22 | 23 | install(ContentNegotiation) { 24 | gson { 25 | } 26 | } 27 | 28 | install(Koin) { 29 | slf4jLogger() 30 | modules(module { 31 | single { MongoClient.create(environment.config.propertyOrNull("ktor.mongo.uri")?.getString() ?: throw RuntimeException("Failed to access MongoDB URI.")) } 32 | single { get().getDatabase(environment.config.property("ktor.mongo.database").getString()) } 33 | }, module { 34 | single { FitnessRepositoryImpl(get()) } 35 | single { ExercisesRepositoryImpl(get()) } 36 | }) 37 | } 38 | 39 | routing { 40 | swaggerUI(path = "swagger-ui", swaggerFile = "openapi/documentation.yaml") { 41 | version = "4.15.5" 42 | } 43 | fitnessRoutes() 44 | exercisesRoutes() 45 | } 46 | } 47 | 48 | fun ApplicationCall.huggingFaceApiUrl(): String { 49 | return application.environment.config.propertyOrNull("ktor.huggingface.api.url")?.getString() 50 | ?: throw RuntimeException("Failed to access Hugging Face API base URL.") 51 | 52 | } 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/main/kotlin/com/mongodb/application/request/FitnessRequest.kt: -------------------------------------------------------------------------------- 1 | package com.mongodb.application.request 2 | 3 | import com.mongodb.domain.entity.Fitness 4 | import com.mongodb.domain.entity.FitnessDetails 5 | import org.bson.types.ObjectId 6 | 7 | data class FitnessRequest( 8 | val exerciseType: String, 9 | val notes: String, 10 | val details: FitnessDetails 11 | ) 12 | fun FitnessRequest.toDomain(): Fitness { 13 | return Fitness( 14 | id = ObjectId(), 15 | exerciseType = exerciseType, 16 | notes = notes, 17 | details = details 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/com/mongodb/application/request/SentenceRequest.kt: -------------------------------------------------------------------------------- 1 | package com.mongodb.application.request 2 | 3 | data class SentenceRequest( 4 | val input: String 5 | ) 6 | { 7 | fun convertResponse(body: String) = 8 | body 9 | .replace("[", "") 10 | .replace("]", "") 11 | .split(",") 12 | .map { it.trim().toDouble() } 13 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/mongodb/application/response/ExercisesResponse.kt: -------------------------------------------------------------------------------- 1 | package com.mongodb.application.response 2 | data class ExercisesResponse( 3 | val exerciseNumber: Int, 4 | val bodyPart: String, 5 | val type: String, 6 | val description: String, 7 | val title: String 8 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/mongodb/application/response/FitnessResponse.kt: -------------------------------------------------------------------------------- 1 | package com.mongodb.application.response 2 | 3 | import com.mongodb.domain.entity.FitnessDetails 4 | 5 | data class FitnessResponse( 6 | val id: String, 7 | val exerciseType: String, 8 | val notes: String, 9 | val details: FitnessDetails 10 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/mongodb/application/routes/ExercisesRoutes.kt: -------------------------------------------------------------------------------- 1 | package com.mongodb.application.routes 2 | 3 | import com.mongodb.application.request.SentenceRequest 4 | import com.mongodb.domain.ports.ExercisesRepository 5 | import com.mongodb.huggingFaceApiUrl 6 | import io.ktor.client.* 7 | import io.ktor.client.call.* 8 | import io.ktor.client.engine.cio.* 9 | import io.ktor.client.request.* 10 | import io.ktor.client.statement.* 11 | import io.ktor.http.* 12 | import io.ktor.http.content.* 13 | import io.ktor.server.application.* 14 | import io.ktor.server.request.* 15 | import io.ktor.server.response.* 16 | import io.ktor.server.routing.* 17 | import org.koin.ktor.ext.inject 18 | 19 | fun Route.exercisesRoutes() { 20 | val repository by inject() 21 | 22 | route("/exercises/processRequest") { 23 | post { 24 | val sentence = call.receive() 25 | 26 | val response = requestSentenceTransform(sentence.input, call.huggingFaceApiUrl()) 27 | 28 | if (response.status.isSuccess()) { 29 | val embedding = sentence.convertResponse(response.body()) 30 | val similarDocuments = repository.findSimilarExercises(embedding) 31 | 32 | call.respond(HttpStatusCode.Accepted, similarDocuments.map { it.toResponse() }) 33 | } 34 | } 35 | } 36 | } 37 | suspend fun requestSentenceTransform(input: String, huggingFaceURL: String): HttpResponse { 38 | return HttpClient(CIO).use { client -> 39 | 40 | val response = client.post(huggingFaceURL) { 41 | val content = TextContent(input, ContentType.Text.Plain) 42 | setBody(content) 43 | } 44 | 45 | response 46 | } 47 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/mongodb/application/routes/FitnessRoute.kt: -------------------------------------------------------------------------------- 1 | package com.mongodb.application.routes 2 | 3 | import com.mongodb.domain.ports.FitnessRepository 4 | import io.ktor.http.* 5 | import io.ktor.server.application.* 6 | import io.ktor.server.request.* 7 | import io.ktor.server.response.* 8 | import io.ktor.server.routing.* 9 | import org.bson.types.ObjectId 10 | import org.koin.ktor.ext.inject 11 | 12 | fun Route.fitnessRoutes() { 13 | 14 | val repository by inject() 15 | 16 | route("/fitness") { 17 | get { 18 | repository.findAll()?.let { list -> 19 | call.respond(list.map { it.toResponse() }) 20 | } ?: call.respondText("No records found") 21 | } 22 | 23 | get("/{id?}") { 24 | val id = call.parameters["id"] 25 | if (id.isNullOrEmpty()) { 26 | return@get call.respondText( 27 | text = "Missing id", 28 | status = HttpStatusCode.BadRequest 29 | ) 30 | } 31 | 32 | repository.findById(ObjectId(id))?.let { 33 | call.respond(it.toResponse()) 34 | } ?: call.respondText("No records found for id $id") 35 | } 36 | 37 | get("/exerciseType/{type?}") { 38 | val type = call.parameters["type"] 39 | type?.takeIf { it.isNotEmpty() }?.let { 40 | call.respond(repository.findByExerciseType(it).ifEmpty { "No records found for exerciseType $it" }) 41 | } ?: call.respond(HttpStatusCode.BadRequest, "Missing exercise type param") 42 | } 43 | 44 | post { 45 | val fitness = call.receive() 46 | println(fitness) 47 | } 48 | // post { 49 | // val fitness = call.receive() 50 | // val insertedId = repository.insertOne(fitness.toDomain()) 51 | // call.respond(HttpStatusCode.Created, "Created fitness with id $insertedId") 52 | // 53 | // } 54 | 55 | delete("/{id?}") { 56 | val id = call.parameters["id"] ?: return@delete call.respondText( 57 | text = "Missing fitness id", 58 | status = HttpStatusCode.BadRequest 59 | ) 60 | 61 | val delete: Long = repository.deleteById(ObjectId(id)) 62 | 63 | if (delete == 1L) { 64 | return@delete call.respondText("Fitness Deleted successfully", status = HttpStatusCode.OK) 65 | } 66 | return@delete call.respondText("Fitness not found", status = HttpStatusCode.NotFound) 67 | 68 | } 69 | 70 | patch("/{id?}") { 71 | val id = call.parameters["id"] ?: return@patch call.respondText( 72 | text = "Missing fitness id", 73 | status = HttpStatusCode.BadRequest 74 | ) 75 | 76 | val updated = repository.updateOne(ObjectId(id), call.receive()) 77 | 78 | call.respondText( 79 | text = if (updated == 1L) "Fitness updated successfully" else "Fitness not found", 80 | status = if (updated == 1L) HttpStatusCode.OK else HttpStatusCode.NotFound 81 | ) 82 | } 83 | } 84 | } 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/main/kotlin/com/mongodb/domain/entity/Exercises.kt: -------------------------------------------------------------------------------- 1 | package com.mongodb.domain.entity 2 | 3 | import com.mongodb.application.response.ExercisesResponse 4 | import org.bson.codecs.pojo.annotations.BsonId 5 | import org.bson.types.ObjectId 6 | 7 | data class Exercises( 8 | @BsonId 9 | val id: ObjectId, 10 | val exerciseNumber: Int, 11 | val title: String, 12 | val description: String, 13 | val type: String, 14 | val bodyPart: String, 15 | val equipment: String, 16 | val level: String, 17 | val rating: Double, 18 | val ratingDesc: String, 19 | val descEmbedding: List 20 | ){ 21 | fun toResponse() = ExercisesResponse( 22 | exerciseNumber = exerciseNumber, 23 | title = title, 24 | description = description, 25 | bodyPart = bodyPart, 26 | type = type 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/com/mongodb/domain/entity/Fitness.kt: -------------------------------------------------------------------------------- 1 | package com.mongodb.domain.entity 2 | 3 | import com.mongodb.application.response.FitnessResponse 4 | import org.bson.codecs.pojo.annotations.BsonId 5 | import org.bson.types.ObjectId 6 | 7 | data class Fitness( 8 | @BsonId 9 | val id: ObjectId, 10 | val exerciseType: String, 11 | val notes: String, 12 | val details: FitnessDetails 13 | ){ 14 | fun toResponse() = FitnessResponse( 15 | id = id.toString(), 16 | exerciseType = exerciseType, 17 | notes = notes, 18 | details = details 19 | ) 20 | } 21 | 22 | data class FitnessDetails( 23 | val durationMinutes: Int, 24 | val distance: Double, 25 | val caloriesBurned: Int 26 | ) 27 | 28 | -------------------------------------------------------------------------------- /src/main/kotlin/com/mongodb/domain/ports/ExerciseRepository.kt: -------------------------------------------------------------------------------- 1 | package com.mongodb.domain.ports 2 | 3 | import com.mongodb.domain.entity.Exercises 4 | interface ExercisesRepository { 5 | suspend fun findSimilarExercises(embedding: List): List 6 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/mongodb/domain/ports/FitnessRepository.kt: -------------------------------------------------------------------------------- 1 | package com.mongodb.domain.ports 2 | 3 | import com.mongodb.domain.entity.Fitness 4 | import org.bson.BsonValue 5 | import org.bson.types.ObjectId 6 | 7 | interface FitnessRepository { 8 | 9 | suspend fun findAll(): List? 10 | 11 | suspend fun findById(objectId: ObjectId): Fitness? 12 | 13 | suspend fun findByExerciseType(type: String): List 14 | 15 | suspend fun insertOne(fitness: Fitness): BsonValue? 16 | 17 | suspend fun deleteById(objectId: ObjectId): Long 18 | 19 | suspend fun updateOne(objectId: ObjectId, fitness: Fitness): Long 20 | 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/mongodb/infrastructure/ExerciseRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.mongodb.infrastructure 2 | 3 | import com.mongodb.domain.entity.Exercises 4 | import com.mongodb.domain.ports.ExercisesRepository 5 | import com.mongodb.kotlin.client.coroutine.MongoDatabase 6 | import kotlinx.coroutines.flow.toList 7 | import org.bson.Document 8 | 9 | class ExercisesRepositoryImpl( 10 | private val mongoDatabase: MongoDatabase 11 | ) : ExercisesRepository { 12 | 13 | companion object { 14 | const val EXERCISES_COLLECTION = "exercises" 15 | } 16 | 17 | override suspend fun findSimilarExercises(embedding: List): List { 18 | val result = 19 | mongoDatabase.getCollection(EXERCISES_COLLECTION).withDocumentClass().aggregate( 20 | listOf( 21 | Document( 22 | "\$vectorSearch", 23 | Document("queryVector", embedding) 24 | .append("path", "descEmbedding") 25 | .append("numCandidates", 3L) 26 | .append("index", "vector_index") 27 | .append("limit", 3L) 28 | ) 29 | ) 30 | ) 31 | 32 | return result.toList() 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/main/kotlin/com/mongodb/infrastructure/FitnessRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.mongodb.infrastructure 2 | 3 | import com.mongodb.MongoException 4 | import com.mongodb.client.model.Filters 5 | import com.mongodb.client.model.UpdateOptions 6 | import com.mongodb.client.model.Updates 7 | import com.mongodb.domain.entity.Fitness 8 | import com.mongodb.domain.ports.FitnessRepository 9 | import com.mongodb.kotlin.client.coroutine.MongoDatabase 10 | import kotlinx.coroutines.flow.firstOrNull 11 | import kotlinx.coroutines.flow.toList 12 | import org.bson.BsonValue 13 | import org.bson.types.ObjectId 14 | 15 | class FitnessRepositoryImpl( 16 | private val mongoDatabase: MongoDatabase 17 | ) : FitnessRepository { 18 | 19 | companion object { 20 | const val FITNESS_COLLECTION = "fitness" 21 | } 22 | 23 | override suspend fun findAll(): List { 24 | return mongoDatabase.getCollection(FITNESS_COLLECTION).find().toList() 25 | } 26 | 27 | override suspend fun findById(objectId: ObjectId): Fitness? = 28 | mongoDatabase.getCollection(FITNESS_COLLECTION).withDocumentClass() 29 | .find(Filters.eq("_id", objectId)) 30 | .firstOrNull() 31 | 32 | override suspend fun findByExerciseType(type: String): List = 33 | mongoDatabase.getCollection(FITNESS_COLLECTION).withDocumentClass() 34 | .find(Filters.eq(Fitness::exerciseType.name, type)) 35 | .toList() 36 | 37 | override suspend fun insertOne(fitness: Fitness): BsonValue? { 38 | 39 | try { 40 | val result = mongoDatabase.getCollection(FITNESS_COLLECTION).insertOne( 41 | fitness 42 | ) 43 | 44 | return result.insertedId 45 | } catch (e: MongoException) { 46 | System.err.println("Unable to insert due to an error: $e") 47 | } 48 | 49 | return null 50 | } 51 | 52 | override suspend fun deleteById(objectId: ObjectId): Long { 53 | 54 | try { 55 | val result = mongoDatabase.getCollection(FITNESS_COLLECTION).deleteOne(Filters.eq("_id", objectId)) 56 | return result.deletedCount 57 | } catch (e: MongoException) { 58 | System.err.println("Unable to delete due to an error: $e") 59 | } 60 | 61 | return 0 62 | } 63 | 64 | override suspend fun updateOne(objectId: ObjectId, fitness: Fitness): Long { 65 | try { 66 | val query = Filters.eq("_id", objectId) 67 | val updates = Updates.combine( 68 | Updates.set(Fitness::exerciseType.name, fitness.exerciseType), 69 | Updates.set(Fitness::notes.name, fitness.notes), 70 | Updates.set(Fitness::details.name, fitness.details) 71 | ) 72 | 73 | val options = UpdateOptions().upsert(true) 74 | 75 | val result = 76 | mongoDatabase.getCollection(FITNESS_COLLECTION) 77 | .updateOne(query, updates, options) 78 | 79 | return result.modifiedCount 80 | } catch (e: MongoException) { 81 | System.err.println("Unable to update due to an error: $e") 82 | } 83 | 84 | return 0 85 | } 86 | } -------------------------------------------------------------------------------- /src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | ktor { 2 | deployment { 3 | port = 8081 4 | } 5 | application { 6 | modules = [ com.mongodb.ApplicationKt.module ] 7 | } 8 | mongo { 9 | uri = ${?MONGO_URI} 10 | database = ${?MONGO_DATABASE} 11 | } 12 | huggingface { 13 | api { 14 | url = "https://api-inference.huggingface.co/pipeline/feature-extraction/sentence-transformers/all-MiniLM-L6-v2" 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/openapi/documentation.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: Fitness API 4 | version: 1.0.0 5 | description: | 6 | This Swagger documentation file outlines the API specifications for a Fitness Tracker application built with Ktor and MongoDB. The API allows users to manage fitness records, including retrieving all records, creating new records, updating and deleting records by ID, and retrieving records by exercise type. The API uses the Fitness and FitnessDetails data classes to structure the fitness-related information. 7 | 8 | - [The Fitness Tracker GitHub repository](https://github.com/ricardohsmello/fitness-tracker) 9 | paths: 10 | /fitness: 11 | get: 12 | summary: Retrieve all fitness records 13 | responses: 14 | '200': 15 | description: Successful response 16 | content: 17 | application/json: 18 | example: [] 19 | post: 20 | summary: Create a new fitness record 21 | requestBody: 22 | required: true 23 | content: 24 | application/json: 25 | schema: 26 | $ref: '#/components/schemas/FitnessRequest' 27 | responses: 28 | '201': 29 | description: Fitness created successfully 30 | '400': 31 | description: Bad request 32 | /fitness/{id}: 33 | get: 34 | summary: Retrieve fitness record by ID 35 | parameters: 36 | - name: id 37 | in: path 38 | required: true 39 | schema: 40 | type: string 41 | responses: 42 | '200': 43 | description: Successful response 44 | content: 45 | application/json: 46 | example: {} 47 | '404': 48 | description: Fitness not found 49 | delete: 50 | summary: Delete fitness record by ID 51 | parameters: 52 | - name: id 53 | in: path 54 | required: true 55 | schema: 56 | type: string 57 | responses: 58 | '200': 59 | description: Fitness deleted successfully 60 | '400': 61 | description: Bad request 62 | '404': 63 | description: Fitness not found 64 | patch: 65 | summary: Update fitness record by ID 66 | parameters: 67 | - name: id 68 | in: path 69 | required: true 70 | schema: 71 | type: string 72 | requestBody: 73 | required: true 74 | content: 75 | application/json: 76 | schema: 77 | $ref: '#/components/schemas/FitnessRequest' 78 | responses: 79 | '200': 80 | description: Fitness updated successfully 81 | '400': 82 | description: Bad request 83 | '404': 84 | description: Fitness not found 85 | /fitness/exerciseType/{type}: 86 | get: 87 | summary: Retrieve fitness records by exercise type 88 | parameters: 89 | - name: type 90 | in: path 91 | required: true 92 | schema: 93 | type: string 94 | responses: 95 | '200': 96 | description: Successful response 97 | content: 98 | application/json: 99 | example: [] 100 | '400': 101 | description: Bad request 102 | components: 103 | schemas: 104 | Fitness: 105 | type: object 106 | properties: 107 | id: 108 | type: string 109 | format: uuid 110 | exerciseType: 111 | type: string 112 | startTime: 113 | type: string 114 | format: date-time 115 | endTime: 116 | type: string 117 | format: date-time 118 | notes: 119 | type: string 120 | details: 121 | $ref: '#/components/schemas/FitnessDetails' 122 | required: 123 | - id 124 | - exerciseType 125 | - startTime 126 | - endTime 127 | - notes 128 | - details 129 | 130 | FitnessDetails: 131 | type: object 132 | properties: 133 | durationMinutes: 134 | type: integer 135 | format: int32 136 | distance: 137 | type: number 138 | format: double 139 | caloriesBurned: 140 | type: integer 141 | format: int32 142 | required: 143 | - durationMinutes 144 | - distance 145 | - caloriesBurned 146 | 147 | FitnessRequest: 148 | type: object 149 | properties: 150 | exerciseType: 151 | type: string 152 | startTime: 153 | type: string 154 | format: date-time 155 | endTime: 156 | type: string 157 | format: date-time 158 | notes: 159 | type: string 160 | details: 161 | $ref: '#/components/schemas/FitnessDetails' 162 | required: 163 | - exerciseType 164 | - startTime 165 | - endTime 166 | - notes 167 | - details 168 | -------------------------------------------------------------------------------- /src/test/kotlin/com/mongodb/ApplicationTest.kt: -------------------------------------------------------------------------------- 1 | package com.mongodb 2 | 3 | class ApplicationTest { 4 | // @Test 5 | // fun testRoot() = testApplication { 6 | // application { 7 | // configureRouting() 8 | // } 9 | // client.get("/").apply { 10 | // assertEquals(HttpStatusCode.OK, status) 11 | // assertEquals("Hello World!", bodyAsText()) 12 | // } 13 | // } 14 | } 15 | --------------------------------------------------------------------------------