From 562fef9d9229d1bbbc4807ca1a858f24feedc768 Mon Sep 17 00:00:00 2001 From: "Wyatt J. Miller" Date: Mon, 4 Jul 2022 19:44:10 -0400 Subject: [PATCH] code dump --- .gitignore | 1 + build.gradle.kts | 11 ++- .../kotlin/com/wyattjmiller/Application.kt | 8 +- .../kotlin/com/wyattjmiller/auth/JwtConfig.kt | 31 ++++++ .../com/wyattjmiller/data/DatabaseManager.kt | 15 +-- .../com/wyattjmiller/data/RecipeEntities.kt | 36 +++++-- .../com/wyattjmiller/data/UserEntities.kt | 33 +++++++ .../com/wyattjmiller/entities/Author.kt | 21 ++++ .../com/wyattjmiller/entities/Recipe.kt | 15 ++- .../kotlin/com/wyattjmiller/entities/User.kt | 20 ++++ src/main/kotlin/com/wyattjmiller/openapi.json | 15 +++ .../wyattjmiller/plugins/Authentication.kt | 30 ++++++ .../com/wyattjmiller/plugins/Routing.kt | 7 +- .../com/wyattjmiller/plugins/Serialization.kt | 14 +-- .../wyattjmiller/repositories/AuthorDao.kt | 66 +++++++++++++ .../repositories/AuthorRepository.kt | 12 +++ .../wyattjmiller/repositories/RecipeDao.kt | 50 ++++++++-- .../repositories/RecipeRepository.kt | 7 +- .../com/wyattjmiller/repositories/UserDao.kt | 75 ++++++++++++++ .../repositories/UserRepository.kt | 11 +++ .../com/wyattjmiller/routes/AuthorRoute.kt | 69 +++++++++++++ .../com/wyattjmiller/routes/RecipeRoute.kt | 28 ++++++ .../com/wyattjmiller/routes/UserRoute.kt | 99 +++++++++++++++++++ src/main/resources/application.conf | 5 + src/test/kotlin/com/wyattjmiller/author.http | 25 +++++ src/test/kotlin/com/wyattjmiller/login.http | 26 +++++ src/test/kotlin/com/wyattjmiller/recipe.http | 10 ++ src/test/kotlin/com/wyattjmiller/user.http | 2 + 28 files changed, 694 insertions(+), 48 deletions(-) create mode 100644 src/main/kotlin/com/wyattjmiller/auth/JwtConfig.kt create mode 100644 src/main/kotlin/com/wyattjmiller/data/UserEntities.kt create mode 100644 src/main/kotlin/com/wyattjmiller/entities/Author.kt create mode 100644 src/main/kotlin/com/wyattjmiller/openapi.json create mode 100644 src/main/kotlin/com/wyattjmiller/plugins/Authentication.kt create mode 100644 src/main/kotlin/com/wyattjmiller/repositories/AuthorDao.kt create mode 100644 src/main/kotlin/com/wyattjmiller/repositories/AuthorRepository.kt create mode 100644 src/main/kotlin/com/wyattjmiller/repositories/UserDao.kt create mode 100644 src/main/kotlin/com/wyattjmiller/repositories/UserRepository.kt create mode 100644 src/main/kotlin/com/wyattjmiller/routes/AuthorRoute.kt create mode 100644 src/main/kotlin/com/wyattjmiller/routes/UserRoute.kt create mode 100644 src/test/kotlin/com/wyattjmiller/author.http create mode 100644 src/test/kotlin/com/wyattjmiller/login.http create mode 100644 src/test/kotlin/com/wyattjmiller/recipe.http create mode 100644 src/test/kotlin/com/wyattjmiller/user.http diff --git a/.gitignore b/.gitignore index c426c32..4812805 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .gradle build/ +gen/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ diff --git a/build.gradle.kts b/build.gradle.kts index 556cf49..569402e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,8 @@ val ktor_version: String by project val kotlin_version: String by project val logback_version: String by project +val ktorm_version: String by project +val postgresql_driver_version: String by project plugins { application @@ -27,10 +29,13 @@ dependencies { implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktor_version") implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:$ktor_version") implementation("io.ktor:ktor-server-netty-jvm:$ktor_version") - implementation("org.ktorm:ktorm-core:3.4.1") - implementation("org.ktorm:ktorm-support-postgresql:3.4.1") - implementation("org.postgresql:postgresql:42.2.2") + implementation("io.ktor:ktor-server-auth:$ktor_version") + implementation("io.ktor:ktor-server-auth-jwt:$ktor_version") + implementation("org.ktorm:ktorm-core:$ktorm_version") + implementation("org.ktorm:ktorm-support-postgresql:$ktorm_version") + implementation("org.postgresql:postgresql:$postgresql_driver_version") implementation("ch.qos.logback:logback-classic:$logback_version") + implementation("org.mindrot:jbcrypt:0.4") testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") } \ No newline at end of file diff --git a/src/main/kotlin/com/wyattjmiller/Application.kt b/src/main/kotlin/com/wyattjmiller/Application.kt index fd7ef02..6bc3609 100644 --- a/src/main/kotlin/com/wyattjmiller/Application.kt +++ b/src/main/kotlin/com/wyattjmiller/Application.kt @@ -2,8 +2,9 @@ package com.wyattjmiller import io.ktor.server.application.* import com.wyattjmiller.plugins.* +import com.wyattjmiller.routes.AuthorRoute.Companion.authorRoutes import com.wyattjmiller.routes.RecipeRoute.Companion.recipeRoutes -import io.ktor.server.config.* +import com.wyattjmiller.routes.UserRoute.Companion.userRoutes fun main(args: Array): Unit = io.ktor.server.netty.EngineMain.main(args) @@ -12,8 +13,7 @@ fun main(args: Array): Unit = fun Application.module() { //val config = this.environment.config - configureRouting() + configureAuthentication() configureSerialization() - - recipeRoutes() + configureRouting() } diff --git a/src/main/kotlin/com/wyattjmiller/auth/JwtConfig.kt b/src/main/kotlin/com/wyattjmiller/auth/JwtConfig.kt new file mode 100644 index 0000000..9135cbd --- /dev/null +++ b/src/main/kotlin/com/wyattjmiller/auth/JwtConfig.kt @@ -0,0 +1,31 @@ +package com.wyattjmiller.auth + +import com.auth0.jwt.JWT +import com.auth0.jwt.JWTVerifier +import com.auth0.jwt.algorithms.Algorithm +import com.wyattjmiller.entities.UserLogin +import io.ktor.server.config.* +import java.util.* + +class JwtConfig(config: HoconApplicationConfig) { + private val audience = config.property("audience").getString() + private val secret = config.property("secret").getString() + private val issuer = config.property("issuer").getString() + private val expirationDate = System.currentTimeMillis() + 60000 + + fun generateJwtToken(user: UserLogin): String { + return JWT.create() + .withAudience(audience) + .withIssuer(issuer) + .withClaim("username", user.username) + .withExpiresAt(Date(expirationDate)) + .sign(Algorithm.HMAC256(secret)) + } + + fun verifyJwtToken() : JWTVerifier { + return JWT.require(Algorithm.HMAC256(secret)) + .withAudience(audience) + .withIssuer(issuer) + .build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wyattjmiller/data/DatabaseManager.kt b/src/main/kotlin/com/wyattjmiller/data/DatabaseManager.kt index b45510d..79b8589 100644 --- a/src/main/kotlin/com/wyattjmiller/data/DatabaseManager.kt +++ b/src/main/kotlin/com/wyattjmiller/data/DatabaseManager.kt @@ -1,16 +1,11 @@ package com.wyattjmiller.data -import io.ktor.server.config.* import org.ktorm.database.Database class DatabaseManager() { - var database: Database - - init { - database = Database.connect("jdbc:postgresql://10.10.10.21:5432/recipefolio", - "org.postgresql.Driver", - user = "wyatt", - password = "wjm" - ) - } + var database: Database = Database.connect("jdbc:postgresql://10.10.10.21:5432/recipefolio", + "org.postgresql.Driver", + user = "wyatt", + password = "wjm" + ) } \ No newline at end of file diff --git a/src/main/kotlin/com/wyattjmiller/data/RecipeEntities.kt b/src/main/kotlin/com/wyattjmiller/data/RecipeEntities.kt index 63ec764..257d237 100644 --- a/src/main/kotlin/com/wyattjmiller/data/RecipeEntities.kt +++ b/src/main/kotlin/com/wyattjmiller/data/RecipeEntities.kt @@ -1,22 +1,42 @@ package com.wyattjmiller.data import org.ktorm.entity.Entity -import org.ktorm.schema.Table -import org.ktorm.schema.int -import org.ktorm.schema.varchar +import org.ktorm.schema.* +import java.time.LocalDateTime -object DbRecipeTable : Table("recipe") { +object DbRecipeTable : Table("recipe") { val id = int("id").primaryKey().bindTo { it.id } val name = varchar("name").bindTo { it.name } val desc = varchar("description").bindTo { it.desc } val ingredients = varchar("ingredients").bindTo { it.ingredients } + val author = int("author_id").references(DbAuthorTable) { it.author } + val createdTimestamp = datetime("created_timestamp").bindTo { it.createdTimestamp } } -interface DbRecipeEntity : Entity { - companion object : Entity.Factory() +object DbAuthorTable : Table("author") { + val id = int("id").primaryKey().bindTo { it.id } + val authorName = varchar("author_name").bindTo { it.authorName } + val user = int("user_id").references(DbUserTable) { it.user } + val createdTimestamp = datetime("created_timestamp").bindTo { it.createdTimestamp } +} - val id: Int +interface Recipes : Entity { + companion object : Entity.Factory() + + val id: Int? val name: String val desc: String val ingredients: String -} \ No newline at end of file + val author: Authors + val createdTimestamp: LocalDateTime +} + +interface Authors : Entity { + companion object : Entity.Factory() + + val id: Int + val authorName: String + val user: Users? + val createdTimestamp: LocalDateTime +} + diff --git a/src/main/kotlin/com/wyattjmiller/data/UserEntities.kt b/src/main/kotlin/com/wyattjmiller/data/UserEntities.kt new file mode 100644 index 0000000..9f5c9ac --- /dev/null +++ b/src/main/kotlin/com/wyattjmiller/data/UserEntities.kt @@ -0,0 +1,33 @@ +package com.wyattjmiller.data + +import io.ktor.server.auth.* +import org.ktorm.entity.Entity +import org.ktorm.schema.Table +import org.ktorm.schema.datetime +import org.ktorm.schema.int +import org.ktorm.schema.varchar +import java.time.LocalDateTime + +object DbUserTable : Table("user") { + val id = int("id").primaryKey().bindTo { it.id } + val firstName = varchar("first_name").bindTo { it.firstName } + val lastName = varchar("last_name").bindTo { it.lastName } + val username = varchar("username").bindTo { it.username } + val password = varchar("password").bindTo { it.password } + val emailAddress = varchar("email_address").bindTo { it.emailAddress } + val createdTimestamp = datetime("created_timestamp").bindTo { it.createdTimestamp } + //val createdAuthors = int("created_authors").references(DbAuthorTable) { it.createdAuthors } +} + +interface Users : Entity { + companion object : Entity.Factory() + + val id: Int + val firstName: String + val lastName: String + val username: String + val password: String + val emailAddress: String + val createdTimestamp: LocalDateTime + //val createdAuthors get() = listOf(DbAuthorTable.id) +} \ No newline at end of file diff --git a/src/main/kotlin/com/wyattjmiller/entities/Author.kt b/src/main/kotlin/com/wyattjmiller/entities/Author.kt new file mode 100644 index 0000000..7150f4c --- /dev/null +++ b/src/main/kotlin/com/wyattjmiller/entities/Author.kt @@ -0,0 +1,21 @@ +package com.wyattjmiller.entities + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind + +@Serializable +@SerialName("Author") +data class Author( + val id: Int?, + val authorName: String, + val user: Int, + var createdTimestamp: String, +) + +@Serializable +@SerialName("AuthorDraft") +data class AuthorDraft( + val authorName: String, + val user: Int? = null, +) \ No newline at end of file diff --git a/src/main/kotlin/com/wyattjmiller/entities/Recipe.kt b/src/main/kotlin/com/wyattjmiller/entities/Recipe.kt index 47a8f15..ef681e1 100644 --- a/src/main/kotlin/com/wyattjmiller/entities/Recipe.kt +++ b/src/main/kotlin/com/wyattjmiller/entities/Recipe.kt @@ -1,11 +1,24 @@ package com.wyattjmiller.entities +import com.wyattjmiller.data.Authors +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable +@SerialName("Recipe") data class Recipe( - val id: Int, + val id: Int?, val name: String, val desc: String, val ingredients: String, + var createdTimestamp: String, + val author: Int, +) +@Serializable +@SerialName("RecipeDraft") +data class RecipeDraft( + val name: String, + val desc: String, + val ingredients: String, + val author: Int, ) diff --git a/src/main/kotlin/com/wyattjmiller/entities/User.kt b/src/main/kotlin/com/wyattjmiller/entities/User.kt index 5f41818..f6b4f32 100644 --- a/src/main/kotlin/com/wyattjmiller/entities/User.kt +++ b/src/main/kotlin/com/wyattjmiller/entities/User.kt @@ -1,12 +1,32 @@ package com.wyattjmiller.entities import kotlinx.serialization.Serializable +import java.sql.Timestamp @Serializable data class User( val id: Int, val firstName: String, val lastName: String, + val username: String, val emailAddress: String, val password: String, + val createdTimestamp: String, + val createdAuthors: Int, ) + +@Serializable +data class UserLogin( + val username: String, + val password: String, +) + +@Serializable +data class UserDraft( + val firstName: String, + val lastName: String, + val username: String, + val emailAddress: String, + val password: String, + val createdTimestamp: String +) \ No newline at end of file diff --git a/src/main/kotlin/com/wyattjmiller/openapi.json b/src/main/kotlin/com/wyattjmiller/openapi.json new file mode 100644 index 0000000..d5983a9 --- /dev/null +++ b/src/main/kotlin/com/wyattjmiller/openapi.json @@ -0,0 +1,15 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "RecipeFolio RESTful API", + "description": "Just a reference! Nothing to see here! **Wyatt proceeds to smuggle servers into a closet**", + "version": "0.0.3 SE-B012" + }, + "servers": [ + { + "url": "https" + } + ], + "paths": { + } +} diff --git a/src/main/kotlin/com/wyattjmiller/plugins/Authentication.kt b/src/main/kotlin/com/wyattjmiller/plugins/Authentication.kt new file mode 100644 index 0000000..0a6c5b0 --- /dev/null +++ b/src/main/kotlin/com/wyattjmiller/plugins/Authentication.kt @@ -0,0 +1,30 @@ +package com.wyattjmiller.plugins + +import com.typesafe.config.ConfigFactory +import com.wyattjmiller.auth.JwtConfig +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.auth.jwt.* +import io.ktor.server.config.* + +fun Application.configureAuthentication() { + val config = HoconApplicationConfig(ConfigFactory.load()) + val tokenManager = JwtConfig(config) + + install(Authentication) { + jwt { + verifier(tokenManager.verifyJwtToken()) + realm = config.property("realm").getString() + + validate { jwt -> + if (jwt.payload.getClaim("username").asString().isNotEmpty()) { + JWTPrincipal(jwt.payload) + } else { + null + } + } + } + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wyattjmiller/plugins/Routing.kt b/src/main/kotlin/com/wyattjmiller/plugins/Routing.kt index 57833bf..5857e9d 100644 --- a/src/main/kotlin/com/wyattjmiller/plugins/Routing.kt +++ b/src/main/kotlin/com/wyattjmiller/plugins/Routing.kt @@ -1,10 +1,15 @@ package com.wyattjmiller.plugins +import com.wyattjmiller.routes.AuthorRoute.Companion.authorRoutes +import com.wyattjmiller.routes.RecipeRoute.Companion.recipeRoutes +import com.wyattjmiller.routes.UserRoute.Companion.userRoutes import io.ktor.server.routing.* import io.ktor.server.application.* fun Application.configureRouting() { routing { - + this@configureRouting.recipeRoutes() + this@configureRouting.authorRoutes() + this@configureRouting.userRoutes() } } diff --git a/src/main/kotlin/com/wyattjmiller/plugins/Serialization.kt b/src/main/kotlin/com/wyattjmiller/plugins/Serialization.kt index 89febfc..8873061 100644 --- a/src/main/kotlin/com/wyattjmiller/plugins/Serialization.kt +++ b/src/main/kotlin/com/wyattjmiller/plugins/Serialization.kt @@ -3,17 +3,13 @@ package com.wyattjmiller.plugins import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* import io.ktor.server.plugins.contentnegotiation.* -import io.ktor.server.response.* -import io.ktor.server.routing.* +import kotlinx.serialization.json.Json fun Application.configureSerialization() { install(ContentNegotiation) { - json() - } - - routing { - get("/json/kotlinx-serialization") { - call.respond(mapOf("hello" to "world")) - } + json( Json { + explicitNulls = true + prettyPrint = true + }) } } diff --git a/src/main/kotlin/com/wyattjmiller/repositories/AuthorDao.kt b/src/main/kotlin/com/wyattjmiller/repositories/AuthorDao.kt new file mode 100644 index 0000000..e0322df --- /dev/null +++ b/src/main/kotlin/com/wyattjmiller/repositories/AuthorDao.kt @@ -0,0 +1,66 @@ +package com.wyattjmiller.repositories + +import com.wyattjmiller.data.DatabaseManager +import com.wyattjmiller.data.DbAuthorTable +import com.wyattjmiller.entities.Author +import com.wyattjmiller.entities.AuthorDraft +import org.ktorm.dsl.delete +import org.ktorm.dsl.eq +import org.ktorm.dsl.insertAndGenerateKey +import org.ktorm.dsl.update +import org.ktorm.entity.firstOrNull +import org.ktorm.entity.sequenceOf +import org.ktorm.entity.toList +import java.time.LocalDateTime + +class AuthorDao : AuthorRepository { + private val db = DatabaseManager().database + + override fun getAllAuthors() : List { + return db + .sequenceOf(DbAuthorTable) + .toList() + .map { + Author(it.id, it.authorName, it.user!!.id, it.createdTimestamp.toString()) + } + } + + override fun getAuthor(id: Int) : Author { + return db + .sequenceOf(DbAuthorTable) + .firstOrNull { + it.id eq id + } + .let { + Author(it!!.id, it.authorName, it.user!!.id, it.createdTimestamp.toString()) + } + } + + override fun createAuthor(author: AuthorDraft) { + db.insertAndGenerateKey(DbAuthorTable) { + set(DbAuthorTable.authorName, author.authorName) + set(DbAuthorTable.user, author.user) + set(DbAuthorTable.createdTimestamp, LocalDateTime.now()) + } as Int + } + + override fun updateAuthor(id: Int, author: AuthorDraft) : Boolean { + val updatedRows = db.update(DbAuthorTable) { + set(DbAuthorTable.authorName, author.authorName) + set(DbAuthorTable.user, author.user) + where { + it.id eq id + } + } + + return updatedRows > 0 + } + + override fun deleteAuthor(id: Int) : Boolean { + val deletedRows = db.delete(DbAuthorTable) { + it.id eq id + } + + return deletedRows > 0 + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wyattjmiller/repositories/AuthorRepository.kt b/src/main/kotlin/com/wyattjmiller/repositories/AuthorRepository.kt new file mode 100644 index 0000000..d50a8e4 --- /dev/null +++ b/src/main/kotlin/com/wyattjmiller/repositories/AuthorRepository.kt @@ -0,0 +1,12 @@ +package com.wyattjmiller.repositories + +import com.wyattjmiller.entities.Author +import com.wyattjmiller.entities.AuthorDraft + +interface AuthorRepository { + fun getAllAuthors() : List + fun getAuthor(id: Int) : Author? + fun createAuthor(author: AuthorDraft) + fun updateAuthor(id: Int, author: AuthorDraft) : Boolean + fun deleteAuthor(id: Int) : Boolean +} \ No newline at end of file diff --git a/src/main/kotlin/com/wyattjmiller/repositories/RecipeDao.kt b/src/main/kotlin/com/wyattjmiller/repositories/RecipeDao.kt index db6afab..536cad7 100644 --- a/src/main/kotlin/com/wyattjmiller/repositories/RecipeDao.kt +++ b/src/main/kotlin/com/wyattjmiller/repositories/RecipeDao.kt @@ -3,8 +3,13 @@ package com.wyattjmiller.repositories import com.wyattjmiller.data.DatabaseManager import com.wyattjmiller.data.DbRecipeTable import com.wyattjmiller.entities.Recipe +import com.wyattjmiller.entities.RecipeDraft +import org.ktorm.dsl.delete import org.ktorm.dsl.eq +import org.ktorm.dsl.insertAndGenerateKey +import org.ktorm.dsl.update import org.ktorm.entity.* +import java.time.LocalDateTime class RecipeDao : RecipeRepository { private val db = DatabaseManager() @@ -13,25 +18,52 @@ class RecipeDao : RecipeRepository { return db.database .sequenceOf(DbRecipeTable) .toList() - .map { Recipe(it.id, it.name, it.desc, it.ingredients) } + .map { + Recipe(it.id, it.name, it.desc, it.ingredients, it.createdTimestamp.toString(), it.author.id) + } } override fun getRecipe(id: Int) : Recipe { return db.database .sequenceOf(DbRecipeTable) - .firstOrNull { it.id eq id } - .let { Recipe(it!!.id, it.name, it.desc, it.ingredients) } + .firstOrNull { + it.id eq id + } + .let { + Recipe(it!!.id, it.name, it.desc, it.ingredients, it.createdTimestamp.toString(), it.author.id) + } } - override fun createRecipe(recipe: Recipe) { - TODO("Not yet implemented") + override fun createRecipe(recipe: RecipeDraft) : Recipe { + val key = db.database.insertAndGenerateKey(DbRecipeTable) { + set(DbRecipeTable.name, recipe.name) + set(DbRecipeTable.desc, recipe.desc) + set(DbRecipeTable.ingredients, recipe.ingredients) + set(DbRecipeTable.createdTimestamp, LocalDateTime.now()) + set(DbRecipeTable.author, recipe.author) + } as Int + + return Recipe(key, recipe.name, recipe.desc, recipe.ingredients, LocalDateTime.now().toString(), recipe.author) } - override fun updateRecipe(id: Int, recipe: Recipe): Boolean { - TODO("Not yet implemented") + override fun updateRecipe(id: Int, recipe: RecipeDraft) : Boolean { + val updatedRows = db.database.update(DbRecipeTable) { + set(DbRecipeTable.name, recipe!!.name) + set(DbRecipeTable.desc, recipe!!.desc) + set(DbRecipeTable.ingredients, recipe!!.ingredients) + where { + it.id eq id + } + } + + return updatedRows > 0 } - override fun deleteRecipe(id: Int) { - TODO("Not yet implemented") + override fun deleteRecipe(id: Int) : Boolean { + val deletedRows = db.database.delete(DbRecipeTable) { + it.id eq id + } + + return deletedRows > 0 } } \ No newline at end of file diff --git a/src/main/kotlin/com/wyattjmiller/repositories/RecipeRepository.kt b/src/main/kotlin/com/wyattjmiller/repositories/RecipeRepository.kt index 2c55b92..b1e432c 100644 --- a/src/main/kotlin/com/wyattjmiller/repositories/RecipeRepository.kt +++ b/src/main/kotlin/com/wyattjmiller/repositories/RecipeRepository.kt @@ -1,11 +1,12 @@ package com.wyattjmiller.repositories import com.wyattjmiller.entities.Recipe +import com.wyattjmiller.entities.RecipeDraft interface RecipeRepository { fun getAllRecipies() : List fun getRecipe(id: Int) : Recipe? - fun createRecipe(recipe: Recipe) - fun updateRecipe(id: Int, recipe: Recipe): Boolean - fun deleteRecipe(id: Int) + fun createRecipe(recipe: RecipeDraft) : Recipe + fun updateRecipe(id: Int, recipe: RecipeDraft) : Boolean + fun deleteRecipe(id: Int) : Boolean } \ No newline at end of file diff --git a/src/main/kotlin/com/wyattjmiller/repositories/UserDao.kt b/src/main/kotlin/com/wyattjmiller/repositories/UserDao.kt new file mode 100644 index 0000000..c157849 --- /dev/null +++ b/src/main/kotlin/com/wyattjmiller/repositories/UserDao.kt @@ -0,0 +1,75 @@ +package com.wyattjmiller.repositories + +import com.wyattjmiller.data.DatabaseManager +import com.wyattjmiller.data.DbAuthorTable +import com.wyattjmiller.data.DbUserTable +import com.wyattjmiller.data.DbUserTable.password +import com.wyattjmiller.data.DbUserTable.username +import com.wyattjmiller.entities.Author +import com.wyattjmiller.entities.User +import com.wyattjmiller.entities.UserDraft +import com.wyattjmiller.entities.UserLogin +import org.ktorm.dsl.* +import org.ktorm.entity.firstOrNull +import org.ktorm.entity.sequenceOf +import java.time.LocalDateTime + +class UserDao : UserRepository { + private val db = DatabaseManager().database + + fun usernameCheck(user: UserLogin) : UserLogin? { + return db.from(DbUserTable) + .select() + .where { username eq user.username } + .map { + // don't know what to do here + UserLogin(user.username, user.password) + }.firstOrNull() + } + + fun passwordCheck(user: UserLogin) : UserLogin? { + return db.from(DbUserTable) + .select() + .where { password eq user.password } + .map { + // don't know what to do here + UserLogin(user.username, user.password) + }.firstOrNull() + } + + fun getAllUsers() { + TODO("Joke's on you! This function is not implemented yet. -Wyatt") + } + + override fun getUser(id: Int): User? { + return db + .sequenceOf(DbUserTable) + .firstOrNull { + it.id eq id + } + .let { + User(it!!.id, it.firstName, it.lastName, it.username, it.password, it.emailAddress, it.createdTimestamp.toString(), 0) + } + } + + override fun createUser(user: UserDraft): User { + val key = db.insertAndGenerateKey(DbUserTable) { + set(DbUserTable.firstName, user.firstName) + set(DbUserTable.lastName, user.lastName) + set(DbUserTable.username, user.username) + set(DbUserTable.password, user.password) + set(DbUserTable.emailAddress, user.emailAddress) + set(DbUserTable.createdTimestamp, LocalDateTime.now()) + } as Int + + return User(key, user.firstName, user.lastName, user.username, user.password, user.emailAddress, LocalDateTime.now().toString(), 0) + } + + override fun updateUser(id: Int, user: UserDraft): Boolean { + TODO("Not yet implemented") + } + + override fun deleteUser(id: Int): Boolean { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wyattjmiller/repositories/UserRepository.kt b/src/main/kotlin/com/wyattjmiller/repositories/UserRepository.kt new file mode 100644 index 0000000..34a7649 --- /dev/null +++ b/src/main/kotlin/com/wyattjmiller/repositories/UserRepository.kt @@ -0,0 +1,11 @@ +package com.wyattjmiller.repositories + +import com.wyattjmiller.entities.User +import com.wyattjmiller.entities.UserDraft + +interface UserRepository { + fun getUser(id: Int) : User? + fun createUser(user: UserDraft) : User + fun updateUser(id: Int, user: UserDraft) : Boolean + fun deleteUser(id: Int) : Boolean +} \ No newline at end of file diff --git a/src/main/kotlin/com/wyattjmiller/routes/AuthorRoute.kt b/src/main/kotlin/com/wyattjmiller/routes/AuthorRoute.kt new file mode 100644 index 0000000..1bbc765 --- /dev/null +++ b/src/main/kotlin/com/wyattjmiller/routes/AuthorRoute.kt @@ -0,0 +1,69 @@ +package com.wyattjmiller.routes + +import com.wyattjmiller.entities.AuthorDraft +import com.wyattjmiller.repositories.AuthorDao +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +class AuthorRoute { + companion object { + private val author = AuthorDao() + + private fun Route.getAllAuthors() { + get("/author") { + call.respond(author.getAllAuthors()) + } + } + + private fun Route.getAuthor() { + get("/author/{id?}") { + val id = call.parameters["id"]?.toInt() ?: return@get call.respond( + HttpStatusCode.BadRequest, "Missing author identifier :(" + ) + + val res = author.getAuthor(id) ?: return@get call.respond( + HttpStatusCode.NotFound, "No recipe found :(" + ) + + call.respond(res) + } + } + + private fun Route.createAuthor() { + post("/author") { + val req = call.receive() + val res = author.createAuthor(req) + call.respond(HttpStatusCode.Created) + } + } + + private fun Route.updateAuthor() { + put("/author/{id?}") { + val req = call.receive() + val id = call.parameters["id"]?.toIntOrNull() ?: return@put call.respond( + HttpStatusCode.BadRequest, "Missing author identifier :(" + ) + + if (author.updateAuthor(id, req)) { + call.respond(HttpStatusCode.Created) + } else { + call.respond( + HttpStatusCode.NotFound, "No author found :(" + ) + } + } + } + + fun Application.authorRoutes() { + routing { + getAllAuthors() + getAuthor() + createAuthor() + updateAuthor() + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wyattjmiller/routes/RecipeRoute.kt b/src/main/kotlin/com/wyattjmiller/routes/RecipeRoute.kt index b85cb51..9300959 100644 --- a/src/main/kotlin/com/wyattjmiller/routes/RecipeRoute.kt +++ b/src/main/kotlin/com/wyattjmiller/routes/RecipeRoute.kt @@ -1,8 +1,10 @@ package com.wyattjmiller.routes +import com.wyattjmiller.entities.RecipeDraft import com.wyattjmiller.repositories.RecipeDao import io.ktor.http.* import io.ktor.server.application.* +import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* @@ -30,10 +32,36 @@ class RecipeRoute { } } + private fun Route.createRecipe() { + post("/recipe") { + val req = call.receive() + val res = rec.createRecipe(req) + call.respond(res) + } + } + + private fun Route.updateRecipe() { + put("/recipe/{id?}") { + val req = call.receive() + val id = call.parameters["id"]?.toIntOrNull() ?: return@put call.respond( + HttpStatusCode.BadRequest,"Missing recipe identifier :(" + ) + + if (rec.updateRecipe(id, req)) { + call.respond(HttpStatusCode.OK) + } else { + call.respond(HttpStatusCode.NotFound, + "No recipe found :(") + } + } + } + fun Application.recipeRoutes() { routing { getAllRecipes() getRecipe() + createRecipe() + updateRecipe() } } } diff --git a/src/main/kotlin/com/wyattjmiller/routes/UserRoute.kt b/src/main/kotlin/com/wyattjmiller/routes/UserRoute.kt new file mode 100644 index 0000000..353cf01 --- /dev/null +++ b/src/main/kotlin/com/wyattjmiller/routes/UserRoute.kt @@ -0,0 +1,99 @@ +package com.wyattjmiller.routes + +import com.typesafe.config.ConfigFactory +import com.wyattjmiller.auth.JwtConfig +import com.wyattjmiller.entities.UserDraft +import com.wyattjmiller.entities.UserLogin +import com.wyattjmiller.repositories.UserDao +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.config.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +class UserRoute { + companion object { + private val user = UserDao() + private val tokenManager = JwtConfig(HoconApplicationConfig(ConfigFactory.load())) + + private fun Route.login() { + post("/login") { + val userCreds = call.receive() + val usernameResult = user.usernameCheck(userCreds) + val passwordResult = user.passwordCheck(userCreds) + + if (usernameResult == null || passwordResult == null) { + call.respond(HttpStatusCode.BadRequest) + } + + //if (!BCrypt.checkpw(password, res?.password)) { + // call.respond(HttpStatusCode.BadRequest) + // return@post + //} + + val token = tokenManager.generateJwtToken(userCreds) + call.respond(HttpStatusCode.OK, token) + } + } + + private fun Route.getAllUsers() { + get("/user") { + call.respond(user.getAllUsers()) + } + } + + private fun Route.getUser() { + authenticate { + get("/user/{id?}") { + val id = call.parameters["id"]?.toInt() ?: return@get call.respond( + HttpStatusCode.BadRequest, "Missing user identifier :(" + ) + + val res = user.getUser(id) ?: return@get call.respond( + HttpStatusCode.NotFound, "No recipe found :(" + ) + + call.respond(res) + } + } + } + + private fun Route.createUser() { + post("/user") { + val req = call.receive() + val res = user.createUser(req) + call.respond(res) + } + } + + private fun Route.updateUser() { + authenticate { + put("/user/{id?}") { + val req = call.receive() + val id = call.parameters["id"]?.toIntOrNull() ?: return@put call.respond( + HttpStatusCode.BadRequest, "Missing author identifier :(" + ) + + if (user.updateUser(id, req)) { + call.respond(HttpStatusCode.Accepted) + } else { + call.respond( + HttpStatusCode.NotFound, "No author found :(" + ) + } + } + } + } + + fun Application.userRoutes() { + routing { + login() + getUser() + createUser() + updateUser() + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 6baad65..ec36988 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -17,3 +17,8 @@ storage { dbUser = "wyatt" dbPasswd = "wjm" } + +secret = "!$#terces!$#" +issuer = "http://0.0.0.0:8080/" +audience = "http://0.0.0.0:8080/hello" +realm = "Access to 'hello'" diff --git a/src/test/kotlin/com/wyattjmiller/author.http b/src/test/kotlin/com/wyattjmiller/author.http new file mode 100644 index 0000000..299c844 --- /dev/null +++ b/src/test/kotlin/com/wyattjmiller/author.http @@ -0,0 +1,25 @@ +// Get all authors +GET http://localhost:8080/author + +### +// Get one author +GET http://localhost:8080/author/3 + +### +// Create author +POST http://localhost:8080/author +Content-Type: application/json + +{ + "authorName": "Aidan Tiernan", + "user": 1 +} + +### +// Create author without a user +POST http://localhost:8080/author +Content-Type: application/json + +{ + "authorName": "Cayde Alpers" +} \ No newline at end of file diff --git a/src/test/kotlin/com/wyattjmiller/login.http b/src/test/kotlin/com/wyattjmiller/login.http new file mode 100644 index 0000000..a8b456a --- /dev/null +++ b/src/test/kotlin/com/wyattjmiller/login.http @@ -0,0 +1,26 @@ +### Valid login +POST http://localhost:8080/login +Content-Type: application/json + +{ + "username": "wymiller", + "password": "wjm192837465" +} + +### Incorrect username +POST http://localhost:8080/login +Content-Type: application/json + +{ + "username": "denthead", + "password": "wjm192837465" +} + +### Incorrect password +POST http://localhost:8080/login +Content-Type: application/json + +{ + "username": "wymiller", + "password": "averyisgay" +} \ No newline at end of file diff --git a/src/test/kotlin/com/wyattjmiller/recipe.http b/src/test/kotlin/com/wyattjmiller/recipe.http new file mode 100644 index 0000000..eae9b54 --- /dev/null +++ b/src/test/kotlin/com/wyattjmiller/recipe.http @@ -0,0 +1,10 @@ +// Get all of the recipes +GET http://localhost:8080/recipe + +### +// Get one recipe +GET http://localhost:8080/recipe/2 + +### +// ANIME NOOOOOODLE +GET http://localhost:8080/recipe/4 \ No newline at end of file diff --git a/src/test/kotlin/com/wyattjmiller/user.http b/src/test/kotlin/com/wyattjmiller/user.http new file mode 100644 index 0000000..6cd1286 --- /dev/null +++ b/src/test/kotlin/com/wyattjmiller/user.http @@ -0,0 +1,2 @@ +// Get user +GET http://localhost:8080/user/1 \ No newline at end of file