Financial transparency portal made for FEUCSC (Federación de Estudiantes UCSC).
- Summary dashboard (budget, spent, remaining)
- Monthly spend trend
- Latest transactions preview
- Full expenses page with category breakdown and receipt links
- Google sign-in (Supabase Auth)
- Dynamic database email allowlist (only approved accounts access)
- Manage expenses and categories
- Secure receipt uploads to Cloudinary
- Receipt preview and deletion
- Next.js (App Router)
- Supabase (Postgres + Auth)
- Cloudinary (receipt storage)
- SWR (admin data fetching/cache)
- Tailwind CSS
- HeroUI
- Zod (server-side validation)
- Vitest
- GitHub Actions CI
- Lighthouse CI
Multiple layers protect admin actions and file uploads.
- Google OAuth via Supabase Auth
- Row Level Security (RLS): Database operations restrict to authorized admin emails via Supabase policies.
- Dynamic email allowlist enforces access in Next.js (middleware/Server Actions) and the database.
- Protected admin routes via
middleware.ts - Server Actions require authenticated and authorized users
Receipt uploads happen server-side.
- Uploads bypass the browser; they go from Next.js to Cloudinary.
- Cloudinary API secrets remain on the server.
- Magic-byte file validation checks file signatures.
- Supported formats: JPEG, PNG, GIF, WebP.
- Max upload size: 5MB.
- Cloudinary restricts files to the
comprobantes/folder. - Signed uploads use the Cloudinary SDK.
- Receipt deletion requires authentication.
- Cloudinary deletions use server-side signed requests.
- Users can only delete assets inside the
comprobantes/folder.
- Node.js (v18+)
- pnpm (v9+)
- A Supabase project
- A Cloudinary account
- Clone the repository and install dependencies:
pnpm install- Create
.env.localinside the project root:
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=
CLOUDINARY_CLOUD_NAME=
CLOUDINARY_API_KEY=
CLOUDINARY_API_SECRET=
NEXT_PUBLIC_PRESUPUESTO_TOTAL=19972000Start the development server:
pnpm devOpen http://localhost:3000.
pnpm build
pnpm startPublic:
/: dashboard/gastos: all expenses/faq: dynamic FAQ list/contacto: contact form and details
Auth/Admin:
/login: Google OAuth entry/auth/callback: OAuth callback/admin: admin UI
Automated tests and CI workflows ensure reliability, security, and frontend quality.
Coverage includes authentication helpers, upload validation logic, and image signature/magic-byte validation.
Run tests locally:
pnpm testGitHub Actions workflows run on every push to main and Pull Requests:
- Code linting (
eslint) - Static type checking (
tsc) - Automated test execution (
vitest) - Production build validation
- Lighthouse CI frontend performance auditing
src/app/: routes (public and admin)src/app/actions/: Server Actions for DB writes and uploadssrc/lib/: Supabase clients, auth helpers, Cloudinary config, utilities, test utilitiessrc/components/: UI componentssrc/hooks/: SWR data hooks
| Column | Type |
|---|---|
id |
uuid |
fecha |
YYYY-MM-DD |
descripcion |
text |
categoria |
text |
monto |
numeric |
comprobante_url |
nullable text |
creado_el |
timestamp |
| Column | Type |
|---|---|
id |
uuid |
nombre |
unique text |
color |
optional hex #RRGGBB |
creado_el |
timestamp |
When users delete a category, existing expenses reassign to N/A.
| Column | Type |
|---|---|
id |
uuid |
email |
unique text |
creado_el |
timestamp |
RLS policies and Next.js middleware check this table to grant access.
- Database RLS: Supabase enforces Row Level Security. Only users with emails in the
adminstable can INSERT, UPDATE, or DELETE records. middleware.tsblocks/admin/*if the session email misses theadminsallowlist.src/app/admin/layout.tsxvalidates the session server-side before rendering UI.- Server Actions require an authenticated session, authorized email, and valid payloads.
src/lib/auth.ts queries the admins table using the Service Role Key to avoid RLS circular dependencies during access checks.
- Public pages use ISR and revalidate after admin writes.
next.config.tsconfigures security headers and CSP.- Cloudinary uploads use signed server-side uploads exclusively.
- The project favors explicit server-side validation alongside strict Postgres RLS.
Proprietary - All rights reserved.