CareerChive

How was the CareerChive application implemented?

Application Build

Application Build

How was the CareerChive application implemented?

Supabase Backend

The schema applied by running scripts/schema.sql enables row-level security on every public table. createAdminClient() talks to these tables with SUPABASE_SECRET_KEY, which bypasses row-level security.

There are no anon or authenticated policies — grants below are for service_role:

TableServer (SUPABASE_SECRET_KEY)
settingsSELECT, INSERT, UPDATE, DELETE
login_attemptsSELECT, INSERT, UPDATE, DELETE
organizationsSELECT, INSERT, UPDATE, DELETE
daysSELECT, INSERT, UPDATE, DELETE
workSELECT, INSERT, UPDATE, DELETE
citationsSELECT, INSERT, UPDATE, DELETE
  • settings is a singleton (id = 1). Username and password_hash (scrypt) are the FLAG_PRIVATE login, and password_encrypted is an AES-256-GCM copy keyed from SESSION_SECRET so Settings can refill the password field.
  • login_attempts is one row per failed login. Count by IP and by username independently — a fourth try in that hour is rejected without checking the password.
  • organizations stores weekday flags Sun–Sat (0 = Sunday … 6 = Saturday) for auto-generation of daily entries.
  • days is one log per organization and work_date.
  • work is the Work Completed rows on a day (title, description, link, position).
  • citations is the Citations / Artifacts rows on a day. attachment is the filename shown in the UI, and attachment_key is the Cloudflare R2 object key.

Next.js Frontend

When CareerChive opens, CareerChiveLayout loads the archive once. loadArchive() reads settings, organizations, days, work, and citations into DemoProvider, and the pages use that copy. Saves go back through the server actions in src/app/actions/archive.ts.

AppChrome is the top navigation:

(/) is ArchiveView, consisting of a calendar and DayPanel daily organization logs:

  • Notes and comments are markdown from rich text NotesEditor which uses Lexical.
  • Work Completed and Citations / Artifacts are drag-and-drop lists, with order captured in position.
  • Citations / Artifacts files are uploaded to Cloudflare R2. src/lib/r2.ts signs upload and download URLs, and src/app/api/attachments/route.ts accepts the upload and redirects a download.

User Login

FLAG_PRIVATE gates the archive with the username and password on settings. The cookie is careerchive_session (httpOnly, path /, 7 days), and signSession() stores the username and an expiry and signs them with HMAC-SHA256 from SESSION_SECRET:

  • loginAction() checks the username and verifies the password with scrypt (verifyPassword()). Failed tries are written to login_attempts before the error is returned.
  • /login renders LoginForm. A signed-in visit redirects to the CareerChive home page (/, Archive).
  • With FLAG_PRIVATE off, getSession() returns a local session, /login redirects home, and Logout is hidden. SESSION_SECRET is still what encrypts the Settings password copy when a password is saved.

Deleting an organization, clearing or deleting a range of days, and Factory Reset ask for that password again and check it against password_hash.

Feature Flags

Flags are built on compile:

  • src/lib/flags.ts reads process.env.FLAG_* with envFlag (true / 1 vs false / 0 — unset uses the default).
  • withCareerChiveConfig() lists FLAG_PRIVATE and FLAG_CHIVEBOT under env so Next can inline them into client components.

When set to true, requires the username and password from Settings before the archive loads. Default is false.

Accessibility

Along with checking pages with Chrome's Lighthouse accessibility audit, as part of pull request checks, a11y lint runs a spot check on all CareerChive application pages to ensure they are somewhat accessible and any errors discovered are patched before deployment.