package dev.svitan.services import io.ktor.server.plugins.NotFoundException import kotlinx.serialization.Serializable import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.transactions.transaction import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.UUID enum class ActionKind { TEXT, SCRIPT; companion object { fun from(it: String): ActionKind { return when (it.lowercase()) { "text" -> TEXT "script" -> SCRIPT else -> throw IllegalArgumentException("Invalid action kind: $it") } } } override fun toString(): String { return super.toString().lowercase() } } @Serializable data class ActionDTO( val id: String, val name: String, val kind: ActionKind, val source: String, val authId: String, val createdAt: String, val updatedAt: String ) @Serializable data class NewActionDTO( val name: String, val kind: String, val source: String, val authId: String ) @Serializable data class UpdateActionDTO( val name: String?, val kind: String?, val source: String? ) @Serializable data class RunActionDTO( val pin: String, val key: String ) class ActionService { object Actions : Table("actions") { val id = uuid("id").autoGenerate() val name = text("name").uniqueIndex() val kind = text("kind") val aSource = text("source") // `source` is another property val auth = uuid("auth_id").references(AuthService.Auths.id) val createdAt = text("created_at") val updatedAt = text("updated_at") override val primaryKey = PrimaryKey(id) } companion object { fun init() { transaction { SchemaUtils.create(Actions) } } fun create(action: NewActionDTO): UUID { val id = transaction { val now = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) val authId = UUID.fromString(action.authId) AuthService.read(authId) ?: throw NotFoundException("auth not found") Actions.insert { it[name] = action.name it[kind] = ActionKind.from(action.kind).toString() it[aSource] = if (action.kind == ActionKind.SCRIPT.toString()) "" else action.source it[auth] = authId it[createdAt] = now it[updatedAt] = now }[Actions.id] } if (action.kind == ActionKind.SCRIPT.toString()) { ExecutorService.writeFile(id, action.source) } return id } fun read(id: UUID): ActionDTO? = transaction { Actions.selectAll() .where { Actions.id eq id } .map { ActionDTO( it[Actions.id].toString(), it[Actions.name], ActionKind.from(it[Actions.kind]), it[Actions.aSource], it[Actions.auth].toString(), it[Actions.createdAt], it[Actions.updatedAt] ) }.singleOrNull() } fun readAll(): List = transaction { Actions.selectAll().map { ActionDTO( it[Actions.id].toString(), it[Actions.name], ActionKind.from(it[Actions.kind]), it[Actions.aSource], it[Actions.auth].toString(), it[Actions.createdAt], it[Actions.updatedAt] ) } } fun update(id: UUID, action: UpdateActionDTO) { val oldAction = read(id) ?: throw NotFoundException("action not found") transaction { val now = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) Actions.update({ Actions.id eq id }) { if (action.name != null) it[name] = action.name if (action.kind != null) it[kind] = ActionKind.from(action.kind).toString() if (action.source != null) it[aSource] = action.source it[updatedAt] = now } } if (action.kind == ActionKind.SCRIPT.toString() && action.source != null) { ExecutorService.writeFile(id, action.source) } else if (action.kind == ActionKind.TEXT.toString() && oldAction.kind == ActionKind.SCRIPT) { ExecutorService.deleteFile(id) } } fun delete(id: UUID) { val action = read(id) ?: throw NotFoundException("action not found") transaction { Actions.deleteWhere { Actions.id eq id } } if (action.kind == ActionKind.SCRIPT) { ExecutorService.deleteFile(id) } } } }