Database works
This commit is contained in:
@@ -15,3 +15,26 @@
|
||||
END $$;
|
||||
|
||||
2026-05-13 20:53:18.805 [main] DEBUG Exposed - SELECT humanbeing.id, humanbeing."name", humanbeing.coordinates, humanbeing.creation_date, humanbeing.real_hero, humanbeing.has_toothpick, humanbeing.impact_speed, humanbeing.soundtrack_name, humanbeing.minutes_of_waiting, humanbeing.car, humanbeing.weapon_type FROM humanbeing
|
||||
2026-05-14 12:02:58.675 [main] DEBUG Exposed -
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE weapontype AS ENUM ('hammer', 'shotgun', 'bat');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
2026-05-14 12:02:59.051 [main] DEBUG Exposed - CREATE TABLE IF NOT EXISTS coordinates (id uuid PRIMARY KEY, x BIGINT NULL, y INT NULL)
|
||||
2026-05-14 12:02:59.301 [main] DEBUG Exposed - CREATE TABLE IF NOT EXISTS car (id uuid PRIMARY KEY, cool BOOLEAN NULL)
|
||||
2026-05-14 12:02:59.750 [main] DEBUG Exposed - CREATE TABLE IF NOT EXISTS "user" (id uuid PRIMARY KEY, username TEXT NOT NULL, "password" TEXT NOT NULL, email TEXT NOT NULL)
|
||||
2026-05-14 12:02:59.946 [main] DEBUG Exposed - CREATE TABLE IF NOT EXISTS humanbeing (id uuid PRIMARY KEY, owner_id uuid NOT NULL, "name" TEXT NOT NULL, coordinates uuid NOT NULL, creation_date DATE NOT NULL, real_hero BOOLEAN NOT NULL, has_toothpick BOOLEAN NOT NULL, impact_speed DOUBLE PRECISION NOT NULL, soundtrack_name TEXT NOT NULL, minutes_of_waiting REAL NOT NULL, car uuid NOT NULL, weapon_type weapontype DEFAULT NULL NULL, CONSTRAINT fk_humanbeing_owner_id__id FOREIGN KEY (owner_id) REFERENCES "user"(id) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT fk_humanbeing_coordinates__id FOREIGN KEY (coordinates) REFERENCES coordinates(id) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT fk_humanbeing_car__id FOREIGN KEY (car) REFERENCES car(id) ON DELETE RESTRICT ON UPDATE RESTRICT)
|
||||
2026-05-14 12:03:00.829 [main] DEBUG Exposed - SELECT humanbeing.id, humanbeing.owner_id, humanbeing."name", humanbeing.coordinates, humanbeing.creation_date, humanbeing.real_hero, humanbeing.has_toothpick, humanbeing.impact_speed, humanbeing.soundtrack_name, humanbeing.minutes_of_waiting, humanbeing.car, humanbeing.weapon_type FROM humanbeing
|
||||
2026-05-15 13:21:48.675 [main] DEBUG Exposed -
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE weapontype AS ENUM ('HAMMER', 'SHOTGUN', 'BAT');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
2026-05-15 13:21:49.467 [main] DEBUG Exposed - CREATE TABLE IF NOT EXISTS coordinates (id uuid PRIMARY KEY, x BIGINT NULL, y INT NULL)
|
||||
2026-05-15 13:21:49.922 [main] DEBUG Exposed - CREATE TABLE IF NOT EXISTS car (id uuid PRIMARY KEY, cool BOOLEAN NULL)
|
||||
2026-05-15 13:21:50.539 [main] DEBUG Exposed - CREATE TABLE IF NOT EXISTS humanbeing (id uuid PRIMARY KEY, owner_id uuid NOT NULL, "name" TEXT NOT NULL, coordinates uuid NOT NULL, creation_date DATE NOT NULL, real_hero BOOLEAN NOT NULL, has_toothpick BOOLEAN NOT NULL, impact_speed DOUBLE PRECISION NOT NULL, soundtrack_name TEXT NOT NULL, minutes_of_waiting REAL NOT NULL, car uuid NOT NULL, weapon_type weapontype DEFAULT NULL NULL, CONSTRAINT fk_humanbeing_owner_id__id FOREIGN KEY (owner_id) REFERENCES "user"(id) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT fk_humanbeing_coordinates__id FOREIGN KEY (coordinates) REFERENCES coordinates(id) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT fk_humanbeing_car__id FOREIGN KEY (car) REFERENCES car(id) ON DELETE RESTRICT ON UPDATE RESTRICT)
|
||||
2026-05-15 13:21:51.146 [main] DEBUG Exposed - SELECT humanbeing.id, humanbeing.owner_id, humanbeing."name", humanbeing.coordinates, humanbeing.creation_date, humanbeing.real_hero, humanbeing.has_toothpick, humanbeing.impact_speed, humanbeing.soundtrack_name, humanbeing.minutes_of_waiting, humanbeing.car, humanbeing.weapon_type FROM humanbeing
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import collection.CollectionManager
|
||||
import commands.*
|
||||
import file.FileManager
|
||||
import database.DatabaseManager
|
||||
import database.Repository
|
||||
import mu.KotlinLogging
|
||||
import network.NetworkManager
|
||||
import runner.CommandInvoker
|
||||
@@ -10,21 +11,23 @@ private val logger = KotlinLogging.logger {}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
System.setProperty("slf4j.internal.verbosity", "WARN")
|
||||
|
||||
val filePath = if (args.isNotEmpty()) args[0] else "data.csv"
|
||||
val port = if (args.isNotEmpty() && args.size > 1) args[1].toInt() else 8080
|
||||
|
||||
logger.info{ "[startup] Starting server on port $port with $args args" }
|
||||
|
||||
val fileManager = FileManager(filePath)
|
||||
val database = DatabaseManager()
|
||||
database.start()
|
||||
val repository = Repository()
|
||||
|
||||
val manager = CollectionManager()
|
||||
val invoker = CommandInvoker()
|
||||
|
||||
// Загружаем коллекцию из файла
|
||||
manager.loadFromFile(fileManager.read())
|
||||
// let's pretend that i'm in
|
||||
manager.loadFromFile(repository.read())
|
||||
|
||||
// Регистрируем команды
|
||||
registerServerCommands(invoker, manager, fileManager)
|
||||
registerServerCommands(invoker, manager, repository)
|
||||
|
||||
// Сохранение при завершении (Ctrl+C или kill)
|
||||
val server = NetworkManager(port = port, invoker = invoker)
|
||||
@@ -36,14 +39,14 @@ fun main(args: Array<String>) {
|
||||
// Консоль на сервере
|
||||
Thread {
|
||||
val scanner = java.util.Scanner(System.`in`)
|
||||
logger.info{ "[Server-console] Server-side commands: save, exit" }
|
||||
logger.info{ "[Server-console] Server-side commands: exit" }
|
||||
while (scanner.hasNextLine()) {
|
||||
when (scanner.nextLine().trim().lowercase()) {
|
||||
"exit" -> {
|
||||
logger.info{ "[Server-console] Shutting down..." }
|
||||
exitProcess(0)
|
||||
}
|
||||
else -> logger.info{ "[Server-console] Server-side commands: save, exit" }
|
||||
else -> logger.info{ "[Server-console] Server-side commands: exit" }
|
||||
}
|
||||
}
|
||||
}.also { it.isDaemon = true }.start()
|
||||
@@ -54,19 +57,17 @@ fun main(args: Array<String>) {
|
||||
fun registerServerCommands(
|
||||
invoker: CommandInvoker,
|
||||
manager: CollectionManager,
|
||||
fileManager: FileManager
|
||||
repository: Repository
|
||||
) {
|
||||
listOf(
|
||||
// HelpCommand(invoker),
|
||||
InfoCommand(manager),
|
||||
ShowCommand(manager),
|
||||
AddCommand(manager),
|
||||
UpdateCommand(manager),
|
||||
RemoveByIdCommand(manager),
|
||||
AddCommand(manager, repository),
|
||||
UpdateCommand(manager, repository),
|
||||
RemoveByIdCommand(manager, repository),
|
||||
ClearCommand(manager),
|
||||
SaveCommand(manager, fileManager), // только на сервере
|
||||
AddIfMaxCommand(manager),
|
||||
AddIfMinCommand(manager),
|
||||
AddIfMaxCommand(manager, repository),
|
||||
AddIfMinCommand(manager, repository),
|
||||
HistoryCommand(invoker),
|
||||
SyncCommand(invoker),
|
||||
SumOfMinutesCommand(manager),
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import database.Repository
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
@Serializable
|
||||
class AddCommand(@Contextual private val manager: CollectionManager) : Command {
|
||||
class AddCommand(
|
||||
@Contextual private val manager: CollectionManager,
|
||||
@Contextual private val repository: Repository
|
||||
) : Command {
|
||||
override val name = "add"
|
||||
override val description = "добавить новый элемент в коллекцию"
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
@@ -15,8 +19,12 @@ class AddCommand(@Contextual private val manager: CollectionManager) : Command {
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
|
||||
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
|
||||
humanBeing.id = repository.write(humanBeing)
|
||||
humanBeing.id ?: return Response(false, "Не удалось добавить элемент")
|
||||
|
||||
return if (manager.add(humanBeing))
|
||||
Response(true, "Элемент добавлен: ${humanBeing.name} [${humanBeing.id}]")
|
||||
|
||||
else
|
||||
Response(false, "Элемент уже существует в коллекции.")
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import database.Repository
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
class AddIfMaxCommand(private val manager: CollectionManager) : Command {
|
||||
@Serializable
|
||||
class AddIfMaxCommand(
|
||||
@Contextual val manager: CollectionManager,
|
||||
@Contextual private val repository: Repository
|
||||
) : Command {
|
||||
override val name = "add_if_max"
|
||||
override val description = "добавить элемент, если он больше максимального"
|
||||
override val type = CommandType.MODEL
|
||||
@@ -14,6 +21,8 @@ class AddIfMaxCommand(private val manager: CollectionManager) : Command {
|
||||
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
|
||||
val max = manager.getMax()
|
||||
return if (max == null || humanBeing > max) {
|
||||
humanBeing.id = repository.write(humanBeing)
|
||||
humanBeing.id ?: return Response(false, "Не удалось добавить элемент")
|
||||
manager.add(humanBeing)
|
||||
Response(true, "Элемент добавлен (превышает максимум).")
|
||||
} else {
|
||||
|
||||
+7
-1
@@ -1,13 +1,17 @@
|
||||
package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import database.Repository
|
||||
import models.HumanBeing
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Contextual
|
||||
import network.Response
|
||||
|
||||
@Serializable
|
||||
class AddIfMinCommand(@Contextual private val manager: CollectionManager) : Command {
|
||||
class AddIfMinCommand(
|
||||
@Contextual private val manager: CollectionManager,
|
||||
@Contextual private val repository: Repository
|
||||
) : Command {
|
||||
override val name = "add_if_min"
|
||||
override val description = "добавить элемент, если он меньше минимального"
|
||||
override val type = CommandType.MODEL
|
||||
@@ -17,6 +21,8 @@ class AddIfMinCommand(@Contextual private val manager: CollectionManager) : Comm
|
||||
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
|
||||
val min = manager.getMin()
|
||||
return if (min == null || humanBeing < min) {
|
||||
humanBeing.id = repository.write(humanBeing)
|
||||
humanBeing.id ?: return Response(false, "Не удалось добавить элемент")
|
||||
manager.add(humanBeing)
|
||||
Response(true, "Элемент добавлен (меньше минимума).")
|
||||
} else {
|
||||
@@ -1,6 +1,7 @@
|
||||
package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import database.Repository
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.UUID
|
||||
@@ -8,10 +9,13 @@ import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
@Serializable
|
||||
class RemoveByIdCommand(@Contextual private val manager: CollectionManager) : Command {
|
||||
class RemoveByIdCommand(
|
||||
@Contextual private val manager: CollectionManager,
|
||||
@Contextual private val repository: Repository
|
||||
) : Command {
|
||||
override val name = "remove_by_id"
|
||||
override val description = "удалить элемент по id: remove_by_id <uuid>"
|
||||
override val type = CommandType.MODEL
|
||||
override val type = CommandType.SIMPLE
|
||||
override val args = listOf(CommandArgType.STRING)
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
|
||||
@@ -23,7 +27,11 @@ class RemoveByIdCommand(@Contextual private val manager: CollectionManager) : Co
|
||||
return Response(false, "[Ошибка] Некорректный UUID: '${args[0]}'")
|
||||
}
|
||||
|
||||
return if (manager.removeById(id)) Response(true, "Элемент удалён.")
|
||||
else Response(false, "[Ошибка] Элемент с id '$id' не найден.")
|
||||
if (repository.delete(id)) {
|
||||
manager.removeById(id)
|
||||
return Response(true, "Элемент удалён.")
|
||||
} else {
|
||||
return Response(false, "[Ошибка] Элемент с id '$id' не найден и не был удалён.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import file.FileManager
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
import java.util.UUID
|
||||
|
||||
@Serializable
|
||||
class SaveCommand(
|
||||
@Contextual private val manager: CollectionManager,
|
||||
@Contextual private val fileManager: FileManager
|
||||
) : Command {
|
||||
override val name = "save"
|
||||
override val description = "[SERVER] сохранить коллекцию в файл"
|
||||
override val type = CommandType.SIMPLE
|
||||
override val args = listOf(CommandArgType.NONE)
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
|
||||
val dummyUser: UUID = UUID.fromString("68db6bf5-65ce-46c7-881d-c85b23254f92")
|
||||
return if (fileManager.write(manager.getAll(), dummyUser)) Response(true, "Коллекция сохранена.")
|
||||
else Response(false, "[Ошибка] Не удалось сохранить коллекцию.")
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package commands
|
||||
|
||||
import collection.CollectionManager
|
||||
import database.Repository
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import models.HumanBeing
|
||||
@@ -8,14 +9,17 @@ import network.Response
|
||||
import java.util.UUID
|
||||
|
||||
@Serializable
|
||||
class UpdateCommand(@Contextual private val manager: CollectionManager) : Command {
|
||||
class UpdateCommand(
|
||||
@Contextual private val manager: CollectionManager,
|
||||
@Contextual private val repository: Repository
|
||||
) : Command {
|
||||
override val name = "update"
|
||||
override val description = "обновить элемент по id: update <uuid>"
|
||||
override val type = CommandType.MODEL
|
||||
override val args = listOf(CommandArgType.STRING)
|
||||
|
||||
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
|
||||
if (args.isEmpty()) return Response(false, "[Ошибка] Укажите id. Пример: update <uuid>")
|
||||
if (args.isEmpty()) return Response(false, "[Ошибка] Укажите id. Пример: update <UUID>")
|
||||
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
|
||||
|
||||
val id = try {
|
||||
@@ -24,7 +28,11 @@ class UpdateCommand(@Contextual private val manager: CollectionManager) : Comman
|
||||
return Response(false, "[Ошибка] Некорректный UUID: '${args[0]}'")
|
||||
}
|
||||
|
||||
return if (manager.update(id, humanBeing)) Response(true, "Элемент обновлён.")
|
||||
else Response(false, "[Ошибка] Элемент с id '$id' не найден.")
|
||||
if (repository.update(id, humanBeing)) {
|
||||
manager.update(id, humanBeing)
|
||||
return Response(true, "Элемент обновлён.")
|
||||
} else {
|
||||
return Response(false, "[Ошибка] Элемент не найден и не был обновлён.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class DatabaseManager {
|
||||
exec(
|
||||
"""
|
||||
DO ${'$'}${'$'} BEGIN
|
||||
CREATE TYPE weapontype AS ENUM ('hammer', 'shotgun', 'bat');
|
||||
CREATE TYPE weapontype AS ENUM ('HAMMER', 'SHOTGUN', 'BAT');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END ${'$'}${'$'};
|
||||
@@ -52,12 +52,4 @@ class DatabaseManager {
|
||||
}
|
||||
return smth
|
||||
}
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val manager = DatabaseManager()
|
||||
println("here we go!")
|
||||
manager.start()
|
||||
println(manager.test())
|
||||
println("here we don't go!")
|
||||
}
|
||||
}
|
||||
@@ -5,25 +5,63 @@ import database.tables.CoordinatesTable
|
||||
import database.tables.HumanBeingTable
|
||||
import database.tables.UserTable
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import models.Car
|
||||
import models.Coordinates
|
||||
import models.HumanBeing
|
||||
import org.jetbrains.exposed.v1.core.*
|
||||
import org.jetbrains.exposed.v1.jdbc.*
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
import java.util.UUID
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
class Repository(private val filePath: String) {
|
||||
class Repository() {
|
||||
|
||||
//fun read(): List<HumanBeing> {}
|
||||
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
fun write(items: Collection<HumanBeing>, userId: UUID): Boolean {
|
||||
fun read(): List<HumanBeing> {
|
||||
val beings = mutableListOf<HumanBeing>()
|
||||
transaction {
|
||||
val humans = HumanBeingTable
|
||||
.selectAll()
|
||||
|
||||
for (item in items) {
|
||||
for (human in humans) {
|
||||
logger.debug { "read. $human" }
|
||||
val cord = CoordinatesTable
|
||||
.select(CoordinatesTable.x, CoordinatesTable.y)
|
||||
.where(CoordinatesTable.id eq human[HumanBeingTable.coordinates])
|
||||
.single()
|
||||
|
||||
val car = CarTable
|
||||
.select(CarTable.cool)
|
||||
.where(CarTable.id eq human[HumanBeingTable.car])
|
||||
.single()
|
||||
|
||||
val newCords = Coordinates(cord[CoordinatesTable.x]!!, cord[CoordinatesTable.y]!!)
|
||||
val newCar = Car(car[CarTable.cool]!!)
|
||||
val newBeing = HumanBeing(
|
||||
name = human[HumanBeingTable.name],
|
||||
ownerId = human[HumanBeingTable.owner_id].value,
|
||||
coordinates = newCords,
|
||||
car = newCar,
|
||||
id = human[HumanBeingTable.id].value,
|
||||
creationDate = human[HumanBeingTable.creationDate],
|
||||
realHero = human[HumanBeingTable.realHero],
|
||||
hasToothpick = human[HumanBeingTable.hasToothpick],
|
||||
impactSpeed = human[HumanBeingTable.impactSpeed],
|
||||
soundtrackName = human[HumanBeingTable.soundtrackName],
|
||||
minutesOfWaiting = human[HumanBeingTable.minutesOfWaiting],
|
||||
weaponType = human[HumanBeingTable.weaponType]
|
||||
)
|
||||
|
||||
beings.add(newBeing)
|
||||
}
|
||||
}
|
||||
return beings
|
||||
}
|
||||
|
||||
fun write(item: HumanBeing): UUID? {
|
||||
var humanId: UUID? = null
|
||||
try {
|
||||
transaction {
|
||||
val cords = CoordinatesTable.insertAndGetId {
|
||||
it[x] = item.coordinates.x
|
||||
it[y] = item.coordinates.y
|
||||
@@ -33,15 +71,9 @@ class Repository(private val filePath: String) {
|
||||
it[cool] = item.car.cool
|
||||
}
|
||||
|
||||
val user = UserTable
|
||||
.select(UserTable.id)
|
||||
.where { UserTable.id eq userId }
|
||||
.first()
|
||||
|
||||
HumanBeingTable.insert {
|
||||
it[owner_id] = userId
|
||||
humanId = HumanBeingTable.insertAndGetId {
|
||||
it[owner_id] = item.ownerId
|
||||
it[name] = item.name
|
||||
it[id] = user[UserTable.id] // it fucking autoincrements (check perplexity)
|
||||
it[coordinates] = cords
|
||||
it[creationDate] = item.creationDate
|
||||
it[realHero] = item.realHero
|
||||
@@ -51,9 +83,71 @@ class Repository(private val filePath: String) {
|
||||
it[minutesOfWaiting] = item.minutesOfWaiting
|
||||
it[car] = cars
|
||||
it[weaponType] = item.weaponType
|
||||
}.value
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
logger.error(e) { "write failed. Here what's going on: \n $e \n " }
|
||||
return humanId
|
||||
}
|
||||
return humanId
|
||||
}
|
||||
|
||||
fun update(id: UUID, item: HumanBeing): Boolean {
|
||||
var allGood = false
|
||||
transaction {
|
||||
val check = HumanBeingTable
|
||||
.select(HumanBeingTable.id)
|
||||
.where(HumanBeingTable.id eq id)
|
||||
.singleOrNull()
|
||||
|
||||
if (check != null) {
|
||||
val cordsInBeing = HumanBeingTable
|
||||
.select(HumanBeingTable.coordinates)
|
||||
.where(HumanBeingTable.id eq id)
|
||||
.single()
|
||||
|
||||
CoordinatesTable.update(
|
||||
{ CoordinatesTable.id eq cordsInBeing[HumanBeingTable.coordinates] }
|
||||
) {
|
||||
it[x] = item.coordinates.x
|
||||
it[y] = item.coordinates.y
|
||||
}
|
||||
|
||||
CarTable.update(
|
||||
{ CarTable.id eq cordsInBeing[HumanBeingTable.coordinates] }
|
||||
) {
|
||||
it[cool] = item.car.cool
|
||||
}
|
||||
|
||||
HumanBeingTable.update(
|
||||
{ HumanBeingTable.id eq id }
|
||||
) {
|
||||
it[owner_id] = item.ownerId
|
||||
it[name] = item.name
|
||||
it[creationDate] = item.creationDate
|
||||
it[realHero] = item.realHero
|
||||
it[impactSpeed] = item.impactSpeed
|
||||
it[soundtrackName] = item.soundtrackName
|
||||
it[minutesOfWaiting] = item.minutesOfWaiting
|
||||
it[hasToothpick] = item.hasToothpick
|
||||
it[weaponType] = item.weaponType
|
||||
}
|
||||
allGood = true
|
||||
}
|
||||
}
|
||||
return allGood
|
||||
}
|
||||
|
||||
fun delete(id: UUID): Boolean {
|
||||
var allGood = true
|
||||
try {
|
||||
transaction {
|
||||
HumanBeingTable.deleteWhere { HumanBeingTable.id eq id }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
allGood = false
|
||||
}
|
||||
return allGood
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
package database.tables
|
||||
|
||||
import models.WeaponType
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.javatime.date
|
||||
import org.postgresql.util.PGobject
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import org.jetbrains.exposed.v1.core.dao.id.java.UUIDTable
|
||||
|
||||
|
||||
object HumanBeingTable : Table("humanBeing") {
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
val id = uuid("id")
|
||||
object HumanBeingTable : UUIDTable("humanBeing") {
|
||||
val owner_id = reference("owner_id", UserTable.id)
|
||||
val name = text("name")
|
||||
val coordinates = reference("coordinates", CoordinatesTable.id)
|
||||
@@ -21,21 +18,21 @@ object HumanBeingTable : Table("humanBeing") {
|
||||
val minutesOfWaiting = float("minutes_of_waiting")
|
||||
val car = reference("car", CarTable.id)
|
||||
val weaponType = customEnumeration(
|
||||
name = "weapon_type",
|
||||
sql = "weapontype",
|
||||
name = "weapon_type",
|
||||
sql = "weapontype",
|
||||
fromDb = { value ->
|
||||
WeaponType.valueOf(
|
||||
(value as PGobject).value!!
|
||||
)
|
||||
val strValue = when (value) {
|
||||
is PGobject -> value.value!!
|
||||
is String -> value
|
||||
else -> error("Unexpected type: ${value::class}")
|
||||
}
|
||||
WeaponType.valueOf(strValue.uppercase())
|
||||
},
|
||||
toDb = { value ->
|
||||
toDb = { value ->
|
||||
PGobject().apply {
|
||||
type = "weapontype"
|
||||
this.value = value.name
|
||||
type = "weapontype"
|
||||
this.value = value.name.lowercase()
|
||||
}
|
||||
}
|
||||
).nullable().default(null)
|
||||
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
package file
|
||||
|
||||
import models.Car
|
||||
import models.Coordinates
|
||||
import models.HumanBeing
|
||||
import models.WeaponType
|
||||
import mu.KotlinLogging
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.OutputStreamWriter
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeParseException
|
||||
import java.util.Scanner
|
||||
import java.util.UUID
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
/**
|
||||
* Менеджер файлового хранилища коллекции.
|
||||
*
|
||||
* Читает данные с помощью [Scanner], записывает через [OutputStreamWriter].
|
||||
* Формат хранения — CSV, одна строка = один объект [HumanBeing].
|
||||
*
|
||||
* Порядок полей в CSV:
|
||||
* `id,name,coordX,coordY,creationDate,realHero,hasToothpick,
|
||||
* impactSpeed,soundtrackName,minutesOfWaiting,weaponType,carCool`
|
||||
*
|
||||
* @property filePath путь к CSV-файлу, полученный из аргумента командной строки
|
||||
*/
|
||||
class FileManager(private val filePath: String) {
|
||||
|
||||
companion object {
|
||||
private const val DELIMITER = ","
|
||||
private const val HEADER =
|
||||
"id,name,coordX,coordY,creationDate,realHero," +
|
||||
"hasToothpick,impactSpeed,soundtrackName,minutesOfWaiting,weaponType,carCool"
|
||||
}
|
||||
|
||||
/**
|
||||
* Читает коллекцию из CSV-файла с помощью [Scanner].
|
||||
*
|
||||
* Пропускает заголовок и битые строки (с предупреждением в stderr).
|
||||
* При отсутствии файла или проблемах с доступом возвращает пустой список.
|
||||
*
|
||||
* @return список корректно распарсенных объектов [HumanBeing]
|
||||
*/
|
||||
fun read(): List<HumanBeing> {
|
||||
val file = File(filePath)
|
||||
|
||||
if (!file.exists()) {
|
||||
logger.warn{"There's no '$filePath'. Collection will be empty."}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
if (!file.canRead()) {
|
||||
logger.warn{"Permission denied: '$filePath'. Collection will be empty."}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val result = mutableListOf<HumanBeing>()
|
||||
var lineNumber = 0
|
||||
|
||||
try {
|
||||
Scanner(file, Charsets.UTF_8).use { scanner ->
|
||||
while (scanner.hasNextLine()) {
|
||||
val line = scanner.nextLine().trim()
|
||||
lineNumber++
|
||||
|
||||
// skip header and empty lines
|
||||
if (lineNumber == 1 && line.startsWith("id")) continue
|
||||
if (line.isBlank()) continue
|
||||
|
||||
val parsedLine = parseLine(line, lineNumber)
|
||||
if (parsedLine != null) {
|
||||
result.add(parsedLine)
|
||||
} else {
|
||||
logger.warn { "Line $lineNumber of $filePath is skipped: invalid data → '$line'" }
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: FileNotFoundException) {
|
||||
logger.warn(e) {"File not found: ${e.message}"}
|
||||
}
|
||||
|
||||
logger.info {"${result.size} elements loaded from'$filePath'"}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Парсит одну CSV-строку в объект [HumanBeing].
|
||||
*
|
||||
* @param line строка CSV
|
||||
* @param lineNumber номер строки (для сообщений об ошибках)
|
||||
* @return объект [HumanBeing] или null при ошибке парсинга
|
||||
*/
|
||||
private fun parseLine(line: String, lineNumber: Int): HumanBeing? {
|
||||
return try {
|
||||
val parts = line.split(DELIMITER, limit = 12)
|
||||
if (parts.size < 12) {
|
||||
logger.warn {"Line $lineNumber: expected 12 fields, found ${parts.size}"}
|
||||
return null
|
||||
}
|
||||
|
||||
val id = UUID.fromString(parts[0].trim())
|
||||
val name = parts[1].trim()
|
||||
val coordX = parts[2].trim().toLong()
|
||||
val coordY = parts[3].trim().toInt()
|
||||
val creationDate = LocalDate.parse(parts[4].trim())
|
||||
val realHero = parts[5].trim().toBoolean()
|
||||
val hasToothpick = parts[6].trim().toBoolean()
|
||||
val impactSpeed = parts[7].trim().toDouble()
|
||||
val soundtrackName = parts[8].trim()
|
||||
val minutesOfWaiting = parts[9].trim().toFloat()
|
||||
val weaponType = parts[10].trim().let {
|
||||
if (it.equals("null", ignoreCase = true) || it.isBlank()) null
|
||||
else WeaponType.valueOf(it)
|
||||
}
|
||||
val carCool = parts[11].trim().toBoolean()
|
||||
|
||||
require(name.isNotBlank()) { "name не может быть пустым" }
|
||||
|
||||
HumanBeing(
|
||||
id = id, name = name, coordinates = Coordinates(coordX, coordY),
|
||||
creationDate = creationDate, realHero = realHero, hasToothpick = hasToothpick,
|
||||
impactSpeed = impactSpeed, soundtrackName = soundtrackName,
|
||||
minutesOfWaiting = minutesOfWaiting, weaponType = weaponType, car = Car(carCool)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
logger.warn(e) {"Line $lineNumber: ${e.message}"}
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Записывает коллекцию в CSV-файл с помощью [OutputStreamWriter].
|
||||
*
|
||||
* @param items коллекция объектов [HumanBeing] для записи
|
||||
* @return true если запись прошла успешно
|
||||
*/
|
||||
fun write(items: Collection<HumanBeing>, user: UUID): Boolean {
|
||||
val file = File(filePath)
|
||||
|
||||
if (file.exists() && !file.canWrite()) {
|
||||
logger.warn{"Permission denied: '$filePath'."}
|
||||
return false
|
||||
}
|
||||
|
||||
return try {
|
||||
OutputStreamWriter(file.outputStream(), Charsets.UTF_8).use { writer ->
|
||||
writer.write(HEADER)
|
||||
writer.write("\n")
|
||||
items.forEach { h ->
|
||||
writer.write(toCSVLine(h))
|
||||
writer.write("\n")
|
||||
}
|
||||
}
|
||||
logger.info {"Collection saved to'$filePath' (${items.size} elements)."}
|
||||
true
|
||||
} catch (e: SecurityException) {
|
||||
logger.warn(e) {"Permission denied: '$filePath'."}
|
||||
false
|
||||
} catch (e: Exception) {
|
||||
logger.warn(e) {"An error occurred: '$filePath'."}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Сериализует объект [HumanBeing] в строку CSV.
|
||||
*
|
||||
* @param h объект для сериализации
|
||||
* @return строка в формате CSV
|
||||
*/
|
||||
private fun toCSVLine(h: HumanBeing): String = listOf(
|
||||
h.id, h.name, h.coordinates.x, h.coordinates.y, h.creationDate,
|
||||
h.realHero, h.hasToothpick, h.impactSpeed, h.soundtrackName,
|
||||
h.minutesOfWaiting, h.weaponType ?: "null", h.car.cool
|
||||
).joinToString(DELIMITER)
|
||||
}
|
||||
@@ -11,8 +11,6 @@ import network.Response
|
||||
* возвращает Response готовый для отправки клиенту.
|
||||
*/
|
||||
class CommandInvoker {
|
||||
|
||||
private val serverOnlyCommands = setOf("save", "sync-server")
|
||||
private val commands = mutableMapOf<String, Command>()
|
||||
private val history = ArrayDeque<String>()
|
||||
|
||||
@@ -21,9 +19,6 @@ class CommandInvoker {
|
||||
}
|
||||
|
||||
fun execute(request: Request): Response {
|
||||
if (request.commandName in serverOnlyCommands) {
|
||||
return Response(false, "Команда '${request.commandName}' недоступна клиенту.")
|
||||
}
|
||||
|
||||
val command = commands[request.commandName]
|
||||
?: return Response(false, "Неизвестная команда: '${request.commandName}'")
|
||||
|
||||
Reference in New Issue
Block a user