diff options
| author | Yuval Adam <_@yuv.al> | 2025-06-27 10:10:56 +0200 |
|---|---|---|
| committer | Yuval Adam <_@yuv.al> | 2025-06-27 10:10:56 +0200 |
| commit | 2ad5ce929f6d69c9aaca71573744b30e02ab8bed (patch) | |
| tree | 078cd3b8a968ebfd99e86492abb9564f183ec21a | |
| parent | 0282fb367ecfd3864cf94c27a44961a61e2ff6f2 (diff) | |
Add cloudflare basic impl
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | README.md | 109 | ||||
| -rw-r--r-- | astro.config.mjs | 11 | ||||
| -rw-r--r-- | package-lock.json | 481 | ||||
| -rw-r--r-- | package.json | 11 | ||||
| -rw-r--r-- | schema.sql | 15 | ||||
| -rw-r--r-- | src/components/SimpleTest.tsx | 32 | ||||
| -rw-r--r-- | src/components/TaskManager.tsx | 202 | ||||
| -rw-r--r-- | src/env.d.ts | 13 | ||||
| -rw-r--r-- | src/pages/api/tasks.ts | 66 | ||||
| -rw-r--r-- | src/pages/api/tasks/[id].ts | 57 | ||||
| -rw-r--r-- | src/pages/index.astro | 71 | ||||
| -rw-r--r-- | wrangler.jsonc | 16 |
13 files changed, 994 insertions, 91 deletions
@@ -24,3 +24,4 @@ pnpm-debug.log* .idea/ .claude/ +.wrangler/ @@ -1,14 +1,103 @@ -# Astro Base +# Full-Stack Astro + Cloudflare -A minimal template for new Astro projects. Includes +A complete full-stack application built with Astro, running on Cloudflare Workers with D1 database. -- TypeScript -- Tailwind CSS -- React -- Tailwind Typography +## Features -## Start a New Project +- **Frontend**: Astro with React components and Tailwind CSS +- **Backend**: Cloudflare Workers with REST API endpoints +- **Database**: Cloudflare D1 SQLite database +- **Local Development**: Integrated Cloudflare Workers runtime via platformProxy -```sh -$ npm create astro@latest new-project -- --template yuvadm/astro-base -```
\ No newline at end of file +## Quick Start + +### Prerequisites + +- Node.js 18+ +- npm or yarn +- Cloudflare account (for deployment) + +### Local Development + +1. **Install dependencies:** + ```bash + npm install + ``` + +2. **Set up local database:** + ```bash + npm run db:migrate + ``` + +3. **Start development server:** + ```bash + npm run dev + ``` + + The app will be available at `http://localhost:4321` (or next available port) + +## How It Works + +This project uses Astro's Cloudflare adapter with `platformProxy.enabled: true`, which provides: + +- **Automatic Cloudflare Workers Runtime**: The `workerd` binary runs alongside Astro's dev server +- **Local D1 Database**: A local SQLite database is automatically created and managed +- **Seamless API Integration**: API endpoints receive a real D1 database instance without additional setup +- **Production Parity**: The local environment closely matches the production Cloudflare Workers environment + +The platform proxy eliminates the need for separate `wrangler dev` commands while providing full D1 database functionality during development. + +### Deployment Setup + +1. **Create D1 database:** + ```bash + npm run db:create + ``` + +2. **Update wrangler.toml:** + - Copy the database ID from the previous command output + - Replace `your-database-id-here` in `wrangler.toml` + +3. **Initialize production database:** + ```bash + npm run db:init + ``` + +4. **Build and deploy:** + ```bash + npm run build + npm run deploy + ``` + +## API Endpoints + +- `GET /api/tasks` - Get all tasks +- `POST /api/tasks` - Create a new task +- `PUT /api/tasks/[id]` - Update a task +- `DELETE /api/tasks/[id]` - Delete a task + +## Project Structure + +``` +src/ +├── components/ +│ └── TaskManager.tsx # React component for task management +├── pages/ +│ ├── api/ +│ │ ├── tasks.ts # Tasks API endpoints +│ │ └── tasks/[id].ts # Individual task operations +│ └── index.astro # Main page +├── layouts/ +│ └── StandardLayout.astro +└── env.d.ts # TypeScript definitions + +schema.sql # Database schema +wrangler.toml # Cloudflare Workers configuration +``` + +## Local vs Production + +- **Local**: Uses Wrangler's local D1 database simulator +- **Production**: Uses actual Cloudflare D1 database in the cloud + +The same codebase works seamlessly in both environments thanks to Astro's Cloudflare adapter and Wrangler's local development tools.
\ No newline at end of file diff --git a/astro.config.mjs b/astro.config.mjs index 0f737ef..cc75542 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,18 +1,21 @@ // @ts-check import { defineConfig } from 'astro/config'; - import tailwindcss from '@tailwindcss/vite'; - import react from '@astrojs/react'; +import cloudflare from '@astrojs/cloudflare'; // https://astro.build/config export default defineConfig({ + output: 'server', + adapter: cloudflare({ + platformProxy: { + enabled: true + } + }), vite: { plugins: [tailwindcss()] }, - integrations: [react()], - devToolbar: { enabled: false, }, diff --git a/package-lock.json b/package-lock.json index b9ec0bc..0d7d3ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "astro-base", "version": "0.0.1", "dependencies": { + "@astrojs/cloudflare": "^12.6.0", "@astrojs/react": "^4.3.0", "@tailwindcss/vite": "^4.1.7", "@types/react": "^19.1.5", @@ -15,7 +16,8 @@ "astro": "^5.10.1", "react": "^19.1.0", "react-dom": "^19.1.0", - "tailwindcss": "^4.1.7" + "tailwindcss": "^4.1.7", + "wrangler": "^4.22.0" }, "devDependencies": { "@tailwindcss/typography": "^0.5.16" @@ -34,6 +36,23 @@ "node": ">=6.0.0" } }, + "node_modules/@astrojs/cloudflare": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@astrojs/cloudflare/-/cloudflare-12.6.0.tgz", + "integrity": "sha512-pQ8bokC59GEiXvyXpC4swBNoL7C/EknP+82KFzQwgR/Aeo5N1oPiAoPHgJbpPya/YF4E26WODdCQfBQDvLRfuw==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.6.1", + "@astrojs/underscore-redirects": "1.0.0", + "@cloudflare/workers-types": "^4.20250507.0", + "tinyglobby": "^0.2.13", + "vite": "^6.3.5", + "wrangler": "^4.14.1" + }, + "peerDependencies": { + "astro": "^5.0.0" + } + }, "node_modules/@astrojs/compiler": { "version": "2.12.2", "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.12.2.tgz", @@ -125,6 +144,12 @@ "node": "18.20.8 || ^20.3.0 || >=22.0.0" } }, + "node_modules/@astrojs/underscore-redirects": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@astrojs/underscore-redirects/-/underscore-redirects-1.0.0.tgz", + "integrity": "sha512-qZxHwVnmb5FXuvRsaIGaqWgnftjCuMY+GSbaVZdBmE4j8AfgPqKPxYp8SUERyJcjpKCEmO4wD6ybuGH8A2kVRQ==", + "license": "MIT" + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -423,6 +448,141 @@ "fontkit": "^2.0.2" } }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.0.tgz", + "integrity": "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "mime": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.3.3.tgz", + "integrity": "sha512-/M3MEcj3V2WHIRSW1eAQBPRJ6JnGQHc6JKMAPLkDb7pLs3m6X9ES/+K3ceGqxI6TKeF32AWAi7ls0AYzVxCP0A==", + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.17", + "workerd": "^1.20250508.0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20250617.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250617.0.tgz", + "integrity": "sha512-toG8JUKVLIks4oOJLe9FeuixE84pDpMZ32ip7mCpE7JaFc5BqGFvevk0YC/db3T71AQlialjRwioH3jS/dzItA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20250617.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250617.0.tgz", + "integrity": "sha512-JTX0exbC9/ZtMmQQA8tDZEZFMXZrxOpTUj2hHnsUkErWYkr5SSZH04RBhPg6dU4VL8bXuB5/eJAh7+P9cZAp7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20250617.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250617.0.tgz", + "integrity": "sha512-8jkSoVRJ+1bOx3tuWlZCGaGCV2ew7/jFMl6V3CPXOoEtERUHsZBQLVkQIGKcmC/LKSj7f/mpyBUeu2EPTo2HEg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20250617.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250617.0.tgz", + "integrity": "sha512-YAzcOyu897z5dQKFzme1oujGWMGEJCR7/Wrrm1nSP6dqutxFPTubRADM8BHn2CV3ij//vaPnAeLmZE3jVwOwig==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20250617.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250617.0.tgz", + "integrity": "sha512-XWM/6sagDrO0CYDKhXhPjM23qusvIN1ju9ZEml6gOQs8tNOFnq6Cn6X9FAmnyapRFCGUSEC3HZYJAm7zwVKaMA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workers-types": { + "version": "4.20250627.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20250627.0.tgz", + "integrity": "sha512-1il2InPF6NwucUwDl9RL6KLUfJuqaLazGTa9IZRO/XqMFggtGOXuc9X9wZ+IH1PTOWkdJ+jN+woyjj0W2CcTKg==", + "license": "MIT OR Apache-2.0" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@emnapi/runtime": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", @@ -833,6 +993,15 @@ "node": ">=18" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", @@ -2077,6 +2246,15 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -2201,6 +2379,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/as-table": { + "version": "1.0.55", + "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", + "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", + "license": "MIT", + "dependencies": { + "printable-characters": "^1.0.42" + } + }, "node_modules/astro": { "version": "5.10.1", "resolved": "https://registry.npmjs.org/astro/-/astro-5.10.1.tgz", @@ -2330,6 +2517,12 @@ ], "license": "MIT" }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "license": "MIT" + }, "node_modules/blob-to-buffer": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/blob-to-buffer/-/blob-to-buffer-1.2.9.tgz", @@ -2571,7 +2764,6 @@ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", "license": "MIT", - "optional": true, "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" @@ -2585,7 +2777,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", - "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2597,15 +2788,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/color-string": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "license": "MIT", - "optional": true, "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -2697,6 +2886,12 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/data-uri-to-buffer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", + "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -2937,6 +3132,24 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, + "node_modules/exit-hook": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", + "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "license": "MIT" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -3034,12 +3247,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-source": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", + "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", + "license": "Unlicense", + "dependencies": { + "data-uri-to-buffer": "^2.0.0", + "source-map": "^0.6.1" + } + }, "node_modules/github-slugger": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", "license": "ISC" }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -3304,8 +3533,7 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/is-docker": { "version": "3.0.0", @@ -4525,6 +4753,65 @@ ], "license": "MIT" }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/miniflare": { + "version": "4.20250617.4", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250617.4.tgz", + "integrity": "sha512-IAoApFKxOJlaaFkym5ETstVX3qWzVt3xyqCDj6vSSTgEH3zxZJ5417jZGg8iQfMHosKCcQH1doPPqqnOZm/yrw==", + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "acorn": "8.14.0", + "acorn-walk": "8.3.2", + "exit-hook": "2.2.1", + "glob-to-regexp": "0.4.1", + "sharp": "^0.33.5", + "stoppable": "1.1.0", + "undici": "^5.28.5", + "workerd": "1.20250617.0", + "ws": "8.18.0", + "youch": "3.3.4", + "zod": "3.22.3" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/miniflare/node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/miniflare/node_modules/zod": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", + "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -4576,6 +4863,15 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -4782,6 +5078,18 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4842,6 +5150,12 @@ "node": ">=4" } }, + "node_modules/printable-characters": { + "version": "1.0.42", + "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", + "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", + "license": "Unlicense" + }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -5228,7 +5542,6 @@ "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", "hasInstallScript": true, "license": "Apache-2.0", - "optional": true, "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", @@ -5283,7 +5596,6 @@ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", "license": "MIT", - "optional": true, "dependencies": { "is-arrayish": "^0.3.1" } @@ -5306,6 +5618,15 @@ "url": "https://github.com/sponsors/cyyynthia" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5325,6 +5646,26 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/stacktracey": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", + "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", + "license": "Unlicense", + "dependencies": { + "as-table": "^1.0.36", + "get-source": "^2.0.12" + } + }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "license": "MIT", + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, "node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -5527,12 +5868,37 @@ "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", "license": "MIT" }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, + "node_modules/unenv": { + "version": "2.0.0-rc.17", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.17.tgz", + "integrity": "sha512-B06u0wXkEd+o5gOCMl/ZHl5cfpYbDZKAT+HWTL+Hws6jWu7dCiqBBXXXzMFcFVJb8D4ytAnYmxJA83uwOQRSsg==", + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "exsolve": "^1.0.4", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "ufo": "^1.6.1" + } + }, "node_modules/unicode-properties": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", @@ -6018,6 +6384,60 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/workerd": { + "version": "1.20250617.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250617.0.tgz", + "integrity": "sha512-Uv6p0PYUHp/W/aWfUPLkZVAoAjapisM27JJlwcX9wCPTfCfnuegGOxFMvvlYpmNaX4YCwEdLCwuNn3xkpSkuZw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20250617.0", + "@cloudflare/workerd-darwin-arm64": "1.20250617.0", + "@cloudflare/workerd-linux-64": "1.20250617.0", + "@cloudflare/workerd-linux-arm64": "1.20250617.0", + "@cloudflare/workerd-windows-64": "1.20250617.0" + } + }, + "node_modules/wrangler": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.22.0.tgz", + "integrity": "sha512-m8qVO3YxhUTII+4U889G/f5UuLSvMkUkCNatupV2f/SJ+iqaWtP1QbuQII8bs2J/O4rqxsz46Wu2S50u7tKB5Q==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.4.0", + "@cloudflare/unenv-preset": "2.3.3", + "blake3-wasm": "2.1.5", + "esbuild": "0.25.4", + "miniflare": "4.20250617.4", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.17", + "workerd": "1.20250617.0" + }, + "bin": { + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20250617.0" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, "node_modules/wrap-ansi": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", @@ -6035,6 +6455,27 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xxhash-wasm": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", @@ -6098,6 +6539,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/youch": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz", + "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==", + "license": "MIT", + "dependencies": { + "cookie": "^0.7.1", + "mustache": "^4.2.0", + "stacktracey": "^2.1.8" + } + }, + "node_modules/youch/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/zod": { "version": "3.25.23", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.23.tgz", diff --git a/package.json b/package.json index af86bf6..af4c89c 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,17 @@ "version": "0.0.1", "scripts": { "dev": "astro dev", + "dev:wrangler": "wrangler pages dev --compatibility-date=2024-06-27 --d1=DB=astro-base-db -- astro dev", "build": "astro build", - "preview": "astro preview", + "preview": "wrangler pages dev dist", + "deploy": "wrangler pages deploy dist", + "db:create": "wrangler d1 create astro-base-db", + "db:init": "wrangler d1 execute astro-base-db --file=./schema.sql", + "db:migrate": "wrangler d1 execute astro-base-db --file=./schema.sql --local", "astro": "astro" }, "dependencies": { + "@astrojs/cloudflare": "^12.6.0", "@astrojs/react": "^4.3.0", "@tailwindcss/vite": "^4.1.7", "@types/react": "^19.1.5", @@ -16,7 +22,8 @@ "astro": "^5.10.1", "react": "^19.1.0", "react-dom": "^19.1.0", - "tailwindcss": "^4.1.7" + "tailwindcss": "^4.1.7", + "wrangler": "^4.22.0" }, "devDependencies": { "@tailwindcss/typography": "^0.5.16" diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..15bdacb --- /dev/null +++ b/schema.sql @@ -0,0 +1,15 @@ +-- Create a simple tasks table for our POC +CREATE TABLE IF NOT EXISTS tasks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + description TEXT, + completed BOOLEAN DEFAULT FALSE, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- Insert some sample data +INSERT INTO tasks (title, description) VALUES + ('Set up Cloudflare Workers', 'Configure the worker environment'), + ('Create D1 database', 'Set up the database schema'), + ('Build API endpoints', 'Create REST API for tasks');
\ No newline at end of file diff --git a/src/components/SimpleTest.tsx b/src/components/SimpleTest.tsx new file mode 100644 index 0000000..cfabeab --- /dev/null +++ b/src/components/SimpleTest.tsx @@ -0,0 +1,32 @@ +import { useState } from 'react'; + +export default function SimpleTest() { + const [count, setCount] = useState(0); + + return ( + <div className="max-w-2xl mx-auto p-6"> + <h1 className="text-3xl font-bold mb-6">Simple Test Component</h1> + <div className="space-y-4"> + <p>This is a simple React component to test if the infinite refresh is caused by the API calls.</p> + <div className="flex items-center space-x-4"> + <button + onClick={() => setCount(count - 1)} + className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600" + > + - + </button> + <span className="text-xl font-bold">{count}</span> + <button + onClick={() => setCount(count + 1)} + className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" + > + + + </button> + </div> + <p className="text-gray-600"> + If this component works without refreshing, the issue is in the TaskManager API calls. + </p> + </div> + </div> + ); +}
\ No newline at end of file diff --git a/src/components/TaskManager.tsx b/src/components/TaskManager.tsx new file mode 100644 index 0000000..a10a5f9 --- /dev/null +++ b/src/components/TaskManager.tsx @@ -0,0 +1,202 @@ +import { useState, useEffect, useCallback } from 'react'; + +interface Task { + id: number; + title: string; + description: string; + completed: boolean; + created_at?: string; + updated_at?: string; +} + +export default function TaskManager() { + const [tasks, setTasks] = useState<Task[]>([]); + const [newTask, setNewTask] = useState({ title: '', description: '' }); + const [loading, setLoading] = useState(true); + const [error, setError] = useState<string | null>(null); + + const fetchTasks = useCallback(async () => { + try { + setError(null); + const response = await fetch('/api/tasks'); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + console.log('Fetched tasks:', data); + setTasks(Array.isArray(data) ? data : []); + } catch (error) { + console.error('Failed to fetch tasks:', error); + setError(error instanceof Error ? error.message : 'Failed to fetch tasks'); + setTasks([]); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchTasks(); + }, [fetchTasks]); + + const createTask = async (e: React.FormEvent) => { + e.preventDefault(); + if (!newTask.title.trim()) return; + + try { + const response = await fetch('/api/tasks', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(newTask) + }); + + if (response.ok) { + setNewTask({ title: '', description: '' }); + fetchTasks(); + } + } catch (error) { + console.error('Failed to create task:', error); + } + }; + + const toggleTask = async (task: Task) => { + try { + await fetch(`/api/tasks/${task.id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ ...task, completed: !task.completed }) + }); + fetchTasks(); + } catch (error) { + console.error('Failed to update task:', error); + } + }; + + const deleteTask = async (id: number) => { + try { + await fetch(`/api/tasks/${id}`, { method: 'DELETE' }); + fetchTasks(); + } catch (error) { + console.error('Failed to delete task:', error); + } + }; + + if (loading) { + return ( + <div className="max-w-2xl mx-auto p-6"> + <h1 className="text-3xl font-bold mb-6">Task Manager</h1> + <div className="p-4 text-center">Loading tasks...</div> + </div> + ); + } + + if (error) { + return ( + <div className="max-w-2xl mx-auto p-6"> + <h1 className="text-3xl font-bold mb-6">Task Manager</h1> + <div className="p-4 bg-red-50 border border-red-200 rounded-lg"> + <p className="text-red-700">Error: {error}</p> + <button + onClick={fetchTasks} + className="mt-2 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600" + > + Retry + </button> + </div> + </div> + ); + } + + return ( + <div className="max-w-2xl mx-auto p-6"> + <h1 className="text-3xl font-bold mb-6">Task Manager</h1> + + <form onSubmit={createTask} className="mb-8 space-y-4"> + <div> + <input + type="text" + placeholder="Task title" + value={newTask.title} + onChange={(e) => setNewTask({ ...newTask, title: e.target.value })} + className="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + /> + </div> + <div> + <textarea + placeholder="Task description (optional)" + value={newTask.description} + onChange={(e) => setNewTask({ ...newTask, description: e.target.value })} + className="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + rows={3} + /> + </div> + <button + type="submit" + className="w-full bg-blue-500 text-white p-3 rounded-lg hover:bg-blue-600 transition-colors" + > + Add Task + </button> + </form> + + <div className="space-y-4"> + {tasks.length === 0 ? ( + <p className="text-gray-500 text-center">No tasks yet. Create your first task above!</p> + ) : ( + tasks.map((task) => ( + <div + key={task.id} + className={`p-4 border rounded-lg ${ + task.completed ? 'bg-green-50 border-green-200' : 'bg-white border-gray-200' + }`} + > + <div className="flex items-start justify-between"> + <div className="flex-1"> + <h3 + className={`font-semibold ${ + task.completed ? 'line-through text-gray-500' : 'text-gray-900' + }`} + > + {task.title} + </h3> + {task.description && ( + <p + className={`mt-1 ${ + task.completed ? 'line-through text-gray-400' : 'text-gray-600' + }`} + > + {task.description} + </p> + )} + {task.created_at && ( + <p className="text-xs text-gray-400 mt-2"> + Created: {new Date(task.created_at).toLocaleString()} + </p> + )} + </div> + <div className="flex space-x-2 ml-4"> + <button + onClick={() => toggleTask(task)} + className={`px-3 py-1 rounded text-sm ${ + task.completed + ? 'bg-yellow-500 text-white hover:bg-yellow-600' + : 'bg-green-500 text-white hover:bg-green-600' + }`} + > + {task.completed ? 'Undo' : 'Complete'} + </button> + <button + onClick={() => deleteTask(task.id)} + className="px-3 py-1 bg-red-500 text-white rounded text-sm hover:bg-red-600" + > + Delete + </button> + </div> + </div> + </div> + )) + )} + </div> + </div> + ); +}
\ No newline at end of file diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..359b598 --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,13 @@ +/// <reference types="astro/client" /> + +type D1Database = import('@cloudflare/workers-types').D1Database; + +declare namespace App { + interface Locals { + runtime: { + env: { + DB: D1Database; + }; + }; + } +}
\ No newline at end of file diff --git a/src/pages/api/tasks.ts b/src/pages/api/tasks.ts new file mode 100644 index 0000000..00328e8 --- /dev/null +++ b/src/pages/api/tasks.ts @@ -0,0 +1,66 @@ +import type { APIRoute } from 'astro'; + +export const GET: APIRoute = async ({ locals }) => { + try { + const db = locals.runtime?.env?.DB; + + if (!db) { + console.error('Database not available'); + return new Response(JSON.stringify([]), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + } + + const { results } = await db.prepare('SELECT * FROM tasks ORDER BY created_at DESC').all(); + + return new Response(JSON.stringify(results || []), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + } catch (error) { + console.error('Database error:', error); + return new Response(JSON.stringify([]), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + } +}; + +export const POST: APIRoute = async ({ request, locals }) => { + try { + const db = locals.runtime?.env?.DB; + + if (!db) { + return new Response(JSON.stringify({ error: 'Database not available' }), { + status: 503, + headers: { 'Content-Type': 'application/json' } + }); + } + + const body = await request.json(); + const { title, description } = body; + + if (!title) { + return new Response(JSON.stringify({ error: 'Title is required' }), { + status: 400, + headers: { 'Content-Type': 'application/json' } + }); + } + + const { results } = await db.prepare( + 'INSERT INTO tasks (title, description) VALUES (?, ?) RETURNING *' + ).bind(title, description || '').all(); + + return new Response(JSON.stringify(results?.[0] || { id: Date.now(), title, description, completed: false }), { + status: 201, + headers: { 'Content-Type': 'application/json' } + }); + } catch (error) { + console.error('Database error:', error); + return new Response(JSON.stringify({ error: 'Failed to create task' }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } +};
\ No newline at end of file diff --git a/src/pages/api/tasks/[id].ts b/src/pages/api/tasks/[id].ts new file mode 100644 index 0000000..e9341b6 --- /dev/null +++ b/src/pages/api/tasks/[id].ts @@ -0,0 +1,57 @@ +import type { APIRoute } from 'astro'; + +export const PUT: APIRoute = async ({ params, request, locals }) => { + try { + const db = locals.runtime.env.DB; + const id = params.id; + const body = await request.json(); + const { title, description, completed } = body; + + const { results } = await db.prepare( + 'UPDATE tasks SET title = ?, description = ?, completed = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? RETURNING *' + ).bind(title, description || '', completed || false, id).all(); + + if (results.length === 0) { + return new Response(JSON.stringify({ error: 'Task not found' }), { + status: 404, + headers: { 'Content-Type': 'application/json' } + }); + } + + return new Response(JSON.stringify(results[0]), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + } catch (error) { + return new Response(JSON.stringify({ error: 'Failed to update task' }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } +}; + +export const DELETE: APIRoute = async ({ params, locals }) => { + try { + const db = locals.runtime.env.DB; + const id = params.id; + + const { success } = await db.prepare('DELETE FROM tasks WHERE id = ?').bind(id).run(); + + if (!success) { + return new Response(JSON.stringify({ error: 'Task not found' }), { + status: 404, + headers: { 'Content-Type': 'application/json' } + }); + } + + return new Response(JSON.stringify({ message: 'Task deleted successfully' }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + } catch (error) { + return new Response(JSON.stringify({ error: 'Failed to delete task' }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } +};
\ No newline at end of file diff --git a/src/pages/index.astro b/src/pages/index.astro index a88712c..09a80cc 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,78 +1,19 @@ --- -import Counter from "../components/Counter"; +import TaskManager from "../components/TaskManager"; import StandardLayout from "../layouts/StandardLayout.astro"; -import { getCollection } from "astro:content"; - -// Get the latest 3 blog posts -const latestPosts = await getCollection("blog"); -latestPosts.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()); -const featuredPosts = latestPosts.slice(0, 3); --- -<StandardLayout title="Astro - Home"> +<StandardLayout title="Full-Stack Astro + Cloudflare"> <section class="py-12"> <div class="text-center mb-12"> <h1 class="text-5xl font-bold text-gray-900 mb-4"> - Welcome to Astro + Full-Stack Astro </h1> <p class="text-xl text-gray-600 max-w-3xl mx-auto"> - A modern framework for building fast, content-focused websites. + Powered by Cloudflare Workers and D1 Database </p> - <div class="mt-8"> - <Counter client:load /> - </div> - </div> - </section> - - <section - class="py-12 bg-gray-50 -mx-4 sm:-mx-6 lg:-mx-8 px-4 sm:px-6 lg:px-8" - > - <div class="max-w-7xl mx-auto"> - <div class="flex justify-between items-center mb-8"> - <h2 class="text-3xl font-bold text-gray-900"> - Latest Blog Posts - </h2> - <a - href="/blog" - class="text-blue-600 hover:text-blue-800 font-medium" - >View all posts →</a - > - </div> - - <div class="grid gap-8 md:grid-cols-2 lg:grid-cols-3"> - { - featuredPosts.map((post) => ( - <article class="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow"> - <a href={`/blog/${post.slug}`} class="block"> - {post.data.image && ( - <img - src={post.data.image} - alt={post.data.title} - class="w-full h-48 object-cover" - /> - )} - <div class="p-6"> - <h3 class="text-xl font-semibold text-gray-900 mb-2"> - {post.data.title} - </h3> - <p class="text-sm text-gray-500 mb-3"> - {new Date( - post.data.pubDate, - ).toLocaleDateString("en-US", { - year: "numeric", - month: "long", - day: "numeric", - })} - </p> - <p class="text-gray-700"> - {post.data.description} - </p> - </div> - </a> - </article> - )) - } - </div> </div> + + <TaskManager client:load /> </section> </StandardLayout> diff --git a/wrangler.jsonc b/wrangler.jsonc new file mode 100644 index 0000000..2c58a61 --- /dev/null +++ b/wrangler.jsonc @@ -0,0 +1,16 @@ +{ + // Cloudflare Workers configuration + "name": "astro-base-cf", + "compatibility_date": "2024-06-27", + "compatibility_flags": ["nodejs_compat"], + + // D1 Database configuration + "d1_databases": [ + { + "binding": "DB", + "database_name": "astro-base-db", + "database_id": "your-database-id-here", + "preview_database_id": "your-preview-database-id-here" + } + ] +}
\ No newline at end of file |
