diff --git a/programmerbar-cms/sanity-typegen.json b/programmerbar-cms/sanity-typegen.json index 5f6441d..aca7b3f 100644 --- a/programmerbar-cms/sanity-typegen.json +++ b/programmerbar-cms/sanity-typegen.json @@ -1,5 +1,5 @@ { - "path": "../www/src/**/*.{ts,tsx,js,jsx}", + "path": "../programmerbar-web/src/**/*.{ts,tsx,js,jsx}", "schema": "schema.json", - "generates": "../www/sanity.types.ts" -} + "generates": "../programmerbar-web/sanity.types.ts" +} \ No newline at end of file diff --git a/programmerbar-web/.env.example b/programmerbar-web/.env.example index d5b0f53..b632170 100644 --- a/programmerbar-web/.env.example +++ b/programmerbar-web/.env.example @@ -6,6 +6,10 @@ FEIDE_CLIENT_ID="" FEIDE_CLIENT_SECRET="" FEIDE_REDIRECT_URI="http://localhost:5173/auth/feide/callback" +# -- Slack +SLACK_VERIFICATION_TOKEN="" +SLACK_CHANNEL_ID="" + # -- API PUBLIC_API_URL=http://localhost:8000 ADMIN_KEY=foobar \ No newline at end of file diff --git a/programmerbar-web/sanity.types.ts b/programmerbar-web/sanity.types.ts index 91fd71d..a44ff55 100644 --- a/programmerbar-web/sanity.types.ts +++ b/programmerbar-web/sanity.types.ts @@ -240,24 +240,24 @@ export type AllSanitySchemaTypes = | Slug | SanityAssetSourceData; export declare const internalGroqTypeReferenceTo: unique symbol; -// Source: ../www/src/lib/api/sanity/events.ts +// Source: ../programmerbar-web/src/lib/api/sanity/events.ts // Variable: getEventsQuery -// Query: *[ _type == "happening" && !(_id in path("drafts.**")) && "programmerbar" in organizers[]->slug.current && date >= now()] | order(date asc) { _id, title, "slug": slug.current, date, registrationStart, body}[0...6] +// Query: *[ _type == "happening" && !(_id in path("drafts.**")) && "programmerbar" in organizers[]->slug.current && date >= now()] | order(date asc) { _id, title, "slug": slug.current, date, registrationStart, _createdAt, body}[0...6] export type GetEventsQueryResult = Array; // Variable: getUpcomingEventsQuery -// Query: *[ _type == "happening" && !(_id in path("drafts.**")) && "programmerbar" in organizers[]->slug.current && date > now()] | order(date asc) { _id, title, "slug": slug.current, date, registrationStart, body} +// Query: *[ _type == "happening" && !(_id in path("drafts.**")) && "programmerbar" in organizers[]->slug.current && date > now()] | order(date asc) { _id, title, "slug": slug.current, date, registrationStart, _createdAt, body} export type GetUpcomingEventsQueryResult = Array; // Variable: getEventBySlugQuery -// Query: *[ _type == "happening" && !(_id in path("drafts.**")) && "programmerbar" in organizers[]->slug.current && slug.current == $slug] { _id, title, "slug": slug.current, date, registrationStart, body}[0] +// Query: *[ _type == "happening" && !(_id in path("drafts.**")) && "programmerbar" in organizers[]->slug.current && slug.current == $slug] { _id, title, "slug": slug.current, date, registrationStart, _createdAt, body}[0] export type GetEventBySlugQueryResult = null; // Variable: getRepeatingEventsQuery -// Query: *[ _type == "repeatingHappening" && !(_id in path("drafts.**")) && "programmerbar" in organizers[]->slug.current] | order(date asc) { _id, title, dayOfWeek, startTime, endTime, startDate, ignoredDates, endDate, interval, "slug": slug.current, body} +// Query: *[ _type == "repeatingHappening" && !(_id in path("drafts.**")) && "programmerbar" in organizers[]->slug.current] | order(date asc) { _id, title, dayOfWeek, startTime, endTime, startDate, ignoredDates, endDate, interval, _createdAt, "slug": slug.current, body} export type GetRepeatingEventsQueryResult = Array; // Variable: getRepeatingEventBySlugQuery -// Query: *[ _type == "repeatingHappening" && !(_id in path("drafts.**")) && "programmerbar" in organizers[]->slug.current && slug.current == $slug] { _id, title, dayOfWeek, startTime, endTime, startDate, ignoredDates, endDate, interval, "slug": slug.current, body}[0] +// Query: *[ _type == "repeatingHappening" && !(_id in path("drafts.**")) && "programmerbar" in organizers[]->slug.current && slug.current == $slug] { _id, title, dayOfWeek, startTime, endTime, startDate, ignoredDates, endDate, interval, _createdAt, "slug": slug.current, body}[0] export type GetRepeatingEventBySlugQueryResult = null; -// Source: ../www/src/lib/api/sanity/products.ts +// Source: ../programmerbar-web/src/lib/api/sanity/products.ts // Variable: getProductsQuery // Query: *[_type == "product" && !(_id in path("drafts.**"))] { _id, sku, name, description, "productTypes": productType[]->{ _id, title }, isSoldOut, priceList, image, "producer": producer->name, volume, alcoholContent, variants,} export type GetProductsQueryResult = Array<{ @@ -319,7 +319,7 @@ export type GetProductByIdQueryResult = { variants: Array | null; } | null; -// Source: ../www/src/lib/api/sanity/programmerbar.ts +// Source: ../programmerbar-web/src/lib/api/sanity/programmerbar.ts // Variable: query // Query: *[_type == "studentGroup" && slug.current == $slug && !(_id in path('drafts.**'))] { _id, _createdAt, _updatedAt, name, groupType, "slug": slug.current, description, image, "members": members[] { role, "profile": profile->{ _id, name, picture, socials, }, }, "socials": socials { facebook, instagram, linkedin, email, } }[0] export type QueryResult = null; @@ -328,11 +328,11 @@ export type QueryResult = null; import '@sanity/client'; declare module '@sanity/client' { interface SanityQueries { - '*[\n\t_type == "happening" &&\n\t!(_id in path("drafts.**")) &&\n\t"programmerbar" in organizers[]->slug.current &&\n\tdate >= now()\n] | order(date asc) {\n\t_id,\n\ttitle,\n\t"slug": slug.current,\n\tdate,\n\tregistrationStart,\n\tbody\n}[0...6]': GetEventsQueryResult; - '*[\n\t_type == "happening" &&\n\t!(_id in path("drafts.**")) &&\n\t"programmerbar" in organizers[]->slug.current &&\n\tdate > now()\n] | order(date asc) {\n\t_id,\n\ttitle,\n\t"slug": slug.current,\n\tdate,\n\tregistrationStart,\n\tbody\n}': GetUpcomingEventsQueryResult; - '*[\n\t_type == "happening" &&\n\t!(_id in path("drafts.**")) &&\n\t"programmerbar" in organizers[]->slug.current &&\n\tslug.current == $slug\n] {\n\t_id,\n\ttitle,\n\t"slug": slug.current,\n\tdate,\n\tregistrationStart,\n\tbody\n}[0]': GetEventBySlugQueryResult; - '*[\n\t_type == "repeatingHappening" &&\n\t!(_id in path("drafts.**")) &&\n\t"programmerbar" in organizers[]->slug.current\n] | order(date asc) {\n\t_id,\n\ttitle,\n\tdayOfWeek,\n\tstartTime,\n\tendTime,\n\tstartDate,\n\tignoredDates,\n\tendDate,\n\tinterval,\n\t"slug": slug.current,\n\tbody\n}': GetRepeatingEventsQueryResult; - '*[\n\t_type == "repeatingHappening" &&\n\t!(_id in path("drafts.**")) &&\n\t"programmerbar" in organizers[]->slug.current &&\n\tslug.current == $slug\n] {\n\t_id,\n\ttitle,\n\tdayOfWeek,\n\tstartTime,\n\tendTime,\n\tstartDate,\n\tignoredDates,\n\tendDate,\n\tinterval,\n\t"slug": slug.current,\n\tbody\n}[0]': GetRepeatingEventBySlugQueryResult; + '*[\n\t_type == "happening" &&\n\t!(_id in path("drafts.**")) &&\n\t"programmerbar" in organizers[]->slug.current &&\n\tdate >= now()\n] | order(date asc) {\n\t_id,\n\ttitle,\n\t"slug": slug.current,\n\tdate,\n\tregistrationStart,\n\t_createdAt,\n\tbody\n}[0...6]': GetEventsQueryResult; + '*[\n\t_type == "happening" &&\n\t!(_id in path("drafts.**")) &&\n\t"programmerbar" in organizers[]->slug.current &&\n\tdate > now()\n] | order(date asc) {\n\t_id,\n\ttitle,\n\t"slug": slug.current,\n\tdate,\n\tregistrationStart,\n\t_createdAt,\n\tbody\n}': GetUpcomingEventsQueryResult; + '*[\n\t_type == "happening" &&\n\t!(_id in path("drafts.**")) &&\n\t"programmerbar" in organizers[]->slug.current &&\n\tslug.current == $slug\n] {\n\t_id,\n\ttitle,\n\t"slug": slug.current,\n\tdate,\n\tregistrationStart,\n\t_createdAt,\n\tbody\n}[0]': GetEventBySlugQueryResult; + '*[\n\t_type == "repeatingHappening" &&\n\t!(_id in path("drafts.**")) &&\n\t"programmerbar" in organizers[]->slug.current\n] | order(date asc) {\n\t_id,\n\ttitle,\n\tdayOfWeek,\n\tstartTime,\n\tendTime,\n\tstartDate,\n\tignoredDates,\n\tendDate,\n\tinterval,\n\t_createdAt,\n\t"slug": slug.current,\n\tbody\n}': GetRepeatingEventsQueryResult; + '*[\n\t_type == "repeatingHappening" &&\n\t!(_id in path("drafts.**")) &&\n\t"programmerbar" in organizers[]->slug.current &&\n\tslug.current == $slug\n] {\n\t_id,\n\ttitle,\n\tdayOfWeek,\n\tstartTime,\n\tendTime,\n\tstartDate,\n\tignoredDates,\n\tendDate,\n\tinterval,\n\t_createdAt,\n\t"slug": slug.current,\n\tbody\n}[0]': GetRepeatingEventBySlugQueryResult; '*[_type == "product" && !(_id in path("drafts.**"))] {\n _id,\n sku,\n name,\n description,\n "productTypes": productType[]->{\n _id,\n title\n },\n isSoldOut,\n priceList,\n image,\n "producer": producer->name,\n volume,\n alcoholContent,\n variants,\n}': GetProductsQueryResult; '*[_type == "product" && _id == $id && !(_id in path("drafts.**"))] {\n _id,\n sku,\n name,\n description,\n "productTypes": productType[]->{\n _id,\n title\n },\n isSoldOut,\n priceList,\n image,\n "producer": producer->name,\n volume,\n alcoholContent,\n variants,\n}[0]': GetProductByIdQueryResult; '*[_type == "studentGroup"\n && slug.current == $slug\n && !(_id in path(\'drafts.**\'))] {\n _id,\n _createdAt,\n _updatedAt,\n name,\n groupType,\n "slug": slug.current,\n description,\n image,\n "members": members[] {\n role,\n "profile": profile->{\n _id,\n name,\n picture,\n socials,\n },\n },\n "socials": socials {\n facebook,\n instagram,\n linkedin,\n email,\n }\n }[0]': QueryResult; diff --git a/programmerbar-web/src/routes/(app)/slack-command/+server.ts b/programmerbar-web/src/routes/(app)/slack-command/+server.ts new file mode 100644 index 0000000..6fc9754 --- /dev/null +++ b/programmerbar-web/src/routes/(app)/slack-command/+server.ts @@ -0,0 +1,65 @@ +import { env } from '$env/dynamic/private'; +import { STATUS, StatusService } from '$lib/services/status.service'; +import type { RequestHandler } from './$types'; + +type Command = (typeof COMMAND)[keyof typeof COMMAND]; + +const COMMAND = { + OPEN: '/åpent', + CLOSED: '/stengt', + PRIVATE: '/privat', + STATUS: '/skjer' +} as const; + +const getStatusNumber = (command: Command) => { + switch (command) { + case COMMAND.OPEN: + return STATUS.OPEN; + case COMMAND.CLOSED: + return STATUS.CLOSED; + case COMMAND.PRIVATE: + return STATUS.PRIVATE; + default: + return STATUS.PRIVATE; + } +}; + +const isCommand = (command: string | null): command is Command => + Object.values(COMMAND).includes(command as Command); + +export const POST: RequestHandler = async ({ locals, request }) => { + const formData = await request.formData(); + const command = formData.get('command') as string; + + if (!isCommand(command)) { + return new Response('Invalid command', { status: 400 }); + } + + if (command === COMMAND.STATUS) { + const status = await locals.statusService.get(); + const text = StatusService.getMessage(status); + + return Response.json({ + response_type: 'in_channel', + text + }); + } + + if (formData.get('token') !== env.SLACK_VERIFICATION_TOKEN) { + return new Response('Invalid token', { status: 403 }); + } + + if (formData.get('channel_id') !== env.SLACK_CHANNEL_ID) { + return new Response('Invalid channel', { status: 403 }); + } + + const status = getStatusNumber(command); + await locals.statusService.set(status); + + const text = StatusService.getMessage(status); + + return Response.json({ + response_type: 'in_channel', + text + }); +}; diff --git a/programmerbar-web/src/worker-configuration.d.ts b/programmerbar-web/src/worker-configuration.d.ts index 57ffdf1..baae645 100644 --- a/programmerbar-web/src/worker-configuration.d.ts +++ b/programmerbar-web/src/worker-configuration.d.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: 68cec6a8a697c1b92839bb21378557b2) +// Generated by Wrangler by running `wrangler types` (hash: f0da2bcbcf1a9ec47f8a610cea1b9e63) // Runtime types generated with workerd@1.20250816.0 2025-08-19 nodejs_compat declare namespace Cloudflare { interface Env { @@ -8,6 +8,8 @@ declare namespace Cloudflare { FEIDE_CLIENT_ID: string; FEIDE_CLIENT_SECRET: string; FEIDE_REDIRECT_URI: string; + PUBLIC_API_URL: string; + ADMIN_KEY: string; BUCKET: R2Bucket; DB: D1Database; ASSETS: Fetcher; @@ -22,7 +24,12 @@ declare namespace NodeJS { extends StringifyValues< Pick< Cloudflare.Env, - 'RESEND_API_KEY' | 'FEIDE_CLIENT_ID' | 'FEIDE_CLIENT_SECRET' | 'FEIDE_REDIRECT_URI' + | 'RESEND_API_KEY' + | 'FEIDE_CLIENT_ID' + | 'FEIDE_CLIENT_SECRET' + | 'FEIDE_REDIRECT_URI' + | 'PUBLIC_API_URL' + | 'ADMIN_KEY' > > {} }