#!/usr/bin/env python3
"""
Website Refactoring Script - Update existing Astro websites to new PDPA-compliant standard
This script migrates existing Astro websites to the new standardized structure with:
- PDPA-compliant legal pages
- Cookie consent system
- Consent logging database
- i18n routing (Thai/English)
- Umami Analytics integration
Usage:
python3 refactor_existing_website.py \
--input "./existing-website" \
--output "./refactored-website" \
--languages "th,en"
"""
import os
import sys
import shutil
import argparse
from pathlib import Path
from datetime import datetime
def main():
parser = argparse.ArgumentParser(
description='Refactor existing Astro website to PDPA-compliant standard'
)
parser.add_argument('--input', '-i', required=True, help='Input directory (existing website)')
parser.add_argument('--output', '-o', required=True, help='Output directory (refactored website)')
parser.add_argument('--languages', default='th,en', help='Languages (comma-separated)')
parser.add_argument('--umami-id', default='', help='Umami Website ID')
parser.add_argument('--umami-domain', default='analytics.example.com', help='Umami domain')
parser.add_argument('--admin-password', default='changeme', help='Admin password')
parser.add_argument('--skip-backup', action='store_true', help='Skip backup creation')
args = parser.parse_args()
input_dir = Path(args.input)
output_dir = Path(args.output)
languages = [lang.strip() for lang in args.languages.split(',')]
if not input_dir.exists():
print(f"Error: Input directory '{input_dir}' does not exist")
sys.exit(1)
print(f"🔄 Refactoring website from: {input_dir}")
print(f"📁 Output directory: {output_dir}")
print(f"🌐 Languages: {languages}")
# Create backup
if not args.skip_backup:
create_backup(input_dir)
# Create new structure
output_dir.mkdir(parents=True, exist_ok=True)
# Migrate content
migrate_content(input_dir, output_dir, languages)
# Add new features
add_pdpa_features(output_dir, args, languages)
# Update configuration
update_configs(output_dir, args, languages)
print(f"\n✅ Refactoring complete!")
print(f"\n📁 Refactored website at: {output_dir}")
print("\n📋 Next steps:")
print(f" 1. cd {output_dir}")
print(" 2. Review changes")
print(" 3. npm install")
print(" 4. Update .env with your credentials")
print(" 5. npm run dev")
print("\n⚠️ Important:")
print(" - Review Privacy Policy content (update company info)")
print(" - Review Terms & Conditions (update service details)")
print(" - Change admin password in .env")
print(" - Test all features before deployment")
def create_backup(input_dir):
"""Create backup of existing website."""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_dir = input_dir.parent / f"{input_dir.name}_backup_{timestamp}"
print(f"💾 Creating backup: {backup_dir}")
shutil.copytree(input_dir, backup_dir)
print(f" ✓ Backup created")
def migrate_content(input_dir, output_dir, languages):
"""Migrate existing content to new structure."""
# Create new folder structure
create_new_structure(output_dir)
# Copy existing content
src_input = input_dir / 'src'
if src_input.exists():
# Copy components
components_input = src_input / 'components'
if components_input.exists():
print("📦 Migrating components...")
shutil.copytree(
components_input,
output_dir / 'src' / 'components' / 'migrated',
dirs_exist_ok=True
)
print(f" ✓ Components migrated")
# Copy layouts
layouts_input = src_input / 'layouts'
if layouts_input.exists():
print("📐 Migrating layouts...")
shutil.copytree(
layouts_input,
output_dir / 'src' / 'layouts' / 'migrated',
dirs_exist_ok=True
)
print(f" ✓ Layouts migrated")
# Copy content collections (blog, products)
content_input = src_input / 'content'
if content_input.exists():
print("📝 Migrating content collections...")
# Copy blog posts
blog_input = content_input / 'blog'
if blog_input.exists():
# Organize by language if possible
for lang in languages:
lang_dir = output_dir / 'src' / 'content' / 'blog' / f'({lang})'
lang_dir.mkdir(parents=True, exist_ok=True)
# Copy all posts to default language folder for now
default_lang = 'en' if 'en' in languages else languages[0]
default_dir = output_dir / 'src' / 'content' / 'blog' / f'({default_lang})'
for md_file in blog_input.glob('*.md'):
shutil.copy2(md_file, default_dir)
print(f" ✓ Copied: {md_file.name}")
# Copy static assets
public_input = input_dir / 'public'
if public_input.exists():
print("🖼️ Migrating static assets...")
shutil.copytree(
public_input,
output_dir / 'public',
dirs_exist_ok=True
)
print(f" ✓ Assets migrated")
# Copy images
for img_dir in src_input.glob('**/images'):
rel_path = img_dir.relative_to(src_input)
dest_dir = output_dir / 'src' / 'components' / rel_path
dest_dir.mkdir(parents=True, exist_ok=True)
for img_file in img_dir.glob('*'):
if img_file.suffix.lower() in ['.jpg', '.jpeg', '.png', '.svg', '.webp']:
shutil.copy2(img_file, dest_dir)
print(f" ✓ Content migration complete")
def create_new_structure(output_dir):
"""Create the new standardized folder structure."""
dirs = [
output_dir / 'public' / 'images',
output_dir / 'src' / 'components' / 'common',
output_dir / 'src' / 'components' / 'consent',
output_dir / 'src' / 'components' / 'ui',
output_dir / 'src' / 'components' / 'migrated',
output_dir / 'src' / 'layouts',
output_dir / 'src' / 'layouts' / 'migrated',
output_dir / 'src' / 'pages',
output_dir / 'src' / 'pages' / 'api' / 'consent',
output_dir / 'src' / 'pages' / 'admin',
output_dir / 'src' / 'styles',
output_dir / 'src' / 'content' / 'blog',
output_dir / 'src' / 'lib',
output_dir / 'db',
]
for d in dirs:
d.mkdir(parents=True, exist_ok=True)
print("📁 Created new folder structure")
def add_pdpa_features(output_dir, args, languages):
"""Add PDPA compliance features."""
default_locale = 'en' if 'en' in languages else languages[0]
# Create Privacy Policy pages
print("📄 Creating Privacy Policy pages...")
for lang in languages:
lang_dir = output_dir / 'src' / 'pages' / lang
lang_dir.mkdir(parents=True, exist_ok=True)
privacy_policy = get_privacy_policy_template(lang, args)
(lang_dir / 'privacy-policy.astro').write_text(privacy_policy)
print(" ✓ Privacy Policy created")
# Create Terms & Conditions pages
print("📄 Creating Terms & Conditions pages...")
for lang in languages:
lang_dir = output_dir / 'src' / 'pages' / lang
terms = get_terms_template(lang)
(lang_dir / 'terms-and-conditions.astro').write_text(terms)
print(" ✓ Terms & Conditions created")
# Create consent components
print("🍪 Creating cookie consent components...")
cookie_banner = get_cookie_banner_template()
(output_dir / 'src' / 'components' / 'consent' / 'CookieBanner.astro').write_text(cookie_banner)
print(" ✓ Cookie banner created")
# Create admin dashboard
print("🔐 Creating admin dashboard...")
admin_page = get_admin_dashboard_template()
admin_dir = output_dir / 'src' / 'pages' / 'admin'
admin_dir.mkdir(parents=True, exist_ok=True)
(admin_dir / 'consent-logs.astro').write_text(admin_page)
print(" ✓ Admin dashboard created")
# Create database schema
print("💾 Creating database schema...")
db_config = get_db_config_template()
(output_dir / 'db' / 'config.ts').write_text(db_config)
db_seed = get_db_seed_template()
(output_dir / 'db' / 'seed.ts').write_text(db_seed)
print(" ✓ Database schema created")
# Create API endpoints
print("🔌 Creating API endpoints...")
create_api_endpoints(output_dir)
print(" ✓ API endpoints created")
# Create i18n lib
print("🌐 Creating i18n utilities...")
i18n_lib = get_i18n_lib_template(languages)
(output_dir / 'src' / 'lib' / 'i18n.ts').write_text(i18n_lib)
print(" ✓ i18n utilities created")
def update_configs(output_dir, args, languages):
"""Update configuration files."""
default_locale = 'en' if 'en' in languages else languages[0]
locales_str = ', '.join([f"'{lang}'" for lang in languages])
# astro.config.mjs
print("⚙️ Updating astro.config.mjs...")
astro_config = f"""import {{ defineConfig }} from 'astro/config';
import tailwindcss from '@tailwindcss/vite';
import db from '@astrojs/db';
import sitemap from '@astrojs/sitemap';
export default defineConfig({{
site: 'https://example.com',
output: 'hybrid',
i18n: {{
locales: [{locales_str}],
defaultLocale: '{default_locale}',
routing: {{
prefixDefaultLocale: false,
fallbackType: 'rewrite',
}},
fallback: {{
th: 'en',
}},
}},
integrations: [
tailwindcss(),
db(),
sitemap({{
i18n: {{
defaultLocale: '{default_locale}',
}},
}}),
],
}});
"""
(output_dir / 'astro.config.mjs').write_text(astro_config)
print(" ✓ astro.config.mjs updated")
# package.json (add dependencies)
print("📦 Updating package.json...")
package_json_path = output_dir / 'package.json'
if package_json_path.exists():
import json
with open(package_json_path, 'r') as f:
package_json = json.load(f)
# Add new dependencies
new_deps = {
"@astrojs/db": "^0.14.0",
"@astrojs/sitemap": "^3.2.0",
"drizzle-orm": "^0.38.0",
"@libsql/client": "^0.14.0",
}
if 'dependencies' not in package_json:
package_json['dependencies'] = {}
package_json['dependencies'].update(new_deps)
# Add new scripts
package_json['scripts']['db:push'] = 'astro db push --remote'
package_json['scripts']['db:seed'] = 'astro db seed'
with open(package_json_path, 'w') as f:
json.dump(package_json, f, indent=2)
print(" ✓ package.json updated")
# Create .env.example
print("🔐 Creating .env.example...")
env_example = f"""# Umami Analytics
UMAMI_WEBSITE_ID={args.umami_id or 'your-website-id-here'}
UMAMI_DOMAIN={args.umami_domain}
# Admin
ADMIN_PASSWORD={args.admin_password}
# Database (optional)
# ASTRO_DB_REMOTE_URL=libsql://your-db.turso.io
# ASTRO_DB_APP_TOKEN=your-turso-token
# Site Configuration
SITE_URL=https://example.com
SITE_NAME="Website Name"
"""
(output_dir / '.env.example').write_text(env_example)
# Update .gitignore
gitignore = """node_modules
dist
.env
.astro
*.db
*.log
.DS_Store
"""
(output_dir / '.gitignore').write_text(gitignore)
print(" ✓ .env.example created")
# Create/update Dockerfile
print("🐳 Creating Dockerfile...")
dockerfile = """FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/db ./db
RUN apk add --no-cache sqlite-libs
EXPOSE 80
ENV NODE_ENV=production
ENV ASTRO_DB_REMOTE_URL=file:/app/data/consent.db
CMD ["sh", "-c", "mkdir -p /app/data && npx astro preview --host 0.0.0.0 --port 80"]
"""
(output_dir / 'Dockerfile').write_text(dockerfile)
print(" ✓ Dockerfile created")
# Create MIGRATION.md
print("📝 Creating migration guide...")
migration_guide = f"""# Migration Guide
## What Changed
This website has been refactored to include:
- ✅ PDPA-compliant Privacy Policy
- ✅ PDPA-compliant Terms & Conditions
- ✅ Cookie consent system
- ✅ Consent logging database
- ✅ i18n routing ({', '.join(languages)})
- ✅ Umami Analytics integration
- ✅ Admin dashboard
## New Features
### 1. Privacy Policy
Location: `/privacy-policy`, `/th/privacy-policy`
- All 14 PDPA Section 36 disclosures
- Version tracking
- Last updated date
### 2. Cookie Consent
- Appears on first visit
- Opt-in model (required by PDPA)
- Granular choices (essential/analytics/marketing)
- Consent logged to database
### 3. Admin Dashboard
URL: `/admin/consent-logs`
Password: {args.admin_password} (**CHANGE THIS!**)
Features:
- View all consent records
- Filter by date/locale
- Delete records (right to be forgotten)
### 4. Database
Schema: `db/config.ts`
- Stores consent logs
- SQLite file (development)
- Turso ready (production)
### 5. i18n Routing
- Default locale: {default_locale}
- URL structure: `/about` ({default_locale}), `/th/about` (Thai)
- Fallback: Thai → English
## Migration Steps
### 1. Review Changes
```bash
# Check new files
ls -la src/pages/
ls -la src/components/consent/
ls -la db/
```
### 2. Update Content
- [ ] Edit Privacy Policy (add your company info)
- [ ] Edit Terms & Conditions (add service details)
- [ ] Update .env with your credentials
- [ ] Change admin password
### 3. Test Features
```bash
npm install
npm run dev
# Open http://localhost:4321
```
Checklist:
- [ ] Language switcher works
- [ ] Cookie consent appears
- [ ] Admin dashboard accessible
- [ ] Consent logging works
### 4. Deploy
```bash
npm run build
docker build -t website:latest .
# Deploy to Easypanel
```
## Content Migration
### Blog Posts
Your existing blog posts have been copied to:
`src/content/blog/(en)/` (or your default language)
To add bilingual content:
1. Translate posts
2. Copy to `src/content/blog/(th)/`
3. Update frontmatter with `locale: 'th'`
### Components
Migrated components are in:
`src/components/migrated/`
You can continue using them or migrate to new structure.
## Rollback
If you need to rollback:
1. Use the backup created at: `{args.input}_backup_TIMESTAMP`
2. Restore from backup
3. Revert deployment
## Support
See documentation:
- `SKILL.md` - Complete skill workflow
- `SPECIFICATION.md` - Technical details
- `IMPLEMENTATION_SUMMARY.md` - Feature summary
"""
(output_dir / 'MIGRATION.md').write_text(migration_guide)
print(" ✓ Migration guide created")
def get_privacy_policy_template(lang, args):
"""Generate Privacy Policy template."""
if lang == 'th':
return f"""---
import BaseLayout from '../../layouts/BaseLayout.astro';
const lastUpdated = new Date().toLocaleDateString('th-TH', {{
year: 'numeric',
month: 'long',
day: 'numeric'
}});
const version = '1.0.0';
---
ฉบับที่ {{version}} | อัปเดตล่าสุด: {{lastUpdated}}
{args.umami_domain.split('.')[0] if args.umami_domain else 'Website'} เป็นผู้ควบคุมข้อมูลส่วนบุคคลของคุณ
เราเก็บรวบรวมข้อมูลส่วนบุคคลประเภทต่อไปนี้: เราประมวลผลข้อมูลของคุณเพื่อวัตถุประสงค์ดังนี้:
ฐานทางกฎหมาย: ความยินยอม ผลประโยชน์โดยชอบด้วยกฎหมาย ความจำเป็นตามสัญญา
เราเก็บรักษาข้อมูลส่วนบุคคลตราบเท่าที่จำเป็นเพื่อวัตถุประสงค์ที่ระบุในนโยบายนี้
หรือตามที่กฎหมายกำหนด บันทึกการยินยอมถูกเก็บรักษาไว้ 10 ปีเพื่อปฏิบัติตามข้อกำหนด PDPA
เราไม่ขายหรือให้เช่าข้อมูลส่วนบุคคลของคุณ เราอาจแบ่งปันข้อมูลกับ:
เราใช้คุกกี้และเทคโนโลยีที่คล้ายคลึงกัน คุกกี้ที่จำเป็นจะทำงานเสมอ
คุกกี้การวิเคราะห์และการตลาดต้องการความยินยอมที่ชัดเจนจากคุณ
คุณสามารถจัดการการตั้งค่าได้ผ่านแบนเนอร์การยินยอมคุกกี้ของเรา
คุณมีสิทธิ์ดังต่อไปนี้ภายใต้ PDPA:
เราใช้มาตรการรักษาความปลอดภัยที่เหมาะสมทั้งทางเทคนิคและองค์กร
เพื่อปกป้องข้อมูลส่วนบุคคลของคุณจากการเข้าถึง การแก้ไข
การเปิดเผย หรือการทำลายโดยไม่ได้รับอนุญาต
[ถ้ามี] ข้อมูลของคุณอาจถูกโอนไปยังและประมวลผลในประเทศอื่นๆ นอกเหนือจากประเทศไทย
เรารับรองว่ามีมาตรการคุ้มครองที่เหมาะสมสำหรับการโอนดังกล่าว
สำหรับคำถามใดๆ หรือเพื่อใช้สิทธิ์ของคุณ ติดต่อเราได้ที่: [อีเมลติดต่อของคุณ]
เราอาจอัปเดตนโยบายนี้เป็นครั้งคราว เราจะแจ้งให้คุณทราบถึงการเปลี่ยนแปลงใดๆ
โดยการลงประกาศนโยบายใหม่บนหน้านี้และอัปเดตเลขฉบับที่
For the English version of this policy, please see:
Privacy Policy
Version {{version}} | Last updated: {{lastUpdated}}
{args.umami_domain.split('.')[0] if args.umami_domain else 'Website'} is the data controller responsible for your personal data.
We collect the following types of personal data: We process your data for the following purposes:
Legal Basis: Consent, legitimate interest, contractual necessity
We retain personal data for as long as necessary to fulfill the purposes outlined in this policy,
or as required by law. Consent records are retained for 10 years to comply with PDPA requirements.
We do not sell or rent your personal data. We may share data with:
We use cookies and similar technologies. Essential cookies are always active.
Analytics and marketing cookies require your explicit consent. You can manage
your preferences through our cookie consent banner.
You have the following rights under PDPA:
We implement appropriate technical and organizational measures to protect your personal data
against unauthorized access, alteration, disclosure, or destruction.
[If applicable] Your data may be transferred to and processed in countries other than Thailand.
We ensure appropriate safeguards are in place for such transfers.
For any questions or to exercise your rights, contact us at: [Your contact email]
We may update this policy from time to time. We will notify you of any changes by posting
the new policy on this page and updating the version number.
สำหรับเวอร์ชันภาษาไทย โปรดดูที่:
นโยบายความเป็นส่วนตัว
อัปเดตล่าสุด: {lastUpdated}
ด้วยการเข้าถึงและใช้เว็บไซต์นี้ คุณยอมรับและตกลงที่จะถูกผูกพันด้วยข้อกำหนด
และบทบัญญัติของข้อตกลงนี้
เว็บไซต์ให้บริการ [ระบุบริการของคุณ] รายละเอียดบริการที่สมบูรณ์จะแจ้งให้ทราบแยกต่างหากเมื่อมีการใช้บริการ
เนื้อหาทั้งหมดบนเว็บไซต์นี้ รวมถึงข้อความ กราฟิก โลโก้ และซอฟต์แวร์
เป็นทรัพย์สินของเว็บไซต์และอยู่ภายใต้การคุ้มครองกฎหมายลิขสิทธิ์ของไทยและสากล
คุณตกลงที่จะใช้เว็บไซต์นี้เพื่อวัตถุประสงค์ที่ถูกต้องตามกฎหมายเท่านั้น
และในวิธีที่ไม่ละเมิดสิทธิ์ จำกัด หรือยับยั้งการใช้งานของผู้อื่น
เว็บไซต์จะไม่รับผิดต่อความเสียหายทางอ้อม โดยบังเอิญ เฉพาะเรื่อง หรือเชิงลงโทษ
อันเกิดจากการใช้หรือไม่สามารถใช้เว็บไซต์นี้
ข้อกำหนดเหล่านี้จะอยู่ภายใต้การตีความและบังคับตามกฎหมายของประเทศไทย
โดยไม่คำนึงถึงหลักการขัดกันแห่งกฎหมาย
ข้อพิพาทใดๆ ที่เกิดจากข้อกำหนดนี้จะได้รับการแก้ไขผ่านการเจรจาต่อรองด้วยดี
หากไม่สำเร็จ ข้อพิพาทจะถูกยื่นต่อศาลที่มีเขตอำนาจในประเทศไทย
เราขอสงวนสิทธิ์ในการแก้ไขข้อกำหนดเหล่านี้ได้ตลอดเวลา
การใช้เว็บไซต์ต่อไปหลังจากมีการเปลี่ยนแปลงถือเป็นการยอมรับการเปลี่ยนแปลง tersebut
สำหรับคำถามเกี่ยวกับข้อกำหนดเหล่านี้ โปรดติดต่อเราที่: [อีเมลติดต่อของคุณ]
For the English version of these terms, please see:
Terms and Conditions
Last updated: {lastUpdated}
By accessing and using this website, you accept and agree to be bound by the terms
and provision of this agreement.
Website provides [describe your services]. Detailed service terms are provided
separately upon engagement.
All content on this website, including text, graphics, logos, and software, is the
property of Website and is protected by Thai and international copyright laws.
You agree to use this website only for lawful purposes and in a way that does not
infringe the rights of, restrict or inhibit anyone else's use of the website.
Website shall not be liable for any indirect, incidental, special, consequential
or punitive damages resulting from your use of or inability to use this website.
These terms shall be governed by and construed in accordance with the laws of
Thailand, without regard to its conflict of law provisions.
Any disputes arising from these terms shall be resolved through good faith negotiations.
If unsuccessful, disputes shall be submitted to the competent courts of Thailand.
We reserve the right to modify these terms at any time. Continued use of the website
following any changes constitutes acceptance of those changes.
For questions about these terms, please contact us at: [Your contact email]
สำหรับเวอร์ชันภาษาไทย โปรดดูที่:
ข้อกำหนดและเงื่อนไข
นโยบายความเป็นส่วนตัว
1. ผู้ควบคุมข้อมูล
ติดต่อ: [ข้อมูลการติดต่อของคุณ]
2. ข้อมูลที่เก็บรวบรวม
3. วัตถุประสงค์ในการประมวลผล
4. ระยะเวลาเก็บรักษาข้อมูล
5. การเปิดเผยข้อมูล
6. คุกกี้และการติดตาม
7. สิทธิ์ของคุณ (PDPA)
8. ความปลอดภัยของข้อมูล
9. การโอนข้อมูลข้ามประเทศ
10. การติดต่อและข้อร้องเรียน
คุณยังมีสิทธิ์ในการยื่นข้อร้องเรียนต่อคณะกรรมการคุ้มครองข้อมูลส่วนบุคคล (PDPC)
11. การอัปเดตนโยบาย
Privacy Policy
1. Data Controller
Contact: [Your contact information]
2. Data We Collect
3. Purpose of Processing
4. Data Retention
5. Data Sharing & Disclosure
6. Cookies & Tracking
7. Your Rights (PDPA)
8. Data Security
9. Cross-Border Transfers
10. Contact & Complaints
You also have the right to lodge a complaint with the Personal Data Protection Committee (PDPC).
11. Policy Updates
ข้อกำหนดและเงื่อนไข
1. การยอมรับเงื่อนไข
2. บริการ
3. ทรัพย์สินทางปัญญา
4. หน้าที่ของผู้ใช้
5. การจำกัดความรับผิด
6. กฎหมายที่ใช้บังคับ
7. การระงับข้อพิพาท
8. การแก้ไข
9. ข้อมูลการติดต่อ
Terms and Conditions
1. Acceptance of Terms
2. Services
3. Intellectual Property
4. User Obligations
5. Limitation of Liability
6. Governing Law
7. Dispute Resolution
8. Modifications
9. Contact Information
| Date | Locale | Session ID | Essential | Analytics | Marketing | Policy Ver |
|---|---|---|---|---|---|---|
| {new Date(log.timestamp).toLocaleString()} | {log.locale} | {log.sessionId} | {log.essential ? '✓' : '✗'} | {log.analytics ? '✓' : '✗'} | {log.marketing ? '✓' : '✗'} | {log.policyVersion} |