hoangsonww / hoangsonww/ToDo-App-NextJS-Fullstack

Feature: Shared Lists & Real-Time Collaboration (invites, roles, presence, activity log)

Open
#2 0 comments 0 reactions 1 assignee Claimed by @hoangsonww View on GitHub
bug documentation enhancement good first issue help wanted question
Dominant language
TypeScript
Stars
27
Forks
18
PR merge metrics
No merged PRs in 30d

Description

## Summary

Introduce **shared lists** so multiple users can collaborate on the same to-do board with **role-based permissions**, **invite links**, **live presence** (who’s online/typing), and an **activity timeline**. Reuses the existing Next.js BE/FE + WebSockets foundation and fits cleanly into the current `/api/todos` surface.

## Why

* Today each user manages their own list; collaboration requires copy/paste or account sharing.
* Teams and families expect shared lists, live updates, and a simple invite flow.
* Makes excellent use of your existing WebSocket plumbing and SSR-friendly UI.

---

## User Stories

* As an owner, I can **create a shared list** and invite collaborators via **link** or **email**.
* As a collaborator, I can **accept an invite** and immediately see/add/update tasks with live updates.
* As an owner, I can set roles: **Owner**, **Editor**, **Viewer**.
* As any member, I can see **who’s online** on a list and when someone is **typing/editing** a task.
* As a member, I can view an **activity timeline** (added/edited/completed, by whom, when).
* As an owner, I can **revoke** access and **transfer ownership**.

---

## UX / IA

### New navigation

* **Lists** switcher (top-left): `My Personal`, `Work`, `Groceries`, `+ New List`
* **Share** button on the list header → opens a modal:

* Invite via email (optional)
* **Invite link** (copy to clipboard)
* Members table: name, role (dropdown), remove

### Task interactions (unchanged UX)

* Tasks remain per-list. Filters and categories still apply, scoped to the active list.
* Presence: avatars pill in the header; typing indicators inline on the task being edited.
* **Activity** side panel (right drawer): chronological feed with compact entries.

---

## Data Model (storage-agnostic)

> Keep names generic; adapt to your current SQLite/ORM layer.

```ts
// Core entities
type ListRole = 'OWNER' | 'EDITOR' | 'VIEWER';

interface List {
id: string; // uuid
name: string;
ownerId: string; // user id
createdAt: string;
updatedAt: string;
inviteCode?: string; // short slug for link invites (rotatable)
}

interface ListMember {
id: string; // uuid
listId: string;
userId: string;
role: ListRole;
createdAt: string;
}

interface Todo {
id: string;
listId: string; // <-- add this
title: string;
description?: string;
completed: boolean;
category?: string;
dueDate?: string;
createdAt: string;
updatedAt: string;
updatedBy?: string; // user id for activity attribution
}

interface Activity {
id: string;
listId: string;
actorId: string;
type:
| 'TASK_CREATED'
| 'TASK_UPDATED'
| 'TASK_COMPLETED'
| 'TASK_DELETED'
| 'MEMBER_ADDED'
| 'MEMBER_REMOVED'
| 'ROLE_CHANGED'
| 'LIST_RENAMED';
payload: Record; // e.g., {taskId, fieldsChanged}
createdAt: string;
}
```

**Indexes suggested**

* `ListMember(listId, userId)` unique
* `Todo(listId)`; `Activity(listId, createdAt desc)`

---

## API (Next.js /api)

> Extend current handlers; keep auth middleware as is.

```
# Lists
POST /api/lists { name } → List
GET /api/lists → [List] (user is member)
GET /api/lists/:id → List (+ members if requester is member)
PATCH /api/lists/:id { name? } (OWNER)
DELETE /api/lists/:id (OWNER)

# Memberships
POST /api/lists/:id/members { userEmail, role } (OWNER)
PATCH /api/lists/:id/members/:uid { role } (OWNER)
DELETE /api/lists/:id/members/:uid (OWNER)

# Invite links
POST /api/lists/:id/invite/rotate → { inviteUrl } (OWNER)
GET /api/invite/:code → accept join (auth’d), adds as EDITOR by default

# Activity
GET /api/lists/:id/activity?after= → Activity[]

# Todos (add listId to payloads & queries)
GET /api/todos?listId=:id
POST /api/todos { listId, title, ... }
PUT /api/todos { id, listId, ... }
PATCH /api/todos { id, listId, completed }
DELETE /api/todos { id, listId }
```

**OpenAPI**

* Update `openapi.yaml` with `List`, `ListMember`, `Activity` schemas and `listId` on Todo routes.
* Document 403 responses for permission failures.

---

## WebSockets

Use your existing WS channel; namespace by list:

**Channels**

* `list:{listId}`

**Events**

* Server → clients:

* `presence:update` `{ membersOnline: UserPresence[] }`
* `todo:created|updated|deleted` `{ todo }`
* `todo:reordered` `{ ids: string[] }` (if reordering is supported)
* `activity:append` `{ activity }`
* Client → server:

* `presence:ping` `{ listId }`
* `typing:start|stop` `{ listId, todoId }`

Presence can be a lightweight TTL map keyed by socket id + user id.

---

## Permissions

* **OWNER**: everything, including delete list, rotate invite, manage roles.
* **EDITOR**: CRUD todos, view members, cannot manage roles or delete the list.
* **VIEWER**: read-only; cannot modify todos or members.
* All endpoints: assert membership + role before action.
* WebSocket: authorize on subscribe; ignore unauthorized events.

---

## Activity Log

Append on:

* task create/update/complete/delete
* member add/remove/role change
* list rename

Keep entries compact; render relative timestamps in UI. Paginate `GET /activity`.

---

## Email (optional)

If SMTP is configured, support email invites in addition to link invites.

* Env: `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `MAIL_FROM`
* Fallback: invite link copy-to-clipboard only.

---

## Acceptance Criteria

* Create a list; invite a second account; both can view the same todos.
* Moving/adding/completing a task by user A updates **instantly** for user B over WebSockets.
* Role enforcement works (viewer cannot edit; editor cannot manage roles).
* Invite link join works; rotating the link invalidates the old one.
* Activity panel shows accurate entries with actor, action, and time.
* Presence shows online members; typing indicator appears while editing a task.

---

## Security & Edge Cases

* Invite codes are **unguessable** (e.g., 22-char base62). Rotation invalidates previous code.
* Prevent leaking list existence: 404 for non-members even if the id exists.
* On member removal, immediately disconnect their WS subscription for that list.
* Rate-limit invite acceptance to prevent abuse.
* Timezone awareness for timestamps; use ISO in API, format client-side.

---

## Migration Plan

1. Add `lists`, `list_members`, `activity` tables and `listId` column to `todos`.
2. Backfill existing todos into a default personal list per user.
3. Ship feature behind `FEATURE_SHARED_LISTS=true` (env or config).
4. Gradual rollout: enable for maintainers → 10% → 100%.

---

## Task Breakdown

### Backend

* [ ] DB migrations (tables + indexes; `listId` on todos).
* [ ] Auth guard helpers: `requireMember(listId, role?)`.
* [ ] Lists CRUD + membership endpoints.
* [ ] Invite link endpoints + code rotation.
* [ ] Extend todos handlers to require `listId`.
* [ ] Activity writer + `/activity` endpoint.
* [ ] WS: add `list:{listId}` namespace, presence + typing events.
* [ ] Tests: permissions, invite flow, activity append.

### Frontend

* [ ] List switcher UI + create/rename/delete.
* [ ] Share modal (invite link + members/roles table).
* [ ] Presence avatars + typing badges on task rows.
* [ ] Activity drawer with virtualized list.
* [ ] WS client: subscribe/unsubscribe by active list.
* [ ] Empty states, toasts, optimistic updates.
* [ ] Update Swagger page & types.

### DevOps / Config

* [ ] Env flags: `FEATURE_SHARED_LISTS`, optional SMTP vars.
* [ ] Update `openapi.yaml` + screenshots in README.
* [ ] CI: run DB migrations step; add basic e2e for invite flow.

---

## Stretch Goals (follow-ups)

* **Public read-only links** for a list (no login required, optional password).
* **Task assignments** (member chips + “My tasks” filter).
* **Calendar view** of due dates and ICS export.
* **Comment threads** per task with @mentions and notifications.

---

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.