From db9a12250e8415fca6b7fe4c41fe00bee305301a Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Fri, 7 Mar 2025 20:15:41 +0100 Subject: [PATCH] Track which account is interested in which events Store a list of ids of events and slots that accounts have marked as being interested in, and show aggeregate counts in the schedule. --- components/EventCard.vue | 53 ++++++++++++++++++++++++++++++++ server/api/account.patch.ts | 46 +++++++++++++++++++++++++++ server/generate-demo-schedule.ts | 35 +++++++++++++++++++++ shared/types/account.d.ts | 1 + shared/types/schedule.d.ts | 2 ++ 5 files changed, 137 insertions(+) create mode 100644 server/api/account.patch.ts diff --git a/components/EventCard.vue b/components/EventCard.vue index 163168a..97c8c2b 100644 --- a/components/EventCard.vue +++ b/components/EventCard.vue @@ -2,10 +2,35 @@

{{ event.name }}

{{ event.description ?? "No description provided" }}

+

+ {{ event.interested }} interested +

+

+ +

+

Timeslots

@@ -17,6 +42,27 @@ import type { ScheduleEvent } from '~/shared/types/schedule'; defineProps<{ event: ScheduleEvent }>() + +const { data: session, refresh: refreshSession } = useAccountSession(); +const interestedIds = computed(() => new Set(session.value?.account.interestedIds ?? [])); + +async function toggle(id: string, slotIds?: string[]) { + let newIds = [...session.value!.account.interestedIds ?? []]; + if (interestedIds.value.has(id)) { + newIds = newIds.filter(newId => newId !== id); + } else { + newIds.push(id); + if (slotIds) { + const filterIds = new Set(slotIds); + newIds = newIds.filter(newId => !filterIds.has(newId)); + } + } + await $fetch("/api/account", { + method: "PATCH", + body: { interestedIds: newIds }, + }) + await refreshSession(); +} diff --git a/server/api/account.patch.ts b/server/api/account.patch.ts new file mode 100644 index 0000000..5431e03 --- /dev/null +++ b/server/api/account.patch.ts @@ -0,0 +1,46 @@ +import { Account } from "~/shared/types/account"; +import { readAccounts, readSchedule, writeAccounts, writeSchedule } from "~/server/database"; +import { broadcastUpdate } from "~/server/streams"; + +export default defineEventHandler(async (event) => { + const session = await requireAccountSession(event); + const body: Pick = await readBody(event); + if ( + !(body.interestedIds instanceof Array) + || !body.interestedIds.every(id => typeof id === "string") + ) { + throw createError({ + status: 400, + message: "Invalid interestedIds", + }); + } + + const accounts = await readAccounts(); + const sessionAccount = accounts.find(account => account.id === session.accountId); + if (!sessionAccount) { + throw Error("Account does not exist"); + } + + if (body.interestedIds.length) { + sessionAccount.interestedIds = body.interestedIds; + } else { + delete sessionAccount.interestedIds; + } + await writeAccounts(accounts); + + const counts = new Map(); + for (const account of accounts) + if (account.interestedIds) + for (const id of account.interestedIds) + counts.set(id, (counts.get(id) ?? 0) + 1); + + const schedule = await readSchedule(); + for (const event of schedule.events) { + event.interested = counts.get(event.id); + for (const slot of event.slots) { + slot.interested = counts.get(slot.id); + } + } + await writeSchedule(schedule); + broadcastUpdate(schedule); +}) diff --git a/server/generate-demo-schedule.ts b/server/generate-demo-schedule.ts index 08bef53..bf7aacd 100644 --- a/server/generate-demo-schedule.ts +++ b/server/generate-demo-schedule.ts @@ -175,5 +175,40 @@ export function generateDemoAccounts(): Account[] { type: (["regular", "crew", "admin"] as const)[Math.floor(random() ** 5 * 3)], }); } + + // These have a much higher probability of being in someone's interested list. + const desiredEvent = ["opening", "closing", "fursuit-games"]; + + for (const account of accounts) { + const interestedIds: string[] = []; + for (const id of desiredEvent) { + if (random() < 0.5) { + interestedIds.push(id); + } + } + + const eventsToAdd = Math.floor(random() * 10); + while (interestedIds.length < eventsToAdd) { + const event = events[Math.floor(random() * events.length)]; + const eventId = toId(event.name); + if (interestedIds.some(id => id.replace(/-\d+$/, "") === eventId)) { + continue; + } + + if (event.slots.length === 1 || random() < 0.8) { + interestedIds.push(toId(event.name)) + } else { + for (const index of event.slots.map((_, index) => index)) { + if (random() < 0.5) { + interestedIds.push(toId(`${toId(event.name)}-${index}`)); + } + } + } + } + + if (interestedIds.length) { + account.interestedIds = interestedIds; + } + } return accounts; } diff --git a/shared/types/account.d.ts b/shared/types/account.d.ts index 2864190..9a3780a 100644 --- a/shared/types/account.d.ts +++ b/shared/types/account.d.ts @@ -3,6 +3,7 @@ export interface Account { type: "anonymous" | "regular" | "crew" | "admin", /** Name of the account. Not present on anonymous accounts */ name?: string, + interestedIds?: string[], } export interface Subscription { diff --git a/shared/types/schedule.d.ts b/shared/types/schedule.d.ts index 8ec0ddd..c09fdb0 100644 --- a/shared/types/schedule.d.ts +++ b/shared/types/schedule.d.ts @@ -4,6 +4,7 @@ export interface ScheduleEvent { host?: string, cancelled?: boolean, description?: string, + interested?: number, slots: TimeSlot[], } @@ -18,6 +19,7 @@ export interface TimeSlot { start: string, end: string, locations: string[], + interested?: number, } export interface Schedule {