feat: add leave submission withdrawal and audit trail

This commit is contained in:
selfrelease
2026-07-18 08:22:59 +08:00
parent 7107c7cc82
commit aaf3bb5b58
12 changed files with 646 additions and 5 deletions
@@ -0,0 +1,38 @@
package com.all8ai.aioa.audit.application
import com.all8ai.aioa.audit.domain.AuditEvent
import com.all8ai.aioa.audit.domain.AuditEventRepository
import com.all8ai.aioa.identity.domain.CurrentUser
import com.all8ai.aioa.shared.id.UuidV7
import com.all8ai.aioa.shared.web.TraceIdFilter
import org.slf4j.MDC
import org.springframework.stereotype.Service
@Service
class AuditService(
private val repository: AuditEventRepository,
) {
fun recordSuccess(
actor: CurrentUser,
action: String,
resourceType: String,
resourceId: String,
idempotencyKey: String?,
details: Map<String, Any?>,
) {
repository.append(
AuditEvent(
id = UuidV7.generate(),
tenantId = actor.tenantId,
actorId = actor.id,
action = action,
resourceType = resourceType,
resourceId = resourceId,
traceId = MDC.get(TraceIdFilter.MDC_TRACE_ID) ?: "unknown",
idempotencyKey = idempotencyKey,
result = "SUCCESS",
details = details,
),
)
}
}
@@ -0,0 +1,20 @@
package com.all8ai.aioa.audit.domain
import java.util.UUID
data class AuditEvent(
val id: UUID,
val tenantId: UUID,
val actorId: UUID,
val action: String,
val resourceType: String,
val resourceId: String,
val traceId: String,
val idempotencyKey: String?,
val result: String,
val details: Map<String, Any?>,
)
fun interface AuditEventRepository {
fun append(event: AuditEvent)
}
@@ -0,0 +1,34 @@
package com.all8ai.aioa.audit.infrastructure
import com.all8ai.aioa.audit.domain.AuditEvent
import com.all8ai.aioa.audit.domain.AuditEventRepository
import com.fasterxml.jackson.databind.ObjectMapper
import org.jooq.DSLContext
import org.springframework.stereotype.Repository
@Repository
class JooqAuditEventRepository(
private val dsl: DSLContext,
private val objectMapper: ObjectMapper,
) : AuditEventRepository {
override fun append(event: AuditEvent) {
dsl.execute(
"""
INSERT INTO audit.event (
id, tenant_id, actor_id, action, resource_type, resource_id,
trace_id, idempotency_key, result, details
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CAST(? AS JSONB))
""".trimIndent(),
event.id,
event.tenantId,
event.actorId,
event.action,
event.resourceType,
event.resourceId,
event.traceId,
event.idempotencyKey,
event.result,
objectMapper.writeValueAsString(event.details),
)
}
}