134 lines
3.9 KiB
TypeScript
134 lines
3.9 KiB
TypeScript
import { db } from '../db/client.js';
|
|
import type { AuditLogEntryRow, AuthContext, FlockNoteRow } from '../types.js';
|
|
|
|
type AuditLogInput = {
|
|
workspaceId: number;
|
|
auth?: AuthContext;
|
|
action: string;
|
|
entityType: string;
|
|
entityId?: string | null;
|
|
entityName?: string | null;
|
|
details?: Record<string, unknown>;
|
|
};
|
|
|
|
export const createAuditLogEntry = async ({
|
|
workspaceId,
|
|
auth,
|
|
action,
|
|
entityType,
|
|
entityId = null,
|
|
entityName = null,
|
|
details = {},
|
|
}: AuditLogInput) => {
|
|
const result = await db.query<AuditLogEntryRow>(
|
|
`INSERT INTO audit_log_entries (workspace_id, user_id, actor_name, actor_email, action, entity_type, entity_id, entity_name, details)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
RETURNING id, workspace_id, user_id, actor_name, actor_email, action, entity_type, entity_id, entity_name, details, created_at`,
|
|
[
|
|
workspaceId,
|
|
auth?.user.id ?? null,
|
|
auth?.user.name ?? null,
|
|
auth?.user.email ?? null,
|
|
action,
|
|
entityType,
|
|
entityId,
|
|
entityName,
|
|
JSON.stringify(details),
|
|
],
|
|
);
|
|
|
|
return result.rows[0] ?? null;
|
|
};
|
|
|
|
export const listAuditLogEntries = async (workspaceId: number, limit = 100) => {
|
|
const result = await db.query<AuditLogEntryRow>(
|
|
`SELECT id, workspace_id, user_id, actor_name, actor_email, action, entity_type, entity_id, entity_name, details, created_at
|
|
FROM audit_log_entries
|
|
WHERE workspace_id = $1
|
|
ORDER BY created_at DESC
|
|
LIMIT $2`,
|
|
[workspaceId, limit],
|
|
);
|
|
|
|
return result.rows;
|
|
};
|
|
|
|
export const listFlockNotes = async (workspaceId: number) => {
|
|
const result = await db.query<FlockNoteRow>(
|
|
`SELECT flock_notes.id,
|
|
flock_notes.workspace_id,
|
|
flock_notes.bird_id,
|
|
birds.name AS bird_name,
|
|
flock_notes.title,
|
|
flock_notes.body,
|
|
flock_notes.created_by_user_id,
|
|
users.name AS created_by_name,
|
|
flock_notes.created_at,
|
|
flock_notes.updated_at
|
|
FROM flock_notes
|
|
LEFT JOIN birds ON birds.id = flock_notes.bird_id
|
|
LEFT JOIN users ON users.id = flock_notes.created_by_user_id
|
|
WHERE flock_notes.workspace_id = $1
|
|
ORDER BY flock_notes.updated_at DESC`,
|
|
[workspaceId],
|
|
);
|
|
|
|
return result.rows;
|
|
};
|
|
|
|
export const createFlockNote = async ({
|
|
workspaceId,
|
|
birdId,
|
|
body,
|
|
createdByUserId,
|
|
}: {
|
|
workspaceId: number;
|
|
birdId: string | null;
|
|
body: string;
|
|
createdByUserId: string | null;
|
|
}) => {
|
|
const title = body.split(/\s+/).join(' ').slice(0, 160) || 'Note';
|
|
const result = await db.query<FlockNoteRow>(
|
|
`WITH inserted_note AS (
|
|
INSERT INTO flock_notes (workspace_id, bird_id, title, body, created_by_user_id)
|
|
SELECT $1, $2, $3, $4, $5
|
|
WHERE $2::uuid IS NULL
|
|
OR EXISTS (
|
|
SELECT 1
|
|
FROM birds
|
|
WHERE birds.id = $2
|
|
AND birds.workspace_id = $1
|
|
)
|
|
RETURNING id, workspace_id, bird_id, title, body, created_by_user_id, created_at, updated_at
|
|
)
|
|
SELECT inserted_note.id,
|
|
inserted_note.workspace_id,
|
|
inserted_note.bird_id,
|
|
birds.name AS bird_name,
|
|
inserted_note.title,
|
|
inserted_note.body,
|
|
inserted_note.created_by_user_id,
|
|
users.name AS created_by_name,
|
|
inserted_note.created_at,
|
|
inserted_note.updated_at
|
|
FROM inserted_note
|
|
LEFT JOIN birds ON birds.id = inserted_note.bird_id
|
|
LEFT JOIN users ON users.id = inserted_note.created_by_user_id`,
|
|
[workspaceId, birdId, title, body, createdByUserId],
|
|
);
|
|
|
|
return result.rows[0] ?? null;
|
|
};
|
|
|
|
export const deleteFlockNote = async (noteId: string, workspaceId: number) => {
|
|
const result = await db.query<{ id: string; title: string }>(
|
|
`DELETE FROM flock_notes
|
|
WHERE id = $1
|
|
AND workspace_id = $2
|
|
RETURNING id, title`,
|
|
[noteId, workspaceId],
|
|
);
|
|
|
|
return result.rows[0] ?? null;
|
|
};
|