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

@@ -30,16 +30,17 @@ func (a *App) GetDashboardRefresh(w http.ResponseWriter, r *http.Request) {
}
func (a *App) dashboardData(r *http.Request) (templates.DashboardData, error) {
stats, err := a.Store.GetDashboardStats(r.Context())
uid := a.userID(r)
stats, err := a.Store.GetDashboardStatsForUser(r.Context(), uid)
if err != nil {
return templates.DashboardData{}, err
}
results, err := a.Store.ListResults(r.Context(), db.ResultsQuery{Limit: 8, ExcludeEnded: true})
results, err := a.Store.ListResults(r.Context(), db.ResultsQuery{OwnerID: uid, Limit: 8, ExcludeEnded: true})
if err != nil {
return templates.DashboardData{}, err
}
itemNames := map[int64]string{}
all, _ := a.Store.ListItems(r.Context())
all, _ := a.Store.ListItemsForUser(r.Context(), uid)
for _, it := range all {
itemNames[it.ID] = it.Name
}
@@ -79,7 +80,7 @@ func (a *App) dashboardData(r *http.Request) (templates.DashboardData, error) {
}
func alertsRecent(a *App, r *http.Request, itemNames map[int64]string) ([]templates.AlertRow, error) {
results, err := a.Store.ListResults(r.Context(), db.ResultsQuery{OnlyAlerted: true, Limit: 5})
results, err := a.Store.ListResults(r.Context(), db.ResultsQuery{OwnerID: a.userID(r), OnlyAlerted: true, Limit: 5})
if err != nil {
return nil, err
}