Skip to content

Repository files navigation

CodeWithPurpose Task Tracker

A task tracker for CodeWithPurpose (CWP) that can be used from a website or from Discord slash commands, with both surfaces sharing one permission-enforcing core.

Overview

  • Shared core (lib/core/*): domain types, Supabase data access for members/tasks, and the permission rules (lib/core/permissions.ts). Both the website's server actions (app/actions/tasks.ts) and the Discord interaction handler (lib/discord/handler.ts) call into this same core, so a rule only has to be written and tested once and it is enforced identically from both surfaces.

  • Website (Next.js App Router): sign in with Discord via Supabase Auth, view/create/assign/complete tasks, browse the org chart (app/page.tsx, app/my-tasks/page.tsx, app/org/page.tsx, app/tasks/[id]/page.tsx).

  • Discord bot: slash commands (/task-create, /task-list, /mytasks, /task-assign, /task-status, /task-complete, /task-remove) handled by a single webhook route (app/api/discord/interactions/route.ts) that verifies the Ed25519 request signature and dispatches into the same core.

  • CWP hierarchy — every member has a level (1–4) and, except Co-founders, a department:

    Level Role Scope
    1 Co-founder org-wide (department: null)
    2 Director one department
    3 Lead one department
    4 Team Member one department

    Lower level number = higher rank (outranks(actor, other) is actor.level < other.level).

  • Delete is gated by hierarchy (canDelete in lib/core/permissions.ts):

    • A Co-founder (level 1) can delete any task.
    • A Director (level 2) can delete a task only if every one of its assignees is in the Director's own department.
    • Leads and Team Members can never delete, regardless of department or whether they created/are assigned to the task.

    This is unit-tested exhaustively in lib/core/permissions.test.ts and lib/core/tasks.test.ts, and the same computed canDelete boolean is what app/tasks/[id]/page.tsx passes to components/task-actions.tsx, which is the only place the Delete button is rendered — so the E2E test in tests/e2e/permissions.spec.ts exercises it end-to-end through the UI rather than just the function.

Prerequisites

  • Node.js 20+
  • A Supabase project (this project targets a hosted Supabase project — there is no local Supabase stack; schema and seed data are applied with psql against the hosted database).
  • A Discord application (for OAuth login and the slash-command bot).
  • psql (PostgreSQL client) available on your PATH, to apply migrations.

1. Install

npm install

2. Configure environment variables

Copy .env.example to .env.local and fill in the values below. .env.local is git-ignored — never commit it.

cp .env.example .env.local
Variable Where to get it
NEXT_PUBLIC_SUPABASE_URL Supabase project → Settings → API → Project URL (e.g. https://YOURREF.supabase.co)
NEXT_PUBLIC_SUPABASE_ANON_KEY Supabase project → Settings → API → anon public key
SUPABASE_SERVICE_ROLE_KEY Supabase project → Settings → API → service_role key (server-only, never exposed to the browser)
SUPABASE_DB_URL Supabase project → Settings → Database → Connection string (URI, direct connection). Used for psql migrations/seed and by the integration tests in lib/core/members.test.ts / lib/core/tasks.test.ts
DISCORD_APP_ID Discord Developer Portal → your application → General Information → Application ID
DISCORD_PUBLIC_KEY Discord Developer Portal → your application → General Information → Public Key
DISCORD_BOT_TOKEN Discord Developer Portal → your application → Bot → Token

3. Apply the schema and seed data (hosted Supabase, via psql)

This project does not use supabase db reset / the local Supabase CLI stack. Instead, the migration and seed SQL files are applied directly to the hosted project with psql:

psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 -f supabase/migrations/0001_init.sql
psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 -f supabase/seed.sql

supabase/seed.sql inserts the 14 CWP members with placeholder Discord ids (REPLACE_shreyan, REPLACE_dir_ops, REPLACE_lead1, …). Before go-live, replace each placeholder with the member's real Discord user id (right-click their name in Discord with Developer Mode enabled → "Copy User ID"), e.g.:

update members set discord_id = '123456789012345678' where display_name = 'Shreyan Mitra';

Repeat for every seeded member. Login and Discord-command lookups both key off discord_id, so a member won't resolve to anything until this is done.

4. Enable Discord OAuth in Supabase Auth

In the Supabase dashboard: Authentication → Providers → Discord.

  • Enable the provider.
  • Set the Client ID / Client Secret from your Discord application (Developer Portal → OAuth2 → General).
  • In Discord Developer Portal → OAuth2 → General, add a redirect URL of <your Supabase project URL>/auth/v1/callback (Supabase shows the exact value to paste on the same provider settings page).
  • The app's own callback route is app/auth/callback/route.ts, reachable at <site>/auth/callback — this is where Supabase redirects back to after the Discord OAuth handshake completes; no further configuration is needed for it beyond what Supabase's provider setup already handles.

5. Configure the Discord application (bot + slash commands)

  1. In the Discord Developer Portal, copy the Application ID, Public Key, and Bot Token into .env.local (see the table above).

  2. Under General Information, set the Interactions Endpoint URL to:

    <site>/api/discord/interactions
    

    e.g. https://yourapp.vercel.app/api/discord/interactions in production, or a tunneled URL (ngrok/Cloudflare Tunnel) pointed at http://localhost:3000/api/discord/interactions for local testing. Discord will send a PING to this URL and expects an immediate signed PONG; app/api/discord/interactions/route.ts verifies the request signature with lib/discord/verify.ts before dispatching, so this only succeeds once DISCORD_PUBLIC_KEY is set correctly.

  3. Under Bot, ensure the bot has been added to your Discord server (OAuth2 → URL Generator → scope bot and applications.commands, then visit the generated URL).

  4. Register the slash commands defined in lib/discord/commands.ts:

    set -a && source .env.local && set +a && npm run register-commands

    This does a bulk overwrite PUT of all commands for your application, so it's safe to re-run whenever lib/discord/commands.ts changes.

6. Run it

npm run dev

Visit http://localhost:3000, log in with Discord, and the app redirects to /login for unauthenticated visitors and back to / on success.

Testing

Unit + integration tests

npm test

WARNING: npm test runs integration suites that write to the Supabase project configured in .env.local (they create real rows, then clean up only the rows they created). Point .env.local at a dedicated dev/test Supabase project — never one holding real member or task data you care about.

This runs Vitest against everything in lib/**/*.test.ts. vitest.config.ts loads vitest.setup.ts, which reads .env.local via dotenv before any test file is collected — so npm test works out of the box with no manual source .env.local step, and no test is silently skipped just because the shell didn't have the environment sourced.

  • lib/core/permissions.test.ts, lib/discord/verify.test.ts, lib/discord/handler.test.ts are pure unit suites (no network).
  • lib/core/members.test.ts and lib/core/tasks.test.ts are integration suites: they run describe.skipIf(!process.env.SUPABASE_SERVICE_ROLE_KEY) and, when the env is present, hit the real hosted Supabase database configured by .env.local — so they require steps 2–3 above (schema + seed applied) to pass.

With a fully configured .env.local, all suites run and pass — nothing is skipped.

End-to-end tests (Playwright)

npm run e2e

playwright.config.ts runs tests in tests/e2e/ against http://localhost:3000, starting npm run dev automatically if nothing is already listening there (reuseExistingServer: true).

tests/e2e/permissions.spec.ts exercises the same delete-gating rule as the unit tests, but through the real UI: it opens a seeded task's detail page as a logged-in member and asserts whether the Delete button (components/task-actions.tsx) is present. Because this needs a live Supabase Auth session (obtained through the real Discord OAuth flow) and a seeded task id, and neither is available in a fresh checkout or CI, the test gracefully skips unless you provide:

Env var Meaning
E2E_TASK_ID id of a seeded task assigned only to members outside a test Lead's department
E2E_SUPABASE_SESSION JSON {"name": "...", "value": "..."} for the Supabase auth cookie of a seeded Lead session
E2E_COFOUNDER_SESSION (optional) same shape, for a seeded Co-founder session — when set, an additional test asserts the Delete button is present

Without these, npm run e2e reports the suite as skipped, not failed — this is expected in a fresh environment.

How to obtain a session cookie value: run npm run dev, open http://localhost:3000/login in a browser, log in as the Discord account mapped to the member you want to test as (see step 3 for wiring real discord_ids), then open DevTools → Application/Storage → Cookies for localhost:3000. Supabase's SSR client stores the session in a cookie named sb-<project-ref>-auth-token (it may be chunked into sb-<project-ref>-auth-token.0, .1, … for large sessions — if so, use the .0 chunk, or reduce cookie size by trimming unused OAuth scopes). Copy that cookie's name and value into the JSON shape above, e.g.:

export E2E_TASK_ID=00000000-0000-0000-0000-000000000000
export E2E_SUPABASE_SESSION='{"name":"sb-abcdefgh-auth-token","value":"base64-..."}'
npm run e2e

Type checking

npx tsc --noEmit

Discord commands

All commands are defined in lib/discord/commands.ts, registered via npm run register-commands, and dispatched by lib/discord/handler.ts into the same lib/core/tasks.ts functions the website's server actions use — so the permission rules below apply identically on Discord and on the website.

Command What it does Permission rule enforced
/task-create Create a task, optionally with an assignee Anyone can create (canCreate); the actor is recorded as createdBy
/task-list List tasks, optionally filtered by status/member Anyone can view (canView)
/mytasks List tasks assigned to the caller Anyone can view their own tasks
/task-assign Assign a member to a task canAssign: actor can assign anyone at or below their own level; a Team Member (level 4) can only assign themselves
/task-status Set a task's status canChangeStatus: the task's creator, an assignee, or someone who outranks every current assignee
/task-complete Shortcut for /task-status status:complete Same as /task-status
/task-remove Delete a task canDelete: Co-founder (any task), or Director whose department covers every assignee — Leads and Team Members never

Any permission violation raises PermissionError in lib/core/tasks.ts, which the handler turns into an ephemeral ⛔ <message> reply on Discord (lib/discord/handler.ts) and a { ok: false, error } result on the website (app/actions/tasks.ts), rather than a generic 500. Discord requests with an invalid signature are rejected with HTTP 401 before any command logic runs (app/api/discord/interactions/route.ts).

Website vs. Discord surface differences

  • Multiple assignees on create: the website's task-create form (components/task-form.tsx) supports selecting several assignees at once. The Discord /task-create command takes a single assignee option — to add more assignees afterward, use /task-assign per member, or recreate the task from the website.
  • Post-creation assignment: adding/changing an assignee after a task exists is currently only exposed via the Discord /task-assign command. A dedicated "assign" control on the website is a planned follow-up.

Project layout

lib/core/        domain types, Supabase clients, member/task data access, permissions
lib/discord/     Discord signature verification, command defs, interaction handler, formatting
app/             Next.js App Router pages, server actions, auth callback, Discord webhook route
components/      client components (task form, task card, task actions, status badge)
supabase/        migrations/0001_init.sql (schema + RLS), seed.sql (14 CWP members)
scripts/         register-commands.ts (bulk-registers Discord slash commands)
tests/e2e/       Playwright E2E specs

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages