From 94fffc569584039b91184432584b6dd27264d07e Mon Sep 17 00:00:00 2001 From: Nandor Magyar Date: Sun, 31 Aug 2025 15:55:44 +0200 Subject: [PATCH 1/3] add healthz, docker fixes, minor readme tweaks --- .env.example | 2 +- Dockerfile | 4 ++++ Makefile | 10 +++++----- README.md | 19 ++++++++++++++----- docker-compose.yml | 3 +-- src/routes/healthz/+server.ts | 16 ++++++++++++++++ 6 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 src/routes/healthz/+server.ts diff --git a/.env.example b/.env.example index c6d7b37..dd31228 100644 --- a/.env.example +++ b/.env.example @@ -5,7 +5,7 @@ POSTGRES_PASSWORD=cactoide_password POSTGRES_PORT=5432 # localhost -DATABASE_URL="postgres://cactoide:cactoide_password@localhost:5432/cactoied_database" +DATABASE_URL="postgres://cactoide:cactoide_password@localhost:5432/cactoide_database" # docker # DATABASE_URL="postgres://cactoide:cactoide_password@postgres:5432/cactoied_database" diff --git a/Dockerfile b/Dockerfile index 1f78765..a44214f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,4 +20,8 @@ EXPOSE 3000 ENV PORT 3000 ENV HOSTNAME "0.0.0.0" + +HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:3000/healthz || exit 1 + CMD [ "node", "build" ] diff --git a/Makefile b/Makefile index 6968741..b811830 100644 --- a/Makefile +++ b/Makefile @@ -19,26 +19,26 @@ help: # Build the Docker images build: @echo "Building Docker images..." - docker-compose build + docker compose build # Start all services up: @echo "Starting all services..." - docker-compose up -d + docker compose up -d # Start only the database db-only: @echo "Starting only the database..." - docker-compose up -d postgres + docker compose up -d postgres # Show logs from all services logs: @echo "Showing logs from all services..." - docker-compose logs -f + docker compose logs -f # Clean up everything (containers, images, volumes) clean: @echo "Cleaning up all Docker resources..." - docker-compose down -v --rmi all + docker compose down -v --rmi all diff --git a/README.md b/README.md index bb4fa41..c465632 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,23 @@ A mobile-first event RSVP platform that lets you create events, share unique URL ### Quick Start +Requirements: git, docker, docker-compose +Uses the [`docker-compose.yml`](docker-compose.yml) file to setup the application with the database. You can define all ENV variables in the [`.env`](.env.example) file from the `.env.example`. + +```bash +git clone https://github.com/polaroi8d/cactoide/ +cd cactoide +cp env.example .env +docker compose up -d +``` + +### Development + +Requirements: git, docker, docker-compose, node at least suggested 20.19.0 + ```bash git clone https://github.com/polaroi8d/cactoide/ cd cactoide -npm install cp env.example .env make db-only npm run dev -- --open @@ -38,10 +51,6 @@ npm run dev -- --open Your app will be available at `http://localhost:5173`. You can use the Makefile commands to run the application or the database, eg.: `make db-only`. -### Self-Host - -Use the [`docker-compose.yml`](docker-compose.yml) file to setup the application with the database. You can define all ENV variables in the [`.env`](.env.example) file from the `.env.example`. - ### License This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details. diff --git a/docker-compose.yml b/docker-compose.yml index fdc7828..7f3ed8d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: # Database postgres: @@ -29,6 +27,7 @@ services: # Application app: image: ghcr.io/polaroi8d/cactoide/cactoide:${APP_VERSION:-latest} + build: . container_name: cactoide-app ports: - '${PORT:-5111}:3000' diff --git a/src/routes/healthz/+server.ts b/src/routes/healthz/+server.ts new file mode 100644 index 0000000..4e8ccce --- /dev/null +++ b/src/routes/healthz/+server.ts @@ -0,0 +1,16 @@ +// src/routes/healthz/+server.ts +import { json } from '@sveltejs/kit'; +import { drizzleQuery } from '$lib/database/db'; +import { sql } from 'drizzle-orm'; + +export async function GET() { + try { + await drizzleQuery.execute(sql`select 1`); + return json({ ok: true }, { headers: { 'cache-control': 'no-store' } }); + } catch (err) { + return json( + { ok: false, error: (err as Error)?.message ?? 'DB unavailable' }, + { status: 503, headers: { 'cache-control': 'no-store' } } + ); + } +} From 8a76421571373c61e613b15baabd71c92284f0ba Mon Sep 17 00:00:00 2001 From: Levente Orban Date: Mon, 1 Sep 2025 10:43:02 +0200 Subject: [PATCH 2/3] fix: small adjusments, renames for the /healthz and readme --- .env.example | 2 +- Makefile | 11 ++++++++--- README.md | 9 ++++++--- docker-compose.yml | 4 +++- src/lib/database/db.ts | 11 +++++++++-- src/routes/create/+page.server.ts | 4 ++-- src/routes/discover/+page.server.ts | 4 ++-- src/routes/event/+page.server.ts | 8 ++++---- src/routes/event/[id]/+page.server.ts | 26 ++++++++------------------ src/routes/healthz/+server.ts | 6 +++--- svelte.config.js | 1 - 11 files changed, 46 insertions(+), 40 deletions(-) diff --git a/.env.example b/.env.example index dd31228..9d94792 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,5 @@ # Postgres configuration -POSTGRES_DB=cactoied_database +POSTGRES_DB=cactoide_database POSTGRES_USER=cactoide POSTGRES_PASSWORD=cactoide_password POSTGRES_PORT=5432 diff --git a/Makefile b/Makefile index b811830..6f98d8f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help build up db-only logs clean +.PHONY: help build up db-only logs db-clean prune # Default target help: @@ -13,7 +13,8 @@ help: @echo "" @echo "Utility commands:" @echo " make logs - Show logs from all services" - @echo " make clean - Remove all containers, images, and volumes" + @echo " make db-clean - Stop & remove database container" + @echo " make prune - Remove all containers, images, and volumes" @echo " make help - Show this help message" # Build the Docker images @@ -36,8 +37,12 @@ logs: @echo "Showing logs from all services..." docker compose logs -f +db-clean: + @echo "Cleaning up all Docker resources..." + docker stop cactoide-db && docker rm cactoide-db && docker volume prune -f && docker network prune -f + # Clean up everything (containers, images, volumes) -clean: +prune: @echo "Cleaning up all Docker resources..." docker compose down -v --rmi all diff --git a/README.md b/README.md index c465632..776b9c6 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,10 @@ A mobile-first event RSVP platform that lets you create events, share unique URL ### Quick Start -Requirements: git, docker, docker-compose +#### Requirements + +`git, docker, docker-compose, node at least suggested 20.19.0` + Uses the [`docker-compose.yml`](docker-compose.yml) file to setup the application with the database. You can define all ENV variables in the [`.env`](.env.example) file from the `.env.example`. ```bash @@ -39,8 +42,6 @@ docker compose up -d ### Development -Requirements: git, docker, docker-compose, node at least suggested 20.19.0 - ```bash git clone https://github.com/polaroi8d/cactoide/ cd cactoide @@ -51,6 +52,8 @@ npm run dev -- --open Your app will be available at `http://localhost:5173`. You can use the Makefile commands to run the application or the database, eg.: `make db-only`. +Use the `database/seed.sql` if you want to populate your database with dummy data. + ### License This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details. diff --git a/docker-compose.yml b/docker-compose.yml index 7f3ed8d..b991904 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,9 @@ services: POSTGRES_USER: ${POSTGRES_USER:-cactoide} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-cactoide_password} expose: - - '${POSTGRES_PORT:-5437}' + - '${POSTGRES_PORT:-5432}' + ports: + - '${POSTGRES_PORT:-5432}:5432' volumes: - postgres_data:/var/lib/postgresql/data - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql diff --git a/src/lib/database/db.ts b/src/lib/database/db.ts index d594bb9..71b6bdc 100644 --- a/src/lib/database/db.ts +++ b/src/lib/database/db.ts @@ -3,6 +3,13 @@ import { env } from '$env/dynamic/private'; import * as schema from './schema'; import postgres from 'postgres'; -const client = postgres(env.DATABASE_URL, {}); +// Database connection configuration +const connectionConfig = { + max: 10, // Maximum number of connections + idle_timeout: 20, // Close idle connections after 20 seconds + connect_timeout: 10 // Connection timeout in seconds +}; -export const drizzleQuery = drizzle(client, { schema }); +const client = postgres(env.DATABASE_URL, connectionConfig); + +export const database = drizzle(client, { schema }); diff --git a/src/routes/create/+page.server.ts b/src/routes/create/+page.server.ts index e7d6392..55385dc 100644 --- a/src/routes/create/+page.server.ts +++ b/src/routes/create/+page.server.ts @@ -1,4 +1,4 @@ -import { drizzleQuery } from '$lib/database/db'; +import { database } from '$lib/database/db'; import { events } from '$lib/database/schema'; import { fail, redirect } from '@sveltejs/kit'; import type { Actions } from './$types'; @@ -66,7 +66,7 @@ export const actions: Actions = { const eventId = generateEventId(); - await drizzleQuery + await database .insert(events) .values({ id: eventId, diff --git a/src/routes/discover/+page.server.ts b/src/routes/discover/+page.server.ts index a9794e6..2fdffde 100644 --- a/src/routes/discover/+page.server.ts +++ b/src/routes/discover/+page.server.ts @@ -1,4 +1,4 @@ -import { drizzleQuery } from '$lib/database/db'; +import { database } from '$lib/database/db'; import { eq, desc } from 'drizzle-orm'; import type { PageServerLoad } from './$types'; import { events } from '$lib/database/schema'; @@ -6,7 +6,7 @@ import { events } from '$lib/database/schema'; export const load: PageServerLoad = async () => { try { // Fetch all public events ordered by creation date (newest first) - const publicEvents = await drizzleQuery + const publicEvents = await database .select() .from(events) .where(eq(events.visibility, 'public')) diff --git a/src/routes/event/+page.server.ts b/src/routes/event/+page.server.ts index d7909c7..704f6d3 100644 --- a/src/routes/event/+page.server.ts +++ b/src/routes/event/+page.server.ts @@ -1,4 +1,4 @@ -import { drizzleQuery } from '$lib/database/db'; +import { database } from '$lib/database/db'; import { events } from '$lib/database/schema'; import { fail } from '@sveltejs/kit'; import { eq, desc } from 'drizzle-orm'; @@ -11,7 +11,7 @@ export const load = async ({ cookies }) => { } try { - const userEvents = await drizzleQuery + const userEvents = await database .select() .from(events) .where(eq(events.userId, userId)) @@ -50,7 +50,7 @@ export const actions: Actions = { try { // First verify the user owns this event - const [eventData] = await drizzleQuery.select().from(events).where(eq(events.id, eventId)); + const [eventData] = await database.select().from(events).where(eq(events.id, eventId)); if (!eventData) { return fail(404, { error: 'Event not found' }); @@ -61,7 +61,7 @@ export const actions: Actions = { } // Delete the event (RSVPs will be deleted automatically due to CASCADE) - await drizzleQuery.delete(events).where(eq(events.id, eventId)); + await database.delete(events).where(eq(events.id, eventId)); return { success: true }; } catch (error) { diff --git a/src/routes/event/[id]/+page.server.ts b/src/routes/event/[id]/+page.server.ts index 35be6ae..06149ae 100644 --- a/src/routes/event/[id]/+page.server.ts +++ b/src/routes/event/[id]/+page.server.ts @@ -1,4 +1,4 @@ -import { drizzleQuery } from '$lib/database/db'; +import { database } from '$lib/database/db'; import { events, rsvps } from '$lib/database/schema'; import { eq, asc } from 'drizzle-orm'; import { error, fail } from '@sveltejs/kit'; @@ -14,12 +14,8 @@ export const load: PageServerLoad = async ({ params, cookies }) => { try { // Fetch event and RSVPs in parallel const [eventData, rsvpData] = await Promise.all([ - drizzleQuery.select().from(events).where(eq(events.id, eventId)).limit(1), - drizzleQuery - .select() - .from(rsvps) - .where(eq(rsvps.eventId, eventId)) - .orderBy(asc(rsvps.createdAt)) + database.select().from(events).where(eq(events.id, eventId)).limit(1), + database.select().from(rsvps).where(eq(rsvps.eventId, eventId)).orderBy(asc(rsvps.createdAt)) ]); if (!eventData[0]) { @@ -81,33 +77,27 @@ export const actions: Actions = { try { // Check if event exists and get its details - const [eventData] = await drizzleQuery.select().from(events).where(eq(events.id, eventId)); + const [eventData] = await database.select().from(events).where(eq(events.id, eventId)); if (!eventData) { return fail(404, { error: 'Event not found' }); } // Check if event is full (for limited type events) if (eventData.type === 'limited' && eventData.attendeeLimit) { - const currentRSVPs = await drizzleQuery - .select() - .from(rsvps) - .where(eq(rsvps.eventId, eventId)); + const currentRSVPs = await database.select().from(rsvps).where(eq(rsvps.eventId, eventId)); if (currentRSVPs.length >= eventData.attendeeLimit) { return fail(400, { error: 'Event is full' }); } } // Check if name is already in the list - const existingRSVPs = await drizzleQuery - .select() - .from(rsvps) - .where(eq(rsvps.eventId, eventId)); + const existingRSVPs = await database.select().from(rsvps).where(eq(rsvps.eventId, eventId)); if (existingRSVPs.some((rsvp) => rsvp.name.toLowerCase() === name.toLowerCase())) { return fail(400, { error: 'Name already exists for this event' }); } // Add RSVP to database - await drizzleQuery.insert(rsvps).values({ + await database.insert(rsvps).values({ eventId: eventId, name: name.trim(), userId: userId, @@ -131,7 +121,7 @@ export const actions: Actions = { } try { - await drizzleQuery.delete(rsvps).where(eq(rsvps.id, rsvpId)); + await database.delete(rsvps).where(eq(rsvps.id, rsvpId)); return { success: true, type: 'remove' }; } catch (err) { console.error('Error removing RSVP:', err); diff --git a/src/routes/healthz/+server.ts b/src/routes/healthz/+server.ts index 4e8ccce..ea1e6a0 100644 --- a/src/routes/healthz/+server.ts +++ b/src/routes/healthz/+server.ts @@ -1,15 +1,15 @@ // src/routes/healthz/+server.ts import { json } from '@sveltejs/kit'; -import { drizzleQuery } from '$lib/database/db'; +import { database } from '$lib/database/db'; import { sql } from 'drizzle-orm'; export async function GET() { try { - await drizzleQuery.execute(sql`select 1`); + await database.execute(sql`select 1`); return json({ ok: true }, { headers: { 'cache-control': 'no-store' } }); } catch (err) { return json( - { ok: false, error: (err as Error)?.message ?? 'DB unavailable' }, + { ok: false, error: (err as Error)?.message, message: 'Database unreachable.' }, { status: 503, headers: { 'cache-control': 'no-store' } } ); } diff --git a/svelte.config.js b/svelte.config.js index 4263c19..59e29ff 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -8,7 +8,6 @@ const config = { preprocess: vitePreprocess(), kit: { - // Using Netlify adapter for deployment adapter: adapter({ // if you want to use 'split' mode, set this to 'split' // and create a _redirects file with the redirects you want From 5c178d8a791eca998a2ae76ca7fd926fc66e01a5 Mon Sep 17 00:00:00 2001 From: Levente Orban Date: Mon, 1 Sep 2025 11:01:38 +0200 Subject: [PATCH 3/3] fix: small adjusments, renames for the /healthz and readme --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 9d94792..d5add05 100644 --- a/.env.example +++ b/.env.example @@ -7,7 +7,7 @@ POSTGRES_PORT=5432 # localhost DATABASE_URL="postgres://cactoide:cactoide_password@localhost:5432/cactoide_database" # docker -# DATABASE_URL="postgres://cactoide:cactoide_password@postgres:5432/cactoied_database" +# DATABASE_URL="postgres://cactoide:cactoide_password@postgres:5432/cactoide_database" # Application configuration APP_VERSION=latest