From fe98df963f3b6af30c14e848ca1915a367d1d894 Mon Sep 17 00:00:00 2001 From: Script Raccoon Date: Tue, 7 Apr 2026 00:19:40 +0200 Subject: [PATCH 1/2] create separate db for tracking page visits --- .env.example | 12 +- .github/workflows/_deploy-reusable.yaml | 6 + .github/workflows/deploy-preview-skip-db.yaml | 2 + .github/workflows/deploy-preview.yaml | 2 + .github/workflows/deploy-prod-skip-db.yaml | 2 + .github/workflows/deploy-prod.yaml | 2 + database/migrations/014_drop-visits.sql | 1 + src/lib/server/db.ts | 12 +- src/routes/api/track/+server.ts | 18 +-- src/routes/stats/+page.server.ts | 127 +++++++++--------- 10 files changed, 107 insertions(+), 77 deletions(-) create mode 100644 database/migrations/014_drop-visits.sql diff --git a/.env.example b/.env.example index 72b2d18d..e956e4fa 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,7 @@ -DB_URL = file:database/local.db # location of local database -DB_AUTH_TOKEN = # you can leave this empty for local development -DB_HTTP_URL = # you can leave this empty for local development -DB_AUTH_TOKEN_HTTP = # you can leave this empty for local development -INTERNAL_API_KEY = # you can leave this empty for local development \ No newline at end of file +DB_URL = file:database/local.db # location of local database +DB_AUTH_TOKEN = # you can leave this empty for local development +DB_HTTP_URL = # you can leave this empty for local development +DB_AUTH_TOKEN_HTTP = # you can leave this empty for local development +INTERNAL_API_KEY = # you can leave this empty for local development +DB_VISITS_AUTH_TOKEN = # you can leave this empty for local development +DB_VISITS_URL = file:database/visits.db # location of local database with site visits \ No newline at end of file diff --git a/.github/workflows/_deploy-reusable.yaml b/.github/workflows/_deploy-reusable.yaml index e9700499..417c5860 100644 --- a/.github/workflows/_deploy-reusable.yaml +++ b/.github/workflows/_deploy-reusable.yaml @@ -24,6 +24,10 @@ on: required: true NETLIFY_ACCESS_TOKEN: required: true + DB_VISITS_URL: + required: true + DB_VISITS_AUTH_TOKEN: + required: true jobs: build-and-deploy: @@ -67,6 +71,8 @@ jobs: DB_HTTP_URL: ${{ secrets.DB_HTTP_URL }} DB_AUTH_TOKEN_HTTP: ${{ secrets.DB_AUTH_TOKEN_HTTP }} INTERNAL_API_KEY: ${{ secrets.INTERNAL_API_KEY }} + DB_VISITS_URL: ${{ secrets.DB_VISITS_URL }} + DB_VISITS_AUTH_TOKEN: ${{ secrets.DB_VISITS_AUTH_TOKEN }} run: pnpm build - name: Get latest commit message diff --git a/.github/workflows/deploy-preview-skip-db.yaml b/.github/workflows/deploy-preview-skip-db.yaml index 910ea803..089042c5 100644 --- a/.github/workflows/deploy-preview-skip-db.yaml +++ b/.github/workflows/deploy-preview-skip-db.yaml @@ -18,3 +18,5 @@ jobs: INTERNAL_API_KEY: ${{ secrets.INTERNAL_API_KEY }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} NETLIFY_ACCESS_TOKEN: ${{ secrets.NETLIFY_ACCESS_TOKEN }} + DB_VISITS_URL: ${{ secrets.DB_VISITS_URL }} + DB_VISITS_AUTH_TOKEN: ${{ secrets.DB_VISITS_AUTH_TOKEN }} diff --git a/.github/workflows/deploy-preview.yaml b/.github/workflows/deploy-preview.yaml index 66e2cd41..dd58f46c 100644 --- a/.github/workflows/deploy-preview.yaml +++ b/.github/workflows/deploy-preview.yaml @@ -18,3 +18,5 @@ jobs: INTERNAL_API_KEY: ${{ secrets.INTERNAL_API_KEY }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} NETLIFY_ACCESS_TOKEN: ${{ secrets.NETLIFY_ACCESS_TOKEN }} + DB_VISITS_URL: ${{ secrets.DB_VISITS_URL }} + DB_VISITS_AUTH_TOKEN: ${{ secrets.DB_VISITS_AUTH_TOKEN }} diff --git a/.github/workflows/deploy-prod-skip-db.yaml b/.github/workflows/deploy-prod-skip-db.yaml index 00ebb61d..138efe16 100644 --- a/.github/workflows/deploy-prod-skip-db.yaml +++ b/.github/workflows/deploy-prod-skip-db.yaml @@ -18,3 +18,5 @@ jobs: INTERNAL_API_KEY: ${{ secrets.INTERNAL_API_KEY }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} NETLIFY_ACCESS_TOKEN: ${{ secrets.NETLIFY_ACCESS_TOKEN }} + DB_VISITS_URL: ${{ secrets.DB_VISITS_URL }} + DB_VISITS_AUTH_TOKEN: ${{ secrets.DB_VISITS_AUTH_TOKEN }} diff --git a/.github/workflows/deploy-prod.yaml b/.github/workflows/deploy-prod.yaml index 255907c1..e3d8da3a 100644 --- a/.github/workflows/deploy-prod.yaml +++ b/.github/workflows/deploy-prod.yaml @@ -18,3 +18,5 @@ jobs: INTERNAL_API_KEY: ${{ secrets.INTERNAL_API_KEY }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} NETLIFY_ACCESS_TOKEN: ${{ secrets.NETLIFY_ACCESS_TOKEN }} + DB_VISITS_URL: ${{ secrets.DB_VISITS_URL }} + DB_VISITS_AUTH_TOKEN: ${{ secrets.DB_VISITS_AUTH_TOKEN }} diff --git a/database/migrations/014_drop-visits.sql b/database/migrations/014_drop-visits.sql new file mode 100644 index 00000000..91afe8b9 --- /dev/null +++ b/database/migrations/014_drop-visits.sql @@ -0,0 +1 @@ +DROP TABLE visits; -- will record these in a separate db \ No newline at end of file diff --git a/src/lib/server/db.ts b/src/lib/server/db.ts index 71e49e98..364009d1 100644 --- a/src/lib/server/db.ts +++ b/src/lib/server/db.ts @@ -1,4 +1,9 @@ -import { DB_AUTH_TOKEN, DB_URL } from '$env/static/private' +import { + DB_AUTH_TOKEN, + DB_URL, + DB_VISITS_AUTH_TOKEN, + DB_VISITS_URL, +} from '$env/static/private' import { createClient, type LibsqlError } from '@libsql/client' import type { Arrayed } from '$lib/commons/types' @@ -41,3 +46,8 @@ export async function batch(queries: { sql: string; values: any return { results: null, err: err as LibsqlError } } } + +export const db_visits = createClient({ + url: DB_VISITS_URL, + authToken: DB_VISITS_AUTH_TOKEN, +}) diff --git a/src/routes/api/track/+server.ts b/src/routes/api/track/+server.ts index 6bfe0cd4..83b6a410 100644 --- a/src/routes/api/track/+server.ts +++ b/src/routes/api/track/+server.ts @@ -1,8 +1,7 @@ -import { query } from '$lib/server/db' import { is_object } from '$lib/server/utils' import { json } from '@sveltejs/kit' -import sql from 'sql-template-tag' import { get_geo_data, is_allowed } from './track.utils' +import { db_visits } from '$lib/server/db' type ValidBody = { device_type: string; theme: string } @@ -30,12 +29,15 @@ export const POST = async (event) => { const { country } = get_geo_data(event.request) - const { err } = await query(sql` - INSERT INTO visits (theme, device_type, country) - VALUES (${theme}, ${device_type}, ${country}) - `) - - if (err) return json({ error: 'Database error' }, { status: 500 }) + try { + await db_visits.execute( + `INSERT INTO visits (theme, device_type, country) VALUES (?, ?, ?)`, + [theme, device_type, country], + ) + } catch (err) { + console.error(err) + return json({ error: 'Database error' }, { status: 500 }) + } return json({ message: 'Visit has been tracked' }) } diff --git a/src/routes/stats/+page.server.ts b/src/routes/stats/+page.server.ts index 1565d5b3..1e878ed3 100644 --- a/src/routes/stats/+page.server.ts +++ b/src/routes/stats/+page.server.ts @@ -1,99 +1,100 @@ -import { batch } from '$lib/server/db' +import { db_visits } from '$lib/server/db' import { error } from '@sveltejs/kit' -import sql from 'sql-template-tag' export const prerender = false const COUNTRY_COUNT_THRESHOLD = 10 export const load = async () => { - const { results, err } = await batch< - [ - { - start: string - total: number - total_last_day: number - total_last_week: number - total_last_month: number - }, - { day: string; count: number }, - { country: string; count: number }, - { theme: string; count: number }, - { device_type: string; count: number }, - ] - >([ - sql` - SELECT + try { + const results = await db_visits.batch([ + `SELECT COALESCE(MIN(created_at), '') AS start, COUNT(*) AS total, COUNT(CASE WHEN created_at >= datetime('now', '-1 day') THEN 1 END) AS total_last_day, COUNT(CASE WHEN created_at >= datetime('now', '-7 days') THEN 1 END) AS total_last_week, COUNT(CASE WHEN created_at >= datetime('now', '-1 month') THEN 1 END) AS total_last_month - FROM visits; - `, - sql` - SELECT + FROM visits`, + `SELECT date(created_at) AS day, COUNT(*) AS count FROM visits GROUP BY day - ORDER BY day; - `, - sql` - SELECT + ORDER BY day`, + `SELECT country, COUNT(*) as count FROM visits GROUP BY country - ORDER BY count DESC; - `, - sql` - SELECT + ORDER BY count DESC`, + `SELECT theme, COUNT(*) as count FROM visits GROUP BY theme - ORDER BY theme; - `, - sql` - SELECT + ORDER BY theme`, + `SELECT device_type, COUNT(*) as count FROM visits GROUP BY device_type - ORDER BY count DESC; - `, - ]) + ORDER BY count DESC`, + ]) - if (err) error(500, 'Failed to load data') + const summary = results[0].rows[0] as unknown as { + start: string + total: number + total_last_day: number + total_last_week: number + total_last_month: number + } - const [ - [{ total, start, total_last_day, total_last_week, total_last_month }], - daily_visits, - detailed_country_stats, - theme_stats, - device_stats, - ] = results + const { start, total, total_last_day, total_last_week, total_last_month } = + summary - const country_stats: typeof detailed_country_stats = [] - let other_count = 0 + const daily_visits = results[1].rows as unknown as { + day: string + count: number + }[] - for (const { country, count } of detailed_country_stats) { - if (count <= COUNTRY_COUNT_THRESHOLD) other_count += count - else country_stats.push({ country, count }) - } + const detailed_country_stats = results[2].rows as unknown as { + country: string + count: number + }[] + + const theme_stats = results[3].rows as unknown as { + theme: string + count: number + }[] + + const device_stats = results[4].rows as unknown as { + device_type: string + count: number + }[] + + const country_stats: typeof detailed_country_stats = [] + let other_count = 0 + + for (const { country, count } of detailed_country_stats) { + if (count <= COUNTRY_COUNT_THRESHOLD) other_count += count + else country_stats.push({ country, count }) + } - country_stats.push({ country: 'Others', count: other_count }) + country_stats.push({ country: 'Others', count: other_count }) - return { - start, - total, - total_last_day, - total_last_week, - total_last_month, - daily_visits, - country_stats, - theme_stats, - device_stats, + return { + start, + total, + total_last_day, + total_last_week, + total_last_month, + daily_visits, + country_stats, + theme_stats, + device_stats, + } + } catch (err) { + console.error(err) + error(500, 'Failed to load data') } } From 3cf749e967cf5dbc7cd4b557ec21cd00cf0b06e9 Mon Sep 17 00:00:00 2001 From: Script Raccoon Date: Tue, 7 Apr 2026 00:29:32 +0200 Subject: [PATCH 2/2] remove backup from here --- .env.example | 3 --- .github/workflows/_deploy-reusable.yaml | 9 ------- .github/workflows/deploy-preview-skip-db.yaml | 3 --- .github/workflows/deploy-preview.yaml | 3 --- .github/workflows/deploy-prod-skip-db.yaml | 3 --- .github/workflows/deploy-prod.yaml | 3 --- src/routes/api/db-dump/+server.ts | 25 ------------------- 7 files changed, 49 deletions(-) delete mode 100644 src/routes/api/db-dump/+server.ts diff --git a/.env.example b/.env.example index e956e4fa..c63a11d0 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,4 @@ DB_URL = file:database/local.db # location of local database DB_AUTH_TOKEN = # you can leave this empty for local development -DB_HTTP_URL = # you can leave this empty for local development -DB_AUTH_TOKEN_HTTP = # you can leave this empty for local development -INTERNAL_API_KEY = # you can leave this empty for local development DB_VISITS_AUTH_TOKEN = # you can leave this empty for local development DB_VISITS_URL = file:database/visits.db # location of local database with site visits \ No newline at end of file diff --git a/.github/workflows/_deploy-reusable.yaml b/.github/workflows/_deploy-reusable.yaml index 417c5860..b2d33caf 100644 --- a/.github/workflows/_deploy-reusable.yaml +++ b/.github/workflows/_deploy-reusable.yaml @@ -14,12 +14,6 @@ on: required: true DB_AUTH_TOKEN: required: true - DB_HTTP_URL: - required: true - DB_AUTH_TOKEN_HTTP: - required: true - INTERNAL_API_KEY: - required: true NETLIFY_SITE_ID: required: true NETLIFY_ACCESS_TOKEN: @@ -68,9 +62,6 @@ jobs: env: DB_URL: ${{ secrets.DB_URL }} DB_AUTH_TOKEN: ${{ secrets.DB_AUTH_TOKEN }} - DB_HTTP_URL: ${{ secrets.DB_HTTP_URL }} - DB_AUTH_TOKEN_HTTP: ${{ secrets.DB_AUTH_TOKEN_HTTP }} - INTERNAL_API_KEY: ${{ secrets.INTERNAL_API_KEY }} DB_VISITS_URL: ${{ secrets.DB_VISITS_URL }} DB_VISITS_AUTH_TOKEN: ${{ secrets.DB_VISITS_AUTH_TOKEN }} run: pnpm build diff --git a/.github/workflows/deploy-preview-skip-db.yaml b/.github/workflows/deploy-preview-skip-db.yaml index 089042c5..10082cc6 100644 --- a/.github/workflows/deploy-preview-skip-db.yaml +++ b/.github/workflows/deploy-preview-skip-db.yaml @@ -13,9 +13,6 @@ jobs: secrets: DB_URL: ${{ secrets.DB_PREVIEW_URL }} DB_AUTH_TOKEN: ${{ secrets.DB_PREVIEW_AUTH_TOKEN }} - DB_HTTP_URL: ${{ secrets.DB_PREVIEW_HTTP_URL }} - DB_AUTH_TOKEN_HTTP: ${{ secrets.DB_PREVIEW_AUTH_TOKEN_HTTP }} - INTERNAL_API_KEY: ${{ secrets.INTERNAL_API_KEY }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} NETLIFY_ACCESS_TOKEN: ${{ secrets.NETLIFY_ACCESS_TOKEN }} DB_VISITS_URL: ${{ secrets.DB_VISITS_URL }} diff --git a/.github/workflows/deploy-preview.yaml b/.github/workflows/deploy-preview.yaml index dd58f46c..d8c4f94b 100644 --- a/.github/workflows/deploy-preview.yaml +++ b/.github/workflows/deploy-preview.yaml @@ -13,9 +13,6 @@ jobs: secrets: DB_URL: ${{ secrets.DB_PREVIEW_URL }} DB_AUTH_TOKEN: ${{ secrets.DB_PREVIEW_AUTH_TOKEN }} - DB_HTTP_URL: ${{ secrets.DB_PREVIEW_HTTP_URL }} - DB_AUTH_TOKEN_HTTP: ${{ secrets.DB_PREVIEW_AUTH_TOKEN_HTTP }} - INTERNAL_API_KEY: ${{ secrets.INTERNAL_API_KEY }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} NETLIFY_ACCESS_TOKEN: ${{ secrets.NETLIFY_ACCESS_TOKEN }} DB_VISITS_URL: ${{ secrets.DB_VISITS_URL }} diff --git a/.github/workflows/deploy-prod-skip-db.yaml b/.github/workflows/deploy-prod-skip-db.yaml index 138efe16..0c4cdcdc 100644 --- a/.github/workflows/deploy-prod-skip-db.yaml +++ b/.github/workflows/deploy-prod-skip-db.yaml @@ -13,9 +13,6 @@ jobs: secrets: DB_URL: ${{ secrets.DB_URL }} DB_AUTH_TOKEN: ${{ secrets.DB_AUTH_TOKEN }} - DB_HTTP_URL: ${{ secrets.DB_HTTP_URL }} - DB_AUTH_TOKEN_HTTP: ${{ secrets.DB_AUTH_TOKEN_HTTP }} - INTERNAL_API_KEY: ${{ secrets.INTERNAL_API_KEY }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} NETLIFY_ACCESS_TOKEN: ${{ secrets.NETLIFY_ACCESS_TOKEN }} DB_VISITS_URL: ${{ secrets.DB_VISITS_URL }} diff --git a/.github/workflows/deploy-prod.yaml b/.github/workflows/deploy-prod.yaml index e3d8da3a..7702369f 100644 --- a/.github/workflows/deploy-prod.yaml +++ b/.github/workflows/deploy-prod.yaml @@ -13,9 +13,6 @@ jobs: secrets: DB_URL: ${{ secrets.DB_URL }} DB_AUTH_TOKEN: ${{ secrets.DB_AUTH_TOKEN }} - DB_HTTP_URL: ${{ secrets.DB_HTTP_URL }} - DB_AUTH_TOKEN_HTTP: ${{ secrets.DB_AUTH_TOKEN_HTTP }} - INTERNAL_API_KEY: ${{ secrets.INTERNAL_API_KEY }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} NETLIFY_ACCESS_TOKEN: ${{ secrets.NETLIFY_ACCESS_TOKEN }} DB_VISITS_URL: ${{ secrets.DB_VISITS_URL }} diff --git a/src/routes/api/db-dump/+server.ts b/src/routes/api/db-dump/+server.ts deleted file mode 100644 index ab609ecb..00000000 --- a/src/routes/api/db-dump/+server.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { DB_AUTH_TOKEN_HTTP, DB_HTTP_URL, INTERNAL_API_KEY } from '$env/static/private' -import { json } from '@sveltejs/kit' - -export const GET = async (event) => { - const auth = event.request.headers.get('x-api-key') - if (auth !== INTERNAL_API_KEY) { - return new Response('Unauthorized', { status: 401 }) - } - - const res = await fetch(`${DB_HTTP_URL}/dump`, { - headers: { - Authorization: `Bearer ${DB_AUTH_TOKEN_HTTP}`, - }, - }) - - if (!res.ok) { - return json({ error: 'Bad Gateway' }, { status: 501 }) - } - - return new Response(res.body, { - headers: { - 'content-type': 'application/sql', - }, - }) -}