├── .idea
├── .name
├── .gitignore
├── compiler.xml
├── misc.xml
├── runConfigurations.xml
├── gradle.xml
└── jarRepositories.xml
├── settings.gradle.kts
├── .gitignore
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── src
├── main
│ ├── kotlin
│ │ └── com
│ │ │ └── example
│ │ │ ├── Constants.kt
│ │ │ ├── data
│ │ │ ├── models
│ │ │ │ ├── UserCredentials.kt
│ │ │ │ └── Notes.kt
│ │ │ ├── entities
│ │ │ │ └── NoteEntity.kt
│ │ │ ├── db
│ │ │ │ └── DatabaseConnection.kt
│ │ │ ├── service
│ │ │ │ ├── NotesService.kt
│ │ │ │ └── NotesServiceImpl.kt
│ │ │ ├── auth
│ │ │ │ └── jwt
│ │ │ │ │ └── JWTConfig.kt
│ │ │ └── repository
│ │ │ │ └── NotesRepository.kt
│ │ │ ├── configure
│ │ │ ├── KoinDI.kt
│ │ │ ├── Serialization.kt
│ │ │ ├── Logging.kt
│ │ │ ├── Routing.kt
│ │ │ └── JWT.kt
│ │ │ ├── plugin
│ │ │ ├── ErrorLoggerPlugin.kt
│ │ │ └── RequestLoggerPlugin.kt
│ │ │ ├── Application.kt
│ │ │ ├── di
│ │ │ └── NotesModule.kt
│ │ │ └── routing
│ │ │ ├── AuthenticationRoutes.kt
│ │ │ └── NotesRoutes.kt
│ └── resources
│ │ ├── application.conf
│ │ └── logback.xml
└── test
│ └── kotlin
│ └── com
│ └── example
│ └── ApplicationTest.kt
├── gradle.properties
├── README.md
├── gradlew.bat
└── gradlew
/.idea/.name:
--------------------------------------------------------------------------------
1 | com.example.ktor-sample-database
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "com.example.ktor-sample-database"
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Project exclude paths
2 | /.gradle/
3 | /build/
4 | /src/main/resources/application.conf
5 | /.idea/
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiteshchopra11/ktor-api/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.example
2 |
3 | object Constants {
4 | const val ASCENDING = "asc"
5 | const val DESCENDING = "dsc"
6 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ktor_version=2.1.3
2 | kotlin_version=1.7.20
3 | logback_version=1.2.6
4 | kotlin.code.style=official
5 | koin_version=3.2.2
6 | ktorm_version = 3.5.0
7 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/data/models/UserCredentials.kt:
--------------------------------------------------------------------------------
1 | package com.example.data.models
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class UserCredentials(
7 | val username: String,
8 | val password: String
9 | )
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/configure/KoinDI.kt:
--------------------------------------------------------------------------------
1 | package com.example.configure
2 |
3 | import com.example.di.notesModule
4 | import io.ktor.server.application.*
5 | import org.koin.ktor.plugin.Koin
6 |
7 | fun Application.configureKoin() {
8 | install(Koin) {
9 | modules(notesModule)
10 | }
11 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/data/entities/NoteEntity.kt:
--------------------------------------------------------------------------------
1 | package com.example.data.entities
2 |
3 | import org.ktorm.schema.Table
4 | import org.ktorm.schema.int
5 | import org.ktorm.schema.varchar
6 |
7 | object NotesEntity : Table("note") {
8 | val id = int("id").primaryKey()
9 | val note = varchar("note")
10 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/configure/Serialization.kt:
--------------------------------------------------------------------------------
1 | package com.example.configure
2 |
3 | import io.ktor.server.application.*
4 | import io.ktor.serialization.kotlinx.json.*
5 | import io.ktor.server.plugins.contentnegotiation.*
6 |
7 | fun Application.configureSerialization() {
8 | install(ContentNegotiation) {
9 | json()
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/data/db/DatabaseConnection.kt:
--------------------------------------------------------------------------------
1 | package com.example.data.db
2 |
3 | import org.ktorm.database.Database
4 |
5 | object DatabaseConnection {
6 | val database = Database.connect(
7 | url = "jdbc:mysql://localhost:3306/notes",
8 | driver = "com.mysql.cj.jdbc.Driver",
9 | user = "root",
10 | password = "Changeme1"
11 | )
12 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/plugin/ErrorLoggerPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.example.plugin
2 |
3 | import io.ktor.server.application.*
4 | import io.ktor.server.application.hooks.*
5 |
6 | val ErrorLoggerPlugin = createApplicationPlugin(name = "ErrorLoggerPlugin") {
7 | on(CallFailed) { _, cause ->
8 | println("PLUGIN ERROR: API call failed because of ${cause.message}")
9 | }
10 | }
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/resources/application.conf:
--------------------------------------------------------------------------------
1 | ktor {
2 | deployment {
3 | port = 3536
4 | }
5 |
6 | application {
7 | modules = [ com.example.ApplicationKt.module ]
8 | }
9 |
10 | development = true
11 | }
12 |
13 | jwt {
14 | secret = "hitesh"
15 | issuer = "http://0.0.0.0:3536/"
16 | audience = "http://0.0.0.0:3536/test"
17 | realm = "Access to test"
18 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/data/models/Notes.kt:
--------------------------------------------------------------------------------
1 | package com.example.data.models
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class Note(
7 | val id: Int,
8 | val note: String
9 | )
10 |
11 | @Serializable
12 | data class NoteRequest(val note: String)
13 |
14 | @Serializable
15 | data class NoteResponse(
16 | val data: T,
17 | val success: Boolean
18 | )
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/Application.kt:
--------------------------------------------------------------------------------
1 | package com.example
2 |
3 | import com.example.configure.*
4 | import io.ktor.server.application.*
5 |
6 | fun main(args: Array): Unit = io.ktor.server.netty.EngineMain.main(args)
7 |
8 | fun Application.module() {
9 | configureKoin()
10 | configureLogging()
11 | configureSerialization()
12 | configureJWT()
13 | configureRouting()
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/plugin/RequestLoggerPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.example.plugin
2 |
3 | import io.ktor.server.application.*
4 | import io.ktor.server.plugins.*
5 |
6 | val RequestLoggerPlugin = createApplicationPlugin(name = "RequestLoggerPlugin") {
7 | onCall { call ->
8 | call.request.origin.apply {
9 | println("Request URL: $scheme://$host:$port$uri")
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/di/NotesModule.kt:
--------------------------------------------------------------------------------
1 | package com.example.di
2 |
3 | import com.example.data.repository.NotesRepository
4 | import com.example.data.service.NotesService
5 | import com.example.data.service.NotesServiceImpl
6 | import org.koin.dsl.module
7 |
8 | val notesModule = module {
9 | single { NotesServiceImpl(get()) } // get() Will resolve NotesRepository
10 | single { NotesRepository() }
11 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/configure/Logging.kt:
--------------------------------------------------------------------------------
1 | package com.example.configure
2 |
3 | import com.example.plugin.ErrorLoggerPlugin
4 | import com.example.plugin.RequestLoggerPlugin
5 | import io.ktor.server.application.*
6 | import io.ktor.server.plugins.callloging.*
7 | import org.slf4j.event.Level
8 |
9 | fun Application.configureLogging() {
10 | install(ErrorLoggerPlugin)
11 | install(RequestLoggerPlugin)
12 | install(CallLogging) {
13 | this.level = Level.INFO
14 | }
15 | }
--------------------------------------------------------------------------------
/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 |
13 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/data/service/NotesService.kt:
--------------------------------------------------------------------------------
1 | package com.example.data.service
2 |
3 | import com.example.data.models.Note
4 |
5 | interface NotesService {
6 | fun addNote(note: String): Int
7 | fun deleteNote(id: Int): Int
8 | fun updateNote(id: Int, note: String)
9 | fun fetchAllNotes(): List
10 | fun fetchNoteWithId(id: Int): Note?
11 | fun fetchAllPaginatedNotes(page: Int, size: Int): List
12 | fun fetchSortedNotes(isDescending: Boolean? = false): List
13 | }
--------------------------------------------------------------------------------
/src/test/kotlin/com/example/ApplicationTest.kt:
--------------------------------------------------------------------------------
1 | package com.example
2 |
3 | import io.ktor.http.*
4 | import kotlin.test.*
5 | import io.ktor.server.testing.*
6 | import com.example.configure.*
7 |
8 | class ApplicationTest {
9 | @Test
10 | fun testRoot() {
11 | withTestApplication({ configureRouting() }) {
12 | handleRequest(HttpMethod.Get, "/").apply {
13 | assertEquals(HttpStatusCode.OK, response.status())
14 | assertEquals("Hello World!", response.content)
15 | }
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/configure/Routing.kt:
--------------------------------------------------------------------------------
1 | package com.example.configure
2 |
3 | import com.example.routing.authenticateRoutes
4 | import com.example.routing.notesRoutes
5 | import io.ktor.server.application.*
6 | import io.ktor.server.response.*
7 | import io.ktor.server.routing.*
8 |
9 | fun Application.configureRouting() {
10 | val secret = environment.config.property("jwt.secret").getString()
11 | val issuer = environment.config.property("jwt.issuer").getString()
12 | val audience = environment.config.property("jwt.audience").getString()
13 | routing {
14 | get("/") {
15 | call.respondText("Hello World!")
16 | }
17 | }
18 | routing {
19 | notesRoutes()
20 | authenticateRoutes(secret, issuer, audience)
21 | }
22 | }
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/configure/JWT.kt:
--------------------------------------------------------------------------------
1 | package com.example.configure
2 |
3 | import com.example.data.auth.jwt.JwtConfig
4 | import io.ktor.server.application.*
5 | import io.ktor.server.auth.*
6 | import io.ktor.server.auth.jwt.*
7 |
8 |
9 | fun Application.configureJWT() {
10 | val secret = environment.config.property("jwt.secret").getString()
11 | val issuer = environment.config.property("jwt.issuer").getString()
12 | val myRealm = environment.config.property("jwt.realm").getString()
13 | val audience = environment.config.property("jwt.audience").getString()
14 | install(Authentication) {
15 | jwt("auth-jwt") {
16 | realm = myRealm
17 | verifier(JwtConfig.getVerifier(secret = secret, issuer = issuer, audience = audience))
18 | validate { credential ->
19 | if (credential.payload.getClaim("username").asString() != "" &&
20 | credential.payload.getClaim("password").asString() != ""
21 | ) {
22 | JWTPrincipal(credential.payload)
23 | } else {
24 | null
25 | }
26 | }
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/data/service/NotesServiceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.example.data.service
2 |
3 | import com.example.data.models.Note
4 | import com.example.data.repository.NotesRepository
5 |
6 | class NotesServiceImpl(private val notesRepository: NotesRepository) : NotesService {
7 | override fun addNote(note: String): Int {
8 | return notesRepository.addNote(note)
9 | }
10 |
11 | override fun deleteNote(id: Int): Int {
12 | return notesRepository.deleteNote(id)
13 | }
14 |
15 | override fun updateNote(id: Int, note: String) {
16 | return notesRepository.updateNote(id, note)
17 | }
18 |
19 | override fun fetchAllNotes(): List {
20 | return notesRepository.fetchNotes()
21 | }
22 |
23 | override fun fetchNoteWithId(id: Int): Note? {
24 | return notesRepository.fetchNoteWithId(id)
25 | }
26 |
27 | override fun fetchAllPaginatedNotes(page: Int, size: Int): List {
28 | return notesRepository.fetchPaginatedNotes(page, size)
29 | }
30 |
31 | override fun fetchSortedNotes(isDescending: Boolean?): List {
32 | return notesRepository.fetchSortedNotes(isDescending = isDescending)
33 | }
34 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/data/auth/jwt/JWTConfig.kt:
--------------------------------------------------------------------------------
1 | package com.example.data.auth.jwt
2 |
3 | import com.auth0.jwt.JWT
4 | import com.auth0.jwt.algorithms.Algorithm
5 | import com.auth0.jwt.interfaces.JWTVerifier
6 | import com.example.data.models.UserCredentials
7 | import java.util.*
8 |
9 | object JwtConfig {
10 |
11 | fun getVerifier(secret: String, issuer: String, audience: String): JWTVerifier {
12 | return JWT
13 | .require(Algorithm.HMAC256(secret))
14 | .withAudience(audience)
15 | .withIssuer(issuer)
16 | .build()
17 | }
18 |
19 | /**
20 | * Produce a token for this combination of name and password
21 | */
22 |
23 | fun generateToken(user: UserCredentials, issuer: String, secret: String, audience: String): String {
24 | return JWT.create()
25 | .withAudience(audience)
26 | .withIssuer(issuer)
27 | .withClaim("username", user.username)
28 | .withExpiresAt(getExpiration()) // optional
29 | .sign(Algorithm.HMAC256(secret))
30 | }
31 |
32 | /**
33 | * Calculate the expiration Date based on current time + the given validity
34 | */
35 |
36 | private const val validityInMs = 3600000 // 1 hour
37 |
38 | private fun getExpiration() = Date(System.currentTimeMillis() + validityInMs)
39 |
40 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/routing/AuthenticationRoutes.kt:
--------------------------------------------------------------------------------
1 | package com.example.routing
2 |
3 |
4 | import com.example.data.auth.jwt.JwtConfig
5 | import com.example.data.models.UserCredentials
6 | import io.ktor.server.application.*
7 | import io.ktor.server.auth.*
8 | import io.ktor.server.auth.jwt.*
9 | import io.ktor.server.request.*
10 | import io.ktor.server.response.*
11 | import io.ktor.server.routing.*
12 | import java.util.concurrent.TimeUnit
13 |
14 | fun Route.authenticateRoutes(secret: String, issuer: String, audience: String) {
15 | post("/generate-token") {
16 | val user = call.receive()
17 | println("${user.username} , pwd= ${user.password}")
18 | val token = JwtConfig.generateToken(user = user, issuer = issuer, secret = secret, audience = audience)
19 | call.respond(hashMapOf("token" to token))
20 | }
21 |
22 | authenticate("auth-jwt") {
23 | get("/test") {
24 | val principal = call.principal()
25 | val username = principal!!.payload.getClaim("username").asString()
26 | val expiresAt = principal.expiresAt?.time?.minus(System.currentTimeMillis())
27 | if (expiresAt != null) {
28 | call.respondText("Hi $username!! Your token expires in ${TimeUnit.MILLISECONDS.toMinutes(expiresAt)} minutes.")
29 | } else {
30 | call.respondText("Hi $username!! Your token expires soon")
31 | }
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/data/repository/NotesRepository.kt:
--------------------------------------------------------------------------------
1 | package com.example.data.repository
2 |
3 | import com.example.data.db.DatabaseConnection
4 | import com.example.data.entities.NotesEntity
5 | import com.example.data.models.Note
6 | import org.ktorm.dsl.*
7 |
8 | class NotesRepository {
9 |
10 | private val db = DatabaseConnection.database
11 |
12 | fun addNote(note: String): Int {
13 | return db.insert(NotesEntity) {
14 | set(it.note, note)
15 | }
16 | }
17 |
18 | fun deleteNote(id: Int): Int {
19 | return db.delete(NotesEntity) {
20 | it.id eq id
21 | }
22 | }
23 |
24 | fun updateNote(id: Int, note: String) {
25 | println("Note with id $id is updated with text $note")
26 | }
27 |
28 | fun fetchNotes(): List {
29 | println("Fetching the notes")
30 | return db.from(NotesEntity).select().map {
31 | val id = it[NotesEntity.id]
32 | val note = it[NotesEntity.note]
33 | Note(id ?: -1, note ?: "")
34 | }
35 | }
36 |
37 | fun fetchPaginatedNotes(page: Int, size: Int): List {
38 | println("Fetching the paginated list of notes")
39 | val limit: Int = size
40 | val pageSize: Int = size
41 | val skip: Int = (page - 1) * pageSize
42 | return db.from(NotesEntity).select().limit(offset = skip, limit = limit).map {
43 | val id = it[NotesEntity.id]
44 | val note = it[NotesEntity.note]
45 | Note(id ?: -1, note ?: "")
46 | }
47 | }
48 |
49 | fun fetchNoteWithId(id: Int): Note? {
50 | return db.from(NotesEntity)
51 | .select()
52 | .where { NotesEntity.id eq id }
53 | .map {
54 | val note = it[NotesEntity.note]!!
55 | Note(id = id, note = note)
56 | }.firstOrNull()
57 | }
58 |
59 | fun fetchSortedNotes(isDescending: Boolean? = false): List {
60 | val notes = NotesEntity
61 | val notesSortedByName = if (isDescending == true) {
62 | notes.note.desc()
63 | } else {
64 | notes.note.asc()
65 | }
66 | return db.from(NotesEntity).select().orderBy(notesSortedByName).map {
67 | val id = it[NotesEntity.id]
68 | val note = it[NotesEntity.note]
69 | Note(id ?: -1, note ?: "")
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # KtorAPI
2 |
3 | KtorAPI is a project which demonstrates the power of Kotlin's Ktor in developing powerful REST APIs with all basic as well as advanced features.
4 |
5 | Ktor version -: 2.1.3
6 |
7 | ## Architecture
8 | Ktor Controller (http) -> Service (business) -> Repository (data)
9 | ## Features
10 | 1. Create Notes
11 | 2. Update Notes
12 | 3. Fetch Notes
13 | 4. Delete Notes
14 | 5. JWT Authentication
15 | 6. Ktorm ORM for DB
16 | 7. Dependency Injection using Koin
17 | 8. Pagination
18 | 9. Sorting results using query parameters
19 | 10. Middlewares/Plugins for Logging
20 |
21 | #### Coming soon -:
22 |
23 | 1. Unit Testing
24 | 2. Searching
25 | 3. Exporting data in excel format
26 | 4. Exporting data in csv format
27 |
28 |
29 | ## Installation
30 |
31 | Use IntelliJ IDEA, community or enterprise edition to open the project and follow [these steps](https://ktor.io/docs/intellij-idea.html#run_app) to run the Application.
32 |
33 | Notes -: Customise the application.conf file which is not included with the project for security reasons. Specify the ktor object in the application.conf file and fill your own secret, audience, issuer, realm to configure the [JWT authenticaton settings](https://ktor.io/docs/jwt.html#jwt-settings). Similarly,specify your port and host inside the [application.conf](https://ktor.io/docs/configurations.html#hocon-file) file in which you want to run the server.
34 |
35 | Start the MySQL server to use the notes API.
36 |
37 | ## Usage
38 | ### Notes API
39 |
40 | 1. ##### Create a new note
41 | ```http
42 | POST http://0.0.0.0:3536/notes
43 | Content-Type: application/json
44 |
45 | {
46 | "note": "your-note",
47 | }
48 | ```
49 |
50 | 2. ##### Fetch all notes
51 | ```http
52 | GET http://0.0.0.0:3536/notes
53 | ```
54 |
55 | 3. ##### Fetch a particular note
56 | ```http
57 | GET http://0.0.0.0:3536/notes/{id}
58 | ```
59 |
60 | 4. ##### Delete a particular note
61 | ```http
62 | DELETE http://0.0.0.0:3536/notes/{id}
63 | ```
64 |
65 | 5. ##### Update a particular note
66 | ```http
67 | PUT http://0.0.0.0:3536/notes/{id}
68 | ```
69 |
70 | 6. ##### Fetch paginated notes
71 | ```http
72 | GET http://0.0.0.0:3536/paginatedNotes?size=5&page=1
73 | ```
74 |
75 | 7. ##### Fetch sorted notes
76 | ```http
77 | GET http://0.0.0.0:3536/notes?sort=asc
78 | ```
79 |
80 | ### JWT Authentication
81 |
82 | 1. ##### Generate an Auth Token
83 | ```http
84 | POST http://0.0.0.0:3536/generate-token
85 | Content-Type: application/json
86 |
87 | {
88 | "username": "username",
89 | "password": "password"
90 | }
91 | ```
92 |
93 | 2. ##### Test the Auth token
94 | ```http
95 | GET http://0.0.0.0:3536/test
96 | Authorization: Bearer {{auth_token}}
97 | ```
98 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/routing/NotesRoutes.kt:
--------------------------------------------------------------------------------
1 | package com.example.routing
2 |
3 | import com.example.Constants.ASCENDING
4 | import com.example.Constants.DESCENDING
5 | import com.example.data.db.DatabaseConnection
6 | import com.example.data.entities.NotesEntity
7 | import com.example.data.models.NoteRequest
8 | import com.example.data.models.NoteResponse
9 | import com.example.data.service.NotesService
10 | import io.ktor.http.*
11 | import io.ktor.server.application.*
12 | import io.ktor.server.request.*
13 | import io.ktor.server.response.*
14 | import io.ktor.server.routing.*
15 | import org.koin.ktor.ext.inject
16 | import org.ktorm.dsl.eq
17 | import org.ktorm.dsl.update
18 |
19 | fun Route.notesRoutes() {
20 | val db = DatabaseConnection.database
21 |
22 | // Lazy inject NotesService
23 | val service: NotesService by inject()
24 |
25 | get("/notes") {
26 | when (val sortType = call.request.queryParameters["sort"]) {
27 | // Sort according to ascending or descending
28 | ASCENDING, DESCENDING -> {
29 | call.respond(
30 | HttpStatusCode.OK,
31 | service.fetchSortedNotes(isDescending = sortType == DESCENDING)
32 | )
33 | }
34 | // No sorting requested
35 | null -> {
36 | call.respond(HttpStatusCode.OK, service.fetchAllNotes())
37 | }
38 | // Invalid sorting type
39 | else -> {
40 | call.respond(
41 | HttpStatusCode.NotAcceptable,
42 | NoteResponse(success = false, data = "Please enter a valid sorting type(asc or desc)")
43 | )
44 | }
45 | }
46 | }
47 |
48 | get("/paginatedNotes") {
49 | val page = call.request.queryParameters["page"]?.toInt() ?: 1 // Default Page is set to 1
50 | val size = call.request.queryParameters["size"]?.toInt() ?: 5 // Default Size is set to 5
51 | call.respond(HttpStatusCode.OK, service.fetchAllPaginatedNotes(page, size))
52 | }
53 |
54 | post("/notes") {
55 | val request = call.receive()
56 | val result = service.addNote(request.note)
57 |
58 | // Verify only 1 row has been inserted
59 | if (result == 1) {
60 | // To Successful response to the client
61 | call.respond(
62 | HttpStatusCode.OK,
63 | NoteResponse(
64 | success = true,
65 | data = "Value has been successfully inserted"
66 | )
67 | )
68 | } else {
69 | // Send Failure response to the client
70 | call.respond(
71 | HttpStatusCode.BadRequest,
72 | NoteResponse(
73 | success = false,
74 | data = "Failed to Insert Value"
75 | )
76 | )
77 | }
78 | }
79 |
80 | get("/notes/{id}") {
81 | val id: Int = call.parameters["id"]?.toInt() ?: -1
82 | val note = service.fetchNoteWithId(id)
83 | if (note == null) {
84 | call.respond(
85 | status = HttpStatusCode.NotFound,
86 | message = NoteResponse(success = false, data = "Could not find note with that id = $id")
87 | )
88 | } else {
89 | call.respond(
90 | HttpStatusCode.OK,
91 | NoteResponse(success = true, data = note)
92 | )
93 | }
94 | }
95 |
96 | put("/notes/{id}") {
97 | val id = call.parameters["id"]?.toInt() ?: -1
98 | val updatedNote = call.receive()
99 | val rowsEffected = db.update(NotesEntity) {
100 | set(it.note, updatedNote.note)
101 | where {
102 | it.id eq id
103 | }
104 | }
105 | if (rowsEffected != 1) {
106 | call.respond(
107 | HttpStatusCode.NotFound,
108 | NoteResponse(success = false, data = "Could not find note with that id = $id")
109 | )
110 | } else {
111 | call.respond(
112 | HttpStatusCode.OK,
113 | NoteResponse(success = true, data = "Success")
114 | )
115 | }
116 | }
117 |
118 | delete("/notes/{id}") {
119 | val id: Int = call.parameters["id"]?.toInt() ?: -1
120 | val rowsEffected = service.deleteNote(id)
121 | if (rowsEffected != 1) {
122 | call.respond(
123 | HttpStatusCode.NotFound,
124 | NoteResponse(success = false, data = "Could not find note with that id = $id")
125 | )
126 | } else {
127 | call.respond(
128 | HttpStatusCode.OK,
129 | NoteResponse(success = true, data = "Success")
130 | )
131 | }
132 | }
133 | }
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or 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 UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MSYS* | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------