|
| 1 | +import type { ContentProvider, PageRecord } from "./types"; |
| 2 | + |
| 3 | +/** |
| 4 | + * Markdown templates for the privacy and cookie policies, embedded |
| 5 | + * in source so the CMS can seed them with no filesystem access at |
| 6 | + * Worker runtime (R2 / KV would be overkill for a few KB). |
| 7 | + * |
| 8 | + * The on-disk versions in `static/legal-templates/*.md` are the |
| 9 | + * canonical reference; this module is a copy that's served from |
| 10 | + * runtime memory. Keep them in sync when editing. |
| 11 | + */ |
| 12 | + |
| 13 | +const PRIVACY_POLICY_TEMPLATE_EN = `# Privacy Policy |
| 14 | +
|
| 15 | +> **⚠️ This is a template seeded by Khao Pad.** Replace every \`[bracketed placeholder]\` with your actual practices, then have a lawyer review before publishing. |
| 16 | +
|
| 17 | +_Last updated: [Date]_ |
| 18 | +
|
| 19 | +## Who we are |
| 20 | +
|
| 21 | +[Site Name] is operated by [Operator Legal Entity], registered at [Operator Address], reachable at [Contact Email]. |
| 22 | +
|
| 23 | +For questions about your personal data, contact [DPO or Privacy Contact Email]. |
| 24 | +
|
| 25 | +## What data we collect |
| 26 | +
|
| 27 | +- **Account data** — when you create an account: email, name, hashed password. |
| 28 | +- **Session data** — a session cookie set by Better Auth so you stay signed in. |
| 29 | +- **Cookie consent** — your choices in the cookie banner are stored in a first-party cookie called \`khaopad_consent\`. |
| 30 | +- **Analytics data** — _only if you opt in_: counts of which pages are visited, by day. We do not store IP addresses or user agents. |
| 31 | +
|
| 32 | +## Why we collect it |
| 33 | +
|
| 34 | +- **Account / Session** — to authenticate you and keep you signed in. Legal basis: contract performance. |
| 35 | +- **Analytics** — to understand which content is read. Legal basis: consent. |
| 36 | +
|
| 37 | +## Who we share it with |
| 38 | +
|
| 39 | +We use the following processors: |
| 40 | +
|
| 41 | +- **Cloudflare** (Workers, D1, R2, KV) — hosting and storage. |
| 42 | +- **Better Auth** — authentication library running inside our worker. No external service. |
| 43 | +
|
| 44 | +We do **not** sell personal data. |
| 45 | +
|
| 46 | +## Your rights |
| 47 | +
|
| 48 | +Depending on your jurisdiction (GDPR, PDPA, CCPA, etc.) you have rights to: |
| 49 | +
|
| 50 | +- access the personal data we hold about you, |
| 51 | +- ask us to correct it, |
| 52 | +- ask us to delete it, |
| 53 | +- object to specific processing, |
| 54 | +- withdraw consent for analytics at any time via the cookie banner. |
| 55 | +
|
| 56 | +To exercise these rights email [DPO or Privacy Contact Email]. |
| 57 | +
|
| 58 | +## Cookies |
| 59 | +
|
| 60 | +See the separate [Cookie Policy](/[locale]/cookie-policy) for the full list of cookies. |
| 61 | +
|
| 62 | +## Contact |
| 63 | +
|
| 64 | +[Operator Legal Entity] |
| 65 | +[Operator Address] |
| 66 | +[Contact Email] |
| 67 | +`; |
| 68 | + |
| 69 | +const COOKIE_POLICY_TEMPLATE_EN = `# Cookie Policy |
| 70 | +
|
| 71 | +> **⚠️ This is a template seeded by Khao Pad.** Edit the cookie list to match what your specific deployment actually sets. The defaults reflect a stock Khao Pad install. |
| 72 | +
|
| 73 | +_Last updated: [Date]_ |
| 74 | +
|
| 75 | +## What are cookies? |
| 76 | +
|
| 77 | +A cookie is a small text file stored on your device by your browser. We use them to keep you signed in, remember your language preference, and (if you opt in) count page views. |
| 78 | +
|
| 79 | +## Functional cookies (always on) |
| 80 | +
|
| 81 | +| Cookie name | Purpose | Lifetime | |
| 82 | +| --------------------- | ------------------------------------------------------------- | -------- | |
| 83 | +| \`better-auth.session\` | Authentication session — keeps you signed in after login | 30 days | |
| 84 | +| \`PARAGLIDE_LOCALE\` | Remembers your language choice | 1 year | |
| 85 | +| \`khaopad_consent\` | Stores your decision in the cookie banner | 1 year | |
| 86 | +
|
| 87 | +## Analytics cookies (off until you accept) |
| 88 | +
|
| 89 | +Set only if you check **Analytics** in the banner. |
| 90 | +
|
| 91 | +We do **not** use Google Analytics, Facebook Pixel, or any other third-party tracker by default. |
| 92 | +
|
| 93 | +## Marketing cookies (off until you accept) |
| 94 | +
|
| 95 | +Set only if you check **Marketing** in the banner. (None by default.) |
| 96 | +
|
| 97 | +## Changing your choices |
| 98 | +
|
| 99 | +Use the cookie banner that appears the first time you visit, or clear the \`khaopad_consent\` cookie to see the banner again. |
| 100 | +
|
| 101 | +## Questions |
| 102 | +
|
| 103 | +[Contact Email] |
| 104 | +`; |
| 105 | + |
| 106 | +/** |
| 107 | + * Seed legal pages (privacy-policy + cookie-policy) for a fresh |
| 108 | + * install. Idempotent: skips templates whose slug already exists. |
| 109 | + * |
| 110 | + * Pre-fills `[Site Name]` and `[Contact Email]` from `site_settings` |
| 111 | + * when those values are present; everything else stays bracketed for |
| 112 | + * the operator to edit. The new pages start in draft status — they |
| 113 | + * never auto-publish, even if the seed runs. |
| 114 | + */ |
| 115 | +export async function seedLegalPages( |
| 116 | + content: ContentProvider, |
| 117 | + authorId: string, |
| 118 | +): Promise<{ |
| 119 | + created: PageRecord[]; |
| 120 | + skipped: Array<{ slug: string; reason: string }>; |
| 121 | +}> { |
| 122 | + const settings = await content.getSettings().catch(() => null); |
| 123 | + const siteName = settings?.siteName ?? "[Site Name]"; |
| 124 | + |
| 125 | + const templates = [ |
| 126 | + { |
| 127 | + slug: "privacy-policy", |
| 128 | + title: "Privacy Policy", |
| 129 | + template: "legal" as const, |
| 130 | + body: PRIVACY_POLICY_TEMPLATE_EN.replace(/\[Site Name\]/g, siteName), |
| 131 | + }, |
| 132 | + { |
| 133 | + slug: "cookie-policy", |
| 134 | + title: "Cookie Policy", |
| 135 | + template: "legal" as const, |
| 136 | + body: COOKIE_POLICY_TEMPLATE_EN, |
| 137 | + }, |
| 138 | + ]; |
| 139 | + |
| 140 | + const created: PageRecord[] = []; |
| 141 | + const skipped: Array<{ slug: string; reason: string }> = []; |
| 142 | + for (const t of templates) { |
| 143 | + const existing = await content.getPageBySlug(t.slug); |
| 144 | + if (existing) { |
| 145 | + skipped.push({ slug: t.slug, reason: "already exists" }); |
| 146 | + continue; |
| 147 | + } |
| 148 | + const page = await content.createPage({ |
| 149 | + slug: t.slug, |
| 150 | + template: t.template, |
| 151 | + status: "draft", |
| 152 | + authorId, |
| 153 | + localizations: { |
| 154 | + en: { |
| 155 | + title: t.title, |
| 156 | + body: t.body, |
| 157 | + seoTitle: t.title, |
| 158 | + seoDescription: `${t.title} for ${siteName}.`, |
| 159 | + }, |
| 160 | + }, |
| 161 | + }); |
| 162 | + created.push(page); |
| 163 | + } |
| 164 | + return { created, skipped }; |
| 165 | +} |
0 commit comments