Skip to content

codezelat/gbe-award-portal

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

36 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Global Business Excellence Awards

Secure nominations, applicant access and award operations for the Global Business Excellence Awards 2026.

🌐 Live portal Β Β·Β  πŸ† Submit a nomination Β Β·Β  πŸ’¬ Get help

Global Business Excellence Awards 2026 trophy

GBE Awards Portal

The production portal for the Global Business Excellence Awards 2026. It gives nominees a clear, secure path from public nomination through payment proof and document upload; gives approved applicants a private self-service portal; and gives staff one focused workspace for review, communications, payments, reporting and audit history.

Live: access.gbeaward.com

Support: info@gbeaward.com Β· WhatsApp

Official site: gbeaward.com

Important

This is a production system for personal, nomination and payment information. Never commit .env, provider credentials, database exports, real nomination data or screenshots containing private information.

✨ What it does

For Experience
Nominees A four-step public nomination journey with category selection, supporting documents, payment choice and payment-proof upload. Every file is uploaded directly to private storage and every completed nomination gets a non-sequential GBE-2026-###### reference.
Approved applicants Invitation-only access to applications, requested documents, payment status, messages, profile, password management and account security. A public nomination never creates an account by itself.
Award staff One focused Staff workspace for applications, applicants, payments, files, communications, exports and reports. Super admins also manage award cycles, categories, staff, settings and audit activity. Staff must enrol TOTP MFA before administration access.
Operations Controlled workflow transitions, reviewer assignment, change requests, payment verification, signed downloads, exports, delivery tracking, retention work and an auditable history of sensitive actions.

🧭 Product flow

flowchart LR
  N[Public nomination] --> T[Turnstile + validation]
  T --> U[Direct private R2 uploads]
  U --> Q[Submitted nomination]
  Q --> R[Staff review]
  R -->|Approved| I[Secure invitation]
  R -->|Changes requested| N
  I --> P[Applicant portal]
  P --> O[Documents, payment, messages and security]
  R --> A[Audited administration]
Loading

The public form is deliberately short and guided:

  1. Nominee β€” company name or full name.
  2. Contact β€” contact details, award category, mandatory nomination statement and optional supporting documents.
  3. Payment β€” card or bank-transfer instructions and one payment-proof file.
  4. Confirm β€” declaration review, Turnstile verification and submission.

Supporting documents and payment proof are independently limited to 5 MB per file. The browser gives upload progress, cancellation and retry feedback; the server repeats validation before accepting a completion request.

πŸ—οΈ Architecture

Layer Technology Responsibility
Application Next.js 16 App Router, React 19, strict TypeScript Public, applicant and staff routes; server actions; route handlers; streaming loading states.
Interface Tailwind CSS 4, shadcn/Base UI, TanStack Table Responsive, keyboard-accessible light-mode interface with tailored mobile/tablet layouts.
Data Neon PostgreSQL, Drizzle ORM Relational award data, workflow state, accounts, audit history, email outbox, rate limits and migrations.
Authentication Better Auth + Drizzle adapter Approval-first invitations, password recovery, session revocation and mandatory TOTP MFA for staff.
File storage Cloudflare R2 + AWS SDK Private, browser-to-R2 presigned uploads; type detection; signed downloads; lifecycle cleanup.
Abuse protection Cloudflare Turnstile + PostgreSQL-backed rate limiting Nomination verification plus an adaptive login challenge after two failed attempts; durable across instances.
Email Resend + React Email Durable outbox, idempotent delivery, retry/backoff and signed delivery webhooks.
Exports ExcelJS, CSV and React PDF Filtered staff exports and private applicant application summaries.
Hosting Vercel + Cloudflare DNS Production hosting, security headers and one Hobby-compatible daily cron.

Security model

  • Public routes are limited to the nomination and information pages. Turnstile checks action and expected hostname server-side. Login challenges appear only after two failed attempts for the same hashed email/IP fingerprint and are verified before a further password check.
  • Private routes are protected early by src/proxy.ts, then enforce a session and application-level profile checks in the server data-access layer.
  • Staff routes require a staff profile, active membership and mandatory TOTP MFA. Staff can complete the full nomination workflow; super admins additionally manage people and system configuration. The QR code and manual setup URI work with Google Authenticator, Microsoft Authenticator and compatible apps.
  • Uploads go to the private R2 bucket by presigned URL. Object metadata, detected types, ownership and disposition are checked before a record becomes available.
  • Programme media on /apply is an on-demand dialog: the Facebook post/reel frames and their client bundle load only after a visitor requests them. The adjacent event-brochure action is a verified direct Google Drive download. No Facebook SDK is loaded into the nomination page; the restrictive policy permits Facebook only as a frame source for this dialog.
  • Sensitive operations write audit events. Administrative data is noindexed, and platform headers block framing and apply a restrictive content policy.
  • Database roles are split: the runtime uses the least-privilege pooled connection; migrations use a separate direct owner connection.

πŸ—ΊοΈ Repository map

src/
β”œβ”€β”€ app/                 # App Router pages, layouts, loading/error boundaries and API routes
β”‚   β”œβ”€β”€ apply/           # Public guided nomination and submitted confirmation
β”‚   β”œβ”€β”€ portal/          # Private applicant portal
β”‚   β”œβ”€β”€ admin/           # Permission-aware staff workspace
β”‚   β”œβ”€β”€ auth/            # Invite acceptance, password recovery and staff MFA
β”‚   └── api/             # Uploads, exports, health, cron and Resend webhook handlers
β”œβ”€β”€ components/          # Reusable interface, forms, uploads, admin and shared shell components
β”œβ”€β”€ config/              # Brand, navigation and role-permission definitions
β”œβ”€β”€ emails/              # React Email presentation components and templates
β”œβ”€β”€ lib/                 # Environment parsing, Better Auth, Drizzle, R2 and domain helpers
└── server/              # DAL, actions, jobs, security and business services

drizzle/migrations/      # Append-only PostgreSQL migrations
scripts/                 # Environment, provider, seed and first-admin commands
tests/                   # Unit, integration and Playwright end-to-end tests
public/brand/            # Versioned logo and award artwork used by the application and this README

πŸš€ Run locally

Prerequisites

  • Bun 1.3.12 (the version pinned in package.json)
  • A non-production Neon/PostgreSQL database with two distinct roles: runtime and migration owner
  • Two non-production Cloudflare R2 buckets (private uploads and public assets), with browser PUT CORS configured for the local origin
  • Cloudflare Turnstile test keys for local work
  • A non-production Resend API key and verified sending domain when email flows are being exercised

First run

bun install
cp .env.example .env
bun run env:verify
bun run db:migrate
bun run dev

Open http://localhost:3000. The public nomination route is /apply.

Tip

The local defaults are intentionally safe for UI work. Full provider operations require the non-production values described in Environment configuration.

Seed an award cycle

The seed command creates the approved 2026 categories but deliberately leaves the cycle in draft. It cannot silently open public nominations.

SEED_CYCLE_OPENS_AT=2026-01-01T00:00:00.000Z \
SEED_CYCLE_CLOSES_AT=2026-12-31T23:59:59.999Z \
bun run db:seed

Review the dates, legal copy, categories, fees, payment instructions and feature flags in administration before an authorized super administrator changes the cycle status.

Bootstrap the first staff account

This command is one-time only. It refuses to run once a super administrator exists.

After securely setting all three BOOTSTRAP_ADMIN_* variables in the local environment, run:

bun run db:bootstrap-admin

Sign in, enrol TOTP MFA immediately, then remove all three BOOTSTRAP_ADMIN_* values from the environment. Create every later staff account from Administration β†’ Staff.

πŸ” Environment configuration

Copy .env.example; it is the complete, non-secret contract. Keep actual values in local environment files or encrypted Vercel settings only.

Group Variables Notes
Application NEXT_PUBLIC_APP_URL, APP_ENV, APP_TIMEZONE, SUPPORT_EMAIL, OFFICIAL_SITE_URL Production URLs must use HTTPS. The portal timezone is Asia/Colombo.
Database DATABASE_URL, DATABASE_URL_DIRECT Required. Use separate runtime and migration-owner roles; never use the direct owner URL in the Vercel runtime.
Authentication BETTER_AUTH_SECRET, BETTER_AUTH_URL Required. The URL must exactly match NEXT_PUBLIC_APP_URL; the secret must be at least 32 characters.
R2 R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_ENDPOINT, R2_PRIVATE_BUCKET, R2_PUBLIC_BUCKET, R2_OBJECT_PREFIX, R2_PUBLIC_ASSET_BASE_URL Keep uploads/exports in the private bucket. Use R2_OBJECT_PREFIX to isolate preview, test and production objects.
Turnstile NEXT_PUBLIC_TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEY, TURNSTILE_EXPECTED_HOSTNAME, TURNSTILE_APPLICATION_ACTION Use Cloudflare’s test keys only outside production. The portal verifies gbe_nomination_submit for nominations and gbe_login after two failed sign-ins.
Resend RESEND_API_KEY, RESEND_WEBHOOK_SECRET, EMAIL_FROM, EMAIL_REPLY_TO EMAIL_FROM must use a verified Resend domain. Configure the signed Resend webhook at /api/webhooks/resend.
Operations CRON_SECRET Required and at least 24 characters. Authorizes maintenance routes; Vercel invokes only /api/cron/daily on the scheduled job.
First admin BOOTSTRAP_ADMIN_NAME, BOOTSTRAP_ADMIN_EMAIL, BOOTSTRAP_ADMIN_PASSWORD Temporary only. Remove immediately after the initial account has been created and secured.
Seed SEED_CYCLE_OPENS_AT, SEED_CYCLE_CLOSES_AT Required only by bun run db:seed; use approved ISO 8601 timestamps.

Run both checks after configuring an environment:

bun run env:verify
bun run providers:verify

env:verify rejects unsafe configuration such as matching runtime/owner database URLs, production Turnstile test keys or leftover bootstrap credentials. providers:verify proves database access, runtime rate-limit permissions, private R2 read/write/delete plus browser CORS, Resend sender status and Turnstile hostname policy.

πŸ§ͺ Quality checks

Command What it proves
bun run lint ESLint passes with zero warnings.
bun run typecheck Strict TypeScript has no errors.
bun run test Fast unit tests for business rules, security/permissions, validation, loading boundaries, exports and PDFs.
bun run test:integration Database-integrity tests against an isolated gbe_award_portal_test* database. Requires TEST_DATABASE_URL and TEST_DATABASE_ADMIN_URL.
bun run test:e2e Playwright desktop/mobile journeys using a recreated gbe_award_portal_test_e2e database and e2e/playwright R2 prefix. It covers public submission, retries, file validation, adaptive login protection, accessibility, staff MFA, export and responsive overflow.
bun run build Production Next.js build.
bun run check Lint, typecheck, unit tests and production build in one command.
bun audit Bun dependency vulnerability audit.

Caution

Integration and end-to-end tests recreate dedicated test databases. Their names are guarded in code, but they still require a database owner connection that is allowed to create and drop only test databases. Never point test variables at a production database.

πŸ—ƒοΈ Database and file operations

Migrations in drizzle/migrations are append-only and are the source of truth for schema history.

bun run db:generate  # generate a migration, then review it carefully
bun run db:migrate   # apply reviewed migrations with DATABASE_URL_DIRECT
bun run db:studio    # local inspection only

Do not run db:push against staging or production. Before any production schema change, take a Neon branch or restore point, confirm the migration is backward-compatible with the currently deployed application, apply it with the owner URL, and retain a rollback path.

Files are not stored in PostgreSQL or Vercel’s filesystem. The application stores file records and audit metadata in PostgreSQL, while original bytes live in R2. Private downloads are signed per request and exports are temporary private objects.

πŸ“¬ Email, webhooks and maintenance

Application responses queue email in PostgreSQL. A Next.js after() callback attempts prompt delivery; a durable outbox retries failed messages with backoff, and Resend’s signed webhook records delivered, bounced and failed events.

Endpoint Purpose Protection
POST /api/webhooks/resend Records verified Resend delivery events RESEND_WEBHOOK_SECRET signature verification
GET /api/cron/daily Runs email retries, stale-upload cleanup, expired-export cleanup, retention and stale rate-limit cleanup Bearer authorization using the configured CRON_SECRET
GET /api/health Reports database reachability No secrets returned; do not treat it as a public status page

Vercel Hobby supports one cron entry. vercel.json schedules the daily route at 23 2 * * *; do not add overlapping Vercel cron entries for individual cleanup jobs.

🚒 Production release checklist

  1. Review scope β€” inspect the diff, check that no secrets or production data are included, and preserve approved award copy unless the change explicitly calls for it.
  2. Validate locally β€” run bun run check, then the relevant integration/E2E suite. Run bun run env:verify and bun run providers:verify with the target environment.
  3. Prepare data safely β€” take a Neon restore point/branch; apply only reviewed backward-compatible migrations with DATABASE_URL_DIRECT.
  4. Deploy β€” push main; Vercel deploys production. Wait for Ready and verify the access.gbeaward.com alias.
  5. Prove critical paths β€” confirm /api/health, one sign-in/MFA path, one authorized private upload/download, the current award-cycle settings, cron authorization and Resend delivery-webhook events.
  6. Observe β€” use Administration β†’ Communications and Activity to inspect delivery, workflow and operational events after release.

The deployment process never opens a cycle automatically. Opening, closing or changing award programme settings remains an explicit, authorized administrative decision.

🀝 Contributing and maintenance

This repository is maintained as a production application. Before changing it, read AGENTS.md for the exact project workflow, architecture boundaries, security requirements and verification expectations.

In short: use Bun, keep server/data access on the server, preserve the private-file and permission model, add migrations rather than rewriting history, test the affected route at desktop and mobile widths, and do not make external provider, deployment or award-programme changes without explicit authorization.

πŸ“„ Public information

About

Production portal for the application process of Global Business Excellence Awards 2026

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages