diff --git a/specs/api/blog.md b/specs/api/blog.md index d1f2568..99072fa 100644 --- a/specs/api/blog.md +++ b/specs/api/blog.md @@ -57,6 +57,7 @@ Standard 404 envelope (per [conventions.md](conventions.md)). Slug-history redir "postedAt": "2026-05-15T18:00:00Z", "editedAt": "2026-05-16T09:30:00Z", // null when unedited "featuredImageKey": "blog-posts/civic-tech-roundup-2026/cover.jpg", // or null + "featuredImageUrl": "/api/attachments/blog-posts/civic-tech-roundup-2026/cover.jpg", // or null — derived from featuredImageKey "body": "Markdown source", "bodyHtml": "

...

", // sanitized HTML, server-rendered "createdAt": "...", diff --git a/specs/api/people.md b/specs/api/people.md index e013194..908c332 100644 --- a/specs/api/people.md +++ b/specs/api/people.md @@ -12,6 +12,7 @@ See [data-model.md](../data-model.md#person). | `GET` | `/api/people/:slug` | public | Fetch a single person's profile. | | `PATCH` | `/api/people/:slug` | self \| staff | Update profile. | | `POST` | `/api/people/:slug/avatar` | self \| staff | Upload an avatar image (multipart). | +| `PATCH` | `/api/people/:slug/newsletter` | self \| staff | Update newsletter opt-in state (private-store mutation; no public commit). | | `DELETE` | `/api/people/:slug` | administrator | Soft-delete (close account). | ## GET /api/people diff --git a/specs/architecture.md b/specs/architecture.md index 47f9815..5ff06ee 100644 --- a/specs/architecture.md +++ b/specs/architecture.md @@ -177,12 +177,22 @@ Runtime configuration (sealed-secrets in our cluster): | `GITHUB_OAUTH_CLIENT_ID` / `GITHUB_OAUTH_CLIENT_SECRET` | GitHub OAuth app credentials — see [api/auth.md](api/auth.md) | | `CFP_JWT_SIGNING_KEY` | HS256 key for session JWTs | | `SAML_PRIVATE_KEY` / `SAML_CERTIFICATE` | Slack SAML IdP cert chain — see [api/saml.md](api/saml.md) | +| `SLACK_TEAM_HOST` | Slack workspace host (default `codeforphilly.slack.com`). Used by the `/chat` redirect ([api/chat](screens/chat.md)) and the SAML SP entity binding. | +| `RESEND_API_KEY` | Optional. When set, mutates the notifier from the no-op `LoggingNotifier` to the live `EmailNotifier` (Resend SDK). | +| `CFP_NOTIFICATION_FROM` | Required when `RESEND_API_KEY` is set; the `From:` address on outbound mail. | +| `CFP_SITE_HOST` | Public site host (e.g., `codeforphilly.org`) — used by notifiers to build canonical URLs in email bodies. | +| `CFP_DATA_RELOAD_SECRET` | Bearer token gating `POST /api/_internal/reload-data` — the hot-reload webhook. Optional in dev; required in prod. | On pod start the entrypoint: 1. Runs `git clone` / `git fetch && git reset --hard origin/main` against `CFP_DATA_REMOTE` to populate the data-repo working tree 2. Boots the API, which loads the gitsheets state and the private-storage `.jsonl` files into memory +Health endpoints: + +- `GET /api/health` — liveness; returns 200 with build metadata as long as the process is up. +- `GET /api/health/ready` — readiness; returns 200 only after the public + private stores have loaded AND the boot-time reconcile has completed. The k8s `readinessProbe` is wired to this so the Service stops routing traffic to a pod that's still loading state. + On every public-side commit the API pushes asynchronously to `CFP_DATA_REMOTE`. On every private-side mutation the API PUTs the relevant `.jsonl` to the bucket synchronously. See the dual-write coordination notes in [behaviors/private-storage.md](behaviors/private-storage.md). The k8s manifests live in `deploy/kustomize/` as a Kustomize base plus per-environment overlays (`base/`, `overlays/staging/`, `overlays/production/`). Apply with `kubectl apply -k deploy/kustomize/overlays/`. Cluster targeting and secret management are unchanged from the legacy stack — sealed-secrets via [`bitnami-labs/sealed-secrets`](https://github.com/bitnami-labs/sealed-secrets), kubeconfig-per-environment in GitHub Environment secrets. See `docs/operations/migrate-to-k8s.md` in the laddr repo for the cluster-level context. diff --git a/specs/behaviors/app-shell.md b/specs/behaviors/app-shell.md index c49acce..67659f3 100644 --- a/specs/behaviors/app-shell.md +++ b/specs/behaviors/app-shell.md @@ -83,7 +83,7 @@ Single search input in the header. Behavior: - Placeholder: "Search projects, members, tags…" - On focus, expands to fit the available space -- As the user types (debounced 200ms), a dropdown shows up to 8 results across types — matches laddr's site-wide search but in a typeahead form instead of a full results page +- As the user types (debounced 200ms), a dropdown shows up to 12 results across types (4 per category, see groups below) — matches laddr's site-wide search but in a typeahead form instead of a full results page - Result groups: - "Projects" — `GET /api/projects?q=...&perPage=4` - "Members" — `GET /api/people?q=...&perPage=4` @@ -124,6 +124,7 @@ Three columns at ≥ md, stacked below. - Hackathons → `/pages/hackathons` - Members → `/members` - Help Wanted → `/help-wanted` +- Blog → `/blog` ### Column 2: About diff --git a/specs/data-model.md b/specs/data-model.md index b525110..9095617 100644 --- a/specs/data-model.md +++ b/specs/data-model.md @@ -383,7 +383,7 @@ This composite path makes "things with tag X" a single directory traversal in th |-------|------|-------| | id | uuid | | | tagId | uuid | | -| taggableType | enum | `project` \| `person` \| `help_wanted_role` | +| taggableType | enum | `project` \| `person` \| `help_wanted_role` \| `blog_post` | | taggableId | uuid | | | assignedById | uuid nullable | references people.id | | createdAt | iso8601 | | diff --git a/specs/screens/person-detail.md b/specs/screens/person-detail.md index 283d00a..9605080 100644 --- a/specs/screens/person-detail.md +++ b/specs/screens/person-detail.md @@ -43,7 +43,7 @@ Heading "Recent updates" — last 5 ProjectUpdate items authored by this person, ### Sidebar (right, ≥ lg) -- "Contact" — Slack DM link if `slackHandle` (deferred field) is set; otherwise hidden in v1 +- "Contact" — Slack DM link if `slackHandle` is set; email shown for self + staff (per the Authorization table below); section hidden when both are absent. - "Member since" date - For self: "Manage account" link to `/account` (settings spec deferred; covered in account.md when written) - For staff: "Audit log" link (deferred)