mirror of
https://github.com/polaroi8d/cactoide.git
synced 2026-08-12 03:09:11 +00:00
feat: change the userId beahviour
This commit is contained in:
@@ -27,17 +27,16 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cactoideUserId = event.cookies.get('cactoideUserId');
|
const cactoideUserId = event.cookies.get('cactoideUserId');
|
||||||
const userId = generateUserId();
|
|
||||||
|
|
||||||
const DAYS = 400; // practical upper bound in many browsers for cookies
|
const DAYS = 400; // practical upper bound in many browsers for cookies
|
||||||
const MAX_AGE = 60 * 60 * 24 * DAYS;
|
const MAX_AGE = 60 * 60 * 24 * DAYS;
|
||||||
const PATH = '/';
|
const PATH = '/';
|
||||||
|
|
||||||
if (!cactoideUserId) {
|
if (!cactoideUserId) {
|
||||||
logger.debug({ userId }, 'No cactoideUserId cookie found, generating new one');
|
logger.debug('No cactoideUserId cookie found, generating new one');
|
||||||
event.cookies.set('cactoideUserId', userId, { path: PATH, maxAge: MAX_AGE });
|
event.cookies.set('cactoideUserId', generateUserId(), { path: PATH, maxAge: MAX_AGE });
|
||||||
} else {
|
} else {
|
||||||
logger.debug({ cactoideUserId }, 'cactoideUserId cookie found, using existing one');
|
logger.debug('cactoideUserId cookie found, using existing one');
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolve(event);
|
return resolve(event);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export const generateUserId = () => {
|
import { randomUUID } from 'crypto';
|
||||||
const userId = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
|
||||||
|
|
||||||
return userId;
|
// This id is the only credential the app has — it must not be guessable,
|
||||||
};
|
// and it must never be serialized to the client.
|
||||||
|
export const generateUserId = () => 'user_' + randomUUID();
|
||||||
|
|||||||
@@ -259,8 +259,6 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"defaultTitle": "Cactoide -",
|
"defaultTitle": "Cactoide -",
|
||||||
"defaultDescription": "Crea e gestisci gli RSVP degli eventi",
|
"defaultDescription": "Crea e gestisci gli RSVP degli eventi",
|
||||||
"userIdCookieText": "Il tuo UserID memorizzato come cookie:",
|
|
||||||
"firstTimeVisiting": "Prima visita. Generazione di un nuovo UserID...",
|
|
||||||
"copyright": "© 2025 Cactoide"
|
"copyright": "© 2025 Cactoide"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,8 +291,6 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"defaultTitle": "Cactoide -",
|
"defaultTitle": "Cactoide -",
|
||||||
"defaultDescription": "Create and manage event RSVPs",
|
"defaultDescription": "Create and manage event RSVPs",
|
||||||
"userIdCookieText": "Your UserID stored as a cookie:",
|
|
||||||
"firstTimeVisiting": "First time visiting. Generating new UserID...",
|
|
||||||
"copyright": "© 2025 Cactoide"
|
"copyright": "© 2025 Cactoide"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export interface Event {
|
|||||||
type: EventType;
|
type: EventType;
|
||||||
attendee_limit?: number;
|
attendee_limit?: number;
|
||||||
visibility: EventVisibility;
|
visibility: EventVisibility;
|
||||||
user_id: string;
|
is_creator?: boolean; // Optional: absent on events fetched from federated instances
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
federation?: boolean; // Optional: true if event is from a federated instance
|
federation?: boolean; // Optional: true if event is from a federated instance
|
||||||
@@ -25,7 +25,7 @@ export interface RSVP {
|
|||||||
id: string;
|
id: string;
|
||||||
event_id: string;
|
event_id: string;
|
||||||
name: string;
|
name: string;
|
||||||
user_id: string;
|
is_mine: boolean;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
export function load({ cookies }) {
|
|
||||||
const cactoideUserId = cookies.get('cactoideUserId');
|
|
||||||
|
|
||||||
return {
|
|
||||||
cactoideUserId
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
import Navbar from '$lib/components/Navbar.svelte';
|
import Navbar from '$lib/components/Navbar.svelte';
|
||||||
import { t } from '$lib/i18n/i18n.js';
|
import { t } from '$lib/i18n/i18n.js';
|
||||||
|
|
||||||
let { data, children } = $props();
|
let { children } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -28,12 +28,6 @@
|
|||||||
<footer class="py-12">
|
<footer class="py-12">
|
||||||
<div class="container mx-auto px-4 text-center">
|
<div class="container mx-auto px-4 text-center">
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<p class="mb-4 text-gray-100/80">
|
|
||||||
{t('layout.userIdCookieText')}
|
|
||||||
<span class="font-bold text-violet-400"
|
|
||||||
>{data.cactoideUserId ? data.cactoideUserId : t('layout.firstTimeVisiting')}</span
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<p>{t('layout.copyright')}</p>
|
<p>{t('layout.copyright')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ export const GET: RequestHandler = async () => {
|
|||||||
federation: true,
|
federation: true,
|
||||||
attendee_limit: event.attendeeLimit,
|
attendee_limit: event.attendeeLimit,
|
||||||
visibility: event.visibility,
|
visibility: event.visibility,
|
||||||
user_id: event.userId,
|
|
||||||
created_at: event.createdAt?.toISOString() || '',
|
created_at: event.createdAt?.toISOString() || '',
|
||||||
updated_at: event.updatedAt?.toISOString() || ''
|
updated_at: event.updatedAt?.toISOString() || ''
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
|
|
||||||
let errors: Record<string, string> = {};
|
let errors: Record<string, string> = {};
|
||||||
let isSubmitting = false;
|
let isSubmitting = false;
|
||||||
let currentUserId = '';
|
|
||||||
|
|
||||||
// Get today's date in YYYY-MM-DD format for min attribute
|
// Get today's date in YYYY-MM-DD format for min attribute
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const today = new Date().toISOString().split('T')[0];
|
||||||
@@ -95,7 +94,6 @@
|
|||||||
}}
|
}}
|
||||||
class="space-y-6"
|
class="space-y-6"
|
||||||
>
|
>
|
||||||
<input type="hidden" name="userId" value={currentUserId} />
|
|
||||||
<input type="hidden" name="type" value={eventData.type} />
|
<input type="hidden" name="type" value={eventData.type} />
|
||||||
<input type="hidden" name="visibility" value={eventData.visibility} />
|
<input type="hidden" name="visibility" value={eventData.visibility} />
|
||||||
<input type="hidden" name="location_type" value={eventData.location_type} />
|
<input type="hidden" name="location_type" value={eventData.location_type} />
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export const load: PageServerLoad = async () => {
|
|||||||
type: event.type,
|
type: event.type,
|
||||||
attendee_limit: event.attendeeLimit,
|
attendee_limit: event.attendeeLimit,
|
||||||
visibility: event.visibility,
|
visibility: event.visibility,
|
||||||
user_id: event.userId,
|
|
||||||
created_at: event.createdAt?.toISOString(),
|
created_at: event.createdAt?.toISOString(),
|
||||||
updated_at: event.updatedAt?.toISOString(),
|
updated_at: event.updatedAt?.toISOString(),
|
||||||
federation: false // Add false for local events
|
federation: false // Add false for local events
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ export const load = async ({ cookies }) => {
|
|||||||
type: event.type,
|
type: event.type,
|
||||||
attendee_limit: event.attendeeLimit,
|
attendee_limit: event.attendeeLimit,
|
||||||
visibility: event.visibility,
|
visibility: event.visibility,
|
||||||
user_id: event.userId,
|
|
||||||
created_at: event.createdAt?.toISOString() || new Date().toISOString(),
|
created_at: event.createdAt?.toISOString() || new Date().toISOString(),
|
||||||
updated_at: event.updatedAt?.toISOString() || new Date().toISOString()
|
updated_at: event.updatedAt?.toISOString() || new Date().toISOString()
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
export let data: { events: Event[] };
|
export let data: { events: Event[] };
|
||||||
|
|
||||||
let userEvents: Event[] = [];
|
let userEvents: Event[] = [];
|
||||||
let currentUserId = '';
|
|
||||||
let showDeleteModal = false;
|
let showDeleteModal = false;
|
||||||
let eventToDelete: Event | null = null;
|
let eventToDelete: Event | null = null;
|
||||||
|
|
||||||
@@ -28,7 +27,6 @@
|
|||||||
// Use server-side action for deletion
|
// Use server-side action for deletion
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('eventId', eventId);
|
formData.append('eventId', eventId);
|
||||||
formData.append('userId', currentUserId);
|
|
||||||
|
|
||||||
const response = await fetch('?/deleteEvent', {
|
const response = await fetch('?/deleteEvent', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { logger } from '$lib/logger';
|
|||||||
|
|
||||||
export const load: PageServerLoad = async ({ params, cookies }) => {
|
export const load: PageServerLoad = async ({ params, cookies }) => {
|
||||||
const eventId = params.id;
|
const eventId = params.id;
|
||||||
|
const userId = cookies.get('cactoideUserId');
|
||||||
|
|
||||||
if (!eventId) {
|
if (!eventId) {
|
||||||
throw error(404, 'EventId not found');
|
throw error(404, 'EventId not found');
|
||||||
@@ -29,7 +30,6 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
|
|||||||
// Check if this is an invite-only event
|
// Check if this is an invite-only event
|
||||||
if (event.visibility === 'invite-only') {
|
if (event.visibility === 'invite-only') {
|
||||||
// For invite-only events, check if user is the event creator
|
// For invite-only events, check if user is the event creator
|
||||||
const userId = cookies.get('cactoideUserId');
|
|
||||||
if (event.userId !== userId) {
|
if (event.userId !== userId) {
|
||||||
// User is not the creator, redirect to a message about needing invite
|
// User is not the creator, redirect to a message about needing invite
|
||||||
throw error(403, 'This event requires an invite link to view');
|
throw error(403, 'This event requires an invite link to view');
|
||||||
@@ -48,7 +48,8 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
|
|||||||
type: event.type,
|
type: event.type,
|
||||||
attendee_limit: event.attendeeLimit,
|
attendee_limit: event.attendeeLimit,
|
||||||
visibility: event.visibility,
|
visibility: event.visibility,
|
||||||
user_id: event.userId,
|
// Never send raw user ids to the client — they are the credential
|
||||||
|
is_creator: !!userId && event.userId === userId,
|
||||||
created_at: event.createdAt?.toISOString() || new Date().toISOString(),
|
created_at: event.createdAt?.toISOString() || new Date().toISOString(),
|
||||||
updated_at: event.updatedAt?.toISOString() || new Date().toISOString()
|
updated_at: event.updatedAt?.toISOString() || new Date().toISOString()
|
||||||
};
|
};
|
||||||
@@ -57,16 +58,13 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
|
|||||||
id: rsvp.id,
|
id: rsvp.id,
|
||||||
event_id: rsvp.eventId,
|
event_id: rsvp.eventId,
|
||||||
name: rsvp.name,
|
name: rsvp.name,
|
||||||
user_id: rsvp.userId,
|
is_mine: !!userId && rsvp.userId === userId,
|
||||||
created_at: rsvp.createdAt?.toISOString() || new Date().toISOString()
|
created_at: rsvp.createdAt?.toISOString() || new Date().toISOString()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const userId = cookies.get('cactoideUserId');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
event: transformedEvent,
|
event: transformedEvent,
|
||||||
rsvps: transformedRsvps,
|
rsvps: transformedRsvps
|
||||||
userId: userId
|
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof Response) throw err; // This is the 404 error
|
if (err instanceof Response) throw err; // This is the 404 error
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import type { CalendarEvent } from '$lib/calendarHelpers.js';
|
import type { CalendarEvent } from '$lib/calendarHelpers.js';
|
||||||
import { t } from '$lib/i18n/i18n.js';
|
import { t } from '$lib/i18n/i18n.js';
|
||||||
|
|
||||||
export let data: { event: Event; rsvps: RSVP[]; userId: string };
|
export let data: { event: Event; rsvps: RSVP[] };
|
||||||
type FormDataLocal = { success?: boolean; error?: string; type?: 'add' | 'remove' | 'copy' };
|
type FormDataLocal = { success?: boolean; error?: string; type?: 'add' | 'remove' | 'copy' };
|
||||||
export let form: FormDataLocal | undefined;
|
export let form: FormDataLocal | undefined;
|
||||||
|
|
||||||
@@ -30,8 +30,7 @@
|
|||||||
// Use server-side data
|
// Use server-side data
|
||||||
$: event = data.event;
|
$: event = data.event;
|
||||||
$: rsvps = data.rsvps;
|
$: rsvps = data.rsvps;
|
||||||
$: currentUserId = data.userId;
|
$: isEventCreator = event.is_creator ?? false;
|
||||||
$: isEventCreator = event.user_id === currentUserId;
|
|
||||||
|
|
||||||
// Create calendar event object when event data changes
|
// Create calendar event object when event data changes
|
||||||
$: if (event && browser) {
|
$: if (event && browser) {
|
||||||
@@ -266,7 +265,6 @@
|
|||||||
}}
|
}}
|
||||||
class="space-y-4"
|
class="space-y-4"
|
||||||
>
|
>
|
||||||
<input type="hidden" name="userId" value={currentUserId} />
|
|
||||||
<div>
|
<div>
|
||||||
<label for="attendeeName" class=" mb-2 block text-sm font-semibold">
|
<label for="attendeeName" class=" mb-2 block text-sm font-semibold">
|
||||||
{t('event.yourNameLabel')}
|
{t('event.yourNameLabel')}
|
||||||
@@ -401,7 +399,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if attendee.user_id === currentUserId}
|
{#if attendee.is_mine}
|
||||||
<form
|
<form
|
||||||
method="POST"
|
method="POST"
|
||||||
action="?/removeRSVP"
|
action="?/removeRSVP"
|
||||||
|
|||||||
@@ -44,10 +44,13 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strip the owner id — it is the credential, and the load above already proved ownership
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { userId: _owner, ...eventRow } = event[0];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
event: event[0],
|
event: eventRow,
|
||||||
inviteToken,
|
inviteToken
|
||||||
userId
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -394,7 +394,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Invite Link Section (only for invite-only events and event creator) -->
|
<!-- Invite Link Section (only for invite-only events and event creator) -->
|
||||||
{#if eventData.visibility === 'invite-only' && inviteToken && data.event.userId === data.userId}
|
{#if eventData.visibility === 'invite-only' && inviteToken}
|
||||||
<div class="rounded-sm border border-amber-500/30 bg-amber-900/20 p-4">
|
<div class="rounded-sm border border-amber-500/30 bg-amber-900/20 p-4">
|
||||||
<div class="mb-3 flex items-center justify-between">
|
<div class="mb-3 flex items-center justify-between">
|
||||||
<h3 class="text-lg font-semibold text-amber-400">Invite Link</h3>
|
<h3 class="text-lg font-semibold text-amber-400">Invite Link</h3>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { isTokenValid } from '$lib/inviteTokenHelpers.js';
|
|||||||
export const load: PageServerLoad = async ({ params, cookies }) => {
|
export const load: PageServerLoad = async ({ params, cookies }) => {
|
||||||
const eventId = params.id;
|
const eventId = params.id;
|
||||||
const token = params.token;
|
const token = params.token;
|
||||||
|
const userId = cookies.get('cactoideUserId');
|
||||||
|
|
||||||
if (!eventId || !token) {
|
if (!eventId || !token) {
|
||||||
throw error(404, 'Event or token not found');
|
throw error(404, 'Event or token not found');
|
||||||
@@ -59,7 +60,8 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
|
|||||||
type: event.type,
|
type: event.type,
|
||||||
attendee_limit: event.attendeeLimit,
|
attendee_limit: event.attendeeLimit,
|
||||||
visibility: event.visibility,
|
visibility: event.visibility,
|
||||||
user_id: event.userId,
|
// Never send raw user ids to the client — they are the credential
|
||||||
|
is_creator: !!userId && event.userId === userId,
|
||||||
created_at: event.createdAt?.toISOString() || new Date().toISOString(),
|
created_at: event.createdAt?.toISOString() || new Date().toISOString(),
|
||||||
updated_at: event.updatedAt?.toISOString() || new Date().toISOString()
|
updated_at: event.updatedAt?.toISOString() || new Date().toISOString()
|
||||||
};
|
};
|
||||||
@@ -68,16 +70,13 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
|
|||||||
id: rsvp.id,
|
id: rsvp.id,
|
||||||
event_id: rsvp.eventId,
|
event_id: rsvp.eventId,
|
||||||
name: rsvp.name,
|
name: rsvp.name,
|
||||||
user_id: rsvp.userId,
|
is_mine: !!userId && rsvp.userId === userId,
|
||||||
created_at: rsvp.createdAt?.toISOString() || new Date().toISOString()
|
created_at: rsvp.createdAt?.toISOString() || new Date().toISOString()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const userId = cookies.get('cactoideUserId');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
event: transformedEvent,
|
event: transformedEvent,
|
||||||
rsvps: transformedRsvps,
|
rsvps: transformedRsvps,
|
||||||
userId: userId,
|
|
||||||
inviteToken: {
|
inviteToken: {
|
||||||
id: inviteToken.id,
|
id: inviteToken.id,
|
||||||
event_id: inviteToken.eventId,
|
event_id: inviteToken.eventId,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import type { CalendarEvent } from '$lib/calendarHelpers.js';
|
import type { CalendarEvent } from '$lib/calendarHelpers.js';
|
||||||
import { t } from '$lib/i18n/i18n.js';
|
import { t } from '$lib/i18n/i18n.js';
|
||||||
|
|
||||||
export let data: { event: Event; rsvps: RSVP[]; userId: string; inviteToken: InviteToken };
|
export let data: { event: Event; rsvps: RSVP[]; inviteToken: InviteToken };
|
||||||
export let form;
|
export let form;
|
||||||
|
|
||||||
let event: Event;
|
let event: Event;
|
||||||
@@ -26,8 +26,7 @@
|
|||||||
// Use server-side data
|
// Use server-side data
|
||||||
$: event = data.event;
|
$: event = data.event;
|
||||||
$: rsvps = data.rsvps;
|
$: rsvps = data.rsvps;
|
||||||
$: currentUserId = data.userId;
|
$: isEventCreator = event.is_creator ?? false;
|
||||||
$: isEventCreator = event.user_id === currentUserId;
|
|
||||||
|
|
||||||
// Create calendar event object when event data changes
|
// Create calendar event object when event data changes
|
||||||
$: if (event && browser) {
|
$: if (event && browser) {
|
||||||
@@ -240,7 +239,6 @@
|
|||||||
}}
|
}}
|
||||||
class="space-y-4"
|
class="space-y-4"
|
||||||
>
|
>
|
||||||
<input type="hidden" name="userId" value={currentUserId} />
|
|
||||||
<div>
|
<div>
|
||||||
<label for="attendeeName" class=" mb-2 block text-sm font-semibold">
|
<label for="attendeeName" class=" mb-2 block text-sm font-semibold">
|
||||||
{t('event.yourNameLabel')}
|
{t('event.yourNameLabel')}
|
||||||
@@ -374,7 +372,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if attendee.user_id === currentUserId}
|
{#if attendee.is_mine}
|
||||||
<form
|
<form
|
||||||
method="POST"
|
method="POST"
|
||||||
action="?/removeRSVP"
|
action="?/removeRSVP"
|
||||||
|
|||||||
@@ -14,10 +14,7 @@ const config = {
|
|||||||
// see "split" mode in https://github.com/sveltejs/kit/tree/main/packages/adapter-netlify
|
// see "split" mode in https://github.com/sveltejs/kit/tree/main/packages/adapter-netlify
|
||||||
edge: false,
|
edge: false,
|
||||||
split: false
|
split: false
|
||||||
}),
|
})
|
||||||
csrf: {
|
|
||||||
checkOrigin: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user