Last verified against code on March 10, 2026.
-
Frontend: Next.js App Router (
impactis-client/src/app/**)- Uses Better Auth for auth/session instead of Supabase JS.
- Uses NestJS REST API for business operations.
- Uses Cloudflare R2 for profile avatars, organization logos, and startup data-room uploads via signed PUT URLs from the backend.
-
Backend: NestJS (
impactis-server/)- Exposes versioned REST (
/api/v1/*). - Uses SQL executor service (
PrismaService) backed bypg. - Owns permission checks, capability-based feature gates, and business rules.
- Exposes versioned REST (
-
Database
- Local dev: standard Postgres running in Docker, using the SQL schema from
supabase/migrations/, mirrored inimpactis-server/prisma/schema.prisma. - Remote: Postgres (Supabase-hosted in legacy environments, or a managed Postgres service), with Supabase Auth now treated as legacy and Better Auth as the active auth layer.
- Local dev: standard Postgres running in Docker, using the SQL schema from
-
Cloudflare R2
- Single bucket (e.g.
impactis-files) with logical prefixes such as:profile-avatars/<userId>/...organization-logos/<orgId>/...startup-data-room-assets/<orgId>/...
- Public access via
R2_PUBLIC_BASE_URL(e.g.https://pub-<id>.r2.dev).
- Single bucket (e.g.
Current REST controllers in server code:
GET /api/v1/health/api/v1/organizations/*/api/v1/profiles/*/api/v1/workspace/*/api/v1/startups/*/api/v1/files/startups/*/api/v1/billing/*/api/v1/billing/stripe/*/api/v1/sessions/*/api/v1/admin/readiness
Notes:
- Engagement flows (
/api/v1/engagements/*) are not implemented yet; both backend controllers and any new frontend modules will be added together in a future milestone. admin/readinessis active and protected by Supabase JWT +ADMIN_USER_EMAILSallow-list.
Current (Better Auth + Turnstile + NestJS guard):
- Next.js uses Better Auth to handle signup, login, and session management backed by the same Postgres database that NestJS uses.
- Auth pages (
/auth/login,/auth/signup, password reset) render a Cloudflare Turnstile widget; the client sends the resulting token in anx-captcha-responseheader on Better Auth auth requests. - Better Auth is configured with its captcha plugin using the
TURNSTILE_SECRET_KEYand validates the Turnstile token server-side before proceeding with the auth operation. - On success, Better Auth issues signed tokens / session cookies containing user
id,email, and session metadata (user agent, IP, expiry). - Client attaches the Better Auth access token (Authorization header) to calls to the NestJS API.
BetterAuthJwtGuardvalidates Better Auth tokens against JWKS (BETTER_AUTH_JWKS_URL) and issuer (BETTER_AUTH_ISSUER) and attaches the authenticated user to the request context.
- Client repository/server action calls
apiRequest()with bearer token. - NestJS guard attaches authenticated user context.
- Controller/service validates role and executes SQL.
- API returns focused payload (
success,message, data payloads).
- Billing settings action checks Stripe redirect feature flag:
BILLING_STRIPE_REDIRECTS_ENABLED- fallback:
NEXT_PUBLIC_BILLING_STRIPE_REDIRECTS_ENABLED
- If redirects are disabled, action calls
PATCH /billing/subscriptiondirectly. - If redirects are enabled for paid plans, action requests Stripe checkout session (
/billing/stripe/checkout-session) and redirects user. - Stripe returns user to
/workspace/settings?section=settings-billing&stripe=success|cancel. - Backend webhook (
/billing/stripe/webhook) verifies signature with Stripe SDK and synchronizes subscription state intoorg_subscriptions.
- Client validates file constraints in workspace settings.
- Client calls NestJS Files module to request a signed PUT URL for a given org and document type.
- Client uploads bytes directly to Cloudflare R2 using the signed URL.
- Client persists metadata through startups APIs (
/startups/data-room/documents), which write intostartup_data_room_documents. - DB source of truth is
startup_data_room_documents(withdocument_type,storage_bucket,storage_object_path, and canonicalfile_urlpointing at R2).
Startup model boundary:
startup_profiles: structured profile metadata + narrative summaries.startup_data_room_documents: actual uploaded evidence and diligence files.
Compatibility note:
- API responses (
/startups/profile, workspace snapshot) still expose pitch/financial/legal file pointer fields for compatibility, but those values are now derived fromstartup_data_room_documents, not stored instartup_profiles.
- Client server actions call NestJS Files module for a signed PUT URL (profile avatar or organization logo).
- Client uploads bytes directly to R2 with the signed URL.
- NestJS returns the final public URL, which the client persists into
profiles.avatar_urlororganizations.logo_url.
- Canonical startup readiness view is
public.startup_readiness(non-versioned). - Admin aggregate readiness view is
public.organization_readiness_summary_v1. - Legacy table
startup_readiness_profileshas been removed. - Legacy document-pointer columns were dropped from
startup_profilesin phase-5 migrations. startup_data_room_documentsnow supports canonical storage references:storage_bucketstorage_object_path
At minimum:
NEXT_PUBLIC_SITE_URLNEXT_PUBLIC_IMPACTIS_API_URL(for examplehttp://localhost:4000/api/v1)- Better Auth public envs required by your
authconfiguration (seeimpactis-client/src/lib/auth.ts).
Optional:
NEXT_PUBLIC_TURNSTILE_SITE_KEY(Cloudflare Turnstile site key used by auth pages).NEXT_PUBLIC_BILLING_STRIPE_REDIRECTS_ENABLED
Core:
PORTWEB_ORIGINDATABASE_URLBETTER_AUTH_JWKS_URLBETTER_AUTH_ISSUER
Optional features:
- Stripe env vars (
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET) BILLING_STRIPE_REDIRECTS_ENABLEDADMIN_USER_EMAILS(for/admin/readiness)- Upstash cache env vars (
UPSTASH_*,CACHE_*) – wired but effectively disabled by code (cache is a no-op).
R2_ACCOUNT_IDR2_ACCESS_KEY_IDR2_SECRET_ACCESS_KEYR2_BUCKET_NAMER2_PUBLIC_BASE_URL(e.g.https://pub-<id>.r2.dev)
-
Implemented and active
- Workspace, startups, billing, sessions, organizations/profiles, health APIs.
- Stripe hosted checkout + billing portal + webhook synchronization.
- Startup readiness computed from
startup_profiles+startup_data_room_documents. - Startup data-room document lifecycle (upsert/delete/list) with plan-gated limits.
-
Implemented but not primary path
- FilesModule R2 signed URL endpoints exist, but workspace upload actions currently use Supabase Storage direct upload.
-
Known gaps
- Engagement / intro workflows are not wired up yet; there is no
/engagementscontroller in the backend and previous frontend hooks have been removed. - Capability-based feature gates are designed and documented in
docs/FEATURE_GATES_IMPLEMENTATION_PLAN.mdbut are not yet backed by dedicated DB tables or NestJS guards; current checks are membership/role + readiness + implicit plan limits.
- Engagement / intro workflows are not wired up yet; there is no