diff --git a/integrations/opentelemetry/build.gradle.kts b/integrations/opentelemetry/build.gradle.kts index c864765bd..6437e05f0 100644 --- a/integrations/opentelemetry/build.gradle.kts +++ b/integrations/opentelemetry/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.arrow.gradle.publish) alias(libs.plugins.semver.gradle) alias(libs.plugins.detekt) + alias(libs.plugins.spotless) } dependencies { detektPlugins(project(":detekt-rules")) } @@ -39,6 +40,15 @@ dependencies { testRuntimeOnly(libs.kotest.junit5) } +spotless { + kotlin { + target("**/*.kt") + ktfmt().googleStyle().configure { + it.setRemoveUnusedImport(true) + } + } +} + tasks { withType().configureEach { dependsOn(":detekt-rules:assemble") diff --git a/integrations/opentelemetry/src/main/kotlin/com/xebia/functional/xef/opentelemetry/OpenTelemetryConfig.kt b/integrations/opentelemetry/src/main/kotlin/com/xebia/functional/xef/opentelemetry/OpenTelemetryConfig.kt index 836811c50..3a650423d 100644 --- a/integrations/opentelemetry/src/main/kotlin/com/xebia/functional/xef/opentelemetry/OpenTelemetryConfig.kt +++ b/integrations/opentelemetry/src/main/kotlin/com/xebia/functional/xef/opentelemetry/OpenTelemetryConfig.kt @@ -11,40 +11,39 @@ import io.opentelemetry.sdk.trace.export.BatchSpanProcessor import java.util.concurrent.TimeUnit data class OpenTelemetryConfig( - val endpointConfig: String, - val defaultScopeName: String, - val serviceName: String + val endpointConfig: String, + val defaultScopeName: String, + val serviceName: String ) { - fun newInstance(): OpenTelemetry { - val jaegerOtlpExporter: OtlpGrpcSpanExporter = OtlpGrpcSpanExporter.builder() - .setEndpoint(endpointConfig) - .setTimeout(30, TimeUnit.SECONDS) - .build() - - val serviceNameResource: Resource = - Resource.create(Attributes.of(AttributeKey.stringKey("service.name"), serviceName)) - - val tracerProvider = SdkTracerProvider.builder() - .addSpanProcessor(BatchSpanProcessor.builder(jaegerOtlpExporter).build()) - .setResource(Resource.getDefault().merge(serviceNameResource)) - .build() - - - val openTelemetry = OpenTelemetrySdk - .builder() - .setTracerProvider(tracerProvider) - .build() - - Runtime.getRuntime().addShutdownHook(Thread { tracerProvider.close() }) - return openTelemetry - } - - companion object { - val DEFAULT = OpenTelemetryConfig( - endpointConfig = "http://localhost:4317", - defaultScopeName = "io.xef", - serviceName = "xef" - ) - } + fun newInstance(): OpenTelemetry { + val jaegerOtlpExporter: OtlpGrpcSpanExporter = + OtlpGrpcSpanExporter.builder() + .setEndpoint(endpointConfig) + .setTimeout(30, TimeUnit.SECONDS) + .build() + + val serviceNameResource: Resource = + Resource.create(Attributes.of(AttributeKey.stringKey("service.name"), serviceName)) + + val tracerProvider = + SdkTracerProvider.builder() + .addSpanProcessor(BatchSpanProcessor.builder(jaegerOtlpExporter).build()) + .setResource(Resource.getDefault().merge(serviceNameResource)) + .build() + + val openTelemetry = OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build() + + Runtime.getRuntime().addShutdownHook(Thread { tracerProvider.close() }) + return openTelemetry + } + + companion object { + val DEFAULT = + OpenTelemetryConfig( + endpointConfig = "http://localhost:4317", + defaultScopeName = "io.xef", + serviceName = "xef" + ) + } } diff --git a/integrations/opentelemetry/src/main/kotlin/com/xebia/functional/xef/opentelemetry/OpenTelemetryMetric.kt b/integrations/opentelemetry/src/main/kotlin/com/xebia/functional/xef/opentelemetry/OpenTelemetryMetric.kt index 897323347..29756c820 100644 --- a/integrations/opentelemetry/src/main/kotlin/com/xebia/functional/xef/opentelemetry/OpenTelemetryMetric.kt +++ b/integrations/opentelemetry/src/main/kotlin/com/xebia/functional/xef/opentelemetry/OpenTelemetryMetric.kt @@ -8,61 +8,60 @@ import io.opentelemetry.api.trace.Span import io.opentelemetry.api.trace.Tracer import io.opentelemetry.context.Context -class OpenTelemetryMetric( - private val config: OpenTelemetryConfig = OpenTelemetryConfig.DEFAULT -) : Metric { +class OpenTelemetryMetric(private val config: OpenTelemetryConfig = OpenTelemetryConfig.DEFAULT) : + Metric { - private val conversations = mutableListOf>() + private val conversations = mutableListOf>() - private val openTelemetry = config.newInstance() + private val openTelemetry = config.newInstance() - override suspend fun promptSpan(conversation: Conversation, prompt: Prompt, block: suspend Metric.() -> A): A { - val cid = conversation.conversationId ?: return block() + override suspend fun promptSpan( + conversation: Conversation, + prompt: Prompt, + block: suspend Metric.() -> A + ): A { + val cid = conversation.conversationId ?: return block() - val parentContext = cid.getParentConversation() + val parentContext = cid.getParentConversation() - val span = getTracer() - .spanBuilder("Prompt: ${prompt.messages.lastOrNull()?.content ?: "empty"}") - .setParent(parentContext) - .startSpan() + val span = + getTracer() + .spanBuilder("Prompt: ${prompt.messages.lastOrNull()?.content ?: "empty"}") + .setParent(parentContext) + .startSpan() - return try { - val output = block() - span.makeCurrent().use { - span.setAttribute("number-of-messages", prompt.messages.count().toString()) - span.setAttribute("last-message", prompt.messages.lastOrNull()?.content ?: "empty") - } - output - } finally { - span.end() - } + return try { + val output = block() + span.makeCurrent().use { + span.setAttribute("number-of-messages", prompt.messages.count().toString()) + span.setAttribute("last-message", prompt.messages.lastOrNull()?.content ?: "empty") + } + output + } finally { + span.end() } + } - override fun log(conversation: Conversation, message: String) { - val cid = conversation.conversationId ?: return + override fun log(conversation: Conversation, message: String) { + val cid = conversation.conversationId ?: return - val parentContext = cid.getParentConversation() + val parentContext = cid.getParentConversation() - val span: Span = getTracer().spanBuilder(message) - .setParent(parentContext) - .startSpan() - span.end() - } - - private fun ConversationId.getParentConversation(): Context { - val parent = conversations.find { it.first == this }?.second - return if (parent == null) { - val newParent = getTracer() - .spanBuilder(value) - .startSpan() - newParent.end() - val newContext = Context.current().with(newParent) - conversations.add(this to newContext) - newContext - } else parent - } + val span: Span = getTracer().spanBuilder(message).setParent(parentContext).startSpan() + span.end() + } - private fun getTracer(scopeName: String? = null): Tracer = - openTelemetry.getTracer(scopeName ?: config.defaultScopeName) + private fun ConversationId.getParentConversation(): Context { + val parent = conversations.find { it.first == this }?.second + return if (parent == null) { + val newParent = getTracer().spanBuilder(value).startSpan() + newParent.end() + val newContext = Context.current().with(newParent) + conversations.add(this to newContext) + newContext + } else parent + } + private fun getTracer(scopeName: String? = null): Tracer = + openTelemetry.getTracer(scopeName ?: config.defaultScopeName) } diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 933be9d1c..6b7025df3 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.node.gradle) alias(libs.plugins.arrow.gradle.publish) alias(libs.plugins.semver.gradle) + alias(libs.plugins.spotless) } repositories { @@ -68,6 +69,15 @@ dependencies { testRuntimeOnly(libs.kotest.junit5) } +spotless { + kotlin { + target("**/*.kt") + ktfmt().googleStyle().configure { + it.setRemoveUnusedImport(true) + } + } +} + tasks.getByName("processResources") { dependsOn(projects.xefGpt4all.dependencyProject.tasks.getByName("jvmProcessResources")) from("${projects.xefGpt4all.dependencyProject.buildDir}/processedResources/jvm/main") diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/Server.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/Server.kt index 990935464..c2a49d7bd 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/Server.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/Server.kt @@ -14,6 +14,7 @@ import com.xebia.functional.xef.server.services.RepositoryService import io.ktor.client.* import io.ktor.client.engine.cio.* import io.ktor.client.plugins.auth.* +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation as ClientContentNegotiation import io.ktor.client.plugins.logging.* import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* @@ -27,63 +28,59 @@ import io.ktor.server.routing.* import kotlinx.coroutines.awaitCancellation import org.jetbrains.exposed.sql.Database import org.slf4j.LoggerFactory -import io.ktor.client.plugins.contentnegotiation.ContentNegotiation as ClientContentNegotiation object Server { - @JvmStatic - fun main(args: Array) = SuspendApp { - resourceScope { - val config = ConfigFactory.load("database.conf").resolve() - val xefDBConfig = XefDatabaseConfig.load("xef", config) - Migrate.migrate(xefDBConfig) - - val logger = LoggerFactory.getLogger("xef-server") + @JvmStatic + fun main(args: Array) = SuspendApp { + resourceScope { + val config = ConfigFactory.load("database.conf").resolve() + val xefDBConfig = XefDatabaseConfig.load("xef", config) + Migrate.migrate(xefDBConfig) - val hikariDataSourceXefDB = RepositoryService.getHikariDataSource( - xefDBConfig.getUrl(), - xefDBConfig.user, - xefDBConfig.password - ) - Database.connect(hikariDataSourceXefDB) - val vectorStoreConfig = XefVectorStoreConfig.load("xef-vector-store", config) - val vectorStoreService = vectorStoreConfig.getVectorStoreService(config, logger) - vectorStoreService.addCollection() + val logger = LoggerFactory.getLogger("xef-server") + val hikariDataSourceXefDB = + RepositoryService.getHikariDataSource( + xefDBConfig.getUrl(), + xefDBConfig.user, + xefDBConfig.password + ) + Database.connect(hikariDataSourceXefDB) + val vectorStoreConfig = XefVectorStoreConfig.load("xef-vector-store", config) + val vectorStoreService = vectorStoreConfig.getVectorStoreService(config, logger) + vectorStoreService.addCollection() - val ktorClient = HttpClient(CIO) { - engine { - requestTimeout = 0 // disabled - } - install(Auth) - install(Logging) { - level = LogLevel.INFO - } - install(ClientContentNegotiation) - } + val ktorClient = + HttpClient(CIO) { + engine { + requestTimeout = 0 // disabled + } + install(Auth) + install(Logging) { level = LogLevel.INFO } + install(ClientContentNegotiation) + } - server(factory = Netty, port = 8081, host = "0.0.0.0") { - install(CORS) { - allowNonSimpleContentTypes = true - HttpMethod.DefaultMethods.forEach { allowMethod(it) } - allowHeaders { true } - anyHost() - } - install(ContentNegotiation) { json() } - install(Resources) - install(Authentication) { - bearer("auth-bearer") { - authenticate { tokenCredential -> - UserIdPrincipal(tokenCredential.token) - } - } - } - exceptionsHandler() - routing { - xefRoutes(logger) - aiRoutes(ktorClient) - } - } - awaitCancellation() + server(factory = Netty, port = 8081, host = "0.0.0.0") { + install(CORS) { + allowNonSimpleContentTypes = true + HttpMethod.DefaultMethods.forEach { allowMethod(it) } + allowHeaders { true } + anyHost() + } + install(ContentNegotiation) { json() } + install(Resources) + install(Authentication) { + bearer("auth-bearer") { + authenticate { tokenCredential -> UserIdPrincipal(tokenCredential.token) } + } + } + exceptionsHandler() + routing { + xefRoutes(logger) + aiRoutes(ktorClient) } + } + awaitCancellation() } + } } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/Web.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/Web.kt index d514ba336..8355f2362 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/Web.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/Web.kt @@ -7,14 +7,9 @@ import io.ktor.server.routing.* object WebApp { - @JvmStatic - fun main(args: Array) { - embeddedServer(Netty, port = 8080) { - routing { - singlePageApplication { - react("web/dist") - } - } - }.start(wait = true) - } -} \ No newline at end of file + @JvmStatic + fun main(args: Array) { + embeddedServer(Netty, port = 8080) { routing { singlePageApplication { react("web/dist") } } } + .start(wait = true) + } +} diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/db/psql/Migrate.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/db/psql/Migrate.kt index b5ef4f2a8..728adf227 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/db/psql/Migrate.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/db/psql/Migrate.kt @@ -7,24 +7,21 @@ import org.flywaydb.core.api.configuration.FluentConfiguration import org.flywaydb.core.api.output.MigrateResult object Migrate { - suspend fun migrate( - config: XefDatabaseConfig, - ): MigrateResult = - withContext(Dispatchers.IO) { - val url = config.getUrl() - val migration: FluentConfiguration = Flyway.configure() - .dataSource( - url, - config.user, - config.password - ) - .table(config.migrationsTable) - .locations(*config.migrationsLocations.toTypedArray()) - .loggers("slf4j") - val isValid = migration.ignoreMigrationPatterns("*:pending").load().validateWithResult() - if (!isValid.validationSuccessful) { - throw IllegalStateException("Migration validation failed: ${isValid.errorDetails}") - } - migration.load().migrate() - } + suspend fun migrate( + config: XefDatabaseConfig, + ): MigrateResult = + withContext(Dispatchers.IO) { + val url = config.getUrl() + val migration: FluentConfiguration = + Flyway.configure() + .dataSource(url, config.user, config.password) + .table(config.migrationsTable) + .locations(*config.migrationsLocations.toTypedArray()) + .loggers("slf4j") + val isValid = migration.ignoreMigrationPatterns("*:pending").load().validateWithResult() + if (!isValid.validationSuccessful) { + throw IllegalStateException("Migration validation failed: ${isValid.errorDetails}") + } + migration.load().migrate() + } } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/db/psql/XefDatabaseConfig.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/db/psql/XefDatabaseConfig.kt index 684eb5b64..c86dd19eb 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/db/psql/XefDatabaseConfig.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/db/psql/XefDatabaseConfig.kt @@ -10,28 +10,24 @@ import kotlinx.serialization.hocon.Hocon @Serializable class XefDatabaseConfig( - val host: String, - val port: Int, - val database: String, - val user: String, - val password: String, - val migrationsTable: String, - val migrationsLocations: List + val host: String, + val port: Int, + val database: String, + val user: String, + val password: String, + val migrationsTable: String, + val migrationsLocations: List ) { - fun getUrl(): String = "jdbc:postgresql://$host:$port/$database" + fun getUrl(): String = "jdbc:postgresql://$host:$port/$database" - companion object { - @OptIn(ExperimentalSerializationApi::class) - suspend fun load( - configNamespace: String, - config: Config? = null - ): XefDatabaseConfig = - withContext(Dispatchers.IO) { - val rawConfig = config ?: ConfigFactory.load().resolve() - val jdbcConfig = rawConfig.getConfig(configNamespace) - Hocon.decodeFromConfig(serializer(), jdbcConfig) - } - - } + companion object { + @OptIn(ExperimentalSerializationApi::class) + suspend fun load(configNamespace: String, config: Config? = null): XefDatabaseConfig = + withContext(Dispatchers.IO) { + val rawConfig = config ?: ConfigFactory.load().resolve() + val jdbcConfig = rawConfig.getConfig(configNamespace) + Hocon.decodeFromConfig(serializer(), jdbcConfig) + } + } } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/db/psql/XefVectorStoreConfig.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/db/psql/XefVectorStoreConfig.kt index c7b75141f..e6bf2b1ae 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/db/psql/XefVectorStoreConfig.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/db/psql/XefVectorStoreConfig.kt @@ -2,10 +2,10 @@ package com.xebia.functional.xef.server.db.psql import com.typesafe.config.Config import com.typesafe.config.ConfigFactory -import com.xebia.functional.xef.server.services.VectorStoreService import com.xebia.functional.xef.server.services.PostgreSQLXef import com.xebia.functional.xef.server.services.PostgresVectorStoreService import com.xebia.functional.xef.server.services.RepositoryService +import com.xebia.functional.xef.server.services.VectorStoreService import com.xebia.functional.xef.store.migrations.PsqlVectorStoreConfig import com.zaxxer.hikari.HikariDataSource import kotlinx.coroutines.Dispatchers @@ -16,82 +16,84 @@ import kotlinx.serialization.hocon.Hocon import org.slf4j.Logger enum class XefVectorStoreType { - PSQL + PSQL } @Serializable class XefVectorStoreConfig( - val type: XefVectorStoreType, - val host: String, - val port: Int, - val database: String, - val driver: String, - val user: String, - val password: String + val type: XefVectorStoreType, + val host: String, + val port: Int, + val database: String, + val driver: String, + val user: String, + val password: String ) { - fun getUrl(): String = "jdbc:postgresql://$host:$port/$database" + fun getUrl(): String = "jdbc:postgresql://$host:$port/$database" - companion object { - @OptIn(ExperimentalSerializationApi::class) - suspend fun load( - configNamespace: String, - config: Config? = null - ): XefVectorStoreConfig = - withContext(Dispatchers.IO) { - val rawConfig = config ?: ConfigFactory.load().resolve() - val jdbcConfig = rawConfig.getConfig(configNamespace) - val config = Hocon.decodeFromConfig(serializer(), jdbcConfig) - config.migrate() - config - } + companion object { + @OptIn(ExperimentalSerializationApi::class) + suspend fun load(configNamespace: String, config: Config? = null): XefVectorStoreConfig = + withContext(Dispatchers.IO) { + val rawConfig = config ?: ConfigFactory.load().resolve() + val jdbcConfig = rawConfig.getConfig(configNamespace) + val config = Hocon.decodeFromConfig(serializer(), jdbcConfig) + config.migrate() + config + } - suspend fun XefVectorStoreConfig.getVectorStoreService(config: Config, logger: Logger): VectorStoreService { - when (this.type) { - XefVectorStoreType.PSQL -> { - val vectorStoreHikariDataSource = RepositoryService.getHikariDataSource(getUrl(), user, password) - return getPsqlVectorStoreService(config, vectorStoreHikariDataSource, logger) - } - } + suspend fun XefVectorStoreConfig.getVectorStoreService( + config: Config, + logger: Logger + ): VectorStoreService { + when (this.type) { + XefVectorStoreType.PSQL -> { + val vectorStoreHikariDataSource = + RepositoryService.getHikariDataSource(getUrl(), user, password) + return getPsqlVectorStoreService(config, vectorStoreHikariDataSource, logger) } + } + } - private suspend fun getPsqlVectorStoreService( - config: Config, - dataSource: HikariDataSource, - logger: Logger - ): VectorStoreService { - val vectorStoreConfig = XefVectorStoreConfig.load("xef-vector-store", config) - val pgVectorStoreConfig = PostgreSQLXef.PGVectorStoreConfig( - dbConfig = PostgreSQLXef.DBConfig( - host = vectorStoreConfig.host, - port = vectorStoreConfig.port, - database = vectorStoreConfig.database, - user = vectorStoreConfig.user, - password = vectorStoreConfig.password - ) + private suspend fun getPsqlVectorStoreService( + config: Config, + dataSource: HikariDataSource, + logger: Logger + ): VectorStoreService { + val vectorStoreConfig = XefVectorStoreConfig.load("xef-vector-store", config) + val pgVectorStoreConfig = + PostgreSQLXef.PGVectorStoreConfig( + dbConfig = + PostgreSQLXef.DBConfig( + host = vectorStoreConfig.host, + port = vectorStoreConfig.port, + database = vectorStoreConfig.database, + user = vectorStoreConfig.user, + password = vectorStoreConfig.password ) - return PostgresVectorStoreService(pgVectorStoreConfig, logger, dataSource) - } - + ) + return PostgresVectorStoreService(pgVectorStoreConfig, logger, dataSource) } + } - private suspend fun migrate() { - when (this.type) { - XefVectorStoreType.PSQL -> { - val psqlConfig = this.toPSQLConfig() - psqlConfig.migrate() - } - } + private suspend fun migrate() { + when (this.type) { + XefVectorStoreType.PSQL -> { + val psqlConfig = this.toPSQLConfig() + psqlConfig.migrate() + } } + } - private fun XefVectorStoreConfig.toPSQLConfig(): PsqlVectorStoreConfig = - PsqlVectorStoreConfig( - host = this.host, - port = this.port, - database = this.database, - driver = this.driver, - user = this.user, - password = this.password, - migrationsTable = "migration" - ) + private fun XefVectorStoreConfig.toPSQLConfig(): PsqlVectorStoreConfig = + PsqlVectorStoreConfig( + host = this.host, + port = this.port, + database = this.database, + driver = this.driver, + user = this.user, + password = this.password, + migrationsTable = "migration" + ) } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/OrganizationTable.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/OrganizationTable.kt index 2df98e106..e76410c33 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/OrganizationTable.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/OrganizationTable.kt @@ -9,24 +9,20 @@ import org.jetbrains.exposed.sql.kotlin.datetime.CurrentTimestamp import org.jetbrains.exposed.sql.kotlin.datetime.timestamp object OrganizationsTable : IntIdTable("organizations") { - val name = varchar("name", 20) - val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp()) - val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp()) - val ownerId = reference( - name = "owner_id", - refColumn = UsersTable.id, - onDelete = ReferenceOption.CASCADE - ) + val name = varchar("name", 20) + val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp()) + val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp()) + val ownerId = + reference(name = "owner_id", refColumn = UsersTable.id, onDelete = ReferenceOption.CASCADE) } class Organization(id: EntityID) : IntEntity(id) { - companion object : IntEntityClass(OrganizationsTable) + companion object : IntEntityClass(OrganizationsTable) - var name by OrganizationsTable.name - var createdAt by OrganizationsTable.createdAt - var updatedAt by OrganizationsTable.updatedAt - var ownerId by OrganizationsTable.ownerId + var name by OrganizationsTable.name + var createdAt by OrganizationsTable.createdAt + var updatedAt by OrganizationsTable.updatedAt + var ownerId by OrganizationsTable.ownerId - var users by User via UsersOrgsTable + var users by User via UsersOrgsTable } - diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/ProjectsTable.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/ProjectsTable.kt index c92b4ba7e..fd7c4c16b 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/ProjectsTable.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/ProjectsTable.kt @@ -8,23 +8,23 @@ import org.jetbrains.exposed.sql.ReferenceOption import org.jetbrains.exposed.sql.kotlin.datetime.CurrentTimestamp import org.jetbrains.exposed.sql.kotlin.datetime.timestamp -object ProjectsTable: IntIdTable("projects") { - val name = varchar("name", 20) - val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp()) - val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp()) - val orgId = reference( - name = "org_id", - refColumn = OrganizationsTable.id, - onDelete = ReferenceOption.CASCADE +object ProjectsTable : IntIdTable("projects") { + val name = varchar("name", 20) + val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp()) + val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp()) + val orgId = + reference( + name = "org_id", + refColumn = OrganizationsTable.id, + onDelete = ReferenceOption.CASCADE ) } class Project(id: EntityID) : IntEntity(id) { - companion object : IntEntityClass(ProjectsTable) + companion object : IntEntityClass(ProjectsTable) - var name by ProjectsTable.name - var createdAt by ProjectsTable.createdAt - var updatedAt by ProjectsTable.updatedAt - var orgId by ProjectsTable.orgId + var name by ProjectsTable.name + var createdAt by ProjectsTable.createdAt + var updatedAt by ProjectsTable.updatedAt + var orgId by ProjectsTable.orgId } - diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/UsersOrgsTable.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/UsersOrgsTable.kt index f8b79ed50..d858a236d 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/UsersOrgsTable.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/UsersOrgsTable.kt @@ -4,16 +4,9 @@ import org.jetbrains.exposed.sql.ReferenceOption import org.jetbrains.exposed.sql.Table object UsersOrgsTable : Table("users_org") { - val userId = reference( - name = "user_id", - foreign = UsersTable, - onDelete = ReferenceOption.CASCADE - ) - val orgId = reference( - name = "org_id", - foreign = OrganizationsTable, - onDelete = ReferenceOption.CASCADE - ) + val userId = reference(name = "user_id", foreign = UsersTable, onDelete = ReferenceOption.CASCADE) + val orgId = + reference(name = "org_id", foreign = OrganizationsTable, onDelete = ReferenceOption.CASCADE) - override val primaryKey = PrimaryKey(userId, orgId) + override val primaryKey = PrimaryKey(userId, orgId) } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/UsersTable.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/UsersTable.kt index 2eaf0abc3..6876e62f9 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/UsersTable.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/UsersTable.kt @@ -7,27 +7,26 @@ import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.kotlin.datetime.CurrentTimestamp import org.jetbrains.exposed.sql.kotlin.datetime.timestamp - object UsersTable : IntIdTable("users") { - val name = varchar("name", 20) - val email = varchar("email", 50) - val passwordHash = binary("password_hash") - val salt = binary("salt") - val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp()) - val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp()) - val authToken = varchar("auth_token", 128) + val name = varchar("name", 20) + val email = varchar("email", 50) + val passwordHash = binary("password_hash") + val salt = binary("salt") + val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp()) + val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp()) + val authToken = varchar("auth_token", 128) } class User(id: EntityID) : IntEntity(id) { - companion object : IntEntityClass(UsersTable) + companion object : IntEntityClass(UsersTable) - var name by UsersTable.name - var email by UsersTable.email - var passwordHash by UsersTable.passwordHash - var salt by UsersTable.salt - var createdAt by UsersTable.createdAt - var updatedAt by UsersTable.updatedAt - var authToken by UsersTable.authToken + var name by UsersTable.name + var email by UsersTable.email + var passwordHash by UsersTable.passwordHash + var salt by UsersTable.salt + var createdAt by UsersTable.createdAt + var updatedAt by UsersTable.updatedAt + var authToken by UsersTable.authToken - var organizations by Organization via UsersOrgsTable + var organizations by Organization via UsersOrgsTable } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/XefTokensTable.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/XefTokensTable.kt index 7fa52fcbc..7df25982a 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/XefTokensTable.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/db/tables/XefTokensTable.kt @@ -14,32 +14,24 @@ import org.jetbrains.exposed.sql.kotlin.datetime.timestamp val format = Json { prettyPrint = true } object XefTokensTable : IntIdTable("xef_tokens") { - val userId = reference( - name = "user_id", - foreign = UsersTable, - onDelete = ReferenceOption.CASCADE - ) - val projectId = reference( - name = "project_id", - foreign = ProjectsTable, - onDelete = ReferenceOption.CASCADE - ) - val name = varchar("name", 20) - val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp()) - val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp()) - val token = varchar("token", 128).uniqueIndex() - val providersConfig = jsonb("providers_config", format) - + val userId = reference(name = "user_id", foreign = UsersTable, onDelete = ReferenceOption.CASCADE) + val projectId = + reference(name = "project_id", foreign = ProjectsTable, onDelete = ReferenceOption.CASCADE) + val name = varchar("name", 20) + val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp()) + val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp()) + val token = varchar("token", 128).uniqueIndex() + val providersConfig = jsonb("providers_config", format) } class XefTokens(id: EntityID) : IntEntity(id) { - companion object : IntEntityClass(XefTokensTable) + companion object : IntEntityClass(XefTokensTable) - var userId by XefTokensTable.userId - var projectId by XefTokensTable.projectId - var name by XefTokensTable.name - var createdAt by XefTokensTable.createdAt - var updatedAt by XefTokensTable.updatedAt - var token by XefTokensTable.token - var providersConfig by XefTokensTable.providersConfig + var userId by XefTokensTable.userId + var projectId by XefTokensTable.projectId + var name by XefTokensTable.name + var createdAt by XefTokensTable.createdAt + var updatedAt by XefTokensTable.updatedAt + var token by XefTokensTable.token + var providersConfig by XefTokensTable.providersConfig } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/exceptions/ExceptionsHandler.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/exceptions/ExceptionsHandler.kt index aa688fcaa..44252c1e0 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/exceptions/ExceptionsHandler.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/exceptions/ExceptionsHandler.kt @@ -7,27 +7,31 @@ import io.ktor.server.plugins.statuspages.* import io.ktor.server.response.* fun Application.exceptionsHandler() { - install(StatusPages) { - exception { call, cause -> - if (cause is XefExceptions) { - call.manageException(cause) - } else { - call.respond(HttpStatusCode.InternalServerError, cause.localizedMessage ?: "Unexpected error") - } - } - status(HttpStatusCode.NotFound) { call, status -> - call.respondText(text = "404: Page Not Found", status = status) - } + install(StatusPages) { + exception { call, cause -> + if (cause is XefExceptions) { + call.manageException(cause) + } else { + call.respond( + HttpStatusCode.InternalServerError, + cause.localizedMessage ?: "Unexpected error" + ) + } } + status(HttpStatusCode.NotFound) { call, status -> + call.respondText(text = "404: Page Not Found", status = status) + } + } } suspend fun ApplicationCall.manageException(cause: XefExceptions) { - when(cause) { - is XefExceptions.ValidationException -> this.respond(HttpStatusCode.BadRequest, cause.message) - is XefExceptions.AuthorizationException -> this.respond(HttpStatusCode.Unauthorized) - is XefExceptions.OrganizationsException -> this.respond(HttpStatusCode.BadRequest, cause.message) - is XefExceptions.ProjectException -> this.respond(HttpStatusCode.BadRequest, cause.message) - is XefExceptions.XefTokenException -> this.respond(HttpStatusCode.BadRequest, cause.message) - is XefExceptions.UserException -> this.respond(HttpStatusCode.BadRequest, cause.message) - } + when (cause) { + is XefExceptions.ValidationException -> this.respond(HttpStatusCode.BadRequest, cause.message) + is XefExceptions.AuthorizationException -> this.respond(HttpStatusCode.Unauthorized) + is XefExceptions.OrganizationsException -> + this.respond(HttpStatusCode.BadRequest, cause.message) + is XefExceptions.ProjectException -> this.respond(HttpStatusCode.BadRequest, cause.message) + is XefExceptions.XefTokenException -> this.respond(HttpStatusCode.BadRequest, cause.message) + is XefExceptions.UserException -> this.respond(HttpStatusCode.BadRequest, cause.message) + } } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/AIRoutes.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/AIRoutes.kt index 488077633..99bb38f43 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/AIRoutes.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/AIRoutes.kt @@ -19,105 +19,104 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.boolean import kotlinx.serialization.json.jsonPrimitive -import java.nio.charset.Charset enum class Provider { - OPENAI, GPT4ALL, GCP + OPENAI, + GPT4ALL, + GCP } -fun String.toProvider(): Provider? = when (this) { +fun String.toProvider(): Provider? = + when (this) { "openai" -> Provider.OPENAI "gpt4all" -> Provider.GPT4ALL "gcp" -> Provider.GCP else -> Provider.OPENAI -} + } @OptIn(BetaOpenAI::class) -fun Routing.aiRoutes( - client: HttpClient -) { - val openAiUrl = "https://api.openai.com/v1" - - authenticate("auth-bearer") { - post("/chat/completions") { - val token = call.getToken() - val byteArrayBody = call.receiveChannel().toByteArray() - val body = byteArrayBody.toString(Charsets.UTF_8) - val data = Json.decodeFromString(body) - - val isStream = data["stream"]?.jsonPrimitive?.boolean ?: false - - if (!isStream) { - client.makeRequest(call, "$openAiUrl/chat/completions", byteArrayBody, token) - } else { - client.makeStreaming(call, "$openAiUrl/chat/completions", byteArrayBody, token) - } - } - - post("/embeddings") { - val token = call.getToken() - val context = call.receiveChannel().toByteArray() - client.makeRequest(call, "$openAiUrl/embeddings", context, token) - } +fun Routing.aiRoutes(client: HttpClient) { + val openAiUrl = "https://api.openai.com/v1" + + authenticate("auth-bearer") { + post("/chat/completions") { + val token = call.getToken() + val byteArrayBody = call.receiveChannel().toByteArray() + val body = byteArrayBody.toString(Charsets.UTF_8) + val data = Json.decodeFromString(body) + + val isStream = data["stream"]?.jsonPrimitive?.boolean ?: false + + if (!isStream) { + client.makeRequest(call, "$openAiUrl/chat/completions", byteArrayBody, token) + } else { + client.makeStreaming(call, "$openAiUrl/chat/completions", byteArrayBody, token) + } + } + + post("/embeddings") { + val token = call.getToken() + val context = call.receiveChannel().toByteArray() + client.makeRequest(call, "$openAiUrl/embeddings", context, token) } + } } private suspend fun HttpClient.makeRequest( - call: ApplicationCall, - url: String, - body: ByteArray, - token: Token + call: ApplicationCall, + url: String, + body: ByteArray, + token: Token ) { - val response = this.request(url) { - headers.copyFrom(call.request.headers) - contentType(ContentType.Application.Json) - method = HttpMethod.Post - setBody(body) + val response = + this.request(url) { + headers.copyFrom(call.request.headers) + contentType(ContentType.Application.Json) + method = HttpMethod.Post + setBody(body) } - call.response.headers.copyFrom(response.headers) - call.respond(response.status, response.readBytes()) + call.response.headers.copyFrom(response.headers) + call.respond(response.status, response.readBytes()) } private suspend fun HttpClient.makeStreaming( - call: ApplicationCall, - url: String, - body: ByteArray, - token: Token + call: ApplicationCall, + url: String, + body: ByteArray, + token: Token ) { - this.preparePost(url) { - headers.copyFrom(call.request.headers) - method = HttpMethod.Post - setBody(body) - }.execute { httpResponse -> - call.response.headers.copyFrom(httpResponse.headers) - call.respondOutputStream { - httpResponse - .bodyAsChannel() - .copyTo(this@respondOutputStream) - } + this.preparePost(url) { + headers.copyFrom(call.request.headers) + method = HttpMethod.Post + setBody(body) + } + .execute { httpResponse -> + call.response.headers.copyFrom(httpResponse.headers) + call.respondOutputStream { httpResponse.bodyAsChannel().copyTo(this@respondOutputStream) } } } -private fun ResponseHeaders.copyFrom(headers: Headers) = headers +private fun ResponseHeaders.copyFrom(headers: Headers) = + headers .entries() - .filter { (key, _) -> !HttpHeaders.isUnsafe(key) } // setting unsafe headers results in exception - .forEach { (key, values) -> - values.forEach { value -> this.appendIfAbsent(key, value) } - } + .filter { (key, _) -> + !HttpHeaders.isUnsafe(key) + } // setting unsafe headers results in exception + .forEach { (key, values) -> values.forEach { value -> this.appendIfAbsent(key, value) } } -internal fun HeadersBuilder.copyFrom(headers: Headers) = headers +internal fun HeadersBuilder.copyFrom(headers: Headers) = + headers .filter { key, value -> !key.equals("HOST", ignoreCase = true) } .forEach { key, values -> appendAll(key, values) } private fun ApplicationCall.getProvider(): Provider = - request.headers["xef-provider"]?.toProvider() - ?: Provider.OPENAI + request.headers["xef-provider"]?.toProvider() ?: Provider.OPENAI fun ApplicationCall.getToken(): Token = - principal()?.name?.let { Token(it) } ?: throw XefExceptions.AuthorizationException("No token found") + principal()?.name?.let { Token(it) } + ?: throw XefExceptions.AuthorizationException("No token found") fun ApplicationCall.getId(): Int = getInt("id") fun ApplicationCall.getInt(field: String): Int = - this.parameters[field]?.toInt() ?: throw XefExceptions.ValidationException("Invalid $field") - + this.parameters[field]?.toInt() ?: throw XefExceptions.ValidationException("Invalid $field") diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/OrganizationRoutes.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/OrganizationRoutes.kt index e13b0de90..6d0af5ec9 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/OrganizationRoutes.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/OrganizationRoutes.kt @@ -2,7 +2,6 @@ package com.xebia.functional.xef.server.http.routes import com.xebia.functional.xef.server.models.OrganizationRequest import com.xebia.functional.xef.server.models.OrganizationUpdateRequest -import com.xebia.functional.xef.server.models.exceptions.XefExceptions import com.xebia.functional.xef.server.services.OrganizationRepositoryService import io.ktor.http.* import io.ktor.server.application.* @@ -12,56 +11,43 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import kotlinx.serialization.json.Json -fun Routing.organizationRoutes( - orgRepositoryService: OrganizationRepositoryService -) { - authenticate("auth-bearer") { - get("/v1/settings/org") { - val token = call.getToken() - val response = orgRepositoryService.getOrganizations(token) - call.respond(response) - } - get("/v1/settings/org/{id}") { - - val token = call.getToken() - val id = call.getId() - val response = orgRepositoryService.getOrganization(token, id) - call.respond(response) - } - get("/v1/settings/org/{id}/users") { - val token = call.getToken() - val id = call.getId() - val response = orgRepositoryService.getUsersInOrganization(token, id) - call.respond(response) - } - post("/v1/settings/org") { - - val request = Json.decodeFromString(call.receive()) - val token = call.getToken() - val response = orgRepositoryService.createOrganization(request, token) - call.respond( - status = HttpStatusCode.Created, - response - ) - } - put("/v1/settings/org/{id}") { - val request = Json.decodeFromString(call.receive()) - val token = call.getToken() - val id = call.getId() - val response = orgRepositoryService.updateOrganization(token, request, id) - call.respond( - status = HttpStatusCode.NoContent, - response - ) - } - delete("/v1/settings/org/{id}") { - val token = call.getToken() - val id = call.getId() - val response = orgRepositoryService.deleteOrganization(token, id) - call.respond( - status = HttpStatusCode.NoContent, - response - ) - } +fun Routing.organizationRoutes(orgRepositoryService: OrganizationRepositoryService) { + authenticate("auth-bearer") { + get("/v1/settings/org") { + val token = call.getToken() + val response = orgRepositoryService.getOrganizations(token) + call.respond(response) + } + get("/v1/settings/org/{id}") { + val token = call.getToken() + val id = call.getId() + val response = orgRepositoryService.getOrganization(token, id) + call.respond(response) + } + get("/v1/settings/org/{id}/users") { + val token = call.getToken() + val id = call.getId() + val response = orgRepositoryService.getUsersInOrganization(token, id) + call.respond(response) + } + post("/v1/settings/org") { + val request = Json.decodeFromString(call.receive()) + val token = call.getToken() + val response = orgRepositoryService.createOrganization(request, token) + call.respond(status = HttpStatusCode.Created, response) + } + put("/v1/settings/org/{id}") { + val request = Json.decodeFromString(call.receive()) + val token = call.getToken() + val id = call.getId() + val response = orgRepositoryService.updateOrganization(token, request, id) + call.respond(status = HttpStatusCode.NoContent, response) + } + delete("/v1/settings/org/{id}") { + val token = call.getToken() + val id = call.getId() + val response = orgRepositoryService.deleteOrganization(token, id) + call.respond(status = HttpStatusCode.NoContent, response) } + } } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/ProjectsRoutes.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/ProjectsRoutes.kt index 7ec75296e..8fe15e9f7 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/ProjectsRoutes.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/ProjectsRoutes.kt @@ -11,59 +11,43 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import kotlinx.serialization.json.Json -fun Routing.projectsRoutes( - projectRepositoryService: ProjectRepositoryService -) { - authenticate("auth-bearer") { - get("/v1/settings/projects") { - val token = call.getToken() - val response = projectRepositoryService.getProjects(token) - call.respond(response) - } - get("/v1/settings/projects/{id}") { - - val token = call.getToken() - val id = call.getId() - val response = projectRepositoryService.getProject(token, id) - call.respond(response) - } - get("/v1/settings/projects/org/{id}") { - - val token = call.getToken() - val id = call.getId() - val response = projectRepositoryService.getProjectsByOrganization(token, id) - call.respond(response) - } - post("/v1/settings/projects") { - - val request = Json.decodeFromString(call.receive()) - val token = call.getToken() - val response = projectRepositoryService.createProject(request, token) - call.respond( - status = HttpStatusCode.Created, - response - ) - } - put("/v1/settings/projects/{id}") { - val request = Json.decodeFromString(call.receive()) - val token = call.getToken() - val id = call.getId() - val response = projectRepositoryService.updateProject(token, request, id) - call.respond( - status = HttpStatusCode.NoContent, - response - ) - } - delete("/v1/settings/projects/{id}") { - val token = call.getToken() - val id = call.getId() - val response = projectRepositoryService.deleteProject(token, id) - call.respond( - status = HttpStatusCode.NoContent, - response - ) - } +fun Routing.projectsRoutes(projectRepositoryService: ProjectRepositoryService) { + authenticate("auth-bearer") { + get("/v1/settings/projects") { + val token = call.getToken() + val response = projectRepositoryService.getProjects(token) + call.respond(response) + } + get("/v1/settings/projects/{id}") { + val token = call.getToken() + val id = call.getId() + val response = projectRepositoryService.getProject(token, id) + call.respond(response) + } + get("/v1/settings/projects/org/{id}") { + val token = call.getToken() + val id = call.getId() + val response = projectRepositoryService.getProjectsByOrganization(token, id) + call.respond(response) } + post("/v1/settings/projects") { + val request = Json.decodeFromString(call.receive()) + val token = call.getToken() + val response = projectRepositoryService.createProject(request, token) + call.respond(status = HttpStatusCode.Created, response) + } + put("/v1/settings/projects/{id}") { + val request = Json.decodeFromString(call.receive()) + val token = call.getToken() + val id = call.getId() + val response = projectRepositoryService.updateProject(token, request, id) + call.respond(status = HttpStatusCode.NoContent, response) + } + delete("/v1/settings/projects/{id}") { + val token = call.getToken() + val id = call.getId() + val response = projectRepositoryService.deleteProject(token, id) + call.respond(status = HttpStatusCode.NoContent, response) + } + } } - - diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/RequestHelpers.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/RequestHelpers.kt index ee57a56d1..73e62e9b5 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/RequestHelpers.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/RequestHelpers.kt @@ -8,26 +8,26 @@ import com.xebia.functional.xef.llm.models.chat.Role @OptIn(BetaOpenAI::class) fun ChatCompletionRequest.toCore(): com.xebia.functional.xef.llm.models.chat.ChatCompletionRequest = - com.xebia.functional.xef.llm.models.chat.ChatCompletionRequest( - messages = messages.map { Message(it.role.toCore(), it.content ?: "", it.name ?: "") }, - temperature = temperature ?: 0.0, - topP = topP ?: 1.0, - n = n ?: 1, - stream = false, - stop = stop, - maxTokens = maxTokens, - presencePenalty = presencePenalty ?: 0.0, - frequencyPenalty = frequencyPenalty ?: 0.0, - logitBias = logitBias ?: emptyMap(), - user = user, - streamToStandardOut = false - ) + com.xebia.functional.xef.llm.models.chat.ChatCompletionRequest( + messages = messages.map { Message(it.role.toCore(), it.content ?: "", it.name ?: "") }, + temperature = temperature ?: 0.0, + topP = topP ?: 1.0, + n = n ?: 1, + stream = false, + stop = stop, + maxTokens = maxTokens, + presencePenalty = presencePenalty ?: 0.0, + frequencyPenalty = frequencyPenalty ?: 0.0, + logitBias = logitBias ?: emptyMap(), + user = user, + streamToStandardOut = false + ) @OptIn(BetaOpenAI::class) fun ChatRole.toCore(): Role = - when (this) { - ChatRole.System -> Role.SYSTEM - ChatRole.User -> Role.USER - ChatRole.Assistant -> Role.ASSISTANT - else -> Role.ASSISTANT - } + when (this) { + ChatRole.System -> Role.SYSTEM + ChatRole.User -> Role.USER + ChatRole.Assistant -> Role.ASSISTANT + else -> Role.ASSISTANT + } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/TokensRoutes.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/TokensRoutes.kt index caab0b29e..52891fc15 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/TokensRoutes.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/TokensRoutes.kt @@ -11,51 +11,37 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import kotlinx.serialization.json.Json -fun Routing.tokensRoutes( - tokenRepositoryService: TokenRepositoryService -) { - authenticate("auth-bearer") { - get("/v1/settings/tokens") { - val token = call.getToken() - val response = tokenRepositoryService.getTokens(token) - call.respond(response) - } - get("/v1/settings/tokens/projects/{id}") { - val token = call.getToken() - val id = call.getId() - val response = tokenRepositoryService.getTokensByProject(token, id) - call.respond(response) - } - post("/v1/settings/tokens") { - - val request = Json.decodeFromString(call.receive()) - val token = call.getToken() - val response = tokenRepositoryService.createToken(request, token) - call.respond( - status = HttpStatusCode.Created, - response - ) - } - put("/v1/settings/tokens/{id}") { - val request = Json.decodeFromString(call.receive()) - val token = call.getToken() - val id = call.getId() - val response = tokenRepositoryService.updateToken(token, request, id) - call.respond( - status = HttpStatusCode.NoContent, - response - ) - } - delete("/v1/settings/tokens/{id}") { - val token = call.getToken() - val id = call.getId() - val response = tokenRepositoryService.deleteToken(token, id) - call.respond( - status = HttpStatusCode.NoContent, - response - ) - } +fun Routing.tokensRoutes(tokenRepositoryService: TokenRepositoryService) { + authenticate("auth-bearer") { + get("/v1/settings/tokens") { + val token = call.getToken() + val response = tokenRepositoryService.getTokens(token) + call.respond(response) + } + get("/v1/settings/tokens/projects/{id}") { + val token = call.getToken() + val id = call.getId() + val response = tokenRepositoryService.getTokensByProject(token, id) + call.respond(response) + } + post("/v1/settings/tokens") { + val request = Json.decodeFromString(call.receive()) + val token = call.getToken() + val response = tokenRepositoryService.createToken(request, token) + call.respond(status = HttpStatusCode.Created, response) + } + put("/v1/settings/tokens/{id}") { + val request = Json.decodeFromString(call.receive()) + val token = call.getToken() + val id = call.getId() + val response = tokenRepositoryService.updateToken(token, request, id) + call.respond(status = HttpStatusCode.NoContent, response) } + delete("/v1/settings/tokens/{id}") { + val token = call.getToken() + val id = call.getId() + val response = tokenRepositoryService.deleteToken(token, id) + call.respond(status = HttpStatusCode.NoContent, response) + } + } } - - diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/UserRoutes.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/UserRoutes.kt index 9b2681567..efde1173d 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/UserRoutes.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/UserRoutes.kt @@ -10,18 +10,16 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import kotlinx.serialization.json.Json -fun Routing.userRoutes( - userRepositoryService: UserRepositoryService -) { - post("/register") { - val request = Json.decodeFromString(call.receive()) - val response = userRepositoryService.register(request) - call.respond(response) - } +fun Routing.userRoutes(userRepositoryService: UserRepositoryService) { + post("/register") { + val request = Json.decodeFromString(call.receive()) + val response = userRepositoryService.register(request) + call.respond(response) + } - post("/login") { - val request = Json.decodeFromString(call.receive()) - val response = userRepositoryService.login(request) - call.respond(response) - } + post("/login") { + val request = Json.decodeFromString(call.receive()) + val response = userRepositoryService.login(request) + call.respond(response) + } } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/XefRoutes.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/XefRoutes.kt index 1fb2ff2cf..5b461b205 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/XefRoutes.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/XefRoutes.kt @@ -7,9 +7,7 @@ import com.xebia.functional.xef.server.services.UserRepositoryService import io.ktor.server.routing.* import org.slf4j.Logger -fun Routing.xefRoutes( - logger: Logger -) { +fun Routing.xefRoutes(logger: Logger) { userRoutes(UserRepositoryService(logger)) organizationRoutes(OrganizationRepositoryService(logger)) projectsRoutes(ProjectRepositoryService(logger)) diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/models/ProvidersConfig.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/models/ProvidersConfig.kt index 9a967335e..028ee1eda 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/models/ProvidersConfig.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/models/ProvidersConfig.kt @@ -3,28 +3,21 @@ package com.xebia.functional.xef.server.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -@Serializable -data class OpenAIConf( - val token: String, - val url: String? -) +@Serializable data class OpenAIConf(val token: String, val url: String?) @Serializable data class GCPConf( - val token: String, - @SerialName("project_id") - val projectId: String, - val location: String + val token: String, + @SerialName("project_id") val projectId: String, + val location: String ) @Serializable data class ProvidersConfig( - @SerialName("open_ai") - val openAI: OpenAIConf?, - @SerialName("gcp") - val gcp: GCPConf? + @SerialName("open_ai") val openAI: OpenAIConf?, + @SerialName("gcp") val gcp: GCPConf? ) { - companion object { - val empty = ProvidersConfig(null, null) - } + companion object { + val empty = ProvidersConfig(null, null) + } } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/models/Requests.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/models/Requests.kt index 5d382e707..75cf4d851 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/models/Requests.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/models/Requests.kt @@ -2,50 +2,18 @@ package com.xebia.functional.xef.server.models import kotlinx.serialization.Serializable -@Serializable -data class RegisterRequest( - val name: String, - val email: String, - val password: String -) - -@Serializable -data class LoginRequest( - val email: String, - val password: String -) - -@Serializable -data class OrganizationRequest( - val name: String -) - -@Serializable -data class OrganizationUpdateRequest( - val name: String, - val owner: Int? = null -) - -@Serializable -data class ProjectRequest( - val name: String, - val orgId: Int -) - -@Serializable -data class ProjectUpdateRequest( - val name: String, - val orgId: Int? = null -) - -@Serializable -data class TokenRequest( - val name: String, - val projectId: Int -) - -@Serializable -data class TokenUpdateRequest( - val name: String, - val providerConfig: ProvidersConfig -) +@Serializable data class RegisterRequest(val name: String, val email: String, val password: String) + +@Serializable data class LoginRequest(val email: String, val password: String) + +@Serializable data class OrganizationRequest(val name: String) + +@Serializable data class OrganizationUpdateRequest(val name: String, val owner: Int? = null) + +@Serializable data class ProjectRequest(val name: String, val orgId: Int) + +@Serializable data class ProjectUpdateRequest(val name: String, val orgId: Int? = null) + +@Serializable data class TokenRequest(val name: String, val projectId: Int) + +@Serializable data class TokenUpdateRequest(val name: String, val providerConfig: ProvidersConfig) diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/models/Responses.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/models/Responses.kt index f9611415e..7db23bad2 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/models/Responses.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/models/Responses.kt @@ -6,40 +6,51 @@ import com.xebia.functional.xef.server.db.tables.User import com.xebia.functional.xef.server.db.tables.XefTokens import kotlinx.serialization.Serializable -@Serializable -data class LoginResponse(val authToken: String) +@Serializable data class LoginResponse(val authToken: String) -@Serializable -data class UserResponse(val id: Int, val name: String) +@Serializable data class UserResponse(val id: Int, val name: String) fun User.toUserResponse() = UserResponse(id.value, name) -@Serializable -data class OrganizationSimpleResponse(val id: Int, val name: String) +@Serializable data class OrganizationSimpleResponse(val id: Int, val name: String) fun Organization.toOrganizationSimpleResponse() = OrganizationSimpleResponse(id.value, name) @Serializable data class OrganizationFullResponse(val id: Int, val name: String, val owner: Int, val users: Long) -fun Organization.toOrganizationFullResponse() = OrganizationFullResponse(id.value, name, ownerId.value, users.count()) +fun Organization.toOrganizationFullResponse() = + OrganizationFullResponse(id.value, name, ownerId.value, users.count()) -@Serializable -data class ProjectSimpleResponse(val id: Int, val name: String, val orgId: Int) +@Serializable data class ProjectSimpleResponse(val id: Int, val name: String, val orgId: Int) fun Project.toProjectSimpleResponse() = ProjectSimpleResponse(id.value, name, orgId.value) @Serializable data class ProjectFullResponse(val id: Int, val name: String, val org: OrganizationFullResponse) -fun Project.toProjectFullResponse(org: Organization) = ProjectFullResponse(id.value, name, org.toOrganizationFullResponse()) +fun Project.toProjectFullResponse(org: Organization) = + ProjectFullResponse(id.value, name, org.toOrganizationFullResponse()) @Serializable -data class TokenSimpleResponse(val id: Int, val projectId: Int, val userId : Int, val name: String, val token: String) +data class TokenSimpleResponse( + val id: Int, + val projectId: Int, + val userId: Int, + val name: String, + val token: String +) -fun XefTokens.toTokenSimpleResponse() = TokenSimpleResponse(id.value, projectId.value, userId.value, name, token) +fun XefTokens.toTokenSimpleResponse() = + TokenSimpleResponse(id.value, projectId.value, userId.value, name, token) @Serializable -data class TokenFullResponse(val id: Int, val project: ProjectSimpleResponse, val name: String, val token: String) +data class TokenFullResponse( + val id: Int, + val project: ProjectSimpleResponse, + val name: String, + val token: String +) -fun XefTokens.toTokenFullResponse(project: ProjectSimpleResponse) = TokenFullResponse(id.value, project, name, token) +fun XefTokens.toTokenFullResponse(project: ProjectSimpleResponse) = + TokenFullResponse(id.value, project, name, token) diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/models/exceptions/Exceptions.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/models/exceptions/Exceptions.kt index fba942d15..bd50642da 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/models/exceptions/Exceptions.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/models/exceptions/Exceptions.kt @@ -1,12 +1,15 @@ package com.xebia.functional.xef.server.models.exceptions -sealed class XefExceptions( - override val message: String -): Throwable() { - class ValidationException(override val message: String): XefExceptions(message) - class OrganizationsException(override val message: String): XefExceptions(message) - class ProjectException(override val message: String): XefExceptions(message) - class XefTokenException(override val message: String): XefExceptions(message) - class AuthorizationException(override val message: String): XefExceptions(message) - class UserException(override val message: String): XefExceptions(message) +sealed class XefExceptions(override val message: String) : Throwable() { + class ValidationException(override val message: String) : XefExceptions(message) + + class OrganizationsException(override val message: String) : XefExceptions(message) + + class ProjectException(override val message: String) : XefExceptions(message) + + class XefTokenException(override val message: String) : XefExceptions(message) + + class AuthorizationException(override val message: String) : XefExceptions(message) + + class UserException(override val message: String) : XefExceptions(message) } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/services/OrganizationRepositoryService.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/services/OrganizationRepositoryService.kt index 6e54f16e8..e7c21635a 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/services/OrganizationRepositoryService.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/services/OrganizationRepositoryService.kt @@ -9,119 +9,105 @@ import org.jetbrains.exposed.sql.SizedCollection import org.jetbrains.exposed.sql.transactions.transaction import org.slf4j.Logger -class OrganizationRepositoryService( - private val logger: Logger -) { - fun createOrganization( - data: OrganizationRequest, - token: Token - ): OrganizationSimpleResponse { - logger.info("Creating organization with name: ${data.name}") - return transaction { - // Getting the user from the token - val user = token.getUser() - - // Creating the organization - val organization = Organization.new { - name = data.name - ownerId = user.id - } - // Adding the organization to the user - user.organizations = SizedCollection(user.organizations + organization) - organization.users = SizedCollection(organization.users + user) - organization.toOrganizationSimpleResponse() +class OrganizationRepositoryService(private val logger: Logger) { + fun createOrganization(data: OrganizationRequest, token: Token): OrganizationSimpleResponse { + logger.info("Creating organization with name: ${data.name}") + return transaction { + // Getting the user from the token + val user = token.getUser() + + // Creating the organization + val organization = + Organization.new { + name = data.name + ownerId = user.id } + // Adding the organization to the user + user.organizations = SizedCollection(user.organizations + organization) + organization.users = SizedCollection(organization.users + user) + organization.toOrganizationSimpleResponse() } + } - fun getOrganizations( - token: Token - ): List { - logger.info("Getting organizations") - return transaction { - // Getting the user from the token - val user = token.getUser() + fun getOrganizations(token: Token): List { + logger.info("Getting organizations") + return transaction { + // Getting the user from the token + val user = token.getUser() - // Getting the organizations from the user - user.organizations.map { it.toOrganizationFullResponse() } - } + // Getting the organizations from the user + user.organizations.map { it.toOrganizationFullResponse() } } + } - fun getOrganization( - token: Token, - id: Int - ): OrganizationFullResponse { - logger.info("Getting organizations") - return transaction { - // Getting the user from the token - val user = token.getUser() - - // Getting the organization - user.organizations.find { - it.id.value == id - }?.toOrganizationFullResponse() ?: throw OrganizationsException("Organization not found") - } - } + fun getOrganization(token: Token, id: Int): OrganizationFullResponse { + logger.info("Getting organizations") + return transaction { + // Getting the user from the token + val user = token.getUser() - fun getUsersInOrganization( - token: Token, - id: Int - ): List { - logger.info("Getting users in organization") - return transaction { - // Getting the user from the token - val user = token.getUser() - - // Getting the organizations from the user - user.organizations.filter { - it.id.value == id - }.flatMap { it.users }.map { it.toUserResponse() } - } + // Getting the organization + user.organizations.find { it.id.value == id }?.toOrganizationFullResponse() + ?: throw OrganizationsException("Organization not found") } - - fun updateOrganization( - token: Token, - data: OrganizationUpdateRequest, - id: Int - ): OrganizationFullResponse { - logger.info("Updating organization with name: ${data.name}") - return transaction { - // Getting the user from the token - val user = token.getUser() - - val organization = Organization.findById(id) - ?: throw OrganizationsException("Organization not found") - - if (organization.ownerId != user.id) { - throw OrganizationsException("User is not the owner of the organization") - } - - // Updating the organization - organization.name = data.name - if (data.owner != null) { - val newOwner = User.findById(data.owner) - ?: throw UserException("User not found") - organization.ownerId = newOwner.id - } - organization.updatedAt = Clock.System.now() - organization.toOrganizationFullResponse() - } + } + + fun getUsersInOrganization(token: Token, id: Int): List { + logger.info("Getting users in organization") + return transaction { + // Getting the user from the token + val user = token.getUser() + + // Getting the organizations from the user + user.organizations + .filter { it.id.value == id } + .flatMap { it.users } + .map { it.toUserResponse() } } - - fun deleteOrganization( - token: Token, - id: Int - ) { - logger.info("Deleting organization with id: $id") - transaction { - val user = token.getUser() - val organization = Organization.findById(id) - ?: throw OrganizationsException("Organization not found") - - if (organization.ownerId != user.id) { - throw OrganizationsException("You can't delete the organization. User is not the owner of the organization") - } - - organization.delete() - } + } + + fun updateOrganization( + token: Token, + data: OrganizationUpdateRequest, + id: Int + ): OrganizationFullResponse { + logger.info("Updating organization with name: ${data.name}") + return transaction { + // Getting the user from the token + val user = token.getUser() + + val organization = + Organization.findById(id) ?: throw OrganizationsException("Organization not found") + + if (organization.ownerId != user.id) { + throw OrganizationsException("User is not the owner of the organization") + } + + // Updating the organization + organization.name = data.name + if (data.owner != null) { + val newOwner = User.findById(data.owner) ?: throw UserException("User not found") + organization.ownerId = newOwner.id + } + organization.updatedAt = Clock.System.now() + organization.toOrganizationFullResponse() + } + } + + fun deleteOrganization(token: Token, id: Int) { + logger.info("Deleting organization with id: $id") + transaction { + val user = token.getUser() + val organization = + Organization.findById(id) ?: throw OrganizationsException("Organization not found") + + if (organization.ownerId != user.id) { + throw OrganizationsException( + "You can't delete the organization. User is not the owner of the organization" + ) + } + + organization.delete() } + } } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/services/PostgresVectorStoreService.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/services/PostgresVectorStoreService.kt index 25ead6ad4..0a01570e7 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/services/PostgresVectorStoreService.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/services/PostgresVectorStoreService.kt @@ -14,62 +14,57 @@ import kotlinx.uuid.generateUUID import org.slf4j.Logger object PostgreSQLXef { - data class DBConfig( - val host: String, - val port: Int, - val database: String, - val user: String, - val password: String - ) + data class DBConfig( + val host: String, + val port: Int, + val database: String, + val user: String, + val password: String + ) - data class PGVectorStoreConfig( - val dbConfig: DBConfig, - val vectorSize: Int = 3, - val collectionName: String = "xef_collection", - val preDeleteCollection: Boolean = false, - val chunkSize: Int? = null, - ) + data class PGVectorStoreConfig( + val dbConfig: DBConfig, + val vectorSize: Int = 3, + val collectionName: String = "xef_collection", + val preDeleteCollection: Boolean = false, + val chunkSize: Int? = null, + ) } - class PostgresVectorStoreService( - private val config: PostgreSQLXef.PGVectorStoreConfig, - private val logger: Logger, - private val dataSource: HikariDataSource + private val config: PostgreSQLXef.PGVectorStoreConfig, + private val logger: Logger, + private val dataSource: HikariDataSource ) : VectorStoreService() { - override fun addCollection() { - dataSource.connection { - // Create collection - val uuid = UUID.generateUUID() - update(addNewCollection) { - bind(uuid.toString()) - bind(config.collectionName) - }.also { logger.info("Created collection ${config.collectionName}") } - } + override fun addCollection() { + dataSource.connection { + // Create collection + val uuid = UUID.generateUUID() + update(addNewCollection) { + bind(uuid.toString()) + bind(config.collectionName) + } + .also { logger.info("Created collection ${config.collectionName}") } } + } - override fun getVectorStore( - provider: Provider, - token: String - ): VectorStore { - val embeddings = when (provider) { - Provider.OPENAI -> OpenAI(token).DEFAULT_EMBEDDING - else -> OpenAI(token).DEFAULT_EMBEDDING - } + override fun getVectorStore(provider: Provider, token: String): VectorStore { + val embeddings = + when (provider) { + Provider.OPENAI -> OpenAI(token).DEFAULT_EMBEDDING + else -> OpenAI(token).DEFAULT_EMBEDDING + } - return PGVectorStore( - vectorSize = config.vectorSize, - dataSource = dataSource, - embeddings = embeddings, - collectionName = config.collectionName, - distanceStrategy = PGDistanceStrategy.Euclidean, - preDeleteCollection = config.preDeleteCollection, - requestConfig = - RequestConfig( - user = RequestConfig.Companion.User("user") - ), - chunkSize = config.chunkSize - ) - } + return PGVectorStore( + vectorSize = config.vectorSize, + dataSource = dataSource, + embeddings = embeddings, + collectionName = config.collectionName, + distanceStrategy = PGDistanceStrategy.Euclidean, + preDeleteCollection = config.preDeleteCollection, + requestConfig = RequestConfig(user = RequestConfig.Companion.User("user")), + chunkSize = config.chunkSize + ) + } } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/services/ProjectRepositoryService.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/services/ProjectRepositoryService.kt index 033ff5d60..14e58ceb1 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/services/ProjectRepositoryService.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/services/ProjectRepositoryService.kt @@ -7,130 +7,116 @@ import kotlinx.datetime.Clock import org.jetbrains.exposed.sql.transactions.transaction import org.slf4j.Logger -class ProjectRepositoryService( - private val logger: Logger -) { - fun createProject( - data: ProjectRequest, - token: Token - ): ProjectSimpleResponse { - logger.info("Creating project with name: ${data.name}") - return transaction { - val user = token.getUser() - - val organization = - Organization.findById(data.orgId) ?: throw OrganizationsException("Organization not found") - - if (user.organizations.none { it.id.value == data.orgId }) { - throw OrganizationsException("User is not part of the organization") - } - - val project = Project.new { - name = data.name - orgId = organization.id - } - project.toProjectSimpleResponse() +class ProjectRepositoryService(private val logger: Logger) { + fun createProject(data: ProjectRequest, token: Token): ProjectSimpleResponse { + logger.info("Creating project with name: ${data.name}") + return transaction { + val user = token.getUser() + + val organization = + Organization.findById(data.orgId) ?: throw OrganizationsException("Organization not found") + + if (user.organizations.none { it.id.value == data.orgId }) { + throw OrganizationsException("User is not part of the organization") + } + + val project = + Project.new { + name = data.name + orgId = organization.id } + project.toProjectSimpleResponse() } + } - fun getProjects( - token: Token - ): List { - logger.info("Getting projects") - return transaction { - val user = token.getUser() - - Project.find { ProjectsTable.orgId inList user.organizations.map { it.id } }.mapNotNull { project -> - val org = user.organizations.find { org -> org.id == project.orgId } - org?.let { - project.toProjectFullResponse(it) - } - } + fun getProjects(token: Token): List { + logger.info("Getting projects") + return transaction { + val user = token.getUser() + + Project.find { ProjectsTable.orgId inList user.organizations.map { it.id } } + .mapNotNull { project -> + val org = user.organizations.find { org -> org.id == project.orgId } + org?.let { project.toProjectFullResponse(it) } } } + } - fun getProject( - token: Token, - id: Int - ): ProjectFullResponse { - logger.info("Getting project") - return transaction { - val user = token.getUser() + fun getProject(token: Token, id: Int): ProjectFullResponse { + logger.info("Getting project") + return transaction { + val user = token.getUser() - val project = Project.findById(id) ?: throw ProjectException("Project not found") + val project = Project.findById(id) ?: throw ProjectException("Project not found") - val org = user.organizations.find { it.id == project.orgId } - ?: throw OrganizationsException("User is not part of the organization") + val org = + user.organizations.find { it.id == project.orgId } + ?: throw OrganizationsException("User is not part of the organization") - project.toProjectFullResponse(org) - } + project.toProjectFullResponse(org) } + } - fun getProjectsByOrganization( - token: Token, - orgId: Int - ): List { - logger.info("Getting projects") - return transaction { - val user = token.getUser() + fun getProjectsByOrganization(token: Token, orgId: Int): List { + logger.info("Getting projects") + return transaction { + val user = token.getUser() - if (user.organizations.none { it.id.value == orgId }) { - throw OrganizationsException("User is not part of the organization") - } + if (user.organizations.none { it.id.value == orgId }) { + throw OrganizationsException("User is not part of the organization") + } - Project.find { ProjectsTable.orgId eq orgId }.map { it.toProjectSimpleResponse() } - } + Project.find { ProjectsTable.orgId eq orgId }.map { it.toProjectSimpleResponse() } } - - fun updateProject( - token: Token, - data: ProjectUpdateRequest, - id: Int - ): ProjectFullResponse { - logger.info("Updating project with name: ${data.name}") - return transaction { - val user = token.getUser() - - val project = Project.findById(id) ?: throw ProjectException("Project not found") - - val organization = Organization.findById(project.orgId) - ?: throw OrganizationsException("Organization not found") - - if (organization.ownerId != user.id) { - throw OrganizationsException("You can't update the project. User is not the owner of the organization") - } - - // Updating the project - project.name = data.name - if (data.orgId != null) { - val newOrg = Organization.findById(data.orgId) - ?: throw OrganizationsException("Organization not found") - project.orgId = newOrg.id - } - project.updatedAt = Clock.System.now() - project.toProjectFullResponse(organization) - } + } + + fun updateProject(token: Token, data: ProjectUpdateRequest, id: Int): ProjectFullResponse { + logger.info("Updating project with name: ${data.name}") + return transaction { + val user = token.getUser() + + val project = Project.findById(id) ?: throw ProjectException("Project not found") + + val organization = + Organization.findById(project.orgId) + ?: throw OrganizationsException("Organization not found") + + if (organization.ownerId != user.id) { + throw OrganizationsException( + "You can't update the project. User is not the owner of the organization" + ) + } + + // Updating the project + project.name = data.name + if (data.orgId != null) { + val newOrg = + Organization.findById(data.orgId) + ?: throw OrganizationsException("Organization not found") + project.orgId = newOrg.id + } + project.updatedAt = Clock.System.now() + project.toProjectFullResponse(organization) } + } - fun deleteProject( - token: Token, - id: Int - ) { - logger.info("Deleting project with id: $id") - transaction { - val user = token.getUser() - val project = Project.findById(id) - ?: throw ProjectException("Project not found") - - val organization = Organization.findById(project.orgId) - ?: throw OrganizationsException("Organization not found") + fun deleteProject(token: Token, id: Int) { + logger.info("Deleting project with id: $id") + transaction { + val user = token.getUser() + val project = Project.findById(id) ?: throw ProjectException("Project not found") - if (organization.ownerId != user.id) { - throw OrganizationsException("You can't delete the project. User is not the owner of the organization") - } + val organization = + Organization.findById(project.orgId) + ?: throw OrganizationsException("Organization not found") - project.delete() + if (organization.ownerId != user.id) { + throw OrganizationsException( + "You can't delete the project. User is not the owner of the organization" + ) + } - } + project.delete() } + } } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/services/RepositoryService.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/services/RepositoryService.kt index a87cc09df..032506fcd 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/services/RepositoryService.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/services/RepositoryService.kt @@ -9,22 +9,19 @@ import com.zaxxer.hikari.HikariDataSource import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq object RepositoryService { - fun getHikariDataSource( - url: String, - usr: String, - passwd: String - ): HikariDataSource { - val hikariConfig = HikariConfig().apply { - jdbcUrl = url - username = usr - password = passwd - driverClassName = "org.postgresql.Driver" - } - - return HikariDataSource(hikariConfig) - } + fun getHikariDataSource(url: String, usr: String, passwd: String): HikariDataSource { + val hikariConfig = + HikariConfig().apply { + jdbcUrl = url + username = usr + password = passwd + driverClassName = "org.postgresql.Driver" + } + return HikariDataSource(hikariConfig) + } } fun Token.getUser() = - User.find { UsersTable.authToken eq this@getUser.value }.firstOrNull() ?: throw XefExceptions.UserException("User not found") + User.find { UsersTable.authToken eq this@getUser.value }.firstOrNull() + ?: throw XefExceptions.UserException("User not found") diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/services/TokenRepositoryService.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/services/TokenRepositoryService.kt index 2f5b3cc41..7a4369857 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/services/TokenRepositoryService.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/services/TokenRepositoryService.kt @@ -3,127 +3,115 @@ package com.xebia.functional.xef.server.services import com.xebia.functional.xef.server.db.tables.* import com.xebia.functional.xef.server.models.* import com.xebia.functional.xef.server.models.exceptions.XefExceptions.* -import org.jetbrains.exposed.sql.transactions.transaction +import java.util.UUID import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.transactions.transaction import org.slf4j.Logger -import java.util.UUID -class TokenRepositoryService( - private val logger: Logger -) { - fun createToken( - data: TokenRequest, - userToken: Token - ): TokenSimpleResponse { - return transaction { - val user = userToken.getUser() - - val project = - Project.findById(data.projectId) ?: throw OrganizationsException("Project not found") - - logger.info("Creating token with name ${data.name} from project ${project.name}") - - if (user.organizations.none { it.id == project.orgId }) { - throw OrganizationsException("User is not part of the organization of the ${project.name} project") - } - - val newXefToken = XefTokens.new { - name = data.name - userId = user.id - projectId = project.id - token = UUID.randomUUID().toString() - providersConfig = ProvidersConfig.empty - } - - newXefToken.toTokenSimpleResponse() +class TokenRepositoryService(private val logger: Logger) { + fun createToken(data: TokenRequest, userToken: Token): TokenSimpleResponse { + return transaction { + val user = userToken.getUser() + + val project = + Project.findById(data.projectId) ?: throw OrganizationsException("Project not found") + + logger.info("Creating token with name ${data.name} from project ${project.name}") + + if (user.organizations.none { it.id == project.orgId }) { + throw OrganizationsException( + "User is not part of the organization of the ${project.name} project" + ) + } + + val newXefToken = + XefTokens.new { + name = data.name + userId = user.id + projectId = project.id + token = UUID.randomUUID().toString() + providersConfig = ProvidersConfig.empty } - } - - fun getTokens( - userToken: Token - ): List { - logger.info("Getting tokens") - return transaction { - val user = userToken.getUser() - - val userProjects = - Project.find { ProjectsTable.orgId inList user.organizations.map { it.id } }.mapNotNull { project -> - val org = user.organizations.find { org -> org.id == project.orgId } - org?.let { - project.toProjectSimpleResponse() - } - } - - XefTokens.find { XefTokensTable.userId eq user.id }.mapNotNull { xefToken -> - userProjects.find { project -> project.id == xefToken.projectId.value }?.let { - xefToken.toTokenFullResponse(it) - } - } + newXefToken.toTokenSimpleResponse() + } + } + + fun getTokens(userToken: Token): List { + logger.info("Getting tokens") + return transaction { + val user = userToken.getUser() + + val userProjects = + Project.find { ProjectsTable.orgId inList user.organizations.map { it.id } } + .mapNotNull { project -> + val org = user.organizations.find { org -> org.id == project.orgId } + org?.let { project.toProjectSimpleResponse() } + } + + XefTokens.find { XefTokensTable.userId eq user.id } + .mapNotNull { xefToken -> + userProjects + .find { project -> project.id == xefToken.projectId.value } + ?.let { xefToken.toTokenFullResponse(it) } } } - - fun getTokensByProject( - userToken: Token, - projectId: Int - ): List { - logger.info("Getting tokens by project") - return transaction { - val user = userToken.getUser() - - XefTokens.find((XefTokensTable.userId eq user.id) and (XefTokensTable.projectId eq projectId)).map { xefToken -> - val project = Project.findById(xefToken.projectId) ?: throw ProjectException("Project not found") - xefToken.toTokenFullResponse(project.toProjectSimpleResponse()) - } + } + + fun getTokensByProject(userToken: Token, projectId: Int): List { + logger.info("Getting tokens by project") + return transaction { + val user = userToken.getUser() + + XefTokens.find((XefTokensTable.userId eq user.id) and (XefTokensTable.projectId eq projectId)) + .map { xefToken -> + val project = + Project.findById(xefToken.projectId) ?: throw ProjectException("Project not found") + xefToken.toTokenFullResponse(project.toProjectSimpleResponse()) } } + } - fun updateToken( - userToken: Token, - data: TokenUpdateRequest, - id: Int - ): TokenFullResponse { - logger.info("Updating token with name: ${data.name}") - return transaction { - val user = userToken.getUser() - - val xefToken = XefTokens.findById(id) ?: throw XefTokenException("Token not found") + fun updateToken(userToken: Token, data: TokenUpdateRequest, id: Int): TokenFullResponse { + logger.info("Updating token with name: ${data.name}") + return transaction { + val user = userToken.getUser() - val project = Project.findById(xefToken.projectId) ?: throw ProjectException("Project not found") + val xefToken = XefTokens.findById(id) ?: throw XefTokenException("Token not found") - if (user.organizations.none { it.id == project.orgId }) { - throw OrganizationsException("User is not part of the organization of this project") - } + val project = + Project.findById(xefToken.projectId) ?: throw ProjectException("Project not found") - val xefTokenUpdated = xefToken.apply { - name = data.name - providersConfig = data.providerConfig - } + if (user.organizations.none { it.id == project.orgId }) { + throw OrganizationsException("User is not part of the organization of this project") + } - xefTokenUpdated.toTokenFullResponse(project.toProjectSimpleResponse()) + val xefTokenUpdated = + xefToken.apply { + name = data.name + providersConfig = data.providerConfig } + + xefTokenUpdated.toTokenFullResponse(project.toProjectSimpleResponse()) } + } - fun deleteToken( - userToken: Token, - id: Int - ) { - logger.info("Deleting token: $id") - transaction { - val user = userToken.getUser() + fun deleteToken(userToken: Token, id: Int) { + logger.info("Deleting token: $id") + transaction { + val user = userToken.getUser() - val xefToken = XefTokens.findById(id) ?: throw XefTokenException("Token not found") + val xefToken = XefTokens.findById(id) ?: throw XefTokenException("Token not found") - val project = Project.findById(xefToken.projectId) ?: throw ProjectException("Project not found") + val project = + Project.findById(xefToken.projectId) ?: throw ProjectException("Project not found") - if (user.organizations.none { it.id == project.orgId }) { - throw OrganizationsException("User is not part of the organization of this project") - } + if (user.organizations.none { it.id == project.orgId }) { + throw OrganizationsException("User is not part of the organization of this project") + } - XefTokensTable.deleteWhere { - this.id eq id - } - } + XefTokensTable.deleteWhere { this.id eq id } } + } } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/services/UserRepositoryService.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/services/UserRepositoryService.kt index 26703d6e2..e7c741bb6 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/services/UserRepositoryService.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/services/UserRepositoryService.kt @@ -12,51 +12,42 @@ import kotlinx.uuid.generateUUID import org.jetbrains.exposed.sql.transactions.transaction import org.slf4j.Logger -class UserRepositoryService( - private val logger: Logger -) { - - fun register(request: RegisterRequest): LoginResponse { - logger.info("Registering user ${request.email}" ) - - return transaction { - if (User.find { UsersTable.email eq request.email }.count() > 0) { - throw UserException("User already exists") - } - - val newSalt = HashUtils.generateSalt() - val passwordHashed = HashUtils.createHash(request.password, newSalt) - val user = transaction { - User.new { - name = request.name - email = request.email - passwordHash = passwordHashed - salt = newSalt - authToken = UUID.generateUUID(passwordHashed).toString() - } - } - LoginResponse(user.authToken) +class UserRepositoryService(private val logger: Logger) { + + fun register(request: RegisterRequest): LoginResponse { + logger.info("Registering user ${request.email}") + + return transaction { + if (User.find { UsersTable.email eq request.email }.count() > 0) { + throw UserException("User already exists") + } + + val newSalt = HashUtils.generateSalt() + val passwordHashed = HashUtils.createHash(request.password, newSalt) + val user = transaction { + User.new { + name = request.name + email = request.email + passwordHash = passwordHashed + salt = newSalt + authToken = UUID.generateUUID(passwordHashed).toString() } + } + LoginResponse(user.authToken) } + } - fun login(request: LoginRequest): LoginResponse { - logger.info("Login user ${request.email}") - return transaction { - val user = - User.find { UsersTable.email eq request.email }.firstOrNull() ?: throw UserException("User not found") - - if (!HashUtils.checkPassword( - request.password, - user.salt, - user.passwordHash - ) - ) - throw Exception("Invalid password") - - LoginResponse(user.authToken) - } + fun login(request: LoginRequest): LoginResponse { + logger.info("Login user ${request.email}") + return transaction { + val user = + User.find { UsersTable.email eq request.email }.firstOrNull() + ?: throw UserException("User not found") + if (!HashUtils.checkPassword(request.password, user.salt, user.passwordHash)) + throw Exception("Invalid password") + LoginResponse(user.authToken) } - + } } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/services/VectorStoreService.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/services/VectorStoreService.kt index ededcc941..7a0a30ec0 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/services/VectorStoreService.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/services/VectorStoreService.kt @@ -5,10 +5,7 @@ import com.xebia.functional.xef.store.VectorStore abstract class VectorStoreService { - abstract fun addCollection(): Unit + abstract fun addCollection(): Unit - abstract fun getVectorStore( - provider: Provider = Provider.OPENAI, - token: String - ): VectorStore + abstract fun getVectorStore(provider: Provider = Provider.OPENAI, token: String): VectorStore } diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/utils/HashUtils.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/utils/HashUtils.kt index c80fe1732..b7860dee4 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/utils/HashUtils.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/utils/HashUtils.kt @@ -8,30 +8,30 @@ import javax.crypto.spec.PBEKeySpec object HashUtils { - fun generateSalt(): ByteArray { - val random = SecureRandom() - val salt = ByteArray(16) - random.nextBytes(salt) - return salt - } + fun generateSalt(): ByteArray { + val random = SecureRandom() + val salt = ByteArray(16) + random.nextBytes(salt) + return salt + } - fun checkPassword(password: String, salt: ByteArray, expectedHash: ByteArray): Boolean { - val pwdHash = createHash(password, salt) - if (pwdHash.size != expectedHash.size) return false - return pwdHash.indices.all { pwdHash[it] == expectedHash[it] } - } + fun checkPassword(password: String, salt: ByteArray, expectedHash: ByteArray): Boolean { + val pwdHash = createHash(password, salt) + if (pwdHash.size != expectedHash.size) return false + return pwdHash.indices.all { pwdHash[it] == expectedHash[it] } + } - fun createHash(password: String, salt: ByteArray): ByteArray { - val spec = PBEKeySpec(password.toCharArray(), salt, 1000, 256) - try { - val skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1") - return skf.generateSecret(spec).encoded - } catch (e: NoSuchAlgorithmException) { - throw AssertionError("Error: ${e.message}", e) - } catch (e: InvalidKeySpecException) { - throw AssertionError("Error: ${e.message}", e) - } finally { - spec.clearPassword() - } + fun createHash(password: String, salt: ByteArray): ByteArray { + val spec = PBEKeySpec(password.toCharArray(), salt, 1000, 256) + try { + val skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1") + return skf.generateSecret(spec).encoded + } catch (e: NoSuchAlgorithmException) { + throw AssertionError("Error: ${e.message}", e) + } catch (e: InvalidKeySpecException) { + throw AssertionError("Error: ${e.message}", e) + } finally { + spec.clearPassword() } + } } diff --git a/server/src/test/kotlin/com/xebia/functional/xef/server/postgresql/DBHelpers.kt b/server/src/test/kotlin/com/xebia/functional/xef/server/postgresql/DBHelpers.kt index 295c57709..a355d4cd0 100644 --- a/server/src/test/kotlin/com/xebia/functional/xef/server/postgresql/DBHelpers.kt +++ b/server/src/test/kotlin/com/xebia/functional/xef/server/postgresql/DBHelpers.kt @@ -7,40 +7,33 @@ import org.jetbrains.exposed.dao.id.EntityID object DBHelpers { - fun testUser( - fName: String = "test", - fEmail: String = "test@test/com", - fPasswordHash: String = "passwordTest", - fSalt: String = "saltTest", - fAuthToken: String = "authTokenTest" - ): User { - return User.new { - name = fName - email = fEmail - passwordHash = fPasswordHash.toByteArray() - salt = fSalt.toByteArray() - authToken = fAuthToken - } + fun testUser( + fName: String = "test", + fEmail: String = "test@test/com", + fPasswordHash: String = "passwordTest", + fSalt: String = "saltTest", + fAuthToken: String = "authTokenTest" + ): User { + return User.new { + name = fName + email = fEmail + passwordHash = fPasswordHash.toByteArray() + salt = fSalt.toByteArray() + authToken = fAuthToken } + } - fun testOrganization( - fName: String = "testOrg", - fOwnerId: EntityID - ): Organization { - return Organization.new { - name = fName - ownerId = fOwnerId - } + fun testOrganization(fName: String = "testOrg", fOwnerId: EntityID): Organization { + return Organization.new { + name = fName + ownerId = fOwnerId } + } - fun testProject( - fName: String = "testProject", - fOrgId: EntityID - ): Project { - return Project.new { - name = fName - orgId = fOrgId - } + fun testProject(fName: String = "testProject", fOrgId: EntityID): Project { + return Project.new { + name = fName + orgId = fOrgId } - + } } diff --git a/server/src/test/kotlin/com/xebia/functional/xef/server/postgresql/XefDatabaseTest.kt b/server/src/test/kotlin/com/xebia/functional/xef/server/postgresql/XefDatabaseTest.kt index 00cbabc01..800b7ca9b 100644 --- a/server/src/test/kotlin/com/xebia/functional/xef/server/postgresql/XefDatabaseTest.kt +++ b/server/src/test/kotlin/com/xebia/functional/xef/server/postgresql/XefDatabaseTest.kt @@ -16,140 +16,144 @@ import org.junit.jupiter.api.TestInstance import org.testcontainers.containers.PostgreSQLContainer object TestDatabase { - private val pgContainer: PostgreSQLContainer = - PostgreSQLContainer("postgres:alpine3.18").apply { - withDatabaseName("xefdb") - withUsername("postgres") - withPassword("postgres") - start() - } - - init { - val config = HikariConfig().apply { - jdbcUrl = pgContainer.jdbcUrl.replace("localhost", "0.0.0.0") - username = pgContainer.username - password = pgContainer.password - driverClassName = "org.postgresql.Driver" - } - - val dataSource = HikariDataSource(config) - - Database.connect(dataSource) + private val pgContainer: PostgreSQLContainer = + PostgreSQLContainer("postgres:alpine3.18").apply { + withDatabaseName("xefdb") + withUsername("postgres") + withPassword("postgres") + start() } + + init { + val config = + HikariConfig().apply { + jdbcUrl = pgContainer.jdbcUrl.replace("localhost", "0.0.0.0") + username = pgContainer.username + password = pgContainer.password + driverClassName = "org.postgresql.Driver" + } + + val dataSource = HikariDataSource(config) + + Database.connect(dataSource) + } } @TestInstance(TestInstance.Lifecycle.PER_CLASS) class XefDatabaseTest { - @BeforeAll - fun setup() { - TestDatabase - } - - @BeforeEach - fun cleanup() { - transaction { - SchemaUtils.drop(UsersTable, OrganizationsTable, ProjectsTable, UsersOrgsTable, XefTokensTable) - SchemaUtils.create(UsersTable, OrganizationsTable, ProjectsTable, UsersOrgsTable, XefTokensTable) - } + @BeforeAll + fun setup() { + TestDatabase + } + + @BeforeEach + fun cleanup() { + transaction { + SchemaUtils.drop( + UsersTable, + OrganizationsTable, + ProjectsTable, + UsersOrgsTable, + XefTokensTable + ) + SchemaUtils.create( + UsersTable, + OrganizationsTable, + ProjectsTable, + UsersOrgsTable, + XefTokensTable + ) } + } - @Test - fun crudUser() { - transaction { - val newUser = DBHelpers.testUser() + @Test + fun crudUser() { + transaction { + val newUser = DBHelpers.testUser() - val retrievedUser = User.findById(newUser.id) - assertEquals("test", retrievedUser?.name) + val retrievedUser = User.findById(newUser.id) + assertEquals("test", retrievedUser?.name) - retrievedUser?.apply { - name = "test2" - } + retrievedUser?.apply { name = "test2" } - val updatedUser = User.findById(newUser.id) - assertEquals("test2", updatedUser?.name) + val updatedUser = User.findById(newUser.id) + assertEquals("test2", updatedUser?.name) - updatedUser?.delete() - val deletedUser = User.findById(newUser.id) - assertNull(deletedUser) - } + updatedUser?.delete() + val deletedUser = User.findById(newUser.id) + assertNull(deletedUser) } - - @Test - fun crudOrganization() { - transaction { - val ownerUser = DBHelpers.testUser() - val newOrganization = DBHelpers.testOrganization(fOwnerId = ownerUser.id) - - val retrievedOrganization = Organization.findById(newOrganization.id) - assertEquals("testOrg", retrievedOrganization?.name) - assertEquals(ownerUser.id, retrievedOrganization?.ownerId) - - ownerUser.delete() - val deletedOrganization = Organization.find { OrganizationsTable.name eq newOrganization.name } - assertEquals(0, deletedOrganization.count()) - - } - + } + + @Test + fun crudOrganization() { + transaction { + val ownerUser = DBHelpers.testUser() + val newOrganization = DBHelpers.testOrganization(fOwnerId = ownerUser.id) + + val retrievedOrganization = Organization.findById(newOrganization.id) + assertEquals("testOrg", retrievedOrganization?.name) + assertEquals(ownerUser.id, retrievedOrganization?.ownerId) + + ownerUser.delete() + val deletedOrganization = + Organization.find { OrganizationsTable.name eq newOrganization.name } + assertEquals(0, deletedOrganization.count()) } - - @Test - fun crudProjects() { - transaction { - val ownerUser = DBHelpers.testUser() - val newOrganization = DBHelpers.testOrganization(fOwnerId = ownerUser.id) - val newProject = DBHelpers.testProject(fOrgId = newOrganization.id) - - val retrievedProject = Project.findById(newProject.id) - assertEquals(newProject.name, retrievedProject?.name) - assertEquals(newOrganization.id, retrievedProject?.orgId) - } + } + + @Test + fun crudProjects() { + transaction { + val ownerUser = DBHelpers.testUser() + val newOrganization = DBHelpers.testOrganization(fOwnerId = ownerUser.id) + val newProject = DBHelpers.testProject(fOrgId = newOrganization.id) + + val retrievedProject = Project.findById(newProject.id) + assertEquals(newProject.name, retrievedProject?.name) + assertEquals(newOrganization.id, retrievedProject?.orgId) } - - @Test - fun organizationsAndUsers() { - transaction { - val ownerUser = DBHelpers.testUser() - val newOrganization = DBHelpers.testOrganization(fOwnerId = ownerUser.id) - ownerUser.organizations = SizedCollection(listOf(newOrganization)) - } - - transaction { - val user = User.all().first() - assertEquals(1, user.organizations.count()) - val newOrganization2 = DBHelpers.testOrganization("testOrg2", fOwnerId = user.id) - val currentOrganizations = user.organizations - user.organizations = SizedCollection(currentOrganizations + newOrganization2) - assertEquals(2, user.organizations.count()) - } + } + + @Test + fun organizationsAndUsers() { + transaction { + val ownerUser = DBHelpers.testUser() + val newOrganization = DBHelpers.testOrganization(fOwnerId = ownerUser.id) + ownerUser.organizations = SizedCollection(listOf(newOrganization)) } - @Test - fun crudXefTokens() { - transaction { - val user = DBHelpers.testUser() - val organization = DBHelpers.testOrganization(fOwnerId = user.id) - user.organizations = SizedCollection(listOf(organization)) - val project = DBHelpers.testProject(fOrgId = organization.id) - - val config = ProvidersConfig( - openAI = OpenAIConf( - token = "testToken", - url = "testUrl" - ), - gcp = null - ) - XefTokens.new { - userId = user.id - projectId = project.id - name = "testEnv" - token = "testToken" - providersConfig = config - } - } - transaction { - val token = XefTokens.all().first() - assertEquals("testToken", token.token) - } + transaction { + val user = User.all().first() + assertEquals(1, user.organizations.count()) + val newOrganization2 = DBHelpers.testOrganization("testOrg2", fOwnerId = user.id) + val currentOrganizations = user.organizations + user.organizations = SizedCollection(currentOrganizations + newOrganization2) + assertEquals(2, user.organizations.count()) } - + } + + @Test + fun crudXefTokens() { + transaction { + val user = DBHelpers.testUser() + val organization = DBHelpers.testOrganization(fOwnerId = user.id) + user.organizations = SizedCollection(listOf(organization)) + val project = DBHelpers.testProject(fOrgId = organization.id) + + val config = + ProvidersConfig(openAI = OpenAIConf(token = "testToken", url = "testUrl"), gcp = null) + XefTokens.new { + userId = user.id + projectId = project.id + name = "testEnv" + token = "testToken" + providersConfig = config + } + } + transaction { + val token = XefTokens.all().first() + assertEquals("testToken", token.token) + } + } }