2
0
forked from jmug/cactoide

fix: refactor, docker, dateformatter

This commit is contained in:
Levente Orban
2025-08-27 11:45:41 +02:00
parent c11060deab
commit 91209d9efc
11 changed files with 266 additions and 199 deletions

View File

@@ -1,47 +1,24 @@
# Use Node.js 20 Alpine for smaller image size FROM node:20-alpine AS builder
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app WORKDIR /app
COPY package*.json .
# Install dependencies based on the preferred package manager
COPY package.json package-lock.json* ./
RUN npm ci RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
# Build the application
RUN npm run build RUN npm run build
RUN npm prune --production
FROM node:20-alpine
# Production image, copy all the files and run the app
FROM base AS runner
WORKDIR /app WORKDIR /app
ENV NODE_ENV production COPY --from=builder /app/build build/
# Uncomment the following line in case you want to disable telemetry during runtime. COPY --from=builder /app/node_modules node_modules/
# ENV NEXT_TELEMETRY_DISABLED 1 COPY package.json .
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 sveltekit
# Copy the built application
COPY --from=builder --chown=sveltekit:nodejs /app/build ./build
COPY --from=builder --chown=sveltekit:nodejs /app/package.json ./package.json
COPY --from=builder --chown=sveltekit:nodejs /app/node_modules ./node_modules
USER sveltekit
EXPOSE 3000 EXPOSE 3000
ENV PORT 3000 ENV PORT 3000
ENV HOSTNAME "0.0.0.0" ENV HOSTNAME "0.0.0.0"
ENV NODE_ENV production
# Start the application CMD [ "node", "build" ]
CMD ["node", "build"]

328
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,6 @@
"@eslint/compat": "^1.2.5", "@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0", "@eslint/js": "^9.18.0",
"@sveltejs/adapter-auto": "^6.0.0", "@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/adapter-netlify": "^5.2.2",
"@sveltejs/kit": "^2.22.0", "@sveltejs/kit": "^2.22.0",
"@sveltejs/vite-plugin-svelte": "^6.0.0", "@sveltejs/vite-plugin-svelte": "^6.0.0",
"@tailwindcss/forms": "^0.5.9", "@tailwindcss/forms": "^0.5.9",
@@ -39,6 +38,7 @@
"vite": "^7.0.4" "vite": "^7.0.4"
}, },
"dependencies": { "dependencies": {
"@sveltejs/adapter-node": "^5.3.1",
"drizzle-orm": "^0.44.5", "drizzle-orm": "^0.44.5",
"postgres": "^3.4.7" "postgres": "^3.4.7"
} }

12
src/lib/dateFormatter.ts Normal file
View File

@@ -0,0 +1,12 @@
export const formatDate = (dateString: string): string => {
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}/${month}/${day}`;
};
export const formatTime = (timeString: string): string => {
const [hours, minutes] = timeString.split(':');
return `${hours}:${minutes}`;
};

View File

@@ -1,5 +1,6 @@
export type EventType = 'limited' | 'unlimited'; export type EventType = 'limited' | 'unlimited';
export type EventVisibility = 'public' | 'private'; export type EventVisibility = 'public' | 'private';
export type ActionType = 'add' | 'remove';
export interface Event { export interface Event {
id: string; id: string;

View File

@@ -2,6 +2,7 @@
import type { Event } from '$lib/types'; import type { Event } from '$lib/types';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import type { PageData } from '../$types'; import type { PageData } from '../$types';
import { formatTime, formatDate } from '$lib/dateFormatter';
let publicEvents: Event[] = []; let publicEvents: Event[] = [];
let error = ''; let error = '';
@@ -9,19 +10,6 @@
export let data: PageData; export let data: PageData;
// Use the server-side data // Use the server-side data
$: publicEvents = data.events; $: publicEvents = data.events;
function formatDate(dateString: string): string {
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}/${month}/${day}`;
}
function formatTime(timeString: string): string {
const [hours, minutes] = timeString.split(':');
return `${hours}:${minutes}`;
}
</script> </script>
<svelte:head> <svelte:head>

View File

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { Event } from '$lib/types'; import type { Event } from '$lib/types';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { formatTime, formatDate } from '$lib/dateFormatter';
export let data: { events: Event[] }; export let data: { events: Event[] };
@@ -56,19 +57,6 @@
showDeleteModal = false; showDeleteModal = false;
eventToDelete = null; eventToDelete = null;
} }
function formatDate(dateString: string): string {
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}/${month}/${day}`;
}
function formatTime(timeString: string): string {
const [hours, minutes] = timeString.split(':');
return `${hours}:${minutes}`;
}
</script> </script>
<svelte:head> <svelte:head>

View File

@@ -117,15 +117,14 @@ export const actions: Actions = {
createdAt: new Date() createdAt: new Date()
}); });
return { success: true }; return { success: true, type: 'add' };
} catch (err) { } catch (err) {
console.error('Error adding RSVP:', err); console.error('Error adding RSVP:', err);
return fail(500, { error: 'Failed to add RSVP' }); return fail(500, { error: 'Failed to add RSVP' });
} }
}, },
removeRSVP: async ({ request, params }) => { removeRSVP: async ({ request }) => {
const eventId = params.id;
const formData = await request.formData(); const formData = await request.formData();
const rsvpId = formData.get('rsvpId') as string; const rsvpId = formData.get('rsvpId') as string;
@@ -136,7 +135,7 @@ export const actions: Actions = {
try { try {
await drizzleQuery.delete(rsvps).where(eq(rsvps.id, rsvpId)); await drizzleQuery.delete(rsvps).where(eq(rsvps.id, rsvpId));
return { success: true }; return { success: true, type: 'remove' };
} catch (err) { } catch (err) {
console.error('Error removing RSVP:', err); console.error('Error removing RSVP:', err);
return fail(500, { error: 'Failed to remove RSVP' }); return fail(500, { error: 'Failed to remove RSVP' });

View File

@@ -3,6 +3,7 @@
import type { Event, RSVP } from '$lib/types'; import type { Event, RSVP } from '$lib/types';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { enhance } from '$app/forms'; import { enhance } from '$app/forms';
import { formatTime, formatDate } from '$lib/dateFormatter';
export let data: { event: Event; rsvps: RSVP[]; userId: string }; export let data: { event: Event; rsvps: RSVP[]; userId: string };
export let form; export let form;
@@ -34,19 +35,6 @@
const eventId = $page.params.id; const eventId = $page.params.id;
function formatDate(dateString: string, timeString: string): string {
const date = new Date(`${dateString}T${timeString}`);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}/${month}/${day}`;
}
function formatTime(timeString: string): string {
const [hours, minutes] = timeString.split(':');
return `${hours}:${minutes}`;
}
function copyEventLink() { function copyEventLink() {
const url = `${window.location.origin}/event/${eventId}`; const url = `${window.location.origin}/event/${eventId}`;
navigator.clipboard.writeText(url).then(() => { navigator.clipboard.writeText(url).then(() => {
@@ -329,11 +317,19 @@
<!-- Success/Error Messages --> <!-- Success/Error Messages -->
{#if success} {#if success}
{#if form?.type === 'add'}
<div <div
class="fixed right-4 bottom-4 z-40 w-128 rounded-sm border border-green-500/30 bg-green-900/20 p-4 text-green-400" class="fixed right-4 bottom-4 z-40 w-128 rounded-sm border border-green-500/30 bg-green-900/20 p-4 text-green-400"
> >
{success} {success}
</div> </div>
{:else if form?.type === 'remove'}
<div
class="fixed right-4 bottom-4 z-40 w-128 rounded-sm border border-yellow-500/30 bg-yellow-900/20 p-4 text-yellow-400"
>
Removed RSVP successfully.
</div>
{/if}
{/if} {/if}
{#if error} {#if error}

View File

@@ -1,4 +1,4 @@
import adapter from '@sveltejs/adapter-netlify'; import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
@@ -15,7 +15,10 @@ 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
}
} }
}; };

View File

@@ -11,9 +11,4 @@
"strict": true, "strict": true,
"moduleResolution": "bundler" "moduleResolution": "bundler"
} }
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// To make changes to top-level options such as include and exclude, we recommend extending
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
} }