diff --git a/apps/api/src/services/blog-post.ts b/apps/api/src/services/blog-post.ts index aedceed..62f032c 100644 --- a/apps/api/src/services/blog-post.ts +++ b/apps/api/src/services/blog-post.ts @@ -4,7 +4,7 @@ * Per specs/api/blog.md. Writes happen via PR to the data repo, not the * runtime — no mutation methods here. */ -import type { BlogPost } from '@cfp/shared/schemas'; +import type { BlogPost, Tag } from '@cfp/shared/schemas'; import type { InMemoryState } from '../store/memory/state.js'; import { serializeBlogPost, type BlogPostResponse } from './serializers/blog-post.js'; @@ -66,6 +66,15 @@ export class BlogPostService { #serialize(post: BlogPost): BlogPostResponse { const author = post.authorId ? (this.#state.people.get(post.authorId) ?? null) : null; - return serializeBlogPost(post, { author }); + return serializeBlogPost(post, { author, tags: this.#tagsFor(post.id) }); + } + + #tagsFor(postId: string): Tag[] { + const taIds = this.#state.tagAssignmentsByTaggable.get(postId) ?? new Set(); + return [...taIds] + .map((taId) => this.#state.tagAssignments.get(taId)) + .filter((ta): ta is NonNullable => ta?.taggableType === 'blog_post') + .map((ta) => this.#state.tags.get(ta.tagId)) + .filter((t): t is Tag => t !== undefined); } } diff --git a/apps/api/src/services/serializers/blog-post.ts b/apps/api/src/services/serializers/blog-post.ts index 5b5747e..e4de21d 100644 --- a/apps/api/src/services/serializers/blog-post.ts +++ b/apps/api/src/services/serializers/blog-post.ts @@ -1,8 +1,14 @@ /** * BlogPost serializer. */ -import type { BlogPost, Person } from '@cfp/shared/schemas'; -import { renderMarkdown, serializePersonAvatar, type PersonAvatar } from './common.js'; +import type { BlogPost, Person, Tag } from '@cfp/shared/schemas'; +import { + renderMarkdown, + serializePersonAvatar, + serializeTagItem, + type PersonAvatar, + type TagItem, +} from './common.js'; export interface BlogPostResponse { readonly id: string; @@ -16,13 +22,14 @@ export interface BlogPostResponse { readonly featuredImageUrl: string | null; readonly body: string; readonly bodyHtml: string; + readonly tags: TagItem[]; readonly createdAt: string; readonly updatedAt: string; } export function serializeBlogPost( post: BlogPost, - opts: { author: Person | null }, + opts: { author: Person | null; tags?: Tag[] }, ): BlogPostResponse { return { id: post.id, @@ -38,6 +45,7 @@ export function serializeBlogPost( : null, body: post.body, bodyHtml: renderMarkdown(post.body).html, + tags: (opts.tags ?? []).map(serializeTagItem), createdAt: post.createdAt, updatedAt: post.updatedAt, }; diff --git a/apps/api/src/services/serializers/project.ts b/apps/api/src/services/serializers/project.ts index dd996fb..2fbe21a 100644 --- a/apps/api/src/services/serializers/project.ts +++ b/apps/api/src/services/serializers/project.ts @@ -76,6 +76,7 @@ export interface ProjectDetail { readonly featured: boolean; readonly createdAt: string; readonly updatedAt: string; + readonly deletedAt: string | null; } export interface HelpWantedRoleSummary { @@ -243,5 +244,6 @@ export function serializeProjectDetail( featured: project.featured, createdAt: project.createdAt, updatedAt: project.updatedAt, + deletedAt: project.deletedAt ?? null, }; } diff --git a/apps/web/src/components/AppHeader.tsx b/apps/web/src/components/AppHeader.tsx index 5a8d1f6..c0d6516 100644 --- a/apps/web/src/components/AppHeader.tsx +++ b/apps/web/src/components/AppHeader.tsx @@ -197,7 +197,7 @@ export function AppHeader() { Code for Philly diff --git a/apps/web/src/lib/api.ts b/apps/web/src/lib/api.ts index 3a7bcb9..94130a3 100644 --- a/apps/web/src/lib/api.ts +++ b/apps/web/src/lib/api.ts @@ -175,6 +175,7 @@ export interface ProjectDetail { readonly featured: boolean; readonly createdAt: string; readonly updatedAt: string; + readonly deletedAt: string | null; } export interface PersonListItem { @@ -296,6 +297,7 @@ export interface BlogPostResponse { readonly featuredImageUrl: string | null; readonly body: string; readonly bodyHtml: string; + readonly tags: TagItem[]; readonly createdAt: string; readonly updatedAt: string; } @@ -591,6 +593,10 @@ export const api = { request(`/api/projects/${encodeURIComponent(slug)}`, { method: 'DELETE' }), restore: (slug: string): Promise> => request(`/api/projects/${encodeURIComponent(slug)}/restore`, { method: 'POST' }), + join: (slug: string): Promise => + request(`/api/projects/${encodeURIComponent(slug)}/members/join`, { method: 'POST' }), + leave: (slug: string): Promise => + request(`/api/projects/${encodeURIComponent(slug)}/members/leave`, { method: 'POST' }), changeMaintainer: (slug: string, personSlug: string): Promise> => request(`/api/projects/${encodeURIComponent(slug)}/change-maintainer`, { method: 'POST', diff --git a/apps/web/src/screens/BlogDetail.tsx b/apps/web/src/screens/BlogDetail.tsx index e7e2a12..8fcbb1c 100644 --- a/apps/web/src/screens/BlogDetail.tsx +++ b/apps/web/src/screens/BlogDetail.tsx @@ -68,6 +68,19 @@ export function BlogDetail() {