Skip to content

Commit

Permalink
added tegral and refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
José Carlos Montañez authored and José Carlos Montañez committed May 7, 2024
1 parent 1aef223 commit 47a8421
Show file tree
Hide file tree
Showing 10 changed files with 467 additions and 73 deletions.
9 changes: 8 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ progressbar = "0.10.0"
jmf = "2.1.1e"
mp3-wav-converter = "1.0.4"
yamlkt="0.13.0"
tegral = "0.0.4"


[libraries]
Expand Down Expand Up @@ -115,7 +116,13 @@ opentelemetry-extension-kotlin = { module = "io.opentelemetry:opentelemetry-exte
progressbar = { module = "me.tongfei:progressbar", version.ref = "progressbar" }
jmf = { module = "javax.media:jmf", version.ref = "jmf" }
mp3-wav-converter = { module = "com.sipgate:mp3-wav", version.ref = "mp3-wav-converter" }

tegral-catalog = { module = "guru.zoroark.tegral:tegral-catalog", version.ref = "tegral" }
tegral-core = { module = "guru.zoroark.tegral:tegral-core", version.ref = "tegral" }
tegral-openapi-dsl = { module = "guru.zoroark.tegral:tegral-openapi-dsl", version.ref = "tegral" }
tegral-openapi-scriptdef = { module = "guru.zoroark.tegral:tegral-openapi-scriptdef", version.ref = "tegral" }
tegral-openapi-ktor = { module = "guru.zoroark.tegral:tegral-openapi-ktor", version.ref = "tegral" }
tegral-openapi-ktor-resources = { module = "guru.zoroark.tegral:tegral-openapi-ktor-resources", version.ref = "tegral" }
tegral-openapi-ktorui = { module = "guru.zoroark.tegral:tegral-openapi-ktorui", version.ref = "tegral" }


[bundles]
Expand Down
6 changes: 6 additions & 0 deletions server/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ dependencies {
implementation(libs.ktor.server.status.pages)
implementation(libs.suspendApp.core)
implementation(libs.suspendApp.ktor)
implementation(libs.tegral.core)
implementation(libs.tegral.openapi.dsl)
implementation(libs.tegral.openapi.scriptdef)
implementation(libs.tegral.openapi.ktor)
implementation(libs.tegral.openapi.ktor.resources)
implementation(libs.tegral.openapi.ktorui)
implementation(libs.uuid)
implementation(projects.xefCore)
implementation(projects.xefPostgresql)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import com.xebia.functional.xef.server.http.routes.xefRoutes
import com.xebia.functional.xef.server.services.hikariDataSource
import com.xebia.functional.xef.server.services.vectorStoreService
import com.xebia.functional.xef.store.migrations.runDatabaseMigrations
import guru.zoroark.tegral.openapi.ktor.TegralOpenApiKtor
import guru.zoroark.tegral.openapi.ktor.openApiEndpoint
import guru.zoroark.tegral.openapi.ktorui.TegralSwaggerUiKtor
import guru.zoroark.tegral.openapi.ktorui.swaggerUiEndpoint
import io.github.oshai.kotlinlogging.KotlinLogging
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
Expand Down Expand Up @@ -77,10 +81,14 @@ object Server {
authenticate { tokenCredential -> UserIdPrincipal(tokenCredential.token) }
}
}
install(TegralOpenApiKtor) { title = "Xef Server" }
install(TegralSwaggerUiKtor)
exceptionsHandler()
routing {
xefRoutes(logger)
aiRoutes(ktorClient)
openApiEndpoint("/openapi")
swaggerUiEndpoint(path = "/docs", openApiPath = "/openapi")
}
}
awaitCancellation()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package com.xebia.functional.xef.server.http.routes

import com.xebia.functional.openai.generated.model.ChatCompletionRequestMessage
import com.xebia.functional.xef.server.models.Token
import com.xebia.functional.xef.server.models.exceptions.XefExceptions
import guru.zoroark.tegral.openapi.dsl.schema
import guru.zoroark.tegral.openapi.ktor.resources.ResourceDescription
import guru.zoroark.tegral.openapi.ktor.resources.describeResource
import guru.zoroark.tegral.openapi.ktor.resources.postD
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.resources.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
Expand All @@ -22,7 +27,7 @@ fun Routing.aiRoutes(client: HttpClient) {
val openAiUrl = "https://api.openai.com/v1"

authenticate("auth-bearer") {
post("/chat/completions") {
postD<ChatCompletionRoutes> {
val token = call.getToken()
val byteArrayBody = call.receiveChannel().toByteArray()
val body = byteArrayBody.toString(Charsets.UTF_8)
Expand All @@ -37,14 +42,48 @@ fun Routing.aiRoutes(client: HttpClient) {
}
}

post("/embeddings") {
postD<EmbeddingsRoutes> {
val token = call.getToken()
val context = call.receiveChannel().toByteArray()
client.makeRequest(call, "$openAiUrl/embeddings", context, token)
}
}
}

@Resource("/chat/completions")
class ChatCompletionRoutes {
companion object :
ResourceDescription by describeResource({
tags += "AI"
post {
description = "Create chat completions"
body {
description = "The chat details"
required = true
json { schema<ChatCompletionRequestMessage>() }
}
HttpStatusCode.OK.value response { description = "Chat completions" }
}
})
}

@Resource("/embeddings")
class EmbeddingsRoutes {
companion object :
ResourceDescription by describeResource({
tags += "AI"
post {
description = "Create embeddings"
body {
description = "The context"
required = true
json { schema<ByteArray>() }
}
HttpStatusCode.OK.value response { description = "Embeddings" }
}
})
}

private val conflictingRequestHeaders =
listOf("Host", "Content-Type", "Content-Length", "Accept", "Accept-Encoding")
private val conflictingResponseHeaders = listOf("Content-Length")
Expand Down Expand Up @@ -97,12 +136,3 @@ internal fun HeadersBuilder.copyFrom(headers: Headers) =
headers
.filter { key, _ -> !conflictingRequestHeaders.any { it.equals(key, true) } }
.forEach { key, values -> appendMissing(key, values) }

fun ApplicationCall.getToken(): Token =
principal<UserIdPrincipal>()?.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")
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.xebia.functional.xef.server.http.routes

import arrow.core.raise.catch
import com.xebia.functional.xef.server.models.Token
import com.xebia.functional.xef.server.models.exceptions.XefExceptions
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import kotlinx.serialization.json.Json

fun ApplicationCall.getToken(): Token =
principal<UserIdPrincipal>()?.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")

suspend inline fun <reified T> ApplicationCall.decodeFromStringRequest(): T =
catch({ Json.decodeFromString<T>(this.receive<String>()) }) {
throw XefExceptions.ValidationException("Invalid ${T::class.simpleName}")
}
Original file line number Diff line number Diff line change
@@ -1,53 +1,142 @@
package com.xebia.functional.xef.server.http.routes

import com.xebia.functional.xef.server.models.OrganizationFullResponse
import com.xebia.functional.xef.server.models.OrganizationRequest
import com.xebia.functional.xef.server.models.OrganizationUpdateRequest
import com.xebia.functional.xef.server.models.UserResponse
import com.xebia.functional.xef.server.services.OrganizationRepositoryService
import guru.zoroark.tegral.openapi.dsl.schema
import guru.zoroark.tegral.openapi.ktor.resources.*
import io.ktor.http.*
import io.ktor.resources.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.json.Json
import io.ktor.server.routing.Routing
import io.swagger.v3.oas.models.media.StringSchema

fun Routing.organizationRoutes(orgRepositoryService: OrganizationRepositoryService) {
authenticate("auth-bearer") {
get("/v1/settings/org") {
getD<OrganizationRoutes> {
val token = call.getToken()
val response = orgRepositoryService.getOrganizations(token)
call.respond(response)
}
get("/v1/settings/org/{id}") {
postD<OrganizationRoutes> {
val request = call.decodeFromStringRequest<OrganizationRequest>()
val token = call.getToken()
val id = call.getId()
val response = orgRepositoryService.getOrganization(token, id)
call.respond(response)
val response = orgRepositoryService.createOrganization(request, token)
call.respond(status = HttpStatusCode.Created, response)
}
get("/v1/settings/org/{id}/users") {
getD<OrganizationDetailsRoutes> {
val token = call.getToken()
val id = call.getId()
val response = orgRepositoryService.getUsersInOrganization(token, id)
val response = orgRepositoryService.getOrganization(token, id)
call.respond(response)
}
post("/v1/settings/org") {
val request = Json.decodeFromString<OrganizationRequest>(call.receive<String>())
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<OrganizationUpdateRequest>(call.receive<String>())
putD<OrganizationDetailsRoutes> {
val request = call.decodeFromStringRequest<OrganizationUpdateRequest>()
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}") {
deleteD<OrganizationDetailsRoutes> {
val token = call.getToken()
val id = call.getId()
val response = orgRepositoryService.deleteOrganization(token, id)
call.respond(status = HttpStatusCode.NoContent, response)
}
getD<OrganizationUsersRoutes> {
val token = call.getToken()
val id = call.getId()
val response = orgRepositoryService.getUsersInOrganization(token, id)
call.respond(response)
}
}
}

@Resource("/v1/settings/org")
class OrganizationRoutes {
companion object :
ResourceDescription by describeResource({
get {
description = "Get all organizations"
tags += "Organization"
HttpStatusCode.OK.value response
{
description = "List of organizations"
json { schema<List<OrganizationFullResponse>>() }
}
}
post {
description = "Create an organization"
tags += "Organization"
body {
description = "The organization to create"
required = true
json { schema<OrganizationRequest>() }
}
}
})
}

@Resource("/v1/settings/org/{id}")
class OrganizationDetailsRoutes {
companion object :
ResourceDescription by describeResource({
"id" pathParameter
{
description = "Organization ID"
required = true
schema = StringSchema()
example = "org_123"
}
get {
description = "Get organization details"
tags += "Organization"
HttpStatusCode.OK.value response
{
description = "Organization details"
json { schema<OrganizationFullResponse>() }
}
}
put {
description = "Update organization details"
tags += "Organization"
body {
description = "The organization details to update"
required = true
json { schema<OrganizationUpdateRequest>() }
}
HttpStatusCode.NoContent.value response { description = "Organization updated" }
}
delete {
description = "Delete organization"
tags += "Organization"
}
})
}

@Resource("/v1/settings/org/{id}/users")
class OrganizationUsersRoutes {
companion object :
ResourceDescription by describeResource({
"id" pathParameter
{
description = "Organization ID"
required = true
schema = StringSchema()
example = "org_123"
}
get {
description = "Get users in organization"
tags += "Organization"
HttpStatusCode.OK.value response
{
description = "List of users in organization"
json { schema<List<UserResponse>>() }
}
}
})
}
Loading

0 comments on commit 47a8421

Please sign in to comment.