hoangsonww / hoangsonww/Boxed-Inventory-App
Collaborators v1 — Share Boxes with Roles, Activity History, and Alerts
- Dominant language
- TypeScript
- Stars
- 17
- Forks
- 10
- PR merge metrics
- No merged PRs in 30d
Description
### Summary
Ship first-class **collaboration** so people can share boxes with roommates/family and keep a lightweight **activity history**. Adds per-box roles (`viewer|editor|owner`), invite links, RLS-safe APIs, and optional notifications.
---
### Why
* Real households aren’t single-user.
* Prevents “who moved my charger?” with a simple audit trail.
* Lays the foundation for real-time presence later.
---
### Scope (MVP)
1. **Per-box roles**
* Respect existing `box_collaborators` (role column: `viewer|editor|owner`).
* RLS: viewers = read-only; editors = CRUD items; owners = manage collaborators & delete.
2. **Invites**
* Owner generates a time-boxed, single-use invite token for a box.
* Recipient accepts to join with `viewer` (owner can upgrade).
3. **Activity history**
* Append-only log for: create/update/delete **box**, **item**, **photo**, **status**, **location**, **collaborator** changes.
* Show human-readable feed on Box page (“Erica changed quantity of ‘Ceramic Mug’ 6 → 8”).
4. **Notifications (optional toggle)**
* Per-box “notify me” switch → in-app toast & bell feed when collaborators edit.
5. **UX**
* “Share” button → modal with role chips, copy invite link, revoke.
* Activity tab (last 50 events), filter by actor/action.
* Badges: `Viewer`, `Editor`, `Owner`.
---
### Acceptance Criteria
* [ ] Viewer cannot mutate box/items via UI **or** API (RLS enforced).
* [ ] Editor can create/edit/delete items; cannot manage collaborators.
* [ ] Owner can transfer ownership, revoke invites, remove collaborators.
* [ ] Invite links expire (default 72h) or upon first redemption.
* [ ] Activity feed renders accurate events with actor + timestamp.
* [ ] “Notify me” shows toasts on collaborator edits within that box.
---
### DB / RLS (TypeORM + SQL)
```sql
-- activity_log
CREATE TABLE activity_log (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
box_id uuid NOT NULL REFERENCES boxes(id) ON DELETE CASCADE,
actor_id uuid NOT NULL REFERENCES profiles(id) ON DELETE SET NULL,
entity_type text NOT NULL, -- 'box' | 'item' | 'photo' | 'collaborator'
entity_id uuid,
action text NOT NULL, -- 'create'|'update'|'delete'|'status_change'|'add'|'remove'
diff jsonb, -- {before:{}, after:{}} minimal snapshot
created_at timestamptz DEFAULT now()
);
CREATE INDEX ON activity_log (box_id, created_at DESC);
-- invites
CREATE TABLE box_invites (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
box_id uuid NOT NULL REFERENCES boxes(id) ON DELETE CASCADE,
created_by uuid NOT NULL REFERENCES profiles(id),
role text NOT NULL CHECK (role IN ('viewer','editor')),
token text UNIQUE NOT NULL,
expires_at timestamptz NOT NULL,
redeemed_by uuid NULL REFERENCES profiles(id),
redeemed_at timestamptz
);
CREATE INDEX ON box_invites (box_id, expires_at);
-- RLS sketch (Postgres)
-- Allow read if owner OR collaborator
-- Allow write only if owner/editor per table and action
```
> Add/adjust RLS policies on `boxes`, `items`, `box_collaborators`, `activity_log`, `box_invites` to enforce roles.
---
### API (examples)
* `POST /api/boxes/:boxId/invites` → {role, expiresAt} → {inviteUrl}
* `POST /api/invites/:token/redeem` → joins as viewer/editor
* `GET /api/boxes/:boxId/collaborators`
* `PATCH /api/boxes/:boxId/collaborators/:profileId` → {role}
* `DELETE /api/boxes/:boxId/collaborators/:profileId`
* `GET /api/boxes/:boxId/activity?limit=50`
* `POST /api/boxes/:boxId/notify/subscribe|unsubscribe`
Hook activity writes from existing item/box mutations (service layer) to keep controllers thin.
---
### Frontend
* **ShareModal**: list collaborators, role dropdown, “Copy link”, “Revoke all invites”.
* **ActivityFeed**: grouped by day, simple icons per action, diff tooltips.
* **RoleGuard**: tiny utility to gate buttons/menus by role.
* **Toasts/Bell**: per-box notifications (local storage flag; later server push).
---
### Implementation Notes
* Tokens: 32+ random bytes base64url; never expose raw IDs inside token.
* Invites: single-use default; also support manual revoke.
* Diff: only store changed keys to keep rows small.
* Performance: index `activity_log(box_id, created_at desc)` for fast feeds.
* Privacy: don’t dump large photo URLs into `diff`; store IDs only.
---
### Risks & Mitigations
* **RLS mistakes** → add unit + integration tests for each role/action.
* **Invite leakage** → short expiry, single-use, easy revoke, audit trail.
* **Noisy feed** → collapse repetitive events (e.g., multiple quantity tweaks in 5m).
---
### More Features
* Real-time presence cursors + live co-editing.
* Email/WebPush notifications.
* Per-folder (collection) sharing beyond single boxes.
Contributor guide
Assessment
This issue has not been assessed yet.