package com.estateunified.panel import com.google.gson.Gson import com.google.gson.reflect.TypeToken import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardCopyOption import java.util.UUID internal data class SubscriptionRecordDto( val id: String = UUID.randomUUID().toString(), val userId: String, val plan: String, val activatedAtIso: String = java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(java.time.LocalDateTime.now()), val activeUntilIso: String, val source: String = "MOCK", val priceAmd: Long = 0, val note: String = "", ) internal object PlanCatalog { const val BASIC = "BASIC" const val PRO = "PRO" const val PRO_PLUS = "PRO_PLUS" data class Tier( val code: String, val priceAmd: Long, val topHours: Int, val cooldownHours: Int, val titleRu: String, ) val tiers: Map = mapOf( BASIC to Tier(BASIC, 0, 0, 0, "Basic"), PRO to Tier(PRO, 19_000, 12, 36, "Pro"), PRO_PLUS to Tier(PRO_PLUS, 39_000, 24, 24, "Pro+"), ) fun isPaid(plan: String): Boolean = plan == PRO || plan == PRO_PLUS fun canBoost(plan: String): Boolean = isPaid(plan) } internal class SubscriptionsStore( private val gson: Gson, private val file: Path, ) { private val lock = Any() fun absolutePath(): String = file.toAbsolutePath().normalize().toString() fun listForUser(userId: String): List = synchronized(lock) { load().filter { it.userId == userId }.sortedByDescending { it.activatedAtIso } } fun activate( userId: String, plan: String, durationDays: Int = 30, priceAmd: Long = 0, source: String = "MOCK", note: String = "", ): SubscriptionRecordDto { val now = java.time.LocalDateTime.now() val until = now.plusDays(durationDays.toLong()) val dto = SubscriptionRecordDto( userId = userId, plan = plan, activatedAtIso = java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(now), activeUntilIso = java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(until), source = source, priceAmd = priceAmd, note = note, ) synchronized(lock) { val list = load().toMutableList() list.add(0, dto) persist(list) } return dto } private fun load(): MutableList { if (!Files.isRegularFile(file)) return mutableListOf() val text = Files.readString(file, StandardCharsets.UTF_8) if (text.isBlank()) return mutableListOf() return runCatching { gson.fromJson>(text, listType) ?: mutableListOf() }.getOrElse { mutableListOf() } } private fun persist(list: List) { Files.createDirectories(file.toAbsolutePath().parent) val tmp = file.resolveSibling(file.fileName.toString() + ".tmp") Files.writeString(tmp, gson.toJson(list), StandardCharsets.UTF_8) Files.move(tmp, file, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE) } private companion object { val listType = object : TypeToken>() {}.type } }