mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-25 03:42:06 +00:00
0a09c1dca7
* feat(store): add users.last_write_at column + write-path hook (TASK-1543)
Engagement metrics need a "last write" signal distinct from last_active_at
(which is bumped on any authenticated request, so it includes reads). Adds:
- Migration 060: users.last_write_at + index. Backfills from activities
table (the canonical record of who-did-what) using the action set
that handlers_items.go / handlers_comments.go actually emit:
created/updated/archived/restored/moved/commented.
- Store.TouchUserWrite(ctx, userID): mirrors TouchUserActivity. Same
5-minute throttle to avoid write-amplification, silent no-op on
empty userID so callers don't have to guard.
- Hook in logActivityWithMetaReturningID (handlers_documents.go) — every
item-write action funnels through this single helper, so one TouchUserWrite
call covers item create/update/archive/restore/move and comment authoring.
- Explicit hook in handlers_attachments.go after CreateAttachment, since
uploads don't go through logActivity.
Test: TestTouchUserWrite covers empty-userID no-op, first-write set,
in-throttle suppression, and out-of-throttle advance.
Architecture note: items.last_modified_by / comments.created_by are
attribution strings ("user"/"agent"/"cli"), not user IDs. The user
identity lives in activities.user_id, populated at the handler layer
where currentUser is in scope. That's why the hook lives in handlers,
not the store layer — and why the backfill reads from activities.
Part of PLAN-1542 (admin user management enhancements).
* fix: address Codex review on TASK-1543
- Add Postgres migration 039 (pgmigrations counterpart to 060). Same
ALTER + index + activities-backfill, using TEXT to match the existing
last_active_at / disabled_at column types in pgmigrations/020-021.
Without this, Postgres deployments silently no-op TouchUserWrite
because the column doesn't exist (and the call's UPDATE error is
swallowed by design).
- Hook TouchUserWrite in handleCreateCommentReply. The reply handler
doesn't go through logActivity (no "commented" activity emitted for
replies — verified by grep), so the activity-helper hook misses it.
Explicit call after a successful CreateComment.
30 lines
1.3 KiB
SQL
30 lines
1.3 KiB
SQL
-- Migration 060: per-user last-write timestamp (PLAN-1542 / TASK-1543).
|
|
--
|
|
-- Adds a dedicated `last_write_at` column on `users` so admin engagement
|
|
-- metrics can distinguish active writers from passive readers. The existing
|
|
-- `last_active_at` column is bumped on any authenticated request (throttled
|
|
-- by TouchUserActivity), so it can't tell us whether the user actually
|
|
-- created or modified anything.
|
|
--
|
|
-- The hook (TouchUserWrite + handler-layer call sites) is wired in the same
|
|
-- task; this migration is the data-layer half.
|
|
--
|
|
-- Backfill draws from the existing `activities` table — the canonical record
|
|
-- of who did what — rather than from items.last_modified_by (which holds
|
|
-- attribution strings like "user"/"agent", not user IDs). The action values
|
|
-- below match what handlers_items.go and handlers_comments.go pass into
|
|
-- logActivity / logActivityWithMeta.
|
|
|
|
ALTER TABLE users ADD COLUMN last_write_at TEXT DEFAULT NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_users_last_write_at ON users(last_write_at);
|
|
|
|
UPDATE users
|
|
SET last_write_at = (
|
|
SELECT MAX(a.created_at)
|
|
FROM activities a
|
|
WHERE a.user_id = users.id
|
|
AND a.action IN ('created', 'updated', 'archived', 'restored',
|
|
'moved', 'commented')
|
|
)
|
|
WHERE last_write_at IS NULL;
|