@roottale/cms-client 0.31.0 → 0.35.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 +26 -0
- package/dist/server.d.ts +270 -240
- package/dist/server.js +567 -533
- 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
|
-
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
11
|
-
lines.push(
|
|
12
|
-
'<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">'
|
|
13
|
-
);
|
|
14
|
-
lines.push("<channel>");
|
|
15
|
-
lines.push(`<title>${escapeXml(input.title)}</title>`);
|
|
16
|
-
lines.push(`<link>${escapeXml(input.link)}</link>`);
|
|
17
|
-
lines.push(
|
|
18
|
-
`<atom:link href="${escapeXml(input.feedUrl)}" rel="self" type="application/rss+xml" />`
|
|
19
|
-
);
|
|
20
|
-
lines.push(
|
|
21
|
-
`<description>${escapeXml(input.description ?? input.title)}</description>`
|
|
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) {
|
|
5
|
+
// src/server-base.ts
|
|
6
|
+
var DEFAULT_BASE_URL = "https://api.roottale.com";
|
|
7
|
+
var CLIENT_VERSION = true ? "0.35.0" : "dev";
|
|
8
|
+
var CMS_CLIENT_VERSION = CLIENT_VERSION;
|
|
9
|
+
function authHeaders(apiKey) {
|
|
180
10
|
return {
|
|
181
|
-
|
|
182
|
-
"
|
|
183
|
-
mainEntity: qa.map((x) => ({
|
|
184
|
-
"@type": "Question",
|
|
185
|
-
name: x.question,
|
|
186
|
-
acceptedAnswer: { "@type": "Answer", text: x.answer }
|
|
187
|
-
}))
|
|
11
|
+
authorization: `Bearer ${apiKey}`,
|
|
12
|
+
"x-roottale-client": `cms-client/${CLIENT_VERSION}`
|
|
188
13
|
};
|
|
189
14
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
}
|
|
298
|
-
}
|
|
299
|
-
let response;
|
|
300
|
-
try {
|
|
301
|
-
response = await fetch(`${baseUrl}/v1/public/inquiries`, {
|
|
302
|
-
method: "POST",
|
|
303
|
-
headers: { authorization: `Bearer ${options.apiKey}` },
|
|
304
|
-
body,
|
|
305
|
-
// intake 는 302 로 결과 신호 — 자동 추적 금지하고 Location 직접 읽음.
|
|
306
|
-
redirect: "manual",
|
|
307
|
-
signal: options.signal
|
|
308
|
-
});
|
|
309
|
-
} catch {
|
|
310
|
-
return { ok: false, code: "internal", message: inquiryErrorMessage("internal") };
|
|
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;
|
|
311
23
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
24
|
+
};
|
|
25
|
+
async function toApiError(response) {
|
|
26
|
+
let code = "request_failed";
|
|
27
|
+
let message = `RootTale CMS API error: ${response.status}`;
|
|
28
|
+
try {
|
|
29
|
+
const body = await response.json();
|
|
30
|
+
if (body.code) code = body.code;
|
|
31
|
+
if (body.message) message = body.message;
|
|
32
|
+
} catch {
|
|
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.31.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();
|
|
@@ -414,8 +189,8 @@ async function fetchBlogSettings(options) {
|
|
|
414
189
|
};
|
|
415
190
|
}
|
|
416
191
|
async function fetchCollections(options) {
|
|
417
|
-
|
|
418
|
-
const baseUrl = (options.baseUrl ??
|
|
192
|
+
assertServer();
|
|
193
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
419
194
|
const params = new URLSearchParams();
|
|
420
195
|
if (options.siteId) params.set("site_id", options.siteId);
|
|
421
196
|
const qs = params.toString();
|
|
@@ -467,6 +242,27 @@ var DEFAULT_BLOG_SETTINGS = {
|
|
|
467
242
|
authorProfileImagePositionX: 50,
|
|
468
243
|
authorProfileImagePositionY: 50
|
|
469
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
|
|
470
266
|
function businessProfileFromWire(json) {
|
|
471
267
|
if (json.configured === false) return null;
|
|
472
268
|
if (typeof json.name !== "string" || json.name.length === 0) return null;
|
|
@@ -483,242 +279,479 @@ function businessProfileFromWire(json) {
|
|
|
483
279
|
openingHours.push({ days: row.days, opens: row.opens, closes: row.closes });
|
|
484
280
|
}
|
|
485
281
|
}
|
|
486
|
-
return {
|
|
487
|
-
name: json.name,
|
|
488
|
-
legalName: json.legal_name ?? null,
|
|
489
|
-
businessType: typeof json.business_type === "string" && json.business_type.length > 0 ? json.business_type : "LocalBusiness",
|
|
490
|
-
telephone: json.telephone ?? null,
|
|
491
|
-
email: json.email ?? null,
|
|
492
|
-
address,
|
|
493
|
-
geo,
|
|
494
|
-
openingHours,
|
|
495
|
-
priceRange: json.price_range ?? null,
|
|
496
|
-
areaServed: json.area_served ?? [],
|
|
497
|
-
profiles: {
|
|
498
|
-
naverPlace: json.profiles?.naver_place ?? null,
|
|
499
|
-
googleBusiness: json.profiles?.google_business ?? null,
|
|
500
|
-
kakaoChannel: json.profiles?.kakao_channel ?? null,
|
|
501
|
-
instagram: json.profiles?.instagram ?? null,
|
|
502
|
-
naverBlog: json.profiles?.naver_blog ?? null
|
|
503
|
-
}
|
|
504
|
-
};
|
|
282
|
+
return {
|
|
283
|
+
name: json.name,
|
|
284
|
+
legalName: json.legal_name ?? null,
|
|
285
|
+
businessType: typeof json.business_type === "string" && json.business_type.length > 0 ? json.business_type : "LocalBusiness",
|
|
286
|
+
telephone: json.telephone ?? null,
|
|
287
|
+
email: json.email ?? null,
|
|
288
|
+
address,
|
|
289
|
+
geo,
|
|
290
|
+
openingHours,
|
|
291
|
+
priceRange: json.price_range ?? null,
|
|
292
|
+
areaServed: json.area_served ?? [],
|
|
293
|
+
profiles: {
|
|
294
|
+
naverPlace: json.profiles?.naver_place ?? null,
|
|
295
|
+
googleBusiness: json.profiles?.google_business ?? null,
|
|
296
|
+
kakaoChannel: json.profiles?.kakao_channel ?? null,
|
|
297
|
+
instagram: json.profiles?.instagram ?? null,
|
|
298
|
+
naverBlog: json.profiles?.naver_blog ?? null
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
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(/\/+$/, "");
|
|
372
|
+
const params = new URLSearchParams();
|
|
373
|
+
if (options.siteId) params.set("site_id", options.siteId);
|
|
374
|
+
const qs = params.toString();
|
|
375
|
+
const url = `${baseUrl}/v1/cms/public/menus/${encodeURIComponent(options.slug)}${qs ? `?${qs}` : ""}`;
|
|
376
|
+
const response = await fetch(url, {
|
|
377
|
+
method: "GET",
|
|
378
|
+
headers: authHeaders(options.apiKey),
|
|
379
|
+
signal: options.signal
|
|
380
|
+
});
|
|
381
|
+
if (response.status === 404) return null;
|
|
382
|
+
if (!response.ok) {
|
|
383
|
+
throw await toApiError(response);
|
|
384
|
+
}
|
|
385
|
+
const json = await response.json();
|
|
386
|
+
return menuFromWire(json);
|
|
387
|
+
}
|
|
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);
|
|
505
406
|
}
|
|
506
|
-
async function
|
|
507
|
-
|
|
508
|
-
const baseUrl = (options.baseUrl ??
|
|
407
|
+
async function fetchRedirects(options) {
|
|
408
|
+
assertServer();
|
|
409
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
509
410
|
const params = new URLSearchParams();
|
|
510
411
|
if (options.siteId) params.set("site_id", options.siteId);
|
|
511
412
|
const qs = params.toString();
|
|
512
|
-
const url = `${baseUrl}/v1/cms/public/
|
|
413
|
+
const url = `${baseUrl}/v1/cms/public/redirects${qs ? `?${qs}` : ""}`;
|
|
513
414
|
const response = await fetch(url, {
|
|
514
415
|
method: "GET",
|
|
515
416
|
headers: authHeaders(options.apiKey),
|
|
516
417
|
signal: options.signal
|
|
517
418
|
});
|
|
518
|
-
if (response.status === 404) return
|
|
419
|
+
if (response.status === 404) return [];
|
|
519
420
|
if (!response.ok) {
|
|
520
421
|
throw await toApiError(response);
|
|
521
422
|
}
|
|
522
423
|
const json = await response.json();
|
|
523
|
-
return
|
|
424
|
+
return (json.items ?? []).map((w) => ({
|
|
425
|
+
id: w.id,
|
|
426
|
+
fromPath: w.from_path,
|
|
427
|
+
toTarget: w.to_target,
|
|
428
|
+
statusCode: w.status_code === 302 ? 302 : 301
|
|
429
|
+
}));
|
|
524
430
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
431
|
+
|
|
432
|
+
// src/seo.ts
|
|
433
|
+
function generateRssXml(input) {
|
|
434
|
+
const lang = input.language ?? "ko-KR";
|
|
435
|
+
const lastBuild = input.items.length > 0 ? toRfc822(input.items[0].pubDate) : "";
|
|
436
|
+
const lines = [];
|
|
437
|
+
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
438
|
+
lines.push(
|
|
439
|
+
'<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">'
|
|
440
|
+
);
|
|
441
|
+
lines.push("<channel>");
|
|
442
|
+
lines.push(`<title>${escapeXml(input.title)}</title>`);
|
|
443
|
+
lines.push(`<link>${escapeXml(input.link)}</link>`);
|
|
444
|
+
lines.push(
|
|
445
|
+
`<atom:link href="${escapeXml(input.feedUrl)}" rel="self" type="application/rss+xml" />`
|
|
446
|
+
);
|
|
447
|
+
lines.push(
|
|
448
|
+
`<description>${escapeXml(input.description ?? input.title)}</description>`
|
|
449
|
+
);
|
|
450
|
+
lines.push(`<language>${escapeXml(lang)}</language>`);
|
|
451
|
+
if (lastBuild) lines.push(`<lastBuildDate>${lastBuild}</lastBuildDate>`);
|
|
452
|
+
for (const item of input.items) {
|
|
453
|
+
const guid = item.guid ?? item.link;
|
|
454
|
+
lines.push("<item>");
|
|
455
|
+
lines.push(`<title>${escapeXml(item.title)}</title>`);
|
|
456
|
+
lines.push(`<link>${escapeXml(item.link)}</link>`);
|
|
457
|
+
lines.push(`<guid isPermaLink="false">${escapeXml(guid)}</guid>`);
|
|
458
|
+
if (item.description) {
|
|
459
|
+
lines.push(`<description>${cdata(item.description)}</description>`);
|
|
460
|
+
}
|
|
461
|
+
const pub = toRfc822(item.pubDate);
|
|
462
|
+
if (pub) lines.push(`<pubDate>${pub}</pubDate>`);
|
|
463
|
+
lines.push("</item>");
|
|
464
|
+
}
|
|
465
|
+
lines.push("</channel>");
|
|
466
|
+
lines.push("</rss>");
|
|
467
|
+
return lines.join("\n");
|
|
468
|
+
}
|
|
469
|
+
function rssItemsFromPosts(posts, opts) {
|
|
470
|
+
return posts.map((p) => ({
|
|
471
|
+
title: p.title,
|
|
472
|
+
link: opts.postUrl(p),
|
|
473
|
+
guid: opts.postUrl(p),
|
|
474
|
+
description: p.excerpt,
|
|
475
|
+
pubDate: p.publishedAt
|
|
476
|
+
}));
|
|
477
|
+
}
|
|
478
|
+
function toRfc822(value) {
|
|
479
|
+
if (!value) return "";
|
|
480
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
481
|
+
return Number.isNaN(d.getTime()) ? "" : d.toUTCString();
|
|
482
|
+
}
|
|
483
|
+
function articleSchema(input) {
|
|
484
|
+
const schema = {
|
|
485
|
+
"@context": "https://schema.org",
|
|
486
|
+
"@type": "Article",
|
|
487
|
+
headline: input.headline,
|
|
488
|
+
mainEntityOfPage: { "@type": "WebPage", "@id": input.url }
|
|
489
|
+
};
|
|
490
|
+
if (input.description) schema.description = input.description;
|
|
491
|
+
if (input.image) schema.image = input.image;
|
|
492
|
+
const pub = toIso(input.datePublished);
|
|
493
|
+
if (pub) schema.datePublished = pub;
|
|
494
|
+
const mod = toIso(input.dateModified);
|
|
495
|
+
if (mod) schema.dateModified = mod;
|
|
496
|
+
if (input.authorName)
|
|
497
|
+
schema.author = { "@type": "Person", name: input.authorName };
|
|
498
|
+
if (input.publisherName) {
|
|
499
|
+
schema.publisher = {
|
|
500
|
+
"@type": "Organization",
|
|
501
|
+
name: input.publisherName,
|
|
502
|
+
...input.publisherLogo ? { logo: { "@type": "ImageObject", url: input.publisherLogo } } : {}
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
return schema;
|
|
506
|
+
}
|
|
507
|
+
function breadcrumbSchema(items) {
|
|
528
508
|
return {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
509
|
+
"@context": "https://schema.org",
|
|
510
|
+
"@type": "BreadcrumbList",
|
|
511
|
+
itemListElement: items.map((it, i) => ({
|
|
512
|
+
"@type": "ListItem",
|
|
513
|
+
position: i + 1,
|
|
514
|
+
name: it.name,
|
|
515
|
+
item: it.url
|
|
516
|
+
}))
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function organizationSchema(input) {
|
|
520
|
+
return {
|
|
521
|
+
"@context": "https://schema.org",
|
|
522
|
+
"@type": "Organization",
|
|
523
|
+
name: input.name,
|
|
524
|
+
url: input.url,
|
|
525
|
+
...input.logo ? { logo: input.logo } : {},
|
|
526
|
+
...input.sameAs && input.sameAs.length > 0 ? { sameAs: input.sameAs } : {}
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
var DAY_OF_WEEK = {
|
|
530
|
+
Mo: "Monday",
|
|
531
|
+
Tu: "Tuesday",
|
|
532
|
+
We: "Wednesday",
|
|
533
|
+
Th: "Thursday",
|
|
534
|
+
Fr: "Friday",
|
|
535
|
+
Sa: "Saturday",
|
|
536
|
+
Su: "Sunday"
|
|
537
|
+
};
|
|
538
|
+
function localBusinessSchema(profile, opts) {
|
|
539
|
+
const input = profile;
|
|
540
|
+
const types = Array.from(
|
|
541
|
+
/* @__PURE__ */ new Set([input.businessType || "LocalBusiness", "LocalBusiness"])
|
|
542
|
+
);
|
|
543
|
+
const schema = {
|
|
544
|
+
"@context": "https://schema.org",
|
|
545
|
+
"@type": types,
|
|
546
|
+
name: input.name,
|
|
547
|
+
url: opts.url
|
|
548
|
+
};
|
|
549
|
+
if (input.legalName) schema.legalName = input.legalName;
|
|
550
|
+
if (opts.image) schema.image = opts.image;
|
|
551
|
+
if (input.telephone) schema.telephone = input.telephone;
|
|
552
|
+
if (input.email) schema.email = input.email;
|
|
553
|
+
if (input.address) {
|
|
554
|
+
schema.address = {
|
|
555
|
+
"@type": "PostalAddress",
|
|
556
|
+
streetAddress: input.address.streetAddress,
|
|
557
|
+
addressLocality: input.address.addressLocality,
|
|
558
|
+
...input.address.addressRegion ? { addressRegion: input.address.addressRegion } : {},
|
|
559
|
+
...input.address.postalCode ? { postalCode: input.address.postalCode } : {},
|
|
560
|
+
addressCountry: "KR"
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
if (input.geo) {
|
|
564
|
+
schema.geo = {
|
|
565
|
+
"@type": "GeoCoordinates",
|
|
566
|
+
latitude: input.geo.latitude,
|
|
567
|
+
longitude: input.geo.longitude
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
const openingHours = (input.openingHours ?? []).filter(
|
|
571
|
+
(row) => row.days.length > 0 && row.opens && row.closes
|
|
572
|
+
);
|
|
573
|
+
if (openingHours.length > 0) {
|
|
574
|
+
schema.openingHoursSpecification = openingHours.map((row) => ({
|
|
575
|
+
"@type": "OpeningHoursSpecification",
|
|
576
|
+
dayOfWeek: row.days.map((d) => DAY_OF_WEEK[d] ?? d),
|
|
577
|
+
opens: row.opens,
|
|
578
|
+
closes: row.closes
|
|
579
|
+
}));
|
|
580
|
+
}
|
|
581
|
+
if (input.priceRange) schema.priceRange = input.priceRange;
|
|
582
|
+
if (input.areaServed && input.areaServed.length > 0) {
|
|
583
|
+
schema.areaServed = input.areaServed;
|
|
584
|
+
}
|
|
585
|
+
const sameAs = Object.values(input.profiles ?? {}).filter(
|
|
586
|
+
(u) => typeof u === "string" && u.length > 0
|
|
587
|
+
);
|
|
588
|
+
if (sameAs.length > 0) schema.sameAs = sameAs;
|
|
589
|
+
return schema;
|
|
590
|
+
}
|
|
591
|
+
function websiteSchema(input) {
|
|
592
|
+
return {
|
|
593
|
+
"@context": "https://schema.org",
|
|
594
|
+
"@type": "WebSite",
|
|
595
|
+
name: input.name,
|
|
596
|
+
url: input.url,
|
|
597
|
+
...input.searchUrl ? {
|
|
598
|
+
potentialAction: {
|
|
599
|
+
"@type": "SearchAction",
|
|
600
|
+
target: `${input.searchUrl}{search_term_string}`,
|
|
601
|
+
"query-input": "required name=search_term_string"
|
|
602
|
+
}
|
|
603
|
+
} : {}
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function faqSchema(qa) {
|
|
607
|
+
return {
|
|
608
|
+
"@context": "https://schema.org",
|
|
609
|
+
"@type": "FAQPage",
|
|
610
|
+
mainEntity: qa.map((x) => ({
|
|
611
|
+
"@type": "Question",
|
|
612
|
+
name: x.question,
|
|
613
|
+
acceptedAnswer: { "@type": "Answer", text: x.answer }
|
|
614
|
+
}))
|
|
542
615
|
};
|
|
543
616
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
617
|
+
function jsonLdScript(schema) {
|
|
618
|
+
const json = JSON.stringify(schema).replace(/</g, "\\u003c");
|
|
619
|
+
return `<script type="application/ld+json">${json}</script>`;
|
|
620
|
+
}
|
|
621
|
+
function toIso(value) {
|
|
622
|
+
if (!value) return null;
|
|
623
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
624
|
+
return Number.isNaN(d.getTime()) ? null : d.toISOString();
|
|
625
|
+
}
|
|
626
|
+
function escapeXml(value) {
|
|
627
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
628
|
+
}
|
|
629
|
+
function cdata(value) {
|
|
630
|
+
return `<![CDATA[${value.replace(/]]>/g, "]]]]><![CDATA[>")}]]>`;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// src/inquiry.ts
|
|
634
|
+
var DEFAULT_BASE_URL2 = "https://api.roottale.com";
|
|
635
|
+
var INQUIRY_ERROR_MESSAGES = {
|
|
636
|
+
consent_privacy: "\uAC1C\uC778\uC815\uBCF4 \uC218\uC9D1\xB7\uC774\uC6A9 \uB3D9\uC758\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
637
|
+
consent_overseas: "\uC758\uB8CC \uBD84\uC57C\uB294 \uAD6D\uC678\uC774\uC804 \uB3D9\uC758\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
638
|
+
invalid_vertical: "\uC5C5\uC885 \uBD84\uB958\uAC00 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
|
|
639
|
+
missing_fields: "\uD544\uC218 \uD56D\uBAA9\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
640
|
+
invalid_email: "\uC774\uBA54\uC77C \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
|
|
641
|
+
invalid_api_key: "\uC5F0\uB3D9 \uC778\uC99D\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. API \uD0A4\uB97C \uD655\uC778\uD574\uC8FC\uC138\uC694.",
|
|
642
|
+
internal: "\uC804\uC1A1\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694."
|
|
643
|
+
};
|
|
644
|
+
var RESERVED_INQUIRY_KEYS = /* @__PURE__ */ new Set([
|
|
645
|
+
"vertical",
|
|
646
|
+
"contact_name",
|
|
647
|
+
"business_name",
|
|
648
|
+
"email",
|
|
649
|
+
"phone",
|
|
650
|
+
"message",
|
|
651
|
+
"consultation_field",
|
|
652
|
+
"current_site_url",
|
|
653
|
+
"privacy_consent",
|
|
654
|
+
"overseas_transfer_consent",
|
|
655
|
+
"lead_kind"
|
|
656
|
+
]);
|
|
657
|
+
function inquiryErrorMessage(code) {
|
|
658
|
+
return code && INQUIRY_ERROR_MESSAGES[code] || INQUIRY_ERROR_MESSAGES.internal;
|
|
659
|
+
}
|
|
660
|
+
function parseInquiryRedirect(location, base = DEFAULT_BASE_URL2) {
|
|
661
|
+
if (!location) {
|
|
662
|
+
return { ok: false, code: null, message: inquiryErrorMessage(null) };
|
|
552
663
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
559
|
-
if (options.cursor) params.set("cursor", options.cursor);
|
|
560
|
-
if (options.type) params.set("type", options.type);
|
|
561
|
-
if (options.siteId) params.set("site_id", options.siteId);
|
|
562
|
-
const qs = params.toString();
|
|
563
|
-
const url = `${baseUrl}/v1/cms/public/posts${qs ? `?${qs}` : ""}`;
|
|
564
|
-
const response = await fetch(url, {
|
|
565
|
-
method: "GET",
|
|
566
|
-
headers: authHeaders(options.apiKey),
|
|
567
|
-
signal: options.signal
|
|
568
|
-
});
|
|
569
|
-
if (!response.ok) {
|
|
570
|
-
throw await toApiError(response);
|
|
664
|
+
let url;
|
|
665
|
+
try {
|
|
666
|
+
url = new URL(location, base);
|
|
667
|
+
} catch {
|
|
668
|
+
return { ok: false, code: null, message: inquiryErrorMessage(null) };
|
|
571
669
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
hasMore: json.has_more,
|
|
576
|
-
nextCursor: json.next_cursor
|
|
577
|
-
};
|
|
670
|
+
if (url.searchParams.get("ok") === "1") return { ok: true };
|
|
671
|
+
const code = url.searchParams.get("err");
|
|
672
|
+
return { ok: false, code, message: inquiryErrorMessage(code) };
|
|
578
673
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
if (options.siteId) params.set("site_id", options.siteId);
|
|
584
|
-
const qs = params.toString();
|
|
585
|
-
const url = `${baseUrl}/v1/cms/public/posts/${encodeURIComponent(options.slugOrId)}${qs ? `?${qs}` : ""}`;
|
|
586
|
-
const response = await fetch(url, {
|
|
587
|
-
method: "GET",
|
|
588
|
-
headers: authHeaders(options.apiKey),
|
|
589
|
-
signal: options.signal
|
|
590
|
-
});
|
|
591
|
-
if (response.status === 404) return null;
|
|
592
|
-
if (!response.ok) {
|
|
593
|
-
throw await toApiError(response);
|
|
674
|
+
function formatKoreanPhoneNumber(input) {
|
|
675
|
+
let digits = input.replace(/\D/g, "");
|
|
676
|
+
if (digits.startsWith("82") && digits.length >= 11) {
|
|
677
|
+
digits = `0${digits.slice(2)}`;
|
|
594
678
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
}
|
|
598
|
-
async function searchPosts(options) {
|
|
599
|
-
assertServer2();
|
|
600
|
-
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
|
|
601
|
-
const params = new URLSearchParams();
|
|
602
|
-
params.set("q", options.query);
|
|
603
|
-
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
604
|
-
if (options.type) params.set("type", options.type);
|
|
605
|
-
if (options.siteId) params.set("site_id", options.siteId);
|
|
606
|
-
const url = `${baseUrl}/v1/cms/public/search?${params.toString()}`;
|
|
607
|
-
const response = await fetch(url, {
|
|
608
|
-
method: "GET",
|
|
609
|
-
headers: authHeaders(options.apiKey),
|
|
610
|
-
signal: options.signal
|
|
611
|
-
});
|
|
612
|
-
if (response.status === 404) return [];
|
|
613
|
-
if (!response.ok) {
|
|
614
|
-
throw await toApiError(response);
|
|
679
|
+
if (digits.length === 11) {
|
|
680
|
+
return `${digits.slice(0, 3)}-${digits.slice(3, 7)}-${digits.slice(7)}`;
|
|
615
681
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
slug: w.slug,
|
|
622
|
-
excerpt: w.excerpt,
|
|
623
|
-
featuredImageUrl: w.featured_media_url ?? null,
|
|
624
|
-
publishedAt: w.published_at
|
|
625
|
-
}));
|
|
626
|
-
}
|
|
627
|
-
function menuItemFromWire(w) {
|
|
628
|
-
const item = { id: w.id, label: w.label, url: w.url };
|
|
629
|
-
if (w.new_tab) item.newTab = true;
|
|
630
|
-
if (w.children && w.children.length > 0) {
|
|
631
|
-
item.children = w.children.map(menuItemFromWire);
|
|
682
|
+
if (digits.length === 10) {
|
|
683
|
+
if (digits.startsWith("02")) {
|
|
684
|
+
return `${digits.slice(0, 2)}-${digits.slice(2, 6)}-${digits.slice(6)}`;
|
|
685
|
+
}
|
|
686
|
+
return `${digits.slice(0, 3)}-${digits.slice(3, 6)}-${digits.slice(6)}`;
|
|
632
687
|
}
|
|
633
|
-
|
|
634
|
-
}
|
|
635
|
-
function menuFromWire(w) {
|
|
636
|
-
return {
|
|
637
|
-
id: w.id,
|
|
638
|
-
name: w.name,
|
|
639
|
-
slug: w.slug,
|
|
640
|
-
items: (w.items ?? []).map(menuItemFromWire),
|
|
641
|
-
updatedAt: w.updated_at ?? null
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
async function fetchMenu(options) {
|
|
645
|
-
assertServer2();
|
|
646
|
-
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
|
|
647
|
-
const params = new URLSearchParams();
|
|
648
|
-
if (options.siteId) params.set("site_id", options.siteId);
|
|
649
|
-
const qs = params.toString();
|
|
650
|
-
const url = `${baseUrl}/v1/cms/public/menus/${encodeURIComponent(options.slug)}${qs ? `?${qs}` : ""}`;
|
|
651
|
-
const response = await fetch(url, {
|
|
652
|
-
method: "GET",
|
|
653
|
-
headers: authHeaders(options.apiKey),
|
|
654
|
-
signal: options.signal
|
|
655
|
-
});
|
|
656
|
-
if (response.status === 404) return null;
|
|
657
|
-
if (!response.ok) {
|
|
658
|
-
throw await toApiError(response);
|
|
688
|
+
if (digits.length === 8) {
|
|
689
|
+
return `${digits.slice(0, 4)}-${digits.slice(4)}`;
|
|
659
690
|
}
|
|
660
|
-
|
|
661
|
-
return menuFromWire(json);
|
|
691
|
+
return input.trim();
|
|
662
692
|
}
|
|
663
|
-
async function
|
|
693
|
+
async function submitInquiry(options) {
|
|
664
694
|
assertServer2();
|
|
665
695
|
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
if (
|
|
676
|
-
if (
|
|
677
|
-
|
|
696
|
+
const f = options.fields;
|
|
697
|
+
const body = new FormData();
|
|
698
|
+
body.set("vertical", f.vertical);
|
|
699
|
+
body.set("contact_name", f.contactName);
|
|
700
|
+
body.set("business_name", f.businessName);
|
|
701
|
+
body.set("email", f.email);
|
|
702
|
+
body.set("phone", formatKoreanPhoneNumber(f.phone));
|
|
703
|
+
if (f.message) body.set("message", f.message);
|
|
704
|
+
if (f.consultationField) body.set("consultation_field", f.consultationField);
|
|
705
|
+
if (f.currentSiteUrl) body.set("current_site_url", f.currentSiteUrl);
|
|
706
|
+
if (f.privacyConsent) body.set("privacy_consent", "on");
|
|
707
|
+
if (f.overseasTransferConsent) body.set("overseas_transfer_consent", "on");
|
|
708
|
+
if (f.leadKind) body.set("lead_kind", f.leadKind);
|
|
709
|
+
if (f.attribution) {
|
|
710
|
+
for (const [key, value] of Object.entries(f.attribution)) {
|
|
711
|
+
if (typeof value !== "string" || !value.trim()) continue;
|
|
712
|
+
body.set(`attr_${key}`, value.trim());
|
|
713
|
+
}
|
|
678
714
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
featuredMediaId: wire.featured_media_id,
|
|
692
|
-
featuredImageUrl: wire.featured_media_url ?? null,
|
|
693
|
-
authorId: wire.author_id,
|
|
694
|
-
authorName: wire.author_name ?? null,
|
|
695
|
-
authorImageUrl: wire.author_image_url ?? null,
|
|
696
|
-
authorBio: wire.author_bio ?? null,
|
|
697
|
-
bodyJson: wire.body_json,
|
|
698
|
-
status: wire.status,
|
|
699
|
-
metaJson: wire.meta_json,
|
|
700
|
-
fields: wire.fields ?? null,
|
|
701
|
-
terms: wire.terms ?? [],
|
|
702
|
-
publishedAt: wire.published_at,
|
|
703
|
-
createdAt: wire.created_at,
|
|
704
|
-
updatedAt: wire.updated_at
|
|
705
|
-
};
|
|
706
|
-
}
|
|
707
|
-
async function toApiError(response) {
|
|
708
|
-
let code = "request_failed";
|
|
709
|
-
let message = `RootTale CMS API error: ${response.status}`;
|
|
715
|
+
if (f.extras) {
|
|
716
|
+
for (const [rawKey, rawValue] of Object.entries(f.extras)) {
|
|
717
|
+
const key = rawKey.trim();
|
|
718
|
+
const value = String(rawValue ?? "").trim();
|
|
719
|
+
if (!key || !value) continue;
|
|
720
|
+
if (key.startsWith("_") || RESERVED_INQUIRY_KEYS.has(key)) continue;
|
|
721
|
+
if (key.startsWith("attr_")) continue;
|
|
722
|
+
if (body.has(key)) continue;
|
|
723
|
+
body.set(key, value);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
let response;
|
|
710
727
|
try {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
728
|
+
response = await fetch(`${baseUrl}/v1/public/inquiries`, {
|
|
729
|
+
method: "POST",
|
|
730
|
+
headers: { authorization: `Bearer ${options.apiKey}` },
|
|
731
|
+
body,
|
|
732
|
+
// intake 는 302 로 결과 신호 — 자동 추적 금지하고 Location 직접 읽음.
|
|
733
|
+
redirect: "manual",
|
|
734
|
+
signal: options.signal
|
|
735
|
+
});
|
|
714
736
|
} catch {
|
|
737
|
+
return { ok: false, code: "internal", message: inquiryErrorMessage("internal") };
|
|
715
738
|
}
|
|
716
|
-
|
|
739
|
+
if (response.status === 401) {
|
|
740
|
+
return {
|
|
741
|
+
ok: false,
|
|
742
|
+
code: "invalid_api_key",
|
|
743
|
+
message: inquiryErrorMessage("invalid_api_key")
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
const location = response.headers.get("location");
|
|
747
|
+
if (location) return parseInquiryRedirect(location, baseUrl);
|
|
748
|
+
if (response.ok) return { ok: true };
|
|
749
|
+
return { ok: false, code: "internal", message: inquiryErrorMessage("internal") };
|
|
717
750
|
}
|
|
718
751
|
function assertServer2() {
|
|
719
752
|
if (typeof window !== "undefined") {
|
|
720
753
|
throw new Error(
|
|
721
|
-
"@roottale/cms-client
|
|
754
|
+
"@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)."
|
|
722
755
|
);
|
|
723
756
|
}
|
|
724
757
|
}
|
|
@@ -740,6 +773,7 @@ export {
|
|
|
740
773
|
fetchMenus,
|
|
741
774
|
fetchPost,
|
|
742
775
|
fetchPosts,
|
|
776
|
+
fetchRedirects,
|
|
743
777
|
fetchTheme,
|
|
744
778
|
formatKoreanPhoneNumber,
|
|
745
779
|
generateRssXml,
|