222 lines
6.1 KiB
TypeScript
222 lines
6.1 KiB
TypeScript
import {
|
|
projects,
|
|
publicRecords,
|
|
releases,
|
|
type Project,
|
|
type ProjectActivity,
|
|
type ProjectDecision,
|
|
type PublicRecord,
|
|
type RecordKind,
|
|
type Release,
|
|
type HomeFocusItem,
|
|
} from "./public-content.ts";
|
|
import type { PublicContentQueries } from "../../application/ports/public-content-queries.ts";
|
|
|
|
export type RecordFilters = {
|
|
kind?: RecordKind;
|
|
topic?: string;
|
|
project?: string;
|
|
openQuestionsOnly?: boolean;
|
|
};
|
|
|
|
export type SearchablePublicEntity = {
|
|
contentType: RecordKind | "PROJECT" | "RELEASE";
|
|
title: string;
|
|
summary: string;
|
|
path: string;
|
|
topic?: string;
|
|
topics?: ReadonlyArray<string>;
|
|
project?: string;
|
|
publishedAt?: string;
|
|
};
|
|
|
|
function comparePublishedAt(
|
|
left: Pick<PublicRecord, "publishedAt">,
|
|
right: Pick<PublicRecord, "publishedAt">,
|
|
) {
|
|
return right.publishedAt.localeCompare(left.publishedAt);
|
|
}
|
|
|
|
export function listRecords(filters: RecordFilters = {}): PublicRecord[] {
|
|
const hasTopicFilter = filters.topic !== undefined;
|
|
const hasProjectFilter = filters.project !== undefined;
|
|
const requestedTopic = filters.topic?.trim().toLocaleLowerCase("ko-KR");
|
|
const requestedProject = filters.project
|
|
?.trim()
|
|
.toLocaleLowerCase("ko-KR");
|
|
|
|
return publicRecords
|
|
.filter((record) => !filters.kind || record.kind === filters.kind)
|
|
.filter(
|
|
(record) =>
|
|
!hasTopicFilter ||
|
|
record.topic.toLocaleLowerCase("ko-KR") === requestedTopic,
|
|
)
|
|
.filter(
|
|
(record) =>
|
|
!hasProjectFilter ||
|
|
record.projectSlug.toLocaleLowerCase("ko-KR") === requestedProject ||
|
|
record.projectTitle.toLocaleLowerCase("ko-KR") === requestedProject,
|
|
)
|
|
.filter(
|
|
(record) =>
|
|
!filters.openQuestionsOnly ||
|
|
(record.kind === "QUESTION" &&
|
|
record.questionStatus !== "RESOLVED" &&
|
|
record.questionStatus !== "ARCHIVED"),
|
|
)
|
|
.sort(comparePublishedAt);
|
|
}
|
|
|
|
export function getRecord<K extends RecordKind>(
|
|
kind: K,
|
|
slug: string,
|
|
): Extract<PublicRecord, { kind: K }> | undefined {
|
|
return publicRecords.find(
|
|
(record) => record.kind === kind && record.slug === slug,
|
|
) as Extract<PublicRecord, { kind: K }> | undefined;
|
|
}
|
|
|
|
export function getProject(slug: string): Project | undefined {
|
|
return projects.find((project) => project.slug === slug);
|
|
}
|
|
|
|
export function getRelease(version: string): Release | undefined {
|
|
return releases.find((release) => release.version === version);
|
|
}
|
|
|
|
export function getProjectRecords(projectSlug: string): PublicRecord[] {
|
|
if (!getProject(projectSlug)) return [];
|
|
return listRecords({ project: projectSlug });
|
|
}
|
|
|
|
export function getProjectDecisions(projectSlug: string): ProjectDecision[] {
|
|
return [...(getProject(projectSlug)?.decisions ?? [])];
|
|
}
|
|
|
|
export function getProjectActivity(projectSlug: string): ProjectActivity[] {
|
|
return [...(getProject(projectSlug)?.activity ?? [])];
|
|
}
|
|
|
|
export function getHomeFocusItems(): HomeFocusItem[] {
|
|
const project = getProject("backend-skeleton");
|
|
const question = getRecord("QUESTION", "validate-edge-token-again");
|
|
const decision = getProjectDecisions("backend-skeleton").find(
|
|
(item) => item.id === "storage-port-unification",
|
|
);
|
|
|
|
if (!project || !question || !decision) {
|
|
throw new Error("Missing canonical entity required for the public home focus");
|
|
}
|
|
|
|
return [
|
|
{
|
|
key: "current",
|
|
label: "현재 작업",
|
|
title: project.title,
|
|
summary: project.summary,
|
|
details: [
|
|
{ label: "단계", value: project.stage },
|
|
{ label: "현재 목표", value: project.currentGoal },
|
|
{ label: "다음 작업", value: project.nextStep },
|
|
],
|
|
targetPath: `/projects/${project.slug}`,
|
|
},
|
|
{
|
|
key: "question",
|
|
label: "열린 질문",
|
|
title: question.title,
|
|
summary: question.summary,
|
|
details: [
|
|
{ label: "확인한 사실", value: question.facts[0] ?? "" },
|
|
{ label: "남은 미지수", value: question.unknowns[0] ?? "" },
|
|
{ label: "다음 검증", value: question.nextValidation },
|
|
],
|
|
targetPath: question.path,
|
|
},
|
|
{
|
|
key: "decision",
|
|
label: "최근 결정",
|
|
title: decision.statement,
|
|
summary: decision.rationale,
|
|
details: [
|
|
{ label: "영향", value: decision.consequences[0] ?? "" },
|
|
{ label: "근거", value: decision.evidence[0]?.title ?? "" },
|
|
],
|
|
targetPath: `/projects/${project.slug}/decisions#${decision.id}`,
|
|
},
|
|
];
|
|
}
|
|
|
|
function recordSearchEntity(record: PublicRecord): SearchablePublicEntity {
|
|
return {
|
|
contentType: record.kind,
|
|
title: record.title,
|
|
summary: record.summary,
|
|
path: record.path,
|
|
topic: record.topic,
|
|
project: record.projectTitle,
|
|
publishedAt: record.publishedAt,
|
|
};
|
|
}
|
|
|
|
function projectSearchEntity(project: Project): SearchablePublicEntity {
|
|
return {
|
|
contentType: "PROJECT",
|
|
title: project.title,
|
|
summary: project.summary,
|
|
path: `/projects/${project.slug}`,
|
|
topics: project.topics,
|
|
project: project.title,
|
|
};
|
|
}
|
|
|
|
function releaseSearchEntity(release: Release): SearchablePublicEntity {
|
|
return {
|
|
contentType: "RELEASE",
|
|
title: release.title,
|
|
summary: release.summary,
|
|
path: release.path,
|
|
topic: "TechLog",
|
|
project: "TechLog",
|
|
publishedAt: release.publishedAt,
|
|
};
|
|
}
|
|
|
|
export function searchPublicContent(query: string): SearchablePublicEntity[] {
|
|
const normalizedQuery = query.trim().toLocaleLowerCase("ko-KR");
|
|
const entities = [
|
|
...publicRecords.map(recordSearchEntity),
|
|
...projects.map(projectSearchEntity),
|
|
...releases.map(releaseSearchEntity),
|
|
];
|
|
|
|
if (!normalizedQuery) return entities;
|
|
|
|
return entities.filter((entity) =>
|
|
[
|
|
entity.title,
|
|
entity.summary,
|
|
entity.topic,
|
|
entity.project,
|
|
...(entity.topics ?? []),
|
|
]
|
|
.filter((value): value is string => Boolean(value))
|
|
.some((value) =>
|
|
value.toLocaleLowerCase("ko-KR").includes(normalizedQuery),
|
|
),
|
|
);
|
|
}
|
|
|
|
export const publicContentQueries = Object.freeze({
|
|
listRecords,
|
|
getRecord,
|
|
getProject,
|
|
getRelease,
|
|
getProjectRecords,
|
|
getProjectDecisions,
|
|
getProjectActivity,
|
|
getHomeFocusItems,
|
|
searchPublicContent,
|
|
}) satisfies PublicContentQueries;
|