diff --git a/e2e/auth.spec.ts b/e2e/auth.spec.ts index f713feb..3b8d3f2 100644 --- a/e2e/auth.spec.ts +++ b/e2e/auth.spec.ts @@ -197,9 +197,9 @@ test.describe.serial("User auth process", () => { if (getConfigResponse.error) throw new Error(getConfigResponse.error.error); switch (getConfigResponse.data?.checksum) { - case "209a644c31a5ef123c432c2885d231a2e0efc4de": // chromium - case "aead21e132a94ab897ec28e0f0c337a66207bad3": // webkit - case "c3e2ff2ce5a8936234552125a54c2fe1ce1a35da": // firefox + case "9d9ba8fa6405653cb98a961c533ac7e92cbc3af6": // webkit + case "cf6316140d481bd5c1728828b065efbe8f7bb537": // firefox + case "9e608cb56e0818c83334389ab3913eade9c011f7": // chromium break; default: @@ -211,11 +211,15 @@ test.describe.serial("User auth process", () => { const putTimePlanningResponse = await PUT("/api/{guildId}/timePlanning", { body: { - enabled: true, channelId: "1234567890123456789", - rolesEnabled: true, - isAvailableRoleId: "1234567890123456789", - wantsToBeNotifieRoledId: "1234567890123456789", + targetMinute: 1, + targetHour: 2, + targetDay: 3, + roles: { + enabled: true, + isAvailableRoleId: "1234567890123456789", + wantsToBeNotifieRoledId: "1234567890123456789", + }, messageIds: { "0": "1234567890123456789", "1": "1234567890123456789", @@ -255,9 +259,9 @@ test.describe.serial("User auth process", () => { if (getConfigResponse.error) throw new Error(getConfigResponse.error.error); switch (getConfigResponse.data?.checksum) { - case "681c8324b21096255d942bb78bd6655da90d352e": // chromium - case "a2fb3601b7d0949b1ceada3b3ac0ba408c6159bb": // webkit - case "bf20daba95e8f3ddd17cc64e8a7ba184b68ad37b": // firefox + case "843ea341487f777b614f4c1a07b19730a7fd12e3": // webkit + case "8c4909abb19f7ca520840c54697f78ca4d0b5089": // firefox + case "17701fc9adcffc4df764f35774a752d3a9b43017": // chromium break; default: diff --git a/kill-running-server.sh b/kill-running-server.sh new file mode 100755 index 0000000..b7ef64e --- /dev/null +++ b/kill-running-server.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# This script checks if a process is running on port 3000 and kills it if it's found + +lsof -i TCP:3000 | grep LISTEN | awk '{print $2}' | xargs kill -9 diff --git a/public/api/specs/liljudd.json b/public/api/specs/liljudd.json index 8a6e2de..878e035 100644 --- a/public/api/specs/liljudd.json +++ b/public/api/specs/liljudd.json @@ -569,54 +569,7 @@ "required": ["timePlanning"], "properties": { "timePlanning": { - "type": "object", - "required": [ - "enabled", - "channelId", - "targetMinute", - "targetHour", - "targetDay", - "roles" - ], - "properties": { - "enabled": { - "type": "boolean" - }, - "channelId": { - "$ref": "#/components/schemas/idOrNull" - }, - "targetMinute": { - "type": "number", - "example": 0 - }, - "targetHour": { - "type": "number", - "example": 1 - }, - "targetDay": { - "type": "number", - "example": 1 - }, - "roles": { - "type": "object", - "required": [ - "enabled", - "isAvailableRoleId", - "wantsToBeNotifieRoledId" - ], - "properties": { - "enabled": { - "type": "boolean" - }, - "isAvailableRoleId": { - "$ref": "#/components/schemas/idOrNull" - }, - "wantsToBeNotifieRoledId": { - "$ref": "#/components/schemas/idOrNull" - } - } - } - } + "$ref": "#/components/schemas/timePlanning" } } }, @@ -674,28 +627,47 @@ "timePlanning": { "type": "object", "required": [ - "enabled", "channelId", - "rolesEnabled", - "isAvailableRoleId", - "wantsToBeNotifieRoledId", + "targetMinute", + "targetHour", + "targetDay", + "roles", "messageIds" ], "properties": { - "enabled": { - "type": "boolean" - }, "channelId": { "$ref": "#/components/schemas/idOrNull" }, - "rolesEnabled": { - "type": "boolean" + "targetMinute": { + "type": "number", + "example": 0 }, - "isAvailableRoleId": { - "$ref": "#/components/schemas/idOrNull" + "targetHour": { + "type": "number", + "example": 1 }, - "wantsToBeNotifieRoledId": { - "$ref": "#/components/schemas/idOrNull" + "targetDay": { + "type": "number", + "example": 1 + }, + "roles": { + "type": "object", + "required": [ + "enabled", + "isAvailableRoleId", + "wantsToBeNotifieRoledId" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "isAvailableRoleId": { + "$ref": "#/components/schemas/idOrNull" + }, + "wantsToBeNotifieRoledId": { + "$ref": "#/components/schemas/idOrNull" + } + } }, "messageIds": { "type": "object", diff --git a/src/drizzle/schema.ts b/src/drizzle/schema.ts index cf96bab..5783eeb 100644 --- a/src/drizzle/schema.ts +++ b/src/drizzle/schema.ts @@ -41,7 +41,6 @@ export const discordTokens = pgTable("tokens", { export const guilds = pgTable("guilds", { id: bigint("id", { mode: "bigint" }).primaryKey(), timezone: text("timezone").notNull().default("Etc/UTC"), - tpEnabled: boolean("tp_enabled").notNull().default(false), tpChannelId: bigint("tp_channel_id", { mode: "bigint" }), tpInterval: smallint("target_interval").notNull().default(64), tpRolesEnabled: boolean("tp_roles_enabled").notNull().default(false), diff --git a/src/lib/responseBuilders.ts b/src/lib/responseBuilders.ts index fe8f4a6..d8c4c1c 100644 --- a/src/lib/responseBuilders.ts +++ b/src/lib/responseBuilders.ts @@ -27,6 +27,39 @@ export const buildMatches = ( }), ); +export const splitInterval = (tpInterval: number) => { + const targetMinute = tpInterval & 63; + const targetHour = (tpInterval >> 6) & 31; + const targetDay = (tpInterval >> 11) & 7; + + return { targetMinute, targetHour, targetDay }; +}; + +export const DayKeys = ["0", "1", "2", "3", "4", "5", "6"] as const; +export type DayKeys = (typeof DayKeys)[number]; +export type Messages = Record; + +export const buildTpMessages = ( + messages: ExtractDataTypes>[], +) => + messages.reduce( + (acc, message) => { + const day = message.day.toString() as DayKeys; + if (!/^[0-6]$/.test(day)) return acc; + acc[day] = message.messageId?.toString() ?? null; + return acc; + }, + { + "0": null, + "1": null, + "2": null, + "3": null, + "4": null, + "5": null, + "6": null, + } as Messages, + ); + export function buildConfig( guildQuery: ExtractDataTypes> & { tpMessages: ExtractDataTypes>[]; @@ -36,33 +69,27 @@ export function buildConfig( const { id, timezone, - tpEnabled, tpChannelId, tpInterval, - tpRolesEnabled: tpRoles, + tpRolesEnabled, isAvailableRoleId, wantsToBeNotifieRoledId, + tpMessages, } = guildQuery; - const targetMinute = tpInterval & 63; - const targetHour = (tpInterval >> 6) & 31; - const targetDay = (tpInterval >> 11) & 7; - const payload = { guildId: id.toString(), timezone, features: { timePlanning: { - enabled: tpEnabled, channelId: tpChannelId?.toString() ?? null, - targetMinute, - targetHour, - targetDay, + ...splitInterval(tpInterval), roles: { - enabled: tpRoles, + enabled: tpRolesEnabled, isAvailableRoleId: isAvailableRoleId?.toString() ?? null, wantsToBeNotifieRoledId: wantsToBeNotifieRoledId?.toString() ?? null, }, + messageIds: buildTpMessages(tpMessages), }, }, matches: buildMatches(guildQuery.matches), diff --git a/src/lib/zod.ts b/src/lib/zod.ts index 13a85f4..6f44f06 100644 --- a/src/lib/zod.ts +++ b/src/lib/zod.ts @@ -7,11 +7,15 @@ const zodId = z export const zodBigIntId = zodId.transform((value) => BigInt(value)); export const zodTpMessages = z.object({ - enabled: z.boolean(), channelId: zodId.nullable(), - rolesEnabled: z.boolean(), - isAvailableRoleId: zodId.nullable(), - wantsToBeNotifieRoledId: zodId.nullable(), + targetMinute: z.number().nonnegative().max(59), + targetHour: z.number().nonnegative().max(23), + targetDay: z.number().nonnegative().max(6), + roles: z.object({ + enabled: z.boolean(), + isAvailableRoleId: zodId.nullable(), + wantsToBeNotifieRoledId: zodId.nullable(), + }), messageIds: z.object({ "0": zodId.nullable(), "1": zodId.nullable(), diff --git a/src/routes/api/[guildId]/timePlanning.ts b/src/routes/api/[guildId]/timePlanning.ts index fd276b5..9b02c35 100644 --- a/src/routes/api/[guildId]/timePlanning.ts +++ b/src/routes/api/[guildId]/timePlanning.ts @@ -3,16 +3,17 @@ import { and, eq } from "drizzle-orm"; import db from "~/drizzle"; import { guilds, tpMessages } from "~/drizzle/schema"; import { BasicAuth } from "~/lib/auth"; +import { + DayKeys, + buildTpMessages, + splitInterval, +} from "~/lib/responseBuilders"; import { ErrorResponse, Res } from "~/lib/responses"; import { zodBigIntId, zodTpMessages } from "~/lib/zod"; import { APIResponse, RequestBody } from "~/types/backend"; type Path = "/api/{guildId}/timePlanning"; -const DayKeys = ["0", "1", "2", "3", "4", "5", "6"] as const; -type DayKeys = (typeof DayKeys)[number]; -type Messages = Record; - export const GET = async ( event: APIEvent, ): Promise> => { @@ -41,31 +42,16 @@ export const GET = async ( if (!guild) return ErrorResponse("NOT_FOUND"); - const tpMessages = guild.tpMessages.reduce( - (acc, message) => { - const day = message.day.toString() as DayKeys; - if (!/^[0-6]$/.test(day)) return acc; - acc[day] = message.messageId?.toString() ?? null; - return acc; - }, - { - "0": null, - "1": null, - "2": null, - "3": null, - "4": null, - "5": null, - "6": null, - } as Messages, - ); - return Res("OK", { - enabled: guild.tpEnabled, channelId: guild.tpChannelId?.toString() ?? null, - rolesEnabled: guild.tpRolesEnabled, - isAvailableRoleId: guild.isAvailableRoleId?.toString() ?? null, - wantsToBeNotifieRoledId: guild.wantsToBeNotifieRoledId?.toString() ?? null, - messageIds: tpMessages, + ...splitInterval(guild.tpInterval), + roles: { + enabled: guild.tpRolesEnabled, + isAvailableRoleId: guild.isAvailableRoleId?.toString() ?? null, + wantsToBeNotifieRoledId: + guild.wantsToBeNotifieRoledId?.toString() ?? null, + }, + messageIds: buildTpMessages(guild.tpMessages), }); }; @@ -106,24 +92,18 @@ export const PUT = async ( return ErrorResponse("BAD_REQUEST", JSON.stringify(e)); } - const { - enabled, - channelId, - rolesEnabled, - isAvailableRoleId, - wantsToBeNotifieRoledId, - messageIds, - } = body; + const { channelId, roles, messageIds } = body; if (guild.tpChannelId !== channelId) await db .update(guilds) .set({ - tpEnabled: enabled, tpChannelId: channelId ? BigInt(channelId) : null, - tpRolesEnabled: rolesEnabled, - isAvailableRoleId: isAvailableRoleId ? BigInt(isAvailableRoleId) : null, - wantsToBeNotifieRoledId: wantsToBeNotifieRoledId - ? BigInt(wantsToBeNotifieRoledId) + tpRolesEnabled: roles.enabled, + isAvailableRoleId: roles.isAvailableRoleId + ? BigInt(roles.isAvailableRoleId) + : null, + wantsToBeNotifieRoledId: roles.wantsToBeNotifieRoledId + ? BigInt(roles.wantsToBeNotifieRoledId) : null, }) .where(eq(guilds.id, guild.id)) diff --git a/src/types/liljudd.d.ts b/src/types/liljudd.d.ts index 3aeb1ca..9485cc4 100644 --- a/src/types/liljudd.d.ts +++ b/src/types/liljudd.d.ts @@ -67,21 +67,7 @@ export interface components { */ timezone: string; features: { - timePlanning: { - enabled: boolean; - channelId: components["schemas"]["idOrNull"]; - /** @example 0 */ - targetMinute: number; - /** @example 1 */ - targetHour: number; - /** @example 1 */ - targetDay: number; - roles: { - enabled: boolean; - isAvailableRoleId: components["schemas"]["idOrNull"]; - wantsToBeNotifieRoledId: components["schemas"]["idOrNull"]; - }; - }; + timePlanning: components["schemas"]["timePlanning"]; }; matches: components["schemas"]["match"][]; checksum: string; @@ -105,11 +91,18 @@ export interface components { utc_ts: string; }; timePlanning: { - enabled: boolean; channelId: components["schemas"]["idOrNull"]; - rolesEnabled: boolean; - isAvailableRoleId: components["schemas"]["idOrNull"]; - wantsToBeNotifieRoledId: components["schemas"]["idOrNull"]; + /** @example 0 */ + targetMinute: number; + /** @example 1 */ + targetHour: number; + /** @example 1 */ + targetDay: number; + roles: { + enabled: boolean; + isAvailableRoleId: components["schemas"]["idOrNull"]; + wantsToBeNotifieRoledId: components["schemas"]["idOrNull"]; + }; messageIds: { 0: components["schemas"]["idOrNull"]; 1: components["schemas"]["idOrNull"];