From f4dd639b65336443b69dfbda84da1b1f5e7ecd0e Mon Sep 17 00:00:00 2001 From: "Gard.Kalland" Date: Thu, 20 Mar 2025 16:16:03 +0100 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=92=85Auto=20mail=20setup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/www/src/hooks.server.ts | 196 +++++++++--------- apps/www/src/lib/services/email.service.tsx | 140 +++++++++---- apps/www/src/lib/validators.ts | 39 ++-- apps/www/src/routes/api/shiftEmail/+server.ts | 18 ++ internal/emails/emails/index.ts | 5 + internal/emails/emails/shiftemail.tsx | 79 +++++++ 6 files changed, 324 insertions(+), 153 deletions(-) create mode 100644 apps/www/src/routes/api/shiftEmail/+server.ts create mode 100644 internal/emails/emails/shiftemail.tsx diff --git a/apps/www/src/hooks.server.ts b/apps/www/src/hooks.server.ts index 0dbafbad..529ac7e6 100644 --- a/apps/www/src/hooks.server.ts +++ b/apps/www/src/hooks.server.ts @@ -7,6 +7,7 @@ import { BeerService } from '$lib/services/beer.service'; import { EmailService } from '$lib/services/email.service'; import { EventService } from '$lib/services/event.service'; import { InvitationService } from '$lib/services/invitation.service'; +//import { EmailShiftService } from '$lib/services/.service'; import { ShiftService } from '$lib/services/shift.service'; import { StatusService } from '$lib/services/status.service'; import { UserService } from '$lib/services/user.service'; @@ -14,100 +15,103 @@ import type { Handle } from '@sveltejs/kit'; import { Resend } from 'resend'; export const handle: Handle = async ({ event, resolve }) => { - const banService = new BanService(event.platform!.env.STATUS_KV); - event.locals.banService = banService; - - const ip = event.getClientAddress(); - const isIpBanned = await banService.isIpBanned(ip); - if (isIpBanned) { - return new Response(null, { - status: 429, - headers: { - 'retry-after': '3600' - } - }); - } - - // Setup Resend - const resend = new Resend(event.platform?.env.RESEND_API_KEY); - event.locals.resend = resend; - - // Setup database - const db = createDatabase(event.platform!.env.DB); - event.locals.db = db; - - // Setup auth - const auth = createAuth(db); - event.locals.auth = auth; - - // Setup feide provider - const feideProvider = createFeideProvider( - event.platform!.env.FEIDE_CLIENT_ID, - event.platform!.env.FEIDE_CLIENT_SECRET, - event.platform!.env.FEIDE_REDIRECT_URI - ); - event.locals.feideProvider = feideProvider; - - // Setup status service - const statusService = new StatusService(event.platform!.env.STATUS_KV); - event.locals.statusService = statusService; - - const invitationService = new InvitationService(db); - event.locals.invitationService = invitationService; - - const userService = new UserService(db); - event.locals.userService = userService; - - const eventService = new EventService(db); - event.locals.eventService = eventService; - - const emailService = new EmailService(resend); - event.locals.emailService = emailService; - - const shiftService = new ShiftService(db); - event.locals.shiftService = shiftService; - - const beerService = new BeerService(db, shiftService); - event.locals.beerService = beerService; - - // Validate auth - const sessionId = event.cookies.get(auth.sessionCookieName); - - if (sessionId) { - const { session, user } = await auth.validateSession(sessionId); - - event.locals.user = user; - event.locals.session = session; - } else { - event.locals.user = null; - event.locals.session = null; - } - - if (!event.locals.user) { - event.cookies.delete(auth.sessionCookieName, { - path: '/', - httpOnly: true, - secure: !dev - }); - } - - if (event.url.pathname.startsWith('/portal/admin') && event.locals.user?.role !== 'board') { - return new Response(null, { - status: 307, - headers: { - location: '/logg-inn' - } - }); - } - - if (event.url.pathname.startsWith('/portal') && !event.locals.user) { - return new Response(null, { - status: 307, - headers: { - location: '/logg-inn' - } - }); - } - - return await resolve(event); + const banService = new BanService(event.platform!.env.STATUS_KV); + event.locals.banService = banService; + + const ip = event.getClientAddress(); + const isIpBanned = await banService.isIpBanned(ip); + if (isIpBanned) { + return new Response(null, { + status: 429, + headers: { + 'retry-after': '3600' + } + }); + } + + // Setup Resend + const resend = new Resend(event.platform?.env.RESEND_API_KEY); + event.locals.resend = resend; + + // Setup database + const db = createDatabase(event.platform!.env.DB); + event.locals.db = db; + + // Setup auth + const auth = createAuth(db); + event.locals.auth = auth; + + // Setup feide provider + const feideProvider = createFeideProvider( + event.platform!.env.FEIDE_CLIENT_ID, + event.platform!.env.FEIDE_CLIENT_SECRET, + event.platform!.env.FEIDE_REDIRECT_URI + ); + event.locals.feideProvider = feideProvider; + + // Setup status service + const statusService = new StatusService(event.platform!.env.STATUS_KV); + event.locals.statusService = statusService; + + const invitationService = new InvitationService(db); + event.locals.invitationService = invitationService; + + const emailShiftService = new EmailShiftService(db); + event.locals.emailShiftService = emailShiftService; + + const userService = new UserService(db); + event.locals.userService = userService; + + const eventService = new EventService(db); + event.locals.eventService = eventService; + + const emailService = new EmailService(resend); + event.locals.emailService = emailService; + + const shiftService = new ShiftService(db); + event.locals.shiftService = shiftService; + + const beerService = new BeerService(db, shiftService); + event.locals.beerService = beerService; + + // Validate auth + const sessionId = event.cookies.get(auth.sessionCookieName); + + if (sessionId) { + const { session, user } = await auth.validateSession(sessionId); + + event.locals.user = user; + event.locals.session = session; + } else { + event.locals.user = null; + event.locals.session = null; + } + + if (!event.locals.user) { + event.cookies.delete(auth.sessionCookieName, { + path: '/', + httpOnly: true, + secure: !dev + }); + } + + if (event.url.pathname.startsWith('/portal/admin') && event.locals.user?.role !== 'board') { + return new Response(null, { + status: 307, + headers: { + location: '/logg-inn' + } + }); + } + + if (event.url.pathname.startsWith('/portal') && !event.locals.user) { + return new Response(null, { + status: 307, + headers: { + location: '/logg-inn' + } + }); + } + + return await resolve(event); }; diff --git a/apps/www/src/lib/services/email.service.tsx b/apps/www/src/lib/services/email.service.tsx index 87ede21b..c8607f9f 100644 --- a/apps/www/src/lib/services/email.service.tsx +++ b/apps/www/src/lib/services/email.service.tsx @@ -1,5 +1,5 @@ import { dev } from '$app/environment'; -import { ContactUsEmail, InvitationEmail } from '@programmerbar/emails'; +import { ContactUsEmail, InvitationEmail, ShiftEmail } from '@programmerbar/emails'; import type { CreateEmailOptions, Resend } from 'resend'; import { render } from '@react-email/render'; @@ -7,53 +7,105 @@ const PROGRAMMERBAR_EMAIL = 'styret@programmerbar.no'; const FROM_EMAIL = 'ikkesvar@programmer.bar'; export type ContactUsEmailProps = { - name: string; - email: string; - message: string; + name: string; + email: string; + message: string; }; export type InvitationEmailProps = { - email: string; + email: string; }; +export type ShiftEmailProps = { + shift: { + startAt: string; + endAt: string; + summary: string; + description?: string; + }; + user: { + name: string; + email: string; + }; +}; + +function generateICS(shift: { startAt: string; endAt: string; summary: string; description?: string }): string { + const uid = `${Date.now()}@programmerbar.no`; + const formatDate = (dateStr: string) => + new Date(dateStr).toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z'; + const dtstamp = formatDate(new Date().toISOString()); + const dtstart = formatDate(shift.startAt); + const dtend = formatDate(shift.endAt); + + return `BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Programmerbar//Shift Notification//EN +BEGIN:VEVENT +UID:${uid} +DTSTAMP:${dtstamp} +DTSTART:${dtstart} +DTEND:${dtend} +SUMMARY:${shift.summary} +DESCRIPTION:${shift.description || ''} +END:VEVENT +END:VCALENDAR`; +} + export class EmailService { - #resend: Resend; - - constructor(resend: Resend) { - this.#resend = resend; - } - - async sendContactUsEmail(data: ContactUsEmailProps) { - await this.sendEmail({ - from: FROM_EMAIL, - subject: 'Kontaktskjema pÃ¥ hjemmesiden', - to: [PROGRAMMERBAR_EMAIL], - html: await render(ContactUsEmail({ ...data })) - }); - } - - async sendInvitaitonEmail(data: InvitationEmailProps) { - await this.sendEmail({ - from: FROM_EMAIL, - subject: 'Invitasjon til Programmerbar', - to: [data.email], - html: await render(InvitationEmail({ ...data })) - }); - } - - private async sendEmail(payload: CreateEmailOptions) { - if (dev) { - console.log('#############################'); - console.log('# NOT SENDING EMAILS IN DEV #'); - console.log('#############################'); - - console.log('########### EMAIL ############'); - console.log(payload.html); - console.log('#############################'); - - return; - } - - await this.#resend.emails.send(payload); - } + #resend: Resend; + + constructor(resend: Resend) { + this.#resend = resend; + } + + async sendContactUsEmail(data: ContactUsEmailProps) { + await this.sendEmail({ + from: FROM_EMAIL, + subject: 'Kontaktskjema pÃ¥ hjemmesiden', + to: [PROGRAMMERBAR_EMAIL], + html: await render(ContactUsEmail({ ...data })) + }); + } + + async sendInvitaitonEmail(data: InvitationEmailProps) { + await this.sendEmail({ + from: FROM_EMAIL, + subject: 'Invitasjon til Programmerbar', + to: [data.email], + html: await render(InvitationEmail({ ...data })) + }); + } + + async sendShiftEmail(data: ShiftEmailProps) { + const icsContent = generateICS(data.shift); + await this.sendEmail({ + from: FROM_EMAIL, + subject: 'Du har fått en vakt', + to: [data.user.email], + html: await render(ShiftEmail({ ...data })), + attachments: [ + { + filename: 'shift.ics', + content: icsContent, + contentType: 'text/calendar' + } + ] + }); + } + + private async sendEmail(payload: CreateEmailOptions) { + if (dev) { + console.log('#############################'); + console.log('# NOT SENDING EMAILS IN DEV #'); + console.log('#############################'); + + console.log('########### EMAIL ############'); + console.log(payload.html); + console.log('#############################'); + + return; + } + + await this.#resend.emails.send(payload); + } } diff --git a/apps/www/src/lib/validators.ts b/apps/www/src/lib/validators.ts index cf99e5ca..7c997570 100644 --- a/apps/www/src/lib/validators.ts +++ b/apps/www/src/lib/validators.ts @@ -2,23 +2,36 @@ import { z } from 'zod'; import { zfd } from 'zod-form-data'; export const CreateEventSchema = z.object({ - name: z.string(), - date: z.coerce.date(), - shifts: z - .object({ - startAt: z.coerce.date(), - endAt: z.coerce.date(), - users: z.array(z.string()) - }) - .array() + name: z.string(), + date: z.coerce.date(), + shifts: z + .object({ + startAt: z.coerce.date(), + endAt: z.coerce.date(), + users: z.array(z.string()) + }) + .array() }); export const ContactUsSchema = zfd.formData({ - namekjkj: zfd.text(z.string().min(2).max(50)), - emailkjkj: zfd.text(z.string().email().min(3)), - messagekjkj: zfd.text(z.string().min(5).max(1000)) + namekjkj: zfd.text(z.string().min(2).max(50)), + emailkjkj: zfd.text(z.string().email().min(3)), + messagekjkj: zfd.text(z.string().min(5).max(1000)) }); export const CreateInvitationSchema = z.object({ - email: z.string().email() + email: z.string().email() +}); + +export const CreateEmailShiftSchema = z.object({ + user: z.object({ + name: z.string().min(1), + email: z.string().email() + }), + shift: z.object({ + startAt: z.coerce.date(), + endAt: z.coerce.date(), + summary: z.string().min(1), + description: z.string().optional() + }) }); diff --git a/apps/www/src/routes/api/shiftEmail/+server.ts b/apps/www/src/routes/api/shiftEmail/+server.ts new file mode 100644 index 00000000..78c70681 --- /dev/null +++ b/apps/www/src/routes/api/shiftEmail/+server.ts @@ -0,0 +1,18 @@ +import { CreateEmailShiftSchema } from '$lib/validators'; +import type { RequestHandler } from './$types'; + +export const POST: RequestHandler = async ({ request, locals }) => { + if (!locals.user) { + return new Response(null, { status: 401 }); + } + + const shiftEmailData = await request + .json() + .then(CreateEmailShiftSchema.parse) + .then((data) => data.email.toLowerCase()); + + await locals.emailShiftService.create(shiftEmailData); + await locals.emailService.sendShiftEmail(shiftEmailData); + + return new Response(null, { status: 201 }); +}; diff --git a/internal/emails/emails/index.ts b/internal/emails/emails/index.ts index 1ad7fae3..1755f150 100644 --- a/internal/emails/emails/index.ts +++ b/internal/emails/emails/index.ts @@ -6,3 +6,8 @@ export { default as InvitationEmail, type InvitationEmailProps, } from "./invitation"; +export { + default as ShiftEmail, + type ShiftEmailProps, +} + from "./shiftemail"; diff --git a/internal/emails/emails/shiftemail.tsx b/internal/emails/emails/shiftemail.tsx new file mode 100644 index 00000000..9fa813ee --- /dev/null +++ b/internal/emails/emails/shiftemail.tsx @@ -0,0 +1,79 @@ +import { + Body, + Container, + Head, + Html, + Text, + Tailwind, +} from "@react-email/components"; + +export interface ShiftEmailProps { + shift: { + startAt: string; + endAt: string; + summary: string; + description?: string; + }; + user: { + name: string; + email: string; + }; +} + +const ShiftEmail = ({ shift, user }: ShiftEmailProps) => { + return ( + + + + + + + Hei {user.name}, du har fått en ny vakt! + + + + Du har blitt tildelt en vakt med følgende detaljer: + + + + Fra: {new Date(shift.startAt).toLocaleString()} + + + + Til: {new Date(shift.endAt).toLocaleString()} + + + + Oppsummering: {shift.summary} + + + {shift.description && ( + + Beskrivelse: {shift.description} + + )} + + + Kalenderinvitasjonen er vedlagt denne e-posten. + + + + + + ); +}; + +ShiftEmail.PreviewProps = { + shift: { + startAt: new Date().toISOString(), + endAt: new Date(Date.now() + 3600000).toISOString(), // 1 hour later + summary: "Vakt for programmerbar fredagsåpent", + description: "Programmerbar.", + }, + user: { + name: "Ola Nordmann", + email: "ola.nordmann@example.com", + }, +} as ShiftEmailProps; + +export default ShiftEmail; From 1928456839dbd7242e0684a86580e80eee6d0854 Mon Sep 17 00:00:00 2001 From: "Gard.Kalland" Date: Wed, 2 Apr 2025 18:03:30 +0200 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=94=90admin=20feature?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/www/src/hooks.server.ts | 4 +- .../portal/arrangementer/[id]/+page.server.ts | 107 ++++++++++-------- .../portal/arrangementer/[id]/+page.svelte | 11 +- 3 files changed, 65 insertions(+), 57 deletions(-) diff --git a/apps/www/src/hooks.server.ts b/apps/www/src/hooks.server.ts index 529ac7e6..d3a0edab 100644 --- a/apps/www/src/hooks.server.ts +++ b/apps/www/src/hooks.server.ts @@ -56,8 +56,8 @@ export const handle: Handle = async ({ event, resolve }) => { const invitationService = new InvitationService(db); event.locals.invitationService = invitationService; - const emailShiftService = new EmailShiftService(db); - event.locals.emailShiftService = emailShiftService; + //const emailShiftService = new EmailShiftService(db); + //event.locals.emailShiftService = emailShiftService; const userService = new UserService(db); event.locals.userService = userService; diff --git a/apps/www/src/routes/portal/arrangementer/[id]/+page.server.ts b/apps/www/src/routes/portal/arrangementer/[id]/+page.server.ts index 2def4818..39874958 100644 --- a/apps/www/src/routes/portal/arrangementer/[id]/+page.server.ts +++ b/apps/www/src/routes/portal/arrangementer/[id]/+page.server.ts @@ -2,65 +2,72 @@ import { error, fail, redirect } from '@sveltejs/kit'; import type { Actions, PageServerLoad } from './$types'; export const load: PageServerLoad = async ({ locals, params }) => { - const event = await locals.eventService.findFullEventById(params.id); + const event = await locals.eventService.findFullEventById(params.id); - if (!event) { - throw error(404, 'Event not found'); - } + if (!event) { + throw error(404, 'Event not found'); + } - return { - event - }; + return { + event + }; }; export const actions: Actions = { - delete: async ({ params, locals }) => { - await locals.eventService.delete(params.id); - throw redirect(303, '/portal/arrangementer'); - }, - join: async ({ request, locals }) => { - if (!locals.user) { - return fail(401, { - message: 'Not logged in' - }); - } + delete: async ({ params, locals }) => { + if (locals.user?.role === 'board') { - const formData = await request.formData(); - const shiftId = formData.get('shiftId'); - if (!shiftId || typeof shiftId !== 'string') { - return fail(400, { - message: 'Missing shiftId' - }); - } + await locals.eventService.delete(params.id); + throw redirect(303, '/portal/arrangementer'); + } - await locals.eventService.createUserShift({ - shiftId, - userId: locals.user.id, - status: 'accepted' - }); + return fail(401, { + message: 'Unauthorized' + }); + }, + join: async ({ request, locals }) => { + if (!locals.user) { + return fail(401, { + message: 'Not logged in' + }); + } - return { success: true }; - }, - leave: async ({ request, locals }) => { - if (!locals.user) { - return fail(401, { - message: 'Not logged in' - }); - } + const formData = await request.formData(); + const shiftId = formData.get('shiftId'); + if (!shiftId || typeof shiftId !== 'string') { + return fail(400, { + message: 'Missing shiftId' + }); + } - const formData = await request.formData(); - const shiftId = formData.get('shiftId'); - if (!shiftId || typeof shiftId !== 'string') { - return fail(400, { - message: 'Missing shiftId' - }); - } + await locals.eventService.createUserShift({ + shiftId, + userId: locals.user.id, + status: 'accepted' + }); - await locals.eventService.deleteUserShift({ - shiftId, - userId: locals.user.id - }); + return { success: true }; + }, + leave: async ({ request, locals }) => { + if (!locals.user) { + return fail(401, { + message: 'Not logged in' + }); + } - return { success: true }; - } + const formData = await request.formData(); + const shiftId = formData.get('shiftId'); + if (!shiftId || typeof shiftId !== 'string') { + return fail(400, { + message: 'Missing shiftId' + }); + } + + await locals.eventService.deleteUserShift({ + shiftId, + userId: locals.user.id + }); + + return { success: true }; + } }; diff --git a/apps/www/src/routes/portal/arrangementer/[id]/+page.svelte b/apps/www/src/routes/portal/arrangementer/[id]/+page.svelte index f7c99449..81c2f091 100644 --- a/apps/www/src/routes/portal/arrangementer/[id]/+page.svelte +++ b/apps/www/src/routes/portal/arrangementer/[id]/+page.svelte @@ -45,9 +45,10 @@
- Farlig - -
- -
+ {#if data.user?.role === 'board'} + Farlig +
+ +
+ {/if}
From e3ac3f719121ee2baa5dd2d9e5fe7d9b25fc0a21 Mon Sep 17 00:00:00 2001 From: "Gard.Kalland" Date: Thu, 3 Apr 2025 13:37:47 +0200 Subject: [PATCH 3/7] Emails --- apps/www/src/lib/services/email.service.tsx | 1 + apps/www/src/routes/api/events/+server.ts | 88 ++++++++++++++----- .../portal/arrangementer/ny/+page.svelte | 61 ++++++++----- 3 files changed, 108 insertions(+), 42 deletions(-) diff --git a/apps/www/src/lib/services/email.service.tsx b/apps/www/src/lib/services/email.service.tsx index c8607f9f..456ce97f 100644 --- a/apps/www/src/lib/services/email.service.tsx +++ b/apps/www/src/lib/services/email.service.tsx @@ -78,6 +78,7 @@ export class EmailService { async sendShiftEmail(data: ShiftEmailProps) { const icsContent = generateICS(data.shift); + await this.sendEmail({ from: FROM_EMAIL, subject: 'Du har fått en vakt', diff --git a/apps/www/src/routes/api/events/+server.ts b/apps/www/src/routes/api/events/+server.ts index 9771eefd..de794fa5 100644 --- a/apps/www/src/routes/api/events/+server.ts +++ b/apps/www/src/routes/api/events/+server.ts @@ -1,35 +1,79 @@ import { CreateEventSchema } from '$lib/validators'; import type { RequestHandler } from './$types'; +import type { ShiftEmailProps } from '$lib/services/email.service'; export const POST: RequestHandler = async ({ request, locals }) => { - if (!locals.user) { - return new Response(null, { status: 401 }); - } + if (!locals.user) { + return new Response(null, { status: 401 }); + } + const { name, date, shifts: jshifts } = await request.json().then(CreateEventSchema.parse); + const event = await locals.eventService.create(name, date); + if (!event) { + return new Response(null, { status: 500 }); + } + const shiftsToInsert = jshifts.map((shift) => ({ + eventId: event.id, + startAt: shift.startAt, + endAt: shift.endAt + })); + const createdShifts = await locals.eventService.createShifts(shiftsToInsert); + const userShiftsToInsert = createdShifts?.flatMap((shift, shiftIndex) => { + return jshifts[shiftIndex].users.map((user) => ({ + shiftId: shift.id, + userId: user + })); + }); + await locals.eventService.createUserShifts(userShiftsToInsert ?? []); - const { name, date, shifts: jshifts } = await request.json().then(CreateEventSchema.parse); + // Send email notifications with ICS calendar attachments + const emailPromises = []; - const event = await locals.eventService.create(name, date); + if (createdShifts && createdShifts.length > 0) { + for (let i = 0; i < createdShifts.length; i++) { + const shift = createdShifts[i]; + const shiftData = jshifts[i]; - if (!event) { - return new Response(null, { status: 500 }); - } + for (const userId of shiftData.users) { + // Get user details from database + const user = await locals.userService.findById(userId); - const shiftsToInsert = jshifts.map((shift) => ({ - eventId: event.id, - startAt: shift.startAt, - endAt: shift.endAt - })); + if (user && user.email) { + // Prepare email data + const emailData: ShiftEmailProps = { + user: { + name: user.name || 'Frivillig', + email: user.email + }, + shift: { + startAt: new Date(shift.startAt).toISOString(), + endAt: new Date(shift.endAt).toISOString(), + summary: `Vakt: ${name}`, + description: `Du har fŒtt en vakt pŒ Programmerbar for arrangementet "${name}".` + } + }; - const createdShifts = await locals.eventService.createShifts(shiftsToInsert); + // Send the email + emailPromises.push(locals.emailService.sendShiftEmail(emailData)); - const userShiftsToInsert = createdShifts?.flatMap((shift, shiftIndex) => { - return jshifts[shiftIndex].users.map((user) => ({ - shiftId: shift.id, - userId: user - })); - }); + // Log email sending + console.log(`Sending shift email to ${user.email} for shift ${shift.id}`); + } + } + } + } - await locals.eventService.createUserShifts(userShiftsToInsert ?? []); + // Wait for all emails to be sent (but don't fail if some emails fail) + if (emailPromises.length > 0) { + try { + await Promise.allSettled(emailPromises); + console.log(`Sent ${emailPromises.length} shift notification emails`); + } catch (emailError) { + console.error('Error sending shift emails:', emailError); + } + } - return new Response(null, { status: 201 }); + return new Response(JSON.stringify({ eventId: event.id }), { + status: 201, + headers: { 'Content-Type': 'application/json' } + }); }; diff --git a/apps/www/src/routes/portal/arrangementer/ny/+page.svelte b/apps/www/src/routes/portal/arrangementer/ny/+page.svelte index 69d1b76d..ec37d61b 100644 --- a/apps/www/src/routes/portal/arrangementer/ny/+page.svelte +++ b/apps/www/src/routes/portal/arrangementer/ny/+page.svelte @@ -9,27 +9,43 @@ import { differenceInHours } from 'date-fns'; let { data } = $props(); - let error = $state(''); - + let successMessage = $state(''); + let isSubmitting = $state(false); let createEventState = new CreateEventState(); const handleSubmit: EventHandler = async (e) => { e.preventDefault(); - if (!createEventState.isValid()) { error = 'Vennligst fyll ut alle feltene'; console.log(createEventState.json()); return; } - const response = await fetch('/api/events', { - method: 'POST', - body: JSON.stringify(createEventState.json()) - }); - - if (response.status === 201) { - createEventState.reset(); + isSubmitting = true; + error = ''; + successMessage = ''; + + try { + const response = await fetch('/api/events', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(createEventState.json()) + }); + + if (response.status === 201) { + successMessage = 'Arrangement opprettet! E-post er sendt.'; + createEventState.reset(); + } else { + error = 'Noe gikk galt ved oppretting av arrangement'; + } + } catch (err) { + error = 'Server error'; + console.error(err); + } finally { + isSubmitting = false; } }; @@ -41,7 +57,15 @@ Nytt arrangement {#if error} -

{error}

+
+

{error}

+
+{/if} + +{#if successMessage} +
+

{successMessage}

+
{/if}
@@ -59,7 +83,6 @@ type="datetime-local" required /> - {#each createEventState.shifts as shift, i} {@const shiftLength = differenceInHours(shift.endAt, shift.startAt)}
@@ -70,7 +93,6 @@ > -

Vakt {i + 1}

- {#if shiftLength >= 4} NB: Vakten er lengre enn 4 timer! {/if} - Ansvarlige - +
+ Alle ansvarlige vil få en e-post med kalenderinvitasjon når arrangementet opprettes. +
{#each createEventState.shifts[i].users as user, j (user)}
Ingen ansvarlige. Husk å legge til.

{/each} -
{/each} - - - + From 3955d20e3190665518d46013f097f5fd69fd7c6e Mon Sep 17 00:00:00 2001 From: "Gard.Kalland" Date: Thu, 3 Apr 2025 15:51:20 +0200 Subject: [PATCH 4/7] Shift email --- apps/www/src/lib/services/email.service.tsx | 8 ++++++++ apps/www/src/routes/api/events/+server.ts | 12 +++++------- .../routes/portal/arrangementer/[id]/+page.server.ts | 1 - internal/emails/emails/shiftemail.tsx | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/apps/www/src/lib/services/email.service.tsx b/apps/www/src/lib/services/email.service.tsx index 456ce97f..b792a555 100644 --- a/apps/www/src/lib/services/email.service.tsx +++ b/apps/www/src/lib/services/email.service.tsx @@ -101,9 +101,17 @@ export class EmailService { console.log('#############################'); console.log('########### EMAIL ############'); + console.log(`Sending real email to: ${payload.to}`); console.log(payload.html); console.log('#############################'); + + if (payload.attachments) { + console.log('########### ATTACHMENTS ############'); + console.log(payload.attachments); + } + + return; } diff --git a/apps/www/src/routes/api/events/+server.ts b/apps/www/src/routes/api/events/+server.ts index de794fa5..4f5d3712 100644 --- a/apps/www/src/routes/api/events/+server.ts +++ b/apps/www/src/routes/api/events/+server.ts @@ -6,16 +6,20 @@ export const POST: RequestHandler = async ({ request, locals }) => { if (!locals.user) { return new Response(null, { status: 401 }); } + const { name, date, shifts: jshifts } = await request.json().then(CreateEventSchema.parse); const event = await locals.eventService.create(name, date); + if (!event) { return new Response(null, { status: 500 }); } + const shiftsToInsert = jshifts.map((shift) => ({ eventId: event.id, startAt: shift.startAt, endAt: shift.endAt })); + const createdShifts = await locals.eventService.createShifts(shiftsToInsert); const userShiftsToInsert = createdShifts?.flatMap((shift, shiftIndex) => { return jshifts[shiftIndex].users.map((user) => ({ @@ -25,7 +29,6 @@ export const POST: RequestHandler = async ({ request, locals }) => { }); await locals.eventService.createUserShifts(userShiftsToInsert ?? []); - // Send email notifications with ICS calendar attachments const emailPromises = []; if (createdShifts && createdShifts.length > 0) { @@ -34,11 +37,9 @@ export const POST: RequestHandler = async ({ request, locals }) => { const shiftData = jshifts[i]; for (const userId of shiftData.users) { - // Get user details from database const user = await locals.userService.findById(userId); if (user && user.email) { - // Prepare email data const emailData: ShiftEmailProps = { user: { name: user.name || 'Frivillig', @@ -48,21 +49,18 @@ export const POST: RequestHandler = async ({ request, locals }) => { startAt: new Date(shift.startAt).toISOString(), endAt: new Date(shift.endAt).toISOString(), summary: `Vakt: ${name}`, - description: `Du har fŒtt en vakt pŒ Programmerbar for arrangementet "${name}".` + description: `Du har fått en vakt! På "${name}".` } }; - // Send the email emailPromises.push(locals.emailService.sendShiftEmail(emailData)); - // Log email sending console.log(`Sending shift email to ${user.email} for shift ${shift.id}`); } } } } - // Wait for all emails to be sent (but don't fail if some emails fail) if (emailPromises.length > 0) { try { await Promise.allSettled(emailPromises); diff --git a/apps/www/src/routes/portal/arrangementer/[id]/+page.server.ts b/apps/www/src/routes/portal/arrangementer/[id]/+page.server.ts index 39874958..69f6c276 100644 --- a/apps/www/src/routes/portal/arrangementer/[id]/+page.server.ts +++ b/apps/www/src/routes/portal/arrangementer/[id]/+page.server.ts @@ -16,7 +16,6 @@ export const load: PageServerLoad = async ({ locals, params }) => { export const actions: Actions = { delete: async ({ params, locals }) => { if (locals.user?.role === 'board') { - await locals.eventService.delete(params.id); throw redirect(303, '/portal/arrangementer'); } diff --git a/internal/emails/emails/shiftemail.tsx b/internal/emails/emails/shiftemail.tsx index 9fa813ee..e1e71966 100644 --- a/internal/emails/emails/shiftemail.tsx +++ b/internal/emails/emails/shiftemail.tsx @@ -66,7 +66,7 @@ const ShiftEmail = ({ shift, user }: ShiftEmailProps) => { ShiftEmail.PreviewProps = { shift: { startAt: new Date().toISOString(), - endAt: new Date(Date.now() + 3600000).toISOString(), // 1 hour later + endAt: new Date(Date.now() + 3600000).toISOString(), summary: "Vakt for programmerbar fredagsåpent", description: "Programmerbar.", }, From 56febff31f21e6a4ad4c857a8ca4785ad17a7839 Mon Sep 17 00:00:00 2001 From: "Gard.Kalland" Date: Thu, 3 Apr 2025 15:52:17 +0200 Subject: [PATCH 5/7] format --- apps/www/src/hooks.server.ts | 198 +++++++++--------- apps/www/src/lib/services/email.service.tsx | 179 ++++++++-------- apps/www/src/lib/validators.ts | 46 ++-- apps/www/src/routes/api/events/+server.ts | 118 +++++------ apps/www/src/routes/api/shiftEmail/+server.ts | 20 +- .../portal/arrangementer/[id]/+page.server.ts | 110 +++++----- 6 files changed, 337 insertions(+), 334 deletions(-) diff --git a/apps/www/src/hooks.server.ts b/apps/www/src/hooks.server.ts index d3a0edab..a07cc4e9 100644 --- a/apps/www/src/hooks.server.ts +++ b/apps/www/src/hooks.server.ts @@ -15,103 +15,103 @@ import type { Handle } from '@sveltejs/kit'; import { Resend } from 'resend'; export const handle: Handle = async ({ event, resolve }) => { - const banService = new BanService(event.platform!.env.STATUS_KV); - event.locals.banService = banService; - - const ip = event.getClientAddress(); - const isIpBanned = await banService.isIpBanned(ip); - if (isIpBanned) { - return new Response(null, { - status: 429, - headers: { - 'retry-after': '3600' - } - }); - } - - // Setup Resend - const resend = new Resend(event.platform?.env.RESEND_API_KEY); - event.locals.resend = resend; - - // Setup database - const db = createDatabase(event.platform!.env.DB); - event.locals.db = db; - - // Setup auth - const auth = createAuth(db); - event.locals.auth = auth; - - // Setup feide provider - const feideProvider = createFeideProvider( - event.platform!.env.FEIDE_CLIENT_ID, - event.platform!.env.FEIDE_CLIENT_SECRET, - event.platform!.env.FEIDE_REDIRECT_URI - ); - event.locals.feideProvider = feideProvider; - - // Setup status service - const statusService = new StatusService(event.platform!.env.STATUS_KV); - event.locals.statusService = statusService; - - const invitationService = new InvitationService(db); - event.locals.invitationService = invitationService; - - //const emailShiftService = new EmailShiftService(db); - //event.locals.emailShiftService = emailShiftService; - - const userService = new UserService(db); - event.locals.userService = userService; - - const eventService = new EventService(db); - event.locals.eventService = eventService; - - const emailService = new EmailService(resend); - event.locals.emailService = emailService; - - const shiftService = new ShiftService(db); - event.locals.shiftService = shiftService; - - const beerService = new BeerService(db, shiftService); - event.locals.beerService = beerService; - - // Validate auth - const sessionId = event.cookies.get(auth.sessionCookieName); - - if (sessionId) { - const { session, user } = await auth.validateSession(sessionId); - - event.locals.user = user; - event.locals.session = session; - } else { - event.locals.user = null; - event.locals.session = null; - } - - if (!event.locals.user) { - event.cookies.delete(auth.sessionCookieName, { - path: '/', - httpOnly: true, - secure: !dev - }); - } - - if (event.url.pathname.startsWith('/portal/admin') && event.locals.user?.role !== 'board') { - return new Response(null, { - status: 307, - headers: { - location: '/logg-inn' - } - }); - } - - if (event.url.pathname.startsWith('/portal') && !event.locals.user) { - return new Response(null, { - status: 307, - headers: { - location: '/logg-inn' - } - }); - } - - return await resolve(event); + const banService = new BanService(event.platform!.env.STATUS_KV); + event.locals.banService = banService; + + const ip = event.getClientAddress(); + const isIpBanned = await banService.isIpBanned(ip); + if (isIpBanned) { + return new Response(null, { + status: 429, + headers: { + 'retry-after': '3600' + } + }); + } + + // Setup Resend + const resend = new Resend(event.platform?.env.RESEND_API_KEY); + event.locals.resend = resend; + + // Setup database + const db = createDatabase(event.platform!.env.DB); + event.locals.db = db; + + // Setup auth + const auth = createAuth(db); + event.locals.auth = auth; + + // Setup feide provider + const feideProvider = createFeideProvider( + event.platform!.env.FEIDE_CLIENT_ID, + event.platform!.env.FEIDE_CLIENT_SECRET, + event.platform!.env.FEIDE_REDIRECT_URI + ); + event.locals.feideProvider = feideProvider; + + // Setup status service + const statusService = new StatusService(event.platform!.env.STATUS_KV); + event.locals.statusService = statusService; + + const invitationService = new InvitationService(db); + event.locals.invitationService = invitationService; + + //const emailShiftService = new EmailShiftService(db); + //event.locals.emailShiftService = emailShiftService; + + const userService = new UserService(db); + event.locals.userService = userService; + + const eventService = new EventService(db); + event.locals.eventService = eventService; + + const emailService = new EmailService(resend); + event.locals.emailService = emailService; + + const shiftService = new ShiftService(db); + event.locals.shiftService = shiftService; + + const beerService = new BeerService(db, shiftService); + event.locals.beerService = beerService; + + // Validate auth + const sessionId = event.cookies.get(auth.sessionCookieName); + + if (sessionId) { + const { session, user } = await auth.validateSession(sessionId); + + event.locals.user = user; + event.locals.session = session; + } else { + event.locals.user = null; + event.locals.session = null; + } + + if (!event.locals.user) { + event.cookies.delete(auth.sessionCookieName, { + path: '/', + httpOnly: true, + secure: !dev + }); + } + + if (event.url.pathname.startsWith('/portal/admin') && event.locals.user?.role !== 'board') { + return new Response(null, { + status: 307, + headers: { + location: '/logg-inn' + } + }); + } + + if (event.url.pathname.startsWith('/portal') && !event.locals.user) { + return new Response(null, { + status: 307, + headers: { + location: '/logg-inn' + } + }); + } + + return await resolve(event); }; diff --git a/apps/www/src/lib/services/email.service.tsx b/apps/www/src/lib/services/email.service.tsx index b792a555..50549451 100644 --- a/apps/www/src/lib/services/email.service.tsx +++ b/apps/www/src/lib/services/email.service.tsx @@ -7,37 +7,42 @@ const PROGRAMMERBAR_EMAIL = 'styret@programmerbar.no'; const FROM_EMAIL = 'ikkesvar@programmer.bar'; export type ContactUsEmailProps = { - name: string; - email: string; - message: string; + name: string; + email: string; + message: string; }; export type InvitationEmailProps = { - email: string; + email: string; }; export type ShiftEmailProps = { - shift: { - startAt: string; - endAt: string; - summary: string; - description?: string; - }; - user: { - name: string; - email: string; - }; + shift: { + startAt: string; + endAt: string; + summary: string; + description?: string; + }; + user: { + name: string; + email: string; + }; }; -function generateICS(shift: { startAt: string; endAt: string; summary: string; description?: string }): string { - const uid = `${Date.now()}@programmerbar.no`; - const formatDate = (dateStr: string) => - new Date(dateStr).toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z'; - const dtstamp = formatDate(new Date().toISOString()); - const dtstart = formatDate(shift.startAt); - const dtend = formatDate(shift.endAt); - - return `BEGIN:VCALENDAR +function generateICS(shift: { + startAt: string; + endAt: string; + summary: string; + description?: string; +}): string { + const uid = `${Date.now()}@programmerbar.no`; + const formatDate = (dateStr: string) => + new Date(dateStr).toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z'; + const dtstamp = formatDate(new Date().toISOString()); + const dtstart = formatDate(shift.startAt); + const dtend = formatDate(shift.endAt); + + return `BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Programmerbar//Shift Notification//EN BEGIN:VEVENT @@ -52,69 +57,67 @@ END:VCALENDAR`; } export class EmailService { - #resend: Resend; - - constructor(resend: Resend) { - this.#resend = resend; - } - - async sendContactUsEmail(data: ContactUsEmailProps) { - await this.sendEmail({ - from: FROM_EMAIL, - subject: 'Kontaktskjema pÃ¥ hjemmesiden', - to: [PROGRAMMERBAR_EMAIL], - html: await render(ContactUsEmail({ ...data })) - }); - } - - async sendInvitaitonEmail(data: InvitationEmailProps) { - await this.sendEmail({ - from: FROM_EMAIL, - subject: 'Invitasjon til Programmerbar', - to: [data.email], - html: await render(InvitationEmail({ ...data })) - }); - } - - async sendShiftEmail(data: ShiftEmailProps) { - const icsContent = generateICS(data.shift); - - await this.sendEmail({ - from: FROM_EMAIL, - subject: 'Du har fått en vakt', - to: [data.user.email], - html: await render(ShiftEmail({ ...data })), - attachments: [ - { - filename: 'shift.ics', - content: icsContent, - contentType: 'text/calendar' - } - ] - }); - } - - private async sendEmail(payload: CreateEmailOptions) { - if (dev) { - console.log('#############################'); - console.log('# NOT SENDING EMAILS IN DEV #'); - console.log('#############################'); - - console.log('########### EMAIL ############'); - console.log(`Sending real email to: ${payload.to}`); - console.log(payload.html); - console.log('#############################'); - - - if (payload.attachments) { - console.log('########### ATTACHMENTS ############'); - console.log(payload.attachments); - } - - - return; - } - - await this.#resend.emails.send(payload); - } + #resend: Resend; + + constructor(resend: Resend) { + this.#resend = resend; + } + + async sendContactUsEmail(data: ContactUsEmailProps) { + await this.sendEmail({ + from: FROM_EMAIL, + subject: 'Kontaktskjema pÃ¥ hjemmesiden', + to: [PROGRAMMERBAR_EMAIL], + html: await render(ContactUsEmail({ ...data })) + }); + } + + async sendInvitaitonEmail(data: InvitationEmailProps) { + await this.sendEmail({ + from: FROM_EMAIL, + subject: 'Invitasjon til Programmerbar', + to: [data.email], + html: await render(InvitationEmail({ ...data })) + }); + } + + async sendShiftEmail(data: ShiftEmailProps) { + const icsContent = generateICS(data.shift); + + await this.sendEmail({ + from: FROM_EMAIL, + subject: 'Du har f�tt en vakt', + to: [data.user.email], + html: await render(ShiftEmail({ ...data })), + attachments: [ + { + filename: 'shift.ics', + content: icsContent, + contentType: 'text/calendar' + } + ] + }); + } + + private async sendEmail(payload: CreateEmailOptions) { + if (dev) { + console.log('#############################'); + console.log('# NOT SENDING EMAILS IN DEV #'); + console.log('#############################'); + + console.log('########### EMAIL ############'); + console.log(`Sending real email to: ${payload.to}`); + console.log(payload.html); + console.log('#############################'); + + if (payload.attachments) { + console.log('########### ATTACHMENTS ############'); + console.log(payload.attachments); + } + + return; + } + + await this.#resend.emails.send(payload); + } } diff --git a/apps/www/src/lib/validators.ts b/apps/www/src/lib/validators.ts index 7c997570..b4c4f8c4 100644 --- a/apps/www/src/lib/validators.ts +++ b/apps/www/src/lib/validators.ts @@ -2,36 +2,36 @@ import { z } from 'zod'; import { zfd } from 'zod-form-data'; export const CreateEventSchema = z.object({ - name: z.string(), - date: z.coerce.date(), - shifts: z - .object({ - startAt: z.coerce.date(), - endAt: z.coerce.date(), - users: z.array(z.string()) - }) - .array() + name: z.string(), + date: z.coerce.date(), + shifts: z + .object({ + startAt: z.coerce.date(), + endAt: z.coerce.date(), + users: z.array(z.string()) + }) + .array() }); export const ContactUsSchema = zfd.formData({ - namekjkj: zfd.text(z.string().min(2).max(50)), - emailkjkj: zfd.text(z.string().email().min(3)), - messagekjkj: zfd.text(z.string().min(5).max(1000)) + namekjkj: zfd.text(z.string().min(2).max(50)), + emailkjkj: zfd.text(z.string().email().min(3)), + messagekjkj: zfd.text(z.string().min(5).max(1000)) }); export const CreateInvitationSchema = z.object({ - email: z.string().email() + email: z.string().email() }); export const CreateEmailShiftSchema = z.object({ - user: z.object({ - name: z.string().min(1), - email: z.string().email() - }), - shift: z.object({ - startAt: z.coerce.date(), - endAt: z.coerce.date(), - summary: z.string().min(1), - description: z.string().optional() - }) + user: z.object({ + name: z.string().min(1), + email: z.string().email() + }), + shift: z.object({ + startAt: z.coerce.date(), + endAt: z.coerce.date(), + summary: z.string().min(1), + description: z.string().optional() + }) }); diff --git a/apps/www/src/routes/api/events/+server.ts b/apps/www/src/routes/api/events/+server.ts index 4f5d3712..3503e8a2 100644 --- a/apps/www/src/routes/api/events/+server.ts +++ b/apps/www/src/routes/api/events/+server.ts @@ -3,75 +3,75 @@ import type { RequestHandler } from './$types'; import type { ShiftEmailProps } from '$lib/services/email.service'; export const POST: RequestHandler = async ({ request, locals }) => { - if (!locals.user) { - return new Response(null, { status: 401 }); - } + if (!locals.user) { + return new Response(null, { status: 401 }); + } - const { name, date, shifts: jshifts } = await request.json().then(CreateEventSchema.parse); - const event = await locals.eventService.create(name, date); + const { name, date, shifts: jshifts } = await request.json().then(CreateEventSchema.parse); + const event = await locals.eventService.create(name, date); - if (!event) { - return new Response(null, { status: 500 }); - } + if (!event) { + return new Response(null, { status: 500 }); + } - const shiftsToInsert = jshifts.map((shift) => ({ - eventId: event.id, - startAt: shift.startAt, - endAt: shift.endAt - })); + const shiftsToInsert = jshifts.map((shift) => ({ + eventId: event.id, + startAt: shift.startAt, + endAt: shift.endAt + })); - const createdShifts = await locals.eventService.createShifts(shiftsToInsert); - const userShiftsToInsert = createdShifts?.flatMap((shift, shiftIndex) => { - return jshifts[shiftIndex].users.map((user) => ({ - shiftId: shift.id, - userId: user - })); - }); - await locals.eventService.createUserShifts(userShiftsToInsert ?? []); + const createdShifts = await locals.eventService.createShifts(shiftsToInsert); + const userShiftsToInsert = createdShifts?.flatMap((shift, shiftIndex) => { + return jshifts[shiftIndex].users.map((user) => ({ + shiftId: shift.id, + userId: user + })); + }); + await locals.eventService.createUserShifts(userShiftsToInsert ?? []); - const emailPromises = []; + const emailPromises = []; - if (createdShifts && createdShifts.length > 0) { - for (let i = 0; i < createdShifts.length; i++) { - const shift = createdShifts[i]; - const shiftData = jshifts[i]; + if (createdShifts && createdShifts.length > 0) { + for (let i = 0; i < createdShifts.length; i++) { + const shift = createdShifts[i]; + const shiftData = jshifts[i]; - for (const userId of shiftData.users) { - const user = await locals.userService.findById(userId); + for (const userId of shiftData.users) { + const user = await locals.userService.findById(userId); - if (user && user.email) { - const emailData: ShiftEmailProps = { - user: { - name: user.name || 'Frivillig', - email: user.email - }, - shift: { - startAt: new Date(shift.startAt).toISOString(), - endAt: new Date(shift.endAt).toISOString(), - summary: `Vakt: ${name}`, - description: `Du har fått en vakt! På "${name}".` - } - }; + if (user && user.email) { + const emailData: ShiftEmailProps = { + user: { + name: user.name || 'Frivillig', + email: user.email + }, + shift: { + startAt: new Date(shift.startAt).toISOString(), + endAt: new Date(shift.endAt).toISOString(), + summary: `Vakt: ${name}`, + description: `Du har f�tt en vakt! P� "${name}".` + } + }; - emailPromises.push(locals.emailService.sendShiftEmail(emailData)); + emailPromises.push(locals.emailService.sendShiftEmail(emailData)); - console.log(`Sending shift email to ${user.email} for shift ${shift.id}`); - } - } - } - } + console.log(`Sending shift email to ${user.email} for shift ${shift.id}`); + } + } + } + } - if (emailPromises.length > 0) { - try { - await Promise.allSettled(emailPromises); - console.log(`Sent ${emailPromises.length} shift notification emails`); - } catch (emailError) { - console.error('Error sending shift emails:', emailError); - } - } + if (emailPromises.length > 0) { + try { + await Promise.allSettled(emailPromises); + console.log(`Sent ${emailPromises.length} shift notification emails`); + } catch (emailError) { + console.error('Error sending shift emails:', emailError); + } + } - return new Response(JSON.stringify({ eventId: event.id }), { - status: 201, - headers: { 'Content-Type': 'application/json' } - }); + return new Response(JSON.stringify({ eventId: event.id }), { + status: 201, + headers: { 'Content-Type': 'application/json' } + }); }; diff --git a/apps/www/src/routes/api/shiftEmail/+server.ts b/apps/www/src/routes/api/shiftEmail/+server.ts index 78c70681..7054c4d6 100644 --- a/apps/www/src/routes/api/shiftEmail/+server.ts +++ b/apps/www/src/routes/api/shiftEmail/+server.ts @@ -2,17 +2,17 @@ import { CreateEmailShiftSchema } from '$lib/validators'; import type { RequestHandler } from './$types'; export const POST: RequestHandler = async ({ request, locals }) => { - if (!locals.user) { - return new Response(null, { status: 401 }); - } + if (!locals.user) { + return new Response(null, { status: 401 }); + } - const shiftEmailData = await request - .json() - .then(CreateEmailShiftSchema.parse) - .then((data) => data.email.toLowerCase()); + const shiftEmailData = await request + .json() + .then(CreateEmailShiftSchema.parse) + .then((data) => data.email.toLowerCase()); - await locals.emailShiftService.create(shiftEmailData); - await locals.emailService.sendShiftEmail(shiftEmailData); + await locals.emailShiftService.create(shiftEmailData); + await locals.emailService.sendShiftEmail(shiftEmailData); - return new Response(null, { status: 201 }); + return new Response(null, { status: 201 }); }; diff --git a/apps/www/src/routes/portal/arrangementer/[id]/+page.server.ts b/apps/www/src/routes/portal/arrangementer/[id]/+page.server.ts index 69f6c276..6d024e44 100644 --- a/apps/www/src/routes/portal/arrangementer/[id]/+page.server.ts +++ b/apps/www/src/routes/portal/arrangementer/[id]/+page.server.ts @@ -2,71 +2,71 @@ import { error, fail, redirect } from '@sveltejs/kit'; import type { Actions, PageServerLoad } from './$types'; export const load: PageServerLoad = async ({ locals, params }) => { - const event = await locals.eventService.findFullEventById(params.id); + const event = await locals.eventService.findFullEventById(params.id); - if (!event) { - throw error(404, 'Event not found'); - } + if (!event) { + throw error(404, 'Event not found'); + } - return { - event - }; + return { + event + }; }; export const actions: Actions = { - delete: async ({ params, locals }) => { - if (locals.user?.role === 'board') { - await locals.eventService.delete(params.id); - throw redirect(303, '/portal/arrangementer'); - } + delete: async ({ params, locals }) => { + if (locals.user?.role === 'board') { + await locals.eventService.delete(params.id); + throw redirect(303, '/portal/arrangementer'); + } - return fail(401, { - message: 'Unauthorized' - }); - }, - join: async ({ request, locals }) => { - if (!locals.user) { - return fail(401, { - message: 'Not logged in' - }); - } + return fail(401, { + message: 'Unauthorized' + }); + }, + join: async ({ request, locals }) => { + if (!locals.user) { + return fail(401, { + message: 'Not logged in' + }); + } - const formData = await request.formData(); - const shiftId = formData.get('shiftId'); - if (!shiftId || typeof shiftId !== 'string') { - return fail(400, { - message: 'Missing shiftId' - }); - } + const formData = await request.formData(); + const shiftId = formData.get('shiftId'); + if (!shiftId || typeof shiftId !== 'string') { + return fail(400, { + message: 'Missing shiftId' + }); + } - await locals.eventService.createUserShift({ - shiftId, - userId: locals.user.id, - status: 'accepted' - }); + await locals.eventService.createUserShift({ + shiftId, + userId: locals.user.id, + status: 'accepted' + }); - return { success: true }; - }, - leave: async ({ request, locals }) => { - if (!locals.user) { - return fail(401, { - message: 'Not logged in' - }); - } + return { success: true }; + }, + leave: async ({ request, locals }) => { + if (!locals.user) { + return fail(401, { + message: 'Not logged in' + }); + } - const formData = await request.formData(); - const shiftId = formData.get('shiftId'); - if (!shiftId || typeof shiftId !== 'string') { - return fail(400, { - message: 'Missing shiftId' - }); - } + const formData = await request.formData(); + const shiftId = formData.get('shiftId'); + if (!shiftId || typeof shiftId !== 'string') { + return fail(400, { + message: 'Missing shiftId' + }); + } - await locals.eventService.deleteUserShift({ - shiftId, - userId: locals.user.id - }); + await locals.eventService.deleteUserShift({ + shiftId, + userId: locals.user.id + }); - return { success: true }; - } + return { success: true }; + } }; From 59952b9a445d6e02a188c57d3e196e74dd0703a2 Mon Sep 17 00:00:00 2001 From: Gard <128602894+GardKalland@users.noreply.github.com> Date: Sun, 6 Apr 2025 14:44:25 +0200 Subject: [PATCH 6/7] deleted emailshift Denne blei redundant. --- apps/www/src/routes/api/shiftEmail/+server.ts | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 apps/www/src/routes/api/shiftEmail/+server.ts diff --git a/apps/www/src/routes/api/shiftEmail/+server.ts b/apps/www/src/routes/api/shiftEmail/+server.ts deleted file mode 100644 index 7054c4d6..00000000 --- a/apps/www/src/routes/api/shiftEmail/+server.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CreateEmailShiftSchema } from '$lib/validators'; -import type { RequestHandler } from './$types'; - -export const POST: RequestHandler = async ({ request, locals }) => { - if (!locals.user) { - return new Response(null, { status: 401 }); - } - - const shiftEmailData = await request - .json() - .then(CreateEmailShiftSchema.parse) - .then((data) => data.email.toLowerCase()); - - await locals.emailShiftService.create(shiftEmailData); - await locals.emailService.sendShiftEmail(shiftEmailData); - - return new Response(null, { status: 201 }); -}; From 351d6815464432af7e580d727c04182acfbeb764 Mon Sep 17 00:00:00 2001 From: Gard <128602894+GardKalland@users.noreply.github.com> Date: Sun, 6 Apr 2025 14:53:24 +0200 Subject: [PATCH 7/7] Update hooks.server.ts Trengte ikkje den pga fjernet emailshift --- apps/www/src/hooks.server.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/www/src/hooks.server.ts b/apps/www/src/hooks.server.ts index a07cc4e9..0dbafbad 100644 --- a/apps/www/src/hooks.server.ts +++ b/apps/www/src/hooks.server.ts @@ -7,7 +7,6 @@ import { BeerService } from '$lib/services/beer.service'; import { EmailService } from '$lib/services/email.service'; import { EventService } from '$lib/services/event.service'; import { InvitationService } from '$lib/services/invitation.service'; -//import { EmailShiftService } from '$lib/services/.service'; import { ShiftService } from '$lib/services/shift.service'; import { StatusService } from '$lib/services/status.service'; import { UserService } from '$lib/services/user.service'; @@ -56,9 +55,6 @@ export const handle: Handle = async ({ event, resolve }) => { const invitationService = new InvitationService(db); event.locals.invitationService = invitationService; - //const emailShiftService = new EmailShiftService(db); - //event.locals.emailShiftService = emailShiftService; - const userService = new UserService(db); event.locals.userService = userService;