mirror of
https://github.com/polaroi8d/cactoide.git
synced 2026-03-22 06:05:28 +00:00
Compare commits
13 Commits
feat/suppo
...
feat/db-fa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb5543fb9b | ||
|
|
706e6cf712 | ||
|
|
c6decaa0d1 | ||
|
|
d193283b11 | ||
|
|
accfd540f0 | ||
|
|
9b1ef64618 | ||
|
|
c340088434 | ||
|
|
984c296725 | ||
|
|
9acfa08ea8 | ||
|
|
45cb95f6a8 | ||
|
|
8426bd5704 | ||
|
|
b9833db3bb | ||
|
|
b3572293ba |
92
package-lock.json
generated
92
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "event-cactus",
|
"name": "event-cactus",
|
||||||
"version": "0.0.1",
|
"version": "0.1.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "event-cactus",
|
"name": "event-cactus",
|
||||||
"version": "0.0.1",
|
"version": "0.1.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sveltejs/adapter-node": "^5.3.1",
|
"@sveltejs/adapter-node": "^5.3.1",
|
||||||
"drizzle-orm": "^0.44.5",
|
"drizzle-orm": "^0.44.5",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"tailwindcss": "^4.0.0",
|
"tailwindcss": "^4.0.0",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"typescript-eslint": "^8.20.0",
|
"typescript-eslint": "^8.20.0",
|
||||||
"vite": "^7.0.4"
|
"vite": "^7.1.11"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@drizzle-team/brocli": {
|
"node_modules/@drizzle-team/brocli": {
|
||||||
@@ -1949,6 +1949,66 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
||||||
|
"version": "1.4.5",
|
||||||
|
"dev": true,
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/wasi-threads": "1.0.4",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||||
|
"version": "1.4.5",
|
||||||
|
"dev": true,
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"dev": true,
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||||
|
"version": "0.2.12",
|
||||||
|
"dev": true,
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/core": "^1.4.3",
|
||||||
|
"@emnapi/runtime": "^1.4.3",
|
||||||
|
"@tybys/wasm-util": "^0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
||||||
|
"version": "0.10.0",
|
||||||
|
"dev": true,
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
||||||
|
"version": "2.8.0",
|
||||||
|
"dev": true,
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "0BSD",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||||
"version": "4.1.12",
|
"version": "4.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz",
|
||||||
@@ -2602,9 +2662,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/devalue": {
|
"node_modules/devalue": {
|
||||||
"version": "5.1.1",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.4.1.tgz",
|
||||||
"integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==",
|
"integrity": "sha512-YtoaOfsqjbZQKGIMRYDWKjUmSB4VJ/RElB+bXZawQAQYAo4xu08GKTMVlsZDTF6R2MbAgjcAQRPI5eIyRAT2OQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/drizzle-kit": {
|
"node_modules/drizzle-kit": {
|
||||||
@@ -4737,13 +4797,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.14",
|
"version": "0.2.15",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||||
"integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
|
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fdir": "^6.4.4",
|
"fdir": "^6.5.0",
|
||||||
"picomatch": "^4.0.2"
|
"picomatch": "^4.0.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
@@ -4864,17 +4924,17 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.1.2",
|
"version": "7.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz",
|
||||||
"integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==",
|
"integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.4.6",
|
"fdir": "^6.5.0",
|
||||||
"picomatch": "^4.0.3",
|
"picomatch": "^4.0.3",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"rollup": "^4.43.0",
|
"rollup": "^4.43.0",
|
||||||
"tinyglobby": "^0.2.14"
|
"tinyglobby": "^0.2.15"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"vite": "bin/vite.js"
|
"vite": "bin/vite.js"
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
"tailwindcss": "^4.0.0",
|
"tailwindcss": "^4.0.0",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"typescript-eslint": "^8.20.0",
|
"typescript-eslint": "^8.20.0",
|
||||||
"vite": "^7.0.4"
|
"vite": "^7.1.11"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sveltejs/adapter-node": "^5.3.1",
|
"@sveltejs/adapter-node": "^5.3.1",
|
||||||
|
|||||||
44
src/hooks.server.ts
Normal file
44
src/hooks.server.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// src/hooks.server.ts
|
||||||
|
import type { Handle } from '@sveltejs/kit';
|
||||||
|
import { generateUserId } from '$lib/generateUserId.js';
|
||||||
|
import { ensureDatabaseConnection } from '$lib/database/healthCheck';
|
||||||
|
|
||||||
|
// Global flag to track if database health check has been performed
|
||||||
|
let dbHealthCheckPerformed = false;
|
||||||
|
|
||||||
|
export const handle: Handle = async ({ event, resolve }) => {
|
||||||
|
// Perform database health check only once during application startup
|
||||||
|
if (!dbHealthCheckPerformed) {
|
||||||
|
try {
|
||||||
|
await ensureDatabaseConnection({
|
||||||
|
maxRetries: 3,
|
||||||
|
baseDelay: 1000,
|
||||||
|
maxDelay: 10000,
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
dbHealthCheckPerformed = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Database health check failed:', error);
|
||||||
|
// The ensureDatabaseConnection function will exit the process
|
||||||
|
// if the database is unavailable, so this catch is just for safety
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cactoideUserId = event.cookies.get('cactoideUserId');
|
||||||
|
const userId = generateUserId();
|
||||||
|
|
||||||
|
const DAYS = 400; // practical upper bound in many browsers for cookies
|
||||||
|
const MAX_AGE = 60 * 60 * 24 * DAYS;
|
||||||
|
const PATH = '/';
|
||||||
|
|
||||||
|
if (!cactoideUserId) {
|
||||||
|
console.debug(`There is no cactoideUserId cookie, generating new one...`);
|
||||||
|
event.cookies.set('cactoideUserId', userId, { path: PATH, maxAge: MAX_AGE });
|
||||||
|
} else {
|
||||||
|
console.debug(`cactoideUserId: ${cactoideUserId}`);
|
||||||
|
console.debug(`cactoideUserId cookie found, using existing one...`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(event);
|
||||||
|
};
|
||||||
@@ -15,7 +15,10 @@ export interface CalendarEvent {
|
|||||||
* Formats a date and time string for iCal format (UTC)
|
* Formats a date and time string for iCal format (UTC)
|
||||||
*/
|
*/
|
||||||
export const formatDateForICal = (date: string, time: string): string => {
|
export const formatDateForICal = (date: string, time: string): string => {
|
||||||
const eventDate = new Date(`${date}T${time}`);
|
// Parse date and time as local timezone to avoid timezone issues
|
||||||
|
const [year, month, day] = date.split('-').map(Number);
|
||||||
|
const [hours, minutes, seconds] = time.split(':').map(Number);
|
||||||
|
const eventDate = new Date(year, month - 1, day, hours, minutes, seconds || 0);
|
||||||
return eventDate.toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z';
|
return eventDate.toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
104
src/lib/database/healthCheck.ts
Normal file
104
src/lib/database/healthCheck.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import postgres from 'postgres';
|
||||||
|
import { env } from '$env/dynamic/private';
|
||||||
|
|
||||||
|
interface HealthCheckOptions {
|
||||||
|
maxRetries?: number;
|
||||||
|
baseDelay?: number;
|
||||||
|
maxDelay?: number;
|
||||||
|
timeout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HealthCheckResult {
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
|
attempts: number;
|
||||||
|
duration?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a database health check with retry logic and exponential backoff
|
||||||
|
*/
|
||||||
|
export async function checkDatabaseHealth(
|
||||||
|
options: HealthCheckOptions = {}
|
||||||
|
): Promise<HealthCheckResult> {
|
||||||
|
const {
|
||||||
|
maxRetries = 3,
|
||||||
|
baseDelay = 1000, // 1 second
|
||||||
|
maxDelay = 10000, // 10 seconds
|
||||||
|
timeout = 5000 // 5 seconds
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
if (!env.DATABASE_URL) {
|
||||||
|
console.error('DATABASE_URL environment variable is not set');
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'DATABASE_URL environment variable is not set',
|
||||||
|
attempts: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastError: Error | null = null;
|
||||||
|
|
||||||
|
console.log(`Starting database health check (max retries: ${maxRetries})`);
|
||||||
|
|
||||||
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
|
console.log(`Attempt ${attempt}/${maxRetries} - Testing database connection`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create a new connection for the health check
|
||||||
|
const client = postgres(env.DATABASE_URL, {
|
||||||
|
max: 1,
|
||||||
|
idle_timeout: 20,
|
||||||
|
connect_timeout: timeout / 1000, // Convert to seconds
|
||||||
|
onnotice: () => {} // Suppress notices
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test the connection with a simple query
|
||||||
|
await client`SELECT 1 as health_check`;
|
||||||
|
await client.end();
|
||||||
|
|
||||||
|
console.log(`Database connection successful on attempt ${attempt}.`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
attempts: attempt
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error as Error;
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||||
|
console.error(`Connection failed on attempt ${attempt}: ${errorMessage}`);
|
||||||
|
|
||||||
|
// Don't wait after the last attempt
|
||||||
|
if (attempt < maxRetries) {
|
||||||
|
const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
|
||||||
|
console.log(`Waiting ${delay}ms before retry...`);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalError = lastError?.message || 'Unknown database connection error';
|
||||||
|
|
||||||
|
console.error(`All ${maxRetries} attempts failed. Final error: ${finalError}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: finalError,
|
||||||
|
attempts: maxRetries
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs database health check and exits the process if it fails
|
||||||
|
*/
|
||||||
|
export async function ensureDatabaseConnection(options?: HealthCheckOptions): Promise<void> {
|
||||||
|
const result = await checkDatabaseHealth(options);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
console.error('Database connection failed after all retry attempts');
|
||||||
|
console.error(`Error: ${result.error}`);
|
||||||
|
console.error(`Attempts made: ${result.attempts}`);
|
||||||
|
console.error('Exiting application...');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
import type { Event } from './types';
|
import type { Event } from './types';
|
||||||
|
|
||||||
export const formatDate = (dateString: string): string => {
|
export const formatDate = (dateString: string): string => {
|
||||||
const date = new Date(dateString);
|
// Parse the date string as local date to avoid timezone issues
|
||||||
const year = date.getFullYear();
|
// Split the date string and create a Date object in local timezone
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
const [year, month, day] = dateString.split('-').map(Number);
|
||||||
const day = String(date.getDate()).padStart(2, '0');
|
const date = new Date(year, month - 1, day); // month is 0-indexed in Date constructor
|
||||||
return `${year}/${month}/${day}`;
|
const formattedYear = date.getFullYear();
|
||||||
|
const formattedMonth = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const formattedDay = String(date.getDate()).padStart(2, '0');
|
||||||
|
return `${formattedYear}/${formattedMonth}/${formattedDay}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatTime = (timeString: string): string => {
|
export const formatTime = (timeString: string): string => {
|
||||||
@@ -17,7 +20,10 @@ export const formatTime = (timeString: string): string => {
|
|||||||
export const isEventInTimeRange = (event: Event, timeFilter: string): boolean => {
|
export const isEventInTimeRange = (event: Event, timeFilter: string): boolean => {
|
||||||
if (timeFilter === 'any') return true;
|
if (timeFilter === 'any') return true;
|
||||||
|
|
||||||
const eventDate = new Date(`${event.date}T${event.time}`);
|
// Parse date and time as local timezone to avoid timezone issues
|
||||||
|
const [year, month, day] = event.date.split('-').map(Number);
|
||||||
|
const [hours, minutes, seconds] = event.time.split(':').map(Number);
|
||||||
|
const eventDate = new Date(year, month - 1, day, hours, minutes, seconds || 0);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
// Handle temporal status filters
|
// Handle temporal status filters
|
||||||
|
|||||||
@@ -1,20 +1,5 @@
|
|||||||
import { generateUserId } from '$lib/generateUserId.js';
|
|
||||||
|
|
||||||
export function load({ cookies }) {
|
export function load({ cookies }) {
|
||||||
const cactoideUserId = cookies.get('cactoideUserId');
|
const cactoideUserId = cookies.get('cactoideUserId');
|
||||||
const userId = generateUserId();
|
|
||||||
|
|
||||||
const DAYS = 400; // practical upper bound in many browsers for cookies
|
|
||||||
const MAX_AGE = 60 * 60 * 24 * DAYS;
|
|
||||||
const PATH = '/';
|
|
||||||
|
|
||||||
if (!cactoideUserId) {
|
|
||||||
console.debug(`There is no cactoideUserId cookie, generating new one...`);
|
|
||||||
cookies.set('cactoideUserId', userId, { path: PATH, maxAge: MAX_AGE });
|
|
||||||
} else {
|
|
||||||
console.debug(`cactoideUserId: ${cactoideUserId}`);
|
|
||||||
console.debug(`cactoideUserId cookie found, using existing one...`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cactoideUserId
|
cactoideUserId
|
||||||
|
|||||||
@@ -56,7 +56,13 @@ export const actions: Actions = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (new Date(date) < new Date()) {
|
// Check if date is in the past using local timezone
|
||||||
|
const [year, month, day] = date.split('-').map(Number);
|
||||||
|
const eventDate = new Date(year, month - 1, day);
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
if (eventDate < today) {
|
||||||
return fail(400, {
|
return fail(400, {
|
||||||
error: 'Date cannot be in the past.',
|
error: 'Date cannot be in the past.',
|
||||||
values: {
|
values: {
|
||||||
@@ -105,7 +111,7 @@ export const actions: Actions = {
|
|||||||
type: type,
|
type: type,
|
||||||
attendeeLimit: type === 'limited' ? parseInt(attendeeLimit) : null,
|
attendeeLimit: type === 'limited' ? parseInt(attendeeLimit) : null,
|
||||||
visibility: visibility,
|
visibility: visibility,
|
||||||
userId: userId
|
userId: userId!
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Unexpected error', error);
|
console.error('Unexpected error', error);
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Event, EventType } from '$lib/types';
|
import type { Event, EventType } from '$lib/types';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import type { PageData } from '../$types';
|
|
||||||
import { formatTime, formatDate, isEventInTimeRange } from '$lib/dateHelpers';
|
import { formatTime, formatDate, isEventInTimeRange } from '$lib/dateHelpers';
|
||||||
import { t } from '$lib/i18n/i18n.js';
|
import { t } from '$lib/i18n/i18n.js';
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
|
|
||||||
|
type DiscoverPageData = {
|
||||||
|
events: Event[];
|
||||||
|
};
|
||||||
|
|
||||||
let publicEvents: Event[] = [];
|
let publicEvents: Event[] = [];
|
||||||
let error = '';
|
let error = '';
|
||||||
let searchQuery = '';
|
let searchQuery = '';
|
||||||
@@ -16,7 +19,7 @@
|
|||||||
let showFilters = false;
|
let showFilters = false;
|
||||||
let fuse: Fuse<Event>;
|
let fuse: Fuse<Event>;
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: DiscoverPageData;
|
||||||
// Use the server-side data
|
// Use the server-side data
|
||||||
$: publicEvents = data?.events || [];
|
$: publicEvents = data?.events || [];
|
||||||
|
|
||||||
@@ -67,8 +70,15 @@
|
|||||||
|
|
||||||
// Sort events by date and time
|
// Sort events by date and time
|
||||||
events = events.sort((a, b) => {
|
events = events.sort((a, b) => {
|
||||||
const dateA = new Date(`${a.date}T${a.time}`);
|
// Parse dates as local timezone to avoid timezone issues
|
||||||
const dateB = new Date(`${b.date}T${b.time}`);
|
const parseEventDateTime = (event: Event) => {
|
||||||
|
const [year, month, day] = event.date.split('-').map(Number);
|
||||||
|
const [hours, minutes, seconds] = event.time.split(':').map(Number);
|
||||||
|
return new Date(year, month - 1, day, hours, minutes, seconds || 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dateA = parseEventDateTime(a);
|
||||||
|
const dateB = parseEventDateTime(b);
|
||||||
|
|
||||||
if (selectedSortOrder === 'asc') {
|
if (selectedSortOrder === 'asc') {
|
||||||
return dateA.getTime() - dateB.getTime();
|
return dateA.getTime() - dateB.getTime();
|
||||||
|
|||||||
@@ -86,8 +86,9 @@ export const actions: Actions = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if date is in the past (but allow editing past events for corrections)
|
// Check if date is in the past using local timezone (but allow editing past events for corrections)
|
||||||
const eventDate = new Date(date);
|
const [year, month, day] = date.split('-').map(Number);
|
||||||
|
const eventDate = new Date(year, month - 1, day);
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
today.setHours(0, 0, 0, 0);
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user