@roottale/cms-client 0.31.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 +11 -0
- package/dist/server.d.ts +248 -239
- package/dist/server.js +519 -510
- 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) {
|
|
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) {
|
|
93
10
|
return {
|
|
94
|
-
|
|
95
|
-
"
|
|
96
|
-
name: input.name,
|
|
97
|
-
url: input.url,
|
|
98
|
-
...input.logo ? { logo: input.logo } : {},
|
|
99
|
-
...input.sameAs && input.sameAs.length > 0 ? { sameAs: input.sameAs } : {}
|
|
11
|
+
authorization: `Bearer ${apiKey}`,
|
|
12
|
+
"x-roottale-client": `cms-client/${CLIENT_VERSION}`
|
|
100
13
|
};
|
|
101
14
|
}
|
|
102
|
-
var
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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;
|
|
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;
|
|
157
23
|
}
|
|
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
24
|
};
|
|
217
|
-
|
|
218
|
-
"
|
|
219
|
-
|
|
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;
|
|
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
33
|
}
|
|
312
|
-
|
|
313
|
-
return {
|
|
314
|
-
ok: false,
|
|
315
|
-
code: "invalid_api_key",
|
|
316
|
-
message: inquiryErrorMessage("invalid_api_key")
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
const location = response.headers.get("location");
|
|
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;
|
|
@@ -503,13 +299,80 @@ function businessProfileFromWire(json) {
|
|
|
503
299
|
}
|
|
504
300
|
};
|
|
505
301
|
}
|
|
506
|
-
async function fetchBusinessProfile(options) {
|
|
507
|
-
|
|
508
|
-
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(/\/+$/, "");
|
|
509
372
|
const params = new URLSearchParams();
|
|
510
373
|
if (options.siteId) params.set("site_id", options.siteId);
|
|
511
374
|
const qs = params.toString();
|
|
512
|
-
const url = `${baseUrl}/v1/cms/public/
|
|
375
|
+
const url = `${baseUrl}/v1/cms/public/menus/${encodeURIComponent(options.slug)}${qs ? `?${qs}` : ""}`;
|
|
513
376
|
const response = await fetch(url, {
|
|
514
377
|
method: "GET",
|
|
515
378
|
headers: authHeaders(options.apiKey),
|
|
@@ -520,205 +383,351 @@ async function fetchBusinessProfile(options) {
|
|
|
520
383
|
throw await toApiError(response);
|
|
521
384
|
}
|
|
522
385
|
const json = await response.json();
|
|
523
|
-
return
|
|
386
|
+
return menuFromWire(json);
|
|
524
387
|
}
|
|
525
|
-
function
|
|
526
|
-
|
|
527
|
-
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) {
|
|
528
484
|
return {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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
|
+
}))
|
|
542
591
|
};
|
|
543
592
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
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) };
|
|
552
639
|
}
|
|
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);
|
|
640
|
+
let url;
|
|
641
|
+
try {
|
|
642
|
+
url = new URL(location, base);
|
|
643
|
+
} catch {
|
|
644
|
+
return { ok: false, code: null, message: inquiryErrorMessage(null) };
|
|
571
645
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
hasMore: json.has_more,
|
|
576
|
-
nextCursor: json.next_cursor
|
|
577
|
-
};
|
|
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) };
|
|
578
649
|
}
|
|
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);
|
|
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)}`;
|
|
594
654
|
}
|
|
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);
|
|
655
|
+
if (digits.length === 11) {
|
|
656
|
+
return `${digits.slice(0, 3)}-${digits.slice(3, 7)}-${digits.slice(7)}`;
|
|
615
657
|
}
|
|
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);
|
|
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)}`;
|
|
632
663
|
}
|
|
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);
|
|
664
|
+
if (digits.length === 8) {
|
|
665
|
+
return `${digits.slice(0, 4)}-${digits.slice(4)}`;
|
|
659
666
|
}
|
|
660
|
-
|
|
661
|
-
return menuFromWire(json);
|
|
667
|
+
return input.trim();
|
|
662
668
|
}
|
|
663
|
-
async function
|
|
669
|
+
async function submitInquiry(options) {
|
|
664
670
|
assertServer2();
|
|
665
671
|
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
if (
|
|
676
|
-
if (
|
|
677
|
-
|
|
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
|
+
}
|
|
678
690
|
}
|
|
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}`;
|
|
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;
|
|
710
703
|
try {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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
|
+
});
|
|
714
712
|
} catch {
|
|
713
|
+
return { ok: false, code: "internal", message: inquiryErrorMessage("internal") };
|
|
715
714
|
}
|
|
716
|
-
|
|
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") };
|
|
717
726
|
}
|
|
718
727
|
function assertServer2() {
|
|
719
728
|
if (typeof window !== "undefined") {
|
|
720
729
|
throw new Error(
|
|
721
|
-
"@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)."
|
|
722
731
|
);
|
|
723
732
|
}
|
|
724
733
|
}
|