Romain
04/07/2026, 12:02 AMUp
04/07/2026, 6:47 AM@OptIn(ExperimentalTime::class)
internal fun nowUTC() = Clock.System.now().toLocalDateTime(TimeZone.UTC)
internal fun EntityClass<*, *>.withAuditLogColumns(vararg exclude: Column<*> = arrayOf()) {
EntityHook.subscribe { change ->
val changedEntity = change.toEntity(this)
if(change.changeType == EntityChangeType.Updated) {
val now = nowUTC()
changedEntity?.let { entity ->
if(exclude.toList().containsAll(entity.writeValues.keys)) {
return@let
}
@Suppress("UNCHECKED_CAST") val modifiedColumn: Column<Any?> = table.columns.find { it.name == "updated_at" && it.columnType is KotlinLocalDateTimeColumnType } as Column<Any?>
if(entity.writeValues[modifiedColumn] == null) {
entity.writeValues[modifiedColumn] = now
}
}
}
}
}Up
04/07/2026, 6:48 AMRomain
04/07/2026, 3:14 PMsimon.vergauwen
04/08/2026, 8:24 AMval createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
This uses CURRENT_TIMESTAMP, but then it still requires to properly track updatedAt yourself. Alternatively combined with triggers on postgres you can mange this entirely automatically but needs to be setup out of of Exposed.Romain
04/08/2026, 2:59 PM