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.security.SecureRandom internal data class TeamInviteDto( val token: String, val agency: String, val role: String, val createdByUserId: String, var usedByUserId: String? = null, val createdAtIso: String = java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(java.time.LocalDateTime.now()), var usedAtIso: String? = null, ) internal class InvitesStore( private val gson: Gson, private val file: Path, ) { private val lock = Any() private val random = SecureRandom() fun listForAgency(agency: String): List = synchronized(lock) { load().filter { it.agency.equals(agency, ignoreCase = true) || agency.isBlank() } } fun create(agency: String, role: String, createdByUserId: String): TeamInviteDto = synchronized(lock) { val token = randomToken() val dto = TeamInviteDto( token = token, agency = agency, role = role.ifBlank { "AGENT" }, createdByUserId = createdByUserId, ) val list = load().toMutableList() list.add(0, dto) persist(list) dto } fun consume(token: String, userId: String): TeamInviteDto? = synchronized(lock) { val list = load().toMutableList() val idx = list.indexOfFirst { it.token == token && it.usedByUserId == null } if (idx < 0) return@synchronized null val updated = list[idx].copy( usedByUserId = userId, usedAtIso = java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(java.time.LocalDateTime.now()), ) list[idx] = updated persist(list) updated } fun byToken(token: String): TeamInviteDto? = synchronized(lock) { load().firstOrNull { it.token == token } } 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) Files.writeString(file, gson.toJson(list), StandardCharsets.UTF_8) } private fun randomToken(): String { val bytes = ByteArray(12) random.nextBytes(bytes) val alphabet = "abcdefghjkmnpqrstuvwxyz23456789" return buildString(16) { for (b in bytes) { append(alphabet[(b.toInt() and 0xff) % alphabet.length]) } append('-') for (i in 0 until 4) append(alphabet[random.nextInt(alphabet.length)]) } } companion object { private val listType = object : TypeToken>() {}.type } }