@spfn/cms 0.1.0-alpha.76 → 0.1.0-alpha.78

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/api.d.ts +2 -2
  2. package/dist/client.d.ts +0 -1
  3. package/dist/index.d.ts +0 -1
  4. package/dist/server/entities/index.d.ts +0 -1
  5. package/dist/server/entities/index.js +36 -69
  6. package/dist/server/entities/index.js.map +1 -1
  7. package/dist/server/generators/index.js +38 -70
  8. package/dist/server/generators/index.js.map +1 -1
  9. package/dist/server/repositories/index.js +36 -68
  10. package/dist/server/repositories/index.js.map +1 -1
  11. package/dist/server/routes/labels/[id]/index.js +36 -68
  12. package/dist/server/routes/labels/[id]/index.js.map +1 -1
  13. package/dist/server/routes/labels/[labelId]/admin/index.js +36 -68
  14. package/dist/server/routes/labels/[labelId]/admin/index.js.map +1 -1
  15. package/dist/server/routes/labels/[labelId]/publish/index.js +36 -68
  16. package/dist/server/routes/labels/[labelId]/publish/index.js.map +1 -1
  17. package/dist/server/routes/labels/[labelId]/versions/index.js +58 -164
  18. package/dist/server/routes/labels/[labelId]/versions/index.js.map +1 -1
  19. package/dist/server/routes/labels/by-key/[key]/index.js +36 -68
  20. package/dist/server/routes/labels/by-key/[key]/index.js.map +1 -1
  21. package/dist/server/routes/labels/index.js +36 -68
  22. package/dist/server/routes/labels/index.js.map +1 -1
  23. package/dist/server/routes/published-cache/index.js +36 -68
  24. package/dist/server/routes/published-cache/index.js.map +1 -1
  25. package/dist/server/routes/values/[labelId]/[version]/index.js +36 -68
  26. package/dist/server/routes/values/[labelId]/[version]/index.js.map +1 -1
  27. package/dist/server/routes/values/[labelId]/index.js +36 -68
  28. package/dist/server/routes/values/[labelId]/index.js.map +1 -1
  29. package/dist/server.d.ts +0 -1
  30. package/dist/server.js +46 -73
  31. package/dist/server.js.map +1 -1
  32. package/migrations/0003_rare_runaways.sql +1 -0
  33. package/migrations/meta/0003_snapshot.json +563 -0
  34. package/migrations/meta/_journal.json +7 -0
  35. package/package.json +1 -1
  36. package/dist/server/entities/cms-label-versions.d.ts +0 -174
  37. package/dist/server/entities/cms-label-versions.js +0 -76
  38. package/dist/server/entities/cms-label-versions.js.map +0 -1
@@ -81,53 +81,21 @@ var cmsLabelValues = schema2.table("label_values", {
81
81
  index2("cms_label_values_locale_idx").on(table.locale)
82
82
  ]);
83
83
 
84
- // src/server/entities/cms-label-versions.ts
85
- import { serial as serial3, integer as integer3, text as text3, timestamp as timestamp3, index as index3, unique as unique2 } from "drizzle-orm/pg-core";
84
+ // src/server/entities/cms-draft-cache.ts
85
+ import { serial as serial3, text as text3, jsonb as jsonb2, timestamp as timestamp3, index as index3, unique as unique2 } from "drizzle-orm/pg-core";
86
86
  import { createFunctionSchema as createFunctionSchema3 } from "@spfn/core/db";
87
87
  var schema3 = createFunctionSchema3("@spfn/cms");
88
- var cmsLabelVersions = schema3.table("label_versions", {
88
+ var cmsDraftCache = schema3.table("draft_cache", {
89
89
  // Primary Key
90
90
  id: serial3("id").primaryKey(),
91
- // Foreign Key: cms_labels
92
- labelId: integer3("label_id").notNull().references(() => cmsLabels.id, { onDelete: "cascade" }),
93
- // 버전 번호
94
- version: integer3("version").notNull(),
95
- // 버전 상태
96
- status: text3("status").notNull(),
97
- // "draft" | "published" | "archived"
98
- // 발행 정보
99
- publishedAt: timestamp3("published_at", { withTimezone: true }),
100
- publishedBy: text3("published_by"),
101
- // 버전 노트 (변경사항 설명)
102
- notes: text3("notes"),
103
- // 버전 생성자
104
- createdBy: text3("created_by"),
105
- // 생성 시각
106
- createdAt: timestamp3("created_at", { withTimezone: true }).notNull().defaultNow()
107
- }, (table) => [
108
- // UNIQUE 제약: 각 라벨의 버전 번호는 고유
109
- unique2("cms_label_versions_label_version_unique").on(table.labelId, table.version),
110
- // 인덱스: labelId로 버전 목록 조회 최적화
111
- index3("cms_label_versions_label_id_idx").on(table.labelId),
112
- // 인덱스: status 필터링 최적화
113
- index3("cms_label_versions_status_idx").on(table.status)
114
- ]);
115
-
116
- // src/server/entities/cms-draft-cache.ts
117
- import { serial as serial4, text as text4, jsonb as jsonb2, timestamp as timestamp4, index as index4, unique as unique3 } from "drizzle-orm/pg-core";
118
- import { createFunctionSchema as createFunctionSchema4 } from "@spfn/core/db";
119
- var schema4 = createFunctionSchema4("@spfn/cms");
120
- var cmsDraftCache = schema4.table("draft_cache", {
121
- // Primary Key
122
- id: serial4("id").primaryKey(),
123
91
  // 섹션 (페이지 단위)
124
- section: text4("section").notNull(),
92
+ section: text3("section").notNull(),
125
93
  // "home" | "why-futureplay" | "team" | "our-companies" | "apply"
126
94
  // 언어
127
- locale: text4("locale").notNull(),
95
+ locale: text3("locale").notNull(),
128
96
  // "ko" | "en" | "ja"
129
97
  // 사용자 ID (핵심 필드!)
130
- userId: text4("user_id").notNull(),
98
+ userId: text3("user_id").notNull(),
131
99
  // 각 관리자의 독립적인 작업 공간
132
100
  // Draft 콘텐츠 (JSONB)
133
101
  content: jsonb2("content").notNull(),
@@ -138,28 +106,28 @@ var cmsDraftCache = schema4.table("draft_cache", {
138
106
  // ...
139
107
  // }
140
108
  // 최종 수정 시각
141
- updatedAt: timestamp4("updated_at", { withTimezone: true }).notNull().defaultNow()
109
+ updatedAt: timestamp3("updated_at", { withTimezone: true }).notNull().defaultNow()
142
110
  }, (table) => [
143
111
  // UNIQUE 제약: section + locale + userId 조합은 유일
144
- unique3("cms_draft_cache_unique").on(table.section, table.locale, table.userId),
112
+ unique2("cms_draft_cache_unique").on(table.section, table.locale, table.userId),
145
113
  // 인덱스: section으로 조회 최적화
146
- index4("cms_draft_cache_section_idx").on(table.section),
114
+ index3("cms_draft_cache_section_idx").on(table.section),
147
115
  // 인덱스: userId로 사용자의 모든 draft 조회 최적화
148
- index4("cms_draft_cache_user_idx").on(table.userId)
116
+ index3("cms_draft_cache_user_idx").on(table.userId)
149
117
  ]);
150
118
 
151
119
  // src/server/entities/cms-published-cache.ts
152
- import { serial as serial5, text as text5, jsonb as jsonb3, integer as integer4, timestamp as timestamp5, index as index5, unique as unique4 } from "drizzle-orm/pg-core";
153
- import { createFunctionSchema as createFunctionSchema5 } from "@spfn/core/db";
154
- var schema5 = createFunctionSchema5("@spfn/cms");
155
- var cmsPublishedCache = schema5.table("published_cache", {
120
+ import { serial as serial4, text as text4, jsonb as jsonb3, integer as integer3, timestamp as timestamp4, index as index4, unique as unique3 } from "drizzle-orm/pg-core";
121
+ import { createFunctionSchema as createFunctionSchema4 } from "@spfn/core/db";
122
+ var schema4 = createFunctionSchema4("@spfn/cms");
123
+ var cmsPublishedCache = schema4.table("published_cache", {
156
124
  // Primary Key
157
- id: serial5("id").primaryKey(),
125
+ id: serial4("id").primaryKey(),
158
126
  // 섹션 (페이지 단위)
159
- section: text5("section").notNull(),
127
+ section: text4("section").notNull(),
160
128
  // "home" | "why-futureplay" | "team" | "our-companies" | "apply"
161
129
  // 언어
162
- locale: text5("locale").notNull(),
130
+ locale: text4("locale").notNull(),
163
131
  // "ko" | "en" | "ja"
164
132
  // 캐시된 콘텐츠 (JSONB)
165
133
  content: jsonb3("content").notNull(),
@@ -170,32 +138,32 @@ var cmsPublishedCache = schema5.table("published_cache", {
170
138
  // ...
171
139
  // }
172
140
  // 발행 정보
173
- publishedAt: timestamp5("published_at", { withTimezone: true }).notNull(),
174
- publishedBy: text5("published_by"),
141
+ publishedAt: timestamp4("published_at", { withTimezone: true }).notNull(),
142
+ publishedBy: text4("published_by"),
175
143
  // 캐시 버전 (클라이언트 캐싱용)
176
- version: integer4("version").notNull().default(1)
144
+ version: integer3("version").notNull().default(1)
177
145
  }, (table) => [
178
146
  // UNIQUE 제약: section + locale 조합은 유일
179
- unique4("cms_published_cache_unique").on(table.section, table.locale),
147
+ unique3("cms_published_cache_unique").on(table.section, table.locale),
180
148
  // 인덱스: section으로 조회 최적화
181
- index5("cms_published_cache_section_idx").on(table.section)
149
+ index4("cms_published_cache_section_idx").on(table.section)
182
150
  ]);
183
151
 
184
152
  // src/server/entities/cms-audit-logs.ts
185
- import { serial as serial6, integer as integer5, text as text6, jsonb as jsonb4, timestamp as timestamp6, index as index6 } from "drizzle-orm/pg-core";
186
- import { createFunctionSchema as createFunctionSchema6 } from "@spfn/core/db";
187
- var schema6 = createFunctionSchema6("@spfn/cms");
188
- var cmsAuditLogs = schema6.table("audit_logs", {
153
+ import { serial as serial5, integer as integer4, text as text5, jsonb as jsonb4, timestamp as timestamp5, index as index5 } from "drizzle-orm/pg-core";
154
+ import { createFunctionSchema as createFunctionSchema5 } from "@spfn/core/db";
155
+ var schema5 = createFunctionSchema5("@spfn/cms");
156
+ var cmsAuditLogs = schema5.table("audit_logs", {
189
157
  // Primary Key
190
- id: serial6("id").primaryKey(),
158
+ id: serial5("id").primaryKey(),
191
159
  // Foreign Key: cms_labels (nullable - 라벨 삭제 시 로그는 유지)
192
- labelId: integer5("label_id").references(() => cmsLabels.id, { onDelete: "set null" }),
160
+ labelId: integer4("label_id").references(() => cmsLabels.id, { onDelete: "set null" }),
193
161
  // 작업 유형
194
- action: text6("action").notNull(),
162
+ action: text5("action").notNull(),
195
163
  // "create" | "update" | "publish" | "unpublish" | "archive" | "delete" | "rollback" | "duplicate"
196
164
  // 사용자 정보
197
- userId: text6("user_id").notNull(),
198
- userName: text6("user_name"),
165
+ userId: text5("user_id").notNull(),
166
+ userName: text5("user_name"),
199
167
  // 변경 내용 (before/after)
200
168
  changes: jsonb4("changes"),
201
169
  // { before: {...}, after: {...} }
@@ -203,16 +171,16 @@ var cmsAuditLogs = schema6.table("audit_logs", {
203
171
  metadata: jsonb4("metadata"),
204
172
  // { version: number, ip: string, userAgent: string, ... }
205
173
  // 작업 시각
206
- createdAt: timestamp6("created_at", { withTimezone: true }).notNull().defaultNow()
174
+ createdAt: timestamp5("created_at", { withTimezone: true }).notNull().defaultNow()
207
175
  }, (table) => [
208
176
  // 인덱스: labelId로 이력 조회 최적화
209
- index6("cms_audit_logs_label_id_idx").on(table.labelId),
177
+ index5("cms_audit_logs_label_id_idx").on(table.labelId),
210
178
  // 인덱스: userId로 사용자 활동 조회 최적화
211
- index6("cms_audit_logs_user_id_idx").on(table.userId),
179
+ index5("cms_audit_logs_user_id_idx").on(table.userId),
212
180
  // 인덱스: action 필터링 최적화
213
- index6("cms_audit_logs_action_idx").on(table.action),
181
+ index5("cms_audit_logs_action_idx").on(table.action),
214
182
  // 인덱스: 시간순 조회 최적화
215
- index6("cms_audit_logs_created_at_idx").on(table.createdAt)
183
+ index5("cms_audit_logs_created_at_idx").on(table.createdAt)
216
184
  ]);
217
185
 
218
186
  // src/server/repositories/cms-labels.repository.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/server/routes/labels/index.ts","../../../../src/server/repositories/cms-labels.repository.ts","../../../../src/server/entities/cms-labels.ts","../../../../src/server/entities/cms-label-values.ts","../../../../src/server/entities/cms-label-versions.ts","../../../../src/server/entities/cms-draft-cache.ts","../../../../src/server/entities/cms-published-cache.ts","../../../../src/server/entities/cms-audit-logs.ts","../../../../src/server/repositories/cms-label-values.repository.ts","../../../../src/server/repositories/cms-draft-cache.repository.ts","../../../../src/server/repositories/cms-published-cache.repository.ts","../../../../src/lib/contracts/labels.ts","../../../../src/server/helpers/sync.ts","../../../../src/server/labels/helpers.ts","../../../../src/lib/constants/index.ts"],"sourcesContent":["/**\n * CMS Labels Routes\n *\n * 라벨 메타데이터 관리 API\n * - GET /labels - 라벨 목록 조회 (final: /_cms/labels)\n * - POST /labels - 새 라벨 생성 (final: /_cms/labels)\n */\n\nimport { createApp } from '@spfn/core/route';\nimport { cmsLabelsRepository } from '@/server/repositories';\nimport { getLabelsContract, createLabelContract } from '@/lib/contracts/labels';\nimport { loadLabelsFromJson } from '@/server/helpers/sync';\nimport { extractLabels } from '@/server/labels';\nimport { DEFAULT_LABELS_DIR } from '@/lib/constants';\nimport { join } from 'path';\n\nconst app = createApp();\n\n/**\n * GET /labels\n * 라벨 목록 조회 (페이지네이션, 섹션 필터)\n */\napp.bind(getLabelsContract, async (c) =>\n{\n const { section, limit = 20, offset = 0, includeDefaultValues } = c.query;\n\n // 라벨 목록 조회\n const labels = await cmsLabelsRepository.findMany({\n section,\n limit,\n offset,\n });\n\n // 전체 개수 조회\n const total = await cmsLabelsRepository.count(section);\n\n // includeDefaultValues가 true이면 라벨 정의에서 기본값 로드\n let defaultValuesMap: Record<string, any> = {};\n if (includeDefaultValues && section)\n {\n try\n {\n const labelsDir = join(process.cwd(), DEFAULT_LABELS_DIR);\n const sections = loadLabelsFromJson(labelsDir);\n const sectionDef = sections.find(s => s.section === section);\n\n if (sectionDef)\n {\n const extracted = extractLabels(sectionDef);\n defaultValuesMap = extracted.reduce((acc, label) => {\n acc[label.key] = label.defaultValue;\n return acc;\n }, {} as Record<string, any>);\n }\n }\n catch (error)\n {\n console.warn('[getLabels] Failed to load default values:', error);\n }\n }\n\n return c.json({\n labels: labels.map((label) => ({\n id: label.id,\n key: label.key,\n section: label.section,\n type: label.type,\n description: label.description,\n publishedVersion: label.publishedVersion,\n createdBy: label.createdBy,\n createdAt: label.createdAt.toISOString(),\n updatedAt: label.updatedAt.toISOString(),\n ...(includeDefaultValues && { defaultValue: defaultValuesMap[label.key] })\n })),\n total,\n limit,\n offset,\n });\n});\n\n/**\n * POST /labels\n * 새 라벨 생성\n */\napp.bind(createLabelContract, async (c) =>\n{\n const body = await c.data();\n\n // 중복 key 체크\n const existing = await cmsLabelsRepository.findByKey(body.key);\n if (existing)\n {\n return c.json(\n { error: 'Label with this key already exists', key: body.key },\n 409\n );\n }\n\n // 라벨 생성\n const label = await cmsLabelsRepository.create({\n key: body.key,\n section: body.section,\n type: body.type,\n createdBy: body.createdBy,\n });\n\n return c.json(\n {\n id: label.id,\n key: label.key,\n section: label.section,\n type: label.type,\n publishedVersion: label.publishedVersion,\n createdBy: label.createdBy,\n createdAt: label.createdAt.toISOString(),\n updatedAt: label.updatedAt.toISOString(),\n },\n 201\n );\n});\n\nexport default app;\n","/**\n * CMS Labels Repository\n *\n * 라벨 메타데이터 관리를 위한 Repository\n */\n\nimport { findOne, findMany as findManyHelper, create as createHelper, updateOne, deleteOne, count as countHelper } from '@spfn/core/db';\nimport { asc } from 'drizzle-orm';\nimport { cmsLabels, type CmsLabel, type NewCmsLabel } from '@/server/entities';\n\n/**\n * 라벨 목록 조회 (페이지네이션)\n */\nexport async function findMany(options?: {\n section?: string;\n limit?: number;\n offset?: number;\n}): Promise<CmsLabel[]>\n{\n const { section, limit = 20, offset = 0 } = options || {};\n\n return findManyHelper(cmsLabels, {\n where: section ? { section } : undefined,\n orderBy: asc(cmsLabels.key), // key 오름차순 정렬 (JSON 파일의 순서 유지)\n limit,\n offset\n });\n}\n\n/**\n * 전체 라벨 수 조회\n */\nexport async function count(section?: string): Promise<number>\n{\n return countHelper(cmsLabels, section ? { section } : undefined);\n}\n\n/**\n * ID로 라벨 조회\n */\nexport async function findById(id: number): Promise<CmsLabel | null>\n{\n return findOne(cmsLabels, { id });\n}\n\n/**\n * Key로 라벨 조회\n */\nexport async function findByKey(key: string): Promise<CmsLabel | null>\n{\n return findOne(cmsLabels, { key });\n}\n\n/**\n * 섹션으로 모든 라벨 조회\n */\nexport async function findBySection(section: string): Promise<CmsLabel[]>\n{\n return findManyHelper(cmsLabels, {\n where: { section },\n orderBy: asc(cmsLabels.key), // key 오름차순 정렬 (JSON 파일의 순서 유지)\n });\n}\n\n/**\n * 라벨 생성\n */\nexport async function create(data: NewCmsLabel): Promise<CmsLabel>\n{\n return createHelper(cmsLabels, data);\n}\n\n/**\n * 라벨 수정\n */\nexport async function updateById(id: number, data: Partial<NewCmsLabel>): Promise<CmsLabel | null>\n{\n return updateOne(cmsLabels, { id }, { ...data, updatedAt: new Date() });\n}\n\n/**\n * 라벨 삭제\n */\nexport async function deleteById(id: number): Promise<CmsLabel | null>\n{\n return deleteOne(cmsLabels, { id });\n}\n\n// Legacy export for backward compatibility\nexport const cmsLabelsRepository = {\n findMany,\n count,\n findById,\n findByKey,\n findBySection,\n create,\n updateById,\n deleteById\n};","/**\n * CMS Labels Entity\n *\n * 라벨의 메타데이터와 현재 발행 상태를 관리합니다.\n * - 라벨 식별 (id, key)\n * - 섹션 분류 (section)\n * - 타입 정의 (type)\n * - 발행 상태 (publishedVersion)\n */\n\nimport { index, integer, serial, text, timestamp } from 'drizzle-orm/pg-core';\nimport { createFunctionSchema } from '@spfn/core/db';\n\n// Create isolated schema for @spfn/cms\nconst schema = createFunctionSchema('@spfn/cms');\n\nexport const cmsLabels = schema.table('labels', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // 라벨 식별자\n key: text('key').notNull().unique(),\n // 예: \"home.hero.title\", \"why-futureplay.hero.subtitle\"\n // 구조: {section}.{component}.{property}\n\n // 섹션 분류 (페이지 단위)\n section: text('section').notNull(),\n // 예: \"home\", \"why-futureplay\", \"team\"\n\n // 값 타입\n type: text('type').notNull(),\n // \"text\" | \"image\" | \"video\" | \"file\" | \"object\"\n\n // 기본값\n defaultValue: text('default_value'),\n // 라벨의 기본값 (sync 시 설정)\n\n // 설명\n description: text('description'),\n // 라벨에 대한 설명 (optional)\n\n // 현재 발행된 버전 번호\n publishedVersion: integer('published_version'),\n // null = 미발행 상태\n // 1, 2, 3... = 발행된 버전 번호\n\n // 생성자 추적\n createdBy: text('created_by'),\n\n // 타임스탬프\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // 인덱스: 섹션별 조회 최적화\n index('cms_labels_section_idx').on(table.section),\n\n // 인덱스: key로 조회 최적화 (unique 제약으로 자동 생성되지만 명시)\n index('cms_labels_key_idx').on(table.key),\n]);\n\n// 타입 추론\nexport type CmsLabel = typeof cmsLabels.$inferSelect;\nexport type NewCmsLabel = typeof cmsLabels.$inferInsert;","/**\n * CMS Label Values Entity\n *\n * 라벨의 실제 값을 저장합니다.\n * - 다국어 지원 (locale)\n * - 반응형 지원 (breakpoint)\n * - 버전 관리 (version)\n * - JSONB로 유연한 값 저장\n */\n\nimport { serial, integer, text, jsonb, timestamp, index, unique } from 'drizzle-orm/pg-core';\nimport { createFunctionSchema } from '@spfn/core/db';\nimport { cmsLabels } from '@/server/entities/cms-labels';\n\n// Create isolated schema for @spfn/cms\nconst schema = createFunctionSchema('@spfn/cms');\n\nexport const cmsLabelValues = schema.table('label_values', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // Foreign Key: cms_labels\n labelId: integer('label_id')\n .notNull()\n .references(() => cmsLabels.id, { onDelete: 'cascade' }),\n\n // 버전 번호 (null = draft, number = published version)\n version: integer('version'),\n\n // 언어 코드\n locale: text('locale').notNull().default('ko'),\n // \"ko\" | \"en\" | \"ja\"\n\n // 반응형 브레이크포인트\n breakpoint: text('breakpoint'),\n // null = 기본값 (모든 화면 크기)\n // \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\"\n\n // 실제 값 (JSONB)\n value: jsonb('value').notNull(),\n // LabelValue 타입:\n // - TextValue: { type: \"text\", content: string }\n // - ImageValue: { type: \"image\", url: string, alt?: string, width?: number, height?: number }\n // - VideoValue: { type: \"video\", url: string, thumbnail?: string, duration?: number }\n // - FileValue: { type: \"file\", url: string, filename: string, size?: number }\n // - ObjectValue: { type: \"object\", fields: Record<string, LabelValue> }\n\n // 생성 시각\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // UNIQUE 제약: 같은 버전에서 locale + breakpoint 조합은 유일\n unique('cms_label_values_locale_breakpoint_unique')\n .on(table.labelId, table.version, table.locale, table.breakpoint),\n\n // 인덱스: labelId + version 복합 조회 최적화\n index('cms_label_values_label_version_idx')\n .on(table.labelId, table.version),\n\n // 인덱스: locale 필터링 최적화\n index('cms_label_values_locale_idx').on(table.locale),\n]);\n\n// 타입 추론\nexport type CmsLabelValue = typeof cmsLabelValues.$inferSelect;\nexport type NewCmsLabelValue = typeof cmsLabelValues.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // 텍스트 값 저장\n * await db.insert(cmsLabelValues).values({\n * labelId: 1,\n * version: 1,\n * locale: 'ko',\n * breakpoint: null,\n * value: {\n * type: 'text',\n * content: '미래를 만드는 기업'\n * }\n * });\n *\n * // 반응형 이미지 저장 (모바일용)\n * await db.insert(cmsLabelValues).values({\n * labelId: 2,\n * version: 1,\n * locale: 'ko',\n * breakpoint: 'sm',\n * value: {\n * type: 'image',\n * url: '/uploads/hero-mobile.jpg',\n * alt: 'Hero Image',\n * width: 640,\n * height: 480\n * }\n * });\n *\n * // 특정 버전의 한국어 값 조회\n * const values = await db.select()\n * .from(cmsLabelValues)\n * .where(and(\n * eq(cmsLabelValues.labelId, 1),\n * eq(cmsLabelValues.version, 2),\n * eq(cmsLabelValues.locale, 'ko')\n * ));\n *\n * // Object 타입 값 저장 (재귀 구조)\n * await db.insert(cmsLabelValues).values({\n * labelId: 3,\n * version: 1,\n * locale: 'ko',\n * value: {\n * type: 'object',\n * fields: {\n * title: { type: 'text', content: '특징 1' },\n * icon: { type: 'image', url: '/icons/feature1.svg', alt: 'Icon' },\n * description: { type: 'text', content: '상세 설명...' }\n * }\n * }\n * });\n */","/**\n * CMS Label Versions Entity\n *\n * 라벨의 버전 메타데이터를 관리합니다.\n * - 버전별 상태 (draft/published/archived)\n * - 발행 정보 (publishedAt, publishedBy)\n * - 버전 노트\n */\n\nimport { serial, integer, text, timestamp, index, unique } from 'drizzle-orm/pg-core';\nimport { createFunctionSchema } from '@spfn/core/db';\nimport { cmsLabels } from '@/server/entities/cms-labels';\n\n// Create isolated schema for @spfn/cms\nconst schema = createFunctionSchema('@spfn/cms');\n\nexport const cmsLabelVersions = schema.table('label_versions', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // Foreign Key: cms_labels\n labelId: integer('label_id')\n .notNull()\n .references(() => cmsLabels.id, { onDelete: 'cascade' }),\n\n // 버전 번호\n version: integer('version').notNull(),\n\n // 버전 상태\n status: text('status').notNull(),\n // \"draft\" | \"published\" | \"archived\"\n\n // 발행 정보\n publishedAt: timestamp('published_at', { withTimezone: true }),\n publishedBy: text('published_by'),\n\n // 버전 노트 (변경사항 설명)\n notes: text('notes'),\n\n // 버전 생성자\n createdBy: text('created_by'),\n\n // 생성 시각\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // UNIQUE 제약: 각 라벨의 버전 번호는 고유\n unique('cms_label_versions_label_version_unique')\n .on(table.labelId, table.version),\n\n // 인덱스: labelId로 버전 목록 조회 최적화\n index('cms_label_versions_label_id_idx').on(table.labelId),\n\n // 인덱스: status 필터링 최적화\n index('cms_label_versions_status_idx').on(table.status),\n]);\n\n// 타입 추론\nexport type CmsLabelVersion = typeof cmsLabelVersions.$inferSelect;\nexport type NewCmsLabelVersion = typeof cmsLabelVersions.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // 새 버전 생성 (draft)\n * await db.insert(cmsLabelVersions).values({\n * labelId: 1,\n * version: 2,\n * status: 'draft',\n * notes: '문구 개선',\n * createdBy: 'editor@futureplay.com'\n * });\n *\n * // 버전 발행\n * await db.update(cmsLabelVersions)\n * .set({\n * status: 'published',\n * publishedAt: new Date(),\n * publishedBy: 'admin@futureplay.com'\n * })\n * .where(and(\n * eq(cmsLabelVersions.labelId, 1),\n * eq(cmsLabelVersions.version, 2)\n * ));\n *\n * // 라벨의 모든 버전 조회\n * const versions = await db.select()\n * .from(cmsLabelVersions)\n * .where(eq(cmsLabelVersions.labelId, 1))\n * .orderBy(desc(cmsLabelVersions.version));\n *\n * // 발행된 버전만 조회\n * const published = await db.select()\n * .from(cmsLabelVersions)\n * .where(eq(cmsLabelVersions.status, 'published'))\n * .orderBy(desc(cmsLabelVersions.publishedAt));\n */","/**\n * CMS Draft Cache Entity\n *\n * 관리자별 Draft 콘텐츠를 캐싱합니다.\n * - 사용자별 격리 (userId)\n * - 실시간 미리보기 지원\n * - 동시 편집 가능\n *\n * 핵심 기능:\n * - 여러 관리자가 같은 섹션을 동시에 편집\n * - 각자의 변경사항은 자신의 미리보기에만 표시\n * - 충돌 없이 안전하게 작업\n */\n\nimport { serial, text, jsonb, timestamp, index, unique } from 'drizzle-orm/pg-core';\nimport { createFunctionSchema } from '@spfn/core/db';\n\n// Create isolated schema for @spfn/cms\nconst schema = createFunctionSchema('@spfn/cms');\n\nexport const cmsDraftCache = schema.table('draft_cache', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // 섹션 (페이지 단위)\n section: text('section').notNull(),\n // \"home\" | \"why-futureplay\" | \"team\" | \"our-companies\" | \"apply\"\n\n // 언어\n locale: text('locale').notNull(),\n // \"ko\" | \"en\" | \"ja\"\n\n // 사용자 ID (핵심 필드!)\n userId: text('user_id').notNull(),\n // 각 관리자의 독립적인 작업 공간\n\n // Draft 콘텐츠 (JSONB)\n content: jsonb('content').notNull(),\n // Record<string, LabelValue>\n // {\n // \"home.hero.title\": { type: \"text\", content: \"수정 중...\" },\n // \"home.hero.subtitle\": { type: \"text\", content: \"새로운 문구\" },\n // ...\n // }\n\n // 최종 수정 시각\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // UNIQUE 제약: section + locale + userId 조합은 유일\n unique('cms_draft_cache_unique')\n .on(table.section, table.locale, table.userId),\n\n // 인덱스: section으로 조회 최적화\n index('cms_draft_cache_section_idx').on(table.section),\n\n // 인덱스: userId로 사용자의 모든 draft 조회 최적화\n index('cms_draft_cache_user_idx').on(table.userId),\n]);\n\n// 타입 추론\nexport type CmsDraftCache = typeof cmsDraftCache.$inferSelect;\nexport type NewCmsDraftCache = typeof cmsDraftCache.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // Draft 초기화 (편집 시작)\n * await db.insert(cmsDraftCache)\n * .values({\n * section: 'home',\n * locale: 'ko',\n * userId: 'user-a@futureplay.com',\n * content: publishedContent // 발행 버전 복사\n * });\n *\n * // Draft 업데이트 (값 수정 시)\n * const cache = await db.select()\n * .from(cmsDraftCache)\n * .where(and(\n * eq(cmsDraftCache.section, 'home'),\n * eq(cmsDraftCache.locale, 'ko'),\n * eq(cmsDraftCache.userId, userId)\n * ))\n * .limit(1);\n *\n * const updatedContent = {\n * ...cache[0].content,\n * 'home.hero.title': newValue // 부분 업데이트\n * };\n *\n * await db.update(cmsDraftCache)\n * .set({ content: updatedContent, updatedAt: new Date() })\n * .where(eq(cmsDraftCache.id, cache[0].id));\n *\n * // Draft 조회 (미리보기)\n * const draft = await db.select()\n * .from(cmsDraftCache)\n * .where(and(\n * eq(cmsDraftCache.section, 'home'),\n * eq(cmsDraftCache.locale, 'ko'),\n * eq(cmsDraftCache.userId, session.user.id)\n * ))\n * .limit(1);\n *\n * // 사용자의 모든 작업 중인 섹션 조회\n * const userDrafts = await db.select()\n * .from(cmsDraftCache)\n * .where(eq(cmsDraftCache.userId, userId))\n * .orderBy(desc(cmsDraftCache.updatedAt));\n *\n * // 오래된 Draft 정리 (30일 이상)\n * const stale = await db.delete(cmsDraftCache)\n * .where(lt(\n * cmsDraftCache.updatedAt,\n * new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)\n * ))\n * .returning();\n *\n * // Draft 폐기 (변경사항 버리기)\n * await db.delete(cmsDraftCache)\n * .where(and(\n * eq(cmsDraftCache.section, 'home'),\n * eq(cmsDraftCache.locale, 'ko'),\n * eq(cmsDraftCache.userId, userId)\n * ));\n */","/**\n * CMS Published Cache Entity\n *\n * 발행된 콘텐츠를 섹션+언어 단위로 캐싱합니다.\n * - 초고속 읽기 성능 (5ms)\n * - 단일 쿼리로 섹션 전체 로드\n * - JSONB로 즉시 사용 가능한 데이터\n *\n * 성능 비교:\n * - 정규화 테이블 JOIN: 87ms\n * - 캐시 테이블: 5ms (17배 빠름!)\n */\n\nimport { serial, text, jsonb, integer, timestamp, index, unique } from 'drizzle-orm/pg-core';\nimport { createFunctionSchema } from '@spfn/core/db';\n\n// Create isolated schema for @spfn/cms\nconst schema = createFunctionSchema('@spfn/cms');\n\nexport const cmsPublishedCache = schema.table('published_cache', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // 섹션 (페이지 단위)\n section: text('section').notNull(),\n // \"home\" | \"why-futureplay\" | \"team\" | \"our-companies\" | \"apply\"\n\n // 언어\n locale: text('locale').notNull(),\n // \"ko\" | \"en\" | \"ja\"\n\n // 캐시된 콘텐츠 (JSONB)\n content: jsonb('content').notNull(),\n // Record<string, LabelValue>\n // {\n // \"home.hero.title\": { type: \"text\", content: \"...\" },\n // \"home.hero.image\": { type: \"image\", url: \"...\", alt: \"...\" },\n // ...\n // }\n\n // 발행 정보\n publishedAt: timestamp('published_at', { withTimezone: true }).notNull(),\n publishedBy: text('published_by'),\n\n // 캐시 버전 (클라이언트 캐싱용)\n version: integer('version').notNull().default(1),\n}, (table) => [\n // UNIQUE 제약: section + locale 조합은 유일\n unique('cms_published_cache_unique').on(table.section, table.locale),\n\n // 인덱스: section으로 조회 최적화\n index('cms_published_cache_section_idx').on(table.section),\n]);\n\n// 타입 추론\nexport type CmsPublishedCache = typeof cmsPublishedCache.$inferSelect;\nexport type NewCmsPublishedCache = typeof cmsPublishedCache.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // 캐시 생성/업데이트 (UPSERT)\n * await db.insert(cmsPublishedCache)\n * .values({\n * section: 'home',\n * locale: 'ko',\n * content: {\n * 'home.hero.title': {\n * type: 'text',\n * content: '미래를 만드는 기업'\n * },\n * 'home.hero.image': {\n * type: 'image',\n * url: '/uploads/hero.jpg',\n * alt: 'Hero',\n * width: 1920,\n * height: 1080\n * }\n * },\n * publishedAt: new Date(),\n * publishedBy: 'admin@futureplay.com'\n * })\n * .onConflictDoUpdate({\n * target: [cmsPublishedCache.section, cmsPublishedCache.locale],\n * set: {\n * content: sql`EXCLUDED.content`,\n * publishedAt: sql`EXCLUDED.published_at`,\n * publishedBy: sql`EXCLUDED.published_by`,\n * version: sql`${cmsPublishedCache.version} + 1`\n * }\n * });\n *\n * // 캐시 조회 (초고속!)\n * const cache = await db.select()\n * .from(cmsPublishedCache)\n * .where(and(\n * eq(cmsPublishedCache.section, 'home'),\n * eq(cmsPublishedCache.locale, 'ko')\n * ))\n * .limit(1);\n *\n * const labels = cache[0].content; // 즉시 사용 가능!\n *\n * // 섹션의 모든 언어 캐시 조회\n * const allLocales = await db.select()\n * .from(cmsPublishedCache)\n * .where(eq(cmsPublishedCache.section, 'home'));\n *\n * // 오래된 캐시 감지\n * const stale = await db.select()\n * .from(cmsPublishedCache)\n * .where(lt(\n * cmsPublishedCache.publishedAt,\n * new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)\n * ));\n */","/**\n * CMS Audit Logs Entity\n *\n * CMS의 모든 변경사항을 추적합니다.\n * - 누가 (userId, userName)\n * - 언제 (createdAt)\n * - 무엇을 (action, changes)\n * - 왜 (metadata)\n */\n\nimport { serial, integer, text, jsonb, timestamp, index } from 'drizzle-orm/pg-core';\nimport { createFunctionSchema } from '@spfn/core/db';\nimport { cmsLabels } from '@/server/entities/cms-labels';\n\n// Create isolated schema for @spfn/cms\nconst schema = createFunctionSchema('@spfn/cms');\n\nexport const cmsAuditLogs = schema.table('audit_logs', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // Foreign Key: cms_labels (nullable - 라벨 삭제 시 로그는 유지)\n labelId: integer('label_id')\n .references(() => cmsLabels.id, { onDelete: 'set null' }),\n\n // 작업 유형\n action: text('action').notNull(),\n // \"create\" | \"update\" | \"publish\" | \"unpublish\" | \"archive\" | \"delete\" | \"rollback\" | \"duplicate\"\n\n // 사용자 정보\n userId: text('user_id').notNull(),\n userName: text('user_name'),\n\n // 변경 내용 (before/after)\n changes: jsonb('changes'),\n // { before: {...}, after: {...} }\n\n // 추가 메타데이터\n metadata: jsonb('metadata'),\n // { version: number, ip: string, userAgent: string, ... }\n\n // 작업 시각\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // 인덱스: labelId로 이력 조회 최적화\n index('cms_audit_logs_label_id_idx').on(table.labelId),\n\n // 인덱스: userId로 사용자 활동 조회 최적화\n index('cms_audit_logs_user_id_idx').on(table.userId),\n\n // 인덱스: action 필터링 최적화\n index('cms_audit_logs_action_idx').on(table.action),\n\n // 인덱스: 시간순 조회 최적화\n index('cms_audit_logs_created_at_idx').on(table.createdAt),\n]);\n\n// 타입 추론\nexport type CmsAuditLog = typeof cmsAuditLogs.$inferSelect;\nexport type NewCmsAuditLog = typeof cmsAuditLogs.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // 라벨 생성 로그\n * await db.insert(cmsAuditLogs).values({\n * labelId: 1,\n * action: 'create',\n * userId: 'user123',\n * userName: '김철수',\n * changes: {\n * before: null,\n * after: {\n * key: 'home.hero.title',\n * section: 'home',\n * type: 'text'\n * }\n * },\n * metadata: {\n * ip: '192.168.1.1',\n * userAgent: 'Mozilla/5.0...'\n * }\n * });\n *\n * // 발행 로그\n * await db.insert(cmsAuditLogs).values({\n * labelId: 1,\n * action: 'publish',\n * userId: 'admin123',\n * userName: '관리자',\n * changes: {\n * before: { status: 'draft', publishedVersion: null },\n * after: { status: 'published', publishedVersion: 2 }\n * },\n * metadata: {\n * version: 2,\n * notes: '신규 브랜딩 적용'\n * }\n * });\n *\n * // 라벨별 이력 조회\n * const logs = await db.select()\n * .from(cmsAuditLogs)\n * .where(eq(cmsAuditLogs.labelId, 1))\n * .orderBy(desc(cmsAuditLogs.createdAt))\n * .limit(20);\n *\n * // 사용자별 활동 조회\n * const userActivity = await db.select()\n * .from(cmsAuditLogs)\n * .where(eq(cmsAuditLogs.userId, 'user123'))\n * .orderBy(desc(cmsAuditLogs.createdAt));\n *\n * // 최근 24시간 변경 이력\n * const recent = await db.select()\n * .from(cmsAuditLogs)\n * .where(gte(cmsAuditLogs.createdAt, new Date(Date.now() - 24 * 60 * 60 * 1000)))\n * .orderBy(desc(cmsAuditLogs.createdAt));\n */","/**\n * CMS Label Values Repository\n *\n * 라벨 값 관리를 위한 Repository\n */\n\nimport { findOne, findMany, create, updateOne, deleteMany } from '@spfn/core/db';\nimport { eq, and, SQL, isNull } from 'drizzle-orm';\nimport { cmsLabelValues, type CmsLabelValue, type NewCmsLabelValue } from '@/server/entities';\n\n/**\n * 특정 라벨의 특정 버전 값들 조회\n */\nexport async function findByLabelIdAndVersion(\n labelId: number,\n version: number,\n options?: {\n locale?: string;\n breakpoint?: string | null;\n }\n): Promise<CmsLabelValue[]>\n{\n const { locale, breakpoint } = options || {};\n\n const conditions: SQL[] = [\n eq(cmsLabelValues.labelId, labelId),\n eq(cmsLabelValues.version, version)\n ];\n\n if (locale)\n {\n conditions.push(eq(cmsLabelValues.locale, locale));\n }\n\n if (breakpoint !== undefined)\n {\n conditions.push(\n breakpoint === null\n ? isNull(cmsLabelValues.breakpoint)\n : eq(cmsLabelValues.breakpoint, breakpoint)\n );\n }\n\n return findMany(cmsLabelValues, {\n where: and(...conditions)\n });\n}\n\n/**\n * 값 저장 (upsert)\n * - version: null → Draft 저장 (덮어쓰기)\n * - version: number → Published 버전 생성 (불변)\n */\nexport async function upsert(data: NewCmsLabelValue): Promise<CmsLabelValue>\n{\n // 기존 값이 있는지 확인\n const versionCondition = data.version === null || data.version === undefined\n ? isNull(cmsLabelValues.version)\n : eq(cmsLabelValues.version, data.version as number);\n\n const existing = await findOne(\n cmsLabelValues,\n and(\n eq(cmsLabelValues.labelId, data.labelId),\n versionCondition,\n eq(cmsLabelValues.locale, data.locale || 'ko'),\n data.breakpoint\n ? eq(cmsLabelValues.breakpoint, data.breakpoint)\n : isNull(cmsLabelValues.breakpoint)\n )\n );\n\n if (existing)\n {\n // UPDATE (only for drafts with version: null)\n if (data.version === null || data.version === undefined)\n {\n const updated = await updateOne(\n cmsLabelValues,\n { id: existing.id },\n { value: data.value }\n );\n return updated!;\n }\n else\n {\n // Published versions are immutable - this shouldn't happen\n throw new Error(`Published version ${data.version} already exists and cannot be overwritten`);\n }\n }\n else\n {\n // INSERT (both draft and new published versions)\n return create(cmsLabelValues, data);\n }\n}\n\n/**\n * Draft 값들 조회 (version = null)\n */\nexport async function findDraftsByLabelId(labelId: number): Promise<CmsLabelValue[]>\n{\n return findMany(cmsLabelValues, {\n where: and(\n eq(cmsLabelValues.labelId, labelId),\n isNull(cmsLabelValues.version)\n )\n });\n}\n\n/**\n * 여러 값 일괄 저장\n */\nexport async function upsertMany(values: NewCmsLabelValue[]): Promise<CmsLabelValue[]>\n{\n const results = [];\n for (const value of values)\n {\n const result = await upsert(value);\n results.push(result);\n }\n return results;\n}\n\n/**\n * 특정 버전의 모든 값 삭제\n */\nexport async function deleteByVersion(labelId: number, version: number): Promise<CmsLabelValue[]>\n{\n return deleteMany(\n cmsLabelValues,\n and(\n eq(cmsLabelValues.labelId, labelId),\n eq(cmsLabelValues.version, version)\n )\n );\n}\n\n// Legacy export for backward compatibility\nexport const cmsLabelValuesRepository = {\n findByLabelIdAndVersion,\n findDraftsByLabelId,\n upsert,\n upsertMany,\n deleteByVersion\n};","/**\n * CMS Draft Cache Repository\n *\n * 관리자별 초안 캐시 관리 (동시 편집 지원)\n */\n\nimport { findOne, findMany, deleteOne, deleteMany, upsert as upsertHelper } from '@spfn/core/db';\nimport { eq, and, lt } from 'drizzle-orm';\nimport { cmsDraftCache, type NewCmsDraftCache } from '@/server/entities';\n\n/**\n * 섹션 + 언어 + 사용자로 초안 캐시 조회\n */\nexport async function findByUser(section: string, locale: string, userId: string)\n{\n return findOne(\n cmsDraftCache,\n and(\n eq(cmsDraftCache.section, section),\n eq(cmsDraftCache.locale, locale),\n eq(cmsDraftCache.userId, userId)\n )\n );\n}\n\n/**\n * 초안 캐시 생성 또는 업데이트 (UPSERT)\n */\nexport async function upsert(data: NewCmsDraftCache)\n{\n return upsertHelper(cmsDraftCache, data, {\n target: [cmsDraftCache.section, cmsDraftCache.locale, cmsDraftCache.userId],\n set: {\n content: data.content,\n updatedAt: new Date(),\n }\n });\n}\n\n/**\n * 특정 사용자의 모든 초안 조회\n */\nexport async function findAllByUser(userId: string)\n{\n return findMany(cmsDraftCache, {\n where: eq(cmsDraftCache.userId, userId)\n });\n}\n\n/**\n * 초안 삭제\n */\nexport async function deleteByUser(section: string, locale: string, userId: string)\n{\n await deleteOne(\n cmsDraftCache,\n and(\n eq(cmsDraftCache.section, section),\n eq(cmsDraftCache.locale, locale),\n eq(cmsDraftCache.userId, userId)\n )\n );\n}\n\n/**\n * 오래된 초안 정리 (30일 이상 미사용)\n */\nexport async function cleanupOldDrafts(daysOld: number = 30)\n{\n const cutoffDate = new Date();\n cutoffDate.setDate(cutoffDate.getDate() - daysOld);\n\n return deleteMany(\n cmsDraftCache,\n lt(cmsDraftCache.updatedAt, cutoffDate)\n );\n}\n\nexport const cmsDraftCacheRepository = {\n findByUser,\n upsert,\n findAllByUser,\n deleteByUser,\n cleanupOldDrafts,\n};","/**\n * CMS Published Cache Repository\n *\n * 발행된 콘텐츠 캐시 관리 (초고속 조회)\n */\n\nimport { findOne, findMany, deleteOne, deleteMany, upsert as upsertHelper } from '@spfn/core/db';\nimport { eq, and, sql } from 'drizzle-orm';\nimport { cmsPublishedCache, type NewCmsPublishedCache } from '@/server/entities';\n\n/**\n * 섹션 + 언어로 발행된 캐시 조회\n */\nexport async function findBySection(section: string, locale: string = 'ko')\n{\n return findOne(\n cmsPublishedCache,\n and(\n eq(cmsPublishedCache.section, section),\n eq(cmsPublishedCache.locale, locale)\n )\n );\n}\n\n/**\n * 캐시 생성 또는 업데이트 (UPSERT)\n */\nexport async function upsert(data: NewCmsPublishedCache)\n{\n return upsertHelper(cmsPublishedCache, data, {\n target: [cmsPublishedCache.section, cmsPublishedCache.locale],\n set: {\n content: data.content,\n publishedAt: data.publishedAt,\n publishedBy: data.publishedBy,\n version: sql`${cmsPublishedCache.version} + 1`, // 버전 증가로 클라이언트 캐시 무효화\n }\n });\n}\n\n/**\n * 섹션별 모든 언어 캐시 조회\n */\nexport async function findAllLanguages(section: string)\n{\n return findMany(cmsPublishedCache, {\n where: eq(cmsPublishedCache.section, section)\n });\n}\n\n/**\n * 캐시 삭제\n */\nexport async function deleteBySection(section: string, locale?: string)\n{\n if (locale)\n {\n await deleteOne(\n cmsPublishedCache,\n and(\n eq(cmsPublishedCache.section, section),\n eq(cmsPublishedCache.locale, locale)\n )\n );\n }\n else\n {\n await deleteMany(\n cmsPublishedCache,\n eq(cmsPublishedCache.section, section)\n );\n }\n}\n\nexport const cmsPublishedCacheRepository = {\n findBySection,\n upsert,\n findAllLanguages,\n deleteBySection,\n};","import { Type } from '@sinclair/typebox';\nimport type { RouteContract } from '@spfn/core/route';\n\n/**\n * CMS Labels Contracts\n *\n * 라벨 메타데이터 관리 API\n */\n\n/**\n * GET /_cms/labels - 라벨 목록 조회\n */\nexport const getLabelsContract = {\n method: 'GET' as const,\n path: '/_cms/labels',\n query: Type.Object({\n section: Type.Optional(Type.String({ description: '섹션으로 필터링 (예: home, why-futureplay)' })),\n limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100, default: 20, description: '페이지당 항목 수' })),\n offset: Type.Optional(Type.Number({ minimum: 0, default: 0, description: '시작 오프셋' })),\n includeDefaultValues: Type.Optional(Type.Boolean({ description: '기본값 포함 여부', default: false }))\n }),\n response: Type.Object({\n labels: Type.Array(Type.Object({\n id: Type.Number(),\n key: Type.String(),\n section: Type.String(),\n type: Type.String(),\n description: Type.Union([Type.String(), Type.Null()], { description: '라벨 설명' }),\n publishedVersion: Type.Union([Type.Number(), Type.Null()]),\n createdBy: Type.Union([Type.String(), Type.Null()]),\n createdAt: Type.String(),\n updatedAt: Type.String(),\n defaultValue: Type.Optional(Type.Any({ description: '라벨 정의 파일의 기본값' }))\n })),\n total: Type.Number(),\n limit: Type.Number(),\n offset: Type.Number()\n })\n} as const satisfies RouteContract;\n\n/**\n * POST /_cms/labels - 새 라벨 생성\n */\nexport const createLabelContract = {\n method: 'POST' as const,\n path: '/_cms/labels',\n body: Type.Object({\n key: Type.String({\n description: '고유 키 (예: home.hero.title)',\n pattern: '^[a-z0-9-]+\\\\.[a-z0-9-]+\\\\.[a-z0-9-]+$'\n }),\n section: Type.String({\n description: '섹션 이름 (예: home, why-futureplay)',\n pattern: '^[a-z0-9-]+$'\n }),\n type: Type.Union([\n Type.Literal('text'),\n Type.Literal('image'),\n Type.Literal('video'),\n Type.Literal('file'),\n Type.Literal('object')\n ], { description: '값 타입' }),\n createdBy: Type.Optional(Type.String({ description: '생성자 ID' }))\n }),\n response: Type.Union([\n Type.Object({\n id: Type.Number(),\n key: Type.String(),\n section: Type.String(),\n type: Type.String(),\n publishedVersion: Type.Union([Type.Number(), Type.Null()]),\n createdBy: Type.Union([Type.String(), Type.Null()]),\n createdAt: Type.String(),\n updatedAt: Type.String()\n }),\n Type.Object({\n error: Type.String(),\n key: Type.Optional(Type.String())\n })\n ])\n} as const satisfies RouteContract;\n\n/**\n * GET /_cms/labels/:id - 라벨 단건 조회\n */\nexport const getLabelContract = {\n method: 'GET' as const,\n path: '/_cms/labels/:id',\n params: Type.Object({\n id: Type.String({ description: '라벨 ID' })\n }),\n response: Type.Union([\n Type.Object({\n id: Type.Number(),\n key: Type.String(),\n section: Type.String(),\n type: Type.String(),\n description: Type.Union([Type.String(), Type.Null()]),\n publishedVersion: Type.Union([Type.Number(), Type.Null()]),\n createdBy: Type.Union([Type.String(), Type.Null()]),\n createdAt: Type.String(),\n updatedAt: Type.String()\n }),\n Type.Object({\n error: Type.String()\n })\n ])\n} as const satisfies RouteContract;\n\n/**\n * PATCH /_cms/labels/:id - 라벨 메타데이터 수정\n */\nexport const updateLabelContract = {\n method: 'PATCH' as const,\n path: '/_cms/labels/:id',\n params: Type.Object({\n id: Type.String({ description: '라벨 ID' })\n }),\n body: Type.Object({\n section: Type.Optional(Type.String({ description: '섹션 변경' })),\n type: Type.Optional(Type.Union([\n Type.Literal('text'),\n Type.Literal('image'),\n Type.Literal('video'),\n Type.Literal('file'),\n Type.Literal('object')\n ]))\n }),\n response: Type.Union([\n Type.Object({\n id: Type.Number(),\n key: Type.String(),\n section: Type.String(),\n type: Type.String(),\n description: Type.Union([Type.String(), Type.Null()]),\n publishedVersion: Type.Union([Type.Number(), Type.Null()]),\n createdBy: Type.Union([Type.String(), Type.Null()]),\n createdAt: Type.String(),\n updatedAt: Type.String()\n }),\n Type.Object({\n error: Type.String()\n })\n ])\n} as const satisfies RouteContract;\n\n/**\n * DELETE /_cms/labels/:id - 라벨 삭제\n */\nexport const deleteLabelContract = {\n method: 'DELETE' as const,\n path: '/_cms/labels/:id',\n params: Type.Object({\n id: Type.String({ description: '라벨 ID' })\n }),\n response: Type.Union([\n Type.Object({\n success: Type.Boolean(),\n id: Type.Number()\n }),\n Type.Object({\n error: Type.String()\n })\n ])\n} as const satisfies RouteContract;\n\n/**\n * GET /_cms/labels/by-key/:key - Key로 라벨 조회\n */\nexport const getLabelByKeyContract = {\n method: 'GET' as const,\n path: '/_cms/labels/by-key/:key',\n params: Type.Object({\n key: Type.String({ description: '라벨 Key (예: home.hero.title)' })\n }),\n response: Type.Union([\n Type.Object({\n id: Type.Number(),\n key: Type.String(),\n section: Type.String(),\n type: Type.String(),\n description: Type.Union([Type.String(), Type.Null()]),\n publishedVersion: Type.Union([Type.Number(), Type.Null()]),\n createdBy: Type.Union([Type.String(), Type.Null()]),\n createdAt: Type.String(),\n updatedAt: Type.String()\n }),\n Type.Object({\n error: Type.String(),\n key: Type.Optional(Type.String())\n })\n ])\n} as const satisfies RouteContract;\n\n/**\n * POST /_cms/labels/:labelId/publish - 라벨 발행 (Draft → Published)\n */\nexport const publishLabelContract = {\n method: 'POST' as const,\n path: '/_cms/labels/:labelId/publish',\n params: Type.Object({\n labelId: Type.String({ description: '라벨 ID' })\n }),\n body: Type.Object({\n notes: Type.Optional(Type.String({ description: '발행 노트 (버전 설명)' })),\n publishedBy: Type.Optional(Type.String({ description: '발행자 ID' }))\n }),\n response: Type.Union([\n Type.Object({\n success: Type.Boolean(),\n labelId: Type.Number(),\n version: Type.Number(),\n message: Type.String()\n }),\n Type.Object({\n error: Type.String()\n })\n ])\n} as const satisfies RouteContract;\n\n/**\n * GET /_cms/labels/:labelId/admin - 관리자용 라벨 조회 (Draft + Published + Status)\n */\nexport const getAdminLabelContract = {\n method: 'GET' as const,\n path: '/_cms/labels/:labelId/admin',\n params: Type.Object({\n labelId: Type.String({ description: '라벨 ID' })\n }),\n response: Type.Union([\n Type.Object({\n label: Type.Object({\n id: Type.Number(),\n key: Type.String(),\n section: Type.String(),\n type: Type.String(),\n description: Type.Union([Type.String(), Type.Null()]),\n publishedVersion: Type.Union([Type.Number(), Type.Null()]),\n createdBy: Type.Union([Type.String(), Type.Null()]),\n createdAt: Type.String(),\n updatedAt: Type.String()\n }),\n draft: Type.Array(Type.Object({\n id: Type.Number(),\n labelId: Type.Number(),\n version: Type.Null(),\n locale: Type.String(),\n breakpoint: Type.Union([Type.String(), Type.Null()]),\n value: Type.Any(),\n createdAt: Type.String()\n })),\n published: Type.Array(Type.Object({\n id: Type.Number(),\n labelId: Type.Number(),\n version: Type.Number(),\n locale: Type.String(),\n breakpoint: Type.Union([Type.String(), Type.Null()]),\n value: Type.Any(),\n createdAt: Type.String()\n })),\n status: Type.Union([\n Type.Literal('default-only'),\n Type.Literal('unpublished'),\n Type.Literal('published'),\n Type.Literal('modified')\n ])\n }),\n Type.Object({\n error: Type.String()\n })\n ])\n} as const satisfies RouteContract;\n\n/**\n * GET /_cms/labels/:labelId/versions - 라벨 버전 히스토리 조회\n */\nexport const getLabelVersionsContract = {\n method: 'GET' as const,\n path: '/_cms/labels/:labelId/versions',\n params: Type.Object({\n labelId: Type.String({ description: '라벨 ID' })\n }),\n response: Type.Union([\n Type.Object({\n versions: Type.Array(Type.Object({\n version: Type.Number({ description: '버전 번호' }),\n publishedAt: Type.String({ description: '발행 시각 (ISO 8601)' }),\n publishedBy: Type.Union([Type.String(), Type.Null()], { description: '발행자 ID' }),\n notes: Type.Union([Type.String(), Type.Null()], { description: '발행 노트' }),\n values: Type.Array(Type.Object({\n id: Type.Number(),\n locale: Type.String(),\n breakpoint: Type.Union([Type.String(), Type.Null()]),\n value: Type.Any(),\n createdAt: Type.String()\n }))\n }))\n }),\n Type.Object({\n error: Type.String()\n })\n ])\n} as const satisfies RouteContract;","/**\n * CMS Sync Utilities\n *\n * JSON 파일 기반 라벨 동기화\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from 'fs';\nimport { basename, extname, join } from 'path';\nimport { extractLabels } from '@/server/labels';\nimport { cmsLabelsRepository, cmsPublishedCacheRepository } from '@/server/repositories';\nimport { DEFAULT_LABELS_DIR } from '@/lib/constants';\nimport type { NestedLabels, SectionDefinition, SyncOptions, SyncResult } from '@/lib/types';\n\n/**\n * 여러 섹션 동기화\n */\nexport async function syncAll(\n sections: SectionDefinition[],\n options: SyncOptions = {}\n): Promise<SyncResult[]>\n{\n const results: SyncResult[] = [];\n\n for (const definition of sections)\n {\n const result = await syncSection(definition, options);\n results.push(result);\n }\n\n return results;\n}\n\n/**\n * JSON 파일에서 라벨 로드\n */\nexport function loadLabelsFromJson(labelsDir: string): SectionDefinition[]\n{\n const sections: SectionDefinition[] = [];\n\n if (!existsSync(labelsDir))\n {\n console.warn(`[CMS] Labels directory not found: ${labelsDir}`);\n console.warn(`[CMS] Expected directory structure:`);\n console.warn(`[CMS] ${labelsDir}/`);\n console.warn(`[CMS] ├── common/ # Section directory`);\n console.warn(`[CMS] │ ├── messages.json`);\n console.warn(`[CMS] │ └── errors.json`);\n console.warn(`[CMS] └── home/ # Section directory`);\n console.warn(`[CMS] └── hero.json`);\n return sections;\n }\n\n try\n {\n const entries = readdirSync(labelsDir);\n\n if (entries.length === 0)\n {\n console.warn(`[CMS] Labels directory is empty: ${labelsDir}`);\n console.warn(`[CMS] Create section directories with JSON files inside`);\n return sections;\n }\n\n const jsonFiles = entries.filter(e => extname(e) === '.json');\n if (jsonFiles.length > 0)\n {\n console.warn(`[CMS] Found JSON files directly in ${labelsDir}:`);\n jsonFiles.forEach(f => console.warn(`[CMS] - ${f} (will be ignored)`));\n console.warn(`[CMS] JSON files should be inside section directories`);\n console.warn(`[CMS] Example: Move ${jsonFiles[0]} to ${labelsDir}/${basename(jsonFiles[0], '.json')}/${jsonFiles[0]}`);\n }\n\n for (const entry of entries)\n {\n const sectionPath = join(labelsDir, entry);\n const stat = statSync(sectionPath);\n\n if (stat.isDirectory())\n {\n const sectionName = entry;\n const labels = loadSectionLabels(sectionPath);\n\n if (Object.keys(labels).length > 0)\n {\n sections.push({ section: sectionName, labels });\n }\n else\n {\n console.warn(`[CMS] Section directory \"${sectionName}\" has no valid JSON files`);\n }\n }\n }\n\n if (sections.length === 0)\n {\n console.warn(`[CMS] No valid section directories found in ${labelsDir}`);\n }\n }\n catch (error)\n {\n console.warn(`[CMS] Could not scan labels directory: ${labelsDir}`);\n console.error(`[CMS] Error:`, error);\n }\n\n return sections;\n}\n\nfunction loadSectionLabels(sectionPath: string): NestedLabels\n{\n const labels: NestedLabels = {};\n\n try\n {\n const files = readdirSync(sectionPath);\n\n for (const file of files)\n {\n if (extname(file) === '.json')\n {\n const filePath = join(sectionPath, file);\n const categoryName = basename(file, '.json');\n\n try\n {\n const content = readFileSync(filePath, 'utf-8');\n labels[categoryName] = JSON.parse(content);\n }\n catch (error)\n {\n console.warn(`[CMS] Failed to parse ${filePath}`);\n }\n }\n }\n }\n catch (error)\n {\n console.warn(`[CMS] Could not read section directory: ${sectionPath}`);\n }\n\n return labels;\n}\n\n/**\n * 섹션 라벨 동기화\n */\nexport async function syncSection(\n definition: SectionDefinition,\n options: SyncOptions = {}\n): Promise<SyncResult>\n{\n const {\n dryRun = false,\n updateExisting = false,\n removeUnused = false,\n verbose = false,\n } = options;\n\n const { section } = definition;\n const result: SyncResult = {\n section,\n created: 0,\n updated: 0,\n deleted: 0,\n unchanged: 0,\n errors: [],\n };\n\n try\n {\n const definedLabels = extractLabels(definition);\n const definedKeys = new Set(definedLabels.map((l) => l.key));\n const existingLabels = await cmsLabelsRepository.findBySection(section);\n const existingMap = new Map(existingLabels.map((l) => [l.key, l]));\n\n if (verbose)\n {\n console.log(`\\n[${section}] Found ${definedLabels.length} labels in definition`);\n console.log(`[${section}] Found ${existingLabels.length} labels in DB`);\n }\n\n // 생성 및 업데이트\n for (const label of definedLabels)\n {\n const existing = existingMap.get(label.key);\n\n if (!existing)\n {\n if (verbose) console.log(` [CREATE] ${label.key}`);\n\n if (!dryRun)\n {\n try\n {\n const defaultValue = typeof label.defaultValue === 'object'\n ? JSON.stringify(label.defaultValue)\n : label.defaultValue;\n\n await cmsLabelsRepository.create({\n section,\n key: label.key,\n type: label.type || 'text', // 라벨 타입 (기본값: 'text')\n defaultValue,\n description: label.description,\n });\n }\n catch (error)\n {\n result.errors.push({\n key: label.key,\n error: error instanceof Error ? error.message : String(error),\n });\n continue;\n }\n }\n\n result.created++;\n }\n else if (updateExisting)\n {\n const newDefaultValue = typeof label.defaultValue === 'object'\n ? JSON.stringify(label.defaultValue)\n : label.defaultValue;\n\n const hasChanged = existing.defaultValue !== newDefaultValue;\n\n if (hasChanged)\n {\n if (verbose)\n {\n console.log(` [UPDATE] ${label.key}`);\n console.log(` Old: \"${existing.defaultValue}\"`);\n console.log(` New: \"${newDefaultValue}\"`);\n }\n\n if (!dryRun)\n {\n try\n {\n await cmsLabelsRepository.updateById(existing.id, {\n defaultValue: newDefaultValue,\n description: label.description,\n });\n }\n catch (error)\n {\n result.errors.push({\n key: label.key,\n error: error instanceof Error ? error.message : String(error),\n });\n continue;\n }\n }\n\n result.updated++;\n }\n else\n {\n result.unchanged++;\n }\n }\n else\n {\n result.unchanged++;\n }\n }\n\n // 사용되지 않는 라벨 삭제\n if (removeUnused)\n {\n for (const existing of existingLabels)\n {\n if (!definedKeys.has(existing.key))\n {\n if (verbose) console.log(` [DELETE] ${existing.key}`);\n\n if (!dryRun)\n {\n try\n {\n await cmsLabelsRepository.deleteById(existing.id);\n }\n catch (error)\n {\n result.errors.push({\n key: existing.key,\n error: error instanceof Error ? error.message : String(error),\n });\n continue;\n }\n }\n\n result.deleted++;\n }\n }\n }\n\n // Published cache 업데이트\n if (!dryRun && (result.created > 0 || result.updated > 0 || result.deleted > 0))\n {\n if (verbose) console.log(` [CACHE] Updating published cache for section: ${section}`);\n await updatePublishedCache(section);\n }\n }\n catch (error)\n {\n result.errors.push({\n key: '__section__',\n error: error instanceof Error ? error.message : String(error),\n });\n }\n\n return result;\n}\n\n/**\n * Published Cache 업데이트\n */\nasync function updatePublishedCache(section: string): Promise<void>\n{\n const labels = await cmsLabelsRepository.findBySection(section);\n const localesSet = new Set<string>();\n const labelsByLocale: Record<string, Record<string, any>> = {};\n\n labels.forEach((label) =>\n {\n try\n {\n const parsed = JSON.parse(label.defaultValue || '{}');\n\n if (typeof parsed === 'object' && !Array.isArray(parsed))\n {\n // Multilingual\n Object.keys(parsed).forEach((locale) => localesSet.add(locale));\n Object.entries(parsed).forEach(([locale, value]) =>\n {\n if (!labelsByLocale[locale]) labelsByLocale[locale] = {};\n labelsByLocale[locale][label.key] = value;\n });\n }\n else\n {\n // Single value\n if (!labelsByLocale.ko) labelsByLocale.ko = {};\n labelsByLocale.ko[label.key] = label.defaultValue;\n localesSet.add('ko');\n }\n }\n catch\n {\n // Plain string\n if (!labelsByLocale.ko) labelsByLocale.ko = {};\n labelsByLocale.ko[label.key] = label.defaultValue;\n localesSet.add('ko');\n }\n });\n\n const timestamp = new Date();\n for (const locale of localesSet)\n {\n await cmsPublishedCacheRepository.upsert({\n section,\n locale,\n content: labelsByLocale[locale] || {},\n publishedAt: timestamp,\n publishedBy: 'system',\n });\n }\n}\n\n/**\n * Initialize label sync for server startup\n *\n * Call this in your server.config.ts beforeRoutes hook\n *\n * @param options - Sync options\n * @param options.labelsDir - Path to labels directory (default: 'src/lib/labels')\n *\n * @example\n * ```typescript\n * import { initLabelSync } from '@spfn/cms';\n *\n * export default {\n * beforeRoutes: async (app) => {\n * await initLabelSync({ verbose: true });\n * },\n * } satisfies ServerConfig;\n * ```\n */\nexport async function initLabelSync(options: SyncOptions & { labelsDir?: string } = {}): Promise<void>\n{\n const isDevelopment = process.env.NODE_ENV === 'development';\n const verbose = options.verbose ?? isDevelopment;\n const labelsDir = options.labelsDir ?? DEFAULT_LABELS_DIR;\n\n if (verbose)\n {\n console.log('\\n🔄 Initializing label sync...\\n');\n }\n\n // Load labels from JSON files\n const sections = loadLabelsFromJson(labelsDir);\n\n if (sections.length === 0)\n {\n if (verbose)\n {\n console.log('⚠️ No labels found in', labelsDir);\n console.log('');\n }\n return;\n }\n\n const results = await syncAll(sections, {\n updateExisting: true, // 🔄 항상 업데이트 (프로덕션 포함)\n ...options,\n verbose,\n });\n\n const totalCreated = results.reduce((sum, r) => sum + r.created, 0);\n const totalUpdated = results.reduce((sum, r) => sum + r.updated, 0);\n const totalUnchanged = results.reduce((sum, r) => sum + r.unchanged, 0);\n const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);\n\n if (verbose)\n {\n console.log('✅ Label sync completed\\n');\n console.log(` Sections: ${results.length}`);\n console.log(` Created: ${totalCreated}`);\n console.log(` Updated: ${totalUpdated}`);\n console.log(` Unchanged: ${totalUnchanged}`);\n\n if (totalErrors > 0)\n {\n console.log(` Errors: ${totalErrors}\\n`);\n }\n else\n {\n console.log('');\n }\n }\n\n // Log errors\n if (totalErrors > 0)\n {\n results.forEach((result) =>\n {\n result.errors.forEach((error) =>\n {\n console.error(`[${result.section}] ${error.key}: ${error.error}`);\n });\n });\n }\n}","/**\n * CMS Label Helpers\n *\n * Utilities for processing label definitions from JSON files\n */\n\nimport type { SectionDefinition, NestedLabels, FlatLabel, LabelDefinition } from '@/lib/types';\n\n/**\n * 중첩된 라벨을 플랫화\n *\n * @param labels - 중첩된 라벨 객체\n * @returns 플랫화된 라벨 배열\n *\n * @example\n * ```ts\n * flattenLabels({\n * nav: {\n * home: { key: 'layout.nav.home', defaultValue: 'Home' },\n * },\n * });\n * // => [{ key: 'layout.nav.home', defaultValue: 'Home' }]\n * ```\n */\nexport function flattenLabels(labels: NestedLabels): FlatLabel[]\n{\n const result: FlatLabel[] = [];\n\n function isLabelDefinition(obj: NestedLabels | LabelDefinition): obj is LabelDefinition\n {\n return (\n 'key' in obj &&\n 'defaultValue' in obj &&\n typeof obj.key === 'string' &&\n (typeof obj.defaultValue === 'string' || typeof obj.defaultValue === 'object')\n );\n }\n\n function traverse(obj: NestedLabels | LabelDefinition)\n {\n if (isLabelDefinition(obj))\n {\n // LabelDefinition인 경우\n result.push({\n key: obj.key,\n defaultValue: obj.defaultValue,\n description: obj.description,\n });\n }\n else\n {\n // NestedLabels인 경우\n Object.values(obj).forEach((value) =>\n {\n if (typeof value === 'object' && value !== null)\n {\n traverse(value);\n }\n });\n }\n }\n\n traverse(labels);\n return result;\n}\n\n/**\n * 섹션 정의에서 모든 라벨 추출\n *\n * @param definition - 섹션 정의\n * @returns 플랫화된 라벨 배열\n */\nexport function extractLabels(definition: SectionDefinition): FlatLabel[]\n{\n return flattenLabels(definition.labels);\n}\n","/**\n * CMS Constants\n *\n * CMS 패키지에서 사용하는 전역 상수\n */\n\n/**\n * 기본 라벨 디렉토리 경로\n *\n * JSON 라벨 파일이 저장되는 기본 디렉토리입니다.\n * 프로젝트 루트 기준 상대 경로입니다.\n *\n * @example\n * ```typescript\n * import { DEFAULT_LABELS_DIR } from '@spfn/cms';\n *\n * console.log(DEFAULT_LABELS_DIR); // 'src/lib/labels'\n * ```\n */\nexport const DEFAULT_LABELS_DIR = 'src/lib/labels';"],"mappings":"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","names":["serial","integer","text","timestamp","index","createFunctionSchema","schema","createFunctionSchema","serial","integer","text","timestamp","index","serial","integer","text","timestamp","index","unique","createFunctionSchema","schema","createFunctionSchema","serial","integer","text","timestamp","unique","index","serial","text","jsonb","timestamp","index","unique","createFunctionSchema","schema","serial","text","jsonb","integer","timestamp","index","unique","createFunctionSchema","schema","serial","integer","text","jsonb","timestamp","index","createFunctionSchema","schema","createFunctionSchema","serial","integer","text","jsonb","timestamp","index","findOne","findMany","create","updateOne","findOne","findMany","deleteOne","deleteMany","eq","and","findOne","findMany","deleteOne","deleteMany","upsertHelper","eq","and","join"]}
1
+ {"version":3,"sources":["../../../../src/server/routes/labels/index.ts","../../../../src/server/repositories/cms-labels.repository.ts","../../../../src/server/entities/cms-labels.ts","../../../../src/server/entities/cms-label-values.ts","../../../../src/server/entities/cms-draft-cache.ts","../../../../src/server/entities/cms-published-cache.ts","../../../../src/server/entities/cms-audit-logs.ts","../../../../src/server/repositories/cms-label-values.repository.ts","../../../../src/server/repositories/cms-draft-cache.repository.ts","../../../../src/server/repositories/cms-published-cache.repository.ts","../../../../src/lib/contracts/labels.ts","../../../../src/server/helpers/sync.ts","../../../../src/server/labels/helpers.ts","../../../../src/lib/constants/index.ts"],"sourcesContent":["/**\n * CMS Labels Routes\n *\n * 라벨 메타데이터 관리 API\n * - GET /labels - 라벨 목록 조회 (final: /_cms/labels)\n * - POST /labels - 새 라벨 생성 (final: /_cms/labels)\n */\n\nimport { createApp } from '@spfn/core/route';\nimport { cmsLabelsRepository } from '@/server/repositories';\nimport { getLabelsContract, createLabelContract } from '@/lib/contracts/labels';\nimport { loadLabelsFromJson } from '@/server/helpers/sync';\nimport { extractLabels } from '@/server/labels';\nimport { DEFAULT_LABELS_DIR } from '@/lib/constants';\nimport { join } from 'path';\n\nconst app = createApp();\n\n/**\n * GET /labels\n * 라벨 목록 조회 (페이지네이션, 섹션 필터)\n */\napp.bind(getLabelsContract, async (c) =>\n{\n const { section, limit = 20, offset = 0, includeDefaultValues } = c.query;\n\n // 라벨 목록 조회\n const labels = await cmsLabelsRepository.findMany({\n section,\n limit,\n offset,\n });\n\n // 전체 개수 조회\n const total = await cmsLabelsRepository.count(section);\n\n // includeDefaultValues가 true이면 라벨 정의에서 기본값 로드\n let defaultValuesMap: Record<string, any> = {};\n if (includeDefaultValues && section)\n {\n try\n {\n const labelsDir = join(process.cwd(), DEFAULT_LABELS_DIR);\n const sections = loadLabelsFromJson(labelsDir);\n const sectionDef = sections.find(s => s.section === section);\n\n if (sectionDef)\n {\n const extracted = extractLabels(sectionDef);\n defaultValuesMap = extracted.reduce((acc, label) => {\n acc[label.key] = label.defaultValue;\n return acc;\n }, {} as Record<string, any>);\n }\n }\n catch (error)\n {\n console.warn('[getLabels] Failed to load default values:', error);\n }\n }\n\n return c.json({\n labels: labels.map((label) => ({\n id: label.id,\n key: label.key,\n section: label.section,\n type: label.type,\n description: label.description,\n publishedVersion: label.publishedVersion,\n createdBy: label.createdBy,\n createdAt: label.createdAt.toISOString(),\n updatedAt: label.updatedAt.toISOString(),\n ...(includeDefaultValues && { defaultValue: defaultValuesMap[label.key] })\n })),\n total,\n limit,\n offset,\n });\n});\n\n/**\n * POST /labels\n * 새 라벨 생성\n */\napp.bind(createLabelContract, async (c) =>\n{\n const body = await c.data();\n\n // 중복 key 체크\n const existing = await cmsLabelsRepository.findByKey(body.key);\n if (existing)\n {\n return c.json(\n { error: 'Label with this key already exists', key: body.key },\n 409\n );\n }\n\n // 라벨 생성\n const label = await cmsLabelsRepository.create({\n key: body.key,\n section: body.section,\n type: body.type,\n createdBy: body.createdBy,\n });\n\n return c.json(\n {\n id: label.id,\n key: label.key,\n section: label.section,\n type: label.type,\n publishedVersion: label.publishedVersion,\n createdBy: label.createdBy,\n createdAt: label.createdAt.toISOString(),\n updatedAt: label.updatedAt.toISOString(),\n },\n 201\n );\n});\n\nexport default app;\n","/**\n * CMS Labels Repository\n *\n * 라벨 메타데이터 관리를 위한 Repository\n */\n\nimport { findOne, findMany as findManyHelper, create as createHelper, updateOne, deleteOne, count as countHelper } from '@spfn/core/db';\nimport { asc } from 'drizzle-orm';\nimport { cmsLabels, type CmsLabel, type NewCmsLabel } from '@/server/entities';\n\n/**\n * 라벨 목록 조회 (페이지네이션)\n */\nexport async function findMany(options?: {\n section?: string;\n limit?: number;\n offset?: number;\n}): Promise<CmsLabel[]>\n{\n const { section, limit = 20, offset = 0 } = options || {};\n\n return findManyHelper(cmsLabels, {\n where: section ? { section } : undefined,\n orderBy: asc(cmsLabels.key), // key 오름차순 정렬 (JSON 파일의 순서 유지)\n limit,\n offset\n });\n}\n\n/**\n * 전체 라벨 수 조회\n */\nexport async function count(section?: string): Promise<number>\n{\n return countHelper(cmsLabels, section ? { section } : undefined);\n}\n\n/**\n * ID로 라벨 조회\n */\nexport async function findById(id: number): Promise<CmsLabel | null>\n{\n return findOne(cmsLabels, { id });\n}\n\n/**\n * Key로 라벨 조회\n */\nexport async function findByKey(key: string): Promise<CmsLabel | null>\n{\n return findOne(cmsLabels, { key });\n}\n\n/**\n * 섹션으로 모든 라벨 조회\n */\nexport async function findBySection(section: string): Promise<CmsLabel[]>\n{\n return findManyHelper(cmsLabels, {\n where: { section },\n orderBy: asc(cmsLabels.key), // key 오름차순 정렬 (JSON 파일의 순서 유지)\n });\n}\n\n/**\n * 라벨 생성\n */\nexport async function create(data: NewCmsLabel): Promise<CmsLabel>\n{\n return createHelper(cmsLabels, data);\n}\n\n/**\n * 라벨 수정\n */\nexport async function updateById(id: number, data: Partial<NewCmsLabel>): Promise<CmsLabel | null>\n{\n return updateOne(cmsLabels, { id }, { ...data, updatedAt: new Date() });\n}\n\n/**\n * 라벨 삭제\n */\nexport async function deleteById(id: number): Promise<CmsLabel | null>\n{\n return deleteOne(cmsLabels, { id });\n}\n\n// Legacy export for backward compatibility\nexport const cmsLabelsRepository = {\n findMany,\n count,\n findById,\n findByKey,\n findBySection,\n create,\n updateById,\n deleteById\n};","/**\n * CMS Labels Entity\n *\n * 라벨의 메타데이터와 현재 발행 상태를 관리합니다.\n * - 라벨 식별 (id, key)\n * - 섹션 분류 (section)\n * - 타입 정의 (type)\n * - 발행 상태 (publishedVersion)\n */\n\nimport { index, integer, serial, text, timestamp } from 'drizzle-orm/pg-core';\nimport { createFunctionSchema } from '@spfn/core/db';\n\n// Create isolated schema for @spfn/cms\nconst schema = createFunctionSchema('@spfn/cms');\n\nexport const cmsLabels = schema.table('labels', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // 라벨 식별자\n key: text('key').notNull().unique(),\n // 예: \"home.hero.title\", \"why-futureplay.hero.subtitle\"\n // 구조: {section}.{component}.{property}\n\n // 섹션 분류 (페이지 단위)\n section: text('section').notNull(),\n // 예: \"home\", \"why-futureplay\", \"team\"\n\n // 값 타입\n type: text('type').notNull(),\n // \"text\" | \"image\" | \"video\" | \"file\" | \"object\"\n\n // 기본값\n defaultValue: text('default_value'),\n // 라벨의 기본값 (sync 시 설정)\n\n // 설명\n description: text('description'),\n // 라벨에 대한 설명 (optional)\n\n // 현재 발행된 버전 번호\n publishedVersion: integer('published_version'),\n // null = 미발행 상태\n // 1, 2, 3... = 발행된 버전 번호\n\n // 생성자 추적\n createdBy: text('created_by'),\n\n // 타임스탬프\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // 인덱스: 섹션별 조회 최적화\n index('cms_labels_section_idx').on(table.section),\n\n // 인덱스: key로 조회 최적화 (unique 제약으로 자동 생성되지만 명시)\n index('cms_labels_key_idx').on(table.key),\n]);\n\n// 타입 추론\nexport type CmsLabel = typeof cmsLabels.$inferSelect;\nexport type NewCmsLabel = typeof cmsLabels.$inferInsert;","/**\n * CMS Label Values Entity\n *\n * 라벨의 실제 값을 저장합니다.\n * - 다국어 지원 (locale)\n * - 반응형 지원 (breakpoint)\n * - 버전 관리 (version)\n * - JSONB로 유연한 값 저장\n */\n\nimport { serial, integer, text, jsonb, timestamp, index, unique } from 'drizzle-orm/pg-core';\nimport { createFunctionSchema } from '@spfn/core/db';\nimport { cmsLabels } from '@/server/entities/cms-labels';\n\n// Create isolated schema for @spfn/cms\nconst schema = createFunctionSchema('@spfn/cms');\n\nexport const cmsLabelValues = schema.table('label_values', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // Foreign Key: cms_labels\n labelId: integer('label_id')\n .notNull()\n .references(() => cmsLabels.id, { onDelete: 'cascade' }),\n\n // 버전 번호 (null = draft, number = published version)\n version: integer('version'),\n\n // 언어 코드\n locale: text('locale').notNull().default('ko'),\n // \"ko\" | \"en\" | \"ja\"\n\n // 반응형 브레이크포인트\n breakpoint: text('breakpoint'),\n // null = 기본값 (모든 화면 크기)\n // \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\"\n\n // 실제 값 (JSONB)\n value: jsonb('value').notNull(),\n // LabelValue 타입:\n // - TextValue: { type: \"text\", content: string }\n // - ImageValue: { type: \"image\", url: string, alt?: string, width?: number, height?: number }\n // - VideoValue: { type: \"video\", url: string, thumbnail?: string, duration?: number }\n // - FileValue: { type: \"file\", url: string, filename: string, size?: number }\n // - ObjectValue: { type: \"object\", fields: Record<string, LabelValue> }\n\n // 생성 시각\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // UNIQUE 제약: 같은 버전에서 locale + breakpoint 조합은 유일\n unique('cms_label_values_locale_breakpoint_unique')\n .on(table.labelId, table.version, table.locale, table.breakpoint),\n\n // 인덱스: labelId + version 복합 조회 최적화\n index('cms_label_values_label_version_idx')\n .on(table.labelId, table.version),\n\n // 인덱스: locale 필터링 최적화\n index('cms_label_values_locale_idx').on(table.locale),\n]);\n\n// 타입 추론\nexport type CmsLabelValue = typeof cmsLabelValues.$inferSelect;\nexport type NewCmsLabelValue = typeof cmsLabelValues.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // 텍스트 값 저장\n * await db.insert(cmsLabelValues).values({\n * labelId: 1,\n * version: 1,\n * locale: 'ko',\n * breakpoint: null,\n * value: {\n * type: 'text',\n * content: '미래를 만드는 기업'\n * }\n * });\n *\n * // 반응형 이미지 저장 (모바일용)\n * await db.insert(cmsLabelValues).values({\n * labelId: 2,\n * version: 1,\n * locale: 'ko',\n * breakpoint: 'sm',\n * value: {\n * type: 'image',\n * url: '/uploads/hero-mobile.jpg',\n * alt: 'Hero Image',\n * width: 640,\n * height: 480\n * }\n * });\n *\n * // 특정 버전의 한국어 값 조회\n * const values = await db.select()\n * .from(cmsLabelValues)\n * .where(and(\n * eq(cmsLabelValues.labelId, 1),\n * eq(cmsLabelValues.version, 2),\n * eq(cmsLabelValues.locale, 'ko')\n * ));\n *\n * // Object 타입 값 저장 (재귀 구조)\n * await db.insert(cmsLabelValues).values({\n * labelId: 3,\n * version: 1,\n * locale: 'ko',\n * value: {\n * type: 'object',\n * fields: {\n * title: { type: 'text', content: '특징 1' },\n * icon: { type: 'image', url: '/icons/feature1.svg', alt: 'Icon' },\n * description: { type: 'text', content: '상세 설명...' }\n * }\n * }\n * });\n */","/**\n * CMS Draft Cache Entity\n *\n * 관리자별 Draft 콘텐츠를 캐싱합니다.\n * - 사용자별 격리 (userId)\n * - 실시간 미리보기 지원\n * - 동시 편집 가능\n *\n * 핵심 기능:\n * - 여러 관리자가 같은 섹션을 동시에 편집\n * - 각자의 변경사항은 자신의 미리보기에만 표시\n * - 충돌 없이 안전하게 작업\n */\n\nimport { serial, text, jsonb, timestamp, index, unique } from 'drizzle-orm/pg-core';\nimport { createFunctionSchema } from '@spfn/core/db';\n\n// Create isolated schema for @spfn/cms\nconst schema = createFunctionSchema('@spfn/cms');\n\nexport const cmsDraftCache = schema.table('draft_cache', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // 섹션 (페이지 단위)\n section: text('section').notNull(),\n // \"home\" | \"why-futureplay\" | \"team\" | \"our-companies\" | \"apply\"\n\n // 언어\n locale: text('locale').notNull(),\n // \"ko\" | \"en\" | \"ja\"\n\n // 사용자 ID (핵심 필드!)\n userId: text('user_id').notNull(),\n // 각 관리자의 독립적인 작업 공간\n\n // Draft 콘텐츠 (JSONB)\n content: jsonb('content').notNull(),\n // Record<string, LabelValue>\n // {\n // \"home.hero.title\": { type: \"text\", content: \"수정 중...\" },\n // \"home.hero.subtitle\": { type: \"text\", content: \"새로운 문구\" },\n // ...\n // }\n\n // 최종 수정 시각\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // UNIQUE 제약: section + locale + userId 조합은 유일\n unique('cms_draft_cache_unique')\n .on(table.section, table.locale, table.userId),\n\n // 인덱스: section으로 조회 최적화\n index('cms_draft_cache_section_idx').on(table.section),\n\n // 인덱스: userId로 사용자의 모든 draft 조회 최적화\n index('cms_draft_cache_user_idx').on(table.userId),\n]);\n\n// 타입 추론\nexport type CmsDraftCache = typeof cmsDraftCache.$inferSelect;\nexport type NewCmsDraftCache = typeof cmsDraftCache.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // Draft 초기화 (편집 시작)\n * await db.insert(cmsDraftCache)\n * .values({\n * section: 'home',\n * locale: 'ko',\n * userId: 'user-a@futureplay.com',\n * content: publishedContent // 발행 버전 복사\n * });\n *\n * // Draft 업데이트 (값 수정 시)\n * const cache = await db.select()\n * .from(cmsDraftCache)\n * .where(and(\n * eq(cmsDraftCache.section, 'home'),\n * eq(cmsDraftCache.locale, 'ko'),\n * eq(cmsDraftCache.userId, userId)\n * ))\n * .limit(1);\n *\n * const updatedContent = {\n * ...cache[0].content,\n * 'home.hero.title': newValue // 부분 업데이트\n * };\n *\n * await db.update(cmsDraftCache)\n * .set({ content: updatedContent, updatedAt: new Date() })\n * .where(eq(cmsDraftCache.id, cache[0].id));\n *\n * // Draft 조회 (미리보기)\n * const draft = await db.select()\n * .from(cmsDraftCache)\n * .where(and(\n * eq(cmsDraftCache.section, 'home'),\n * eq(cmsDraftCache.locale, 'ko'),\n * eq(cmsDraftCache.userId, session.user.id)\n * ))\n * .limit(1);\n *\n * // 사용자의 모든 작업 중인 섹션 조회\n * const userDrafts = await db.select()\n * .from(cmsDraftCache)\n * .where(eq(cmsDraftCache.userId, userId))\n * .orderBy(desc(cmsDraftCache.updatedAt));\n *\n * // 오래된 Draft 정리 (30일 이상)\n * const stale = await db.delete(cmsDraftCache)\n * .where(lt(\n * cmsDraftCache.updatedAt,\n * new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)\n * ))\n * .returning();\n *\n * // Draft 폐기 (변경사항 버리기)\n * await db.delete(cmsDraftCache)\n * .where(and(\n * eq(cmsDraftCache.section, 'home'),\n * eq(cmsDraftCache.locale, 'ko'),\n * eq(cmsDraftCache.userId, userId)\n * ));\n */","/**\n * CMS Published Cache Entity\n *\n * 발행된 콘텐츠를 섹션+언어 단위로 캐싱합니다.\n * - 초고속 읽기 성능 (5ms)\n * - 단일 쿼리로 섹션 전체 로드\n * - JSONB로 즉시 사용 가능한 데이터\n *\n * 성능 비교:\n * - 정규화 테이블 JOIN: 87ms\n * - 캐시 테이블: 5ms (17배 빠름!)\n */\n\nimport { serial, text, jsonb, integer, timestamp, index, unique } from 'drizzle-orm/pg-core';\nimport { createFunctionSchema } from '@spfn/core/db';\n\n// Create isolated schema for @spfn/cms\nconst schema = createFunctionSchema('@spfn/cms');\n\nexport const cmsPublishedCache = schema.table('published_cache', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // 섹션 (페이지 단위)\n section: text('section').notNull(),\n // \"home\" | \"why-futureplay\" | \"team\" | \"our-companies\" | \"apply\"\n\n // 언어\n locale: text('locale').notNull(),\n // \"ko\" | \"en\" | \"ja\"\n\n // 캐시된 콘텐츠 (JSONB)\n content: jsonb('content').notNull(),\n // Record<string, LabelValue>\n // {\n // \"home.hero.title\": { type: \"text\", content: \"...\" },\n // \"home.hero.image\": { type: \"image\", url: \"...\", alt: \"...\" },\n // ...\n // }\n\n // 발행 정보\n publishedAt: timestamp('published_at', { withTimezone: true }).notNull(),\n publishedBy: text('published_by'),\n\n // 캐시 버전 (클라이언트 캐싱용)\n version: integer('version').notNull().default(1),\n}, (table) => [\n // UNIQUE 제약: section + locale 조합은 유일\n unique('cms_published_cache_unique').on(table.section, table.locale),\n\n // 인덱스: section으로 조회 최적화\n index('cms_published_cache_section_idx').on(table.section),\n]);\n\n// 타입 추론\nexport type CmsPublishedCache = typeof cmsPublishedCache.$inferSelect;\nexport type NewCmsPublishedCache = typeof cmsPublishedCache.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // 캐시 생성/업데이트 (UPSERT)\n * await db.insert(cmsPublishedCache)\n * .values({\n * section: 'home',\n * locale: 'ko',\n * content: {\n * 'home.hero.title': {\n * type: 'text',\n * content: '미래를 만드는 기업'\n * },\n * 'home.hero.image': {\n * type: 'image',\n * url: '/uploads/hero.jpg',\n * alt: 'Hero',\n * width: 1920,\n * height: 1080\n * }\n * },\n * publishedAt: new Date(),\n * publishedBy: 'admin@futureplay.com'\n * })\n * .onConflictDoUpdate({\n * target: [cmsPublishedCache.section, cmsPublishedCache.locale],\n * set: {\n * content: sql`EXCLUDED.content`,\n * publishedAt: sql`EXCLUDED.published_at`,\n * publishedBy: sql`EXCLUDED.published_by`,\n * version: sql`${cmsPublishedCache.version} + 1`\n * }\n * });\n *\n * // 캐시 조회 (초고속!)\n * const cache = await db.select()\n * .from(cmsPublishedCache)\n * .where(and(\n * eq(cmsPublishedCache.section, 'home'),\n * eq(cmsPublishedCache.locale, 'ko')\n * ))\n * .limit(1);\n *\n * const labels = cache[0].content; // 즉시 사용 가능!\n *\n * // 섹션의 모든 언어 캐시 조회\n * const allLocales = await db.select()\n * .from(cmsPublishedCache)\n * .where(eq(cmsPublishedCache.section, 'home'));\n *\n * // 오래된 캐시 감지\n * const stale = await db.select()\n * .from(cmsPublishedCache)\n * .where(lt(\n * cmsPublishedCache.publishedAt,\n * new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)\n * ));\n */","/**\n * CMS Audit Logs Entity\n *\n * CMS의 모든 변경사항을 추적합니다.\n * - 누가 (userId, userName)\n * - 언제 (createdAt)\n * - 무엇을 (action, changes)\n * - 왜 (metadata)\n */\n\nimport { serial, integer, text, jsonb, timestamp, index } from 'drizzle-orm/pg-core';\nimport { createFunctionSchema } from '@spfn/core/db';\nimport { cmsLabels } from '@/server/entities/cms-labels';\n\n// Create isolated schema for @spfn/cms\nconst schema = createFunctionSchema('@spfn/cms');\n\nexport const cmsAuditLogs = schema.table('audit_logs', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // Foreign Key: cms_labels (nullable - 라벨 삭제 시 로그는 유지)\n labelId: integer('label_id')\n .references(() => cmsLabels.id, { onDelete: 'set null' }),\n\n // 작업 유형\n action: text('action').notNull(),\n // \"create\" | \"update\" | \"publish\" | \"unpublish\" | \"archive\" | \"delete\" | \"rollback\" | \"duplicate\"\n\n // 사용자 정보\n userId: text('user_id').notNull(),\n userName: text('user_name'),\n\n // 변경 내용 (before/after)\n changes: jsonb('changes'),\n // { before: {...}, after: {...} }\n\n // 추가 메타데이터\n metadata: jsonb('metadata'),\n // { version: number, ip: string, userAgent: string, ... }\n\n // 작업 시각\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // 인덱스: labelId로 이력 조회 최적화\n index('cms_audit_logs_label_id_idx').on(table.labelId),\n\n // 인덱스: userId로 사용자 활동 조회 최적화\n index('cms_audit_logs_user_id_idx').on(table.userId),\n\n // 인덱스: action 필터링 최적화\n index('cms_audit_logs_action_idx').on(table.action),\n\n // 인덱스: 시간순 조회 최적화\n index('cms_audit_logs_created_at_idx').on(table.createdAt),\n]);\n\n// 타입 추론\nexport type CmsAuditLog = typeof cmsAuditLogs.$inferSelect;\nexport type NewCmsAuditLog = typeof cmsAuditLogs.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // 라벨 생성 로그\n * await db.insert(cmsAuditLogs).values({\n * labelId: 1,\n * action: 'create',\n * userId: 'user123',\n * userName: '김철수',\n * changes: {\n * before: null,\n * after: {\n * key: 'home.hero.title',\n * section: 'home',\n * type: 'text'\n * }\n * },\n * metadata: {\n * ip: '192.168.1.1',\n * userAgent: 'Mozilla/5.0...'\n * }\n * });\n *\n * // 발행 로그\n * await db.insert(cmsAuditLogs).values({\n * labelId: 1,\n * action: 'publish',\n * userId: 'admin123',\n * userName: '관리자',\n * changes: {\n * before: { status: 'draft', publishedVersion: null },\n * after: { status: 'published', publishedVersion: 2 }\n * },\n * metadata: {\n * version: 2,\n * notes: '신규 브랜딩 적용'\n * }\n * });\n *\n * // 라벨별 이력 조회\n * const logs = await db.select()\n * .from(cmsAuditLogs)\n * .where(eq(cmsAuditLogs.labelId, 1))\n * .orderBy(desc(cmsAuditLogs.createdAt))\n * .limit(20);\n *\n * // 사용자별 활동 조회\n * const userActivity = await db.select()\n * .from(cmsAuditLogs)\n * .where(eq(cmsAuditLogs.userId, 'user123'))\n * .orderBy(desc(cmsAuditLogs.createdAt));\n *\n * // 최근 24시간 변경 이력\n * const recent = await db.select()\n * .from(cmsAuditLogs)\n * .where(gte(cmsAuditLogs.createdAt, new Date(Date.now() - 24 * 60 * 60 * 1000)))\n * .orderBy(desc(cmsAuditLogs.createdAt));\n */","/**\n * CMS Label Values Repository\n *\n * 라벨 값 관리를 위한 Repository\n */\n\nimport { findOne, findMany, create, updateOne, deleteMany } from '@spfn/core/db';\nimport { eq, and, SQL, isNull } from 'drizzle-orm';\nimport { cmsLabelValues, type CmsLabelValue, type NewCmsLabelValue } from '@/server/entities';\n\n/**\n * 특정 라벨의 특정 버전 값들 조회\n */\nexport async function findByLabelIdAndVersion(\n labelId: number,\n version: number,\n options?: {\n locale?: string;\n breakpoint?: string | null;\n }\n): Promise<CmsLabelValue[]>\n{\n const { locale, breakpoint } = options || {};\n\n const conditions: SQL[] = [\n eq(cmsLabelValues.labelId, labelId),\n eq(cmsLabelValues.version, version)\n ];\n\n if (locale)\n {\n conditions.push(eq(cmsLabelValues.locale, locale));\n }\n\n if (breakpoint !== undefined)\n {\n conditions.push(\n breakpoint === null\n ? isNull(cmsLabelValues.breakpoint)\n : eq(cmsLabelValues.breakpoint, breakpoint)\n );\n }\n\n return findMany(cmsLabelValues, {\n where: and(...conditions)\n });\n}\n\n/**\n * 값 저장 (upsert)\n * - version: null → Draft 저장 (덮어쓰기)\n * - version: number → Published 버전 생성 (불변)\n */\nexport async function upsert(data: NewCmsLabelValue): Promise<CmsLabelValue>\n{\n // 기존 값이 있는지 확인\n const versionCondition = data.version === null || data.version === undefined\n ? isNull(cmsLabelValues.version)\n : eq(cmsLabelValues.version, data.version as number);\n\n const existing = await findOne(\n cmsLabelValues,\n and(\n eq(cmsLabelValues.labelId, data.labelId),\n versionCondition,\n eq(cmsLabelValues.locale, data.locale || 'ko'),\n data.breakpoint\n ? eq(cmsLabelValues.breakpoint, data.breakpoint)\n : isNull(cmsLabelValues.breakpoint)\n )\n );\n\n if (existing)\n {\n // UPDATE (only for drafts with version: null)\n if (data.version === null || data.version === undefined)\n {\n const updated = await updateOne(\n cmsLabelValues,\n { id: existing.id },\n { value: data.value }\n );\n return updated!;\n }\n else\n {\n // Published versions are immutable - this shouldn't happen\n throw new Error(`Published version ${data.version} already exists and cannot be overwritten`);\n }\n }\n else\n {\n // INSERT (both draft and new published versions)\n return create(cmsLabelValues, data);\n }\n}\n\n/**\n * Draft 값들 조회 (version = null)\n */\nexport async function findDraftsByLabelId(labelId: number): Promise<CmsLabelValue[]>\n{\n return findMany(cmsLabelValues, {\n where: and(\n eq(cmsLabelValues.labelId, labelId),\n isNull(cmsLabelValues.version)\n )\n });\n}\n\n/**\n * 여러 값 일괄 저장\n */\nexport async function upsertMany(values: NewCmsLabelValue[]): Promise<CmsLabelValue[]>\n{\n const results = [];\n for (const value of values)\n {\n const result = await upsert(value);\n results.push(result);\n }\n return results;\n}\n\n/**\n * 특정 버전의 모든 값 삭제\n */\nexport async function deleteByVersion(labelId: number, version: number): Promise<CmsLabelValue[]>\n{\n return deleteMany(\n cmsLabelValues,\n and(\n eq(cmsLabelValues.labelId, labelId),\n eq(cmsLabelValues.version, version)\n )\n );\n}\n\n// Legacy export for backward compatibility\nexport const cmsLabelValuesRepository = {\n findByLabelIdAndVersion,\n findDraftsByLabelId,\n upsert,\n upsertMany,\n deleteByVersion\n};","/**\n * CMS Draft Cache Repository\n *\n * 관리자별 초안 캐시 관리 (동시 편집 지원)\n */\n\nimport { findOne, findMany, deleteOne, deleteMany, upsert as upsertHelper } from '@spfn/core/db';\nimport { eq, and, lt } from 'drizzle-orm';\nimport { cmsDraftCache, type NewCmsDraftCache } from '@/server/entities';\n\n/**\n * 섹션 + 언어 + 사용자로 초안 캐시 조회\n */\nexport async function findByUser(section: string, locale: string, userId: string)\n{\n return findOne(\n cmsDraftCache,\n and(\n eq(cmsDraftCache.section, section),\n eq(cmsDraftCache.locale, locale),\n eq(cmsDraftCache.userId, userId)\n )\n );\n}\n\n/**\n * 초안 캐시 생성 또는 업데이트 (UPSERT)\n */\nexport async function upsert(data: NewCmsDraftCache)\n{\n return upsertHelper(cmsDraftCache, data, {\n target: [cmsDraftCache.section, cmsDraftCache.locale, cmsDraftCache.userId],\n set: {\n content: data.content,\n updatedAt: new Date(),\n }\n });\n}\n\n/**\n * 특정 사용자의 모든 초안 조회\n */\nexport async function findAllByUser(userId: string)\n{\n return findMany(cmsDraftCache, {\n where: eq(cmsDraftCache.userId, userId)\n });\n}\n\n/**\n * 초안 삭제\n */\nexport async function deleteByUser(section: string, locale: string, userId: string)\n{\n await deleteOne(\n cmsDraftCache,\n and(\n eq(cmsDraftCache.section, section),\n eq(cmsDraftCache.locale, locale),\n eq(cmsDraftCache.userId, userId)\n )\n );\n}\n\n/**\n * 오래된 초안 정리 (30일 이상 미사용)\n */\nexport async function cleanupOldDrafts(daysOld: number = 30)\n{\n const cutoffDate = new Date();\n cutoffDate.setDate(cutoffDate.getDate() - daysOld);\n\n return deleteMany(\n cmsDraftCache,\n lt(cmsDraftCache.updatedAt, cutoffDate)\n );\n}\n\nexport const cmsDraftCacheRepository = {\n findByUser,\n upsert,\n findAllByUser,\n deleteByUser,\n cleanupOldDrafts,\n};","/**\n * CMS Published Cache Repository\n *\n * 발행된 콘텐츠 캐시 관리 (초고속 조회)\n */\n\nimport { findOne, findMany, deleteOne, deleteMany, upsert as upsertHelper } from '@spfn/core/db';\nimport { eq, and, sql } from 'drizzle-orm';\nimport { cmsPublishedCache, type NewCmsPublishedCache } from '@/server/entities';\n\n/**\n * 섹션 + 언어로 발행된 캐시 조회\n */\nexport async function findBySection(section: string, locale: string = 'ko')\n{\n return findOne(\n cmsPublishedCache,\n and(\n eq(cmsPublishedCache.section, section),\n eq(cmsPublishedCache.locale, locale)\n )\n );\n}\n\n/**\n * 캐시 생성 또는 업데이트 (UPSERT)\n */\nexport async function upsert(data: NewCmsPublishedCache)\n{\n return upsertHelper(cmsPublishedCache, data, {\n target: [cmsPublishedCache.section, cmsPublishedCache.locale],\n set: {\n content: data.content,\n publishedAt: data.publishedAt,\n publishedBy: data.publishedBy,\n version: sql`${cmsPublishedCache.version} + 1`, // 버전 증가로 클라이언트 캐시 무효화\n }\n });\n}\n\n/**\n * 섹션별 모든 언어 캐시 조회\n */\nexport async function findAllLanguages(section: string)\n{\n return findMany(cmsPublishedCache, {\n where: eq(cmsPublishedCache.section, section)\n });\n}\n\n/**\n * 캐시 삭제\n */\nexport async function deleteBySection(section: string, locale?: string)\n{\n if (locale)\n {\n await deleteOne(\n cmsPublishedCache,\n and(\n eq(cmsPublishedCache.section, section),\n eq(cmsPublishedCache.locale, locale)\n )\n );\n }\n else\n {\n await deleteMany(\n cmsPublishedCache,\n eq(cmsPublishedCache.section, section)\n );\n }\n}\n\nexport const cmsPublishedCacheRepository = {\n findBySection,\n upsert,\n findAllLanguages,\n deleteBySection,\n};","import { Type } from '@sinclair/typebox';\nimport type { RouteContract } from '@spfn/core/route';\n\n/**\n * CMS Labels Contracts\n *\n * 라벨 메타데이터 관리 API\n */\n\n/**\n * GET /_cms/labels - 라벨 목록 조회\n */\nexport const getLabelsContract = {\n method: 'GET' as const,\n path: '/_cms/labels',\n query: Type.Object({\n section: Type.Optional(Type.String({ description: '섹션으로 필터링 (예: home, why-futureplay)' })),\n limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100, default: 20, description: '페이지당 항목 수' })),\n offset: Type.Optional(Type.Number({ minimum: 0, default: 0, description: '시작 오프셋' })),\n includeDefaultValues: Type.Optional(Type.Boolean({ description: '기본값 포함 여부', default: false }))\n }),\n response: Type.Object({\n labels: Type.Array(Type.Object({\n id: Type.Number(),\n key: Type.String(),\n section: Type.String(),\n type: Type.String(),\n description: Type.Union([Type.String(), Type.Null()], { description: '라벨 설명' }),\n publishedVersion: Type.Union([Type.Number(), Type.Null()]),\n createdBy: Type.Union([Type.String(), Type.Null()]),\n createdAt: Type.String(),\n updatedAt: Type.String(),\n defaultValue: Type.Optional(Type.Any({ description: '라벨 정의 파일의 기본값' }))\n })),\n total: Type.Number(),\n limit: Type.Number(),\n offset: Type.Number()\n })\n} as const satisfies RouteContract;\n\n/**\n * POST /_cms/labels - 새 라벨 생성\n */\nexport const createLabelContract = {\n method: 'POST' as const,\n path: '/_cms/labels',\n body: Type.Object({\n key: Type.String({\n description: '고유 키 (예: home.hero.title)',\n pattern: '^[a-z0-9-]+\\\\.[a-z0-9-]+\\\\.[a-z0-9-]+$'\n }),\n section: Type.String({\n description: '섹션 이름 (예: home, why-futureplay)',\n pattern: '^[a-z0-9-]+$'\n }),\n type: Type.Union([\n Type.Literal('text'),\n Type.Literal('image'),\n Type.Literal('video'),\n Type.Literal('file'),\n Type.Literal('object')\n ], { description: '값 타입' }),\n createdBy: Type.Optional(Type.String({ description: '생성자 ID' }))\n }),\n response: Type.Union([\n Type.Object({\n id: Type.Number(),\n key: Type.String(),\n section: Type.String(),\n type: Type.String(),\n publishedVersion: Type.Union([Type.Number(), Type.Null()]),\n createdBy: Type.Union([Type.String(), Type.Null()]),\n createdAt: Type.String(),\n updatedAt: Type.String()\n }),\n Type.Object({\n error: Type.String(),\n key: Type.Optional(Type.String())\n })\n ])\n} as const satisfies RouteContract;\n\n/**\n * GET /_cms/labels/:id - 라벨 단건 조회\n */\nexport const getLabelContract = {\n method: 'GET' as const,\n path: '/_cms/labels/:id',\n params: Type.Object({\n id: Type.String({ description: '라벨 ID' })\n }),\n response: Type.Union([\n Type.Object({\n id: Type.Number(),\n key: Type.String(),\n section: Type.String(),\n type: Type.String(),\n description: Type.Union([Type.String(), Type.Null()]),\n publishedVersion: Type.Union([Type.Number(), Type.Null()]),\n createdBy: Type.Union([Type.String(), Type.Null()]),\n createdAt: Type.String(),\n updatedAt: Type.String()\n }),\n Type.Object({\n error: Type.String()\n })\n ])\n} as const satisfies RouteContract;\n\n/**\n * PATCH /_cms/labels/:id - 라벨 메타데이터 수정\n */\nexport const updateLabelContract = {\n method: 'PATCH' as const,\n path: '/_cms/labels/:id',\n params: Type.Object({\n id: Type.String({ description: '라벨 ID' })\n }),\n body: Type.Object({\n section: Type.Optional(Type.String({ description: '섹션 변경' })),\n type: Type.Optional(Type.Union([\n Type.Literal('text'),\n Type.Literal('image'),\n Type.Literal('video'),\n Type.Literal('file'),\n Type.Literal('object')\n ]))\n }),\n response: Type.Union([\n Type.Object({\n id: Type.Number(),\n key: Type.String(),\n section: Type.String(),\n type: Type.String(),\n description: Type.Union([Type.String(), Type.Null()]),\n publishedVersion: Type.Union([Type.Number(), Type.Null()]),\n createdBy: Type.Union([Type.String(), Type.Null()]),\n createdAt: Type.String(),\n updatedAt: Type.String()\n }),\n Type.Object({\n error: Type.String()\n })\n ])\n} as const satisfies RouteContract;\n\n/**\n * DELETE /_cms/labels/:id - 라벨 삭제\n */\nexport const deleteLabelContract = {\n method: 'DELETE' as const,\n path: '/_cms/labels/:id',\n params: Type.Object({\n id: Type.String({ description: '라벨 ID' })\n }),\n response: Type.Union([\n Type.Object({\n success: Type.Boolean(),\n id: Type.Number()\n }),\n Type.Object({\n error: Type.String()\n })\n ])\n} as const satisfies RouteContract;\n\n/**\n * GET /_cms/labels/by-key/:key - Key로 라벨 조회\n */\nexport const getLabelByKeyContract = {\n method: 'GET' as const,\n path: '/_cms/labels/by-key/:key',\n params: Type.Object({\n key: Type.String({ description: '라벨 Key (예: home.hero.title)' })\n }),\n response: Type.Union([\n Type.Object({\n id: Type.Number(),\n key: Type.String(),\n section: Type.String(),\n type: Type.String(),\n description: Type.Union([Type.String(), Type.Null()]),\n publishedVersion: Type.Union([Type.Number(), Type.Null()]),\n createdBy: Type.Union([Type.String(), Type.Null()]),\n createdAt: Type.String(),\n updatedAt: Type.String()\n }),\n Type.Object({\n error: Type.String(),\n key: Type.Optional(Type.String())\n })\n ])\n} as const satisfies RouteContract;\n\n/**\n * POST /_cms/labels/:labelId/publish - 라벨 발행 (Draft → Published)\n */\nexport const publishLabelContract = {\n method: 'POST' as const,\n path: '/_cms/labels/:labelId/publish',\n params: Type.Object({\n labelId: Type.String({ description: '라벨 ID' })\n }),\n body: Type.Object({\n notes: Type.Optional(Type.String({ description: '발행 노트 (버전 설명)' })),\n publishedBy: Type.Optional(Type.String({ description: '발행자 ID' }))\n }),\n response: Type.Union([\n Type.Object({\n success: Type.Boolean(),\n labelId: Type.Number(),\n version: Type.Number(),\n message: Type.String()\n }),\n Type.Object({\n error: Type.String()\n })\n ])\n} as const satisfies RouteContract;\n\n/**\n * GET /_cms/labels/:labelId/admin - 관리자용 라벨 조회 (Draft + Published + Status)\n */\nexport const getAdminLabelContract = {\n method: 'GET' as const,\n path: '/_cms/labels/:labelId/admin',\n params: Type.Object({\n labelId: Type.String({ description: '라벨 ID' })\n }),\n response: Type.Union([\n Type.Object({\n label: Type.Object({\n id: Type.Number(),\n key: Type.String(),\n section: Type.String(),\n type: Type.String(),\n description: Type.Union([Type.String(), Type.Null()]),\n publishedVersion: Type.Union([Type.Number(), Type.Null()]),\n createdBy: Type.Union([Type.String(), Type.Null()]),\n createdAt: Type.String(),\n updatedAt: Type.String()\n }),\n draft: Type.Array(Type.Object({\n id: Type.Number(),\n labelId: Type.Number(),\n version: Type.Null(),\n locale: Type.String(),\n breakpoint: Type.Union([Type.String(), Type.Null()]),\n value: Type.Any(),\n createdAt: Type.String()\n })),\n published: Type.Array(Type.Object({\n id: Type.Number(),\n labelId: Type.Number(),\n version: Type.Number(),\n locale: Type.String(),\n breakpoint: Type.Union([Type.String(), Type.Null()]),\n value: Type.Any(),\n createdAt: Type.String()\n })),\n status: Type.Union([\n Type.Literal('default-only'),\n Type.Literal('unpublished'),\n Type.Literal('published'),\n Type.Literal('modified')\n ])\n }),\n Type.Object({\n error: Type.String()\n })\n ])\n} as const satisfies RouteContract;\n\n/**\n * GET /_cms/labels/:labelId/versions - 라벨 버전 히스토리 조회\n */\nexport const getLabelVersionsContract = {\n method: 'GET' as const,\n path: '/_cms/labels/:labelId/versions',\n params: Type.Object({\n labelId: Type.String({ description: '라벨 ID' })\n }),\n response: Type.Union([\n Type.Object({\n versions: Type.Array(Type.Object({\n version: Type.Number({ description: '버전 번호' }),\n publishedAt: Type.String({ description: '발행 시각 (ISO 8601)' }),\n publishedBy: Type.Union([Type.String(), Type.Null()], { description: '발행자 ID' }),\n notes: Type.Union([Type.String(), Type.Null()], { description: '발행 노트' }),\n values: Type.Array(Type.Object({\n id: Type.Number(),\n locale: Type.String(),\n breakpoint: Type.Union([Type.String(), Type.Null()]),\n value: Type.Any(),\n createdAt: Type.String()\n }))\n }))\n }),\n Type.Object({\n error: Type.String()\n })\n ])\n} as const satisfies RouteContract;","/**\n * CMS Sync Utilities\n *\n * JSON 파일 기반 라벨 동기화\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from 'fs';\nimport { basename, extname, join } from 'path';\nimport { extractLabels } from '@/server/labels';\nimport { cmsLabelsRepository, cmsPublishedCacheRepository } from '@/server/repositories';\nimport { DEFAULT_LABELS_DIR } from '@/lib/constants';\nimport type { NestedLabels, SectionDefinition, SyncOptions, SyncResult } from '@/lib/types';\n\n/**\n * 여러 섹션 동기화\n */\nexport async function syncAll(\n sections: SectionDefinition[],\n options: SyncOptions = {}\n): Promise<SyncResult[]>\n{\n const results: SyncResult[] = [];\n\n for (const definition of sections)\n {\n const result = await syncSection(definition, options);\n results.push(result);\n }\n\n return results;\n}\n\n/**\n * JSON 파일에서 라벨 로드\n */\nexport function loadLabelsFromJson(labelsDir: string): SectionDefinition[]\n{\n const sections: SectionDefinition[] = [];\n\n if (!existsSync(labelsDir))\n {\n console.warn(`[CMS] Labels directory not found: ${labelsDir}`);\n console.warn(`[CMS] Expected directory structure:`);\n console.warn(`[CMS] ${labelsDir}/`);\n console.warn(`[CMS] ├── common/ # Section directory`);\n console.warn(`[CMS] │ ├── messages.json`);\n console.warn(`[CMS] │ └── errors.json`);\n console.warn(`[CMS] └── home/ # Section directory`);\n console.warn(`[CMS] └── hero.json`);\n return sections;\n }\n\n try\n {\n const entries = readdirSync(labelsDir);\n\n if (entries.length === 0)\n {\n console.warn(`[CMS] Labels directory is empty: ${labelsDir}`);\n console.warn(`[CMS] Create section directories with JSON files inside`);\n return sections;\n }\n\n const jsonFiles = entries.filter(e => extname(e) === '.json');\n if (jsonFiles.length > 0)\n {\n console.warn(`[CMS] Found JSON files directly in ${labelsDir}:`);\n jsonFiles.forEach(f => console.warn(`[CMS] - ${f} (will be ignored)`));\n console.warn(`[CMS] JSON files should be inside section directories`);\n console.warn(`[CMS] Example: Move ${jsonFiles[0]} to ${labelsDir}/${basename(jsonFiles[0], '.json')}/${jsonFiles[0]}`);\n }\n\n for (const entry of entries)\n {\n const sectionPath = join(labelsDir, entry);\n const stat = statSync(sectionPath);\n\n if (stat.isDirectory())\n {\n const sectionName = entry;\n const labels = loadSectionLabels(sectionPath);\n\n if (Object.keys(labels).length > 0)\n {\n sections.push({ section: sectionName, labels });\n }\n else\n {\n console.warn(`[CMS] Section directory \"${sectionName}\" has no valid JSON files`);\n }\n }\n }\n\n if (sections.length === 0)\n {\n console.warn(`[CMS] No valid section directories found in ${labelsDir}`);\n }\n }\n catch (error)\n {\n console.warn(`[CMS] Could not scan labels directory: ${labelsDir}`);\n console.error(`[CMS] Error:`, error);\n }\n\n return sections;\n}\n\nfunction loadSectionLabels(sectionPath: string): NestedLabels\n{\n const labels: NestedLabels = {};\n\n try\n {\n const files = readdirSync(sectionPath);\n\n for (const file of files)\n {\n if (extname(file) === '.json')\n {\n const filePath = join(sectionPath, file);\n const categoryName = basename(file, '.json');\n\n try\n {\n const content = readFileSync(filePath, 'utf-8');\n labels[categoryName] = JSON.parse(content);\n }\n catch (error)\n {\n console.warn(`[CMS] Failed to parse ${filePath}`);\n }\n }\n }\n }\n catch (error)\n {\n console.warn(`[CMS] Could not read section directory: ${sectionPath}`);\n }\n\n return labels;\n}\n\n/**\n * 섹션 라벨 동기화\n */\nexport async function syncSection(\n definition: SectionDefinition,\n options: SyncOptions = {}\n): Promise<SyncResult>\n{\n const {\n dryRun = false,\n updateExisting = false,\n removeUnused = false,\n verbose = false,\n } = options;\n\n const { section } = definition;\n const result: SyncResult = {\n section,\n created: 0,\n updated: 0,\n deleted: 0,\n unchanged: 0,\n errors: [],\n };\n\n try\n {\n const definedLabels = extractLabels(definition);\n const definedKeys = new Set(definedLabels.map((l) => l.key));\n const existingLabels = await cmsLabelsRepository.findBySection(section);\n const existingMap = new Map(existingLabels.map((l) => [l.key, l]));\n\n if (verbose)\n {\n console.log(`\\n[${section}] Found ${definedLabels.length} labels in definition`);\n console.log(`[${section}] Found ${existingLabels.length} labels in DB`);\n }\n\n // 생성 및 업데이트\n for (const label of definedLabels)\n {\n const existing = existingMap.get(label.key);\n\n if (!existing)\n {\n if (verbose) console.log(` [CREATE] ${label.key}`);\n\n if (!dryRun)\n {\n try\n {\n const defaultValue = typeof label.defaultValue === 'object'\n ? JSON.stringify(label.defaultValue)\n : label.defaultValue;\n\n await cmsLabelsRepository.create({\n section,\n key: label.key,\n type: label.type || 'text', // 라벨 타입 (기본값: 'text')\n defaultValue,\n description: label.description,\n });\n }\n catch (error)\n {\n result.errors.push({\n key: label.key,\n error: error instanceof Error ? error.message : String(error),\n });\n continue;\n }\n }\n\n result.created++;\n }\n else if (updateExisting)\n {\n const newDefaultValue = typeof label.defaultValue === 'object'\n ? JSON.stringify(label.defaultValue)\n : label.defaultValue;\n\n const hasChanged = existing.defaultValue !== newDefaultValue;\n\n if (hasChanged)\n {\n if (verbose)\n {\n console.log(` [UPDATE] ${label.key}`);\n console.log(` Old: \"${existing.defaultValue}\"`);\n console.log(` New: \"${newDefaultValue}\"`);\n }\n\n if (!dryRun)\n {\n try\n {\n await cmsLabelsRepository.updateById(existing.id, {\n defaultValue: newDefaultValue,\n description: label.description,\n });\n }\n catch (error)\n {\n result.errors.push({\n key: label.key,\n error: error instanceof Error ? error.message : String(error),\n });\n continue;\n }\n }\n\n result.updated++;\n }\n else\n {\n result.unchanged++;\n }\n }\n else\n {\n result.unchanged++;\n }\n }\n\n // 사용되지 않는 라벨 삭제\n if (removeUnused)\n {\n for (const existing of existingLabels)\n {\n if (!definedKeys.has(existing.key))\n {\n if (verbose) console.log(` [DELETE] ${existing.key}`);\n\n if (!dryRun)\n {\n try\n {\n await cmsLabelsRepository.deleteById(existing.id);\n }\n catch (error)\n {\n result.errors.push({\n key: existing.key,\n error: error instanceof Error ? error.message : String(error),\n });\n continue;\n }\n }\n\n result.deleted++;\n }\n }\n }\n\n // Published cache 업데이트\n if (!dryRun && (result.created > 0 || result.updated > 0 || result.deleted > 0))\n {\n if (verbose) console.log(` [CACHE] Updating published cache for section: ${section}`);\n await updatePublishedCache(section);\n }\n }\n catch (error)\n {\n result.errors.push({\n key: '__section__',\n error: error instanceof Error ? error.message : String(error),\n });\n }\n\n return result;\n}\n\n/**\n * Published Cache 업데이트\n */\nasync function updatePublishedCache(section: string): Promise<void>\n{\n const labels = await cmsLabelsRepository.findBySection(section);\n const localesSet = new Set<string>();\n const labelsByLocale: Record<string, Record<string, any>> = {};\n\n labels.forEach((label) =>\n {\n try\n {\n const parsed = JSON.parse(label.defaultValue || '{}');\n\n if (typeof parsed === 'object' && !Array.isArray(parsed))\n {\n // Multilingual\n Object.keys(parsed).forEach((locale) => localesSet.add(locale));\n Object.entries(parsed).forEach(([locale, value]) =>\n {\n if (!labelsByLocale[locale]) labelsByLocale[locale] = {};\n labelsByLocale[locale][label.key] = value;\n });\n }\n else\n {\n // Single value\n if (!labelsByLocale.ko) labelsByLocale.ko = {};\n labelsByLocale.ko[label.key] = label.defaultValue;\n localesSet.add('ko');\n }\n }\n catch\n {\n // Plain string\n if (!labelsByLocale.ko) labelsByLocale.ko = {};\n labelsByLocale.ko[label.key] = label.defaultValue;\n localesSet.add('ko');\n }\n });\n\n const timestamp = new Date();\n for (const locale of localesSet)\n {\n await cmsPublishedCacheRepository.upsert({\n section,\n locale,\n content: labelsByLocale[locale] || {},\n publishedAt: timestamp,\n publishedBy: 'system',\n });\n }\n}\n\n/**\n * Initialize label sync for server startup\n *\n * Call this in your server.config.ts beforeRoutes hook\n *\n * @param options - Sync options\n * @param options.labelsDir - Path to labels directory (default: 'src/lib/labels')\n *\n * @example\n * ```typescript\n * import { initLabelSync } from '@spfn/cms';\n *\n * export default {\n * beforeRoutes: async (app) => {\n * await initLabelSync({ verbose: true });\n * },\n * } satisfies ServerConfig;\n * ```\n */\nexport async function initLabelSync(options: SyncOptions & { labelsDir?: string } = {}): Promise<void>\n{\n const isDevelopment = process.env.NODE_ENV === 'development';\n const verbose = options.verbose ?? isDevelopment;\n const labelsDir = options.labelsDir ?? DEFAULT_LABELS_DIR;\n\n if (verbose)\n {\n console.log('\\n🔄 Initializing label sync...\\n');\n }\n\n // Load labels from JSON files\n const sections = loadLabelsFromJson(labelsDir);\n\n if (sections.length === 0)\n {\n if (verbose)\n {\n console.log('⚠️ No labels found in', labelsDir);\n console.log('');\n }\n return;\n }\n\n const results = await syncAll(sections, {\n updateExisting: true, // 🔄 항상 업데이트 (프로덕션 포함)\n ...options,\n verbose,\n });\n\n const totalCreated = results.reduce((sum, r) => sum + r.created, 0);\n const totalUpdated = results.reduce((sum, r) => sum + r.updated, 0);\n const totalUnchanged = results.reduce((sum, r) => sum + r.unchanged, 0);\n const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);\n\n if (verbose)\n {\n console.log('✅ Label sync completed\\n');\n console.log(` Sections: ${results.length}`);\n console.log(` Created: ${totalCreated}`);\n console.log(` Updated: ${totalUpdated}`);\n console.log(` Unchanged: ${totalUnchanged}`);\n\n if (totalErrors > 0)\n {\n console.log(` Errors: ${totalErrors}\\n`);\n }\n else\n {\n console.log('');\n }\n }\n\n // Log errors\n if (totalErrors > 0)\n {\n results.forEach((result) =>\n {\n result.errors.forEach((error) =>\n {\n console.error(`[${result.section}] ${error.key}: ${error.error}`);\n });\n });\n }\n}","/**\n * CMS Label Helpers\n *\n * Utilities for processing label definitions from JSON files\n */\n\nimport type { SectionDefinition, NestedLabels, FlatLabel, LabelDefinition } from '@/lib/types';\n\n/**\n * 중첩된 라벨을 플랫화\n *\n * @param labels - 중첩된 라벨 객체\n * @returns 플랫화된 라벨 배열\n *\n * @example\n * ```ts\n * flattenLabels({\n * nav: {\n * home: { key: 'layout.nav.home', defaultValue: 'Home' },\n * },\n * });\n * // => [{ key: 'layout.nav.home', defaultValue: 'Home' }]\n * ```\n */\nexport function flattenLabels(labels: NestedLabels): FlatLabel[]\n{\n const result: FlatLabel[] = [];\n\n function isLabelDefinition(obj: NestedLabels | LabelDefinition): obj is LabelDefinition\n {\n return (\n 'key' in obj &&\n 'defaultValue' in obj &&\n typeof obj.key === 'string' &&\n (typeof obj.defaultValue === 'string' || typeof obj.defaultValue === 'object')\n );\n }\n\n function traverse(obj: NestedLabels | LabelDefinition)\n {\n if (isLabelDefinition(obj))\n {\n // LabelDefinition인 경우\n result.push({\n key: obj.key,\n defaultValue: obj.defaultValue,\n description: obj.description,\n });\n }\n else\n {\n // NestedLabels인 경우\n Object.values(obj).forEach((value) =>\n {\n if (typeof value === 'object' && value !== null)\n {\n traverse(value);\n }\n });\n }\n }\n\n traverse(labels);\n return result;\n}\n\n/**\n * 섹션 정의에서 모든 라벨 추출\n *\n * @param definition - 섹션 정의\n * @returns 플랫화된 라벨 배열\n */\nexport function extractLabels(definition: SectionDefinition): FlatLabel[]\n{\n return flattenLabels(definition.labels);\n}\n","/**\n * CMS Constants\n *\n * CMS 패키지에서 사용하는 전역 상수\n */\n\n/**\n * 기본 라벨 디렉토리 경로\n *\n * JSON 라벨 파일이 저장되는 기본 디렉토리입니다.\n * 프로젝트 루트 기준 상대 경로입니다.\n *\n * @example\n * ```typescript\n * import { DEFAULT_LABELS_DIR } from '@spfn/cms';\n *\n * console.log(DEFAULT_LABELS_DIR); // 'src/lib/labels'\n * ```\n */\nexport const DEFAULT_LABELS_DIR = 'src/lib/labels';"],"mappings":"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","names":["serial","integer","text","timestamp","index","createFunctionSchema","schema","createFunctionSchema","serial","integer","text","timestamp","index","serial","text","jsonb","timestamp","index","unique","createFunctionSchema","schema","serial","text","jsonb","integer","timestamp","index","unique","createFunctionSchema","schema","serial","integer","text","jsonb","timestamp","index","createFunctionSchema","schema","createFunctionSchema","serial","integer","text","jsonb","timestamp","index","findOne","findMany","create","updateOne","findOne","findMany","deleteOne","deleteMany","eq","and","findOne","findMany","deleteOne","deleteMany","upsertHelper","eq","and","join"]}