Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ Config store for the Slack praise-bot (repo `ohack-slack-bot/praise-bot`): the b
- `GET /api/praise-bot/config` — bot-facing, authed via `X-Api-Key` against `BACKEND_BOT_CONFIG_TOKEN` (falls back to `BACKEND_PRAISE_TOKEN`) through the shared `common/utils/api_key.py:check_api_key` (hmac.compare_digest; new code should use this instead of the inline checks in messages_views). Returns `configured: false` when the collection is empty → bot uses its env defaults.
- `GET/POST /api/praise-bot/admin/config`, `PATCH/DELETE /api/praise-bot/admin/config/<doc_id>` — `volunteer.admin`-gated. Validation is whitelist-per-type (`_ALLOWED_KEYS` — the enforcement point that keeps secrets out of docs); crons validated as 5 fields (bot re-validates with `cron.validate()`); repos normalized to `owner/repo`; `global` is upsert-only (no DELETE); `community` is a singleton (POST 400s if one exists). `source.orgs` is accepted/stored but ignored by the bot until org-watching ships. 15s TTL cache on the assembled config, cleared on mutation. Tests: `api/praisebot/tests/` (mockfirestore, run with `ENVIRONMENT=test`).

## Volunteer job board (`api/jobs/`, `job_listings` + `job_applications` collections, Aug 2026)
Powers the frontend's `/jobs` pages and `/admin/jobs`. Blueprint `api/jobs/jobs_views.py` + `jobs_service.py`.
- `job_listings` doc id = **slug** (immutable after create; POST 409s duplicates). Statuses draft|published|hidden|closed — public list returns published+closed (lean fields), single-get 404s draft/hidden but returns closed (shared links render a closed panel). `posted_at` auto-stamped on first publish. 300s TTL caches (`get_public_listings`/`get_public_listing`) cleared on every admin write. Validators + `ALLOWED_JOB_*`/`JOB_LISTING_ADMIN_KEYS` constants live in `common/utils/validators.py`.
- `POST /api/jobs/<slug>/apply` is `@auth.require_user` + `@RateLimiter` + recaptcha (imports volunteers_service `verify_recaptcha`, keeps the `FLASK_ENV=development` bypass): validates via `validate_job_application` (visa_ack must be True, work sample ≥ 200 chars — keep in sync with the frontend's `MIN_WORK_SAMPLE_CHARS`), verifies `resume_url` is under the caller's own `job_applications/<db_id>/` CDN prefix and `video_url` is own-CDN (`users/<db_id>/`, the bio-video mint) or an `ALLOWED_VIDEO_LINK_HOSTS` link, then 409s if the user already applied to that listing. Resume mint `POST /api/jobs/apply/resume-upload-url` reuses `common/utils/cdn.generate_signed_upload_url` (PDF only, 10MB, resolves the user via users_service `_resolve_and_ensure_user`).
- Emails (Resend, all behind the local `_notifications_disabled()` mirror): applicant confirmation with the **reply-within-5-days responsiveness ask** (`reply_to: questions@ohack.org`), FYI to questions@ohack.org, and warm accept/reject decision emails via `POST /api/jobs/admin/applications/<id>/decision` (`{decision, personal_note?}`; records into `sent_emails` ArrayUnion + `status_history`). Admin routes are `volunteer.admin`-gated; application PATCH allowlist is `status`/`admin_notes` only.
- Seed: `scripts/seed_job_listings.py` (dry-run default, `--apply` writes the three Fall 2026 roles as drafts, **skips existing slugs** so admin edits survive re-runs; validates against `validate_job_listing` so seed/validator drift fails loudly).

## Public portfolio (profile → portfolio, Aug 2026)

The public profile payload (`GET /api/users/<id_or_slug>/profile/public`) is now the "portfolio" payload. Load-bearing contracts:
Expand Down
2 changes: 2 additions & 0 deletions api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ def add_headers(response):
from api.surveys import surveys_views
from api.feedback import feedback_views
from api.praisebot import praisebot_views
from api.jobs import jobs_views

app.register_blueprint(messages_views.bp)
app.register_blueprint(exception_views.bp)
Expand All @@ -215,5 +216,6 @@ def add_headers(response):
app.register_blueprint(surveys_views.bp)
app.register_blueprint(feedback_views.bp)
app.register_blueprint(praisebot_views.bp)
app.register_blueprint(jobs_views.bp)

return app
Empty file added api/jobs/__init__.py
Empty file.
Loading
Loading