feat(audit): локальный журнал и push в auth-portal
Таблица audit_log, recordAudit на CRUD, GET /api/v1/audit и dual-write source_app=cfdm. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Vendored
+573
-2
File diff suppressed because one or more lines are too long
Vendored
+187
-95
@@ -245,6 +245,22 @@ var notificationLog = sqliteTable("notification_log", {
|
||||
message: text("message").notNull(),
|
||||
created_at: text("created_at").notNull().default(sql`datetime('now')`)
|
||||
});
|
||||
var auditLog = sqliteTable("audit_log", {
|
||||
id: text("id").primaryKey(),
|
||||
event_id: text("event_id"),
|
||||
source_app: text("source_app").notNull().default("cfdm"),
|
||||
action: text("action").notNull(),
|
||||
severity: text("severity").notNull().default("info"),
|
||||
actor_user_id: text("actor_user_id"),
|
||||
actor_email: text("actor_email"),
|
||||
actor_name: text("actor_name"),
|
||||
target_type: text("target_type"),
|
||||
target_id: text("target_id"),
|
||||
summary: text("summary").notNull(),
|
||||
details_json: text("details_json"),
|
||||
ip: text("ip"),
|
||||
created_at: text("created_at").notNull().default(sql`datetime('now')`)
|
||||
});
|
||||
var schema = {
|
||||
groups,
|
||||
services,
|
||||
@@ -264,7 +280,8 @@ var schema = {
|
||||
domainTags,
|
||||
domainMonitors,
|
||||
domainMonitorResults,
|
||||
notificationLog
|
||||
notificationLog,
|
||||
auditLog
|
||||
};
|
||||
|
||||
// src/client.ts
|
||||
@@ -329,8 +346,80 @@ var ConflictError = class extends Error {
|
||||
}
|
||||
};
|
||||
|
||||
// src/audit-log.ts
|
||||
import { and, desc, eq, or } from "drizzle-orm";
|
||||
import { randomUUID } from "crypto";
|
||||
function mapRow(row) {
|
||||
let details = null;
|
||||
if (row.details_json) {
|
||||
try {
|
||||
details = JSON.parse(row.details_json);
|
||||
} catch {
|
||||
details = { raw: row.details_json };
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: row.id,
|
||||
event_id: row.event_id,
|
||||
source_app: row.source_app || "cfdm",
|
||||
action: row.action,
|
||||
severity: row.severity,
|
||||
actor_user_id: row.actor_user_id,
|
||||
actor_email: row.actor_email,
|
||||
actor_name: row.actor_name,
|
||||
target_type: row.target_type ?? null,
|
||||
target_id: row.target_id,
|
||||
summary: row.summary,
|
||||
details,
|
||||
ip: row.ip,
|
||||
created_at: row.created_at
|
||||
};
|
||||
}
|
||||
function appendAudit(db, input) {
|
||||
const now = input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
||||
const eventId = input.eventId ?? null;
|
||||
if (eventId) {
|
||||
const existing = db.select({ id: auditLog.id }).from(auditLog).where(eq(auditLog.event_id, eventId)).get();
|
||||
if (existing) return false;
|
||||
}
|
||||
db.insert(auditLog).values({
|
||||
id: randomUUID(),
|
||||
event_id: eventId,
|
||||
source_app: input.sourceApp ?? "cfdm",
|
||||
action: input.action,
|
||||
severity: input.severity ?? "info",
|
||||
actor_user_id: input.actorUserId ?? null,
|
||||
actor_email: input.actorEmail ?? null,
|
||||
actor_name: input.actorName ?? null,
|
||||
target_type: input.targetType ?? null,
|
||||
target_id: input.targetId ?? null,
|
||||
summary: input.summary,
|
||||
details_json: input.details ? JSON.stringify(input.details) : null,
|
||||
ip: input.ip ?? null,
|
||||
created_at: now
|
||||
}).run();
|
||||
return true;
|
||||
}
|
||||
function listAudit(db, opts = {}) {
|
||||
const limit = opts.limit ?? 200;
|
||||
const conditions = [];
|
||||
if (opts.action) conditions.push(eq(auditLog.action, opts.action));
|
||||
if (opts.severity) conditions.push(eq(auditLog.severity, opts.severity));
|
||||
if (opts.sourceApp) conditions.push(eq(auditLog.source_app, opts.sourceApp));
|
||||
if (opts.userId) {
|
||||
conditions.push(
|
||||
or(
|
||||
eq(auditLog.actor_user_id, opts.userId),
|
||||
eq(auditLog.target_id, opts.userId)
|
||||
)
|
||||
);
|
||||
}
|
||||
const rows = conditions.length > 0 ? db.select().from(auditLog).where(and(...conditions)).orderBy(desc(auditLog.created_at)).limit(limit).all() : db.select().from(auditLog).orderBy(desc(auditLog.created_at)).limit(limit).all();
|
||||
return rows.map(mapRow);
|
||||
}
|
||||
|
||||
// src/settings-repo.ts
|
||||
import { eq } from "drizzle-orm";
|
||||
import { eq as eq2 } from "drizzle-orm";
|
||||
var SETTINGS_ID = "settings-main";
|
||||
function toDto(row) {
|
||||
return {
|
||||
@@ -345,17 +434,17 @@ function toDto(row) {
|
||||
};
|
||||
}
|
||||
function getAppSettings(db) {
|
||||
const row = db.select().from(appSettings).where(eq(appSettings.id, SETTINGS_ID)).get();
|
||||
const row = db.select().from(appSettings).where(eq2(appSettings.id, SETTINGS_ID)).get();
|
||||
if (!row) {
|
||||
db.insert(appSettings).values({ id: SETTINGS_ID }).run();
|
||||
return toDto(
|
||||
db.select().from(appSettings).where(eq(appSettings.id, SETTINGS_ID)).get()
|
||||
db.select().from(appSettings).where(eq2(appSettings.id, SETTINGS_ID)).get()
|
||||
);
|
||||
}
|
||||
return toDto(row);
|
||||
}
|
||||
function getAppSettingsSecrets(db) {
|
||||
const row = db.select().from(appSettings).where(eq(appSettings.id, SETTINGS_ID)).get();
|
||||
const row = db.select().from(appSettings).where(eq2(appSettings.id, SETTINGS_ID)).get();
|
||||
return {
|
||||
vpsTrackerUrl: row?.vps_tracker_url?.trim() ?? "",
|
||||
vpsTrackerIntegrationToken: row?.vps_tracker_integration_token?.trim() ?? "",
|
||||
@@ -363,11 +452,11 @@ function getAppSettingsSecrets(db) {
|
||||
};
|
||||
}
|
||||
function updateAppSettings(db, patch) {
|
||||
const existing = db.select().from(appSettings).where(eq(appSettings.id, SETTINGS_ID)).get();
|
||||
const existing = db.select().from(appSettings).where(eq2(appSettings.id, SETTINGS_ID)).get();
|
||||
if (!existing) {
|
||||
db.insert(appSettings).values({ id: SETTINGS_ID }).run();
|
||||
}
|
||||
const current = db.select().from(appSettings).where(eq(appSettings.id, SETTINGS_ID)).get();
|
||||
const current = db.select().from(appSettings).where(eq2(appSettings.id, SETTINGS_ID)).get();
|
||||
db.update(appSettings).set({
|
||||
// app_switcher_json: deprecated — source of truth is auth-portal
|
||||
vps_tracker_url: patch.vpsTrackerUrl !== void 0 ? patch.vpsTrackerUrl : current.vps_tracker_url,
|
||||
@@ -375,14 +464,14 @@ function updateAppSettings(db, patch) {
|
||||
vps_tracker_sync_enabled: patch.vpsTrackerSyncEnabled !== void 0 ? patch.vpsTrackerSyncEnabled : current.vps_tracker_sync_enabled,
|
||||
show_quick_actions: patch.showQuickActions !== void 0 ? patch.showQuickActions : current.show_quick_actions,
|
||||
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
||||
}).where(eq(appSettings.id, SETTINGS_ID)).run();
|
||||
}).where(eq2(appSettings.id, SETTINGS_ID)).run();
|
||||
return getAppSettings(db);
|
||||
}
|
||||
function touchVpsTrackerSync(db) {
|
||||
db.update(appSettings).set({
|
||||
vps_tracker_last_sync_at: (/* @__PURE__ */ new Date()).toISOString(),
|
||||
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
||||
}).where(eq(appSettings.id, SETTINGS_ID)).run();
|
||||
}).where(eq2(appSettings.id, SETTINGS_ID)).run();
|
||||
}
|
||||
|
||||
// src/repos.ts
|
||||
@@ -496,12 +585,12 @@ __export(repos_exports, {
|
||||
upsertSubdomain: () => upsertSubdomain
|
||||
});
|
||||
import { dnsRecordNamesMatch } from "@cfdm/shared";
|
||||
import { and, asc, count, eq as eq2, isNull, like, notInArray, or, sql as sql2 } from "drizzle-orm";
|
||||
import { and as and2, asc, count, eq as eq3, isNull, like, notInArray, or as or2, sql as sql2 } from "drizzle-orm";
|
||||
function listGroups(db) {
|
||||
return db.select().from(groups).orderBy(asc(groups.name)).all();
|
||||
}
|
||||
function getGroup(db, id) {
|
||||
const row = db.select().from(groups).where(eq2(groups.id, id)).get();
|
||||
const row = db.select().from(groups).where(eq3(groups.id, id)).get();
|
||||
if (!row) throw new NotFoundError(`group ${id}`);
|
||||
return row;
|
||||
}
|
||||
@@ -519,17 +608,17 @@ function createGroup(db, name, slug) {
|
||||
return getGroup(db, id);
|
||||
}
|
||||
function updateGroup(db, id, name, slug) {
|
||||
const result = db.update(groups).set({ name, slug, updated_at: sql2`datetime('now')` }).where(eq2(groups.id, id)).run();
|
||||
const result = db.update(groups).set({ name, slug, updated_at: sql2`datetime('now')` }).where(eq3(groups.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`group ${id}`);
|
||||
return getGroup(db, id);
|
||||
}
|
||||
function deleteGroup(db, id) {
|
||||
const result = db.delete(groups).where(eq2(groups.id, id)).run();
|
||||
const result = db.delete(groups).where(eq3(groups.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`group ${id}`);
|
||||
}
|
||||
function listDomains(db, groupId) {
|
||||
if (groupId != null) {
|
||||
return db.select().from(domains).where(eq2(domains.group_id, groupId)).orderBy(asc(domains.zone_name)).all();
|
||||
return db.select().from(domains).where(eq3(domains.group_id, groupId)).orderBy(asc(domains.zone_name)).all();
|
||||
}
|
||||
return db.select().from(domains).orderBy(asc(domains.zone_name)).all();
|
||||
}
|
||||
@@ -598,7 +687,7 @@ function findDomainByZoneName(db, zoneName) {
|
||||
return rows[0] ?? null;
|
||||
}
|
||||
function getDomain(db, id) {
|
||||
const row = db.select().from(domains).where(eq2(domains.id, id)).get();
|
||||
const row = db.select().from(domains).where(eq3(domains.id, id)).get();
|
||||
if (!row) throw new NotFoundError(`domain ${id}`);
|
||||
return row;
|
||||
}
|
||||
@@ -623,33 +712,33 @@ function updateDomain(db, id, patch) {
|
||||
if (patch.environment !== void 0) {
|
||||
updates.environment = patch.environment;
|
||||
}
|
||||
const result = db.update(domains).set(updates).where(eq2(domains.id, id)).run();
|
||||
const result = db.update(domains).set(updates).where(eq3(domains.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`domain ${id}`);
|
||||
return getDomain(db, id);
|
||||
}
|
||||
function deleteDomain(db, id) {
|
||||
const result = db.delete(domains).where(eq2(domains.id, id)).run();
|
||||
const result = db.delete(domains).where(eq3(domains.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`domain ${id}`);
|
||||
}
|
||||
function setDomainLastSynced(db, id) {
|
||||
db.update(domains).set({
|
||||
last_synced_at: sql2`datetime('now')`,
|
||||
updated_at: sql2`datetime('now')`
|
||||
}).where(eq2(domains.id, id)).run();
|
||||
}).where(eq3(domains.id, id)).run();
|
||||
}
|
||||
function listAllDomains(db) {
|
||||
return listDomains(db);
|
||||
}
|
||||
function listSubdomainsByDomain(db, domainId) {
|
||||
return db.select().from(subdomains).where(eq2(subdomains.domain_id, domainId)).orderBy(asc(subdomains.name)).all();
|
||||
return db.select().from(subdomains).where(eq3(subdomains.domain_id, domainId)).orderBy(asc(subdomains.name)).all();
|
||||
}
|
||||
function getSubdomain(db, id) {
|
||||
const row = db.select().from(subdomains).where(eq2(subdomains.id, id)).get();
|
||||
const row = db.select().from(subdomains).where(eq3(subdomains.id, id)).get();
|
||||
if (!row) throw new NotFoundError(`subdomain ${id}`);
|
||||
return row;
|
||||
}
|
||||
function findSubdomainByDomainAndName(db, domainId, name) {
|
||||
const row = db.select().from(subdomains).where(and(eq2(subdomains.domain_id, domainId), eq2(subdomains.name, name))).get();
|
||||
const row = db.select().from(subdomains).where(and2(eq3(subdomains.domain_id, domainId), eq3(subdomains.name, name))).get();
|
||||
return row ?? null;
|
||||
}
|
||||
function upsertSubdomain(db, domainId, name, fqdn) {
|
||||
@@ -673,12 +762,12 @@ function updateSubdomain(db, id, patch) {
|
||||
if (patch.cert_monitoring !== void 0) {
|
||||
updates.cert_monitoring = patch.cert_monitoring;
|
||||
}
|
||||
const result = db.update(subdomains).set(updates).where(eq2(subdomains.id, id)).run();
|
||||
const result = db.update(subdomains).set(updates).where(eq3(subdomains.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`subdomain ${id}`);
|
||||
return getSubdomain(db, id);
|
||||
}
|
||||
function deleteSubdomain(db, id) {
|
||||
const result = db.delete(subdomains).where(eq2(subdomains.id, id)).run();
|
||||
const result = db.delete(subdomains).where(eq3(subdomains.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`subdomain ${id}`);
|
||||
}
|
||||
function listAllSubdomains(db) {
|
||||
@@ -688,9 +777,9 @@ function mapDnsRecord(row) {
|
||||
return row;
|
||||
}
|
||||
function listDnsRecords(db, domainId, filter = {}) {
|
||||
const conditions = [eq2(dnsRecords.domain_id, domainId)];
|
||||
const conditions = [eq3(dnsRecords.domain_id, domainId)];
|
||||
if (filter.record_type) {
|
||||
conditions.push(eq2(dnsRecords.record_type, filter.record_type.toUpperCase()));
|
||||
conditions.push(eq3(dnsRecords.record_type, filter.record_type.toUpperCase()));
|
||||
}
|
||||
if (filter.name) {
|
||||
conditions.push(like(dnsRecords.name, `%${filter.name}%`));
|
||||
@@ -699,15 +788,15 @@ function listDnsRecords(db, domainId, filter = {}) {
|
||||
conditions.push(like(dnsRecords.content, `%${filter.content}%`));
|
||||
}
|
||||
if (filter.proxied != null) {
|
||||
conditions.push(eq2(dnsRecords.proxied, filter.proxied));
|
||||
conditions.push(eq3(dnsRecords.proxied, filter.proxied));
|
||||
}
|
||||
if (filter.sync_status) {
|
||||
conditions.push(eq2(dnsRecords.sync_status, filter.sync_status));
|
||||
conditions.push(eq3(dnsRecords.sync_status, filter.sync_status));
|
||||
}
|
||||
if (filter.q) {
|
||||
const pat = `%${filter.q}%`;
|
||||
conditions.push(
|
||||
or(
|
||||
or2(
|
||||
like(dnsRecords.name, pat),
|
||||
like(dnsRecords.content, pat),
|
||||
like(dnsRecords.record_type, pat)
|
||||
@@ -718,10 +807,10 @@ function listDnsRecords(db, domainId, filter = {}) {
|
||||
const page = Math.max(1, filter.page ?? 1);
|
||||
const limit = Math.min(200, Math.max(1, filter.limit ?? 50));
|
||||
const offset = (page - 1) * limit;
|
||||
return db.select().from(dnsRecords).where(and(...conditions)).orderBy(asc(sortCol)).limit(limit).offset(offset).all().map(mapDnsRecord);
|
||||
return db.select().from(dnsRecords).where(and2(...conditions)).orderBy(asc(sortCol)).limit(limit).offset(offset).all().map(mapDnsRecord);
|
||||
}
|
||||
function getDnsRecord(db, domainId, id) {
|
||||
const row = db.select().from(dnsRecords).where(and(eq2(dnsRecords.id, id), eq2(dnsRecords.domain_id, domainId))).get();
|
||||
const row = db.select().from(dnsRecords).where(and2(eq3(dnsRecords.id, id), eq3(dnsRecords.domain_id, domainId))).get();
|
||||
if (!row) throw new NotFoundError(`dns record ${id}`);
|
||||
return mapDnsRecord(row);
|
||||
}
|
||||
@@ -752,7 +841,7 @@ function updateDnsFields(db, id, recordType, name, content, ttl, proxied, priori
|
||||
sync_status: syncStatus,
|
||||
last_error: lastError,
|
||||
updated_at: sql2`datetime('now')`
|
||||
}).where(eq2(dnsRecords.id, id)).run();
|
||||
}).where(eq3(dnsRecords.id, id)).run();
|
||||
}
|
||||
function setDnsSyncStatus(db, id, syncStatus, cfRecordId, lastError) {
|
||||
db.update(dnsRecords).set({
|
||||
@@ -760,19 +849,19 @@ function setDnsSyncStatus(db, id, syncStatus, cfRecordId, lastError) {
|
||||
cf_record_id: cfRecordId,
|
||||
last_error: lastError,
|
||||
updated_at: sql2`datetime('now')`
|
||||
}).where(eq2(dnsRecords.id, id)).run();
|
||||
}).where(eq3(dnsRecords.id, id)).run();
|
||||
}
|
||||
function deleteDnsRecord(db, id) {
|
||||
db.delete(dnsRecords).where(eq2(dnsRecords.id, id)).run();
|
||||
db.delete(dnsRecords).where(eq3(dnsRecords.id, id)).run();
|
||||
}
|
||||
function listDnsByDomain(db, domainId) {
|
||||
return db.select().from(dnsRecords).where(eq2(dnsRecords.domain_id, domainId)).all().map(mapDnsRecord);
|
||||
return db.select().from(dnsRecords).where(eq3(dnsRecords.domain_id, domainId)).all().map(mapDnsRecord);
|
||||
}
|
||||
function findDnsByCfId(db, domainId, cfRecordId) {
|
||||
const row = db.select().from(dnsRecords).where(
|
||||
and(
|
||||
eq2(dnsRecords.domain_id, domainId),
|
||||
eq2(dnsRecords.cf_record_id, cfRecordId)
|
||||
and2(
|
||||
eq3(dnsRecords.domain_id, domainId),
|
||||
eq3(dnsRecords.cf_record_id, cfRecordId)
|
||||
)
|
||||
).get();
|
||||
return row ? mapDnsRecord(row) : null;
|
||||
@@ -781,7 +870,7 @@ function markDnsPendingDelete(db, id) {
|
||||
setDnsSyncStatus(db, id, "pending_delete", null, null);
|
||||
}
|
||||
function maxSortOrderInGroup(db, groupId) {
|
||||
const condition = groupId === null ? isNull(services.service_group_id) : eq2(services.service_group_id, groupId);
|
||||
const condition = groupId === null ? isNull(services.service_group_id) : eq3(services.service_group_id, groupId);
|
||||
const row = db.select({ maxOrder: sql2`coalesce(max(${services.sort_order}), -1)` }).from(services).where(condition).get();
|
||||
return row?.maxOrder ?? -1;
|
||||
}
|
||||
@@ -789,13 +878,13 @@ function listServices(db) {
|
||||
return db.select().from(services).orderBy(asc(services.sort_order), asc(services.name)).all();
|
||||
}
|
||||
function listServicesByGroup(db, groupId) {
|
||||
return db.select().from(services).where(eq2(services.service_group_id, groupId)).orderBy(asc(services.sort_order), asc(services.name)).all();
|
||||
return db.select().from(services).where(eq3(services.service_group_id, groupId)).orderBy(asc(services.sort_order), asc(services.name)).all();
|
||||
}
|
||||
function listUngroupedServices(db) {
|
||||
return db.select().from(services).where(isNull(services.service_group_id)).orderBy(asc(services.sort_order), asc(services.name)).all();
|
||||
}
|
||||
function getService(db, id) {
|
||||
const row = db.select().from(services).where(eq2(services.id, id)).get();
|
||||
const row = db.select().from(services).where(eq3(services.id, id)).get();
|
||||
if (!row) throw new NotFoundError(`service ${id}`);
|
||||
return row;
|
||||
}
|
||||
@@ -810,11 +899,11 @@ function updateService(db, id, name, slug) {
|
||||
slug,
|
||||
subdomain: slug,
|
||||
updated_at: sql2`datetime('now')`
|
||||
}).where(eq2(services.id, id)).run();
|
||||
}).where(eq3(services.id, id)).run();
|
||||
return getService(db, id);
|
||||
}
|
||||
function setServiceEnabled(db, id, enabled) {
|
||||
db.update(services).set({ enabled, updated_at: sql2`datetime('now')` }).where(eq2(services.id, id)).run();
|
||||
db.update(services).set({ enabled, updated_at: sql2`datetime('now')` }).where(eq3(services.id, id)).run();
|
||||
return getService(db, id);
|
||||
}
|
||||
function setServiceLb(db, id, weight, priority) {
|
||||
@@ -822,7 +911,7 @@ function setServiceLb(db, id, weight, priority) {
|
||||
lb_weight: weight,
|
||||
lb_priority: priority,
|
||||
updated_at: sql2`datetime('now')`
|
||||
}).where(eq2(services.id, id)).run();
|
||||
}).where(eq3(services.id, id)).run();
|
||||
}
|
||||
function setServiceGroup(db, id, groupId) {
|
||||
const sortOrder = maxSortOrderInGroup(db, groupId) + 1;
|
||||
@@ -830,14 +919,14 @@ function setServiceGroup(db, id, groupId) {
|
||||
service_group_id: groupId,
|
||||
sort_order: sortOrder,
|
||||
updated_at: sql2`datetime('now')`
|
||||
}).where(eq2(services.id, id)).run();
|
||||
}).where(eq3(services.id, id)).run();
|
||||
}
|
||||
function reorderServices(db, groupId, orderedIds) {
|
||||
const uniqueIds = new Set(orderedIds);
|
||||
if (uniqueIds.size !== orderedIds.length) {
|
||||
throw new Error("duplicate service ids in reorder request");
|
||||
}
|
||||
const condition = groupId === null ? isNull(services.service_group_id) : eq2(services.service_group_id, groupId);
|
||||
const condition = groupId === null ? isNull(services.service_group_id) : eq3(services.service_group_id, groupId);
|
||||
const existing = db.select({ id: services.id }).from(services).where(condition).all().map((row) => row.id);
|
||||
const existingSet = new Set(existing);
|
||||
for (const serviceId of orderedIds) {
|
||||
@@ -847,12 +936,12 @@ function reorderServices(db, groupId, orderedIds) {
|
||||
}
|
||||
db.transaction((tx) => {
|
||||
for (let index = 0; index < orderedIds.length; index++) {
|
||||
tx.update(services).set({ sort_order: index, updated_at: sql2`datetime('now')` }).where(eq2(services.id, orderedIds[index])).run();
|
||||
tx.update(services).set({ sort_order: index, updated_at: sql2`datetime('now')` }).where(eq3(services.id, orderedIds[index])).run();
|
||||
}
|
||||
});
|
||||
}
|
||||
function deleteService(db, id) {
|
||||
const result = db.delete(services).where(eq2(services.id, id)).run();
|
||||
const result = db.delete(services).where(eq3(services.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`service ${id}`);
|
||||
}
|
||||
function mapServiceGroup(row) {
|
||||
@@ -880,7 +969,7 @@ function listServiceGroups(db) {
|
||||
return db.select().from(serviceGroups).orderBy(asc(serviceGroups.name)).all().map(mapServiceGroup);
|
||||
}
|
||||
function getServiceGroup(db, id) {
|
||||
const row = db.select().from(serviceGroups).where(eq2(serviceGroups.id, id)).get();
|
||||
const row = db.select().from(serviceGroups).where(eq3(serviceGroups.id, id)).get();
|
||||
if (!row) throw new NotFoundError(`service group ${id}`);
|
||||
return mapServiceGroup(row);
|
||||
}
|
||||
@@ -929,37 +1018,37 @@ function updateServiceGroup(db, id, name, groupType, icon, domain, lbPatch) {
|
||||
if (lbPatch.health_check_verify_tls !== void 0)
|
||||
update.health_check_verify_tls = lbPatch.health_check_verify_tls;
|
||||
}
|
||||
const result = db.update(serviceGroups).set(update).where(eq2(serviceGroups.id, id)).run();
|
||||
const result = db.update(serviceGroups).set(update).where(eq3(serviceGroups.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`service group ${id}`);
|
||||
return getServiceGroup(db, id);
|
||||
}
|
||||
function setServiceGroupEnabled(db, id, enabled) {
|
||||
const result = db.update(serviceGroups).set({ enabled, updated_at: sql2`datetime('now')` }).where(eq2(serviceGroups.id, id)).run();
|
||||
const result = db.update(serviceGroups).set({ enabled, updated_at: sql2`datetime('now')` }).where(eq3(serviceGroups.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`service group ${id}`);
|
||||
return getServiceGroup(db, id);
|
||||
}
|
||||
function deleteServiceGroup(db, id) {
|
||||
const result = db.delete(serviceGroups).where(eq2(serviceGroups.id, id)).run();
|
||||
const result = db.delete(serviceGroups).where(eq3(serviceGroups.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`service group ${id}`);
|
||||
}
|
||||
function listServiceIps(db, serviceId) {
|
||||
return db.select({ ip: serviceIps.ip }).from(serviceIps).where(eq2(serviceIps.service_id, serviceId)).all().map((r) => r.ip);
|
||||
return db.select({ ip: serviceIps.ip }).from(serviceIps).where(eq3(serviceIps.service_id, serviceId)).all().map((r) => r.ip);
|
||||
}
|
||||
function replaceServiceIps(db, serviceId, ips) {
|
||||
db.delete(serviceIps).where(eq2(serviceIps.service_id, serviceId)).run();
|
||||
db.delete(serviceIps).where(eq3(serviceIps.service_id, serviceId)).run();
|
||||
for (const ip of ips) {
|
||||
db.insert(serviceIps).values({ service_id: serviceId, ip }).run();
|
||||
}
|
||||
}
|
||||
function listBindingIps(db, bindingId) {
|
||||
return db.select({ ip: serviceBindingIps.ip }).from(serviceBindingIps).where(eq2(serviceBindingIps.binding_id, bindingId)).all().map((r) => r.ip);
|
||||
return db.select({ ip: serviceBindingIps.ip }).from(serviceBindingIps).where(eq3(serviceBindingIps.binding_id, bindingId)).all().map((r) => r.ip);
|
||||
}
|
||||
function listBindingIpsWithMeta(db, bindingId) {
|
||||
return db.select({
|
||||
ip: serviceBindingIps.ip,
|
||||
weight: serviceBindingIps.weight,
|
||||
priority: serviceBindingIps.priority
|
||||
}).from(serviceBindingIps).where(eq2(serviceBindingIps.binding_id, bindingId)).all();
|
||||
}).from(serviceBindingIps).where(eq3(serviceBindingIps.binding_id, bindingId)).all();
|
||||
}
|
||||
function replaceBindingIps(db, bindingId, ips) {
|
||||
replaceBindingIpsWithMeta(
|
||||
@@ -969,7 +1058,7 @@ function replaceBindingIps(db, bindingId, ips) {
|
||||
);
|
||||
}
|
||||
function replaceBindingIpsWithMeta(db, bindingId, entries) {
|
||||
db.delete(serviceBindingIps).where(eq2(serviceBindingIps.binding_id, bindingId)).run();
|
||||
db.delete(serviceBindingIps).where(eq3(serviceBindingIps.binding_id, bindingId)).run();
|
||||
for (const entry of entries) {
|
||||
db.insert(serviceBindingIps).values({
|
||||
binding_id: bindingId,
|
||||
@@ -1000,13 +1089,13 @@ function updateBindingLbConfig(db, bindingId, patch) {
|
||||
update.health_check_timeout_ms = patch.health_check_timeout_ms;
|
||||
if (patch.health_check_verify_tls !== void 0)
|
||||
update.health_check_verify_tls = patch.health_check_verify_tls;
|
||||
db.update(serviceBindings).set(update).where(eq2(serviceBindings.id, bindingId)).run();
|
||||
db.update(serviceBindings).set(update).where(eq3(serviceBindings.id, bindingId)).run();
|
||||
}
|
||||
function setBindingCnameTarget(db, bindingId, target) {
|
||||
db.update(serviceBindings).set({
|
||||
cname_target: target,
|
||||
updated_at: sql2`datetime('now')`
|
||||
}).where(eq2(serviceBindings.id, bindingId)).run();
|
||||
}).where(eq3(serviceBindings.id, bindingId)).run();
|
||||
}
|
||||
function listRecordsForBinding(db, bindingId) {
|
||||
return db.all(sql2`
|
||||
@@ -1024,9 +1113,9 @@ function linkBindingRecord(db, bindingId, dnsRecordId) {
|
||||
}
|
||||
function unlinkBindingRecord(db, bindingId, dnsRecordId) {
|
||||
db.delete(serviceBindingRecords).where(
|
||||
and(
|
||||
eq2(serviceBindingRecords.binding_id, bindingId),
|
||||
eq2(serviceBindingRecords.dns_record_id, dnsRecordId)
|
||||
and2(
|
||||
eq3(serviceBindingRecords.binding_id, bindingId),
|
||||
eq3(serviceBindingRecords.dns_record_id, dnsRecordId)
|
||||
)
|
||||
).run();
|
||||
}
|
||||
@@ -1046,9 +1135,9 @@ function linkGroupDnsRecord(db, groupId, dnsRecordId) {
|
||||
}
|
||||
function unlinkGroupDnsRecord(db, groupId, dnsRecordId) {
|
||||
db.delete(serviceGroupDnsRecords).where(
|
||||
and(
|
||||
eq2(serviceGroupDnsRecords.group_id, groupId),
|
||||
eq2(serviceGroupDnsRecords.dns_record_id, dnsRecordId)
|
||||
and2(
|
||||
eq3(serviceGroupDnsRecords.group_id, groupId),
|
||||
eq3(serviceGroupDnsRecords.dns_record_id, dnsRecordId)
|
||||
)
|
||||
).run();
|
||||
}
|
||||
@@ -1138,7 +1227,7 @@ function listBindingsByService(db, serviceId) {
|
||||
`);
|
||||
}
|
||||
function getBinding(db, id) {
|
||||
const row = db.select().from(serviceBindings).where(eq2(serviceBindings.id, id)).get();
|
||||
const row = db.select().from(serviceBindings).where(eq3(serviceBindings.id, id)).get();
|
||||
if (!row) throw new NotFoundError(`service binding ${id}`);
|
||||
return row;
|
||||
}
|
||||
@@ -1157,10 +1246,10 @@ function getBindingView(db, id) {
|
||||
}
|
||||
function findBinding(db, serviceId, domainId, hostname) {
|
||||
const row = db.select().from(serviceBindings).where(
|
||||
and(
|
||||
eq2(serviceBindings.service_id, serviceId),
|
||||
eq2(serviceBindings.domain_id, domainId),
|
||||
eq2(serviceBindings.hostname, hostname)
|
||||
and2(
|
||||
eq3(serviceBindings.service_id, serviceId),
|
||||
eq3(serviceBindings.domain_id, domainId),
|
||||
eq3(serviceBindings.hostname, hostname)
|
||||
)
|
||||
).get();
|
||||
return row ?? null;
|
||||
@@ -1180,43 +1269,43 @@ function updateBindingFields(db, id, serviceId, hostname, dnsRecordId) {
|
||||
hostname,
|
||||
dns_record_id: dnsRecordId,
|
||||
updated_at: sql2`datetime('now')`
|
||||
}).where(eq2(serviceBindings.id, id)).run();
|
||||
}).where(eq3(serviceBindings.id, id)).run();
|
||||
}
|
||||
function setBindingDnsRecordId(db, bindingId, dnsRecordId) {
|
||||
db.update(serviceBindings).set({
|
||||
dns_record_id: dnsRecordId,
|
||||
updated_at: sql2`datetime('now')`
|
||||
}).where(eq2(serviceBindings.id, bindingId)).run();
|
||||
}).where(eq3(serviceBindings.id, bindingId)).run();
|
||||
}
|
||||
function bindingsToRemove(db, serviceId, keepIds) {
|
||||
const all = db.select().from(serviceBindings).where(eq2(serviceBindings.service_id, serviceId)).all();
|
||||
const all = db.select().from(serviceBindings).where(eq3(serviceBindings.service_id, serviceId)).all();
|
||||
return all.filter((b) => !keepIds.includes(b.id));
|
||||
}
|
||||
function deleteBindingsExcept(db, serviceId, keepIds) {
|
||||
const all = db.select().from(serviceBindings).where(eq2(serviceBindings.service_id, serviceId)).all();
|
||||
const all = db.select().from(serviceBindings).where(eq3(serviceBindings.service_id, serviceId)).all();
|
||||
for (const binding of all) {
|
||||
if (!keepIds.includes(binding.id)) {
|
||||
db.delete(serviceBindings).where(eq2(serviceBindings.id, binding.id)).run();
|
||||
db.delete(serviceBindings).where(eq3(serviceBindings.id, binding.id)).run();
|
||||
}
|
||||
}
|
||||
}
|
||||
function deleteBinding(db, id) {
|
||||
const result = db.delete(serviceBindings).where(eq2(serviceBindings.id, id)).run();
|
||||
const result = db.delete(serviceBindings).where(eq3(serviceBindings.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`service binding ${id}`);
|
||||
}
|
||||
function listCertificates(db, status) {
|
||||
if (status) {
|
||||
return db.select().from(certificates).where(eq2(certificates.status, status)).orderBy(asc(certificates.expires_at)).all();
|
||||
return db.select().from(certificates).where(eq3(certificates.status, status)).orderBy(asc(certificates.expires_at)).all();
|
||||
}
|
||||
return db.select().from(certificates).orderBy(asc(certificates.expires_at)).all();
|
||||
}
|
||||
function getCertificate(db, id) {
|
||||
const row = db.select().from(certificates).where(eq2(certificates.id, id)).get();
|
||||
const row = db.select().from(certificates).where(eq3(certificates.id, id)).get();
|
||||
if (!row) throw new NotFoundError(`certificate ${id}`);
|
||||
return row;
|
||||
}
|
||||
function upsertCertificateCheck(db, domainId, subdomainId, hostname, expiresAt, status, lastError) {
|
||||
const existing = db.select().from(certificates).where(eq2(certificates.hostname, hostname)).get();
|
||||
const existing = db.select().from(certificates).where(eq3(certificates.hostname, hostname)).get();
|
||||
if (existing) {
|
||||
db.update(certificates).set({
|
||||
domain_id: domainId,
|
||||
@@ -1226,7 +1315,7 @@ function upsertCertificateCheck(db, domainId, subdomainId, hostname, expiresAt,
|
||||
last_error: lastError,
|
||||
status,
|
||||
updated_at: sql2`datetime('now')`
|
||||
}).where(eq2(certificates.id, existing.id)).run();
|
||||
}).where(eq3(certificates.id, existing.id)).run();
|
||||
return getCertificate(db, existing.id);
|
||||
}
|
||||
const id = db.insert(certificates).values({
|
||||
@@ -1259,7 +1348,7 @@ function createSyncJob(db, id, domainId) {
|
||||
db.insert(syncJobs).values({ id, domain_id: domainId, status: "pending" }).run();
|
||||
}
|
||||
function getSyncJob(db, id) {
|
||||
const row = db.select().from(syncJobs).where(eq2(syncJobs.id, id)).get();
|
||||
const row = db.select().from(syncJobs).where(eq3(syncJobs.id, id)).get();
|
||||
if (!row) throw new NotFoundError(`sync job ${id}`);
|
||||
return row;
|
||||
}
|
||||
@@ -1268,7 +1357,7 @@ function finishSyncJob(db, id, status, message) {
|
||||
status,
|
||||
message,
|
||||
finished_at: sql2`datetime('now')`
|
||||
}).where(eq2(syncJobs.id, id)).run();
|
||||
}).where(eq3(syncJobs.id, id)).run();
|
||||
}
|
||||
function listIpHealthStatus(db, scope, refId) {
|
||||
return db.all(sql2`
|
||||
@@ -1409,18 +1498,18 @@ function upsertIpHealthStatus(db, scope, refId, ip, status, latencyMs, consecuti
|
||||
}
|
||||
function deleteIpHealthStatusForRef(db, scope, refId) {
|
||||
db.delete(ipHealthStatus).where(
|
||||
and(
|
||||
eq2(ipHealthStatus.scope, scope),
|
||||
eq2(ipHealthStatus.ref_id, refId)
|
||||
and2(
|
||||
eq3(ipHealthStatus.scope, scope),
|
||||
eq3(ipHealthStatus.ref_id, refId)
|
||||
)
|
||||
).run();
|
||||
}
|
||||
function deleteIpHealthStatusForIp(db, scope, refId, ip) {
|
||||
db.delete(ipHealthStatus).where(
|
||||
and(
|
||||
eq2(ipHealthStatus.scope, scope),
|
||||
eq2(ipHealthStatus.ref_id, refId),
|
||||
eq2(ipHealthStatus.ip, ip)
|
||||
and2(
|
||||
eq3(ipHealthStatus.scope, scope),
|
||||
eq3(ipHealthStatus.ref_id, refId),
|
||||
eq3(ipHealthStatus.ip, ip)
|
||||
)
|
||||
).run();
|
||||
}
|
||||
@@ -1553,10 +1642,10 @@ function listHealthCheckTargets(db) {
|
||||
}));
|
||||
}
|
||||
function listDomainTags(db, domainId) {
|
||||
return db.select({ tag: domainTags.tag }).from(domainTags).where(eq2(domainTags.domain_id, domainId)).all().map((r) => r.tag);
|
||||
return db.select({ tag: domainTags.tag }).from(domainTags).where(eq3(domainTags.domain_id, domainId)).all().map((r) => r.tag);
|
||||
}
|
||||
function setDomainTags(db, domainId, tags) {
|
||||
db.delete(domainTags).where(eq2(domainTags.domain_id, domainId)).run();
|
||||
db.delete(domainTags).where(eq3(domainTags.domain_id, domainId)).run();
|
||||
const unique = [...new Set(tags.map((t) => t.trim()).filter(Boolean))];
|
||||
for (const tag of unique) {
|
||||
db.insert(domainTags).values({ domain_id: domainId, tag }).run();
|
||||
@@ -1573,13 +1662,13 @@ function addDomainTags(db, domainId, tags) {
|
||||
}
|
||||
}
|
||||
function listDomainMonitors(db, domainId) {
|
||||
return db.select().from(domainMonitors).where(eq2(domainMonitors.domain_id, domainId)).orderBy(asc(domainMonitors.id)).all();
|
||||
return db.select().from(domainMonitors).where(eq3(domainMonitors.domain_id, domainId)).orderBy(asc(domainMonitors.id)).all();
|
||||
}
|
||||
function listEnabledDomainMonitors(db) {
|
||||
return db.select().from(domainMonitors).where(eq2(domainMonitors.enabled, true)).all();
|
||||
return db.select().from(domainMonitors).where(eq3(domainMonitors.enabled, true)).all();
|
||||
}
|
||||
function getDomainMonitor(db, id) {
|
||||
const row = db.select().from(domainMonitors).where(eq2(domainMonitors.id, id)).get();
|
||||
const row = db.select().from(domainMonitors).where(eq3(domainMonitors.id, id)).get();
|
||||
if (!row) throw new NotFoundError(`domain_monitor ${id}`);
|
||||
return row;
|
||||
}
|
||||
@@ -1597,7 +1686,7 @@ function createDomainMonitor(db, domainId, input) {
|
||||
return getDomainMonitor(db, id);
|
||||
}
|
||||
function deleteDomainMonitor(db, id) {
|
||||
const result = db.delete(domainMonitors).where(eq2(domainMonitors.id, id)).run();
|
||||
const result = db.delete(domainMonitors).where(eq3(domainMonitors.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`domain_monitor ${id}`);
|
||||
}
|
||||
function updateDomainMonitorResult(db, monitorId, status, latencyMs, error) {
|
||||
@@ -1607,7 +1696,7 @@ function updateDomainMonitorResult(db, monitorId, status, latencyMs, error) {
|
||||
last_checked_at: sql2`datetime('now')`,
|
||||
last_error: error,
|
||||
updated_at: sql2`datetime('now')`
|
||||
}).where(eq2(domainMonitors.id, monitorId)).run();
|
||||
}).where(eq3(domainMonitors.id, monitorId)).run();
|
||||
db.insert(domainMonitorResults).values({
|
||||
monitor_id: monitorId,
|
||||
status,
|
||||
@@ -1665,6 +1754,8 @@ export {
|
||||
ConflictError,
|
||||
NotFoundError,
|
||||
appSettings,
|
||||
appendAudit,
|
||||
auditLog,
|
||||
certificates,
|
||||
createDb,
|
||||
createMemoryDb,
|
||||
@@ -1678,6 +1769,7 @@ export {
|
||||
groups,
|
||||
healthCheck,
|
||||
ipHealthStatus,
|
||||
listAudit,
|
||||
notificationLog,
|
||||
repos_exports as repos,
|
||||
resolveDatabasePath,
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
event_id TEXT,
|
||||
source_app TEXT NOT NULL DEFAULT 'cfdm',
|
||||
action TEXT NOT NULL,
|
||||
severity TEXT NOT NULL DEFAULT 'info',
|
||||
actor_user_id TEXT,
|
||||
actor_email TEXT,
|
||||
actor_name TEXT,
|
||||
target_type TEXT,
|
||||
target_id TEXT,
|
||||
summary TEXT NOT NULL,
|
||||
details_json TEXT,
|
||||
ip TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_created ON audit_log(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_action ON audit_log(action);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_actor ON audit_log(actor_user_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_target ON audit_log(target_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_source ON audit_log(source_app, created_at);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_audit_log_event_id ON audit_log(event_id) WHERE event_id IS NOT NULL;
|
||||
@@ -0,0 +1,131 @@
|
||||
import { and, desc, eq, or } from "drizzle-orm";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type {
|
||||
AuditLogEntry,
|
||||
AuditSeverity,
|
||||
AuditSourceApp,
|
||||
AuditTargetType,
|
||||
} from "@cfdm/shared";
|
||||
import type { Db } from "./client.js";
|
||||
import { auditLog } from "./schema.js";
|
||||
|
||||
export type AppendAuditInput = {
|
||||
eventId?: string | null;
|
||||
sourceApp?: AuditSourceApp;
|
||||
action: string;
|
||||
severity?: AuditSeverity;
|
||||
actorUserId?: string | null;
|
||||
actorEmail?: string | null;
|
||||
actorName?: string | null;
|
||||
targetType?: AuditTargetType | null;
|
||||
targetId?: string | null;
|
||||
summary: string;
|
||||
details?: Record<string, unknown> | null;
|
||||
ip?: string | null;
|
||||
createdAt?: string | null;
|
||||
};
|
||||
|
||||
function mapRow(row: typeof auditLog.$inferSelect): AuditLogEntry {
|
||||
let details: Record<string, unknown> | null = null;
|
||||
if (row.details_json) {
|
||||
try {
|
||||
details = JSON.parse(row.details_json) as Record<string, unknown>;
|
||||
} catch {
|
||||
details = { raw: row.details_json };
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: row.id,
|
||||
event_id: row.event_id,
|
||||
source_app: (row.source_app as AuditSourceApp) || "cfdm",
|
||||
action: row.action,
|
||||
severity: row.severity as AuditSeverity,
|
||||
actor_user_id: row.actor_user_id,
|
||||
actor_email: row.actor_email,
|
||||
actor_name: row.actor_name,
|
||||
target_type: (row.target_type as AuditTargetType | null) ?? null,
|
||||
target_id: row.target_id,
|
||||
summary: row.summary,
|
||||
details,
|
||||
ip: row.ip,
|
||||
created_at: row.created_at,
|
||||
};
|
||||
}
|
||||
|
||||
/** @returns true if inserted, false if duplicate event_id */
|
||||
export function appendAudit(db: Db, input: AppendAuditInput): boolean {
|
||||
const now = input.createdAt ?? new Date().toISOString();
|
||||
const eventId = input.eventId ?? null;
|
||||
|
||||
if (eventId) {
|
||||
const existing = db
|
||||
.select({ id: auditLog.id })
|
||||
.from(auditLog)
|
||||
.where(eq(auditLog.event_id, eventId))
|
||||
.get();
|
||||
if (existing) return false;
|
||||
}
|
||||
|
||||
db.insert(auditLog)
|
||||
.values({
|
||||
id: randomUUID(),
|
||||
event_id: eventId,
|
||||
source_app: input.sourceApp ?? "cfdm",
|
||||
action: input.action,
|
||||
severity: input.severity ?? "info",
|
||||
actor_user_id: input.actorUserId ?? null,
|
||||
actor_email: input.actorEmail ?? null,
|
||||
actor_name: input.actorName ?? null,
|
||||
target_type: input.targetType ?? null,
|
||||
target_id: input.targetId ?? null,
|
||||
summary: input.summary,
|
||||
details_json: input.details ? JSON.stringify(input.details) : null,
|
||||
ip: input.ip ?? null,
|
||||
created_at: now,
|
||||
})
|
||||
.run();
|
||||
return true;
|
||||
}
|
||||
|
||||
export function listAudit(
|
||||
db: Db,
|
||||
opts: {
|
||||
action?: string;
|
||||
severity?: AuditSeverity;
|
||||
userId?: string;
|
||||
sourceApp?: AuditSourceApp;
|
||||
limit?: number;
|
||||
} = {},
|
||||
): AuditLogEntry[] {
|
||||
const limit = opts.limit ?? 200;
|
||||
const conditions = [];
|
||||
if (opts.action) conditions.push(eq(auditLog.action, opts.action));
|
||||
if (opts.severity) conditions.push(eq(auditLog.severity, opts.severity));
|
||||
if (opts.sourceApp) conditions.push(eq(auditLog.source_app, opts.sourceApp));
|
||||
if (opts.userId) {
|
||||
conditions.push(
|
||||
or(
|
||||
eq(auditLog.actor_user_id, opts.userId),
|
||||
eq(auditLog.target_id, opts.userId),
|
||||
)!,
|
||||
);
|
||||
}
|
||||
|
||||
const rows =
|
||||
conditions.length > 0
|
||||
? db
|
||||
.select()
|
||||
.from(auditLog)
|
||||
.where(and(...conditions))
|
||||
.orderBy(desc(auditLog.created_at))
|
||||
.limit(limit)
|
||||
.all()
|
||||
: db
|
||||
.select()
|
||||
.from(auditLog)
|
||||
.orderBy(desc(auditLog.created_at))
|
||||
.limit(limit)
|
||||
.all();
|
||||
|
||||
return rows.map(mapRow);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from "./schema.js";
|
||||
export * from "./client.js";
|
||||
export * from "./errors.js";
|
||||
export * from "./audit-log.js";
|
||||
export * from "./settings-repo.js";
|
||||
export * as repos from "./repos.js";
|
||||
export type { DnsListFilter, UpdateSubdomainPatch } from "./repos.js";
|
||||
|
||||
@@ -359,6 +359,25 @@ export const notificationLog = sqliteTable("notification_log", {
|
||||
.default(sql`datetime('now')`),
|
||||
});
|
||||
|
||||
export const auditLog = sqliteTable("audit_log", {
|
||||
id: text("id").primaryKey(),
|
||||
event_id: text("event_id"),
|
||||
source_app: text("source_app").notNull().default("cfdm"),
|
||||
action: text("action").notNull(),
|
||||
severity: text("severity").notNull().default("info"),
|
||||
actor_user_id: text("actor_user_id"),
|
||||
actor_email: text("actor_email"),
|
||||
actor_name: text("actor_name"),
|
||||
target_type: text("target_type"),
|
||||
target_id: text("target_id"),
|
||||
summary: text("summary").notNull(),
|
||||
details_json: text("details_json"),
|
||||
ip: text("ip"),
|
||||
created_at: text("created_at")
|
||||
.notNull()
|
||||
.default(sql`datetime('now')`),
|
||||
});
|
||||
|
||||
export const schema = {
|
||||
groups,
|
||||
services,
|
||||
@@ -379,4 +398,5 @@ export const schema = {
|
||||
domainMonitors,
|
||||
domainMonitorResults,
|
||||
notificationLog,
|
||||
auditLog,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user