code dump
This commit is contained in:
parent
f8165fccbd
commit
562fef9d92
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
.gradle
|
.gradle
|
||||||
build/
|
build/
|
||||||
|
gen/
|
||||||
!gradle/wrapper/gradle-wrapper.jar
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
!**/src/main/**/build/
|
!**/src/main/**/build/
|
||||||
!**/src/test/**/build/
|
!**/src/test/**/build/
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
val ktor_version: String by project
|
val ktor_version: String by project
|
||||||
val kotlin_version: String by project
|
val kotlin_version: String by project
|
||||||
val logback_version: String by project
|
val logback_version: String by project
|
||||||
|
val ktorm_version: String by project
|
||||||
|
val postgresql_driver_version: String by project
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
application
|
application
|
||||||
@ -27,10 +29,13 @@ dependencies {
|
|||||||
implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktor_version")
|
implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktor_version")
|
||||||
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:$ktor_version")
|
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:$ktor_version")
|
||||||
implementation("io.ktor:ktor-server-netty-jvm:$ktor_version")
|
implementation("io.ktor:ktor-server-netty-jvm:$ktor_version")
|
||||||
implementation("org.ktorm:ktorm-core:3.4.1")
|
implementation("io.ktor:ktor-server-auth:$ktor_version")
|
||||||
implementation("org.ktorm:ktorm-support-postgresql:3.4.1")
|
implementation("io.ktor:ktor-server-auth-jwt:$ktor_version")
|
||||||
implementation("org.postgresql:postgresql:42.2.2")
|
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("ch.qos.logback:logback-classic:$logback_version")
|
||||||
|
implementation("org.mindrot:jbcrypt:0.4")
|
||||||
testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version")
|
testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version")
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
|
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
|
||||||
}
|
}
|
@ -2,8 +2,9 @@ package com.wyattjmiller
|
|||||||
|
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import com.wyattjmiller.plugins.*
|
import com.wyattjmiller.plugins.*
|
||||||
|
import com.wyattjmiller.routes.AuthorRoute.Companion.authorRoutes
|
||||||
import com.wyattjmiller.routes.RecipeRoute.Companion.recipeRoutes
|
import com.wyattjmiller.routes.RecipeRoute.Companion.recipeRoutes
|
||||||
import io.ktor.server.config.*
|
import com.wyattjmiller.routes.UserRoute.Companion.userRoutes
|
||||||
|
|
||||||
fun main(args: Array<String>): Unit =
|
fun main(args: Array<String>): Unit =
|
||||||
io.ktor.server.netty.EngineMain.main(args)
|
io.ktor.server.netty.EngineMain.main(args)
|
||||||
@ -12,8 +13,7 @@ fun main(args: Array<String>): Unit =
|
|||||||
fun Application.module() {
|
fun Application.module() {
|
||||||
//val config = this.environment.config
|
//val config = this.environment.config
|
||||||
|
|
||||||
configureRouting()
|
configureAuthentication()
|
||||||
configureSerialization()
|
configureSerialization()
|
||||||
|
configureRouting()
|
||||||
recipeRoutes()
|
|
||||||
}
|
}
|
||||||
|
31
src/main/kotlin/com/wyattjmiller/auth/JwtConfig.kt
Normal file
31
src/main/kotlin/com/wyattjmiller/auth/JwtConfig.kt
Normal file
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,11 @@
|
|||||||
package com.wyattjmiller.data
|
package com.wyattjmiller.data
|
||||||
|
|
||||||
import io.ktor.server.config.*
|
|
||||||
import org.ktorm.database.Database
|
import org.ktorm.database.Database
|
||||||
|
|
||||||
class DatabaseManager() {
|
class DatabaseManager() {
|
||||||
var database: Database
|
var database: Database = Database.connect("jdbc:postgresql://10.10.10.21:5432/recipefolio",
|
||||||
|
"org.postgresql.Driver",
|
||||||
init {
|
user = "wyatt",
|
||||||
database = Database.connect("jdbc:postgresql://10.10.10.21:5432/recipefolio",
|
password = "wjm"
|
||||||
"org.postgresql.Driver",
|
)
|
||||||
user = "wyatt",
|
|
||||||
password = "wjm"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,22 +1,42 @@
|
|||||||
package com.wyattjmiller.data
|
package com.wyattjmiller.data
|
||||||
|
|
||||||
import org.ktorm.entity.Entity
|
import org.ktorm.entity.Entity
|
||||||
import org.ktorm.schema.Table
|
import org.ktorm.schema.*
|
||||||
import org.ktorm.schema.int
|
import java.time.LocalDateTime
|
||||||
import org.ktorm.schema.varchar
|
|
||||||
|
|
||||||
object DbRecipeTable : Table<DbRecipeEntity>("recipe") {
|
object DbRecipeTable : Table<Recipes>("recipe") {
|
||||||
val id = int("id").primaryKey().bindTo { it.id }
|
val id = int("id").primaryKey().bindTo { it.id }
|
||||||
val name = varchar("name").bindTo { it.name }
|
val name = varchar("name").bindTo { it.name }
|
||||||
val desc = varchar("description").bindTo { it.desc }
|
val desc = varchar("description").bindTo { it.desc }
|
||||||
val ingredients = varchar("ingredients").bindTo { it.ingredients }
|
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<DbRecipeEntity> {
|
object DbAuthorTable : Table<Authors>("author") {
|
||||||
companion object : Entity.Factory<DbRecipeEntity>()
|
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<Recipes> {
|
||||||
|
companion object : Entity.Factory<Recipes>()
|
||||||
|
|
||||||
|
val id: Int?
|
||||||
val name: String
|
val name: String
|
||||||
val desc: String
|
val desc: String
|
||||||
val ingredients: String
|
val ingredients: String
|
||||||
}
|
val author: Authors
|
||||||
|
val createdTimestamp: LocalDateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Authors : Entity<Authors> {
|
||||||
|
companion object : Entity.Factory<Authors>()
|
||||||
|
|
||||||
|
val id: Int
|
||||||
|
val authorName: String
|
||||||
|
val user: Users?
|
||||||
|
val createdTimestamp: LocalDateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
33
src/main/kotlin/com/wyattjmiller/data/UserEntities.kt
Normal file
33
src/main/kotlin/com/wyattjmiller/data/UserEntities.kt
Normal file
@ -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<Users>("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<Users> {
|
||||||
|
companion object : Entity.Factory<Users>()
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
21
src/main/kotlin/com/wyattjmiller/entities/Author.kt
Normal file
21
src/main/kotlin/com/wyattjmiller/entities/Author.kt
Normal file
@ -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,
|
||||||
|
)
|
@ -1,11 +1,24 @@
|
|||||||
package com.wyattjmiller.entities
|
package com.wyattjmiller.entities
|
||||||
|
|
||||||
|
import com.wyattjmiller.data.Authors
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("Recipe")
|
||||||
data class Recipe(
|
data class Recipe(
|
||||||
val id: Int,
|
val id: Int?,
|
||||||
val name: String,
|
val name: String,
|
||||||
val desc: String,
|
val desc: String,
|
||||||
val ingredients: 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,
|
||||||
)
|
)
|
||||||
|
@ -1,12 +1,32 @@
|
|||||||
package com.wyattjmiller.entities
|
package com.wyattjmiller.entities
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.sql.Timestamp
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class User(
|
data class User(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val firstName: String,
|
val firstName: String,
|
||||||
val lastName: String,
|
val lastName: String,
|
||||||
|
val username: String,
|
||||||
val emailAddress: String,
|
val emailAddress: String,
|
||||||
val password: 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
|
||||||
|
)
|
15
src/main/kotlin/com/wyattjmiller/openapi.json
Normal file
15
src/main/kotlin/com/wyattjmiller/openapi.json
Normal file
@ -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": {
|
||||||
|
}
|
||||||
|
}
|
30
src/main/kotlin/com/wyattjmiller/plugins/Authentication.kt
Normal file
30
src/main/kotlin/com/wyattjmiller/plugins/Authentication.kt
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -1,10 +1,15 @@
|
|||||||
package com.wyattjmiller.plugins
|
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.routing.*
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
|
|
||||||
fun Application.configureRouting() {
|
fun Application.configureRouting() {
|
||||||
routing {
|
routing {
|
||||||
|
this@configureRouting.recipeRoutes()
|
||||||
|
this@configureRouting.authorRoutes()
|
||||||
|
this@configureRouting.userRoutes()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,13 @@ package com.wyattjmiller.plugins
|
|||||||
import io.ktor.serialization.kotlinx.json.*
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.plugins.contentnegotiation.*
|
import io.ktor.server.plugins.contentnegotiation.*
|
||||||
import io.ktor.server.response.*
|
import kotlinx.serialization.json.Json
|
||||||
import io.ktor.server.routing.*
|
|
||||||
|
|
||||||
fun Application.configureSerialization() {
|
fun Application.configureSerialization() {
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json()
|
json( Json {
|
||||||
}
|
explicitNulls = true
|
||||||
|
prettyPrint = true
|
||||||
routing {
|
})
|
||||||
get("/json/kotlinx-serialization") {
|
|
||||||
call.respond(mapOf("hello" to "world"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
66
src/main/kotlin/com/wyattjmiller/repositories/AuthorDao.kt
Normal file
66
src/main/kotlin/com/wyattjmiller/repositories/AuthorDao.kt
Normal file
@ -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<Author> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.wyattjmiller.repositories
|
||||||
|
|
||||||
|
import com.wyattjmiller.entities.Author
|
||||||
|
import com.wyattjmiller.entities.AuthorDraft
|
||||||
|
|
||||||
|
interface AuthorRepository {
|
||||||
|
fun getAllAuthors() : List<Author>
|
||||||
|
fun getAuthor(id: Int) : Author?
|
||||||
|
fun createAuthor(author: AuthorDraft)
|
||||||
|
fun updateAuthor(id: Int, author: AuthorDraft) : Boolean
|
||||||
|
fun deleteAuthor(id: Int) : Boolean
|
||||||
|
}
|
@ -3,8 +3,13 @@ package com.wyattjmiller.repositories
|
|||||||
import com.wyattjmiller.data.DatabaseManager
|
import com.wyattjmiller.data.DatabaseManager
|
||||||
import com.wyattjmiller.data.DbRecipeTable
|
import com.wyattjmiller.data.DbRecipeTable
|
||||||
import com.wyattjmiller.entities.Recipe
|
import com.wyattjmiller.entities.Recipe
|
||||||
|
import com.wyattjmiller.entities.RecipeDraft
|
||||||
|
import org.ktorm.dsl.delete
|
||||||
import org.ktorm.dsl.eq
|
import org.ktorm.dsl.eq
|
||||||
|
import org.ktorm.dsl.insertAndGenerateKey
|
||||||
|
import org.ktorm.dsl.update
|
||||||
import org.ktorm.entity.*
|
import org.ktorm.entity.*
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
class RecipeDao : RecipeRepository {
|
class RecipeDao : RecipeRepository {
|
||||||
private val db = DatabaseManager()
|
private val db = DatabaseManager()
|
||||||
@ -13,25 +18,52 @@ class RecipeDao : RecipeRepository {
|
|||||||
return db.database
|
return db.database
|
||||||
.sequenceOf(DbRecipeTable)
|
.sequenceOf(DbRecipeTable)
|
||||||
.toList()
|
.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 {
|
override fun getRecipe(id: Int) : Recipe {
|
||||||
return db.database
|
return db.database
|
||||||
.sequenceOf(DbRecipeTable)
|
.sequenceOf(DbRecipeTable)
|
||||||
.firstOrNull { it.id eq id }
|
.firstOrNull {
|
||||||
.let { Recipe(it!!.id, it.name, it.desc, it.ingredients) }
|
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) {
|
override fun createRecipe(recipe: RecipeDraft) : Recipe {
|
||||||
TODO("Not yet implemented")
|
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 {
|
override fun updateRecipe(id: Int, recipe: RecipeDraft) : Boolean {
|
||||||
TODO("Not yet implemented")
|
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) {
|
override fun deleteRecipe(id: Int) : Boolean {
|
||||||
TODO("Not yet implemented")
|
val deletedRows = db.database.delete(DbRecipeTable) {
|
||||||
|
it.id eq id
|
||||||
|
}
|
||||||
|
|
||||||
|
return deletedRows > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,12 @@
|
|||||||
package com.wyattjmiller.repositories
|
package com.wyattjmiller.repositories
|
||||||
|
|
||||||
import com.wyattjmiller.entities.Recipe
|
import com.wyattjmiller.entities.Recipe
|
||||||
|
import com.wyattjmiller.entities.RecipeDraft
|
||||||
|
|
||||||
interface RecipeRepository {
|
interface RecipeRepository {
|
||||||
fun getAllRecipies() : List<Recipe>
|
fun getAllRecipies() : List<Recipe>
|
||||||
fun getRecipe(id: Int) : Recipe?
|
fun getRecipe(id: Int) : Recipe?
|
||||||
fun createRecipe(recipe: Recipe)
|
fun createRecipe(recipe: RecipeDraft) : Recipe
|
||||||
fun updateRecipe(id: Int, recipe: Recipe): Boolean
|
fun updateRecipe(id: Int, recipe: RecipeDraft) : Boolean
|
||||||
fun deleteRecipe(id: Int)
|
fun deleteRecipe(id: Int) : Boolean
|
||||||
}
|
}
|
75
src/main/kotlin/com/wyattjmiller/repositories/UserDao.kt
Normal file
75
src/main/kotlin/com/wyattjmiller/repositories/UserDao.kt
Normal file
@ -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")
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
69
src/main/kotlin/com/wyattjmiller/routes/AuthorRoute.kt
Normal file
69
src/main/kotlin/com/wyattjmiller/routes/AuthorRoute.kt
Normal file
@ -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<AuthorDraft>()
|
||||||
|
val res = author.createAuthor(req)
|
||||||
|
call.respond(HttpStatusCode.Created)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Route.updateAuthor() {
|
||||||
|
put("/author/{id?}") {
|
||||||
|
val req = call.receive<AuthorDraft>()
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
package com.wyattjmiller.routes
|
package com.wyattjmiller.routes
|
||||||
|
|
||||||
|
import com.wyattjmiller.entities.RecipeDraft
|
||||||
import com.wyattjmiller.repositories.RecipeDao
|
import com.wyattjmiller.repositories.RecipeDao
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.request.*
|
||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
@ -30,10 +32,36 @@ class RecipeRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Route.createRecipe() {
|
||||||
|
post("/recipe") {
|
||||||
|
val req = call.receive<RecipeDraft>()
|
||||||
|
val res = rec.createRecipe(req)
|
||||||
|
call.respond(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Route.updateRecipe() {
|
||||||
|
put("/recipe/{id?}") {
|
||||||
|
val req = call.receive<RecipeDraft>()
|
||||||
|
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() {
|
fun Application.recipeRoutes() {
|
||||||
routing {
|
routing {
|
||||||
getAllRecipes()
|
getAllRecipes()
|
||||||
getRecipe()
|
getRecipe()
|
||||||
|
createRecipe()
|
||||||
|
updateRecipe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
99
src/main/kotlin/com/wyattjmiller/routes/UserRoute.kt
Normal file
99
src/main/kotlin/com/wyattjmiller/routes/UserRoute.kt
Normal file
@ -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<UserLogin>()
|
||||||
|
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<UserDraft>()
|
||||||
|
val res = user.createUser(req)
|
||||||
|
call.respond(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Route.updateUser() {
|
||||||
|
authenticate {
|
||||||
|
put("/user/{id?}") {
|
||||||
|
val req = call.receive<UserDraft>()
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,3 +17,8 @@ storage {
|
|||||||
dbUser = "wyatt"
|
dbUser = "wyatt"
|
||||||
dbPasswd = "wjm"
|
dbPasswd = "wjm"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secret = "!$#terces!$#"
|
||||||
|
issuer = "http://0.0.0.0:8080/"
|
||||||
|
audience = "http://0.0.0.0:8080/hello"
|
||||||
|
realm = "Access to 'hello'"
|
||||||
|
25
src/test/kotlin/com/wyattjmiller/author.http
Normal file
25
src/test/kotlin/com/wyattjmiller/author.http
Normal file
@ -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"
|
||||||
|
}
|
26
src/test/kotlin/com/wyattjmiller/login.http
Normal file
26
src/test/kotlin/com/wyattjmiller/login.http
Normal file
@ -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"
|
||||||
|
}
|
10
src/test/kotlin/com/wyattjmiller/recipe.http
Normal file
10
src/test/kotlin/com/wyattjmiller/recipe.http
Normal file
@ -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
|
2
src/test/kotlin/com/wyattjmiller/user.http
Normal file
2
src/test/kotlin/com/wyattjmiller/user.http
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// Get user
|
||||||
|
GET http://localhost:8080/user/1
|
Reference in New Issue
Block a user