@roottale/cms-client 0.25.0 → 0.33.0
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.
- package/CHANGELOG.md +23 -0
- package/dist/server.d.ts +283 -240
- package/dist/server.js +562 -512
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -2,346 +2,49 @@ import {
|
|
|
2
2
|
parseAttributionJson
|
|
3
3
|
} from "./chunk-FRCLSU55.js";
|
|
4
4
|
|
|
5
|
-
// src/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
lines.push(`<language>${escapeXml(lang)}</language>`);
|
|
24
|
-
if (lastBuild) lines.push(`<lastBuildDate>${lastBuild}</lastBuildDate>`);
|
|
25
|
-
for (const item of input.items) {
|
|
26
|
-
const guid = item.guid ?? item.link;
|
|
27
|
-
lines.push("<item>");
|
|
28
|
-
lines.push(`<title>${escapeXml(item.title)}</title>`);
|
|
29
|
-
lines.push(`<link>${escapeXml(item.link)}</link>`);
|
|
30
|
-
lines.push(`<guid isPermaLink="false">${escapeXml(guid)}</guid>`);
|
|
31
|
-
if (item.description) {
|
|
32
|
-
lines.push(`<description>${cdata(item.description)}</description>`);
|
|
33
|
-
}
|
|
34
|
-
const pub = toRfc822(item.pubDate);
|
|
35
|
-
if (pub) lines.push(`<pubDate>${pub}</pubDate>`);
|
|
36
|
-
lines.push("</item>");
|
|
37
|
-
}
|
|
38
|
-
lines.push("</channel>");
|
|
39
|
-
lines.push("</rss>");
|
|
40
|
-
return lines.join("\n");
|
|
41
|
-
}
|
|
42
|
-
function rssItemsFromPosts(posts, opts) {
|
|
43
|
-
return posts.map((p) => ({
|
|
44
|
-
title: p.title,
|
|
45
|
-
link: opts.postUrl(p),
|
|
46
|
-
guid: opts.postUrl(p),
|
|
47
|
-
description: p.excerpt,
|
|
48
|
-
pubDate: p.publishedAt
|
|
49
|
-
}));
|
|
50
|
-
}
|
|
51
|
-
function toRfc822(value) {
|
|
52
|
-
if (!value) return "";
|
|
53
|
-
const d = value instanceof Date ? value : new Date(value);
|
|
54
|
-
return Number.isNaN(d.getTime()) ? "" : d.toUTCString();
|
|
55
|
-
}
|
|
56
|
-
function articleSchema(input) {
|
|
57
|
-
const schema = {
|
|
58
|
-
"@context": "https://schema.org",
|
|
59
|
-
"@type": "Article",
|
|
60
|
-
headline: input.headline,
|
|
61
|
-
mainEntityOfPage: { "@type": "WebPage", "@id": input.url }
|
|
62
|
-
};
|
|
63
|
-
if (input.description) schema.description = input.description;
|
|
64
|
-
if (input.image) schema.image = input.image;
|
|
65
|
-
const pub = toIso(input.datePublished);
|
|
66
|
-
if (pub) schema.datePublished = pub;
|
|
67
|
-
const mod = toIso(input.dateModified);
|
|
68
|
-
if (mod) schema.dateModified = mod;
|
|
69
|
-
if (input.authorName)
|
|
70
|
-
schema.author = { "@type": "Person", name: input.authorName };
|
|
71
|
-
if (input.publisherName) {
|
|
72
|
-
schema.publisher = {
|
|
73
|
-
"@type": "Organization",
|
|
74
|
-
name: input.publisherName,
|
|
75
|
-
...input.publisherLogo ? { logo: { "@type": "ImageObject", url: input.publisherLogo } } : {}
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
return schema;
|
|
79
|
-
}
|
|
80
|
-
function breadcrumbSchema(items) {
|
|
81
|
-
return {
|
|
82
|
-
"@context": "https://schema.org",
|
|
83
|
-
"@type": "BreadcrumbList",
|
|
84
|
-
itemListElement: items.map((it, i) => ({
|
|
85
|
-
"@type": "ListItem",
|
|
86
|
-
position: i + 1,
|
|
87
|
-
name: it.name,
|
|
88
|
-
item: it.url
|
|
89
|
-
}))
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
function organizationSchema(input) {
|
|
93
|
-
return {
|
|
94
|
-
"@context": "https://schema.org",
|
|
95
|
-
"@type": "Organization",
|
|
96
|
-
name: input.name,
|
|
97
|
-
url: input.url,
|
|
98
|
-
...input.logo ? { logo: input.logo } : {},
|
|
99
|
-
...input.sameAs && input.sameAs.length > 0 ? { sameAs: input.sameAs } : {}
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
var DAY_OF_WEEK = {
|
|
103
|
-
Mo: "Monday",
|
|
104
|
-
Tu: "Tuesday",
|
|
105
|
-
We: "Wednesday",
|
|
106
|
-
Th: "Thursday",
|
|
107
|
-
Fr: "Friday",
|
|
108
|
-
Sa: "Saturday",
|
|
109
|
-
Su: "Sunday"
|
|
110
|
-
};
|
|
111
|
-
function localBusinessSchema(profile, opts) {
|
|
112
|
-
const input = profile;
|
|
113
|
-
const types = Array.from(
|
|
114
|
-
/* @__PURE__ */ new Set([input.businessType || "LocalBusiness", "LocalBusiness"])
|
|
115
|
-
);
|
|
116
|
-
const schema = {
|
|
117
|
-
"@context": "https://schema.org",
|
|
118
|
-
"@type": types,
|
|
119
|
-
name: input.name,
|
|
120
|
-
url: opts.url
|
|
121
|
-
};
|
|
122
|
-
if (input.legalName) schema.legalName = input.legalName;
|
|
123
|
-
if (opts.image) schema.image = opts.image;
|
|
124
|
-
if (input.telephone) schema.telephone = input.telephone;
|
|
125
|
-
if (input.email) schema.email = input.email;
|
|
126
|
-
if (input.address) {
|
|
127
|
-
schema.address = {
|
|
128
|
-
"@type": "PostalAddress",
|
|
129
|
-
streetAddress: input.address.streetAddress,
|
|
130
|
-
addressLocality: input.address.addressLocality,
|
|
131
|
-
...input.address.addressRegion ? { addressRegion: input.address.addressRegion } : {},
|
|
132
|
-
...input.address.postalCode ? { postalCode: input.address.postalCode } : {},
|
|
133
|
-
addressCountry: "KR"
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
if (input.geo) {
|
|
137
|
-
schema.geo = {
|
|
138
|
-
"@type": "GeoCoordinates",
|
|
139
|
-
latitude: input.geo.latitude,
|
|
140
|
-
longitude: input.geo.longitude
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
const openingHours = (input.openingHours ?? []).filter(
|
|
144
|
-
(row) => row.days.length > 0 && row.opens && row.closes
|
|
145
|
-
);
|
|
146
|
-
if (openingHours.length > 0) {
|
|
147
|
-
schema.openingHoursSpecification = openingHours.map((row) => ({
|
|
148
|
-
"@type": "OpeningHoursSpecification",
|
|
149
|
-
dayOfWeek: row.days.map((d) => DAY_OF_WEEK[d] ?? d),
|
|
150
|
-
opens: row.opens,
|
|
151
|
-
closes: row.closes
|
|
152
|
-
}));
|
|
153
|
-
}
|
|
154
|
-
if (input.priceRange) schema.priceRange = input.priceRange;
|
|
155
|
-
if (input.areaServed && input.areaServed.length > 0) {
|
|
156
|
-
schema.areaServed = input.areaServed;
|
|
157
|
-
}
|
|
158
|
-
const sameAs = Object.values(input.profiles ?? {}).filter(
|
|
159
|
-
(u) => typeof u === "string" && u.length > 0
|
|
160
|
-
);
|
|
161
|
-
if (sameAs.length > 0) schema.sameAs = sameAs;
|
|
162
|
-
return schema;
|
|
163
|
-
}
|
|
164
|
-
function websiteSchema(input) {
|
|
165
|
-
return {
|
|
166
|
-
"@context": "https://schema.org",
|
|
167
|
-
"@type": "WebSite",
|
|
168
|
-
name: input.name,
|
|
169
|
-
url: input.url,
|
|
170
|
-
...input.searchUrl ? {
|
|
171
|
-
potentialAction: {
|
|
172
|
-
"@type": "SearchAction",
|
|
173
|
-
target: `${input.searchUrl}{search_term_string}`,
|
|
174
|
-
"query-input": "required name=search_term_string"
|
|
175
|
-
}
|
|
176
|
-
} : {}
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
function faqSchema(qa) {
|
|
180
|
-
return {
|
|
181
|
-
"@context": "https://schema.org",
|
|
182
|
-
"@type": "FAQPage",
|
|
183
|
-
mainEntity: qa.map((x) => ({
|
|
184
|
-
"@type": "Question",
|
|
185
|
-
name: x.question,
|
|
186
|
-
acceptedAnswer: { "@type": "Answer", text: x.answer }
|
|
187
|
-
}))
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
function jsonLdScript(schema) {
|
|
191
|
-
const json = JSON.stringify(schema).replace(/</g, "\\u003c");
|
|
192
|
-
return `<script type="application/ld+json">${json}</script>`;
|
|
193
|
-
}
|
|
194
|
-
function toIso(value) {
|
|
195
|
-
if (!value) return null;
|
|
196
|
-
const d = value instanceof Date ? value : new Date(value);
|
|
197
|
-
return Number.isNaN(d.getTime()) ? null : d.toISOString();
|
|
198
|
-
}
|
|
199
|
-
function escapeXml(value) {
|
|
200
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
201
|
-
}
|
|
202
|
-
function cdata(value) {
|
|
203
|
-
return `<![CDATA[${value.replace(/]]>/g, "]]]]><![CDATA[>")}]]>`;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// src/inquiry.ts
|
|
207
|
-
var DEFAULT_BASE_URL = "https://api.roottale.com";
|
|
208
|
-
var INQUIRY_ERROR_MESSAGES = {
|
|
209
|
-
consent_privacy: "\uAC1C\uC778\uC815\uBCF4 \uC218\uC9D1\xB7\uC774\uC6A9 \uB3D9\uC758\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
210
|
-
consent_overseas: "\uC758\uB8CC \uBD84\uC57C\uB294 \uAD6D\uC678\uC774\uC804 \uB3D9\uC758\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
211
|
-
invalid_vertical: "\uC5C5\uC885 \uBD84\uB958\uAC00 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
|
|
212
|
-
missing_fields: "\uD544\uC218 \uD56D\uBAA9\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
213
|
-
invalid_email: "\uC774\uBA54\uC77C \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
|
|
214
|
-
invalid_api_key: "\uC5F0\uB3D9 \uC778\uC99D\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. API \uD0A4\uB97C \uD655\uC778\uD574\uC8FC\uC138\uC694.",
|
|
215
|
-
internal: "\uC804\uC1A1\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694."
|
|
216
|
-
};
|
|
217
|
-
var RESERVED_INQUIRY_KEYS = /* @__PURE__ */ new Set([
|
|
218
|
-
"vertical",
|
|
219
|
-
"contact_name",
|
|
220
|
-
"business_name",
|
|
221
|
-
"email",
|
|
222
|
-
"phone",
|
|
223
|
-
"message",
|
|
224
|
-
"consultation_field",
|
|
225
|
-
"current_site_url",
|
|
226
|
-
"privacy_consent",
|
|
227
|
-
"overseas_transfer_consent",
|
|
228
|
-
"lead_kind"
|
|
229
|
-
]);
|
|
230
|
-
function inquiryErrorMessage(code) {
|
|
231
|
-
return code && INQUIRY_ERROR_MESSAGES[code] || INQUIRY_ERROR_MESSAGES.internal;
|
|
232
|
-
}
|
|
233
|
-
function parseInquiryRedirect(location, base = DEFAULT_BASE_URL) {
|
|
234
|
-
if (!location) {
|
|
235
|
-
return { ok: false, code: null, message: inquiryErrorMessage(null) };
|
|
236
|
-
}
|
|
237
|
-
let url;
|
|
238
|
-
try {
|
|
239
|
-
url = new URL(location, base);
|
|
240
|
-
} catch {
|
|
241
|
-
return { ok: false, code: null, message: inquiryErrorMessage(null) };
|
|
242
|
-
}
|
|
243
|
-
if (url.searchParams.get("ok") === "1") return { ok: true };
|
|
244
|
-
const code = url.searchParams.get("err");
|
|
245
|
-
return { ok: false, code, message: inquiryErrorMessage(code) };
|
|
246
|
-
}
|
|
247
|
-
function formatKoreanPhoneNumber(input) {
|
|
248
|
-
let digits = input.replace(/\D/g, "");
|
|
249
|
-
if (digits.startsWith("82") && digits.length >= 11) {
|
|
250
|
-
digits = `0${digits.slice(2)}`;
|
|
251
|
-
}
|
|
252
|
-
if (digits.length === 11) {
|
|
253
|
-
return `${digits.slice(0, 3)}-${digits.slice(3, 7)}-${digits.slice(7)}`;
|
|
254
|
-
}
|
|
255
|
-
if (digits.length === 10) {
|
|
256
|
-
if (digits.startsWith("02")) {
|
|
257
|
-
return `${digits.slice(0, 2)}-${digits.slice(2, 6)}-${digits.slice(6)}`;
|
|
258
|
-
}
|
|
259
|
-
return `${digits.slice(0, 3)}-${digits.slice(3, 6)}-${digits.slice(6)}`;
|
|
260
|
-
}
|
|
261
|
-
if (digits.length === 8) {
|
|
262
|
-
return `${digits.slice(0, 4)}-${digits.slice(4)}`;
|
|
263
|
-
}
|
|
264
|
-
return input.trim();
|
|
265
|
-
}
|
|
266
|
-
async function submitInquiry(options) {
|
|
267
|
-
assertServer();
|
|
268
|
-
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
269
|
-
const f = options.fields;
|
|
270
|
-
const body = new FormData();
|
|
271
|
-
body.set("vertical", f.vertical);
|
|
272
|
-
body.set("contact_name", f.contactName);
|
|
273
|
-
body.set("business_name", f.businessName);
|
|
274
|
-
body.set("email", f.email);
|
|
275
|
-
body.set("phone", formatKoreanPhoneNumber(f.phone));
|
|
276
|
-
if (f.message) body.set("message", f.message);
|
|
277
|
-
if (f.consultationField) body.set("consultation_field", f.consultationField);
|
|
278
|
-
if (f.currentSiteUrl) body.set("current_site_url", f.currentSiteUrl);
|
|
279
|
-
if (f.privacyConsent) body.set("privacy_consent", "on");
|
|
280
|
-
if (f.overseasTransferConsent) body.set("overseas_transfer_consent", "on");
|
|
281
|
-
if (f.leadKind) body.set("lead_kind", f.leadKind);
|
|
282
|
-
if (f.attribution) {
|
|
283
|
-
for (const [key, value] of Object.entries(f.attribution)) {
|
|
284
|
-
if (typeof value !== "string" || !value.trim()) continue;
|
|
285
|
-
body.set(`attr_${key}`, value.trim());
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
if (f.extras) {
|
|
289
|
-
for (const [rawKey, rawValue] of Object.entries(f.extras)) {
|
|
290
|
-
const key = rawKey.trim();
|
|
291
|
-
const value = String(rawValue ?? "").trim();
|
|
292
|
-
if (!key || !value) continue;
|
|
293
|
-
if (key.startsWith("_") || RESERVED_INQUIRY_KEYS.has(key)) continue;
|
|
294
|
-
if (key.startsWith("attr_")) continue;
|
|
295
|
-
if (body.has(key)) continue;
|
|
296
|
-
body.set(key, value);
|
|
297
|
-
}
|
|
5
|
+
// src/server-base.ts
|
|
6
|
+
var DEFAULT_BASE_URL = "https://api.roottale.com";
|
|
7
|
+
var CLIENT_VERSION = true ? "0.33.0" : "dev";
|
|
8
|
+
var CMS_CLIENT_VERSION = CLIENT_VERSION;
|
|
9
|
+
function authHeaders(apiKey) {
|
|
10
|
+
return {
|
|
11
|
+
authorization: `Bearer ${apiKey}`,
|
|
12
|
+
"x-roottale-client": `cms-client/${CLIENT_VERSION}`
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
var CmsApiError = class extends Error {
|
|
16
|
+
status;
|
|
17
|
+
code;
|
|
18
|
+
constructor(message, status, code) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "CmsApiError";
|
|
21
|
+
this.status = status;
|
|
22
|
+
this.code = code;
|
|
298
23
|
}
|
|
299
|
-
|
|
24
|
+
};
|
|
25
|
+
async function toApiError(response) {
|
|
26
|
+
let code = "request_failed";
|
|
27
|
+
let message = `RootTale CMS API error: ${response.status}`;
|
|
300
28
|
try {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
body,
|
|
305
|
-
// intake 는 302 로 결과 신호 — 자동 추적 금지하고 Location 직접 읽음.
|
|
306
|
-
redirect: "manual",
|
|
307
|
-
signal: options.signal
|
|
308
|
-
});
|
|
29
|
+
const body = await response.json();
|
|
30
|
+
if (body.code) code = body.code;
|
|
31
|
+
if (body.message) message = body.message;
|
|
309
32
|
} catch {
|
|
310
|
-
return { ok: false, code: "internal", message: inquiryErrorMessage("internal") };
|
|
311
|
-
}
|
|
312
|
-
if (response.status === 401) {
|
|
313
|
-
return {
|
|
314
|
-
ok: false,
|
|
315
|
-
code: "invalid_api_key",
|
|
316
|
-
message: inquiryErrorMessage("invalid_api_key")
|
|
317
|
-
};
|
|
318
33
|
}
|
|
319
|
-
|
|
320
|
-
if (location) return parseInquiryRedirect(location, baseUrl);
|
|
321
|
-
if (response.ok) return { ok: true };
|
|
322
|
-
return { ok: false, code: "internal", message: inquiryErrorMessage("internal") };
|
|
34
|
+
return new CmsApiError(message, response.status, code);
|
|
323
35
|
}
|
|
324
36
|
function assertServer() {
|
|
325
37
|
if (typeof window !== "undefined") {
|
|
326
38
|
throw new Error(
|
|
327
|
-
"@roottale/cms-client
|
|
39
|
+
"@roottale/cms-client/server must not run in the browser. Move this call to a Server Component, getServerSideProps, or a Route Handler. Shipping API keys to the browser breaks the security model (ADR-0023 \xA75.1 #15)."
|
|
328
40
|
);
|
|
329
41
|
}
|
|
330
42
|
}
|
|
331
43
|
|
|
332
|
-
// src/server.ts
|
|
333
|
-
var DEFAULT_BASE_URL2 = "https://api.roottale.com";
|
|
334
|
-
var CLIENT_VERSION = true ? "0.25.0" : "dev";
|
|
335
|
-
var CMS_CLIENT_VERSION = CLIENT_VERSION;
|
|
336
|
-
function authHeaders(apiKey) {
|
|
337
|
-
return {
|
|
338
|
-
authorization: `Bearer ${apiKey}`,
|
|
339
|
-
"x-roottale-client": `cms-client/${CLIENT_VERSION}`
|
|
340
|
-
};
|
|
341
|
-
}
|
|
44
|
+
// src/server-posts-theme.ts
|
|
342
45
|
async function fetchTheme(options) {
|
|
343
|
-
|
|
344
|
-
const baseUrl = (options.baseUrl ??
|
|
46
|
+
assertServer();
|
|
47
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
345
48
|
const params = new URLSearchParams();
|
|
346
49
|
if (options.siteId) params.set("site_id", options.siteId);
|
|
347
50
|
const qs = params.toString();
|
|
@@ -362,8 +65,8 @@ async function fetchTheme(options) {
|
|
|
362
65
|
return theme;
|
|
363
66
|
}
|
|
364
67
|
async function fetchAnalyticsConfig(options) {
|
|
365
|
-
|
|
366
|
-
const baseUrl = (options.baseUrl ??
|
|
68
|
+
assertServer();
|
|
69
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
367
70
|
const params = new URLSearchParams();
|
|
368
71
|
if (options.siteId) params.set("site_id", options.siteId);
|
|
369
72
|
const qs = params.toString();
|
|
@@ -379,9 +82,81 @@ async function fetchAnalyticsConfig(options) {
|
|
|
379
82
|
const json = await response.json();
|
|
380
83
|
return { siteId: json.site_id, tags: json.tags ?? [] };
|
|
381
84
|
}
|
|
85
|
+
async function fetchPosts(options) {
|
|
86
|
+
assertServer();
|
|
87
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
88
|
+
const params = new URLSearchParams();
|
|
89
|
+
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
90
|
+
if (options.cursor) params.set("cursor", options.cursor);
|
|
91
|
+
if (options.type) params.set("type", options.type);
|
|
92
|
+
if (options.siteId) params.set("site_id", options.siteId);
|
|
93
|
+
const qs = params.toString();
|
|
94
|
+
const url = `${baseUrl}/v1/cms/public/posts${qs ? `?${qs}` : ""}`;
|
|
95
|
+
const response = await fetch(url, {
|
|
96
|
+
method: "GET",
|
|
97
|
+
headers: authHeaders(options.apiKey),
|
|
98
|
+
signal: options.signal
|
|
99
|
+
});
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
throw await toApiError(response);
|
|
102
|
+
}
|
|
103
|
+
const json = await response.json();
|
|
104
|
+
return {
|
|
105
|
+
items: json.items.map(fromWire),
|
|
106
|
+
hasMore: json.has_more,
|
|
107
|
+
nextCursor: json.next_cursor
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
async function fetchPost(options) {
|
|
111
|
+
assertServer();
|
|
112
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
113
|
+
const params = new URLSearchParams();
|
|
114
|
+
if (options.siteId) params.set("site_id", options.siteId);
|
|
115
|
+
const qs = params.toString();
|
|
116
|
+
const url = `${baseUrl}/v1/cms/public/posts/${encodeURIComponent(options.slugOrId)}${qs ? `?${qs}` : ""}`;
|
|
117
|
+
const response = await fetch(url, {
|
|
118
|
+
method: "GET",
|
|
119
|
+
headers: authHeaders(options.apiKey),
|
|
120
|
+
signal: options.signal
|
|
121
|
+
});
|
|
122
|
+
if (response.status === 404) return null;
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
throw await toApiError(response);
|
|
125
|
+
}
|
|
126
|
+
const json = await response.json();
|
|
127
|
+
return fromWire(json);
|
|
128
|
+
}
|
|
129
|
+
function fromWire(wire) {
|
|
130
|
+
return {
|
|
131
|
+
id: wire.id,
|
|
132
|
+
tenantId: wire.tenant_id,
|
|
133
|
+
siteId: wire.site_id,
|
|
134
|
+
type: wire.type,
|
|
135
|
+
collectionKey: wire.collection_key ?? null,
|
|
136
|
+
slug: wire.slug,
|
|
137
|
+
title: wire.title,
|
|
138
|
+
excerpt: wire.excerpt,
|
|
139
|
+
featuredMediaId: wire.featured_media_id,
|
|
140
|
+
featuredImageUrl: wire.featured_media_url ?? null,
|
|
141
|
+
authorId: wire.author_id,
|
|
142
|
+
authorName: wire.author_name ?? null,
|
|
143
|
+
authorImageUrl: wire.author_image_url ?? null,
|
|
144
|
+
authorBio: wire.author_bio ?? null,
|
|
145
|
+
bodyJson: wire.body_json,
|
|
146
|
+
status: wire.status,
|
|
147
|
+
metaJson: wire.meta_json,
|
|
148
|
+
fields: wire.fields ?? null,
|
|
149
|
+
terms: wire.terms ?? [],
|
|
150
|
+
publishedAt: wire.published_at,
|
|
151
|
+
createdAt: wire.created_at,
|
|
152
|
+
updatedAt: wire.updated_at
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/server-blog.ts
|
|
382
157
|
async function fetchBlogSettings(options) {
|
|
383
|
-
|
|
384
|
-
const baseUrl = (options.baseUrl ??
|
|
158
|
+
assertServer();
|
|
159
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
385
160
|
const params = new URLSearchParams();
|
|
386
161
|
if (options.siteId) params.set("site_id", options.siteId);
|
|
387
162
|
const qs = params.toString();
|
|
@@ -413,6 +188,45 @@ async function fetchBlogSettings(options) {
|
|
|
413
188
|
authorProfileImagePositionY: percent(json.author_profile_image_position_y)
|
|
414
189
|
};
|
|
415
190
|
}
|
|
191
|
+
async function fetchCollections(options) {
|
|
192
|
+
assertServer();
|
|
193
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
194
|
+
const params = new URLSearchParams();
|
|
195
|
+
if (options.siteId) params.set("site_id", options.siteId);
|
|
196
|
+
const qs = params.toString();
|
|
197
|
+
const url = `${baseUrl}/v1/cms/public/collections${qs ? `?${qs}` : ""}`;
|
|
198
|
+
const response = await fetch(url, {
|
|
199
|
+
method: "GET",
|
|
200
|
+
headers: authHeaders(options.apiKey),
|
|
201
|
+
signal: options.signal
|
|
202
|
+
});
|
|
203
|
+
if (!response.ok) {
|
|
204
|
+
throw await toApiError(response);
|
|
205
|
+
}
|
|
206
|
+
const json = await response.json();
|
|
207
|
+
const items = Array.isArray(json.collections) ? json.collections : [];
|
|
208
|
+
return items.flatMap((o) => {
|
|
209
|
+
const key = typeof o.key === "string" ? o.key : "";
|
|
210
|
+
const basePath = typeof o.base_path === "string" ? o.base_path : "";
|
|
211
|
+
if (!key || !basePath) return [];
|
|
212
|
+
return [
|
|
213
|
+
{
|
|
214
|
+
key,
|
|
215
|
+
basePath,
|
|
216
|
+
label: typeof o.label === "string" ? o.label : key,
|
|
217
|
+
layout: typeof o.layout === "string" ? o.layout : "grid",
|
|
218
|
+
categories: Array.isArray(o.categories) ? o.categories.filter((s) => typeof s === "string") : [],
|
|
219
|
+
feed: o.feed === true,
|
|
220
|
+
archives: o.archives === true,
|
|
221
|
+
og: o.og === true,
|
|
222
|
+
requireSubcategory: o.require_subcategory === true,
|
|
223
|
+
pinning: o.pinning === true,
|
|
224
|
+
icon: typeof o.icon === "string" ? o.icon : null
|
|
225
|
+
}
|
|
226
|
+
];
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
var fetchCollectionsSafeDefault = [];
|
|
416
230
|
var DEFAULT_BLOG_SETTINGS = {
|
|
417
231
|
showTableOfContents: false,
|
|
418
232
|
showAuthor: true,
|
|
@@ -428,6 +242,27 @@ var DEFAULT_BLOG_SETTINGS = {
|
|
|
428
242
|
authorProfileImagePositionX: 50,
|
|
429
243
|
authorProfileImagePositionY: 50
|
|
430
244
|
};
|
|
245
|
+
function resolvePostDisplay(post, settings, explicit) {
|
|
246
|
+
const display = post.metaJson && typeof post.metaJson === "object" ? post.metaJson.display : void 0;
|
|
247
|
+
const ov = (key) => display && typeof display[key] === "boolean" ? display[key] : void 0;
|
|
248
|
+
return {
|
|
249
|
+
showTableOfContents: explicit?.showTableOfContents ?? ov("toc") ?? settings.showTableOfContents,
|
|
250
|
+
showAuthor: ov("author") ?? settings.showAuthor,
|
|
251
|
+
showDate: ov("date") ?? settings.showDate,
|
|
252
|
+
showAuthorCard: settings.showAuthorCard,
|
|
253
|
+
tocTitle: explicit?.tableOfContentsTitle ?? settings.tocTitle ?? "\uBAA9\uCC28",
|
|
254
|
+
authorCardEyebrow: settings.authorCardEyebrow ?? "\uC791\uC131\uC790",
|
|
255
|
+
authorCardDescription: settings.authorCardDescription,
|
|
256
|
+
authorProfileName: settings.authorProfileName,
|
|
257
|
+
authorProfileBio: settings.authorProfileBio,
|
|
258
|
+
authorProfileImageUrl: settings.authorProfileImageUrl,
|
|
259
|
+
authorProfileImageRadius: settings.authorProfileImageRadius,
|
|
260
|
+
authorProfileImagePositionX: settings.authorProfileImagePositionX,
|
|
261
|
+
authorProfileImagePositionY: settings.authorProfileImagePositionY
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/server-business.ts
|
|
431
266
|
function businessProfileFromWire(json) {
|
|
432
267
|
if (json.configured === false) return null;
|
|
433
268
|
if (typeof json.name !== "string" || json.name.length === 0) return null;
|
|
@@ -464,13 +299,80 @@ function businessProfileFromWire(json) {
|
|
|
464
299
|
}
|
|
465
300
|
};
|
|
466
301
|
}
|
|
467
|
-
async function fetchBusinessProfile(options) {
|
|
468
|
-
|
|
469
|
-
const baseUrl = (options.baseUrl ??
|
|
302
|
+
async function fetchBusinessProfile(options) {
|
|
303
|
+
assertServer();
|
|
304
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
305
|
+
const params = new URLSearchParams();
|
|
306
|
+
if (options.siteId) params.set("site_id", options.siteId);
|
|
307
|
+
const qs = params.toString();
|
|
308
|
+
const url = `${baseUrl}/v1/cms/public/business-profile${qs ? `?${qs}` : ""}`;
|
|
309
|
+
const response = await fetch(url, {
|
|
310
|
+
method: "GET",
|
|
311
|
+
headers: authHeaders(options.apiKey),
|
|
312
|
+
signal: options.signal
|
|
313
|
+
});
|
|
314
|
+
if (response.status === 404) return null;
|
|
315
|
+
if (!response.ok) {
|
|
316
|
+
throw await toApiError(response);
|
|
317
|
+
}
|
|
318
|
+
const json = await response.json();
|
|
319
|
+
return businessProfileFromWire(json);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/server-discovery.ts
|
|
323
|
+
async function searchPosts(options) {
|
|
324
|
+
assertServer();
|
|
325
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
326
|
+
const params = new URLSearchParams();
|
|
327
|
+
params.set("q", options.query);
|
|
328
|
+
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
329
|
+
if (options.type) params.set("type", options.type);
|
|
330
|
+
if (options.siteId) params.set("site_id", options.siteId);
|
|
331
|
+
const url = `${baseUrl}/v1/cms/public/search?${params.toString()}`;
|
|
332
|
+
const response = await fetch(url, {
|
|
333
|
+
method: "GET",
|
|
334
|
+
headers: authHeaders(options.apiKey),
|
|
335
|
+
signal: options.signal
|
|
336
|
+
});
|
|
337
|
+
if (response.status === 404) return [];
|
|
338
|
+
if (!response.ok) {
|
|
339
|
+
throw await toApiError(response);
|
|
340
|
+
}
|
|
341
|
+
const json = await response.json();
|
|
342
|
+
return json.items.map((w) => ({
|
|
343
|
+
id: w.id,
|
|
344
|
+
type: w.type,
|
|
345
|
+
title: w.title,
|
|
346
|
+
slug: w.slug,
|
|
347
|
+
excerpt: w.excerpt,
|
|
348
|
+
featuredImageUrl: w.featured_media_url ?? null,
|
|
349
|
+
publishedAt: w.published_at
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
function menuItemFromWire(w) {
|
|
353
|
+
const item = { id: w.id, label: w.label, url: w.url };
|
|
354
|
+
if (w.new_tab) item.newTab = true;
|
|
355
|
+
if (w.children && w.children.length > 0) {
|
|
356
|
+
item.children = w.children.map(menuItemFromWire);
|
|
357
|
+
}
|
|
358
|
+
return item;
|
|
359
|
+
}
|
|
360
|
+
function menuFromWire(w) {
|
|
361
|
+
return {
|
|
362
|
+
id: w.id,
|
|
363
|
+
name: w.name,
|
|
364
|
+
slug: w.slug,
|
|
365
|
+
items: (w.items ?? []).map(menuItemFromWire),
|
|
366
|
+
updatedAt: w.updated_at ?? null
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
async function fetchMenu(options) {
|
|
370
|
+
assertServer();
|
|
371
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
470
372
|
const params = new URLSearchParams();
|
|
471
373
|
if (options.siteId) params.set("site_id", options.siteId);
|
|
472
374
|
const qs = params.toString();
|
|
473
|
-
const url = `${baseUrl}/v1/cms/public/
|
|
375
|
+
const url = `${baseUrl}/v1/cms/public/menus/${encodeURIComponent(options.slug)}${qs ? `?${qs}` : ""}`;
|
|
474
376
|
const response = await fetch(url, {
|
|
475
377
|
method: "GET",
|
|
476
378
|
headers: authHeaders(options.apiKey),
|
|
@@ -481,205 +383,351 @@ async function fetchBusinessProfile(options) {
|
|
|
481
383
|
throw await toApiError(response);
|
|
482
384
|
}
|
|
483
385
|
const json = await response.json();
|
|
484
|
-
return
|
|
386
|
+
return menuFromWire(json);
|
|
485
387
|
}
|
|
486
|
-
function
|
|
487
|
-
|
|
488
|
-
const
|
|
388
|
+
async function fetchMenus(options) {
|
|
389
|
+
assertServer();
|
|
390
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
391
|
+
const params = new URLSearchParams();
|
|
392
|
+
if (options.siteId) params.set("site_id", options.siteId);
|
|
393
|
+
const qs = params.toString();
|
|
394
|
+
const url = `${baseUrl}/v1/cms/public/menus${qs ? `?${qs}` : ""}`;
|
|
395
|
+
const response = await fetch(url, {
|
|
396
|
+
method: "GET",
|
|
397
|
+
headers: authHeaders(options.apiKey),
|
|
398
|
+
signal: options.signal
|
|
399
|
+
});
|
|
400
|
+
if (response.status === 404) return [];
|
|
401
|
+
if (!response.ok) {
|
|
402
|
+
throw await toApiError(response);
|
|
403
|
+
}
|
|
404
|
+
const json = await response.json();
|
|
405
|
+
return (json.items ?? []).map(menuFromWire);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/seo.ts
|
|
409
|
+
function generateRssXml(input) {
|
|
410
|
+
const lang = input.language ?? "ko-KR";
|
|
411
|
+
const lastBuild = input.items.length > 0 ? toRfc822(input.items[0].pubDate) : "";
|
|
412
|
+
const lines = [];
|
|
413
|
+
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
414
|
+
lines.push(
|
|
415
|
+
'<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">'
|
|
416
|
+
);
|
|
417
|
+
lines.push("<channel>");
|
|
418
|
+
lines.push(`<title>${escapeXml(input.title)}</title>`);
|
|
419
|
+
lines.push(`<link>${escapeXml(input.link)}</link>`);
|
|
420
|
+
lines.push(
|
|
421
|
+
`<atom:link href="${escapeXml(input.feedUrl)}" rel="self" type="application/rss+xml" />`
|
|
422
|
+
);
|
|
423
|
+
lines.push(
|
|
424
|
+
`<description>${escapeXml(input.description ?? input.title)}</description>`
|
|
425
|
+
);
|
|
426
|
+
lines.push(`<language>${escapeXml(lang)}</language>`);
|
|
427
|
+
if (lastBuild) lines.push(`<lastBuildDate>${lastBuild}</lastBuildDate>`);
|
|
428
|
+
for (const item of input.items) {
|
|
429
|
+
const guid = item.guid ?? item.link;
|
|
430
|
+
lines.push("<item>");
|
|
431
|
+
lines.push(`<title>${escapeXml(item.title)}</title>`);
|
|
432
|
+
lines.push(`<link>${escapeXml(item.link)}</link>`);
|
|
433
|
+
lines.push(`<guid isPermaLink="false">${escapeXml(guid)}</guid>`);
|
|
434
|
+
if (item.description) {
|
|
435
|
+
lines.push(`<description>${cdata(item.description)}</description>`);
|
|
436
|
+
}
|
|
437
|
+
const pub = toRfc822(item.pubDate);
|
|
438
|
+
if (pub) lines.push(`<pubDate>${pub}</pubDate>`);
|
|
439
|
+
lines.push("</item>");
|
|
440
|
+
}
|
|
441
|
+
lines.push("</channel>");
|
|
442
|
+
lines.push("</rss>");
|
|
443
|
+
return lines.join("\n");
|
|
444
|
+
}
|
|
445
|
+
function rssItemsFromPosts(posts, opts) {
|
|
446
|
+
return posts.map((p) => ({
|
|
447
|
+
title: p.title,
|
|
448
|
+
link: opts.postUrl(p),
|
|
449
|
+
guid: opts.postUrl(p),
|
|
450
|
+
description: p.excerpt,
|
|
451
|
+
pubDate: p.publishedAt
|
|
452
|
+
}));
|
|
453
|
+
}
|
|
454
|
+
function toRfc822(value) {
|
|
455
|
+
if (!value) return "";
|
|
456
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
457
|
+
return Number.isNaN(d.getTime()) ? "" : d.toUTCString();
|
|
458
|
+
}
|
|
459
|
+
function articleSchema(input) {
|
|
460
|
+
const schema = {
|
|
461
|
+
"@context": "https://schema.org",
|
|
462
|
+
"@type": "Article",
|
|
463
|
+
headline: input.headline,
|
|
464
|
+
mainEntityOfPage: { "@type": "WebPage", "@id": input.url }
|
|
465
|
+
};
|
|
466
|
+
if (input.description) schema.description = input.description;
|
|
467
|
+
if (input.image) schema.image = input.image;
|
|
468
|
+
const pub = toIso(input.datePublished);
|
|
469
|
+
if (pub) schema.datePublished = pub;
|
|
470
|
+
const mod = toIso(input.dateModified);
|
|
471
|
+
if (mod) schema.dateModified = mod;
|
|
472
|
+
if (input.authorName)
|
|
473
|
+
schema.author = { "@type": "Person", name: input.authorName };
|
|
474
|
+
if (input.publisherName) {
|
|
475
|
+
schema.publisher = {
|
|
476
|
+
"@type": "Organization",
|
|
477
|
+
name: input.publisherName,
|
|
478
|
+
...input.publisherLogo ? { logo: { "@type": "ImageObject", url: input.publisherLogo } } : {}
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
return schema;
|
|
482
|
+
}
|
|
483
|
+
function breadcrumbSchema(items) {
|
|
489
484
|
return {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
485
|
+
"@context": "https://schema.org",
|
|
486
|
+
"@type": "BreadcrumbList",
|
|
487
|
+
itemListElement: items.map((it, i) => ({
|
|
488
|
+
"@type": "ListItem",
|
|
489
|
+
position: i + 1,
|
|
490
|
+
name: it.name,
|
|
491
|
+
item: it.url
|
|
492
|
+
}))
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
function organizationSchema(input) {
|
|
496
|
+
return {
|
|
497
|
+
"@context": "https://schema.org",
|
|
498
|
+
"@type": "Organization",
|
|
499
|
+
name: input.name,
|
|
500
|
+
url: input.url,
|
|
501
|
+
...input.logo ? { logo: input.logo } : {},
|
|
502
|
+
...input.sameAs && input.sameAs.length > 0 ? { sameAs: input.sameAs } : {}
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
var DAY_OF_WEEK = {
|
|
506
|
+
Mo: "Monday",
|
|
507
|
+
Tu: "Tuesday",
|
|
508
|
+
We: "Wednesday",
|
|
509
|
+
Th: "Thursday",
|
|
510
|
+
Fr: "Friday",
|
|
511
|
+
Sa: "Saturday",
|
|
512
|
+
Su: "Sunday"
|
|
513
|
+
};
|
|
514
|
+
function localBusinessSchema(profile, opts) {
|
|
515
|
+
const input = profile;
|
|
516
|
+
const types = Array.from(
|
|
517
|
+
/* @__PURE__ */ new Set([input.businessType || "LocalBusiness", "LocalBusiness"])
|
|
518
|
+
);
|
|
519
|
+
const schema = {
|
|
520
|
+
"@context": "https://schema.org",
|
|
521
|
+
"@type": types,
|
|
522
|
+
name: input.name,
|
|
523
|
+
url: opts.url
|
|
524
|
+
};
|
|
525
|
+
if (input.legalName) schema.legalName = input.legalName;
|
|
526
|
+
if (opts.image) schema.image = opts.image;
|
|
527
|
+
if (input.telephone) schema.telephone = input.telephone;
|
|
528
|
+
if (input.email) schema.email = input.email;
|
|
529
|
+
if (input.address) {
|
|
530
|
+
schema.address = {
|
|
531
|
+
"@type": "PostalAddress",
|
|
532
|
+
streetAddress: input.address.streetAddress,
|
|
533
|
+
addressLocality: input.address.addressLocality,
|
|
534
|
+
...input.address.addressRegion ? { addressRegion: input.address.addressRegion } : {},
|
|
535
|
+
...input.address.postalCode ? { postalCode: input.address.postalCode } : {},
|
|
536
|
+
addressCountry: "KR"
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
if (input.geo) {
|
|
540
|
+
schema.geo = {
|
|
541
|
+
"@type": "GeoCoordinates",
|
|
542
|
+
latitude: input.geo.latitude,
|
|
543
|
+
longitude: input.geo.longitude
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
const openingHours = (input.openingHours ?? []).filter(
|
|
547
|
+
(row) => row.days.length > 0 && row.opens && row.closes
|
|
548
|
+
);
|
|
549
|
+
if (openingHours.length > 0) {
|
|
550
|
+
schema.openingHoursSpecification = openingHours.map((row) => ({
|
|
551
|
+
"@type": "OpeningHoursSpecification",
|
|
552
|
+
dayOfWeek: row.days.map((d) => DAY_OF_WEEK[d] ?? d),
|
|
553
|
+
opens: row.opens,
|
|
554
|
+
closes: row.closes
|
|
555
|
+
}));
|
|
556
|
+
}
|
|
557
|
+
if (input.priceRange) schema.priceRange = input.priceRange;
|
|
558
|
+
if (input.areaServed && input.areaServed.length > 0) {
|
|
559
|
+
schema.areaServed = input.areaServed;
|
|
560
|
+
}
|
|
561
|
+
const sameAs = Object.values(input.profiles ?? {}).filter(
|
|
562
|
+
(u) => typeof u === "string" && u.length > 0
|
|
563
|
+
);
|
|
564
|
+
if (sameAs.length > 0) schema.sameAs = sameAs;
|
|
565
|
+
return schema;
|
|
566
|
+
}
|
|
567
|
+
function websiteSchema(input) {
|
|
568
|
+
return {
|
|
569
|
+
"@context": "https://schema.org",
|
|
570
|
+
"@type": "WebSite",
|
|
571
|
+
name: input.name,
|
|
572
|
+
url: input.url,
|
|
573
|
+
...input.searchUrl ? {
|
|
574
|
+
potentialAction: {
|
|
575
|
+
"@type": "SearchAction",
|
|
576
|
+
target: `${input.searchUrl}{search_term_string}`,
|
|
577
|
+
"query-input": "required name=search_term_string"
|
|
578
|
+
}
|
|
579
|
+
} : {}
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
function faqSchema(qa) {
|
|
583
|
+
return {
|
|
584
|
+
"@context": "https://schema.org",
|
|
585
|
+
"@type": "FAQPage",
|
|
586
|
+
mainEntity: qa.map((x) => ({
|
|
587
|
+
"@type": "Question",
|
|
588
|
+
name: x.question,
|
|
589
|
+
acceptedAnswer: { "@type": "Answer", text: x.answer }
|
|
590
|
+
}))
|
|
503
591
|
};
|
|
504
592
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
593
|
+
function jsonLdScript(schema) {
|
|
594
|
+
const json = JSON.stringify(schema).replace(/</g, "\\u003c");
|
|
595
|
+
return `<script type="application/ld+json">${json}</script>`;
|
|
596
|
+
}
|
|
597
|
+
function toIso(value) {
|
|
598
|
+
if (!value) return null;
|
|
599
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
600
|
+
return Number.isNaN(d.getTime()) ? null : d.toISOString();
|
|
601
|
+
}
|
|
602
|
+
function escapeXml(value) {
|
|
603
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
604
|
+
}
|
|
605
|
+
function cdata(value) {
|
|
606
|
+
return `<![CDATA[${value.replace(/]]>/g, "]]]]><![CDATA[>")}]]>`;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// src/inquiry.ts
|
|
610
|
+
var DEFAULT_BASE_URL2 = "https://api.roottale.com";
|
|
611
|
+
var INQUIRY_ERROR_MESSAGES = {
|
|
612
|
+
consent_privacy: "\uAC1C\uC778\uC815\uBCF4 \uC218\uC9D1\xB7\uC774\uC6A9 \uB3D9\uC758\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
613
|
+
consent_overseas: "\uC758\uB8CC \uBD84\uC57C\uB294 \uAD6D\uC678\uC774\uC804 \uB3D9\uC758\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
614
|
+
invalid_vertical: "\uC5C5\uC885 \uBD84\uB958\uAC00 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
|
|
615
|
+
missing_fields: "\uD544\uC218 \uD56D\uBAA9\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
616
|
+
invalid_email: "\uC774\uBA54\uC77C \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
|
|
617
|
+
invalid_api_key: "\uC5F0\uB3D9 \uC778\uC99D\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. API \uD0A4\uB97C \uD655\uC778\uD574\uC8FC\uC138\uC694.",
|
|
618
|
+
internal: "\uC804\uC1A1\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694."
|
|
619
|
+
};
|
|
620
|
+
var RESERVED_INQUIRY_KEYS = /* @__PURE__ */ new Set([
|
|
621
|
+
"vertical",
|
|
622
|
+
"contact_name",
|
|
623
|
+
"business_name",
|
|
624
|
+
"email",
|
|
625
|
+
"phone",
|
|
626
|
+
"message",
|
|
627
|
+
"consultation_field",
|
|
628
|
+
"current_site_url",
|
|
629
|
+
"privacy_consent",
|
|
630
|
+
"overseas_transfer_consent",
|
|
631
|
+
"lead_kind"
|
|
632
|
+
]);
|
|
633
|
+
function inquiryErrorMessage(code) {
|
|
634
|
+
return code && INQUIRY_ERROR_MESSAGES[code] || INQUIRY_ERROR_MESSAGES.internal;
|
|
635
|
+
}
|
|
636
|
+
function parseInquiryRedirect(location, base = DEFAULT_BASE_URL2) {
|
|
637
|
+
if (!location) {
|
|
638
|
+
return { ok: false, code: null, message: inquiryErrorMessage(null) };
|
|
513
639
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
520
|
-
if (options.cursor) params.set("cursor", options.cursor);
|
|
521
|
-
if (options.type) params.set("type", options.type);
|
|
522
|
-
if (options.siteId) params.set("site_id", options.siteId);
|
|
523
|
-
const qs = params.toString();
|
|
524
|
-
const url = `${baseUrl}/v1/cms/public/posts${qs ? `?${qs}` : ""}`;
|
|
525
|
-
const response = await fetch(url, {
|
|
526
|
-
method: "GET",
|
|
527
|
-
headers: authHeaders(options.apiKey),
|
|
528
|
-
signal: options.signal
|
|
529
|
-
});
|
|
530
|
-
if (!response.ok) {
|
|
531
|
-
throw await toApiError(response);
|
|
640
|
+
let url;
|
|
641
|
+
try {
|
|
642
|
+
url = new URL(location, base);
|
|
643
|
+
} catch {
|
|
644
|
+
return { ok: false, code: null, message: inquiryErrorMessage(null) };
|
|
532
645
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
hasMore: json.has_more,
|
|
537
|
-
nextCursor: json.next_cursor
|
|
538
|
-
};
|
|
646
|
+
if (url.searchParams.get("ok") === "1") return { ok: true };
|
|
647
|
+
const code = url.searchParams.get("err");
|
|
648
|
+
return { ok: false, code, message: inquiryErrorMessage(code) };
|
|
539
649
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
if (options.siteId) params.set("site_id", options.siteId);
|
|
545
|
-
const qs = params.toString();
|
|
546
|
-
const url = `${baseUrl}/v1/cms/public/posts/${encodeURIComponent(options.slugOrId)}${qs ? `?${qs}` : ""}`;
|
|
547
|
-
const response = await fetch(url, {
|
|
548
|
-
method: "GET",
|
|
549
|
-
headers: authHeaders(options.apiKey),
|
|
550
|
-
signal: options.signal
|
|
551
|
-
});
|
|
552
|
-
if (response.status === 404) return null;
|
|
553
|
-
if (!response.ok) {
|
|
554
|
-
throw await toApiError(response);
|
|
650
|
+
function formatKoreanPhoneNumber(input) {
|
|
651
|
+
let digits = input.replace(/\D/g, "");
|
|
652
|
+
if (digits.startsWith("82") && digits.length >= 11) {
|
|
653
|
+
digits = `0${digits.slice(2)}`;
|
|
555
654
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
}
|
|
559
|
-
async function searchPosts(options) {
|
|
560
|
-
assertServer2();
|
|
561
|
-
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
|
|
562
|
-
const params = new URLSearchParams();
|
|
563
|
-
params.set("q", options.query);
|
|
564
|
-
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
565
|
-
if (options.type) params.set("type", options.type);
|
|
566
|
-
if (options.siteId) params.set("site_id", options.siteId);
|
|
567
|
-
const url = `${baseUrl}/v1/cms/public/search?${params.toString()}`;
|
|
568
|
-
const response = await fetch(url, {
|
|
569
|
-
method: "GET",
|
|
570
|
-
headers: authHeaders(options.apiKey),
|
|
571
|
-
signal: options.signal
|
|
572
|
-
});
|
|
573
|
-
if (response.status === 404) return [];
|
|
574
|
-
if (!response.ok) {
|
|
575
|
-
throw await toApiError(response);
|
|
655
|
+
if (digits.length === 11) {
|
|
656
|
+
return `${digits.slice(0, 3)}-${digits.slice(3, 7)}-${digits.slice(7)}`;
|
|
576
657
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
slug: w.slug,
|
|
583
|
-
excerpt: w.excerpt,
|
|
584
|
-
featuredImageUrl: w.featured_media_url ?? null,
|
|
585
|
-
publishedAt: w.published_at
|
|
586
|
-
}));
|
|
587
|
-
}
|
|
588
|
-
function menuItemFromWire(w) {
|
|
589
|
-
const item = { id: w.id, label: w.label, url: w.url };
|
|
590
|
-
if (w.new_tab) item.newTab = true;
|
|
591
|
-
if (w.children && w.children.length > 0) {
|
|
592
|
-
item.children = w.children.map(menuItemFromWire);
|
|
658
|
+
if (digits.length === 10) {
|
|
659
|
+
if (digits.startsWith("02")) {
|
|
660
|
+
return `${digits.slice(0, 2)}-${digits.slice(2, 6)}-${digits.slice(6)}`;
|
|
661
|
+
}
|
|
662
|
+
return `${digits.slice(0, 3)}-${digits.slice(3, 6)}-${digits.slice(6)}`;
|
|
593
663
|
}
|
|
594
|
-
|
|
595
|
-
}
|
|
596
|
-
function menuFromWire(w) {
|
|
597
|
-
return {
|
|
598
|
-
id: w.id,
|
|
599
|
-
name: w.name,
|
|
600
|
-
slug: w.slug,
|
|
601
|
-
items: (w.items ?? []).map(menuItemFromWire),
|
|
602
|
-
updatedAt: w.updated_at ?? null
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
async function fetchMenu(options) {
|
|
606
|
-
assertServer2();
|
|
607
|
-
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
|
|
608
|
-
const params = new URLSearchParams();
|
|
609
|
-
if (options.siteId) params.set("site_id", options.siteId);
|
|
610
|
-
const qs = params.toString();
|
|
611
|
-
const url = `${baseUrl}/v1/cms/public/menus/${encodeURIComponent(options.slug)}${qs ? `?${qs}` : ""}`;
|
|
612
|
-
const response = await fetch(url, {
|
|
613
|
-
method: "GET",
|
|
614
|
-
headers: authHeaders(options.apiKey),
|
|
615
|
-
signal: options.signal
|
|
616
|
-
});
|
|
617
|
-
if (response.status === 404) return null;
|
|
618
|
-
if (!response.ok) {
|
|
619
|
-
throw await toApiError(response);
|
|
664
|
+
if (digits.length === 8) {
|
|
665
|
+
return `${digits.slice(0, 4)}-${digits.slice(4)}`;
|
|
620
666
|
}
|
|
621
|
-
|
|
622
|
-
return menuFromWire(json);
|
|
667
|
+
return input.trim();
|
|
623
668
|
}
|
|
624
|
-
async function
|
|
669
|
+
async function submitInquiry(options) {
|
|
625
670
|
assertServer2();
|
|
626
671
|
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
if (
|
|
637
|
-
if (
|
|
638
|
-
|
|
672
|
+
const f = options.fields;
|
|
673
|
+
const body = new FormData();
|
|
674
|
+
body.set("vertical", f.vertical);
|
|
675
|
+
body.set("contact_name", f.contactName);
|
|
676
|
+
body.set("business_name", f.businessName);
|
|
677
|
+
body.set("email", f.email);
|
|
678
|
+
body.set("phone", formatKoreanPhoneNumber(f.phone));
|
|
679
|
+
if (f.message) body.set("message", f.message);
|
|
680
|
+
if (f.consultationField) body.set("consultation_field", f.consultationField);
|
|
681
|
+
if (f.currentSiteUrl) body.set("current_site_url", f.currentSiteUrl);
|
|
682
|
+
if (f.privacyConsent) body.set("privacy_consent", "on");
|
|
683
|
+
if (f.overseasTransferConsent) body.set("overseas_transfer_consent", "on");
|
|
684
|
+
if (f.leadKind) body.set("lead_kind", f.leadKind);
|
|
685
|
+
if (f.attribution) {
|
|
686
|
+
for (const [key, value] of Object.entries(f.attribution)) {
|
|
687
|
+
if (typeof value !== "string" || !value.trim()) continue;
|
|
688
|
+
body.set(`attr_${key}`, value.trim());
|
|
689
|
+
}
|
|
639
690
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
featuredMediaId: wire.featured_media_id,
|
|
653
|
-
featuredImageUrl: wire.featured_media_url ?? null,
|
|
654
|
-
authorId: wire.author_id,
|
|
655
|
-
authorName: wire.author_name ?? null,
|
|
656
|
-
authorImageUrl: wire.author_image_url ?? null,
|
|
657
|
-
authorBio: wire.author_bio ?? null,
|
|
658
|
-
bodyJson: wire.body_json,
|
|
659
|
-
status: wire.status,
|
|
660
|
-
metaJson: wire.meta_json,
|
|
661
|
-
fields: wire.fields ?? null,
|
|
662
|
-
terms: wire.terms ?? [],
|
|
663
|
-
publishedAt: wire.published_at,
|
|
664
|
-
createdAt: wire.created_at,
|
|
665
|
-
updatedAt: wire.updated_at
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
async function toApiError(response) {
|
|
669
|
-
let code = "request_failed";
|
|
670
|
-
let message = `RootTale CMS API error: ${response.status}`;
|
|
691
|
+
if (f.extras) {
|
|
692
|
+
for (const [rawKey, rawValue] of Object.entries(f.extras)) {
|
|
693
|
+
const key = rawKey.trim();
|
|
694
|
+
const value = String(rawValue ?? "").trim();
|
|
695
|
+
if (!key || !value) continue;
|
|
696
|
+
if (key.startsWith("_") || RESERVED_INQUIRY_KEYS.has(key)) continue;
|
|
697
|
+
if (key.startsWith("attr_")) continue;
|
|
698
|
+
if (body.has(key)) continue;
|
|
699
|
+
body.set(key, value);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
let response;
|
|
671
703
|
try {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
704
|
+
response = await fetch(`${baseUrl}/v1/public/inquiries`, {
|
|
705
|
+
method: "POST",
|
|
706
|
+
headers: { authorization: `Bearer ${options.apiKey}` },
|
|
707
|
+
body,
|
|
708
|
+
// intake 는 302 로 결과 신호 — 자동 추적 금지하고 Location 직접 읽음.
|
|
709
|
+
redirect: "manual",
|
|
710
|
+
signal: options.signal
|
|
711
|
+
});
|
|
675
712
|
} catch {
|
|
713
|
+
return { ok: false, code: "internal", message: inquiryErrorMessage("internal") };
|
|
676
714
|
}
|
|
677
|
-
|
|
715
|
+
if (response.status === 401) {
|
|
716
|
+
return {
|
|
717
|
+
ok: false,
|
|
718
|
+
code: "invalid_api_key",
|
|
719
|
+
message: inquiryErrorMessage("invalid_api_key")
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
const location = response.headers.get("location");
|
|
723
|
+
if (location) return parseInquiryRedirect(location, baseUrl);
|
|
724
|
+
if (response.ok) return { ok: true };
|
|
725
|
+
return { ok: false, code: "internal", message: inquiryErrorMessage("internal") };
|
|
678
726
|
}
|
|
679
727
|
function assertServer2() {
|
|
680
728
|
if (typeof window !== "undefined") {
|
|
681
729
|
throw new Error(
|
|
682
|
-
"@roottale/cms-client
|
|
730
|
+
"@roottale/cms-client submitInquiry must not run in the browser. Call it from a Server Action / Route Handler. Shipping the API key to the browser breaks the security model (ADR-0023 \xA75.1 #15)."
|
|
683
731
|
);
|
|
684
732
|
}
|
|
685
733
|
}
|
|
@@ -695,6 +743,8 @@ export {
|
|
|
695
743
|
fetchAnalyticsConfig,
|
|
696
744
|
fetchBlogSettings,
|
|
697
745
|
fetchBusinessProfile,
|
|
746
|
+
fetchCollections,
|
|
747
|
+
fetchCollectionsSafeDefault,
|
|
698
748
|
fetchMenu,
|
|
699
749
|
fetchMenus,
|
|
700
750
|
fetchPost,
|