From 305e2bd2174625fbbb5766bbc2d01847a2d9aed0 Mon Sep 17 00:00:00 2001 From: Kunthawat Date: Tue, 10 Mar 2026 13:09:17 +0700 Subject: [PATCH] feat: Add PDPA compliance features to Astro project - Cookie consent banner with Thai language - Consent logging API with SQLite database - Admin dashboard for viewing consent logs - PDPA-compliant privacy policy - Environment configuration template PDPA compliance as per website-creator skill specifications. Build: npm install, Astro project ready for Docker deployment. --- dealplustech-astro/.env.example | 4 + dealplustech-astro/.gitignore | 10 +- .../src/components/CookieConsentBanner.astro | 227 +++++++++++++ .../src/pages/admin/consent-logs.astro | 309 ++++++++++++++++++ .../src/pages/api/consent/index.ts | 92 ++++++ .../src/pages/privacy-policy.astro | 203 ++++++++++++ 6 files changed, 844 insertions(+), 1 deletion(-) create mode 100644 dealplustech-astro/.env.example create mode 100644 dealplustech-astro/src/components/CookieConsentBanner.astro create mode 100644 dealplustech-astro/src/pages/admin/consent-logs.astro create mode 100644 dealplustech-astro/src/pages/api/consent/index.ts create mode 100644 dealplustech-astro/src/pages/privacy-policy.astro diff --git a/dealplustech-astro/.env.example b/dealplustech-astro/.env.example new file mode 100644 index 000000000..9e7485e2d --- /dev/null +++ b/dealplustech-astro/.env.example @@ -0,0 +1,4 @@ +PUBLIC_UMAMI_WEBSITE_ID=your-website-id-here +PUBLIC_UMAMI_DOMAIN=https://analytics.moreminimore.com +ADMIN_PASSWORD=changeme +ASTRO_DB_REMOTE_URL=file:./data/consent.db diff --git a/dealplustech-astro/.gitignore b/dealplustech-astro/.gitignore index d46219b93..123e31af4 100644 --- a/dealplustech-astro/.gitignore +++ b/dealplustech-astro/.gitignore @@ -1 +1,9 @@ -.node-version +node_modules/ +dist/ +.astro/ +data/*.db +.env +.env.* +*.log +.DS_Store +Thumbs.db diff --git a/dealplustech-astro/src/components/CookieConsentBanner.astro b/dealplustech-astro/src/components/CookieConsentBanner.astro new file mode 100644 index 000000000..0eae55cbc --- /dev/null +++ b/dealplustech-astro/src/components/CookieConsentBanner.astro @@ -0,0 +1,227 @@ +--- +// Cookie Consent Banner Component +// Displays on first visit, allows users to accept/reject cookie categories +--- + + + + diff --git a/dealplustech-astro/src/pages/admin/consent-logs.astro b/dealplustech-astro/src/pages/admin/consent-logs.astro new file mode 100644 index 000000000..796c009a0 --- /dev/null +++ b/dealplustech-astro/src/pages/admin/consent-logs.astro @@ -0,0 +1,309 @@ +--- +import { createClient } from '@libsql/client'; +import BaseLayout from '../../layouts/BaseLayout.astro'; + +const client = createClient({ + url: import.meta.env.ASTRO_DB_REMOTE_URL || 'file:./data/consent.db', + authToken: import.meta.env.ASTRO_DB_APP_TOKEN, +}); + +const ADMIN_PASSWORD = import.meta.env.ADMIN_PASSWORD || 'changeme'; +let isAuthenticated = false; + +const authCookie = Astro.cookies.get('admin_auth')?.value; +if (authCookie === 'true') { + isAuthenticated = true; +} + +if (Astro.request.method === 'POST') { + const formData = await Astro.request.formData(); + const action = formData.get('action'); + + if (action === 'login') { + const password = formData.get('password'); + if (password === ADMIN_PASSWORD) { + Astro.cookies.set('admin_auth', 'true', { + path: '/', + httpOnly: true, + secure: import.meta.env.PROD, + maxAge: 60 * 60 * 2 + }); + isAuthenticated = true; + } + } else if (action === 'logout') { + Astro.cookies.delete('admin_auth', { path: '/' }); + isAuthenticated = false; + } else if (action === 'delete' && isAuthenticated) { + const id = formData.get('id'); + if (id) { + await client.execute({ + sql: 'DELETE FROM consent_logs WHERE id = ?', + args: [Number(id)], + }); + } + } else if (action === 'delete-all' && isAuthenticated) { + await client.execute({ + sql: 'DELETE FROM consent_logs', + args: [], + }); + } +} + +let consentLogs: any[] = []; +if (isAuthenticated) { + const result = await client.execute({ + sql: 'SELECT * FROM consent_logs ORDER BY created_at DESC LIMIT 100', + args: [], + }); + consentLogs = result.rows || []; +} +--- + + +
+
+
+
+
+

+ Consent Logs Admin +

+

+ View and manage user consent records (PDPA compliance) +

+
+ + {isAuthenticated ? ( +
+ + +
+ ) : null} +
+ + {!isAuthenticated ? ( +
+
+

Admin Login

+
+ +
+ + +
+ +
+
+
+ ) : ( +
+
+
+
Total Consents
+
{consentLogs.length}
+
+
+
Analytics Accepted
+
+ {consentLogs.filter(l => l.analytics === 1).length} +
+
+
+
Marketing Accepted
+
+ {consentLogs.filter(l => l.marketing === 1).length} +
+
+
+
Acceptance Rate
+
+ {consentLogs.length > 0 + ? Math.round((consentLogs.filter(l => l.analytics === 1).length / consentLogs.length) * 100) + : 0}% +
+
+
+ +
+
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + {consentLogs.map((log, index) => ( + + + + + + + + + + + + ))} + +
IDSession IDTimestampLocaleEssentialAnalyticsMarketingPolicy VersionActions
{log.id}{log.session_id}{new Date(log.timestamp).toLocaleString('th-TH')}{log.locale} + + {log.essential === 1 ? 'Yes' : 'No'} + + + {log.analytics === 1 ? ( + Accepted + ) : ( + Rejected + )} + + {log.marketing === 1 ? ( + Accepted + ) : ( + Rejected + )} + {log.policy_version} +
+ + + +
+
+
+ + {consentLogs.length === 0 && ( +
+

No consent logs found

+
+ )} + + +
+ )} +
+
+
+
+ + diff --git a/dealplustech-astro/src/pages/api/consent/index.ts b/dealplustech-astro/src/pages/api/consent/index.ts new file mode 100644 index 000000000..4a76f5262 --- /dev/null +++ b/dealplustech-astro/src/pages/api/consent/index.ts @@ -0,0 +1,92 @@ +import { createClient } from '@libsql/client'; +import type { APIRoute } from 'astro'; + +const client = createClient({ + url: import.meta.env.ASTRO_DB_REMOTE_URL || 'file:./data/consent.db', + authToken: import.meta.env.ASTRO_DB_APP_TOKEN, +}); + +await client.execute({ + sql: ` + CREATE TABLE IF NOT EXISTS consent_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + session_id TEXT UNIQUE NOT NULL, + timestamp TEXT NOT NULL, + locale TEXT DEFAULT 'th', + essential INTEGER NOT NULL DEFAULT 1, + analytics INTEGER NOT NULL DEFAULT 0, + marketing INTEGER NOT NULL DEFAULT 0, + policy_version TEXT NOT NULL, + ip_hash TEXT, + user_agent TEXT, + created_at TEXT DEFAULT CURRENT_TIMESTAMP + ) + `, +}); + +export const POST: APIRoute = async ({ request }) => { + try { + const body = await request.json(); + const { sessionId, consent, policyVersion } = body; + + if (!sessionId || !consent) { + return new Response(JSON.stringify({ error: 'Missing required fields' }), { + status: 400, + headers: { 'Content-Type': 'application/json' }, + }); + } + + const ip = request.headers.get('x-forwarded-for') || 'unknown'; + const ipHash = await hashIP(ip); + const userAgent = request.headers.get('user-agent') || 'unknown'; + const acceptLanguage = request.headers.get('accept-language') || 'th'; + const locale = acceptLanguage.startsWith('th') ? 'th' : 'en'; + + await client.execute({ + sql: ` + INSERT OR REPLACE INTO consent_logs ( + session_id, + timestamp, + locale, + essential, + analytics, + marketing, + policy_version, + ip_hash, + user_agent + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + `, + args: [ + sessionId, + consent.timestamp, + locale, + consent.essential ? 1 : 0, + consent.analytics ? 1 : 0, + consent.marketing ? 1 : 0, + policyVersion, + ipHash, + userAgent, + ], + }); + + return new Response(JSON.stringify({ success: true }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + } catch (error) { + console.error('Consent API error:', error); + return new Response(JSON.stringify({ error: 'Internal server error' }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }); + } +}; + +async function hashIP(ip: string): Promise { + const encoder = new TextEncoder(); + const data = encoder.encode(ip); + const hashBuffer = await crypto.subtle.digest('SHA-256', data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + return hashHex.substring(0, 16); +} diff --git a/dealplustech-astro/src/pages/privacy-policy.astro b/dealplustech-astro/src/pages/privacy-policy.astro new file mode 100644 index 000000000..35e8c36fe --- /dev/null +++ b/dealplustech-astro/src/pages/privacy-policy.astro @@ -0,0 +1,203 @@ +--- +import BaseLayout from '../layouts/BaseLayout.astro'; + +const POLICY_VERSION = '1.0.0'; +const LAST_UPDATED = '2026-03-10'; +--- + + +
+
+
+
+

+ นโยบายความเป็นส่วนตัว +

+

+ Privacy Policy (Personal Data Protection Policy) +

+
+

Version: {POLICY_VERSION}

+

Last Updated: {new Date(LAST_UPDATED).toLocaleDateString('th-TH', { year: 'numeric', month: 'long', day: 'numeric' })}

+
+
+ +
+

+ 1. ข้อมูลของผู้ควบคุมข้อมูลส่วนบุคคล +

+
+

+ บริษัท ดีล พลัส เทค จำกัด เป็นผู้ควบคุมข้อมูลส่วนบุคคล ตาม พ.ร.บ. คุ้มครองข้อมูลส่วนบุคคล พ.ศ. 2562 (PDPA) +

+
    +
  • ที่อยู่: 9/70 ซอยนครลุง 17 แขวงบางไผ่ เขตบางแค กทม. 10160
  • +
  • โทรศัพท์: 090-555-1415
  • +
  • อีเมล: info@dealplustech.co.th
  • +
+
+
+ +
+

+ 2. ประเภทของข้อมูลที่เก็บรวบรวม +

+
    +
  • + +
    + ข้อมูลประจำตัว: + ชื่อ, นามสกุล, ที่อยู่อีเมล, เบอร์โทรศัพท์ +
    +
  • +
  • + +
    + ข้อมูลการใช้งาน: + IP Address, ข้อมูลเบราว์เซอร์, อุปกรณ์ที่ใช้ +
    +
  • +
  • + +
    + ข้อมูลคุกกี้: + การตั้งค่าคุกกี้, Session ID +
    +
  • +
+
+ +
+

+ 3. วัตถุประสงค์ในการประมวลผลข้อมูล +

+
+
+ การให้บริการ + ตอบสนองคำขอ, ให้บริการลูกค้า +
+
+ การติดต่อกลับ + ตอบคำถาม, ให้ข้อมูลผลิตภัณฑ์ +
+
+ การวิเคราะห์ + ปรับปรุงเว็บไซต์, ประสบการณ์ผู้ใช้ +
+
+ ตามกฎหมาย + ปฏิบัติตามข้อบังคับทางกฎหมาย +
+
+
+ +
+

+ 4. ฐานกฎหมายในการประมวลผลข้อมูล +

+
+
+

4.1 การยินยอม (Consent)

+

สำหรับการใช้คุกกี้ที่ไม่จำเป็น, การตลาด

+
+
+

4.2 การ履行合同 (Contract)

+

เพื่อการให้บริการและดำเนินการตามคำขอของคุณ

+
+
+

4.3 ข้อบังคับทางกฎหมาย (Legal Obligation)

+

เพื่อปฏิบัติตามกฎหมายและระเบียบที่เกี่ยวข้อง

+
+
+
+ +
+

+ 5. ระยะเวลาการเก็บรักษาข้อมูล +

+
+
    +
  • + ข้อมูลการใช้งานเว็บไซต์ + 10 ปี +
  • +
  • + บันทึกการยินยอมคุกกี้ + 10 ปี +
  • +
  • + ข้อมูลติดต่อลูกค้า + 5 ปี +
  • +
+
+
+ +
+

+ 6. สิทธิของเจ้าของข้อมูลส่วนบุคคล +

+

+ ภายใต้ PDPA คุณมีสิทธิดังนี้: +

+
+
+

สิทธิขอเข้าถึง

+

ขอรับสำเนาข้อมูลส่วนบุคคล

+
+
+

สิทธิขอแก้ไข

+

ขอให้แก้ไขข้อมูลที่ไม่ถูกต้อง

+
+
+

สิทธิขอลบ

+

ขอให้ลบข้อมูลส่วนบุคคล

+
+
+

สิทธิเพิกถอน

+

เพิกถอนความยินยอมเมื่อใดก็ได้

+
+
+
+ +
+

+ 7. คุกกี้และเทคโนโลยีการติดตาม +

+
    +
  • +

    คุกกี้จำเป็น (Essential Cookies)

    +

    จำเป็นสำหรับการทำงานของเว็บไซต์ ไม่สามารถปิดได้

    +
  • +
  • +

    คุกกี้วิเคราะห์ (Analytics Cookies)

    +

    帮助我们了解网站使用情况 (Umami Analytics)

    +
  • +
  • +

    คุกกี้การตลาด (Marketing Cookies)

    +

    ใช้สำหรับแสดงโฆษณาที่เกี่ยวข้อง

    +
  • +
+
+ +
+

+ 8. การติดต่อ +

+
+

+ หากคุณมีคำถามหรือต้องการใช้สิทธิของคุณ กรุณาติดต่อ: +

+
+

อีเมล: info@dealplustech.co.th

+

โทรศัพท์: 090-555-1415

+
+
+
+
+
+
+