Remove Astro templates, fix sitemap template for Next.js
- Delete: CookieConsent.astro (old Astro component) - Delete: consent.ts, right-to-be-forgotten.ts (Astro API routes) - Update: route.ts is now proper Next.js route handler - Update: sitemap-template.md - replace Astro pages structure with Next.js app/ structure - Update: payload-nextjs-notes.md - fix MongoDB port reference - Note: seo-multi-channel auto_publish.py is for Astro sites (kept as-is)
This commit is contained in:
281
.opencode/package-lock.json
generated
281
.opencode/package-lock.json
generated
@@ -5,21 +5,100 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@opencode-ai/plugin": "1.3.15"
|
||||
"@opencode-ai/plugin": "1.4.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz",
|
||||
"integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz",
|
||||
"integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz",
|
||||
"integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz",
|
||||
"integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz",
|
||||
"integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz",
|
||||
"integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@opencode-ai/plugin": {
|
||||
"version": "1.3.15",
|
||||
"resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.3.15.tgz",
|
||||
"integrity": "sha512-jZJbuvUXc5Limz8pacQl+ffATjjKGlq+xaA4wTUeW+/spwOf7Yr5Ryyvan8eNlYM8wy6h5SLfznl1rlFpjYC8w==",
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.4.7.tgz",
|
||||
"integrity": "sha512-RbzMl7ILvQDHpZNvqzi6RCYaGcB3eBwNIMRZww467drLvMd1eOwr4/qAurrvYDsIIEctE6cKsrLuSGIKCW/Fxg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "1.3.15",
|
||||
"@opencode-ai/sdk": "1.4.7",
|
||||
"effect": "4.0.0-beta.48",
|
||||
"zod": "4.1.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentui/core": ">=0.1.96",
|
||||
"@opentui/solid": ">=0.1.96"
|
||||
"@opentui/core": ">=0.1.99",
|
||||
"@opentui/solid": ">=0.1.99"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@opentui/core": {
|
||||
@@ -31,14 +110,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@opencode-ai/sdk": {
|
||||
"version": "1.3.15",
|
||||
"resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.3.15.tgz",
|
||||
"integrity": "sha512-Uk59C7wsK20wpdr277yx7Xz7TqG5jGqlZUpSW3wDH/7a2K2iBg0lXc2wskHuCXLRXMhXpPZtb4a3SOpPENkkbg==",
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.4.7.tgz",
|
||||
"integrity": "sha512-onEtaooQyoDP5gTShQeQSf0Sd8V7949G9pPNyIyRXnVtFqyDIhUDLGtL/a/+EIW9x5s+Y6lDy/3oVoGMvQ0rQQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-spawn": "7.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@@ -53,12 +138,135 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/effect": {
|
||||
"version": "4.0.0-beta.48",
|
||||
"resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-beta.48.tgz",
|
||||
"integrity": "sha512-MMAM/ZabuNdNmgXiin+BAanQXK7qM8mlt7nfXDoJ/Gn9V8i89JlCq+2N0AiWmqFLXjGLA0u3FjiOjSOYQk5uMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"fast-check": "^4.6.0",
|
||||
"find-my-way-ts": "^0.1.6",
|
||||
"ini": "^6.0.0",
|
||||
"kubernetes-types": "^1.30.0",
|
||||
"msgpackr": "^1.11.9",
|
||||
"multipasta": "^0.2.7",
|
||||
"toml": "^4.1.1",
|
||||
"uuid": "^13.0.0",
|
||||
"yaml": "^2.8.3"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-check": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.6.0.tgz",
|
||||
"integrity": "sha512-h7H6Dm0Fy+H4ciQYFxFjXnXkzR2kr9Fb22c0UBpHnm59K2zpr2t13aPTHlltFiNT6zuxp6HMPAVVvgur4BLdpA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/dubzzz"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fast-check"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pure-rand": "^8.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/find-my-way-ts": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/find-my-way-ts/-/find-my-way-ts-0.1.6.tgz",
|
||||
"integrity": "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz",
|
||||
"integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "^20.17.0 || >=22.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/kubernetes-types": {
|
||||
"version": "1.30.0",
|
||||
"resolved": "https://registry.npmjs.org/kubernetes-types/-/kubernetes-types-1.30.0.tgz",
|
||||
"integrity": "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/msgpackr": {
|
||||
"version": "1.11.9",
|
||||
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.9.tgz",
|
||||
"integrity": "sha512-FkoAAyyA6HM8wL882EcEyFZ9s7hVADSwG9xrVx3dxxNQAtgADTrJoEWivID82Iv1zWDsv/OtbrrcZAzGzOMdNw==",
|
||||
"license": "MIT",
|
||||
"optionalDependencies": {
|
||||
"msgpackr-extract": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/msgpackr-extract": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz",
|
||||
"integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build-optional-packages": "5.2.2"
|
||||
},
|
||||
"bin": {
|
||||
"download-msgpackr-prebuilds": "bin/download-prebuilds.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3",
|
||||
"@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3",
|
||||
"@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3",
|
||||
"@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3",
|
||||
"@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3",
|
||||
"@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/multipasta": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/multipasta/-/multipasta-0.2.7.tgz",
|
||||
"integrity": "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-gyp-build-optional-packages": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz",
|
||||
"integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"node-gyp-build-optional-packages": "bin.js",
|
||||
"node-gyp-build-optional-packages-optional": "optional.js",
|
||||
"node-gyp-build-optional-packages-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
@@ -68,6 +276,22 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pure-rand": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.0.tgz",
|
||||
"integrity": "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/dubzzz"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fast-check"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -89,6 +313,28 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/toml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/toml/-/toml-4.1.1.tgz",
|
||||
"integrity": "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "13.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
|
||||
"integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist-node/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
@@ -104,6 +350,21 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
|
||||
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "4.1.8",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
## PostgreSQL Connection Issues
|
||||
|
||||
### Wrong port
|
||||
- Docker container `astro-starter-db-1` exposes PostgreSQL on port **5555** (not 5432)
|
||||
- Docker container `payload-db-1` exposes MongoDB on port **27017** (default)
|
||||
- Fix: Use `localhost:5555` in DATABASE_URL for local development
|
||||
|
||||
### Wrong database name
|
||||
|
||||
@@ -160,39 +160,59 @@ src/
|
||||
│ └── team/
|
||||
│ ├── member-1.md
|
||||
│ └── ...
|
||||
├── layouts/
|
||||
│ ├── BaseLayout.astro
|
||||
│ ├── PageLayout.astro
|
||||
│ ├── BlogLayout.astro
|
||||
│ └── AuthLayout.astro
|
||||
├── app/
|
||||
│ ├── (frontend)/
|
||||
│ │ ├── layout.tsx
|
||||
│ │ ├── page.tsx
|
||||
│ │ ├── about/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ ├── services/
|
||||
│ │ │ ├── page.tsx
|
||||
│ │ │ └── [slug]/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ ├── blog/
|
||||
│ │ │ ├── page.tsx
|
||||
│ │ │ └── [slug]/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ ├── contact/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ ├── privacy-policy/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ ├── terms-of-service/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ ├── login/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ ├── register/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ └── account/
|
||||
│ │ ├── page.tsx
|
||||
│ │ ├── profile/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ ├── orders/
|
||||
│ │ │ └── page.tsx
|
||||
│ │ └── settings/
|
||||
│ │ └── page.tsx
|
||||
│ └── (payload)/
|
||||
│ ├── admin/
|
||||
│ │ └── [[...segments]]/
|
||||
│ │ └── page.tsx
|
||||
│ └── api/
|
||||
│ └── ...
|
||||
├── components/
|
||||
│ ├── Navigation.astro
|
||||
│ ├── Footer.astro
|
||||
│ ├── Hero.astro
|
||||
│ ├── ServiceCard.astro
|
||||
│ ├── BlogCard.astro
|
||||
│ ├── ContactForm.astro
|
||||
│ ├── CookieConsent.astro
|
||||
│ ├── Navigation.tsx
|
||||
│ ├── Footer.tsx
|
||||
│ ├── Hero.tsx
|
||||
│ ├── ServiceCard.tsx
|
||||
│ ├── BlogCard.tsx
|
||||
│ ├── ContactForm.tsx
|
||||
│ ├── CookieConsent.tsx
|
||||
│ └── ...
|
||||
└── pages/
|
||||
├── index.astro
|
||||
├── about.astro
|
||||
├── services/
|
||||
│ ├── index.astro
|
||||
│ └── [slug].astro
|
||||
├── blog/
|
||||
│ ├── index.astro
|
||||
│ └── [slug].astro
|
||||
├── contact.astro
|
||||
├── privacy-policy.astro
|
||||
├── terms-of-service.astro
|
||||
├── login.astro
|
||||
├── register.astro
|
||||
└── account/
|
||||
├── index.astro
|
||||
├── profile.astro
|
||||
├── orders.astro
|
||||
└── settings.astro
|
||||
└── collections/
|
||||
├── Posts.ts
|
||||
├── Pages.ts
|
||||
├── Media.ts
|
||||
├── Users.ts
|
||||
└── ConsentLogs.ts
|
||||
```
|
||||
|
||||
---
|
||||
@@ -230,7 +250,7 @@ src/
|
||||
- ติดต่อเรา - ติดต่อ - นโยบายความเป็นส่วนตัว - {EMAIL}
|
||||
- สมัครสมาชิก - เงื่อนไขการให้บริการ
|
||||
|
||||
Copyright (c) {YEAR} {SITE_NAME} | Built with Astro
|
||||
Copyright (c) {YEAR} {SITE_NAME} | Built with Next.js + Payload CMS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -1,462 +0,0 @@
|
||||
---
|
||||
// CookieConsent.astro - PDPA Cookie Consent Banner
|
||||
// ทำงานจริง: ถ้า reject จะไม่ load tracking scripts
|
||||
|
||||
interface Props {
|
||||
position?: 'bottom' | 'top';
|
||||
theme?: 'light' | 'dark';
|
||||
}
|
||||
|
||||
const { position = 'bottom', theme = 'light' } = Astro.props;
|
||||
|
||||
// Consent states
|
||||
const CONSENT_TYPES = {
|
||||
ESSENTIAL: 'essential',
|
||||
ANALYTICS: 'analytics',
|
||||
MARKETING: 'marketing',
|
||||
FUNCTIONAL: 'functional',
|
||||
} as const;
|
||||
---
|
||||
|
||||
<div id="cookie-consent-banner" class={`cookie-consent cookie-consent--${position} cookie-consent--${theme}`} hidden>
|
||||
<div class="cookie-consent__content">
|
||||
<div class="cookie-consent__text">
|
||||
<h3 class="cookie-consent__title">นโยบายคุกกี้</h3>
|
||||
<p class="cookie-consent__description">
|
||||
เราใช้คุกกี้เพื่อปรับปรุงประสบการณ์การใช้งานเว็บไซต์ของคุณ
|
||||
คุณสามารถเลือกได้ว่าจะอนุญาตคุกกี้ประเภทใด
|
||||
<a href="/privacy-policy" target="_blank">อ่านนโยบายความเป็นส่วนตัว</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="cookie-consent__categories">
|
||||
<div class="cookie-consent__category">
|
||||
<div class="cookie-consent__category-header">
|
||||
<span class="cookie-consent__category-name">คุกกี้ที่จำเป็น</span>
|
||||
<span class="cookie-consent__badge cookie-consent__badge--required">จำเป็นเสมอ</span>
|
||||
</div>
|
||||
<p class="cookie-consent__category-desc">ใช้สำหรับการทำงานพื้นฐานของเว็บไซต์ ไม่สามารถปิดได้</p>
|
||||
</div>
|
||||
|
||||
<div class="cookie-consent__category">
|
||||
<div class="cookie-consent__category-header">
|
||||
<span class="cookie-consent__category-name">คุกกี้วิเคราะห์</span>
|
||||
<label class="cookie-consent__toggle">
|
||||
<input type="checkbox" id="consent-analytics" checked />
|
||||
<span class="cookie-consent__toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="cookie-consent__category-desc">ช่วยให้เราเข้าใจพฤติกรรมการใช้งานเว็บไซต์</p>
|
||||
</div>
|
||||
|
||||
<div class="cookie-consent__category">
|
||||
<div class="cookie-consent__category-header">
|
||||
<span class="cookie-consent__category-name">คุกกี้การตลาด</span>
|
||||
<label class="cookie-consent__toggle">
|
||||
<input type="checkbox" id="consent-marketing" checked />
|
||||
<span class="cookie-consent__toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="cookie-consent__category-desc">ใช้สำหรับแสดงโฆษณาที่ตรงกับความสนใจของคุณ</p>
|
||||
</div>
|
||||
|
||||
<div class="cookie-consent__category">
|
||||
<div class="cookie-consent__category-header">
|
||||
<span class="cookie-consent__category-name">คุกกี้ฟังก์ชัน</span>
|
||||
<label class="cookie-consent__toggle">
|
||||
<input type="checkbox" id="consent-functional" checked />
|
||||
<span class="cookie-consent__toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="cookie-consent__category-desc">ช่วยจดจำการตั้งค่าของคุณ</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cookie-consent__actions">
|
||||
<button id="cookie-consent-accept-all" class="cookie-consent__btn cookie-consent__btn--primary">
|
||||
ยอมรับทั้งหมด
|
||||
</button>
|
||||
<button id="cookie-consent-reject-all" class="cookie-consent__btn cookie-consent__btn--secondary">
|
||||
ปฏิเสธทั้งหมด
|
||||
</button>
|
||||
<button id="cookie-consent-save" class="cookie-consent__btn cookie-consent__btn--outline">
|
||||
บันทึกการตั้งค่า
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.cookie-consent {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
background: var(--color-bg, #ffffff);
|
||||
border-top: 1px solid var(--color-border, #e5e7eb);
|
||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
font-family: 'Kanit', 'Noto Sans Thai', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.cookie-consent--bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.cookie-consent--top {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.cookie-consent[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cookie-consent__content {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.cookie-consent__title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin: 0 0 0.5rem 0;
|
||||
color: var(--color-text, #111827);
|
||||
}
|
||||
|
||||
.cookie-consent__description {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary, #6b7280);
|
||||
margin: 0 0 1rem 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.cookie-consent__description a {
|
||||
color: var(--color-primary, #3b82f6);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.cookie-consent__categories {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.cookie-consent__category {
|
||||
background: var(--color-bg-secondary, #f9fafb);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.cookie-consent__category-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.cookie-consent__category-name {
|
||||
font-weight: 500;
|
||||
color: var(--color-text, #111827);
|
||||
}
|
||||
|
||||
.cookie-consent__badge {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 9999px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cookie-consent__badge--required {
|
||||
background: var(--color-primary, #3b82f6);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cookie-consent__category-desc {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-text-secondary, #6b7280);
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.cookie-consent__toggle {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.cookie-consent__toggle input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.cookie-consent__toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
inset: 0;
|
||||
background: var(--color-border, #d1d5db);
|
||||
border-radius: 24px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.cookie-consent__toggle-slider::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.cookie-consent__toggle input:checked + .cookie-consent__toggle-slider {
|
||||
background: var(--color-primary, #3b82f6);
|
||||
}
|
||||
|
||||
.cookie-consent__toggle input:checked + .cookie-consent__toggle-slider::before {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.cookie-consent__toggle input:disabled + .cookie-consent__toggle-slider {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.cookie-consent__actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.cookie-consent__btn {
|
||||
padding: 0.625rem 1.25rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-family: inherit;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.cookie-consent__btn--primary {
|
||||
background: var(--color-primary, #3b82f6);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cookie-consent__btn--primary:hover {
|
||||
background: var(--color-primary-dark, #2563eb);
|
||||
}
|
||||
|
||||
.cookie-consent__btn--secondary {
|
||||
background: var(--color-text, #111827);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cookie-consent__btn--secondary:hover {
|
||||
background: var(--color-text-dark, #000000);
|
||||
}
|
||||
|
||||
.cookie-consent__btn--outline {
|
||||
background: transparent;
|
||||
color: var(--color-text, #111827);
|
||||
border: 1px solid var(--color-border, #d1d5db);
|
||||
}
|
||||
|
||||
.cookie-consent__btn--outline:hover {
|
||||
background: var(--color-bg-secondary, #f9fafb);
|
||||
}
|
||||
|
||||
/* Dark theme */
|
||||
.cookie-consent--dark {
|
||||
--color-bg: #1f2937;
|
||||
--color-bg-secondary: #374151;
|
||||
--color-border: #4b5563;
|
||||
--color-text: #f9fafb;
|
||||
--color-text-secondary: #d1d5db;
|
||||
--color-primary: #60a5fa;
|
||||
--color-primary-dark: #3b82f6;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.cookie-consent {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.cookie-consent__actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.cookie-consent__btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Consent Manager
|
||||
class ConsentManager {
|
||||
private readonly CONSENT_KEY = 'cookie_consent';
|
||||
private readonly API_URL = '/api/consent';
|
||||
|
||||
async init() {
|
||||
// Check if consent already given
|
||||
const existing = this.getStoredConsent();
|
||||
if (!existing) {
|
||||
this.showBanner();
|
||||
} else {
|
||||
this.applyConsent(existing);
|
||||
}
|
||||
|
||||
// Bind event listeners
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
private bindEvents() {
|
||||
const acceptAll = document.getElementById('cookie-consent-accept-all');
|
||||
const rejectAll = document.getElementById('cookie-consent-reject-all');
|
||||
const save = document.getElementById('cookie-consent-save');
|
||||
|
||||
acceptAll?.addEventListener('click', () => this.acceptAll());
|
||||
rejectAll?.addEventListener('click', () => this.rejectAll());
|
||||
save?.addEventListener('click', () => this.saveCustom());
|
||||
}
|
||||
|
||||
private showBanner() {
|
||||
const banner = document.getElementById('cookie-consent-banner');
|
||||
banner?.removeAttribute('hidden');
|
||||
}
|
||||
|
||||
private hideBanner() {
|
||||
const banner = document.getElementById('cookie-consent-banner');
|
||||
banner?.setAttribute('hidden', '');
|
||||
}
|
||||
|
||||
private getStoredConsent(): Record<string, boolean> | null {
|
||||
const stored = localStorage.getItem(this.CONSENT_KEY);
|
||||
return stored ? JSON.parse(stored) : null;
|
||||
}
|
||||
|
||||
private async saveConsent(consent: Record<string, boolean>) {
|
||||
// Save to localStorage
|
||||
localStorage.setItem(this.CONSENT_KEY, JSON.stringify(consent));
|
||||
|
||||
// Send to server
|
||||
try {
|
||||
await fetch(this.API_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
...consent,
|
||||
session_id: this.getSessionId(),
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to save consent:', error);
|
||||
}
|
||||
|
||||
// Apply consent
|
||||
this.applyConsent(consent);
|
||||
this.hideBanner();
|
||||
}
|
||||
|
||||
private async acceptAll() {
|
||||
const consent = {
|
||||
essential: true,
|
||||
analytics: true,
|
||||
marketing: true,
|
||||
functional: true,
|
||||
};
|
||||
await this.saveConsent(consent);
|
||||
}
|
||||
|
||||
private async rejectAll() {
|
||||
const consent = {
|
||||
essential: true, // Always required
|
||||
analytics: false,
|
||||
marketing: false,
|
||||
functional: false,
|
||||
};
|
||||
await this.saveConsent(consent);
|
||||
}
|
||||
|
||||
private async saveCustom() {
|
||||
const consent = {
|
||||
essential: true, // Always required
|
||||
analytics: (document.getElementById('consent-analytics') as HTMLInputElement)?.checked ?? false,
|
||||
marketing: (document.getElementById('consent-marketing') as HTMLInputElement)?.checked ?? false,
|
||||
functional: (document.getElementById('consent-functional') as HTMLInputElement)?.checked ?? false,
|
||||
};
|
||||
await this.saveConsent(consent);
|
||||
}
|
||||
|
||||
private applyConsent(consent: Record<string, boolean>) {
|
||||
// Essential cookies - always on (handled by server)
|
||||
|
||||
// Analytics
|
||||
if (consent.analytics) {
|
||||
this.enableAnalytics();
|
||||
} else {
|
||||
this.disableAnalytics();
|
||||
}
|
||||
|
||||
// Marketing
|
||||
if (consent.marketing) {
|
||||
this.enableMarketing();
|
||||
} else {
|
||||
this.disableMarketing();
|
||||
}
|
||||
|
||||
// Functional
|
||||
if (consent.functional) {
|
||||
this.enableFunctional();
|
||||
} else {
|
||||
this.disableFunctional();
|
||||
}
|
||||
}
|
||||
|
||||
private enableAnalytics() {
|
||||
// Enable GA4 etc.
|
||||
window.dispatchEvent(new CustomEvent('consent:analytics:Granted'));
|
||||
}
|
||||
|
||||
private disableAnalytics() {
|
||||
// Disable GA4, clear existing cookies
|
||||
window.dispatchEvent(new CustomEvent('consent:analytics:Denied'));
|
||||
}
|
||||
|
||||
private enableMarketing() {
|
||||
window.dispatchEvent(new CustomEvent('consent:marketing:Granted'));
|
||||
}
|
||||
|
||||
private disableMarketing() {
|
||||
window.dispatchEvent(new CustomEvent('consent:marketing:Denied'));
|
||||
}
|
||||
|
||||
private enableFunctional() {
|
||||
window.dispatchEvent(new CustomEvent('consent:functional:Granted'));
|
||||
}
|
||||
|
||||
private disableFunctional() {
|
||||
window.dispatchEvent(new CustomEvent('consent:functional:Denied'));
|
||||
}
|
||||
|
||||
private getSessionId(): string {
|
||||
let sessionId = sessionStorage.getItem('session_id');
|
||||
if (!sessionId) {
|
||||
sessionId = crypto.randomUUID();
|
||||
sessionStorage.setItem('session_id', sessionId);
|
||||
}
|
||||
return sessionId;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ConsentManager().init();
|
||||
});
|
||||
</script>
|
||||
@@ -1,81 +0,0 @@
|
||||
import type { APIRoute } from 'astro'
|
||||
|
||||
// POST /api/consent - บันทึก consent
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { session_id, essential, analytics, marketing, functional } = body
|
||||
|
||||
if (!session_id) {
|
||||
return new Response(JSON.stringify({ error: 'session_id is required' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
|
||||
// Get client info
|
||||
const ipAddress = request.headers.get('x-forwarded-for')?.split(',')[0] || 'unknown'
|
||||
const userAgent = request.headers.get('user-agent') || 'unknown'
|
||||
|
||||
// Build consent record
|
||||
const consentTypes = []
|
||||
if (essential) consentTypes.push('essential')
|
||||
if (analytics) consentTypes.push('analytics')
|
||||
if (marketing) consentTypes.push('marketing')
|
||||
if (functional) consentTypes.push('functional')
|
||||
|
||||
// In Payload CMS, you would save this to the consent-logs collection
|
||||
// For now, return success (Payload integration happens at build time)
|
||||
const record = {
|
||||
sessionId: session_id,
|
||||
consentType: consentTypes.length === 4 ? 'accept_all' : consentTypes.join(','),
|
||||
granted: analytics || marketing || functional,
|
||||
ipAddress,
|
||||
userAgent,
|
||||
metadata: { essential, analytics, marketing, functional },
|
||||
createdAt: new Date().toISOString(),
|
||||
}
|
||||
|
||||
// Log for debugging (remove in production)
|
||||
console.log('[Consent API] New consent record:', JSON.stringify(record))
|
||||
|
||||
return new Response(JSON.stringify({ success: true, record }), {
|
||||
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' },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/consent - ตรวจสอบ consent ของ session
|
||||
export const GET: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
const url = new URL(request.url)
|
||||
const sessionId = url.searchParams.get('session_id')
|
||||
|
||||
if (!sessionId) {
|
||||
return new Response(JSON.stringify({ error: 'session_id is required' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
|
||||
// In Payload CMS, query the consent-logs collection
|
||||
// For now, return not found (Payload integration happens at build time)
|
||||
return new Response(JSON.stringify({ error: 'Not implemented in template' }), {
|
||||
status: 501,
|
||||
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' },
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import type { APIRoute } from 'astro'
|
||||
|
||||
// Right to be Forgotten API - PDPA Article 17
|
||||
// DELETE /api/consent?session_id=xxx - ลบข้อมูลของ session นี้
|
||||
|
||||
export const DELETE: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
const url = new URL(request.url)
|
||||
const sessionId = url.searchParams.get('session_id')
|
||||
|
||||
if (!sessionId) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'session_id is required' }),
|
||||
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
}
|
||||
|
||||
// In Payload CMS, you would:
|
||||
// 1. Find all consent-logs with this sessionId
|
||||
// 2. Delete them
|
||||
// 3. Also delete any user data associated with this session
|
||||
|
||||
// Example Payload query (for reference):
|
||||
// await payload.delete({
|
||||
// collection: 'consent-logs',
|
||||
// where: { sessionId: { equals: sessionId } },
|
||||
// })
|
||||
|
||||
console.log(`[Right to be Forgotten] Deleting data for session: ${sessionId}`)
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: 'ข้อมูลของคุณถูกลบแล้ว',
|
||||
deletedAt: new Date().toISOString(),
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('[Right to be Forgotten] Error:', error)
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Internal server error' }),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/consent/export - ขอ export ข้อมูลของตัวเอง (PDPA Article 31)
|
||||
export const GET: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
const url = new URL(request.url)
|
||||
const sessionId = url.searchParams.get('session_id')
|
||||
|
||||
if (!sessionId) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'session_id is required' }),
|
||||
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
}
|
||||
|
||||
// In Payload CMS, query consent-logs for this session
|
||||
// Return the data as JSON for the user to review
|
||||
|
||||
// Example Payload query (for reference):
|
||||
// const logs = await payload.find({
|
||||
// collection: 'consent-logs',
|
||||
// where: { sessionId: { equals: sessionId } },
|
||||
// })
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: 'ข้อมูลของคุณ',
|
||||
data: [], // Replace with actual Payload query result
|
||||
requestedAt: new Date().toISOString(),
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('[Consent Export] Error:', error)
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Internal server error' }),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3,78 +3,37 @@ import { getPayload } from 'payload'
|
||||
import config from '@/payload.config'
|
||||
|
||||
/**
|
||||
* POST /api/consent - Record consent action
|
||||
*
|
||||
* Request body:
|
||||
* {
|
||||
* action: 'accept' | 'reject' | 'update',
|
||||
* purpose: 'analytics' | 'marketing' | 'functional' | 'all',
|
||||
* analytics: boolean,
|
||||
* marketing: boolean,
|
||||
* functional: boolean,
|
||||
* previousConsent?: { analytics: boolean, marketing: boolean, functional: boolean }
|
||||
* }
|
||||
* DELETE /api/consent - Right to be forgotten (GDPR/PDPA)
|
||||
*
|
||||
* Deletes all consent records for a given session or user
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
export async function DELETE(request: NextRequest) {
|
||||
try {
|
||||
const payloadConfig = await config
|
||||
const payload = await getPayload({ config: payloadConfig })
|
||||
|
||||
const body = await request.json()
|
||||
const { action, purpose, analytics, marketing, functional, previousConsent } = body
|
||||
const { searchParams } = new URL(request.url)
|
||||
const sessionId = searchParams.get('sessionId')
|
||||
|
||||
// Validate required fields
|
||||
if (!action || !['accept', 'reject', 'update'].includes(action)) {
|
||||
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
||||
}
|
||||
if (!purpose || !['analytics', 'marketing', 'functional', 'all'].includes(purpose)) {
|
||||
return NextResponse.json({ error: 'Invalid purpose' }, { status: 400 })
|
||||
if (!sessionId) {
|
||||
return NextResponse.json({ error: 'sessionId is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Get IP and User Agent
|
||||
const ip = request.headers.get('x-forwarded-for')?.split(',')[0]
|
||||
|| request.headers.get('x-real-ip')
|
||||
|| 'unknown'
|
||||
const userAgent = request.headers.get('user-agent') || 'unknown'
|
||||
|
||||
// Create consent log
|
||||
const consentLog = await payload.create({
|
||||
// Find and delete all consent logs for this session
|
||||
const result = await payload.delete({
|
||||
collection: 'consent-logs',
|
||||
data: {
|
||||
action,
|
||||
purpose,
|
||||
analytics: analytics ?? false,
|
||||
marketing: marketing ?? false,
|
||||
functional: functional ?? false,
|
||||
userAgent,
|
||||
ip,
|
||||
timestamp: new Date().toISOString(),
|
||||
previousConsent: previousConsent || null,
|
||||
newConsent: {
|
||||
analytics: analytics ?? false,
|
||||
marketing: marketing ?? false,
|
||||
functional: functional ?? false,
|
||||
},
|
||||
where: {
|
||||
sessionId: { equals: sessionId },
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({ success: true, doc: consentLog })
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
deleted: result.deletedDocs?.length || 0,
|
||||
message: 'All consent records for this session have been deleted'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Consent logging error:', error)
|
||||
return NextResponse.json({ error: 'Failed to log consent' }, { status: 500 })
|
||||
console.error('Right to be forgotten error:', error)
|
||||
return NextResponse.json({ error: 'Failed to delete consent records' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/consent - Get current consent status (from cookie or localStorage)
|
||||
* This endpoint is mainly for verification, actual consent is stored client-side
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
// Consent is stored client-side in localStorage
|
||||
// This endpoint is for compliance verification
|
||||
return NextResponse.json({
|
||||
message: 'Consent is stored client-side',
|
||||
purposes: ['analytics', 'marketing', 'functional'],
|
||||
note: 'Use POST to update consent preferences'
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user