Activate is a Next.js web application that tracks Slack users' presence (active / away) over time and turns it into per-user activity reports: total active time, number of activity sessions, and a timeline of status changes for a given day.
It supports multiple Slack workspaces at once. Each workspace installs the Slack app via OAuth; the resulting bot token is stored in the database and used to poll that workspace's users.
- A cron-triggered endpoint (
GET/POST /api/cron/check-presence) reads all active workspaces from the database, calls the Slack Web API (users.list,users.getPresence) for each workspace's users, and diffs the new presence against the last known value. - Transitions between
activeandawayopen or close anActivitySessionrow, which is what lets the app compute total active time and session counts per day. The start/continue/end decision is a pure function (lib/presenceSession.ts), unit tested independently of Slack or the database. - The current status of every user is kept in a
UserStatusrow and is what the dashboard reads. - The dashboard itself never talks to the database directly — client components (
app/page.tsx,app/users/[userId]/page.tsx,stores/workspaceStore.ts) fetch from small JSON API routes (/api/workspaces,/api/user-statuses,/api/activity,/api/activity/heatmap), which run the Prisma queries server-side. - A dashboard (
app/page.tsxand related components) renders this data as tables, cards, and an activity heatmap, with a workspace switcher backed by a Zustand store. - A Slack slash command handler (
POST /api/slack/webhook) verifies the Slack request signature; command handling beyond that is a stub (no commands are implemented right now). - Sign-in to the dashboard itself uses NextAuth.js with a simple credentials provider (a single configured username/password), not Slack OAuth — Slack OAuth is only used to install the bot into a workspace.
- Next.js 15 (App Router) + React 19 + TypeScript
- Prisma + Postgres (Neon) as the data store — see Database below
- NextAuth.js (
next-authv5 beta) for dashboard authentication @slack/web-apifor Slack API calls- Tailwind CSS + shadcn/ui (Radix primitives) + Recharts/Tremor for the UI
- Zustand for client-side workspace state
- Vitest for unit tests
app/
api/auth/[...nextauth]/ NextAuth route handlers
api/auth/slack/callback/ Slack OAuth callback — exchanges code, upserts the workspace + bot token
api/cron/check-presence/ Polls Slack presence for all active workspaces, writes sessions/status
api/slack/webhook/ Slack event/slash-command endpoint (signature-verified)
api/test-presence/ Manual test endpoint for a single hardcoded Slack user ID
api/workspaces/ List active workspaces (used by the workspace switcher)
api/user-statuses/ List/get user statuses for a workspace
api/activity/ Per-day activity (work sessions, total active time)
api/activity/heatmap/ A full year of daily activity totals in one query
users/[userId]/ Per-user activity detail page
page.tsx Main dashboard
components/ Dashboard UI (sidebar, tables, cards, charts) and shadcn/ui primitives
prisma/schema.prisma Database schema (Workspace, UserStatus, ActivitySession)
lib/prisma.ts Prisma client singleton (server-only)
lib/activityService.ts Session/activity calculations that hit the database (server-only)
lib/activityUtils.ts Pure formatting helpers + shared types, safe to import from client components
lib/presenceSession.ts Pure active/away session state machine used by the cron job
stores/workspaceStore.ts Zustand store for the selected workspace (fetches /api/workspaces)
auth.ts / auth.config.ts NextAuth configuration (credentials provider)
middleware.ts Route protection, delegates to auth.ts
app.slack.manifest.json Slack app manifest (bot scopes: users:read)
Postgres, hosted on Neon, used for both local development and production. Prisma's generated client has no native query engine (engineType = "client" in prisma/schema.prisma) — queries run through @prisma/adapter-pg and a pg connection pool (lib/prisma.ts) instead, which avoids the native-binary bundling issues that native Prisma engines hit on serverless platforms like Vercel.
pnpm prisma migrate dev # apply schema changes
pnpm prisma studio # browse/edit the database in a GUI
pnpm db:seed # populate demo data (a workspace + two users with sample activity)These are the environment variables actually read by the code:
# Postgres connection string (Neon), used by lib/prisma.ts and the Prisma CLI
DATABASE_URL=
# Base URL used to build OAuth redirect/callback URLs
NEXT_PUBLIC_BASE_URL=http://localhost:3000
# Dashboard login (NextAuth credentials provider)
AUTH_USERNAME=
AUTH_PASSWORD=
AUTH_SECRET= # openssl rand -hex 32
# Slack app OAuth (installing the bot into a workspace)
SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET=
# Slack request verification (for /api/slack/webhook)
SLACK_SIGNING_SECRET=
# Slack bot token, only used by the manual /api/test-presence endpoint
# (the cron job instead uses per-workspace bot tokens stored in the database)
SLACK_BOT_TOKEN=
# Cron job authorization for /api/cron/check-presence (Authorization: Bearer <CRON_SECRET>)
CRON_SECRET=Note: what triggers /api/cron/check-presence on a schedule (Vercel Cron, GitHub Actions, or something else) is not part of this repository — you need to set that up yourself and send Authorization: Bearer <CRON_SECRET> with the request.
app.slack.manifest.json defines the Slack app used by this project:
- Bot scopes:
users:read - User scope:
users:read - Socket mode and org-wide install are disabled
To connect a workspace, create/configure a Slack app with these scopes, set its OAuth redirect URL to <NEXT_PUBLIC_BASE_URL>/api/auth/slack/callback, and point Slack event/slash-command requests at <NEXT_PUBLIC_BASE_URL>/api/slack/webhook.
- Install dependencies:
pnpm install
- Create a
.env.localfile with the variables listed above (DATABASE_URLneeds a real Postgres connection string, e.g. from a free Neon project). - Apply the database schema:
pnpm prisma migrate dev
- Run the dev server:
The app runs at http://localhost:3000.
pnpm dev
Other scripts: pnpm build, pnpm start, pnpm lint, pnpm test.
MIT. See LICENSE.