Welcome to the Fall Stack event's GitHub repository. Here you'll find everything you need to contribute with your amazing code and ideas!
This is a Núcleo de Estudantes de Informática project, made by students from ISEP.
Fall Stack is a tech event that happens every year with the intention of presenting tech companies to students that are looking for an internship.
This is also a great place for networking and really getting to know the market.
The event takes place at ISEP (Instituto Superior de Engenharia do Porto). Each year's edition is tracked as a <year>-edition git tag on this repo — see CHANGELOG.md for the current edition's dates and what changed.
Next.js, TypeScript, Tailwind CSS, HeroUI, PostgreSQL/Prisma, and Supabase (Auth + Storage). See AGENTS.md's Stack table for the full, authoritative list.
All authentication, including password recovery and password updates, goes through Supabase Auth. Application tables must not store passwords or password reset tokens, and application routes must not provide separate password-change flows.
auth.users.id and public."User".id match by convention; no foreign key can
span Supabase Auth and Prisma. Always delete accounts through the admin
backoffice, which removes the Supabase Auth identity before the application
row. Never delete users directly in the Supabase Auth dashboard: that leaves an
unloginable application account.
Before deploying changes that affect account deletion, and after any manual
Auth operation, run
supabase/audit-orphaned-accounts.sql
manually in every environment's Supabase SQL editor. It is read-only and reports
orphans in both directions; investigate each result before deleting anything.
git clone https://github.com/<org>/fallstack-website.git
cd fallstack-websitepnpm installCopy:
cp .env.example .envDATABASE_URLDIRECT_URLNEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEY(service role)JWT_SECRET
Defaulted (override only if you need something other than local dev defaults):
NEXT_PUBLIC_BASE_URL(defaults tohttp://localhost:3000/api)NODE_ENV(defaults todevelopment)
Only needed to run pnpm seed:
ADMIN_EMAILADMIN_PASSWORD
See .env.example for the full list, including optional docker compose overrides and Sentry/Pino observability variables (covered below).
Production logging and error monitoring use Pino and Sentry. See docs/OBSERVABILITY.md for Sentry project creation, environment variables, privacy controls, Docker source-map uploads, alerts, verification, and troubleshooting.
The in-process upload limiter is keyed by authenticated student ID and uses a
fixed window. It does not trust proxy IP headers. Before an event, exercise the
QR and upload-ticket paths against staging with
tests/load/event-readiness.js. The harness
requires CONFIRM_NON_PRODUCTION=yes and rejects an upload load that would
exceed five tickets per minute for any supplied staging student session.
Only replace the limiter with a shared token bucket if staging results exceed the latency/error thresholds, or before scaling the app beyond one replica.
Create two storage buckets:
| Bucket | Access | Allowed MIME types | Max file size |
|---|---|---|---|
| avatars | public | image/png, image/jpeg |
5 MB |
| cvs | private | application/pdf |
10 MB |
After creating the buckets, run
supabase/storage-bucket-limits.sql
in the Supabase SQL editor for every Supabase project (including staging
and production). It fails if either bucket is missing and configures the MIME
and size restrictions without changing the bucket access policy.
Student uploads use a short-lived signed upload URL, so file bytes go directly from browser to Storage rather than through the Next.js server. Browser file signature checks are UX only; the bucket restrictions are the enforcement boundary for direct uploads.
Student media uploads are reconciled daily at 03:00 UTC. Objects are eligible
only when they are under the app-managed avatar/CV prefixes, are unreferenced by
Student.avatar/Student.cv, and are at least 48 hours old.
-
In Supabase Vault, create
storage_gc_project_urlwith the project URL andstorage_gc_service_role_keywith the service-role key. -
Run
supabase/storage-gc.sqlmanually in the hosted Supabase SQL editor. Do not add the service-role key to the SQL file. Its final query is non-destructive and returns the exact candidate set. -
Check every returned bucket/path against
Student.avatar/Student.cv. The installer intentionally does not schedule deletion. -
Only after confirming the dry run, run
supabase/storage-gc-enable.sqlmanually. -
Confirm the job exists with:
select jobid, schedule, command, active from cron.job where jobname = 'storage-orphan-gc';
The job reads storage.objects but deletes through the Storage API; direct SQL
deletion would remove only metadata and leave the billed blob behind. Failed API
deletions remain in storage.objects, so the next daily run retries them.
Monitor runs and asynchronous deletion failures after 03:00 UTC:
select status, return_message, start_time, end_time
from cron.job_run_details
where jobid = (select jobid from cron.job where jobname = 'storage-orphan-gc')
order by start_time desc
limit 10;
select id, status_code, timed_out, error_msg, created
from net._http_response
where timed_out or error_msg is not null or status_code not between 200 and 299
order by created desc;
select bucket_id, count(*)
from public.storage_gc_candidates()
group by bucket_id;pg_net responses expire after six hours by default, so inspect them soon after
the run. A candidate count that does not shrink indicates persistent failures.
Student CVs are purged twice a year, on May 1 and Nov 1 at 02:00 UTC, once
Student.cvUploadedAt is more than 6 months old. The job only clears the DB
reference (cv = NULL, cvPurgedAt = now()); the CV upload path stamps
cvUploadedAt and clears cvPurgedAt on every successful upload. A profile
banner tells the student their CV was removed whenever cvPurgedAt is set.
-
Run
supabase/cv-retention-purge.sqlmanually in the hosted Supabase SQL editor. Its final query is non-destructive and returns the exact candidate set. -
Check every returned row against
Student.cv/Student.cvUploadedAt. The installer intentionally does not schedule the purge. -
Only after confirming the dry run, run
supabase/cv-retention-purge-enable.sqlmanually. -
Confirm the job exists with:
select jobid, schedule, command, active from cron.job where jobname = 'cv-retention-purge';
The purge only clears the DB reference; it does not delete the storage object. It runs at 02:00 UTC, one hour before the orphaned-file GC job's daily 03:00 UTC run (above), so the now-unreferenced CV object is deleted the same day instead of waiting on its own cadence.
You can run a full Supabase stack locally (Auth, Storage, DB, Studio, Realtime, Gateway).
scoop bucket add supabase https://github.com/supabase/scoop-bucket.git
scoop install supabaseVerify installation:
supabase --versionRun from the project root:
supabase startThis launches:
| Service | URL |
|---|---|
| API Gateway | http://127.0.0.1:54321 |
| GraphQL API | http://127.0.0.1:54321/graphql/v1 |
| Supabase Studio | http://127.0.0.1:54323 |
| SMTP Inbox | http://127.0.0.1:54324 |
| Database | postgresql://postgres:postgres@127.0.0.1:54322 |
Supabase CLI sometimes starts a vector container that repeatedly fails on Windows.
This container is NOT required to run the app.
You may run:
docker rm -f supabase_vector_fallstack-websiteIf the name differs, check:
docker ps -aAfter stopping:
supabase stop
docker rm -f $(docker ps -aq --filter "name=supabase")Then:
supabase startsupabase stopTo also remove local data volumes:
docker compose --profile supabase down -vSchema changes are tracked with Prisma Migrate (prisma/migrations/), not db push. See docs/database-workflow.md for creating and applying migrations, the one-time baseline-adoption note, resetting a local database, seeding, and wiping the database.
Start the Next.js dev server:
pnpm devApp runs on:
http://localhost:3000
| Tool | URL |
|---|---|
| Supabase Studio | http://127.0.0.1:54323 |
| API Gateway | http://127.0.0.1:54321 |
| SMTP Inbox | http://127.0.0.1:54324 |
docker compose up -d dbdocker compose --profile supabase up -dStop:
docker compose --profile supabase downSee AGENTS.md's Contribution workflow section for branch naming (Conventional Branch), commit style (Conventional Commits), and the PR-into-dev process. Task tracking lives on the repository's GitHub Projects board.