From 3b197febc1bbdbad7d9a78eb6d61183dd6b3e3fd Mon Sep 17 00:00:00 2001 From: Eric Jinks <3147296+Jinksi@users.noreply.github.com> Date: Sat, 16 Aug 2025 22:51:59 +1000 Subject: [PATCH 1/3] Add Astro DB integration with counter functionality - Add @astrojs/db integration to astro.config.mjs - Create Counter table with id, name, and count columns - Add database seeding with initial adminCounter - Implement Astro Actions for getting and incrementing counters - Update admin dashboard with interactive counter button - Add --remote flag to dev and build scripts for database connectivity - Fix typo in counter name reference (adminCouter -> adminCounter) --- astro.config.mjs | 14 +- db/config.ts | 13 ++ db/seed.ts | 8 + package-lock.json | 432 +++++++++++++++++++++++++++++++++++- package.json | 4 +- src/actions/index.ts | 50 +++++ src/pages/admin/index.astro | 53 ++++- 7 files changed, 557 insertions(+), 17 deletions(-) create mode 100644 db/config.ts create mode 100644 db/seed.ts create mode 100644 src/actions/index.ts diff --git a/astro.config.mjs b/astro.config.mjs index 3f856d7..4a4c35f 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,6 +1,7 @@ import pagefind from 'astro-pagefind' import { defineConfig, envField } from 'astro/config' +import db from '@astrojs/db' import mdx from '@astrojs/mdx' import netlify from '@astrojs/netlify' import react from '@astrojs/react' @@ -24,6 +25,7 @@ export default defineConfig({ applyBaseStyles: false, }), pagefind(), + db(), ], trailingSlash: 'always', markdown: { @@ -49,17 +51,17 @@ export default defineConfig({ schema: { ADMIN_USERNAME: envField.string({ context: 'server', - access: 'secret' + access: 'secret', }), ADMIN_PASSWORD: envField.string({ context: 'server', - access: 'secret' + access: 'secret', }), ADMIN_SECRET: envField.string({ context: 'server', - access: 'secret' - }) + access: 'secret', + }), }, - validateSecrets: true - } + validateSecrets: true, + }, }) diff --git a/db/config.ts b/db/config.ts new file mode 100644 index 0000000..f96063d --- /dev/null +++ b/db/config.ts @@ -0,0 +1,13 @@ +import { column, defineDb, defineTable } from 'astro:db' + +const Counter = defineTable({ + columns: { + id: column.text({ primaryKey: true }), + name: column.text({ unique: true }), + count: column.number({ default: 0 }), + }, +}) + +export default defineDb({ + tables: { Counter }, +}) diff --git a/db/seed.ts b/db/seed.ts new file mode 100644 index 0000000..2e8850e --- /dev/null +++ b/db/seed.ts @@ -0,0 +1,8 @@ +import { Counter, db } from 'astro:db' + +// https://astro.build/db/seed +export default async function seed() { + await db + .insert(Counter) + .values([{ id: crypto.randomUUID(), name: 'adminCounter', count: 0 }]) +} diff --git a/package-lock.json b/package-lock.json index dfc38ad..04326a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "dependencies": { "@astrojs/check": "^0.9.4", + "@astrojs/db": "^0.17.1", "@astrojs/mdx": "^4.3.1", "@astrojs/netlify": "^6.5.2", "@astrojs/react": "^4.3.0", @@ -101,6 +102,161 @@ "integrity": "sha512-w2zfvhjNCkNMmMMOn5b0J8+OmUaBL1o40ipMvqcG6NRpdC+lKxmTi48DT8Xw0SzJ3AfmeFLB45zXZXtmbsjcgw==", "license": "MIT" }, + "node_modules/@astrojs/db": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@astrojs/db/-/db-0.17.1.tgz", + "integrity": "sha512-QL09xZf5Om8AshIlt+YhLDYf6M1QSzv+kfuljsPrhEXJ8U/tuKnbWs2M3wimFaLG3/fU0prFix8lWt7zU8ytfA==", + "license": "MIT", + "dependencies": { + "@libsql/client": "^0.15.2", + "deep-diff": "^1.0.2", + "drizzle-orm": "^0.42.0", + "kleur": "^4.1.5", + "nanoid": "^5.1.5", + "prompts": "^2.4.2", + "yargs-parser": "^21.1.1", + "zod": "^3.24.4" + } + }, + "node_modules/@astrojs/db/node_modules/drizzle-orm": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.42.0.tgz", + "integrity": "sha512-pS8nNJm2kBNZwrOjTHJfdKkaU+KuUQmV/vk5D57NojDq4FG+0uAYGMulXtYT///HfgsMF0hnFFvu1ezI3OwOkg==", + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1.13", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/sql.js": "*", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "gel": ">=2", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "gel": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, + "node_modules/@astrojs/db/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, "node_modules/@astrojs/internal-helpers": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.6.1.tgz", @@ -1804,6 +1960,212 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@libsql/client": { + "version": "0.15.11", + "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.15.11.tgz", + "integrity": "sha512-JB8RWRs+cAbHX35/dQ9wD3m4W5EVGevq1fFqiHKTT4Pa5HR7WrcGRVT+8NL2M7gtTlOvyPh9zzms2DPLBCswig==", + "license": "MIT", + "dependencies": { + "@libsql/core": "^0.15.11", + "@libsql/hrana-client": "^0.7.0", + "js-base64": "^3.7.5", + "libsql": "^0.5.15", + "promise-limit": "^2.7.0" + } + }, + "node_modules/@libsql/client/node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "license": "BSD-3-Clause" + }, + "node_modules/@libsql/core": { + "version": "0.15.11", + "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.15.11.tgz", + "integrity": "sha512-DQDYnEhCSYOsx30ASlOGuOqcQhvwELhOS2qM4dnIP+ZhKki2epZU1j5VZSNeQlrQXHkByMcWBy+wt7tBNx/9uA==", + "license": "MIT", + "dependencies": { + "js-base64": "^3.7.5" + } + }, + "node_modules/@libsql/core/node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "license": "BSD-3-Clause" + }, + "node_modules/@libsql/darwin-arm64": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.5.17.tgz", + "integrity": "sha512-WTYG2skZsUnZmfZ2v7WFj7s3/5s2PfrYBZOWBKOnxHA8g4XCDc/4bFDaqob9Q2e88+GC7cWeJ8VNkVBFpD2Xxg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/darwin-x64": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.5.17.tgz", + "integrity": "sha512-ab0RlTR4KYrxgjNrZhAhY/10GibKoq6G0W4oi0kdm+eYiAv/Ip8GDMpSaZdAcoKA4T+iKR/ehczKHnMEB8MFxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/hrana-client": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.7.0.tgz", + "integrity": "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==", + "license": "MIT", + "dependencies": { + "@libsql/isomorphic-fetch": "^0.3.1", + "@libsql/isomorphic-ws": "^0.1.5", + "js-base64": "^3.7.5", + "node-fetch": "^3.3.2" + } + }, + "node_modules/@libsql/hrana-client/node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "license": "BSD-3-Clause" + }, + "node_modules/@libsql/hrana-client/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/@libsql/isomorphic-fetch": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.3.1.tgz", + "integrity": "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@libsql/isomorphic-ws": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", + "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", + "license": "MIT", + "dependencies": { + "@types/ws": "^8.5.4", + "ws": "^8.13.0" + } + }, + "node_modules/@libsql/linux-arm-gnueabihf": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm-gnueabihf/-/linux-arm-gnueabihf-0.5.17.tgz", + "integrity": "sha512-PcASh4k47RqC+kMWAbLUKf1y6Do0q8vnUGi0yhKY4ghJcimMExViBimjbjYRSa+WIb/zh3QxNoXOhQAXx3tiuw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm-musleabihf": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm-musleabihf/-/linux-arm-musleabihf-0.5.17.tgz", + "integrity": "sha512-vxOkSLG9Wspit+SNle84nuIzMtr2G2qaxFzW7BhsZBjlZ8+kErf9RXcT2YJQdJYxmBYRbsOrc91gg0jLEQVCqg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm64-gnu": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.17.tgz", + "integrity": "sha512-L8jnaN01TxjBJlDuDTX2W2BKzBkAOhcnKfCOf3xzvvygblxnDOK0whkYwIXeTfwtd/rr4jN/d6dZD/bcHiDxEQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm64-musl": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.17.tgz", + "integrity": "sha512-HfFD7TzQtmmTwyQsuiHhWZdMRtdNpKJ1p4tbMMTMRECk+971NFHrj69D64cc2ClVTAmn7fA9XibKPil7WN/Q7w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-gnu": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.17.tgz", + "integrity": "sha512-5l3XxWqUPVFrtX0xnZaXwqsXs0BFbP4w6ahRFTPSdXU50YBfUOajFznJRB6bJTMsCvraDSD0IkHhjSNfrE1CuQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-musl": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.17.tgz", + "integrity": "sha512-FvSpWlwc+dIeYIFYlsSv+UdQ/NiZWr+SstwVji+QZ//8NnvzwWQU9cgP+Vpps6Qiq4jyYQm9chJhTYOVT9Y3BA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/win32-x64-msvc": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.17.tgz", + "integrity": "sha512-f5bGH8+3A5sn6Lrqg8FsQ09a1pYXPnKGXGTFiAYlfQXVst1tUTxDTugnuWcJYKXyzDe/T7ccxyIZXeSmPOhq8A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@lit-labs/ssr-dom-shim": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz", @@ -1940,6 +2302,12 @@ "tslib": "^2.3.1" } }, + "node_modules/@neon-rs/load": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", + "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==", + "license": "MIT" + }, "node_modules/@netlify/api": { "version": "14.0.3", "resolved": "https://registry.npmjs.org/@netlify/api/-/api-14.0.3.tgz", @@ -3656,6 +4024,15 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", @@ -6144,6 +6521,12 @@ "integrity": "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==", "license": "MIT" }, + "node_modules/deep-diff": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz", + "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==", + "license": "MIT" + }, "node_modules/deep-equal": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", @@ -9979,6 +10362,47 @@ "node": ">=6" } }, + "node_modules/libsql": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.5.17.tgz", + "integrity": "sha512-RRlj5XQI9+Wq+/5UY8EnugSWfRmHEw4hn3DKlPrkUgZONsge1PwTtHcpStP6MSNi8ohcbsRgEHJaymA33a8cBw==", + "cpu": [ + "x64", + "arm64", + "wasm32", + "arm" + ], + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32" + ], + "dependencies": { + "@neon-rs/load": "^0.0.4", + "detect-libc": "2.0.2" + }, + "optionalDependencies": { + "@libsql/darwin-arm64": "0.5.17", + "@libsql/darwin-x64": "0.5.17", + "@libsql/linux-arm-gnueabihf": "0.5.17", + "@libsql/linux-arm-musleabihf": "0.5.17", + "@libsql/linux-arm64-gnu": "0.5.17", + "@libsql/linux-arm64-musl": "0.5.17", + "@libsql/linux-x64-gnu": "0.5.17", + "@libsql/linux-x64-musl": "0.5.17", + "@libsql/win32-x64-msvc": "0.5.17" + } + }, + "node_modules/libsql/node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -12941,6 +13365,12 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/promise-limit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", + "license": "ISC" + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -16470,8 +16900,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", - "optional": true, - "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index 5edaa1b..46f37d8 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,9 @@ "private": true, "scripts": { "dev": "astro dev", + "dev:remote": "astro dev --remote", "start": "astro dev", - "build": "npm run fetch-stars && astro build && npm run build:pagefind", + "build": "npm run fetch-stars && astro build --remote && npm run build:pagefind", "build:pagefind": "node scripts/index-github-stars.js", "preview": "astro preview", "astro": "astro", @@ -15,6 +16,7 @@ }, "dependencies": { "@astrojs/check": "^0.9.4", + "@astrojs/db": "^0.17.1", "@astrojs/mdx": "^4.3.1", "@astrojs/netlify": "^6.5.2", "@astrojs/react": "^4.3.0", diff --git a/src/actions/index.ts b/src/actions/index.ts new file mode 100644 index 0000000..f2496f9 --- /dev/null +++ b/src/actions/index.ts @@ -0,0 +1,50 @@ +import { defineAction } from 'astro:actions' +import { Counter, db, eq } from 'astro:db' +import { z } from 'astro:schema' + +export const server = { + getCounter: defineAction({ + input: z.object({ + counterName: z.string(), + }), + handler: async (input) => { + const counterResult = await db + .select() + .from(Counter) + .where(eq(Counter.name, input.counterName)) + .limit(1) + const counter = counterResult?.[0] + + if (!counter) { + throw new Error(`Counter ${input.counterName} not found`) + } + + return counter + }, + }), + incrementCounter: defineAction({ + input: z.object({ + counterName: z.string(), + }), + handler: async (input) => { + const counterResult = await db + .select() + .from(Counter) + .where(eq(Counter.name, input.counterName)) + .limit(1) + const counter = counterResult?.[0] + + if (!counter) { + throw new Error(`Counter ${input.counterName} not found`) + } + + const result = await db + .update(Counter) + .set({ count: counter.count + 1 }) + .where(eq(Counter.name, input.counterName)) + .returning({ count: Counter.count }) + + return result[0] + }, + }), +} diff --git a/src/pages/admin/index.astro b/src/pages/admin/index.astro index c2b63b9..296e279 100644 --- a/src/pages/admin/index.astro +++ b/src/pages/admin/index.astro @@ -1,17 +1,26 @@ --- export const prerender = false // Enable SSR for authentication +import { actions } from 'astro:actions' +import { SITE_TITLE, SITE_DESCRIPTION } from '../../config' +import BaseHead from '../../components/BaseHead.astro' + // Get auth context from middleware const auth = Astro.locals.auth const user = auth?.user + +const { data: initialCounter, error } = await Astro.callAction( + actions.getCounter, + { + counterName: 'adminCounter', + } +) --- - - - Admin Dashboard - Eric Jinks + @@ -106,10 +118,35 @@ const user = auth?.user + { + !error && ( + + ) + } + + {error &&
{error.message}
} + From cb7bf75d388b82545ad145822b8aa9df55b5dd73 Mon Sep 17 00:00:00 2001 From: Eric Jinks <3147296+Jinksi@users.noreply.github.com> Date: Sat, 16 Aug 2025 22:57:33 +1000 Subject: [PATCH 2/3] Add database documentation and npm script - Add comprehensive Astro DB documentation to CLAUDE.md - Document database schema, actions, and development workflow - Add npm run db:push script for schema deployment - Update development commands with database-related scripts --- CLAUDE.md | 45 +++++++++++++++++++++++++++++++++++++++++++-- package.json | 3 ++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2525a1f..42e7a1f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,12 +5,14 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Development Commands - **Dev server**: `npm run dev` (starts Astro dev server on localhost:4321) -- **Build**: `npm run build` (fetches GitHub stars, then generates static site in `dist/`) +- **Dev server (with DB)**: `npm run dev:remote` (starts dev server with remote database connectivity) +- **Build**: `npm run build` (fetches GitHub stars, then generates static site in `dist/` with remote DB) - **Preview**: `npm run preview` (serves built site for testing) - **Tests**: `npm run test:e2e` (runs Playwright E2E tests) - **Single test**: `npx playwright test tests/specific-test.spec.ts` - **Format**: `npx prettier --write .` - **Fetch GitHub stars**: `npm run fetch-stars` (updates cached star data from GitHub REST API) +- **Database push**: `npm run db:push` (pushes local schema changes to remote database) - I'll be running the dev server in the background - To view GitHub dependabot autodetected vulnerabilities, use 'gh api repos/Jinksi/ericjinks.com/dependabot/alerts' @@ -26,6 +28,7 @@ This is a personal blog/portfolio website built with **Astro** as the primary fr ### Tech Stack - **Framework**: Astro 5.x (static site generation with hybrid SSR) +- **Database**: Astro DB (LibSQL/SQLite with local development and remote production) - **UI Libraries**: React 18, Svelte 5, TypeScript - **Content**: Astro Content Collections with Zod schema validation - **Styling**: SCSS with CSS custom properties, dark/light theme support @@ -45,6 +48,10 @@ This is a personal blog/portfolio website built with **Astro** as the primary fr - `react/` subdirectory for React components requiring client-side interactivity - `ml/` subdirectory for TensorFlow.js machine learning demos +- **`src/actions/`**: Astro Actions (server-side functions) + - `index.ts` - Type-safe server actions for database operations + - Exports `getCounter` and `incrementCounter` actions for admin functionality + - **`src/sketches/`**: Canvas-based generative art using canvas-sketch library - Each sketch has corresponding `.ts`/`.tsx` file and MDX content file - Uses Two.js, canvas-sketch, and custom Vector utilities @@ -62,9 +69,13 @@ This is a personal blog/portfolio website built with **Astro** as the primary fr - Fetched via REST API using `scripts/fetch-github-stars.js` - **Authentication**: - `/login/` - Admin login page with form-based authentication - - `/admin/` - Protected admin dashboard (requires authentication) + - `/admin/` - Protected admin dashboard with interactive counter functionality - `/api/logout/` - Logout endpoint supporting both GET and POST requests +- **`db/`**: Database configuration and seeding + - `config.ts` - Astro DB schema definition with Counter table + - `seed.ts` - Database seeding script for initial data + ### Content Management Content is managed through Astro Content Collections with strict TypeScript schemas: @@ -124,6 +135,36 @@ Single-user admin authentication with the following components: - Creates cycling blocks for automated attacks while allowing legitimate use - Prevents brute force attacks and conserves Netlify function invocations +### Database System + +Astro DB integration for persistent data storage: + +- **Database Engine**: LibSQL (SQLite-compatible) with local development and remote production +- **Schema Definition** (`db/config.ts`): + - `Counter` table with `id` (text, primary key), `name` (text, unique), `count` (number, default 0) + - Uses Astro's `defineDb` and `defineTable` for type-safe schema definitions + +- **Data Seeding** (`db/seed.ts`): + - Initialises database with `adminCounter` starting at 0 + - Uses `crypto.randomUUID()` for unique ID generation + +- **Server Actions** (`src/actions/index.ts`): + - Type-safe server-side functions using Astro Actions + - `getCounter`: Retrieves counter by name with error handling + - `incrementCounter`: Atomically increments counter value and returns new count + - Uses Drizzle ORM queries with `eq()` for safe SQL operations + +- **Development Setup**: + - Local SQLite database for development (`npm run dev`) + - Remote database connectivity for production builds (`npm run dev:remote`, `npm run build`) + - Schema changes pushed to remote with `npm run db:push` + - Automatic table creation and seeding on first run + +- **Client Integration**: + - Admin dashboard uses `Astro.callAction()` for server-side data fetching + - Client-side JavaScript calls actions for interactive updates + - Seamless hydration between server and client state + ### Testing Strategy Playwright E2E tests configured to: diff --git a/package.json b/package.json index 46f37d8..ed7d381 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "preview": "astro preview", "astro": "astro", "test:e2e": "playwright test", - "fetch-stars": "node scripts/fetch-github-stars.js" + "fetch-stars": "node scripts/fetch-github-stars.js", + "db:push": "astro db push --remote" }, "dependencies": { "@astrojs/check": "^0.9.4", From 8735fb199d6cf4c8fd78c5114e751a7a3a6fc733 Mon Sep 17 00:00:00 2001 From: Eric Jinks <3147296+Jinksi@users.noreply.github.com> Date: Sat, 16 Aug 2025 23:01:32 +1000 Subject: [PATCH 3/3] Document Turso environment variables and setup - Add ASTRO_DB_REMOTE_URL and ASTRO_DB_APP_TOKEN to environment variables - Add Turso CLI setup commands to README for database creation - Document Remote Database (Turso) section in CLAUDE.md - Provide clear instructions for getting database URL and auth token --- CLAUDE.md | 8 ++++++++ README.md | 17 ++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 42e7a1f..004a8a7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,6 +20,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - The GH_TOKEN is in the `.env` file - Authentication credentials (ADMIN_USERNAME, ADMIN_PASSWORD, ADMIN_SECRET) are in `.env` +- Database credentials (ASTRO_DB_REMOTE_URL, ASTRO_DB_APP_TOKEN) are in `.env` for Turso integration - Uses Astro 5.x `astro:env` system for type-safe environment variable access ## Architecture Overview @@ -160,6 +161,13 @@ Astro DB integration for persistent data storage: - Schema changes pushed to remote with `npm run db:push` - Automatic table creation and seeding on first run +- **Remote Database (Turso)**: + - **Required Environment Variables**: + - `ASTRO_DB_REMOTE_URL`: Connection URL to LibSQL database (e.g., `libsql://your-db.turso.io`) + - `ASTRO_DB_APP_TOKEN`: Authentication token for remote database access + - Setup via [Turso](https://turso.tech/) for production LibSQL hosting + - Compatible with Astro DB's LibSQL adapter for seamless local/remote development + - **Client Integration**: - Admin dashboard uses `Astro.callAction()` for server-side data fetching - Client-side JavaScript calls actions for interactive updates diff --git a/README.md b/README.md index 3767daa..4c1c6ae 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,24 @@ The site includes a secure admin area: ### Environment Variables Required ```bash +# Authentication ADMIN_USERNAME=your-admin-username ADMIN_PASSWORD=your-super-secret-password ADMIN_SECRET=your-32-char-secret-key + +# Database (Turso) +ASTRO_DB_REMOTE_URL=libsql://your-database.turso.io +ASTRO_DB_APP_TOKEN=your-turso-auth-token ``` -Generate secure secret: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"` +**Setup Commands:** + +```bash +# Generate secure authentication secret +node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" + +# Turso database setup (requires Turso CLI) +turso db create your-database-name # Create database +turso db show your-database-name # Get database URL +turso db tokens create your-database-name # Generate auth token +```