Skip to content
Merged

v1.0 #15

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ out/
/.nb-gradle/

### VS Code ###
.vscode/
.vscode/
/config/
27 changes: 24 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ val kotlin_version: String by project
val logback_version: String by project
val kmongo_version: String by project
val koin_version: String by project
val firebase_version: String by project
val prometheus_version: String by project

plugins {
application
Expand All @@ -29,12 +31,15 @@ dependencies {
implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktor_version")
implementation("io.ktor:ktor-server-core-jvm:$ktor_version")
implementation("io.ktor:ktor-serialization-gson-jvm:$ktor_version")
implementation("io.ktor:ktor-server-metrics-jvm:$ktor_version")
implementation("io.ktor:ktor-server-call-logging-jvm:$ktor_version")
implementation("io.ktor:ktor-server-cors-jvm:$ktor_version")
implementation("io.ktor:ktor-server-auth-jvm:$ktor_version")
implementation("io.ktor:ktor-server-auth-jwt-jvm:$ktor_version")
implementation("io.ktor:ktor-server-netty-jvm:$ktor_version")
implementation("io.ktor:ktor-server-rate-limit:$ktor_version")
implementation("io.ktor:ktor-server-metrics-jvm:$ktor_version")
implementation("io.ktor:ktor-server-metrics-micrometer:$ktor_version")
implementation("io.micrometer:micrometer-registry-prometheus:$prometheus_version")
testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")

Expand All @@ -47,17 +52,33 @@ dependencies {
// Koin
implementation("io.insert-koin:koin-ktor:$koin_version")
implementation("io.insert-koin:koin-logger-slf4j:$koin_version")

// Firebase
implementation("com.google.firebase:firebase-admin:$firebase_version")

// BCrypt
implementation("org.mindrot:jbcrypt:0.4")

// Google auth
implementation("com.google.api-client:google-api-client:2.1.1")

// JavaMail
implementation("javax.mail:javax.mail-api:1.6.2")
implementation("com.sun.mail:javax.mail:1.6.2")

// TOTP
implementation("dev.turingcomplete:kotlin-onetimepassword:2.4.0")
}

ktor {
fatJar {
archiveFileName.set("fat.jar")
archiveFileName.set("${rootProject.name}-${rootProject.version}.jar")
}

docker {
jreVersion.set(io.ktor.plugin.features.JreVersion.JRE_17)
localImageName.set(rootProject.name)
imageTag.set("0.0.1-preview")
imageTag.set("1.0-preview")

externalRegistry.set(
io.ktor.plugin.features.DockerImageRegistry.dockerHub(
Expand Down
4 changes: 3 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
ktor_version=2.1.3
ktor_version=2.2.4
kotlin_version=1.7.21
logback_version=1.4.4
kmongo_version=4.7.2
koin_version=3.2.2
firebase_version=9.1.1
prometheus_version=1.10.3
kotlin.code.style=official
17 changes: 14 additions & 3 deletions src/main/kotlin/es/wokis/Application.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
package es.wokis

import com.typesafe.config.ConfigFactory
import es.wokis.plugins.*
import io.ktor.server.application.*
import io.ktor.server.config.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import es.wokis.plugins.*

fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module)
.start(wait = true)
embeddedServer(Netty, environment = applicationEngineEnvironment {
config = HoconApplicationConfig(ConfigFactory.load("application.conf"))

connector {
host = config.host
port = config.port
}
}).start(wait = true)
}

fun Application.module() {
initConfig()
configureFirebase()
configureKoin()
configureSerialization()
configureMonitoring()
configureHTTP()
configureSecurity()
configureRateLimit()
configureRouting()
}
37 changes: 37 additions & 0 deletions src/main/kotlin/es/wokis/data/bo/invoice/InvoiceBO.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package es.wokis.data.bo.invoice

import es.wokis.data.constants.ServerConstants.EMPTY_TEXT
import java.util.*

data class InvoiceBO(
val id: String? = null,
val idApp: Long = 0L,
val title: String = EMPTY_TEXT,
val description: String = EMPTY_TEXT,
val quantity: Int = 0,
val date: Date = Date(),
val type: InvoiceType,
val userId: String,
val category: CategoryBO? = null,
val reactions: List<ReactionBO> = emptyList()
)

data class CategoryBO(
val id: Long,
val title: String,
val color: String,
)

data class ReactionBO(
val id: Long,
val unicode: String
)

enum class InvoiceType(val key: String) {
DEPOSIT("DEPOSIT"),
EXPENSE("EXPENSE");

companion object {
fun getFromKey(key: String) = values().find { it.key == key } ?: DEPOSIT
}
}
10 changes: 10 additions & 0 deletions src/main/kotlin/es/wokis/data/bo/recover/RecoverBO.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package es.wokis.data.bo.recover

import java.util.*

data class RecoverBO(
val id: String? = null,
val email: String,
val verificationToken: String,
val timeStamp: Date = Date()
)
5 changes: 5 additions & 0 deletions src/main/kotlin/es/wokis/data/bo/response/AcknowledgeBO.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package es.wokis.data.bo.response

data class AcknowledgeBO(
val acknowledge: Boolean
)
7 changes: 7 additions & 0 deletions src/main/kotlin/es/wokis/data/bo/user/TOTPResponseBO.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package es.wokis.data.bo.user

data class TOTPResponseBO(
val encodedSecret: String,
val totpUrl: String,
val words: List<String>
)
6 changes: 6 additions & 0 deletions src/main/kotlin/es/wokis/data/bo/user/UpdateUserBO.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package es.wokis.data.bo.user

data class UpdateUserBO(
val username: String?,
val email: String?,
)
71 changes: 71 additions & 0 deletions src/main/kotlin/es/wokis/data/bo/user/UserBO.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package es.wokis.data.bo.user

import es.wokis.data.constants.ServerConstants.DEFAULT_LANG
import es.wokis.data.constants.ServerConstants.EMPTY_TEXT
import io.ktor.server.auth.*
import java.util.*

data class UserBO(
val id: String? = null,
val username: String,
val email: String,
val password: String,
val image: String = EMPTY_TEXT,
val lang: String = DEFAULT_LANG,
val createdOn: Long = Date().time,
val totpEncodedSecret: ByteArray? = null,
val currentSession: String? = null,
val emailVerified: Boolean = false,
val loginWithGoogle: Boolean = false,
val sessions: List<String> = emptyList(),
val badges: List<BadgeBO> = emptyList(),
val devices: List<String> = emptyList(),
val recoverWords: List<String> = emptyList()
) : Principal {

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as UserBO

if (id != other.id) return false
if (username != other.username) return false
if (email != other.email) return false
if (password != other.password) return false
if (image != other.image) return false
if (lang != other.lang) return false
if (createdOn != other.createdOn) return false
if (totpEncodedSecret != null) {
if (other.totpEncodedSecret == null) return false
if (!totpEncodedSecret.contentEquals(other.totpEncodedSecret)) return false
} else if (other.totpEncodedSecret != null) return false
if (emailVerified != other.emailVerified) return false
if (sessions != other.sessions) return false
if (badges != other.badges) return false
if (devices != other.devices) return false

return true
}

override fun hashCode(): Int {
var result = id?.hashCode() ?: 0
result = 31 * result + username.hashCode()
result = 31 * result + email.hashCode()
result = 31 * result + password.hashCode()
result = 31 * result + image.hashCode()
result = 31 * result + lang.hashCode()
result = 31 * result + createdOn.hashCode()
result = 31 * result + (totpEncodedSecret?.contentHashCode() ?: 0)
result = 31 * result + emailVerified.hashCode()
result = 31 * result + sessions.hashCode()
result = 31 * result + badges.hashCode()
result = 31 * result + devices.hashCode()
return result
}
}

data class BadgeBO(
val id: Int,
val color: String
)
10 changes: 10 additions & 0 deletions src/main/kotlin/es/wokis/data/bo/verification/VerificationBO.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package es.wokis.data.bo.verification

import java.util.*

data class VerificationBO(
val id: String? = null,
val email: String,
val verificationToken: String,
val timeStamp: Date = Date()
)
8 changes: 8 additions & 0 deletions src/main/kotlin/es/wokis/data/constants/ServerConstants.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package es.wokis.data.constants

object ServerConstants {
const val EMPTY_TEXT = ""
const val DEFAULT_LANG = "en"
const val LANG_ES = "es"
const val MAX_OG_DATE = "01/06/2023"
}
34 changes: 34 additions & 0 deletions src/main/kotlin/es/wokis/data/database/AppDataBase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package es.wokis.data.database

import com.mongodb.ConnectionString
import com.mongodb.MongoClientSettings
import com.mongodb.MongoCredential
import es.wokis.data.dbo.invoice.InvoiceDBO
import es.wokis.data.dbo.recover.RecoverDBO
import es.wokis.data.dbo.user.UserDBO
import es.wokis.data.dbo.verification.VerificationDBO
import es.wokis.plugins.config
import org.litote.kmongo.KMongo
import org.litote.kmongo.getCollection

class AppDataBase {
private val username = config.getString("db.user")
private val password = config.getString("db.password")
private val dataBaseUrl = "$MONGODB_PREFIX$username:$password@${config.getString("db.ip")}:${config.getString("db.port")}"
private val databaseName = config.getString("db.databaseName")
private val client = KMongo.createClient(
MongoClientSettings.builder()
.credential(MongoCredential.createCredential(username, databaseName, password.toCharArray()))
.applyConnectionString(ConnectionString(dataBaseUrl)).build()
)

val database by lazy { client.getDatabase(databaseName) }
val usersCollection by lazy { database.getCollection<UserDBO>("users") }
val invoicesCollection by lazy { database.getCollection<InvoiceDBO>("invoices") }
val verificationCollection by lazy { database.getCollection<VerificationDBO>("verification") }
val recoverCollection by lazy { database.getCollection<RecoverDBO>("recover") }

companion object {
private const val MONGODB_PREFIX = "mongodb://"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package es.wokis.data.datasource.invoice

import com.mongodb.client.MongoCollection
import es.wokis.data.bo.invoice.InvoiceBO
import es.wokis.data.dbo.invoice.InvoiceDBO
import es.wokis.data.mapper.invoice.toBO
import es.wokis.data.mapper.invoice.toDBO
import org.bson.types.ObjectId
import org.litote.kmongo.Id
import org.litote.kmongo.eq
import org.litote.kmongo.id.toId
import org.litote.kmongo.updateOne
import java.util.regex.Pattern

interface InvoiceLocalDataSource {
suspend fun getInvoicesOfUser(id: String): List<InvoiceBO>
suspend fun addInvoices(id: String, invoices: List<InvoiceBO>): Boolean
suspend fun updateInvoices(id: String, invoices: List<InvoiceBO>): Boolean
suspend fun deleteInvoices(id: String, invoicesIds: List<String>): Boolean
}

class InvoiceLocalDataSourceImpl(private val invoiceCollection: MongoCollection<InvoiceDBO>) : InvoiceLocalDataSource {

private val getCaseInsensitive: (element: String) -> Pattern = {
Pattern.compile(it, Pattern.CASE_INSENSITIVE)
}

override suspend fun getInvoicesOfUser(id: String): List<InvoiceBO> =
invoiceCollection.find(InvoiceDBO::userId eq id).map {
it.toBO()
}.toList()

override suspend fun addInvoices(id: String, invoices: List<InvoiceBO>): Boolean = try {
val localInvoices = getInvoicesOfUser(id)
var lastUpdatedId = localInvoices.map { it.idApp }.maxByOrNull { it } ?: 0
invoices.map { it.copy(userId = id) }.toDBO().map {
invoiceCollection.insertOne(
if (localInvoices.any { localInvoice -> localInvoice.idApp == it.idApp }) {
lastUpdatedId++
it.copy(idApp = lastUpdatedId)

} else {
it
}
).wasAcknowledged()
}.all { it }

} catch (e: Throwable) {
println(e.stackTraceToString())
false
}

override suspend fun updateInvoices(id: String, invoices: List<InvoiceBO>): Boolean = try {
invoices.map { it.copy(userId = id) }.toDBO().map {
invoiceCollection.updateOne(InvoiceDBO::id eq it.id, it).wasAcknowledged()
}.all { it }

} catch (e: Throwable) {
println(e.stackTraceToString())
false
}

override suspend fun deleteInvoices(id: String, invoicesIds: List<String>): Boolean = try {
invoicesIds.map {
val bsonId: Id<InvoiceDBO> = ObjectId(it).toId()
invoiceCollection.deleteOne(InvoiceDBO::id eq bsonId).wasAcknowledged()
}.all { it }

} catch (e: Throwable) {
println(e.stackTraceToString())
false
}

}
Loading