- activity_log table (sqlite + pg, structurally identical; schema-parity covers it). Plain-text columns, no FKs — an append-only trail that must outlive the projects/users it references, so target_label snapshots a human-readable name at write time. - ActivityRepository: record() (fire-and-forget, never breaks the caller) + list() (org-scoped, actor/action filters, keyset pagination) + listActors(). - Recording wired into the mutations worth tracking: project create/archive/restore/domain, audit start, team user create/remove/ password-reset, invitation sent. - getActivityLog / getActivityActors server functions (owner/admin gated) + Settings → Activity tab (ActivityLogView: filter by user & action, load more). - Migration: drizzle/0045_*, drizzle-pg/0023_*. The pipeline does not run migrations — see docs/SELF_HOSTING_TEAM_MODE.md step 5 for the one-time `drizzle-kit migrate` on the server. Writes fail silently until the table exists. tsc / oxlint / knip clean. New ActivityRepository.test.ts (4) + schema-parity picks up the new table; suite otherwise unchanged (pre-existing samSkills CRLF failure only). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
16 lines
619 B
SQL
16 lines
619 B
SQL
CREATE TABLE `activity_log` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`organization_id` text NOT NULL,
|
|
`actor_user_id` text NOT NULL,
|
|
`actor_email` text NOT NULL,
|
|
`action` text NOT NULL,
|
|
`target_type` text,
|
|
`target_id` text,
|
|
`target_label` text,
|
|
`metadata` text,
|
|
`ip_address` text,
|
|
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE INDEX `activity_log_org_created_at_idx` ON `activity_log` (`organization_id`,`created_at`);--> statement-breakpoint
|
|
CREATE INDEX `activity_log_org_actor_idx` ON `activity_log` (`organization_id`,`actor_user_id`); |