Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
46878bc
feat(platform): establish Cloudflare application foundation
HazAT Aug 3, 2026
2b2edc6
feat(auth): enforce Cloudflare Access identity
HazAT Aug 3, 2026
2a485cf
feat(projects): rebuild core project workflows
HazAT Aug 3, 2026
638ed7a
feat(voting): rebuild awards and administration
HazAT Aug 4, 2026
a5b05e6
feat(migration): add Firebase import tooling
HazAT Aug 4, 2026
2a0cfa3
feat(auth): add safe local browser identity
HazAT Aug 4, 2026
af397d6
feat(ui): restore the legacy Hackweek identity
HazAT Aug 4, 2026
de74bc2
feat(video): implement Cloudflare Stream lifecycle
HazAT Aug 4, 2026
0bd60e7
feat(video): add upload and screening experiences
HazAT Aug 4, 2026
fa64d4c
feat(cutover): complete local migration readiness
HazAT Aug 4, 2026
53c3d9c
fix(migration): handle production Firebase edge cases
HazAT Aug 4, 2026
f9078a5
feat(auth): replace Access with Google OAuth
HazAT Aug 5, 2026
161fbd3
feat(deploy): launch core Cloudflare production
HazAT Aug 6, 2026
7b3692a
feat(ui): restore Google-branded sign-in button
HazAT Aug 8, 2026
d25900f
feat(auth): require Google OAuth for all environments
HazAT Aug 8, 2026
c0e94a6
feat(ui): add current-year hero and archive layout to years page
HazAT Aug 8, 2026
24453fa
feat(ui): refine years page layout and add 2025/2026 banners
HazAT Aug 8, 2026
cb8597f
feat(auth): add admin view-as-user session mode
HazAT Aug 8, 2026
59828fe
fix(readiness): run child commands without stdin
HazAT Aug 8, 2026
ad4aae4
feat(years): restrict submissions and voting to the current year
HazAT Aug 8, 2026
349f878
feat(ui): add grid/list view switch to project overviews
HazAT Aug 8, 2026
c3d167b
feat(ui): replace team picker with searchable member select
HazAT Aug 8, 2026
6e3d377
polish(ui): tighten layout and copy details
HazAT Aug 8, 2026
4962a55
fix(ui): hide the vote action when voting is not enabled
HazAT Aug 8, 2026
93289d2
chore(cutover): clean repository for the core launch
HazAT Aug 10, 2026
1bda61a
Update production Wrangler configuration
HazAT Aug 10, 2026
bfeedf0
chore: remove repository documentation
HazAT Aug 10, 2026
7833e7a
fix(ui): center page state cards
HazAT Aug 10, 2026
a994f5e
fix(ui): remove error state border accent
HazAT Aug 10, 2026
83a867d
fix(ui): remove page state border
HazAT Aug 10, 2026
30376ca
feat(projects): add server-side listing search
HazAT Aug 10, 2026
9164c32
fix(ui): use the official Sentry favicon
HazAT Aug 10, 2026
dba04bf
chore: format production configuration
HazAT Aug 10, 2026
f675427
fix(ui): debounce project search
HazAT Aug 10, 2026
7319b0f
fix(ui): live-update project search results
HazAT Aug 10, 2026
c76ac1b
fix(tooling): make Worker type generation deterministic
HazAT Aug 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .dev.vars.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copy to .dev.vars for local browser development only. The Google OAuth client
# must allow the loopback origin and exact callback below.
APP_ORIGIN="http://localhost:5173"
GOOGLE_CLIENT_ID=<non-secret client ID>
GOOGLE_CLIENT_SECRET=<secret from the shared vault; never commit>
GOOGLE_REDIRECT_URI=http://localhost:5173/api/auth/callback
ALLOWED_EMAIL_DOMAIN="sentry.io"

# The local adapter issues direct-upload fixtures and protected-playback contracts.
# It does not transcode, generate HLS, or move video bytes.
STREAM_MODE="fake"
STREAM_ALLOWED_ORIGIN="localhost"
STREAM_DELIVERY_HOST="customer-fake.cloudflarestream.com"
STREAM_WEBHOOK_SECRET="replace-with-a-local-signing-secret"
VIDEO_SERVICE_TOKEN="replace-with-a-local-job-token"
6 changes: 0 additions & 6 deletions .firebaserc

This file was deleted.

51 changes: 51 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Deploy Cloudflare environment

on:
workflow_dispatch:
inputs:
confirm:
description: Type deploy-hackweek after operator review
required: true
type: string

permissions:
contents: read

concurrency:
group: hackweek-cloudflare
cancel-in-progress: false

jobs:
verify:
if: inputs.confirm == 'deploy-hackweek'
uses: ./.github/workflows/test.yml

deploy:
name: Deploy reviewed configuration
needs: verify
runs-on: ubuntu-latest
environment: hackweek-cloudflare
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24.19.0
cache: npm
- run: npm ci --no-audit --no-fund
- name: Validate required configuration
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
test -n "$CLOUDFLARE_API_TOKEN" || { echo 'Missing CLOUDFLARE_API_TOKEN'; exit 1; }
test "$CLOUDFLARE_ACCOUNT_ID" = '773afa1f62ff86c80db4f24f7ff1e9c8' || { echo 'Unexpected Cloudflare account'; exit 1; }
node -e "const c=require('./wrangler.production.json'); if (c.account_id !== '773afa1f62ff86c80db4f24f7ff1e9c8' || c.vars.STREAM_MODE !== 'disabled') process.exit(1); for (const value of [c.d1_databases[0].database_id,c.r2_buckets[0].bucket_name,c.vars.APP_ORIGIN,c.vars.GOOGLE_REDIRECT_URI,c.vars.GOOGLE_CLIENT_ID]) if (!value || /replace.me/i.test(value) || value === '00000000-0000-0000-0000-000000000000') process.exit(1)"
- run: npm run build
- name: Apply reviewed D1 migrations
run: npx wrangler d1 migrations apply hackweek-db --remote --config wrangler.production.json --yes
env: &cloudflare
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
- name: Deploy Worker and static assets
run: npx wrangler deploy --config wrangler.production.json
env: *cloudflare
67 changes: 0 additions & 67 deletions .github/workflows/release.yml

This file was deleted.

51 changes: 14 additions & 37 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,48 +1,25 @@
name: Test

on:
pull_request:
workflow_call:

jobs:
build:
name: Build
verify:
name: Verify
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v3
- name: Checkout repository
uses: actions/checkout@v4

- name: Cache node modules
id: cache-npm
uses: actions/cache@v3
env:
cache-name: cache-node-modules
- name: Set up Node.js
uses: actions/setup-node@v4
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-

- if: ${{ steps.cache-npm.outputs.cache-hit == 'false' }}
name: List the state of node modules
continue-on-error: true
run: npm list

- name: Install Dependencies
run: npm install
node-version: 24.19.0
cache: npm

- name: Build
run: npm run build
- name: Install dependencies deterministically
run: npm ci --no-audit --no-fund

- name: Bind Config
run: npm run bind-version

- name: Test
run: npm test -- --testResultsProcessor="jest-junit" --coverage

- name: Publish Test Report
uses: mikepenz/action-junit-report@v3
if: always() # always run even if the previous step fails
with:
report_paths: '**/junit.xml'
- name: Verify
run: npm run verify
Comment on lines +9 to +25
34 changes: 34 additions & 0 deletions .github/workflows/video-archive.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Archive videos to Drive

on:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: video-archive
cancel-in-progress: false

jobs:
archive:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24.19.0
cache: npm
- run: npm ci
- name: Install rclone
run: curl https://rclone.org/install.sh | sudo bash
- name: Configure Drive and archive ready videos
run: |
mkdir -p ~/.config/rclone
printf '%s' "$RCLONE_CONFIG" > ~/.config/rclone/rclone.conf
npx tsx scripts/archive-to-drive.ts
env:
VIDEO_API_URL: ${{ secrets.VIDEO_API_URL }}
VIDEO_SERVICE_TOKEN: ${{ secrets.VIDEO_SERVICE_TOKEN }}
RCLONE_CONFIG: ${{ secrets.RCLONE_CONFIG }}
RCLONE_DRIVE_DESTINATION: ${{ vars.RCLONE_DRIVE_DESTINATION }}
26 changes: 26 additions & 0 deletions .github/workflows/video-measure.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Measure video loudness

on:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: video-measurement
cancel-in-progress: false

jobs:
measure:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24.19.0
cache: npm
- run: npm ci
- run: npx tsx scripts/measure-loudness.ts
env:
VIDEO_API_URL: ${{ secrets.VIDEO_API_URL }}
VIDEO_SERVICE_TOKEN: ${{ secrets.VIDEO_SERVICE_TOKEN }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Measure workflow missing ffmpeg

Medium Severity

video-measure.yml runs scripts/measure-loudness.ts, which spawns ffmpeg, but the workflow never installs it. GitHub ubuntu-latest runners do not ship ffmpeg, unlike the archive workflow which installs rclone. When videos are queued, every measurement fails with a missing-command error and is reported as a measurement failure.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c4d6200. Configure here.

45 changes: 30 additions & 15 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,31 +1,46 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.

# dependencies
/node_modules

# testing
# tests
/coverage
/.vitest-attachments

# production
# build output
/build
/dist

# misc
.firebase
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
# Cloudflare local state and secrets
/.wrangler
.dev.vars*
!.dev.vars.example

# local environment
.env
.env.*
!.env.example

# editor and operating system
.DS_Store
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
firebase-debug.log*

junit.xml
# operator-provided migration exports and generated reports
/migration-input/
/migration-output/
/source-database-export/
/source-storage-export/
*.source-export.json
*.migration-report.json

# generated
# generated reports and TypeScript state
junit.xml
*.tsbuildinfo
src/version.json

# videos
# local documentation
/docs/

# local videos
public/videos/
10 changes: 0 additions & 10 deletions .prettierrc

This file was deleted.

Empty file removed NOTES.md
Empty file.
43 changes: 34 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,47 @@
# Sentry Hackweek

Welcome to #hackweek!
Hackweek is an internal React + TypeScript application served by one Hono Cloudflare Worker. Application-owned Google OAuth authenticates users, D1 owns sessions/data/roles, and private R2 stores attachments. The core production rollout serves the SPA with Cloudflare Static Assets at `https://hackweek.getsentry.workers.dev` and keeps `STREAM_MODE=disabled`; Stream, video screening, and archive operations are dormant until a separately approved rollout. The UI preserves the Sentry `#HACKWEEK` identity and archive hierarchy.

To get the project running:
## Requirements

- Node.js 24.11 or newer (Volta and CI pin 24.19)
- npm 11 or newer

## Deterministic local start

```bash
npm install && npm start
npm ci
cp .dev.vars.example .dev.vars
rm -rf .wrangler/state
npm run db:migrate:local
npm run migrate:local -- \
--database test/fixtures/firebase/database.json \
--storage-manifest test/fixtures/firebase/storage-manifest.json \
--storage-root test/fixtures/firebase/storage
npm run dev
```

The Hackweek site uses firebase, so to deploy it you'll need Google credentials.
Before starting the app, configure a Google OAuth Web application to allow the JavaScript origin `http://localhost:5173` and redirect URI `http://localhost:5173/api/auth/callback`. Replace the placeholders in `.dev.vars` with its client ID and the client secret from the shared vault; never commit `.dev.vars`.

Open `http://localhost:5173` and sign in with Google. D1 remains the sole role authority. To promote your local user after signing in once, replace the email below and run:

```bash
npx wrangler d1 execute hackweek-db --local --command \
"UPDATE users SET is_admin = 1, updated_at = CURRENT_TIMESTAMP WHERE google_subject IS NOT NULL AND email = 'you@sentry.io'"
```

Resetting `.wrangler/state` removes the promotion. Never run that command with `--remote`.

## Authentication

Google OAuth is the only browser authentication path in every environment, including local development. It uses the Authorization Code flow with PKCE, state, nonce, confidential server exchange, Google JWKS validation, exact verified `@sentry.io` enforcement, hashed opaque D1 sessions, and HttpOnly cookies.

## Deployment
All core browser APIs except health require a D1-backed user. Authenticated mutations and logout require an exact same-origin `Origin` header. Logout revokes the current D1 session. Login rotates existing sessions. Google/client claims never grant admin access. Dormant Stream webhook and video-job endpoints use separate machine-auth boundaries if real Stream is approved later.

For dev deployments:
## Quality gates

```bash
npm run build && npm run deploy-dev
npm run verify
```

> [!NOTE]
> You don't need to run dev deploys if you don't change the `database.rules.bolt` file.
This generates binding types, typechecks, checks formatting/lint, runs Worker/frontend/migration/player tests, builds, performs a credential-free deployment dry run, and runs an isolated seeded D1/R2 journey with fake-Stream contract coverage. Local tests do not prove real Google OAuth, deployed bindings, or imported data. Real Stream is outside that gate.
Loading
Loading