Fix: Using bigint and added backend testing

This commit is contained in:
Aron Malcher 2024-03-10 17:12:50 +01:00
parent ed6195e1e2
commit 89507f8412
Signed by: aronmal
GPG key ID: 816B7707426FC612
20 changed files with 830 additions and 292 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
src/drizzle/migrations
log
dist
.vinxi

View file

@ -1,30 +0,0 @@
GET https://discord.com/api/users/@me
Authorization: Bearer {{$dotenv DISCORD_ACCESS_TOKEN}}
###
GET https://discord.com/api/users/@me
Authorization: Bot {{$dotenv DISCORD_BOT_TOKEN}}
###
GET https://discord.com/api/users/@me/guilds
Authorization: Bearer {{$dotenv DISCORD_ACCESS_TOKEN}}
###
GET https://discord.com/api/users/@me/guilds/{{$dotenv DISCORD_GUILD_ID}}/member
Authorization: Bearer {{$dotenv DISCORD_ACCESS_TOKEN}}
###
GET https://discord.com/api/guilds/{{$dotenv DISCORD_GUILD_ID}}
Authorization: Bot {{$dotenv DISCORD_BOT_TOKEN}}
###
POST https://discord.com/api/oauth2/token/revoke
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {{$dotenv DISCORD_CLIENT_ID}}:{{$dotenv DISCORD_CLIENT_SECRET}}
token={{$dotenv DISCORD_ACCESS_TOKEN}}&token_type_hint=access_token

View file

@ -1,14 +1,18 @@
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
import { createId } from "@paralleldrive/cuid2";
import { expect, test, type BrowserContext, type Page } from "@playwright/test";
import "dotenv/config";
import { eq } from "drizzle-orm";
import { drizzle } from "drizzle-orm/postgres-js";
import { Lucia, type Cookie } from "lucia";
import createClient from "openapi-fetch";
import * as schema from "~/drizzle/schema";
import { paths } from "~/types/discord";
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
import "dotenv/config";
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "~/drizzle/schema";
import type * as discord from "~/types/discord";
import type * as liljudd from "~/types/liljudd";
const unencoded = `${process.env.DISCORD_CLIENT_ID}:${process.env.DISCORD_CLIENT_SECRET}`;
const encoded = btoa(unencoded);
const queryClient = postgres(process.env.DATABASE_URL!);
const db = drizzle(queryClient, {
@ -25,7 +29,22 @@ let page: Page;
let sessionCookie: Cookie | undefined;
let userId = createId();
let guildId: bigint;
test.describe.serial("User auth process", () => {
test.beforeAll(() => {
expect(
[
"DISCORD_CLIENT_ID",
"DISCORD_CLIENT_SECRET",
"DATABASE_URL",
"DISCORD_BOT_TOKEN",
].filter((e) => typeof process.env[e] === "undefined").length,
{ message: "Please specify all env vars." },
).toBeFalsy();
});
test.beforeAll(async ({ browser }) => {
context = await browser.newContext();
page = await context.newPage();
@ -59,6 +78,14 @@ test.describe.serial("User auth process", () => {
]);
});
test.afterAll("Delete DB entries", async () => {
await db.delete(schema.users).where(eq(schema.users.id, userId)).execute();
await db
.delete(schema.guilds)
.where(eq(schema.guilds.id, guildId))
.execute();
});
test.afterAll(async () => {
await context.close();
});
@ -75,8 +102,8 @@ test.describe.serial("User auth process", () => {
await page.waitForURL("/");
});
test("Generate auth session for further tests", async () => {
const { GET } = createClient<paths>({
test("Generate auth session for further tests", async ({ browser }) => {
const { GET } = createClient<discord.paths>({
baseUrl: "https://discord.com/api/v10",
});
const discordUserResponse = await GET("/users/@me", {
@ -86,7 +113,22 @@ test.describe.serial("User auth process", () => {
});
if (discordUserResponse.error) throw discordUserResponse.error;
const discordUser = discordUserResponse.data;
const userId = createId();
const browserName = browser.browserType().name() as
| "chromium"
| "webkit"
| "firefox";
userId = discordUser.id + userId.slice(discordUser.id.length);
userId = userId.slice(0, -browserName.length) + browserName;
enum BrowserIds {
chromium,
webkit,
firefox,
}
guildId = BigInt(discordUser.id) ^ BigInt(BrowserIds[browserName]);
await db.insert(schema.users).values({
id: userId,
discord_id: discordUser.id,
@ -119,4 +161,110 @@ test.describe.serial("User auth process", () => {
"landing_page_logged_in.png",
);
});
test("Test Api", async () => {
const { GET, POST, PUT } = createClient<liljudd.paths>({
baseUrl: "http://localhost:3000/",
});
const createConfigResponse = await POST("/api/{guildId}/config", {
params: {
path: {
guildId: guildId.toString(),
},
},
headers: {
Authorization: `Basic ${encoded}`,
Origin: "http://localhost:3000",
},
});
if (createConfigResponse.error)
throw new Error(createConfigResponse.error.error);
let getConfigResponse = await GET("/api/{guildId}/config", {
params: {
path: {
guildId: guildId.toString(),
},
},
headers: {
Authorization: `Basic ${encoded}`,
Origin: "http://localhost:3000",
},
});
if (getConfigResponse.error) throw new Error(getConfigResponse.error.error);
switch (getConfigResponse.data?.checksum) {
case "209a644c31a5ef123c432c2885d231a2e0efc4de": // chromium
case "aead21e132a94ab897ec28e0f0c337a66207bad3": // webkit
case "c3e2ff2ce5a8936234552125a54c2fe1ce1a35da": // firefox
break;
default:
throw new Error(
"Before guild GET checksum didn't matched known ones: " +
getConfigResponse.data?.checksum,
);
}
const putTimePlanningResponse = await PUT("/api/{guildId}/timePlanning", {
body: {
enabled: true,
channelId: "1234567890123456789",
rolesEnabled: true,
isAvailableRoleId: "1234567890123456789",
wantsToBeNotifieRoledId: "1234567890123456789",
messageIds: {
"0": "1234567890123456789",
"1": "1234567890123456789",
"2": "1234567890123456789",
"3": "1234567890123456789",
"4": "1234567890123456789",
"5": "1234567890123456789",
"6": "1234567890123456789",
},
},
params: {
path: {
guildId: guildId.toString(),
},
},
headers: {
Authorization: `Basic ${encoded}`,
Origin: "http://localhost:3000",
},
});
if (putTimePlanningResponse.error)
throw new Error(putTimePlanningResponse.error.error);
getConfigResponse = await GET("/api/{guildId}/config", {
params: {
path: {
guildId: guildId.toString(),
},
},
headers: {
Authorization: `Basic ${encoded}`,
Origin: "http://localhost:3000",
},
});
if (getConfigResponse.error) throw new Error(getConfigResponse.error.error);
switch (getConfigResponse.data?.checksum) {
case "681c8324b21096255d942bb78bd6655da90d352e": // chromium
case "a2fb3601b7d0949b1ceada3b3ac0ba408c6159bb": // webkit
case "bf20daba95e8f3ddd17cc64e8a7ba184b68ad37b": // firefox
break;
default:
throw new Error(
"After guild GET checksum didn't matched known ones: " +
getConfigResponse.data?.checksum,
);
}
});
});

View file

@ -28,6 +28,7 @@
"@solidjs/router": "^0.12.4",
"@solidjs/start": "^0.6.0",
"arctic": "^1.2.1",
"colors": "^1.4.0",
"drizzle-orm": "^0.29.4",
"http-status": "^1.7.4",
"json-stable-stringify": "^1.1.1",

View file

@ -44,6 +44,9 @@ dependencies:
arctic:
specifier: ^1.2.1
version: 1.2.1
colors:
specifier: ^1.4.0
version: 1.4.0
drizzle-orm:
specifier: ^0.29.4
version: 0.29.4(pg@8.11.3)(postgres@3.4.3)
@ -2761,6 +2764,11 @@ packages:
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
dev: false
/colors@1.4.0:
resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==}
engines: {node: '>=0.1.90'}
dev: false
/commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
dev: false
@ -3370,6 +3378,7 @@ packages:
/eslint-config-prettier@9.1.0(eslint@8.57.0):
resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==}
hasBin: true
peerDependencies:
eslint: '>=7.0.0'
dependencies:
@ -4671,6 +4680,7 @@ packages:
/nitropack@2.8.1:
resolution: {integrity: sha512-pODv2kEEzZSDQR+1UMXbGyNgMedUDq/qUomtiAnQKQvLy52VGlecXO1xDfH3i0kP1yKEcKTnWsx1TAF5gHM7xQ==}
engines: {node: ^16.11.0 || >=17.0.0}
hasBin: true
peerDependencies:
xml2js: ^0.6.2
peerDependenciesMeta:
@ -5344,6 +5354,7 @@ packages:
/rollup-plugin-visualizer@5.12.0(rollup@4.12.0):
resolution: {integrity: sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==}
engines: {node: '>=14'}
hasBin: true
peerDependencies:
rollup: 2.x || 3.x || 4.x
peerDependenciesMeta:
@ -6048,6 +6059,7 @@ packages:
/update-browserslist-db@1.0.13(browserslist@4.23.0):
resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
dependencies:
@ -6265,6 +6277,7 @@ packages:
/vite@5.1.1(@types/node@20.11.22)(sass@1.71.1):
resolution: {integrity: sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
'@types/node': ^18.0.0 || >=20.0.0
less: '*'

View file

@ -31,13 +31,34 @@
}
},
"400": {
"description": "Invalid ID supplied"
"description": "Invalid ID supplied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
},
"401": {
"description": "Unauthorized"
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
},
"404": {
"description": "Guild not found"
"description": "Guild not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
}
},
"security": [
@ -77,13 +98,92 @@
}
},
"400": {
"description": "Invalid ID supplied"
"description": "Invalid ID supplied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
},
"401": {
"description": "Unauthorized"
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
},
"404": {
"description": "Guild not found"
"description": "Guild not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
}
},
"security": [
{
"basicAuth": []
}
]
},
"post": {
"tags": ["Guild config"],
"summary": "Creates a guild's config by ID",
"description": "Create a guild's config when the bot is has joined a new guild.",
"operationId": "postGuildById",
"parameters": [
{
"name": "guildId",
"in": "path",
"description": "ID of guild's config to create",
"required": true,
"schema": {
"type": "string",
"format": "varchar(20)"
}
}
],
"responses": {
"204": {
"description": "successful operation"
},
"400": {
"description": "Invalid ID supplied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
},
"404": {
"description": "Guild not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
}
},
"security": [
@ -101,7 +201,7 @@
{
"name": "guildId",
"in": "path",
"description": "ID of guild config to delete",
"description": "ID of guild's config to delete",
"required": true,
"schema": {
"type": "string",
@ -114,13 +214,34 @@
"description": "successful operation"
},
"400": {
"description": "Invalid ID supplied"
"description": "Invalid ID supplied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
},
"401": {
"description": "Unauthorized"
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
},
"404": {
"description": "Guild not found"
"description": "Guild not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
}
},
"security": [
@ -130,17 +251,17 @@
]
}
},
"/api/{guildId}/tp_messages": {
"/api/{guildId}/timePlanning": {
"get": {
"tags": ["Time planning messages"],
"summary": "Find the tp_messages of guild by ID",
"description": "Returns tp_messages for a guild",
"operationId": "getTp_messagesOfGuildById",
"summary": "Find the timePlanning of guild by ID",
"description": "Returns timePlanning for a guild",
"operationId": "gettimePlanningOfGuildById",
"parameters": [
{
"name": "guildId",
"in": "path",
"description": "ID of guild's tp_messages to return",
"description": "ID of guild's timePlanning to return",
"required": true,
"schema": {
"type": "string",
@ -154,22 +275,40 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/tp_messages"
"$ref": "#/components/schemas/timePlanning"
}
}
}
},
"204": {
"description": "Time planning not enabled for this guild"
},
"400": {
"description": "Invalid ID supplied"
"description": "Invalid ID supplied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
},
"401": {
"description": "Unauthorized"
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
},
"404": {
"description": "Guild not found"
"description": "Guild not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
}
},
"security": [
@ -180,14 +319,14 @@
},
"put": {
"tags": ["Time planning messages"],
"summary": "Put new message IDs for tp_messages of guild by ID",
"description": "Returns tp_messages for a guild",
"operationId": "putTp_messagesOfGuildById",
"summary": "Put new message IDs for timePlanning of guild by ID",
"description": "Returns timePlanning for a guild",
"operationId": "puttimePlanningOfGuildById",
"parameters": [
{
"name": "guildId",
"in": "path",
"description": "ID of guild's tp_messages to return",
"description": "ID of guild's timePlanning to return",
"required": true,
"schema": {
"type": "string",
@ -196,11 +335,11 @@
}
],
"requestBody": {
"description": "Put new message IDs for tp_messages in channel",
"description": "Put new message IDs for timePlanning in channel",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/tp_messages"
"$ref": "#/components/schemas/timePlanning"
}
}
},
@ -211,16 +350,34 @@
"description": "successful operation"
},
"400": {
"description": "Invalid ID supplied"
"description": "Invalid ID supplied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Time planning not enabled for this guild"
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
},
"404": {
"description": "Guild not found"
"description": "Guild not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
}
},
"security": [
@ -234,13 +391,13 @@
"get": {
"tags": ["Matches"],
"summary": "Find all matches of guild by ID",
"description": "Returns tp_messages for a guild",
"description": "Returns timePlanning for a guild",
"operationId": "getMatchesOfGuildById",
"parameters": [
{
"name": "guildId",
"in": "path",
"description": "ID of guild's tp_messages to return",
"description": "ID of guild's timePlanning to return",
"required": true,
"schema": {
"type": "string",
@ -273,17 +430,35 @@
}
}
},
"204": {
"description": "Time planning not enabled for this guild"
},
"400": {
"description": "Invalid ID supplied"
"description": "Invalid ID supplied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
},
"401": {
"description": "Unauthorized"
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
},
"404": {
"description": "Guild not found"
"description": "Guild not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
}
},
"security": [
@ -295,7 +470,7 @@
"post": {
"tags": ["Matches"],
"summary": "Save a new created match of guild by ID",
"description": "Returns tp_messages for a guild",
"description": "Returns timePlanning for a guild",
"operationId": "postMatchOfGuildById",
"parameters": [
{
@ -337,13 +512,34 @@
"description": "successful operation"
},
"400": {
"description": "Invalid ID supplied"
"description": "Invalid ID supplied",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
},
"401": {
"description": "Unauthorized"
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
},
"404": {
"description": "Guild not found"
"description": "Guild not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
}
},
"security": [
@ -361,8 +557,7 @@
"required": ["guildId", "timezone", "features", "matches", "checksum"],
"properties": {
"guildId": {
"type": "number",
"example": 1234567890123456789
"$ref": "#/components/schemas/id"
},
"timezone": {
"type": "string",
@ -388,9 +583,7 @@
"type": "boolean"
},
"channelId": {
"type": "number",
"example": 1234567890123456789,
"nullable": true
"$ref": "#/components/schemas/idOrNull"
},
"targetMinute": {
"type": "number",
@ -416,14 +609,10 @@
"type": "boolean"
},
"isAvailableRoleId": {
"type": "number",
"example": 1234567890123456789,
"nullable": true
"$ref": "#/components/schemas/idOrNull"
},
"wantsToBeNotifieRoledId": {
"type": "number",
"example": 1234567890123456789,
"nullable": true
"$ref": "#/components/schemas/idOrNull"
}
}
}
@ -455,20 +644,16 @@
],
"properties": {
"channelId": {
"type": "number",
"example": 1234567890123456789
"$ref": "#/components/schemas/id"
},
"createrId": {
"type": "number",
"example": 1234567890123456789
"$ref": "#/components/schemas/id"
},
"roleId": {
"type": "number",
"example": 1234567890123456789
"$ref": "#/components/schemas/id"
},
"messageId": {
"type": "number",
"example": 1234567890123456789
"$ref": "#/components/schemas/id"
},
"matchType": {
"type": "string",
@ -486,54 +671,78 @@
}
}
},
"tp_messages": {
"timePlanning": {
"type": "object",
"required": ["channelId", "messageIds"],
"required": [
"enabled",
"channelId",
"rolesEnabled",
"isAvailableRoleId",
"wantsToBeNotifieRoledId",
"messageIds"
],
"properties": {
"enabled": {
"type": "boolean"
},
"channelId": {
"type": "number",
"example": 1234567890123456789
"$ref": "#/components/schemas/idOrNull"
},
"rolesEnabled": {
"type": "boolean"
},
"isAvailableRoleId": {
"$ref": "#/components/schemas/idOrNull"
},
"wantsToBeNotifieRoledId": {
"$ref": "#/components/schemas/idOrNull"
},
"messageIds": {
"type": "object",
"required": ["0", "1", "2", "3", "4", "5", "6"],
"properties": {
"0": {
"type": "number",
"example": 1234567890123456789,
"nullable": true
"$ref": "#/components/schemas/idOrNull"
},
"1": {
"type": "number",
"example": 1234567890123456789,
"nullable": true
"$ref": "#/components/schemas/idOrNull"
},
"2": {
"type": "number",
"example": 1234567890123456789,
"nullable": true
"$ref": "#/components/schemas/idOrNull"
},
"3": {
"type": "number",
"example": 1234567890123456789,
"nullable": true
"$ref": "#/components/schemas/idOrNull"
},
"4": {
"type": "number",
"example": 1234567890123456789,
"nullable": true
"$ref": "#/components/schemas/idOrNull"
},
"5": {
"type": "number",
"example": 1234567890123456789,
"nullable": true
"$ref": "#/components/schemas/idOrNull"
},
"6": {
"type": "number",
"example": 1234567890123456789,
"$ref": "#/components/schemas/idOrNull"
}
}
}
}
},
"id": {
"type": "string",
"pattern": "^\\d{7,20}$",
"example": "1234567890123456789"
},
"idOrNull": {
"type": "string",
"pattern": "^\\d{7,20}$",
"example": "1234567890123456789",
"nullable": true
}
}
},
"error": {
"type": "object",
"required": "error",
"properties": {
"error": {
"type": "string"
}
}
}

View file

@ -1,7 +1,7 @@
import { relations } from "drizzle-orm";
import {
bigint,
boolean,
integer,
pgTable,
primaryKey,
serial,
@ -39,14 +39,16 @@ export const discordTokens = pgTable("tokens", {
});
export const guilds = pgTable("guilds", {
id: integer("id").primaryKey(),
id: bigint("id", { mode: "bigint" }).primaryKey(),
timezone: text("timezone").notNull().default("Etc/UTC"),
tpEnabled: boolean("tp_enabled").notNull().default(false),
tpChannelId: integer("tp_channel_id"),
tpInterval: smallint("target_interval").notNull(),
tpRoles: boolean("tp_roles").notNull(),
isAvailableRoleId: integer("is_available_role_id"),
wantsToBeNotifieRoledId: integer("wants_to_be_notified_role_id"),
tpChannelId: bigint("tp_channel_id", { mode: "bigint" }),
tpInterval: smallint("target_interval").notNull().default(64),
tpRolesEnabled: boolean("tp_roles_enabled").notNull().default(false),
isAvailableRoleId: bigint("is_available_role_id", { mode: "bigint" }),
wantsToBeNotifieRoledId: bigint("wants_to_be_notified_role_id", {
mode: "bigint",
}),
});
export const guildsRelations = relations(guilds, ({ many }) => ({
@ -57,9 +59,9 @@ export const guildsRelations = relations(guilds, ({ many }) => ({
export const tpMessages = pgTable(
"tp_messages",
{
messageId: integer("message_id"),
messageId: bigint("message_id", { mode: "bigint" }),
day: smallint("day").notNull(),
guildId: integer("guild_id")
guildId: bigint("guild_id", { mode: "bigint" })
.notNull()
.references(() => guilds.id, { onDelete: "cascade" }),
},
@ -79,14 +81,14 @@ export const tpMessagesRelations = relations(tpMessages, ({ one }) => ({
export const matches = pgTable("matches", {
id: serial("id").primaryKey(),
channelId: integer("channel_id").notNull(),
channelId: bigint("channel_id", { mode: "bigint" }).notNull(),
matchType: varchar("match_type", { length: 50 }).notNull(),
createrId: integer("creater_id").notNull(),
roleId: integer("role_id").notNull(),
createrId: bigint("creater_id", { mode: "bigint" }).notNull(),
roleId: bigint("role_id", { mode: "bigint" }).notNull(),
opponentName: varchar("opponent_name", { length: 100 }).notNull(),
messageId: integer("message_id").notNull(),
messageId: bigint("message_id", { mode: "bigint" }).notNull(),
utc_ts: timestamp("utc_ts").notNull(),
guildId: integer("guild_id")
guildId: bigint("guild_id", { mode: "bigint" })
.notNull()
.references(() => guilds.id, { onDelete: "cascade" }),
});

View file

@ -2,14 +2,27 @@ import stringify from "json-stable-stringify";
import objectHash from "object-hash";
import { guilds, matches, tpMessages } from "~/drizzle/schema";
import { ExtractDataTypes, GetColumns } from "~/types/db";
import { components } from "~/types/liljudd";
export const buildMatches = (
queryMatches: ExtractDataTypes<GetColumns<typeof matches>>[],
) =>
): components["schemas"]["match"][] =>
queryMatches.map(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
({ id, guildId, utc_ts, ...match }) => ({
...match,
({
channelId,
createrId,
roleId,
messageId,
matchType,
opponentName,
utc_ts,
}) => ({
channelId: channelId.toString(),
createrId: createrId.toString(),
roleId: roleId.toString(),
messageId: messageId.toString(),
matchType,
opponentName,
utc_ts: utc_ts.toISOString(),
}),
);
@ -19,14 +32,14 @@ export function buildConfig(
tpMessages: ExtractDataTypes<GetColumns<typeof tpMessages>>[];
matches: ExtractDataTypes<GetColumns<typeof matches>>[];
},
) {
): components["schemas"]["guildConfig"] {
const {
id,
timezone,
tpEnabled,
tpChannelId,
tpInterval,
tpRoles,
tpRolesEnabled: tpRoles,
isAvailableRoleId,
wantsToBeNotifieRoledId,
} = guildQuery;
@ -36,20 +49,26 @@ export function buildConfig(
const targetDay = (tpInterval >> 11) & 7;
const payload = {
guildId: id,
guildId: id.toString(),
timezone,
features: {
timePlanning: {
enabled: tpEnabled,
channelId: tpChannelId,
channelId: tpChannelId?.toString() ?? null,
targetMinute,
targetHour,
targetDay,
roles: { enabled: tpRoles, isAvailableRoleId, wantsToBeNotifieRoledId },
roles: {
enabled: tpRoles,
isAvailableRoleId: isAvailableRoleId?.toString() ?? null,
wantsToBeNotifieRoledId: wantsToBeNotifieRoledId?.toString() ?? null,
},
},
},
matches: buildMatches(guildQuery.matches),
checksum: objectHash(stringify(guildQuery)),
};
return payload;
// generate checksum from payload because
// from guildQuery results in bigint serialization error
return { ...payload, checksum: objectHash(stringify(payload)) };
}

View file

@ -12,6 +12,7 @@ export function ErrorResponse<
M extends Methods<P>,
C extends StatusCodes<P, M> = StatusCodes<P, M>,
>(code: C, error?: string): APIResponse<P, M> {
console.log(code, error);
const responseData = {
error: error ?? httpStatus[`${httpStatus[code]}_NAME`],
};

View file

@ -1,13 +1,17 @@
import moment from "moment-timezone";
import { z } from "zod";
export const zodId = z
const zodId = z
.string()
.refine((value) => /^\d{7,20}$/.test(value), "Invalid ID supplied")
.transform((value) => parseInt(value));
.refine((value) => /^\d{7,20}$/.test(value), "Invalid ID supplied");
export const zodBigIntId = zodId.transform((value) => BigInt(value));
export const zodTpMessages = z.object({
channelId: zodId,
enabled: z.boolean(),
channelId: zodId.nullable(),
rolesEnabled: z.boolean(),
isAvailableRoleId: zodId.nullable(),
wantsToBeNotifieRoledId: zodId.nullable(),
messageIds: z.object({
"0": zodId.nullable(),
"1": zodId.nullable(),

View file

@ -1,8 +1,14 @@
import { createMiddleware } from "@solidjs/start/middleware";
import { Session, User, verifyRequestOrigin } from "lucia";
import colors from "colors";
import fs from "fs";
import { verifyRequestOrigin } from "lucia";
import { appendHeader, getCookie, getHeader } from "vinxi/http";
import { lucia } from "./lib/auth";
colors.enable();
let started: boolean = false;
export default createMiddleware({
onRequest: async (event) => {
if (event.nativeEvent.node.req.method !== "GET") {
@ -43,12 +49,52 @@ export default createMiddleware({
event.nativeEvent.context.session = session;
event.nativeEvent.context.user = user;
},
});
onBeforeResponse: async (event, response) => {
let consoleLog = "",
fileLog = "";
declare module "h3" {
// eslint-disable-next-line no-unused-vars
interface H3EventContext {
user: User | null;
session: Session | null;
if (!started) {
try {
await fs.promises.mkdir("log");
console.log("Created 'log' Folder.");
} catch {}
started = true;
}
}
const currentDate = new Date();
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, "0");
const day = String(currentDate.getDate()).padStart(2, "0");
const hours = String(currentDate.getHours()).padStart(2, "0");
const minutes = String(currentDate.getMinutes()).padStart(2, "0");
const seconds = String(currentDate.getSeconds()).padStart(2, "0");
// Create a short and numeric representation
const date = `[${year}-${month}-${day}_${hours}:${minutes}:${seconds}]`;
const xForwardedFor = event.request.headers.get("x-forwarded-for");
const ip = (xForwardedFor || "127.0.0.1, 192.168.178.1").split(",");
const route = event.request.url;
const frontend = !new URL(event.request.url).pathname.startsWith("/api");
const method = frontend ? "Frontend" : event.request.method;
const code =
(response.body as Response | undefined)?.status ?? event.response.status;
consoleLog += [
date,
ip[0].yellow,
method,
code,
route?.green,
event.nativeEvent.context.user?.discord_id.rainbow,
].join(" ");
fileLog += [
date,
ip[0],
method,
code,
route,
event.nativeEvent.context.user?.discord_id,
].join(" ");
await fs.promises.appendFile("log/log.txt", fileLog + "\n");
console.log(consoleLog);
},
});

View file

@ -5,7 +5,7 @@ import { guilds } from "~/drizzle/schema";
import { BasicAuth } from "~/lib/auth";
import { buildConfig } from "~/lib/responseBuilders";
import { ErrorResponse, Res } from "~/lib/responses";
import { zodId } from "~/lib/zod";
import { zodBigIntId } from "~/lib/zod";
import { APIResponse } from "~/types/backend";
type Path = "/api/{guildId}/config";
@ -22,9 +22,9 @@ export const GET = async (
return ErrorResponse("UNAUTHORIZED");
}
let guildId: number;
let guildId: bigint;
try {
guildId = zodId.parse(event.params.guildId);
guildId = zodBigIntId.parse(event.params.guildId);
} catch (e) {
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
}
@ -41,6 +41,32 @@ export const GET = async (
return Res("OK", buildConfig(guildQuery));
};
export const POST = async (
event: APIEvent,
): Promise<APIResponse<Path, "post">> => {
switch (event.request.headers.get("authorization")) {
case BasicAuth.unencoded:
case BasicAuth.encoded:
break;
default:
return ErrorResponse("UNAUTHORIZED");
}
let guildId: bigint;
try {
guildId = zodBigIntId.parse(event.params.guildId);
} catch (e) {
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
}
const guildQuery = await db.insert(guilds).values({ id: guildId }).execute();
if (!guildQuery) return ErrorResponse("NOT_FOUND");
return Res("NO_CONTENT", null);
};
export const DELETE = async (
event: APIEvent,
): Promise<APIResponse<Path, "delete">> => {
@ -53,9 +79,9 @@ export const DELETE = async (
return ErrorResponse("UNAUTHORIZED");
}
let guildId: number;
let guildId: bigint;
try {
guildId = zodId.parse(event.params.guildId);
guildId = zodBigIntId.parse(event.params.guildId);
} catch (e) {
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
}

View file

@ -5,7 +5,7 @@ import { guilds, matches } from "~/drizzle/schema";
import { BasicAuth } from "~/lib/auth";
import { buildMatches } from "~/lib/responseBuilders";
import { ErrorResponse, Res } from "~/lib/responses";
import { zodId, zodMatch } from "~/lib/zod";
import { zodBigIntId, zodMatch } from "~/lib/zod";
import { APIResponse, RequestBody } from "~/types/backend";
type Path = "/api/{guildId}/matches";
@ -22,9 +22,9 @@ export const GET = async (
return ErrorResponse("UNAUTHORIZED");
}
let guildId: number;
let guildId: bigint;
try {
guildId = zodId.parse(event.params.guildId);
guildId = zodBigIntId.parse(event.params.guildId);
} catch (e) {
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
}
@ -60,9 +60,9 @@ export const POST = async (
return ErrorResponse("UNAUTHORIZED");
}
let guildId: number;
let guildId: bigint;
try {
guildId = zodId.parse(event.params.guildId);
guildId = zodBigIntId.parse(event.params.guildId);
} catch (e) {
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
}
@ -94,8 +94,13 @@ export const POST = async (
);
await db.insert(matches).values({
...body.match,
guildId: guild.id,
channelId: BigInt(body.match.channelId),
roleId: BigInt(body.match.roleId),
createrId: BigInt(body.match.createrId),
messageId: BigInt(body.match.messageId),
matchType: body.match.matchType,
opponentName: body.match.opponentName,
utc_ts: new Date(body.match.utc_ts),
});

View file

@ -4,14 +4,14 @@ import db from "~/drizzle";
import { guilds, tpMessages } from "~/drizzle/schema";
import { BasicAuth } from "~/lib/auth";
import { ErrorResponse, Res } from "~/lib/responses";
import { zodId, zodTpMessages } from "~/lib/zod";
import { zodBigIntId, zodTpMessages } from "~/lib/zod";
import { APIResponse, RequestBody } from "~/types/backend";
type Path = "/api/{guildId}/tp_messages";
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, number | null>;
type Messages = Record<DayKeys, string | null>;
export const GET = async (
event: APIEvent,
@ -25,9 +25,9 @@ export const GET = async (
return ErrorResponse("UNAUTHORIZED");
}
let guildId: number;
let guildId: bigint;
try {
guildId = zodId.parse(event.params.guildId);
guildId = zodBigIntId.parse(event.params.guildId);
} catch (e) {
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
}
@ -41,13 +41,11 @@ export const GET = async (
if (!guild) return ErrorResponse("NOT_FOUND");
if (!guild.tpEnabled || !guild.tpChannelId) return Res("NO_CONTENT", null);
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;
acc[day] = message.messageId?.toString() ?? null;
return acc;
},
{
@ -62,7 +60,11 @@ export const GET = async (
);
return Res("OK", {
channelId: guild.tpChannelId,
enabled: guild.tpEnabled,
channelId: guild.tpChannelId?.toString() ?? null,
rolesEnabled: guild.tpRolesEnabled,
isAvailableRoleId: guild.isAvailableRoleId?.toString() ?? null,
wantsToBeNotifieRoledId: guild.wantsToBeNotifieRoledId?.toString() ?? null,
messageIds: tpMessages,
});
};
@ -79,9 +81,9 @@ export const PUT = async (
return ErrorResponse("UNAUTHORIZED");
}
let guildId: number;
let guildId: bigint;
try {
guildId = zodId.parse(event.params.guildId);
guildId = zodBigIntId.parse(event.params.guildId);
} catch (e) {
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
}
@ -95,8 +97,6 @@ export const PUT = async (
if (!guild) return ErrorResponse("NOT_FOUND");
if (!guild.tpEnabled) return ErrorResponse("FORBIDDEN");
const unparsedBody = await new Response(event.request.body).json();
let body: RequestBody<Path, "put">;
@ -106,19 +106,36 @@ export const PUT = async (
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
}
if (guild.tpChannelId !== body.channelId)
const {
enabled,
channelId,
rolesEnabled,
isAvailableRoleId,
wantsToBeNotifieRoledId,
messageIds,
} = body;
if (guild.tpChannelId !== channelId)
await db
.update(guilds)
.set({ tpChannelId: body.channelId })
.set({
tpEnabled: enabled,
tpChannelId: channelId ? BigInt(channelId) : null,
tpRolesEnabled: rolesEnabled,
isAvailableRoleId: isAvailableRoleId ? BigInt(isAvailableRoleId) : null,
wantsToBeNotifieRoledId: wantsToBeNotifieRoledId
? BigInt(wantsToBeNotifieRoledId)
: null,
})
.where(eq(guilds.id, guild.id))
.execute();
await Promise.all(
DayKeys.map(async (dayStr) => {
const day = parseInt(dayStr);
const messageId = messageIds[dayStr];
await db
.update(tpMessages)
.set({ messageId: body.messageIds[dayStr] })
.set({ messageId: messageId ? BigInt(messageId) : null })
.where(and(eq(tpMessages.guildId, guild.id), eq(tpMessages.day, day)))
.execute();
}),

View file

@ -5,7 +5,7 @@ import { guilds } from "~/drizzle/schema";
import { BasicAuth } from "~/lib/auth";
import { buildConfig } from "~/lib/responseBuilders";
import { ErrorResponse, Res } from "~/lib/responses";
import { zodId } from "~/lib/zod";
import { zodBigIntId } from "~/lib/zod";
import { APIResponse } from "~/types/backend";
type Path = "/api/boot";
@ -22,9 +22,9 @@ export const GET = async (
return ErrorResponse("UNAUTHORIZED");
}
let guildId: number;
let guildId: bigint;
try {
guildId = zodId.parse(event.params.guildId);
guildId = zodBigIntId.parse(event.params.guildId);
} catch (e) {
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
}

2
src/types/db.d.ts vendored
View file

@ -6,6 +6,8 @@ export type GetColumns<T> =
export type ExtractDataTypes<T> = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[K in keyof T]: T[K] extends PgColumn<infer ColumnConfig, any, any>
? ColumnConfig["notNull"] extends true
? ColumnConfig["data"]
: ColumnConfig["data"] | null
: unknown;
};

1
src/types/env.d.ts vendored
View file

@ -12,7 +12,6 @@ interface ImportMetaEnv {
readonly VITE_DATABASE_URL: string;
}
// eslint-disable-next-line no-unused-vars
interface ImportMeta {
readonly env: ImportMetaEnv;
}

242
src/types/liljudd.d.ts vendored
View file

@ -18,33 +18,38 @@ export interface paths {
* @description Returns a single guild's config.
*/
get: operations["getGuildById"];
/**
* Creates a guild's config by ID
* @description Create a guild's config when the bot is has joined a new guild.
*/
post: operations["postGuildById"];
/**
* Deletes a guild's config by ID
* @description Delete a guild's config when the bot is removed from the guild.
*/
delete: operations["deleteGuildById"];
};
"/api/{guildId}/tp_messages": {
"/api/{guildId}/timePlanning": {
/**
* Find the tp_messages of guild by ID
* @description Returns tp_messages for a guild
* Find the timePlanning of guild by ID
* @description Returns timePlanning for a guild
*/
get: operations["getTp_messagesOfGuildById"];
get: operations["gettimePlanningOfGuildById"];
/**
* Put new message IDs for tp_messages of guild by ID
* @description Returns tp_messages for a guild
* Put new message IDs for timePlanning of guild by ID
* @description Returns timePlanning for a guild
*/
put: operations["putTp_messagesOfGuildById"];
put: operations["puttimePlanningOfGuildById"];
};
"/api/{guildId}/matches": {
/**
* Find all matches of guild by ID
* @description Returns tp_messages for a guild
* @description Returns timePlanning for a guild
*/
get: operations["getMatchesOfGuildById"];
/**
* Save a new created match of guild by ID
* @description Returns tp_messages for a guild
* @description Returns timePlanning for a guild
*/
post: operations["postMatchOfGuildById"];
};
@ -55,8 +60,7 @@ export type webhooks = Record<string, never>;
export interface components {
schemas: {
guildConfig: {
/** @example 1234567890123456800 */
guildId: number;
guildId: components["schemas"]["id"];
/**
* Format: text
* @example Europe/Berlin
@ -65,8 +69,7 @@ export interface components {
features: {
timePlanning: {
enabled: boolean;
/** @example 1234567890123456800 */
channelId: number | null;
channelId: components["schemas"]["idOrNull"];
/** @example 0 */
targetMinute: number;
/** @example 1 */
@ -75,10 +78,8 @@ export interface components {
targetDay: number;
roles: {
enabled: boolean;
/** @example 1234567890123456800 */
isAvailableRoleId: number | null;
/** @example 1234567890123456800 */
wantsToBeNotifieRoledId: number | null;
isAvailableRoleId: components["schemas"]["idOrNull"];
wantsToBeNotifieRoledId: components["schemas"]["idOrNull"];
};
};
};
@ -86,14 +87,10 @@ export interface components {
checksum: string;
};
match: {
/** @example 1234567890123456800 */
channelId: number;
/** @example 1234567890123456800 */
createrId: number;
/** @example 1234567890123456800 */
roleId: number;
/** @example 1234567890123456800 */
messageId: number;
channelId: components["schemas"]["id"];
createrId: components["schemas"]["id"];
roleId: components["schemas"]["id"];
messageId: components["schemas"]["id"];
/**
* Format: varchar(50)
* @example Scrim
@ -107,26 +104,29 @@ export interface components {
/** @example 2020-01-01T00:00:00Z */
utc_ts: string;
};
tp_messages: {
/** @example 1234567890123456800 */
channelId: number;
timePlanning: {
enabled: boolean;
channelId: components["schemas"]["idOrNull"];
rolesEnabled: boolean;
isAvailableRoleId: components["schemas"]["idOrNull"];
wantsToBeNotifieRoledId: components["schemas"]["idOrNull"];
messageIds: {
/** @example 1234567890123456800 */
0: number | null;
/** @example 1234567890123456800 */
1: number | null;
/** @example 1234567890123456800 */
2: number | null;
/** @example 1234567890123456800 */
3: number | null;
/** @example 1234567890123456800 */
4: number | null;
/** @example 1234567890123456800 */
5: number | null;
/** @example 1234567890123456800 */
6: number | null;
0: components["schemas"]["idOrNull"];
1: components["schemas"]["idOrNull"];
2: components["schemas"]["idOrNull"];
3: components["schemas"]["idOrNull"];
4: components["schemas"]["idOrNull"];
5: components["schemas"]["idOrNull"];
6: components["schemas"]["idOrNull"];
};
};
/** @example 1234567890123456789 */
id: string;
/** @example 1234567890123456789 */
idOrNull: string | null;
error: {
error?: string;
};
};
responses: never;
parameters: never;
@ -155,15 +155,21 @@ export interface operations {
};
/** @description Invalid ID supplied */
400: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
/** @description Unauthorized */
401: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
/** @description Guild not found */
404: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
};
};
@ -187,16 +193,58 @@ export interface operations {
};
/** @description Invalid ID supplied */
400: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
/** @description Unauthorized */
401: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
/** @description Guild not found */
404: {
content: {
"application/json": components["schemas"]["error"];
};
};
};
};
/**
* Creates a guild's config by ID
* @description Create a guild's config when the bot is has joined a new guild.
*/
postGuildById: {
parameters: {
path: {
/** @description ID of guild's config to create */
guildId: string;
};
};
responses: {
/** @description successful operation */
204: {
content: never;
};
/** @description Invalid ID supplied */
400: {
content: {
"application/json": components["schemas"]["error"];
};
};
/** @description Unauthorized */
401: {
content: {
"application/json": components["schemas"]["error"];
};
};
/** @description Guild not found */
404: {
content: {
"application/json": components["schemas"]["error"];
};
};
};
};
/**
@ -206,7 +254,7 @@ export interface operations {
deleteGuildById: {
parameters: {
path: {
/** @description ID of guild config to delete */
/** @description ID of guild's config to delete */
guildId: string;
};
};
@ -217,26 +265,32 @@ export interface operations {
};
/** @description Invalid ID supplied */
400: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
/** @description Unauthorized */
401: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
/** @description Guild not found */
404: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
};
};
/**
* Find the tp_messages of guild by ID
* @description Returns tp_messages for a guild
* Find the timePlanning of guild by ID
* @description Returns timePlanning for a guild
*/
getTp_messagesOfGuildById: {
gettimePlanningOfGuildById: {
parameters: {
path: {
/** @description ID of guild's tp_messages to return */
/** @description ID of guild's timePlanning to return */
guildId: string;
};
};
@ -244,42 +298,44 @@ export interface operations {
/** @description successful operation */
200: {
content: {
"application/json": components["schemas"]["tp_messages"];
"application/json": components["schemas"]["timePlanning"];
};
};
/** @description Time planning not enabled for this guild */
204: {
content: never;
};
/** @description Invalid ID supplied */
400: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
/** @description Unauthorized */
401: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
/** @description Guild not found */
404: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
};
};
/**
* Put new message IDs for tp_messages of guild by ID
* @description Returns tp_messages for a guild
* Put new message IDs for timePlanning of guild by ID
* @description Returns timePlanning for a guild
*/
putTp_messagesOfGuildById: {
puttimePlanningOfGuildById: {
parameters: {
path: {
/** @description ID of guild's tp_messages to return */
/** @description ID of guild's timePlanning to return */
guildId: string;
};
};
/** @description Put new message IDs for tp_messages in channel */
/** @description Put new message IDs for timePlanning in channel */
requestBody: {
content: {
"application/json": components["schemas"]["tp_messages"];
"application/json": components["schemas"]["timePlanning"];
};
};
responses: {
@ -289,30 +345,32 @@ export interface operations {
};
/** @description Invalid ID supplied */
400: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
/** @description Unauthorized */
401: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
/** @description Time planning not enabled for this guild */
403: {
content: never;
};
/** @description Guild not found */
404: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
};
};
/**
* Find all matches of guild by ID
* @description Returns tp_messages for a guild
* @description Returns timePlanning for a guild
*/
getMatchesOfGuildById: {
parameters: {
path: {
/** @description ID of guild's tp_messages to return */
/** @description ID of guild's timePlanning to return */
guildId: string;
};
};
@ -330,27 +388,29 @@ export interface operations {
};
};
};
/** @description Time planning not enabled for this guild */
204: {
content: never;
};
/** @description Invalid ID supplied */
400: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
/** @description Unauthorized */
401: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
/** @description Guild not found */
404: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
};
};
/**
* Save a new created match of guild by ID
* @description Returns tp_messages for a guild
* @description Returns timePlanning for a guild
*/
postMatchOfGuildById: {
parameters: {
@ -380,15 +440,21 @@ export interface operations {
};
/** @description Invalid ID supplied */
400: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
/** @description Unauthorized */
401: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
/** @description Guild not found */
404: {
content: never;
content: {
"application/json": components["schemas"]["error"];
};
};
};
};

View file

@ -3,7 +3,6 @@ import { lucia } from "~/lib/auth";
import { ExtractDataTypes, GetColumns } from "./db";
declare module "lucia" {
// eslint-disable-next-line no-unused-vars
interface Register {
Lucia: typeof lucia;
DatabaseUserAttributes: DatabaseUserAttributes;
@ -11,6 +10,4 @@ declare module "lucia" {
}
interface DatabaseUserAttributes
extends ExtractDataTypes<GetColumns<typeof users>> {
warst: string;
}
extends ExtractDataTypes<GetColumns<typeof users>> {}

12
src/types/vinxi.d.ts vendored Normal file
View file

@ -0,0 +1,12 @@
import { H3EventContext as EventContext } from "h3/dist";
import { Session, User } from "lucia";
declare module "vinxi/http" {
interface H3EventContext extends EventContext {
user: User | null;
session: Session | null;
}
class H3Eventt {
context: H3EventContext;
}
}