Add per-user item ownership and isolation

Items are now private to their owner. Add items.user_id (migrated in
place, existing items backfilled to the first admin) and scope every
item, results, and dashboard view to the signed-in user via owner-scoped
queries plus an ownedItem 404 guard. Admins get strict isolation too;
elevation stays limited to settings and user management.

Alert routing follows ownership: deal emails go only to the item owner
and the weekly digest is built per-recipient from their own items. The
scheduler still polls every active item; the Apify/eBay budget stays a
shared pool visible to all.

Add TestItemsArePrivatePerUser and seed owners in db tests.
This commit is contained in:
prosolis
2026-06-20 13:39:19 -07:00
parent 90a74967e0
commit feea126c5e
11 changed files with 327 additions and 72 deletions

View File

@@ -21,6 +21,13 @@ CREATE TABLE IF NOT EXISTS users (
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
-- user_id is the owning user. Items are private to their owner; the
-- scheduler still polls every active item regardless of owner. On existing
-- databases the column is added by addColumnIfMissing in db.go (without a
-- REFERENCES clause) and backfilled to the first admin, so user deletion
-- removes owned items explicitly in Store.DeleteUser rather than relying on
-- the cascade below.
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
name TEXT NOT NULL,
search_query TEXT,
url TEXT,
@@ -53,6 +60,10 @@ CREATE TABLE IF NOT EXISTS items (
);
CREATE INDEX IF NOT EXISTS idx_items_active ON items(active);
-- idx_items_user is created in db.go AFTER addColumnIfMissing adds user_id, so
-- it works on databases whose items table predates the column (this CREATE
-- INDEX would otherwise fail on them, since IF NOT EXISTS on the table is a
-- no-op and the old table lacks user_id).
CREATE TABLE IF NOT EXISTS item_marketplaces (
item_id INTEGER NOT NULL REFERENCES items(id) ON DELETE CASCADE,