CodeForPhilly / CodeForPhilly/codeforphilly-ng
Revive blog posts as a content-typed gitsheets sheet (revises the deferred decision)
- Dominant language
- TypeScript
- Stars
- 1
- Forks
- 1
- Avg merge
- 5d 3h
- Merged PRs (30d)
- 9
Description
[specs/deferred.md](https://github.com/CodeForPhilly/codeforphilly-ng/blob/main/specs/deferred.md) currently says blog posts get replaced by "staff-authored markdown files in the code repo at `apps/web/src/content/blog/.md`, shipped via PR." That decision predates [gitsheets v1.2](https://github.com/JarvusInnovations/gitsheets/releases/tag/v1.2.0)'s content-typed records.
With v1.2 we can give blog posts their own gitsheets sheet — markdown bodies + TOML frontmatter — and get a better outcome than files-in-code-repo:
## Why this beats the original deferral
| Concern | Files-in-code-repo | Content-typed sheet |
|---|---|---|
| PR-reviewable | ✅ | ✅ |
| Publish cadence | Tied to web deploys | Immediate on data-repo merge |
| Tags / cross-links | Ad-hoc frontmatter | Native `TagAssignment` |
| Author attribution | Hand-stamp in frontmatter | Native `Person` reference |
| Snapshot inclusion | Not in data snapshot | In the snapshot (pseudonymized) |
| API serving | Bespoke Vite handler | Existing read API pipeline |
| `/blog` index cost | Bundle every post into web build | `queryAll({ withBody: false })` |
| laddr-import revival | Out of scope | Resurrect `blog_posts` table on the existing one-shot import |
## Sheet shape
```toml
# .gitsheets/blog-posts.toml
[gitsheet]
root = 'blog-posts'
path = '${{ slug }}'
[gitsheet.format]
type = 'markdown'
body = 'body'
[gitsheet.schema]
$ref = './schemas/BlogPost.schema.json'
```
`BlogPost` entity (in `packages/shared/src/schemas/blog-post.ts`):
- `id` UUIDv7
- `legacyId` (laddr's `BlogPost.ID`, for the importer's idempotence)
- `slug` (kebab-case, slug-handle conventions)
- `title`
- `summary` (short markdown — stays in frontmatter)
- `authorId` → Person
- `postedAt` (iso8601)
- `editedAt` nullable
- `featuredImageKey` nullable (attachment via gitsheets)
- `deletedAt` nullable (soft-delete)
- `body` (the markdown body — the designated content field)
- standard `createdAt` / `updatedAt`
## Routing
Add to the SPA:
- `/blog` — index (paginated, optional tag filter)
- `/blog/:slug` — detail
- `/blog/tag/:namespace/:slug` — tag-filtered (reuse `TagsNamespace` pattern)
API:
- `GET /api/blog-posts` (list with facets, q, sort, page)
- `GET /api/blog-posts/:slug` (detail)
- `POST/PATCH/DELETE` — staff-only (per the original spec, blog wasn't a per-user-role CMS)
## laddr-import revival
The existing one-shot importer at `apps/api/scripts/import-laddr.ts` currently skips `blog_posts`. Re-add it as another translator in [`apps/api/scripts/import-laddr/translators.ts`](https://github.com/CodeForPhilly/codeforphilly-ng/blob/main/apps/api/scripts/import-laddr/translators.ts):
- Map `BlogPost.Slug` → `slug` (slugify-with-dedupe if invalid)
- Map `BlogPost.Title` → `title`
- Map `BlogPost.Body` → `body`
- Map `BlogPost.AuthorID` → resolve via the existing `idMaps.personByLegacy`
- Map `BlogPost.Published` (and similar) → `postedAt`
- Preserve `legacyId` so re-runs are idempotent
## Sequencing
- Depends on [#44](https://github.com/CodeForPhilly/codeforphilly-ng/issues/44) (content-typed gitsheets is the substrate) — or stand on its own as the first content-typed sheet in the project. Either order works since blog-posts is a brand-new sheet that doesn't conflict with the existing TOML-only ones.
- Sequenced after `cutover-prep` so existing migration paths stay valid through cutover.
## Spec updates needed
- `specs/deferred.md` — update the "Blog (`/blog`) as a user-facing CMS" entry from "files in code repo" to "content-typed sheet, see this issue."
- New spec files: `specs/api/blog.md`, `specs/screens/blog-index.md`, `specs/screens/blog-detail.md`.
- `specs/data-model.md` — add `BlogPost` entity.
- `specs/behaviors/legacy-id-mapping.md` — note the new `BlogPost.legacyId` axis.
Out of scope: comments, reactions, the multi-author "posts under a topic" workflow — keep it as simple as the original deferral imagined.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.