From 93b0bac48ab13240a8c9951745da275e01252f6e Mon Sep 17 00:00:00 2001 From: Levente Orban Date: Sun, 26 Oct 2025 16:47:51 +0100 Subject: [PATCH] feat: invite only events --- Makefile | 109 ++++++++++-------- database/init.sql | 20 ---- src/lib/i18n/it.json | 7 +- src/lib/i18n/messages.json | 1 - src/routes/event/[id]/+page.svelte | 20 ++-- src/routes/event/[id]/edit/+page.server.ts | 3 +- src/routes/event/[id]/edit/+page.svelte | 27 +++-- .../event/[id]/invite/[token]/+page.svelte | 8 +- 8 files changed, 103 insertions(+), 92 deletions(-) diff --git a/Makefile b/Makefile index 5284be6..37879b8 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,4 @@ -# Cactoide Makefile -# Database and application management commands - -.PHONY: help migrate-up migrate-down db-reset dev build test - -# Default target -help: - @echo "Available commands:" - @echo " migrate-up - Apply invite-only events migration" - @echo " migrate-down - Rollback invite-only events migration" - @echo " db-reset - Reset database to initial state" - @echo " dev - Start development server" - @echo " build - Build the application" - @echo " test - Run tests" +.PHONY: help build up down db-only logs db-clean prune i18n lint format migrate-up migrate-down # Database connection variables DB_HOST ?= localhost @@ -26,6 +13,21 @@ MIGRATIONS_DIR = database/migrations # Database connection string DB_URL = postgresql://$(DB_USER):$(DB_PASSWORD)@$(DB_HOST):$(DB_PORT)/$(DB_NAME) +help: + @echo "Available commands:" + @echo " build - Docker build the application" + @echo " up - Start all services" + @echo " down - Stop all services" + @echo " db-only - Start only the database" + @echo " logs - Show logs from all services" + @echo " db-clean - Clean up all Docker resources" + @echo " prune - Clean up everything (containers, images, volumes)" + @echo " i18n - Validate translation files" + @echo " lint - Lint the project" + @echo " format - Format the project" + @echo " migrate-up - Apply invite-only events migration" + @echo " migrate-down - Rollback invite-only events migration" + # Apply invite-only events migration migrate-up: @echo "Applying invite-only events migration..." @@ -48,47 +50,52 @@ migrate-down: exit 1; \ fi -# Reset database to initial state -db-reset: - @echo "Resetting database..." - @psql "$(DB_URL)" -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public; GRANT ALL ON SCHEMA public TO postgres; GRANT ALL ON SCHEMA public TO public;" - @psql "$(DB_URL)" -f database/init.sql - @echo "Database reset complete!" - -# Development server -dev: - @echo "Starting development server..." - npm run dev - -# Build application +# Build the Docker images build: - @echo "Building application..." - npm run build + @echo "Building Docker images..." + docker compose build -# Run tests -test: - @echo "Running tests..." - npm run test +# Start all services +up: + @echo "Starting all services..." + docker compose up -d -# Install dependencies -install: - @echo "Installing dependencies..." - npm install +down: + @echo "Stopping all services..." + docker compose down -# Docker commands -docker-build: - @echo "Building Docker image..." - docker build -t cactoide . +db-clean: + @echo "Cleaning up all Docker resources..." + docker stop cactoide-db && docker rm cactoide-db && docker volume prune -f && docker network prune -f -docker-run: - @echo "Running Docker container..." - docker run -p 3000:3000 cactoide +# Start only the database +db-only: + @echo "Starting only the database..." + docker compose up -d postgres -# Database setup for development -db-setup: install db-reset migrate-up - @echo "Database setup complete!" +# Show logs from all services +logs: + @echo "Showing logs from all services..." + docker compose logs -f -# Full development setup -setup: install db-setup - @echo "Development environment ready!" - @echo "Run 'make dev' to start the development server" \ No newline at end of file + + +# Clean up everything (containers, images, volumes) +prune: + @echo "Cleaning up all Docker resources..." + docker compose down -v --rmi all + + +lint: + @echo "Linting the project..." + npm run lint + +format: + @echo "Formatting the project..." + npm run format + +#TODO: not working yet +i18n: + @echo "Validating translation files..." + @if [ -n "$(FILE)" ]; then \ + ./scripts/i18n-check.sh $(FILE); \ diff --git a/database/init.sql b/database/init.sql index 1ddc30e..a65afd6 100644 --- a/database/init.sql +++ b/database/init.sql @@ -42,25 +42,5 @@ CREATE INDEX IF NOT EXISTS idx_events_date ON events(date); CREATE INDEX IF NOT EXISTS idx_events_location_type ON events(location_type); CREATE INDEX IF NOT EXISTS idx_rsvps_event_id ON rsvps(event_id); CREATE INDEX IF NOT EXISTS idx_rsvps_user_id ON rsvps(user_id); -CREATE INDEX IF NOT EXISTS idx_invite_tokens_event_id ON invite_tokens(event_id); -CREATE INDEX IF NOT EXISTS idx_invite_tokens_token ON invite_tokens(token); -CREATE INDEX IF NOT EXISTS idx_invite_tokens_expires_at ON invite_tokens(expires_at); - --- ======================================= --- Triggers (updated_at maintenance) --- ======================================= -CREATE OR REPLACE FUNCTION update_updated_at_column() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = NOW(); - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -DROP TRIGGER IF EXISTS update_events_updated_at ON events; -CREATE TRIGGER update_events_updated_at - BEFORE UPDATE ON events - FOR EACH ROW - EXECUTE FUNCTION update_updated_at_column(); COMMIT; \ No newline at end of file diff --git a/src/lib/i18n/it.json b/src/lib/i18n/it.json index cfba74f..06006f0 100644 --- a/src/lib/i18n/it.json +++ b/src/lib/i18n/it.json @@ -41,7 +41,6 @@ "numberOfGuests": "Numero di Ospiti", "addGuests": "Aggiungi ospiti", "joinEvent": "Partecipa all'Evento", - "copyLink": "Copia Link", "addToCalendar": "Aggiungi al Calendario", "close": "Chiudi", "closeModal": "Chiudi finestra", @@ -64,7 +63,6 @@ "eventNotFound": "Evento Non Trovato", "eventIsFull": "L'Evento è Pieno!", "maximumCapacityReached": "Raggiunta la capacità massima", - "eventLinkCopied": "Link dell'evento copiato negli appunti!", "rsvpAddedSuccessfully": "RSVP aggiunto con successo!", "removedRsvpSuccessfully": "RSVP rimosso con successo.", "anUnexpectedErrorOccurred": "Si è verificato un errore inaspettato.", @@ -188,8 +186,10 @@ "noAttendeesYet": "Ancora nessun partecipante", "beFirstToJoin": "Sii il primo a partecipare!", "copyLinkButton": "Copia Link", + "copyInviteLinkButton": "Copia Link Invito", "addToCalendarButton": "Aggiungi al Calendario", "eventLinkCopied": "Link dell'evento copiato negli appunti!", + "inviteLinkCopied": "Link invito copiato negli appunti!", "rsvpAddedSuccessfully": "RSVP aggiunto con successo!", "removedRsvpSuccessfully": "RSVP rimosso con successo.", "failedToAddRsvp": "Impossibile aggiungere RSVP", @@ -208,7 +208,8 @@ "viewEventAriaLabel": "Visualizza evento", "editEventAriaLabel": "Modifica evento", "deleteEventAriaLabel": "Elimina evento", - "removeRsvpAriaLabel": "Rimuovi RSVP" + "removeRsvpAriaLabel": "Rimuovi RSVP", + "inviteLinkExpiresAt": "Questo link scade quando l'evento inizia: {time}" }, "discover": { "title": "Scopri Eventi - Cactoide", diff --git a/src/lib/i18n/messages.json b/src/lib/i18n/messages.json index 6bcac7f..113d613 100644 --- a/src/lib/i18n/messages.json +++ b/src/lib/i18n/messages.json @@ -42,7 +42,6 @@ "numberOfGuests": "Number of Guests", "addGuests": "Add guest users", "joinEvent": "Join Event", - "copyLink": "Event link copied to clipboard.", "addToCalendar": "Add to Calendar", "close": "Close", "closeModal": "Close modal", diff --git a/src/routes/event/[id]/+page.svelte b/src/routes/event/[id]/+page.svelte index f05e3d1..abd1558 100644 --- a/src/routes/event/[id]/+page.svelte +++ b/src/routes/event/[id]/+page.svelte @@ -31,6 +31,7 @@ $: event = data.event; $: rsvps = data.rsvps; $: currentUserId = data.userId; + $: isEventCreator = event.user_id === currentUserId; // Create calendar event object when event data changes $: if (event && browser) { @@ -77,7 +78,7 @@ const eventId = $page.params.id || ''; const copyEventLink = () => { - if (browser) { + if (browser && isEventCreator) { const url = `${$page.url.origin}/event/${eventId}`; navigator.clipboard.writeText(url).then(() => { toastType = 'copy'; @@ -442,12 +443,17 @@
- + {#if event.visibility !== 'invite-only'} + + {/if}
- - {#if eventData.visibility === 'invite-only' && inviteToken} + + {#if eventData.visibility === 'invite-only' && inviteToken && data.event.userId === data.userId}

Invite Link

@@ -395,7 +399,7 @@ on:click={copyInviteLink} class="rounded-sm border border-amber-300 bg-amber-200 px-3 py-2 text-sm font-medium text-amber-900 hover:bg-amber-300" > - {inviteLinkCopied ? t('common.success') : t('common.copyLink')} + {t('event.copyInviteLinkButton')}

@@ -436,3 +440,12 @@

+ + +{#if showInviteLinkToast} +
+ {t('event.inviteLinkCopied')} +
+{/if} diff --git a/src/routes/event/[id]/invite/[token]/+page.svelte b/src/routes/event/[id]/invite/[token]/+page.svelte index 8cec4a8..946f8c2 100644 --- a/src/routes/event/[id]/invite/[token]/+page.svelte +++ b/src/routes/event/[id]/invite/[token]/+page.svelte @@ -27,6 +27,7 @@ $: event = data.event; $: rsvps = data.rsvps; $: currentUserId = data.userId; + $: isEventCreator = event.user_id === currentUserId; // Create calendar event object when event data changes $: if (event && browser) { @@ -58,7 +59,7 @@ const token = $page.params.token || ''; const copyInviteLink = () => { - if (browser) { + if (browser && isEventCreator) { const url = `${$page.url.origin}/event/${eventId}/invite/${token}`; navigator.clipboard.writeText(url).then(() => { success = 'Invite link copied to clipboard!'; @@ -415,7 +416,10 @@