Skip to content

Commit

Permalink
feat: add better clean up and cancel workflow (#91)
Browse files Browse the repository at this point in the history
Co-authored-by: Anton Lilleby <[email protected]>
  • Loading branch information
an2n and Anton Lilleby authored Jul 29, 2024
1 parent 919004a commit 27e6a3b
Show file tree
Hide file tree
Showing 11 changed files with 308 additions and 250 deletions.
114 changes: 67 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ Design drodling finner man her: [Nettside design](https://www.figma.com/design/Z

## Komme i gang

For å kjøre koden:
For å kjøre koden loaklt:

1. Be om environment variabler for lokal testing i kanalen [#tmp_arrangementsoversikt]().
Du må selv opprette en `.env` fil i /studio og /app.
Du må selv opprette en `.env.local` fil i /studio og /app.

Hvis du trenger tilgang til Sanity Studio, eventuelt Google Console, Vercel og Supabase, må dette også spesifikt forespørres.

2. Installer dependencies:

Expand All @@ -67,13 +69,13 @@ cd capra-web/studio
pnpm dev
```

- SvelteKit skal nå kjøre på [http://localhost:5173](http://localhost:517/)
- SvelteKit applikasjonen skal nå kjøre på [http://localhost:5173](http://localhost:517/)
- Sanity Studio skal kjøre på [http://localhost:3333](http://localhost:3333)

NB: Du kan også starte dev serverne hver for seg i deres respektive mapper.

## Sanity

Vi har to dataset i Sanity studio, en for dev testing og en for produksjon.

### Bygg

For å bygge en produksjonsversjon av Sanity studio lokalt, naviger deg til /studio og kjør følgende kommando:
Expand All @@ -87,7 +89,7 @@ Bygg bør alltid kjøres som en del av vår pull request policy 👷
### Deploy

Sanity Studio blir deployet til [https://capra.sanity.studio](https://capra.sanity.studio).
GitHub Actions CI/CD deploy kjører automatisk ved push til main-branch og ved endringer i /studio mappen. Alternativt kan deploy også utføres manuelt ved å navigere til /studio-katalogen og kjøre følgende kommando:
GitHub Actions deploy kjører automatisk ved push til main-branch og ved endringer i /studio mappen. Alternativt kan deploy også utføres manuelt ved å navigere til /studio og kjøre følgende kommando:

```bash
sanity deploy
Expand All @@ -106,11 +108,6 @@ sanity typegen generate

NB: Når sanity.model.ts er generert i /studio/models, skal den også kopieres til /app.

### Lage Innhold

1. Gå inn i Sanity Studio og legg til nye events, og trykk publiser
2. Besøk SvelteKit appen, eventuelt refresh siden, og se at innholdet vises

## SvelteKit

### Bygg
Expand All @@ -137,13 +134,9 @@ vercel deploy

SvelteKit templaten [sanity-template-sveltekit-clean](https://github.com/sanity-io/sanity-template-sveltekit-clean) har en eslint konfigurasjon som ikke funker. Har prøvd å oppgradere til eslint 9 med flatconfig fra denne [issuen](https://github.com/sveltejs/eslint-plugin-svelte/issues/732). 👷 Det er en del lint-errors som må undersøkes.

### CRON

CRON jobben "daily-event-cleaner" kjører daglig i vercel for å finne arrangementer som ble avsluttet for mer enn 7 dager siden. Sletter deretter database arrangementer, deltagerinformasjonen og matpreferanser for å sikre samsvar med GDPR regelverket. Sanity arrangementet beholdes.

## Supabase

Supabase Postgres database kan konfigures fra [https://supabase.com/dashboard/project/<project-id>](https://supabase.com/dashboard/project/<project-id>).
Postgres-databasen kan konfigures fra [https://supabase.com/dashboard/project/<project-id>](https://supabase.com/dashboard/project/<project-id>). Vi har to prosjekter i supabase dashboardet, en for dev testing og en for produksjon.

### TypeScript Generering

Expand Down Expand Up @@ -183,12 +176,69 @@ E-post med kalenderinvitasjon (.ics-fil) sendes fra SvelteKit på serversiden. P

E-post domene for alle selskaper må verifiseres. Vi er på en trial-plan her og 👷

### Testing av E-post Lokalt

For å teste e-postfunksjonaliteten lokalt:

1. Fjern "development"-sjekker i funksjonskallene for å kjøre i lokalt miljø.
2. For å teste e-post sendt fra Sanity: Legg til `http://localhost:3333` i `Access-Control-Allow-Origin`.

### Kalenderinvitasjon 👷

Vi kan kun oppdatere kalenderinvitasjoner som allerede er sendt ut. Vi har ikke toveis kommunikasjon gjennom kalenderinvitasjonene, og kan derfor ikke se endringer hvis en deltager svarer Ja, Kanskje eller Nei. For å løse dette, vurderer vi å sette opp en MandrillApp webhook som kan lytte på deltagerens svar. Inntil videre må avmeldinger skje via vår nettside.

---

## Sanity Arbeidsflyt

### Publisering

1. Gå inn i Sanity Studio og legg først til et nytt arrangement, og trykk "Publiser".
2. Når et arrangement publiseres, blir det automatisk opprettet et arrangement i Postgres-databasen.
3. Besøk SvelteKit appen, eventuelt refresh siden, og se at innholdet vises.

Hvis tid eller lokasjon for et publisert arrangement endres i Sanity, følges denne prosessen:

1. En dialogboks for å bekrefte endringen vises.
2. En e-post sendes til alle påmeldte deltagere for å informere om ny tid/lokasjon.
3. Den eksisterende kalenderinvitasjonen oppdateres med de nye detaljene, slik at deltagerne har oppdatert informasjon i kalenderen.
4. Innhodet blir publisert på nytt.

### Avpublisering

1. Gå inn i Sanity Studio og trykk "Avpubliser" på et publisert arrangement.
2. Arrangement blir avpublisert og vises ikke i SvelteKit-appen.

Innholdet kan republiseres uten noen konsekvenser.

### Sletting

1. Gå inn i Sanity Studio og trykk "Slett".
2. En dialogboks for å bekrefte slettingen vises.
3. Arrangementinformasjon lagret i Sanity og Postgres-databasen blir permanent slettet.

### Avlysning

1. Gå inn i Sanity Studio og trykk "Avlys arrangement".
2. En dialogboks for å bekrefte avlysningen vises.
3. En e-post sendes ut til alle påmeldte deltagere for å informere om avlysningen.
4. Kalenderinvitasjonen markeres som avlyst i deltagerens kalender.
5. Arrangementet blir avpublisert i Sanity og innholdet blir "Read only".

Innholdet kan ikke republiseres på nytt, men kan dupliseres for nytt bruk.

### Opprydding av Arrangementer

For å oppfylle GDPR-krav og spare lagringsplass, slettes arrangementer fra Postgres-databasen som ble avsluttet for mer enn 7 dager siden. Dette håndteres av CRON-jobben "daily-event-cleaner". Innholdet forblir lagret i Sanity.

## SvelteKit Arbeidsflyt

### Påmelding

Når en bruker melder seg på et arrangement, utløses følgende prosess:

1. En e-postbekreftelse sendes til brukeren.
2. Denne e-posten inkluderer en kalenderinvitasjon med deltagerstatus satt som akseptert.
2. E-posten inkluderer en kalenderinvitasjon med deltagerstatus satt som akseptert.
3. Kalenderinvitasjonen legges automatisk inn i deltagerens kalender, slik at arrangementet blir synlig i kalenderen umiddelbart etter påmelding.

### Avmelding
Expand All @@ -205,33 +255,3 @@ Avhengig av om deltageren er intern eller ekstern, håndteres avmeldinger på fo
1. Eksterne deltagere som ønsker å melde seg av, mottar først en e-post med en lenke for å bekrefte avmeldingen.
2. Når mottaker klikker på bekreftelseslenken og den blir godkjent på nettsiden, sendes en ny e-post som bekrefter avmeldingen.
3. Kalenderinvitasjonen oppdateres til å vise status som avslått, på samme måte som for interne deltagere.

### Endring av Tid/Lokasjon

Hvis tid eller lokasjon for et arrangement endres i Sanity, følges denne prosessen:

1. Brukeren får en dialogboks for å bekrefte endringen.
2. En e-post sendes til alle påmeldte deltagere for å informere om ny tid/lokasjon.
3. Den eksisterende kalenderinvitasjonen oppdateres med de nye detaljene, slik at deltagerne har oppdatert informasjon i sine kalendere.

### Avlysing av Arrangement

Ved avlysing av et arrangement i Sanity:

1. Brukeren får en dialogboks for å bekrefte avlysningen.
2. En e-post sendes ut til alle påmeldte deltagere for å informere om avlysningen.
3. Kalenderinvitasjonen markeres som avlyst i deltagerens kalender.
4. Arrangementet blir avpublisert i Sanity og tittelen blir markert med "Avlyst"

### Testing av E-post Lokalt

For å teste e-postfunksjonaliteten lokalt:

1. Fjern "development"-sjekker i funksjonskallene for å kjøre i lokalt miljø.
2. For å teste e-post sendt fra Sanity: Legg til `http://localhost:3333` i `Access-Control-Allow-Origin`.

### Kalenderinvitasjon 👷

Vi kan kun oppdatere kalenderinvitasjoner som allerede er sendt ut. Vi har ikke toveis kommunikasjon gjennom kalenderinvitasjonene, og kan derfor ikke se endringer hvis en deltager svarer Ja, Kanskje eller Nei. For å løse dette, vurderer vi å sette opp en MandrillApp webhook som kan lytte på deltagerens svar. Inntil videre må avmeldinger skje via vår nettside.

---
7 changes: 7 additions & 0 deletions app/src/lib/server/sanity/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,10 @@ export const clientWithoutStega = client.withConfig({
useCdn: false,
stega: false,
});

export const previewDraftsClient = client.withConfig({
token,
stega: false,
useCdn: false, // must be false, required for this perspective
perspective: "previewDrafts",
});
5 changes: 3 additions & 2 deletions app/src/models/sanity.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export type BlockContent = Array<{
_type: "span";
_key: string;
}>;
style?: "normal" | "h1" | "h2" | "h3" | "h4" | "blockquote";
style?: "normal" | "h2";
listItem?: "bullet";
markDefs?: Array<{
href?: string;
Expand All @@ -95,6 +95,7 @@ export type Event = {
_createdAt: string;
_updatedAt: string;
_rev: string;
cancleId?: string;
title: string;
image?: {
asset?: {
Expand All @@ -114,13 +115,13 @@ export type Event = {
deadline: string;
category: Category;
place: string;
maxParticipant?: number;
organisers: Array<string>;
isDigital: boolean;
visibleForExternals: boolean;
openForExternals: boolean;
foodPreference: boolean;
food?: string;
maxParticipant?: number;
customOptions?: Array<{
fieldOption: string;
fieldType: "radio" | "checkbox" | "input";
Expand Down
6 changes: 3 additions & 3 deletions app/src/routes/api/daily-event-cleaner/+server.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import groq from "groq";
import type { Event } from "$models/sanity.model";
import type { RequestHandler } from "@sveltejs/kit";
import { client as sanityClient } from "$lib/sanity/client";
import { supabase } from "$lib/server/supabase/client";
import { CRON_SECRET } from "$env/static/private";
import { previewDraftsClient } from "$lib/server/sanity/client";

export const GET: RequestHandler = async ({ request }) => {
const authHeader = request.headers.get("authorization");
Expand All @@ -13,10 +13,10 @@ export const GET: RequestHandler = async ({ request }) => {

try {
const query = groq`*[_type == "event" && dateTime(end) < dateTime(now()) - 7*24*60*60]._id`;
const events = await sanityClient.fetch<Event[]>(query);
const events = await previewDraftsClient.fetch<Event[]>(query);

if (!events.length) {
return new Response("No events to delete", { status: 204 });
return new Response("No events to delete", { status: 200 });
}

const result = await supabase.from("event").delete().in("document_id", events);
Expand Down
135 changes: 66 additions & 69 deletions studio/actions/cancel-event.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { ErrorOutlineIcon, WarningOutlineIcon } from "@sanity/icons";
import { useToast, Card, Text } from "@sanity/ui";
import { ErrorOutlineIcon } from "@sanity/icons";
import { useToast, Stack, Button, Card, Text } from "@sanity/ui";
import { useState } from "react";
import { DocumentActionProps, DocumentActionComponent, useDocumentOperation } from "sanity";
import { Event } from "../models/sanity.model";
import { sendEmailEventCanceled } from "../lib/event-email";
import { deleteEvent } from "../supabase/queries";

export const CancelAction: DocumentActionComponent = (props: DocumentActionProps) => {
const toast = useToast();
Expand All @@ -13,6 +12,49 @@ export const CancelAction: DocumentActionComponent = (props: DocumentActionProps
const { patch, unpublish } = useDocumentOperation(props.id, props.type);

const publishedEvent = props.published as Event | null;

const handleCancelEvent = async () => {
if (!publishedEvent) return;

const emailProps = {
id: publishedEvent._id,
summary: publishedEvent.title,
description: publishedEvent.summary,
start: publishedEvent.start,
end: publishedEvent.end,
location: publishedEvent.place,
organiser: publishedEvent.organisers.join(" | "),
};

try {
const result = await sendEmailEventCanceled(emailProps);

if (!result) {
toast.push({
status: "error",
title: "En feil oppstod ved utsending av e-post",
});
props.onComplete();
return;
}

patch.execute([{ set: { cancleId: props.id } }]);
unpublish.execute();

toast.push({
status: "success",
title: "Arrangementet er avlyst",
});
} catch (error) {
console.error("Error handling cancel event:", error);
toast.push({
status: "error",
title: "En feil oppstod ved avlysning av arrangementet",
});
}
props.onComplete();
};

return {
disabled: !publishedEvent,
label: "Avlys arrangement",
Expand All @@ -22,74 +64,29 @@ export const CancelAction: DocumentActionComponent = (props: DocumentActionProps
setDialogOpen(true);
},
dialog: dialogOpen && {
type: "confirm",
onCancel: props.onComplete,
onConfirm: async () => {
if (publishedEvent) {
const result = await handleEventCancel(publishedEvent);
if (!result) {
toast.push({
status: "error",
title: "En feil oppstod ved avlysing av arrangementet",
});
return;
}

if (!publishedEvent.title.endsWith("Avlyst")) {
patch.execute([{ set: { title: `${publishedEvent.title} - Avlyst` } }]);
}

unpublish.execute();

toast.push({
status: "success",
title: "Arrangementet er avlyst",
});
}
props.onComplete();
},
message: (
<Card padding={4}>
<Text>
<WarningOutlineIcon
style={{
color: "red",
fontSize: "24px",
marginRight: "3px",
marginLeft: "1px",
}}
/>
<span>
Vennligst bekreft at du ønsker å avlyse arrangementet. Alle påmeldte blir varslet på
e-post om at arrangementet er avlyst.
</span>
</Text>
header: "Bekreft avlysning",
type: "dialog",
onClose: props.onComplete,
content: (
<Card padding={3}>
<Stack space={3}>
<Text>
<span>
Vennligst bekreft at du ønsker å avlyse arrangementet. Alle påmeldte blir varslet på
e-post om at arrangementet er avlyst.
</span>
</Text>
<div style={{ display: "flex", justifyContent: "flex-end", gap: "10px" }}>
<Button mode="ghost" tone="critical" onClick={handleCancelEvent}>
Ja, avlys
</Button>
<Button mode="ghost" tone="default" onClick={props.onComplete}>
Avbryt
</Button>
</div>
</Stack>
</Card>
),
},
};
};

const handleEventCancel = async ({ _id, title, summary, start, end, place, organisers }: Event) => {
const emailProps = {
id: _id,
summary: title,
description: summary,
start,
end,
location: place,
organiser: organisers.join(" | "),
};
try {
const result = await sendEmailEventCanceled(emailProps);
if (result?.error) {
return false;
}

await deleteEvent({ document_id: _id });
return true;
} catch (error) {
console.error("Error handling cancel event:", error);
return false;
}
};
Loading

1 comment on commit 27e6a3b

@vercel
Copy link

@vercel vercel bot commented on 27e6a3b Jul 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.