refactor: Update time planning feature
- Update checksums for browser compatibility - Add script to kill running server process - Refactor time planning structure and handling - Adjust database schema and type definitions accordingly
This commit is contained in:
parent
faa42f0899
commit
3c404ab5fa
8 changed files with 130 additions and 146 deletions
|
@ -197,9 +197,9 @@ test.describe.serial("User auth process", () => {
|
||||||
if (getConfigResponse.error) throw new Error(getConfigResponse.error.error);
|
if (getConfigResponse.error) throw new Error(getConfigResponse.error.error);
|
||||||
|
|
||||||
switch (getConfigResponse.data?.checksum) {
|
switch (getConfigResponse.data?.checksum) {
|
||||||
case "209a644c31a5ef123c432c2885d231a2e0efc4de": // chromium
|
case "9d9ba8fa6405653cb98a961c533ac7e92cbc3af6": // webkit
|
||||||
case "aead21e132a94ab897ec28e0f0c337a66207bad3": // webkit
|
case "cf6316140d481bd5c1728828b065efbe8f7bb537": // firefox
|
||||||
case "c3e2ff2ce5a8936234552125a54c2fe1ce1a35da": // firefox
|
case "9e608cb56e0818c83334389ab3913eade9c011f7": // chromium
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -211,11 +211,15 @@ test.describe.serial("User auth process", () => {
|
||||||
|
|
||||||
const putTimePlanningResponse = await PUT("/api/{guildId}/timePlanning", {
|
const putTimePlanningResponse = await PUT("/api/{guildId}/timePlanning", {
|
||||||
body: {
|
body: {
|
||||||
enabled: true,
|
|
||||||
channelId: "1234567890123456789",
|
channelId: "1234567890123456789",
|
||||||
rolesEnabled: true,
|
targetMinute: 1,
|
||||||
|
targetHour: 2,
|
||||||
|
targetDay: 3,
|
||||||
|
roles: {
|
||||||
|
enabled: true,
|
||||||
isAvailableRoleId: "1234567890123456789",
|
isAvailableRoleId: "1234567890123456789",
|
||||||
wantsToBeNotifieRoledId: "1234567890123456789",
|
wantsToBeNotifieRoledId: "1234567890123456789",
|
||||||
|
},
|
||||||
messageIds: {
|
messageIds: {
|
||||||
"0": "1234567890123456789",
|
"0": "1234567890123456789",
|
||||||
"1": "1234567890123456789",
|
"1": "1234567890123456789",
|
||||||
|
@ -255,9 +259,9 @@ test.describe.serial("User auth process", () => {
|
||||||
if (getConfigResponse.error) throw new Error(getConfigResponse.error.error);
|
if (getConfigResponse.error) throw new Error(getConfigResponse.error.error);
|
||||||
|
|
||||||
switch (getConfigResponse.data?.checksum) {
|
switch (getConfigResponse.data?.checksum) {
|
||||||
case "681c8324b21096255d942bb78bd6655da90d352e": // chromium
|
case "843ea341487f777b614f4c1a07b19730a7fd12e3": // webkit
|
||||||
case "a2fb3601b7d0949b1ceada3b3ac0ba408c6159bb": // webkit
|
case "8c4909abb19f7ca520840c54697f78ca4d0b5089": // firefox
|
||||||
case "bf20daba95e8f3ddd17cc64e8a7ba184b68ad37b": // firefox
|
case "17701fc9adcffc4df764f35774a752d3a9b43017": // chromium
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
5
kill-running-server.sh
Executable file
5
kill-running-server.sh
Executable file
|
@ -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
|
|
@ -569,54 +569,7 @@
|
||||||
"required": ["timePlanning"],
|
"required": ["timePlanning"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"timePlanning": {
|
"timePlanning": {
|
||||||
"type": "object",
|
"$ref": "#/components/schemas/timePlanning"
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -674,21 +627,38 @@
|
||||||
"timePlanning": {
|
"timePlanning": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"enabled",
|
|
||||||
"channelId",
|
"channelId",
|
||||||
"rolesEnabled",
|
"targetMinute",
|
||||||
"isAvailableRoleId",
|
"targetHour",
|
||||||
"wantsToBeNotifieRoledId",
|
"targetDay",
|
||||||
|
"roles",
|
||||||
"messageIds"
|
"messageIds"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"enabled": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"channelId": {
|
"channelId": {
|
||||||
"$ref": "#/components/schemas/idOrNull"
|
"$ref": "#/components/schemas/idOrNull"
|
||||||
},
|
},
|
||||||
"rolesEnabled": {
|
"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"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"isAvailableRoleId": {
|
"isAvailableRoleId": {
|
||||||
|
@ -696,6 +666,8 @@
|
||||||
},
|
},
|
||||||
"wantsToBeNotifieRoledId": {
|
"wantsToBeNotifieRoledId": {
|
||||||
"$ref": "#/components/schemas/idOrNull"
|
"$ref": "#/components/schemas/idOrNull"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"messageIds": {
|
"messageIds": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
|
@ -41,7 +41,6 @@ export const discordTokens = pgTable("tokens", {
|
||||||
export const guilds = pgTable("guilds", {
|
export const guilds = pgTable("guilds", {
|
||||||
id: bigint("id", { mode: "bigint" }).primaryKey(),
|
id: bigint("id", { mode: "bigint" }).primaryKey(),
|
||||||
timezone: text("timezone").notNull().default("Etc/UTC"),
|
timezone: text("timezone").notNull().default("Etc/UTC"),
|
||||||
tpEnabled: boolean("tp_enabled").notNull().default(false),
|
|
||||||
tpChannelId: bigint("tp_channel_id", { mode: "bigint" }),
|
tpChannelId: bigint("tp_channel_id", { mode: "bigint" }),
|
||||||
tpInterval: smallint("target_interval").notNull().default(64),
|
tpInterval: smallint("target_interval").notNull().default(64),
|
||||||
tpRolesEnabled: boolean("tp_roles_enabled").notNull().default(false),
|
tpRolesEnabled: boolean("tp_roles_enabled").notNull().default(false),
|
||||||
|
|
|
@ -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<DayKeys, string | null>;
|
||||||
|
|
||||||
|
export const buildTpMessages = (
|
||||||
|
messages: ExtractDataTypes<GetColumns<typeof tpMessages>>[],
|
||||||
|
) =>
|
||||||
|
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(
|
export function buildConfig(
|
||||||
guildQuery: ExtractDataTypes<GetColumns<typeof guilds>> & {
|
guildQuery: ExtractDataTypes<GetColumns<typeof guilds>> & {
|
||||||
tpMessages: ExtractDataTypes<GetColumns<typeof tpMessages>>[];
|
tpMessages: ExtractDataTypes<GetColumns<typeof tpMessages>>[];
|
||||||
|
@ -36,33 +69,27 @@ export function buildConfig(
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
timezone,
|
timezone,
|
||||||
tpEnabled,
|
|
||||||
tpChannelId,
|
tpChannelId,
|
||||||
tpInterval,
|
tpInterval,
|
||||||
tpRolesEnabled: tpRoles,
|
tpRolesEnabled,
|
||||||
isAvailableRoleId,
|
isAvailableRoleId,
|
||||||
wantsToBeNotifieRoledId,
|
wantsToBeNotifieRoledId,
|
||||||
|
tpMessages,
|
||||||
} = guildQuery;
|
} = guildQuery;
|
||||||
|
|
||||||
const targetMinute = tpInterval & 63;
|
|
||||||
const targetHour = (tpInterval >> 6) & 31;
|
|
||||||
const targetDay = (tpInterval >> 11) & 7;
|
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
guildId: id.toString(),
|
guildId: id.toString(),
|
||||||
timezone,
|
timezone,
|
||||||
features: {
|
features: {
|
||||||
timePlanning: {
|
timePlanning: {
|
||||||
enabled: tpEnabled,
|
|
||||||
channelId: tpChannelId?.toString() ?? null,
|
channelId: tpChannelId?.toString() ?? null,
|
||||||
targetMinute,
|
...splitInterval(tpInterval),
|
||||||
targetHour,
|
|
||||||
targetDay,
|
|
||||||
roles: {
|
roles: {
|
||||||
enabled: tpRoles,
|
enabled: tpRolesEnabled,
|
||||||
isAvailableRoleId: isAvailableRoleId?.toString() ?? null,
|
isAvailableRoleId: isAvailableRoleId?.toString() ?? null,
|
||||||
wantsToBeNotifieRoledId: wantsToBeNotifieRoledId?.toString() ?? null,
|
wantsToBeNotifieRoledId: wantsToBeNotifieRoledId?.toString() ?? null,
|
||||||
},
|
},
|
||||||
|
messageIds: buildTpMessages(tpMessages),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
matches: buildMatches(guildQuery.matches),
|
matches: buildMatches(guildQuery.matches),
|
||||||
|
|
|
@ -7,11 +7,15 @@ const zodId = z
|
||||||
export const zodBigIntId = zodId.transform((value) => BigInt(value));
|
export const zodBigIntId = zodId.transform((value) => BigInt(value));
|
||||||
|
|
||||||
export const zodTpMessages = z.object({
|
export const zodTpMessages = z.object({
|
||||||
enabled: z.boolean(),
|
|
||||||
channelId: zodId.nullable(),
|
channelId: zodId.nullable(),
|
||||||
rolesEnabled: z.boolean(),
|
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(),
|
isAvailableRoleId: zodId.nullable(),
|
||||||
wantsToBeNotifieRoledId: zodId.nullable(),
|
wantsToBeNotifieRoledId: zodId.nullable(),
|
||||||
|
}),
|
||||||
messageIds: z.object({
|
messageIds: z.object({
|
||||||
"0": zodId.nullable(),
|
"0": zodId.nullable(),
|
||||||
"1": zodId.nullable(),
|
"1": zodId.nullable(),
|
||||||
|
|
|
@ -3,16 +3,17 @@ import { and, eq } from "drizzle-orm";
|
||||||
import db from "~/drizzle";
|
import db from "~/drizzle";
|
||||||
import { guilds, tpMessages } from "~/drizzle/schema";
|
import { guilds, tpMessages } from "~/drizzle/schema";
|
||||||
import { BasicAuth } from "~/lib/auth";
|
import { BasicAuth } from "~/lib/auth";
|
||||||
|
import {
|
||||||
|
DayKeys,
|
||||||
|
buildTpMessages,
|
||||||
|
splitInterval,
|
||||||
|
} from "~/lib/responseBuilders";
|
||||||
import { ErrorResponse, Res } from "~/lib/responses";
|
import { ErrorResponse, Res } from "~/lib/responses";
|
||||||
import { zodBigIntId, zodTpMessages } from "~/lib/zod";
|
import { zodBigIntId, zodTpMessages } from "~/lib/zod";
|
||||||
import { APIResponse, RequestBody } from "~/types/backend";
|
import { APIResponse, RequestBody } from "~/types/backend";
|
||||||
|
|
||||||
type Path = "/api/{guildId}/timePlanning";
|
type Path = "/api/{guildId}/timePlanning";
|
||||||
|
|
||||||
const DayKeys = ["0", "1", "2", "3", "4", "5", "6"] as const;
|
|
||||||
type DayKeys = (typeof DayKeys)[number];
|
|
||||||
type Messages = Record<DayKeys, string | null>;
|
|
||||||
|
|
||||||
export const GET = async (
|
export const GET = async (
|
||||||
event: APIEvent,
|
event: APIEvent,
|
||||||
): Promise<APIResponse<Path, "get">> => {
|
): Promise<APIResponse<Path, "get">> => {
|
||||||
|
@ -41,31 +42,16 @@ export const GET = async (
|
||||||
|
|
||||||
if (!guild) return ErrorResponse("NOT_FOUND");
|
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", {
|
return Res("OK", {
|
||||||
enabled: guild.tpEnabled,
|
|
||||||
channelId: guild.tpChannelId?.toString() ?? null,
|
channelId: guild.tpChannelId?.toString() ?? null,
|
||||||
rolesEnabled: guild.tpRolesEnabled,
|
...splitInterval(guild.tpInterval),
|
||||||
|
roles: {
|
||||||
|
enabled: guild.tpRolesEnabled,
|
||||||
isAvailableRoleId: guild.isAvailableRoleId?.toString() ?? null,
|
isAvailableRoleId: guild.isAvailableRoleId?.toString() ?? null,
|
||||||
wantsToBeNotifieRoledId: guild.wantsToBeNotifieRoledId?.toString() ?? null,
|
wantsToBeNotifieRoledId:
|
||||||
messageIds: tpMessages,
|
guild.wantsToBeNotifieRoledId?.toString() ?? null,
|
||||||
|
},
|
||||||
|
messageIds: buildTpMessages(guild.tpMessages),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -106,24 +92,18 @@ export const PUT = async (
|
||||||
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
|
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { channelId, roles, messageIds } = body;
|
||||||
enabled,
|
|
||||||
channelId,
|
|
||||||
rolesEnabled,
|
|
||||||
isAvailableRoleId,
|
|
||||||
wantsToBeNotifieRoledId,
|
|
||||||
messageIds,
|
|
||||||
} = body;
|
|
||||||
if (guild.tpChannelId !== channelId)
|
if (guild.tpChannelId !== channelId)
|
||||||
await db
|
await db
|
||||||
.update(guilds)
|
.update(guilds)
|
||||||
.set({
|
.set({
|
||||||
tpEnabled: enabled,
|
|
||||||
tpChannelId: channelId ? BigInt(channelId) : null,
|
tpChannelId: channelId ? BigInt(channelId) : null,
|
||||||
tpRolesEnabled: rolesEnabled,
|
tpRolesEnabled: roles.enabled,
|
||||||
isAvailableRoleId: isAvailableRoleId ? BigInt(isAvailableRoleId) : null,
|
isAvailableRoleId: roles.isAvailableRoleId
|
||||||
wantsToBeNotifieRoledId: wantsToBeNotifieRoledId
|
? BigInt(roles.isAvailableRoleId)
|
||||||
? BigInt(wantsToBeNotifieRoledId)
|
: null,
|
||||||
|
wantsToBeNotifieRoledId: roles.wantsToBeNotifieRoledId
|
||||||
|
? BigInt(roles.wantsToBeNotifieRoledId)
|
||||||
: null,
|
: null,
|
||||||
})
|
})
|
||||||
.where(eq(guilds.id, guild.id))
|
.where(eq(guilds.id, guild.id))
|
||||||
|
|
27
src/types/liljudd.d.ts
vendored
27
src/types/liljudd.d.ts
vendored
|
@ -67,21 +67,7 @@ export interface components {
|
||||||
*/
|
*/
|
||||||
timezone: string;
|
timezone: string;
|
||||||
features: {
|
features: {
|
||||||
timePlanning: {
|
timePlanning: components["schemas"]["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"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
matches: components["schemas"]["match"][];
|
matches: components["schemas"]["match"][];
|
||||||
checksum: string;
|
checksum: string;
|
||||||
|
@ -105,11 +91,18 @@ export interface components {
|
||||||
utc_ts: string;
|
utc_ts: string;
|
||||||
};
|
};
|
||||||
timePlanning: {
|
timePlanning: {
|
||||||
enabled: boolean;
|
|
||||||
channelId: components["schemas"]["idOrNull"];
|
channelId: components["schemas"]["idOrNull"];
|
||||||
rolesEnabled: boolean;
|
/** @example 0 */
|
||||||
|
targetMinute: number;
|
||||||
|
/** @example 1 */
|
||||||
|
targetHour: number;
|
||||||
|
/** @example 1 */
|
||||||
|
targetDay: number;
|
||||||
|
roles: {
|
||||||
|
enabled: boolean;
|
||||||
isAvailableRoleId: components["schemas"]["idOrNull"];
|
isAvailableRoleId: components["schemas"]["idOrNull"];
|
||||||
wantsToBeNotifieRoledId: components["schemas"]["idOrNull"];
|
wantsToBeNotifieRoledId: components["schemas"]["idOrNull"];
|
||||||
|
};
|
||||||
messageIds: {
|
messageIds: {
|
||||||
0: components["schemas"]["idOrNull"];
|
0: components["schemas"]["idOrNull"];
|
||||||
1: components["schemas"]["idOrNull"];
|
1: components["schemas"]["idOrNull"];
|
||||||
|
|
Loading…
Reference in a new issue