A strict TypeScript full-stack workspace for cloud-hosted team tracking, with a React client and a Fastify + Prisma server. Each registered account owns an isolated workspace with its own projects, team members, tasks, dashboard, and calendar.
client: React, Vite, React Router, TanStack Query, React Hook Form, Mantine, FullCalendarserver: Fastify, Prisma Client, PostgreSQL, Zod, cookie-based sessions, SMTP email verificationpackages/shared: shared DTOs and validation schemas used by both apps
- Multi-account registration with
email + password - Email verification through 6-digit OTP sent over SMTP
- Forgot-password recovery through 6-digit OTP sent over email
- Cookie-based authenticated sessions
- Per-account isolation for projects, members, tasks, dashboard, and calendar data
- Team members remain manager-owned records only; they do not sign in
-
Install dependencies:
npm install
-
Start PostgreSQL and create a database that matches
DATABASE_URL. -
Copy the server env file and fill in the values:
cp server/.env.example server/.env
-
Generate Prisma client and apply migrations:
npm run setup
-
Start both apps:
npm run dev
-
Open
http://localhost:5173.
npm run setup: generate Prisma client and apply Prisma migrationsnpm run dev: run server and client togethernpm run build: build shared package, server, and clientnpm run test: run server and client testsnpm run typecheck: strict TypeScript checks across the workspacenpm run deploy:production: run the Ubuntu production bootstrap/deploy script
Server-only commands:
npm run prisma:generate --workspace servernpm run prisma:migrate --workspace servernpm run test --workspace server
Development values live in [server/.env.example](/Users/eevangelinos/Documents/Team management/server/.env.example).
Production values live in [server/.env.production.example](/Users/eevangelinos/Documents/Team management/server/.env.production.example).
Required production variables:
DATABASE_URLSESSION_SECRETCLIENT_ORIGINAPP_BASE_URLEMAIL_PROVIDEREMAIL_FROM
If you host the app under a subpath (for example https://softaware-tools.gr/team-management/), set:
APP_BASE_URLto the full subpath URL (for examplehttps://softaware-tools.gr/team-management/)
The production build and Fastify routes use APP_BASE_URL to serve frontend assets and API endpoints from the same base path.
For Gmail with Nodemailer:
EMAIL_PROVIDER=gmailGMAIL_USER=softaware.studios@gmail.comGMAIL_APP_PASSWORD=...EMAIL_FROM="Softaware Team Management <softaware.studios@gmail.com>"EMAIL_REPLY_TO=softaware.studios@gmail.com
For generic SMTP:
EMAIL_PROVIDER=smtpSMTP_HOSTSMTP_PORTSMTP_USERSMTP_PASSSMTP_SECUREEMAIL_FROM
The production bootstrap script is [deploy/production/deploy.sh](/Users/eevangelinos/Documents/Team management/deploy/production/deploy.sh). It targets Ubuntu 22.04/24.04 and does the following:
- installs system packages
- installs Node.js if missing
- installs PostgreSQL and PM2
- creates or updates the local PostgreSQL role/database when
DATABASE_URLpoints to localhost - installs npm dependencies
- runs Prisma generate and
migrate deploy - builds client and server
- starts or reloads the server with PM2 using [deploy/production/ecosystem.config.cjs](/Users/eevangelinos/Documents/Team management/deploy/production/ecosystem.config.cjs)
- serves the built React app from the same Fastify process when
client/distexists - leaves reverse proxy setup to your existing nginx / proxy configuration
Typical production flow:
-
Clone the repo on the server.
-
Copy
server/.env.production.exampletoserver/.env.productionand fill in real values. -
Run:
bash deploy/production/deploy.sh
After the script finishes, point your reverse proxy to the PM2-managed app separately. The Fastify server runs from server/dist/index.js using the PORT defined in server/.env.production and listens on 127.0.0.1 unless HOST is set, serves /api/* itself, and also serves the built frontend from client/dist for all non-API routes.
If you use Gmail, create a Google App Password for softaware.studios@gmail.com and place it in GMAIL_APP_PASSWORD. The app uses Nodemailer with Gmail transport when EMAIL_PROVIDER=gmail.
Pushing to main runs .github/workflows/deploy-production.yml. On the GitHub runner it typechecks, runs the tests, builds shared/server/client, and packages server-only production node_modules (with the Prisma client generated) plus server/dist and client/dist into a zstd tarball. The droplet just resets the checkout to the pushed commit and runs deploy.sh with DEPLOY_PREBUILT_ARTIFACT: it unpacks the tarball at idle CPU/IO priority, swaps it in, runs prisma migrate deploy, and reloads PM2. It never runs npm install or Vite on the server.
Repository settings it needs:
- Secrets:
DROPLET_HOST,DROPLET_USER,DROPLET_SSH_KEY,DROPLET_HOST_KEY(known_hosts lines),DEPLOY_PATH, and optionallyDROPLET_PORT. - Variable:
APP_BASE_URL, which must matchAPP_BASE_URLin the droplet'sserver/.env.production. Vite builds the base path into the bundle, anddeploy.shrejects an artifact built for a different URL.
Running deploy.sh by hand without DEPLOY_PREBUILT_ARTIFACT still does the full provision and build on the server.
- Server tests start an isolated PostgreSQL instance under
/tmp/team-management-pg-testand run Prisma migrations against it. - OTP tests use a fixed override code only in the test environment so registration and verification flows can be asserted deterministically.
- The same OTP pipeline is reused for password reset, with purpose-based token separation inside the existing auth table.
POST /api/auth/request-password-reset: accepts{ email }and always returns a neutral response so the reset request does not reveal whether an account exists.POST /api/auth/reset-password: accepts{ email, otp, password }, validates the reset OTP, updates the stored password, and invalidates existing sessions.- Password reset is available only for verified accounts; unverified and unknown emails still receive the same neutral request response.