Fix: Finished Backend
This commit is contained in:
parent
6b388729d9
commit
ffaf8d989e
30 changed files with 1478 additions and 873 deletions
|
@ -4,5 +4,8 @@
|
|||
"node": true
|
||||
},
|
||||
"plugins": ["solid"],
|
||||
"extends": ["eslint:recommended", "plugin:solid/typescript"]
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:solid/typescript"
|
||||
]
|
||||
}
|
||||
|
|
25
package.json
25
package.json
|
@ -23,31 +23,36 @@
|
|||
"@lucia-auth/adapter-drizzle": "^1.0.2",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@solidjs/meta": "^0.29.3",
|
||||
"@solidjs/router": "^0.12.3",
|
||||
"@solidjs/start": "^0.5.9",
|
||||
"arctic": "^1.1.6",
|
||||
"drizzle-orm": "^0.29.3",
|
||||
"@solidjs/router": "^0.12.4",
|
||||
"@solidjs/start": "^0.5.10",
|
||||
"arctic": "^1.2.0",
|
||||
"drizzle-orm": "^0.29.4",
|
||||
"http-status": "^1.7.4",
|
||||
"json-stable-stringify": "^1.1.1",
|
||||
"lucia": "^3.0.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"openapi-fetch": "^0.9.1",
|
||||
"object-hash": "^3.0.0",
|
||||
"openapi-fetch": "^0.9.2",
|
||||
"postgres": "^3.4.3",
|
||||
"solid-js": "^1.8.15",
|
||||
"vinxi": "^0.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.1",
|
||||
"dotenv": "^16.4.4",
|
||||
"@types/json-stable-stringify": "^1.0.36",
|
||||
"@types/object-hash": "^3.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"drizzle-kit": "^0.20.14",
|
||||
"drizzle-zod": "^0.5.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-solid": "^0.13.1",
|
||||
"h3": "^1.10.1",
|
||||
"h3": "^1.10.2",
|
||||
"openapi-typescript": "^6.7.4",
|
||||
"pg": "^8.11.3",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"sass": "^1.71.0",
|
||||
"sass": "^1.71.1",
|
||||
"typescript": "^5.3.3",
|
||||
"zod": "3.22.4"
|
||||
},
|
||||
|
|
660
pnpm-lock.yaml
660
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -10,19 +10,22 @@
|
|||
"version": "0.0.0"
|
||||
},
|
||||
"paths": {
|
||||
"/api/boot/config": {
|
||||
"/api/boot": {
|
||||
"get": {
|
||||
"tags": ["Guild configs"],
|
||||
"summary": "Find a guild's config by ID",
|
||||
"description": "Returns a single guild's config.",
|
||||
"operationId": "getGuildsFromBoot",
|
||||
"tags": ["Bot bootup"],
|
||||
"summary": "Retrieve all guild's configs",
|
||||
"description": "Returns all guild's configs.",
|
||||
"operationId": "getGuildsForBoot",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/bootConfig"
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/guildConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,20 +33,23 @@
|
|||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"404": {
|
||||
"description": "Guild not found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bot_token": []
|
||||
"basicAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/{guildId}/config": {
|
||||
"get": {
|
||||
"tags": ["Guild configs"],
|
||||
"tags": ["Guild config"],
|
||||
"summary": "Find a guild's config by ID",
|
||||
"description": "Returns a single guild's config.",
|
||||
"operationId": "getGuildById",
|
||||
|
@ -73,18 +79,21 @@
|
|||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"404": {
|
||||
"description": "Guild not found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bot_token": []
|
||||
"basicAuth": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"tags": ["Guild configs"],
|
||||
"tags": ["Guild config"],
|
||||
"summary": "Deletes a guild's config by ID",
|
||||
"description": "Delete a guild's config when the bot is removed from the guild.",
|
||||
"operationId": "deleteGuildById",
|
||||
|
@ -107,13 +116,16 @@
|
|||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"404": {
|
||||
"description": "Guild not found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bot_token": []
|
||||
"basicAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -153,19 +165,22 @@
|
|||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"404": {
|
||||
"description": "Guild not found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bot_token": []
|
||||
"basicAuth": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"tags": ["Time planning messages"],
|
||||
"summary": "Put message IDs for tp_messages of guild by ID",
|
||||
"summary": "Put new message IDs for tp_messages of guild by ID",
|
||||
"description": "Returns tp_messages for a guild",
|
||||
"operationId": "putTp_messagesOfGuildById",
|
||||
"parameters": [
|
||||
|
@ -180,30 +195,37 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"requestBody": {
|
||||
"description": "Put new message IDs for tp_messages in channel",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/tp_messages"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Time planning not enabled for this guild"
|
||||
"description": "successful operation"
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"description": "Time planning not enabled for this guild"
|
||||
},
|
||||
"404": {
|
||||
"description": "Guild not found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bot_token": []
|
||||
"basicAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -232,9 +254,20 @@
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["matches", "timezone"],
|
||||
"properties": {
|
||||
"matches": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/tp_messages"
|
||||
"$ref": "#/components/schemas/match"
|
||||
}
|
||||
},
|
||||
"timezone": {
|
||||
"type": "string",
|
||||
"format": "text",
|
||||
"example": "Europe/Berlin"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -246,21 +279,22 @@
|
|||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"404": {
|
||||
"description": "Guild not found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bot_token": []
|
||||
"basicAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/{guildId}/matches/{channelId}": {
|
||||
"post": {
|
||||
"tags": ["Matches"],
|
||||
"summary": "Save a new created match in channel of guild by IDs",
|
||||
"summary": "Save a new created match of guild by ID",
|
||||
"description": "Returns tp_messages for a guild",
|
||||
"operationId": "postMatchOfGuildById",
|
||||
"parameters": [
|
||||
|
@ -273,108 +307,48 @@
|
|||
"type": "string",
|
||||
"format": "varchar(20)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "channelId",
|
||||
"in": "path",
|
||||
"description": "ID of match's channel to set",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "varchar(20)"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"requestBody": {
|
||||
"description": "Save a new created match in channel",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/tp_messages"
|
||||
"type": "object",
|
||||
"required": ["match", "timezone"],
|
||||
"properties": {
|
||||
"match": {
|
||||
"$ref": "#/components/schemas/match"
|
||||
},
|
||||
"timezone": {
|
||||
"type": "string",
|
||||
"format": "text",
|
||||
"example": "Europe/Berlin",
|
||||
"description": "Has to match guild tz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Time planning not enabled for this guild"
|
||||
"description": "successful operation"
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"404": {
|
||||
"description": "Guild not found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bot_token": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/{guildId}/matches/{channelId}/{matchMessageId}": {
|
||||
"put": {
|
||||
"tags": ["Matches"],
|
||||
"summary": "Set state for match of guild by IDs",
|
||||
"description": "Returns tp_messages for a guild",
|
||||
"operationId": "putMatchOfGuildById",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "guildId",
|
||||
"in": "path",
|
||||
"description": "ID of guild's tp_messages to return",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "varchar(20)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "channelId",
|
||||
"in": "path",
|
||||
"description": "ID of match's channel to set",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "varchar(20)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "matchMessageId",
|
||||
"in": "path",
|
||||
"description": "ID of match's message Id to set",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "varchar(20)"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "successful operation",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/tp_messages"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"204": {
|
||||
"description": "Time planning not enabled for this guild"
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid ID supplied"
|
||||
},
|
||||
"404": {
|
||||
"description": "Guild not found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bot_token": []
|
||||
"basicAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -382,21 +356,11 @@
|
|||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"bootConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"guilds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/guildConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"guildConfig": {
|
||||
"type": "object",
|
||||
"required": ["guildId", "timezone", "features", "matches", "checksum"],
|
||||
"properties": {
|
||||
"guildID": {
|
||||
"guildId": {
|
||||
"type": "string",
|
||||
"format": "varchar(20)",
|
||||
"example": "1234567890123456789"
|
||||
|
@ -408,14 +372,27 @@
|
|||
},
|
||||
"features": {
|
||||
"type": "object",
|
||||
"required": ["timePlanning"],
|
||||
"properties": {
|
||||
"time_planning": {
|
||||
"timePlanning": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enabled",
|
||||
"channelId",
|
||||
"targetMinute",
|
||||
"targetHour",
|
||||
"targetDay",
|
||||
"roles"
|
||||
],
|
||||
"properties": {
|
||||
"channelID": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"channelId": {
|
||||
"type": "string",
|
||||
"format": "varchar(20)",
|
||||
"example": "1234567890123456789"
|
||||
"example": "1234567890123456789",
|
||||
"nullable": true
|
||||
},
|
||||
"targetMinute": {
|
||||
"type": "number",
|
||||
|
@ -431,6 +408,11 @@
|
|||
},
|
||||
"roles": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enabled",
|
||||
"isAvailableRoleId",
|
||||
"wantsToBeNotifieRoledId"
|
||||
],
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
|
@ -458,13 +440,25 @@
|
|||
"items": {
|
||||
"$ref": "#/components/schemas/match"
|
||||
}
|
||||
},
|
||||
"checksum": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"match": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"channelId",
|
||||
"matchType",
|
||||
"createrId",
|
||||
"roleId",
|
||||
"opponentName",
|
||||
"messageId",
|
||||
"utc_ts"
|
||||
],
|
||||
"properties": {
|
||||
"channelID": {
|
||||
"channelId": {
|
||||
"type": "string",
|
||||
"format": "varcharq(20)",
|
||||
"example": "1234567890123456789"
|
||||
|
@ -489,54 +483,81 @@
|
|||
"format": "varchar(100)",
|
||||
"example": "?"
|
||||
},
|
||||
"messsageId": {
|
||||
"messageId": {
|
||||
"type": "string",
|
||||
"format": "varchar(20)",
|
||||
"example": "1234567890123456789"
|
||||
},
|
||||
"utc_ts": {
|
||||
"type": "string",
|
||||
"example": "1706180188"
|
||||
"example": "2020-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tp_messages": {
|
||||
"type": "object",
|
||||
"required": ["channelId", "messageIds"],
|
||||
"properties": {
|
||||
"guildId": {
|
||||
"type": "string",
|
||||
"format": "varchar(20)",
|
||||
"example": "1234567890123456789"
|
||||
},
|
||||
"channelId": {
|
||||
"type": "string",
|
||||
"format": "varchar(20)",
|
||||
"example": "1234567890123456789"
|
||||
},
|
||||
"messageIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["0", "1", "2", "3", "4", "5", "6"],
|
||||
"properties": {
|
||||
"0": {
|
||||
"type": "string",
|
||||
"format": "varchar(20)"
|
||||
"format": "varchar(20)",
|
||||
"example": "1234567890123456789",
|
||||
"nullable": true
|
||||
},
|
||||
"example": [
|
||||
"1234567890123456789",
|
||||
"1234567890123456789",
|
||||
"1234567890123456789",
|
||||
"1234567890123456789",
|
||||
"1234567890123456789",
|
||||
"1234567890123456789",
|
||||
"1234567890123456789"
|
||||
]
|
||||
"1": {
|
||||
"type": "string",
|
||||
"format": "varchar(20)",
|
||||
"example": "1234567890123456789",
|
||||
"nullable": true
|
||||
},
|
||||
"2": {
|
||||
"type": "string",
|
||||
"format": "varchar(20)",
|
||||
"example": "1234567890123456789",
|
||||
"nullable": true
|
||||
},
|
||||
"3": {
|
||||
"type": "string",
|
||||
"format": "varchar(20)",
|
||||
"example": "1234567890123456789",
|
||||
"nullable": true
|
||||
},
|
||||
"4": {
|
||||
"type": "string",
|
||||
"format": "varchar(20)",
|
||||
"example": "1234567890123456789",
|
||||
"nullable": true
|
||||
},
|
||||
"5": {
|
||||
"type": "string",
|
||||
"format": "varchar(20)",
|
||||
"example": "1234567890123456789",
|
||||
"nullable": true
|
||||
},
|
||||
"6": {
|
||||
"type": "string",
|
||||
"format": "varchar(20)",
|
||||
"example": "1234567890123456789",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securitySchemes": {
|
||||
"bot_token": {
|
||||
"basicAuth": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT"
|
||||
"scheme": "basic"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
{
|
||||
"data": {
|
||||
"guilds": [
|
||||
{
|
||||
"guildID": "some ID",
|
||||
"UTCOffset": 0,
|
||||
"features": {
|
||||
"time_planning": {
|
||||
"channelID": "some ID",
|
||||
"targetWeekday": 0,
|
||||
"targetHour": 0,
|
||||
"targetMinute": 0,
|
||||
"isAvailableRoleId": "some ID",
|
||||
"wantsToBeNotifieRoledId": "some ID"
|
||||
}
|
||||
},
|
||||
"matches": [
|
||||
{
|
||||
"channelID": "some ID",
|
||||
"matchType": "",
|
||||
"createrId": "some ID",
|
||||
"roleId": "some ID",
|
||||
"opponentName": "",
|
||||
"messsageId": "",
|
||||
"plannedFor": 1704314625000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"accessToken": "some Token"
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { faCirclePlus } from "@fortawesome/pro-regular-svg-icons";
|
||||
import { JSX, Show, Suspense } from "solid-js";
|
||||
import { JSX, Show } from "solid-js";
|
||||
import "../styles/components/NavBar.scss";
|
||||
import { FontAwesomeIcon } from "./FontAwesomeIcon";
|
||||
import NavUser from "./NavUser";
|
||||
|
@ -41,9 +41,7 @@ function NavBar() {
|
|||
>
|
||||
<FontAwesomeIcon class="lower" icon={faCirclePlus} size="xl" />
|
||||
</Li>
|
||||
<Suspense>
|
||||
<NavUser />
|
||||
</Suspense>
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
|
|
|
@ -3,48 +3,27 @@ import {
|
|||
faArrowRightToBracket,
|
||||
faGear,
|
||||
} from "@fortawesome/pro-regular-svg-icons";
|
||||
import { User } from "lucia";
|
||||
import { Show, createResource } from "solid-js";
|
||||
import { cache, createAsync } from "@solidjs/router";
|
||||
import { Show } from "solid-js";
|
||||
import { getRequestEvent } from "solid-js/web";
|
||||
import { FontAwesomeIcon } from "./FontAwesomeIcon";
|
||||
import { Li } from "./NavBar";
|
||||
|
||||
async function getUser(): Promise<
|
||||
| ({
|
||||
success: false;
|
||||
message: string;
|
||||
// user?: undefined;
|
||||
} & Partial<User>)
|
||||
| ({
|
||||
success: true;
|
||||
message?: undefined;
|
||||
} & User)
|
||||
> {
|
||||
async function getUser() {
|
||||
"use server";
|
||||
|
||||
const event = getRequestEvent();
|
||||
if (!event) return { success: false, message: "No request event!" };
|
||||
|
||||
const pathname = new URL(event.request.url).pathname;
|
||||
const { user } = event.nativeEvent.context;
|
||||
if (!user) return { success: false, message: "User not logged in!" };
|
||||
|
||||
console.log("userInfo", pathname, "success");
|
||||
|
||||
return { success: true, ...user };
|
||||
return event?.nativeEvent.context.user;
|
||||
}
|
||||
|
||||
const cachedUser = cache(() => getUser(), "userInfo");
|
||||
|
||||
function NavUser() {
|
||||
const [user] = createResource(async () => {
|
||||
const user = await getUser();
|
||||
|
||||
if (!user.success) console.error("userInfo", user.message);
|
||||
|
||||
return user;
|
||||
});
|
||||
const user = createAsync(() => cachedUser());
|
||||
const pfp = () => {
|
||||
const thisUser = user();
|
||||
if (!thisUser?.success) return "";
|
||||
if (!thisUser?.id) return "";
|
||||
|
||||
return thisUser.image
|
||||
? `https://cdn.discordapp.com/avatars/${thisUser.discord_id}/${thisUser.image}.png`
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { relations } from "drizzle-orm";
|
||||
import {
|
||||
boolean,
|
||||
integer,
|
||||
pgTable,
|
||||
primaryKey,
|
||||
serial,
|
||||
smallint,
|
||||
text,
|
||||
|
@ -37,10 +37,51 @@ export const discordTokens = pgTable("tokens", {
|
|||
expiresAt: timestamp("expires_at", { mode: "date" }).notNull(),
|
||||
});
|
||||
|
||||
export const matchPlannings = pgTable("match_planning", {
|
||||
export const guilds = pgTable("guilds", {
|
||||
id: varchar("id", { length: 20 }).primaryKey(),
|
||||
timezone: text("timezone").notNull().default("Etc/UTC"),
|
||||
tpEnabled: boolean("tp_enabled").notNull().default(false),
|
||||
tpChannelId: varchar("tp_channel_id", { length: 20 }),
|
||||
tpInterval: smallint("target_interval").notNull(),
|
||||
tpRoles: boolean("tp_roles").notNull(),
|
||||
isAvailableRoleId: varchar("is_available_role_id", { length: 20 }),
|
||||
wantsToBeNotifieRoledId: varchar("wants_to_be_notified_role_id", {
|
||||
length: 20,
|
||||
}),
|
||||
});
|
||||
|
||||
export const guildsRelations = relations(guilds, ({ many }) => ({
|
||||
tpMessages: many(tpMessages),
|
||||
matches: many(matches),
|
||||
}));
|
||||
|
||||
export const tpMessages = pgTable(
|
||||
"tp_messages",
|
||||
{
|
||||
messageId: varchar("message_id", { length: 20 }),
|
||||
day: smallint("day").notNull(),
|
||||
guildId: varchar("guild_id", { length: 20 })
|
||||
.notNull()
|
||||
.references(() => guilds.id, { onDelete: "cascade" }),
|
||||
},
|
||||
(table) => {
|
||||
return {
|
||||
pk: primaryKey({ columns: [table.guildId, table.day] }),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
export const tpMessagesRelations = relations(tpMessages, ({ one }) => ({
|
||||
guild: one(guilds, {
|
||||
fields: [tpMessages.guildId],
|
||||
references: [guilds.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const matches = pgTable("matches", {
|
||||
id: serial("id").primaryKey(),
|
||||
channelId: varchar("channel_id", { length: 20 }).notNull(),
|
||||
matchtype: varchar("match_type", { length: 50 }).notNull(),
|
||||
matchType: varchar("match_type", { length: 50 }).notNull(),
|
||||
createrId: varchar("creater_id", { length: 20 }).notNull(),
|
||||
roleId: varchar("role_id", { length: 20 }).notNull(),
|
||||
opponentName: varchar("opponent_name", { length: 100 }).notNull(),
|
||||
|
@ -51,65 +92,9 @@ export const matchPlannings = pgTable("match_planning", {
|
|||
.references(() => guilds.id, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const matchPlanningsRelations = relations(matchPlannings, ({ one }) => ({
|
||||
export const matchPlanningsRelations = relations(matches, ({ one }) => ({
|
||||
guild: one(guilds, {
|
||||
fields: [matchPlannings.guildId],
|
||||
fields: [matches.guildId],
|
||||
references: [guilds.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const guilds = pgTable("guild", {
|
||||
id: varchar("id", { length: 20 }).primaryKey(),
|
||||
timezone: text("timezone").notNull(),
|
||||
});
|
||||
|
||||
export const guildsRelations = relations(guilds, ({ one, many }) => ({
|
||||
matches: many(matchPlannings),
|
||||
timePlanning: one(timePlannings, {
|
||||
fields: [guilds.id],
|
||||
references: [timePlannings.guildId],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const timePlannings = pgTable("time_planning", {
|
||||
id: serial("id").primaryKey(),
|
||||
guildId: varchar("guild_id", { length: 20 })
|
||||
.notNull()
|
||||
.unique()
|
||||
.references(() => guilds.id, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
channelId: varchar("channel_id", { length: 20 }).notNull(),
|
||||
target_interval: smallint("target_interval").notNull(),
|
||||
roles: boolean("roles").notNull(),
|
||||
isAvailableRoleId: varchar("is_available_role_id", { length: 20 }),
|
||||
wantsToBeNotifieRoledId: varchar("wants_to_be_notified_role_id", {
|
||||
length: 20,
|
||||
}),
|
||||
});
|
||||
|
||||
export const timePlanningsRelations = relations(
|
||||
timePlannings,
|
||||
({ one, many }) => ({
|
||||
guild: one(guilds, {
|
||||
fields: [timePlannings.guildId],
|
||||
references: [guilds.id],
|
||||
}),
|
||||
messages: many(tpMessages),
|
||||
}),
|
||||
);
|
||||
|
||||
export const tpMessages = pgTable("tp_message", {
|
||||
messageId: varchar("message_id", { length: 20 }).primaryKey(),
|
||||
day: smallint("day").notNull(),
|
||||
planId: integer("plan_id")
|
||||
.notNull()
|
||||
.references(() => timePlannings.id, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const tpMessagesRelations = relations(tpMessages, ({ one }) => ({
|
||||
plan: one(timePlannings, {
|
||||
fields: [tpMessages.planId],
|
||||
references: [timePlannings.id],
|
||||
}),
|
||||
}));
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
|
||||
import { Discord } from "arctic";
|
||||
import { PgColumn, PgTableWithColumns } from "drizzle-orm/pg-core";
|
||||
import { Lucia } from "lucia";
|
||||
import db from "~/drizzle";
|
||||
import { sessions, users } from "~/drizzle/schema";
|
||||
|
@ -17,30 +16,16 @@ export const lucia = new Lucia(adapter, {
|
|||
getUserAttributes: (attributes) => attributes,
|
||||
});
|
||||
|
||||
declare module "lucia" {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
interface Register {
|
||||
Lucia: typeof lucia;
|
||||
DatabaseUserAttributes: DatabaseUserAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
type GetColumns<T> =
|
||||
T extends PgTableWithColumns<infer First> ? First["columns"] : never;
|
||||
|
||||
type ExtractDataTypes<T> = {
|
||||
[K in keyof T]: T[K] extends PgColumn<infer DataType, any, any>
|
||||
? DataType["data"]
|
||||
: never;
|
||||
};
|
||||
|
||||
interface DatabaseUserAttributes
|
||||
extends ExtractDataTypes<GetColumns<typeof users>> {
|
||||
warst: string;
|
||||
}
|
||||
|
||||
export const discord = new Discord(
|
||||
import.meta.env.VITE_DISCORD_CLIENT_ID,
|
||||
import.meta.env.VITE_DISCORD_CLIENT_SECRET,
|
||||
import.meta.env.VITE_AUTH_REDIRECT_URL,
|
||||
);
|
||||
|
||||
const unencoded = `${import.meta.env.VITE_DISCORD_CLIENT_ID}:${import.meta.env.VITE_DISCORD_CLIENT_SECRET}`;
|
||||
const encoded = btoa(unencoded);
|
||||
|
||||
export const BasicAuth = {
|
||||
unencoded: `Basic ${unencoded}`,
|
||||
encoded: `Basic ${encoded}`,
|
||||
};
|
||||
|
|
83
src/lib/responseBuilders.ts
Normal file
83
src/lib/responseBuilders.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
import stringify from "json-stable-stringify";
|
||||
import objectHash from "object-hash";
|
||||
|
||||
export const buildMatches = (
|
||||
matches: {
|
||||
id: number;
|
||||
messageId: string;
|
||||
guildId: string;
|
||||
channelId: string;
|
||||
matchType: string;
|
||||
createrId: string;
|
||||
roleId: string;
|
||||
opponentName: string;
|
||||
utc_ts: Date;
|
||||
}[],
|
||||
) =>
|
||||
matches.map(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
({ id, guildId, utc_ts, ...match }) => ({
|
||||
...match,
|
||||
utc_ts: utc_ts.toISOString(),
|
||||
}),
|
||||
);
|
||||
|
||||
export function buildConfig(guildQuery: {
|
||||
id: string;
|
||||
timezone: string;
|
||||
tpEnabled: boolean;
|
||||
tpChannelId: string | null;
|
||||
tpInterval: number;
|
||||
tpRoles: boolean;
|
||||
isAvailableRoleId: string | null;
|
||||
wantsToBeNotifieRoledId: string | null;
|
||||
tpMessages: {
|
||||
messageId: string | null;
|
||||
day: number;
|
||||
guildId: string;
|
||||
}[];
|
||||
matches: {
|
||||
id: number;
|
||||
messageId: string;
|
||||
guildId: string;
|
||||
channelId: string;
|
||||
matchType: string;
|
||||
createrId: string;
|
||||
roleId: string;
|
||||
opponentName: string;
|
||||
utc_ts: Date;
|
||||
}[];
|
||||
}) {
|
||||
const {
|
||||
id,
|
||||
timezone,
|
||||
tpEnabled,
|
||||
tpChannelId,
|
||||
tpInterval,
|
||||
tpRoles,
|
||||
isAvailableRoleId,
|
||||
wantsToBeNotifieRoledId,
|
||||
} = guildQuery;
|
||||
|
||||
const targetMinute = tpInterval & 63;
|
||||
const targetHour = (tpInterval >> 6) & 31;
|
||||
const targetDay = (tpInterval >> 11) & 7;
|
||||
|
||||
const payload = {
|
||||
guildId: id,
|
||||
timezone,
|
||||
features: {
|
||||
timePlanning: {
|
||||
enabled: tpEnabled,
|
||||
channelId: tpChannelId,
|
||||
targetMinute,
|
||||
targetHour,
|
||||
targetDay,
|
||||
roles: { enabled: tpRoles, isAvailableRoleId, wantsToBeNotifieRoledId },
|
||||
},
|
||||
},
|
||||
matches: buildMatches(guildQuery.matches),
|
||||
checksum: objectHash(stringify(guildQuery)),
|
||||
};
|
||||
return payload;
|
||||
}
|
39
src/lib/responses.ts
Normal file
39
src/lib/responses.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import httpStatus from "http-status";
|
||||
import {
|
||||
APIResponse,
|
||||
Methods,
|
||||
MyPaths,
|
||||
ResponseSchemas,
|
||||
StatusCodes,
|
||||
} from "~/types/backend";
|
||||
|
||||
export function ErrorResponse<
|
||||
P extends MyPaths,
|
||||
M extends Methods<P>,
|
||||
C extends StatusCodes<P, M> = StatusCodes<P, M>,
|
||||
>(code: C, error?: string): APIResponse<P, M> {
|
||||
const responseData = {
|
||||
error: error ?? httpStatus[`${httpStatus[code]}_NAME`],
|
||||
};
|
||||
|
||||
console.log(responseData);
|
||||
return new Response(JSON.stringify(responseData), {
|
||||
status: httpStatus[code],
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function Res<
|
||||
P extends MyPaths,
|
||||
M extends Methods<P>,
|
||||
C extends StatusCodes<P, M> = StatusCodes<P, M>,
|
||||
>(code: C, payload: ResponseSchemas<P, M, C>): APIResponse<P, M> {
|
||||
return new Response(payload === null ? null : JSON.stringify(payload), {
|
||||
status: httpStatus[code],
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
37
src/lib/zod.ts
Normal file
37
src/lib/zod.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import moment from "moment-timezone";
|
||||
import { z } from "zod";
|
||||
|
||||
export const zodId = z
|
||||
.string()
|
||||
.refine((value) => /^\d{7,20}$/.test(value), "Invalid ID supplied");
|
||||
|
||||
export const zodTpMessages = z.object({
|
||||
channelId: zodId,
|
||||
messageIds: z.object({
|
||||
"0": zodId.nullable(),
|
||||
"1": zodId.nullable(),
|
||||
"2": zodId.nullable(),
|
||||
"3": zodId.nullable(),
|
||||
"4": zodId.nullable(),
|
||||
"5": zodId.nullable(),
|
||||
"6": zodId.nullable(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const zodMatch = z.object({
|
||||
match: z.object({
|
||||
channelId: zodId,
|
||||
createrId: zodId,
|
||||
messageId: zodId,
|
||||
roleId: zodId,
|
||||
matchType: z.string(),
|
||||
opponentName: z.string(),
|
||||
utc_ts: z.string().datetime(),
|
||||
}),
|
||||
timezone: z
|
||||
.string()
|
||||
.refine(
|
||||
(value) => moment.tz.names().includes(value),
|
||||
"Unknown timezone supplied",
|
||||
),
|
||||
});
|
73
src/routes/api/[guildId]/config.ts
Normal file
73
src/routes/api/[guildId]/config.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { APIEvent } from "@solidjs/start/server/types";
|
||||
import { eq } from "drizzle-orm";
|
||||
import db from "~/drizzle";
|
||||
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 { APIResponse } from "~/types/backend";
|
||||
|
||||
type Path = "/api/{guildId}/config";
|
||||
|
||||
export const GET = async (
|
||||
event: APIEvent,
|
||||
): Promise<APIResponse<Path, "get">> => {
|
||||
switch (event.request.headers.get("authorization")) {
|
||||
case BasicAuth.unencoded:
|
||||
case BasicAuth.encoded:
|
||||
break;
|
||||
|
||||
default:
|
||||
return ErrorResponse("UNAUTHORIZED");
|
||||
}
|
||||
|
||||
try {
|
||||
zodId.parse(event.params.guildId);
|
||||
} catch (e) {
|
||||
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
|
||||
}
|
||||
|
||||
const guildQuery = await db.query.guilds
|
||||
.findFirst({
|
||||
where: eq(guilds.id, event.params.guildId),
|
||||
with: { tpMessages: true, matches: true },
|
||||
})
|
||||
.execute();
|
||||
|
||||
if (!guildQuery) return ErrorResponse("NOT_FOUND");
|
||||
|
||||
return Res("OK", buildConfig(guildQuery));
|
||||
};
|
||||
|
||||
export const DELETE = async (
|
||||
event: APIEvent,
|
||||
): Promise<APIResponse<Path, "delete">> => {
|
||||
switch (event.request.headers.get("authorization")) {
|
||||
case BasicAuth.unencoded:
|
||||
case BasicAuth.encoded:
|
||||
break;
|
||||
|
||||
default:
|
||||
return ErrorResponse("UNAUTHORIZED");
|
||||
}
|
||||
|
||||
try {
|
||||
zodId.parse(event.params.guildId);
|
||||
} catch (e) {
|
||||
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
|
||||
}
|
||||
|
||||
const guildQuery = await db.query.guilds
|
||||
.findFirst({
|
||||
where: eq(guilds.id, event.params.guildId),
|
||||
with: { tpMessages: true, matches: true },
|
||||
})
|
||||
.execute();
|
||||
|
||||
if (!guildQuery) return ErrorResponse("NOT_FOUND");
|
||||
|
||||
await db.delete(guilds).where(eq(guilds.id, event.params.guildId)).execute();
|
||||
|
||||
return Res("NO_CONTENT", null);
|
||||
};
|
93
src/routes/api/[guildId]/matches.ts
Normal file
93
src/routes/api/[guildId]/matches.ts
Normal file
|
@ -0,0 +1,93 @@
|
|||
import { APIEvent } from "@solidjs/start/server/types";
|
||||
import { eq } from "drizzle-orm";
|
||||
import db from "~/drizzle";
|
||||
import { guilds, matches } from "~/drizzle/schema";
|
||||
import { BasicAuth } from "~/lib/auth";
|
||||
import { buildMatches } from "~/lib/responseBuilders";
|
||||
import { ErrorResponse, Res } from "~/lib/responses";
|
||||
import { zodMatch } from "~/lib/zod";
|
||||
import { APIResponse, RequestBody } from "~/types/backend";
|
||||
|
||||
type Path = "/api/{guildId}/matches";
|
||||
|
||||
export const GET = async (
|
||||
event: APIEvent,
|
||||
): Promise<APIResponse<Path, "get">> => {
|
||||
switch (event.request.headers.get("authorization")) {
|
||||
case BasicAuth.unencoded:
|
||||
case BasicAuth.encoded:
|
||||
break;
|
||||
|
||||
default:
|
||||
return ErrorResponse("UNAUTHORIZED");
|
||||
}
|
||||
|
||||
const guild = await db.query.guilds
|
||||
.findFirst({
|
||||
where: eq(guilds.id, event.params.guildId),
|
||||
with: {
|
||||
matches: true,
|
||||
},
|
||||
})
|
||||
.execute();
|
||||
|
||||
console.log(event.params.guildId, guild);
|
||||
|
||||
if (!guild) return ErrorResponse("NOT_FOUND");
|
||||
|
||||
if (guild.matches.length < 1) return Res("NO_CONTENT", null);
|
||||
|
||||
return Res("OK", {
|
||||
matches: buildMatches(guild.matches),
|
||||
timezone: guild.timezone,
|
||||
});
|
||||
};
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
const guild = await db.query.guilds
|
||||
.findFirst({
|
||||
where: eq(guilds.id, event.params.guildId),
|
||||
with: {
|
||||
matches: true,
|
||||
},
|
||||
})
|
||||
.execute();
|
||||
|
||||
console.log(event.params.guildId, guild);
|
||||
|
||||
if (!guild) return ErrorResponse("NOT_FOUND");
|
||||
|
||||
const unparsedBody = await new Response(event.request.body).json();
|
||||
|
||||
let body: RequestBody<Path, "post">;
|
||||
try {
|
||||
body = zodMatch.parse(unparsedBody);
|
||||
} catch (e) {
|
||||
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
|
||||
}
|
||||
|
||||
if (body.timezone !== guild.timezone)
|
||||
return ErrorResponse(
|
||||
"BAD_REQUEST",
|
||||
"Match's timezone is different from guild's timezone",
|
||||
);
|
||||
|
||||
await db.insert(matches).values({
|
||||
...body.match,
|
||||
guildId: guild.id,
|
||||
utc_ts: new Date(body.match.utc_ts),
|
||||
});
|
||||
|
||||
return Res("NO_CONTENT", null);
|
||||
};
|
114
src/routes/api/[guildId]/tp_messages.ts
Normal file
114
src/routes/api/[guildId]/tp_messages.ts
Normal file
|
@ -0,0 +1,114 @@
|
|||
import { APIEvent } from "@solidjs/start/server/types";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import db from "~/drizzle";
|
||||
import { guilds, tpMessages } from "~/drizzle/schema";
|
||||
import { BasicAuth } from "~/lib/auth";
|
||||
import { ErrorResponse, Res } from "~/lib/responses";
|
||||
import { zodTpMessages } from "~/lib/zod";
|
||||
import { APIResponse, RequestBody } from "~/types/backend";
|
||||
|
||||
type Path = "/api/{guildId}/tp_messages";
|
||||
|
||||
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 (
|
||||
event: APIEvent,
|
||||
): Promise<APIResponse<Path, "get">> => {
|
||||
switch (event.request.headers.get("authorization")) {
|
||||
case BasicAuth.unencoded:
|
||||
case BasicAuth.encoded:
|
||||
break;
|
||||
|
||||
default:
|
||||
return ErrorResponse("UNAUTHORIZED");
|
||||
}
|
||||
|
||||
const guild = await db.query.guilds.findFirst({
|
||||
where: eq(guilds.id, event.params.guildId),
|
||||
with: {
|
||||
tpMessages: true,
|
||||
},
|
||||
});
|
||||
|
||||
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;
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
"0": null,
|
||||
"1": null,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": null,
|
||||
"5": null,
|
||||
"6": null,
|
||||
} as Messages,
|
||||
);
|
||||
|
||||
return Res("OK", {
|
||||
channelId: guild.tpChannelId,
|
||||
messageIds: tpMessages,
|
||||
});
|
||||
};
|
||||
|
||||
export const PUT = async (
|
||||
event: APIEvent,
|
||||
): Promise<APIResponse<Path, "put">> => {
|
||||
switch (event.request.headers.get("authorization")) {
|
||||
case BasicAuth.unencoded:
|
||||
case BasicAuth.encoded:
|
||||
break;
|
||||
|
||||
default:
|
||||
return ErrorResponse("UNAUTHORIZED");
|
||||
}
|
||||
|
||||
const guild = await db.query.guilds
|
||||
.findFirst({
|
||||
where: eq(guilds.id, event.params.guildId),
|
||||
with: { tpMessages: true },
|
||||
})
|
||||
.execute();
|
||||
|
||||
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">;
|
||||
try {
|
||||
body = zodTpMessages.parse(unparsedBody);
|
||||
} catch (e) {
|
||||
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
|
||||
}
|
||||
|
||||
if (guild.tpChannelId !== body.channelId)
|
||||
await db
|
||||
.update(guilds)
|
||||
.set({ tpChannelId: body.channelId })
|
||||
.where(eq(guilds.id, guild.id))
|
||||
.execute();
|
||||
|
||||
await Promise.all(
|
||||
DayKeys.map(async (dayStr) => {
|
||||
const day = parseInt(dayStr);
|
||||
await db
|
||||
.update(tpMessages)
|
||||
.set({ messageId: body.messageIds[dayStr] })
|
||||
.where(and(eq(tpMessages.guildId, guild.id), eq(tpMessages.day, day)))
|
||||
.execute();
|
||||
}),
|
||||
);
|
||||
|
||||
return Res("NO_CONTENT", null);
|
||||
};
|
|
@ -2,6 +2,7 @@ import { createId } from "@paralleldrive/cuid2";
|
|||
import { APIEvent } from "@solidjs/start/server/types";
|
||||
import { OAuth2RequestError } from "arctic";
|
||||
import { eq } from "drizzle-orm";
|
||||
import httpStatus from "http-status";
|
||||
import createClient from "openapi-fetch";
|
||||
import { getCookie, setCookie } from "vinxi/http";
|
||||
import db from "~/drizzle";
|
||||
|
@ -20,20 +21,20 @@ export async function GET(event: APIEvent): Promise<Response> {
|
|||
switch (error) {
|
||||
case "access_denied":
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
status: httpStatus.FOUND,
|
||||
headers: { Location: "/" },
|
||||
});
|
||||
default:
|
||||
console.log("Discord oauth error:", error_description);
|
||||
return new Response(decodeURI(error_description ?? ""), {
|
||||
status: 400,
|
||||
status: httpStatus.BAD_REQUEST,
|
||||
});
|
||||
}
|
||||
|
||||
const storedState = getCookie("discord_oauth_state") ?? null;
|
||||
if (!code || !state || !storedState || state !== storedState) {
|
||||
return new Response(null, {
|
||||
status: 400,
|
||||
status: httpStatus.BAD_REQUEST,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -77,7 +78,7 @@ export async function GET(event: APIEvent): Promise<Response> {
|
|||
.execute();
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
status: httpStatus.FOUND,
|
||||
headers: { Location: "/config" },
|
||||
});
|
||||
}
|
||||
|
@ -112,7 +113,7 @@ export async function GET(event: APIEvent): Promise<Response> {
|
|||
sessionCookie.attributes,
|
||||
);
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
status: httpStatus.FOUND,
|
||||
headers: { Location: "/config" },
|
||||
});
|
||||
} catch (e) {
|
||||
|
@ -120,13 +121,13 @@ export async function GET(event: APIEvent): Promise<Response> {
|
|||
if (e instanceof OAuth2RequestError) {
|
||||
// invalid code
|
||||
return new Response(null, {
|
||||
status: 400,
|
||||
status: httpStatus.BAD_REQUEST,
|
||||
});
|
||||
}
|
||||
console.error("Unknown error on callback.");
|
||||
console.error(e);
|
||||
return new Response(null, {
|
||||
status: 500,
|
||||
status: httpStatus.INTERNAL_SERVER_ERROR,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { APIEvent } from "@solidjs/start/server/types";
|
||||
import { generateState } from "arctic";
|
||||
import httpStatus from "http-status";
|
||||
import { setCookie } from "vinxi/http";
|
||||
import { discord } from "~/lib/auth";
|
||||
|
||||
|
@ -18,7 +19,7 @@ export async function GET(event: APIEvent) {
|
|||
});
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
status: httpStatus.FOUND,
|
||||
headers: { Location: url.toString() },
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { APIEvent } from "@solidjs/start/server/types";
|
||||
import httpStatus from "http-status";
|
||||
import { appendHeader } from "vinxi/http";
|
||||
import { lucia } from "~/lib/auth";
|
||||
|
||||
|
@ -13,7 +14,7 @@ export const GET = async (event: APIEvent) => {
|
|||
lucia.createBlankSessionCookie().serialize(),
|
||||
);
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
status: httpStatus.FOUND,
|
||||
headers: { Location: "/" },
|
||||
});
|
||||
};
|
||||
|
|
35
src/routes/api/boot.ts
Normal file
35
src/routes/api/boot.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { APIEvent } from "@solidjs/start/server/types";
|
||||
import { eq } from "drizzle-orm";
|
||||
import db from "~/drizzle";
|
||||
import { guilds } from "~/drizzle/schema";
|
||||
import { BasicAuth } from "~/lib/auth";
|
||||
import { buildConfig } from "~/lib/responseBuilders";
|
||||
import { ErrorResponse, Res } from "~/lib/responses";
|
||||
import { APIResponse } from "~/types/backend";
|
||||
|
||||
type Path = "/api/boot";
|
||||
|
||||
export const GET = async (
|
||||
event: APIEvent,
|
||||
): Promise<APIResponse<Path, "get">> => {
|
||||
switch (event.request.headers.get("authorization")) {
|
||||
case BasicAuth.unencoded:
|
||||
case BasicAuth.encoded:
|
||||
break;
|
||||
|
||||
default:
|
||||
return ErrorResponse("UNAUTHORIZED");
|
||||
}
|
||||
|
||||
const guildQuery = await db.query.guilds
|
||||
.findMany({
|
||||
where: eq(guilds.id, event.params.guildId),
|
||||
with: { tpMessages: true, matches: true },
|
||||
})
|
||||
.execute();
|
||||
|
||||
return Res(
|
||||
"OK",
|
||||
guildQuery.map((e) => buildConfig(e)),
|
||||
);
|
||||
};
|
|
@ -1,61 +0,0 @@
|
|||
import { APIEvent } from "@solidjs/start/server/types";
|
||||
import { eq } from "drizzle-orm";
|
||||
import db from "~/drizzle";
|
||||
import { guilds } from "~/drizzle/schema";
|
||||
|
||||
export const GET = async ({ params }: APIEvent) => {
|
||||
if (params.guildId === "boot") {
|
||||
const guilds = await db.query.guilds
|
||||
.findMany({
|
||||
with: {
|
||||
timePlanning: { with: { messages: true } },
|
||||
matches: true,
|
||||
},
|
||||
})
|
||||
.execute();
|
||||
|
||||
return { guilds };
|
||||
}
|
||||
|
||||
const guild = await db.query.guilds
|
||||
.findFirst({
|
||||
where: eq(guilds.id, params.guildId),
|
||||
with: {
|
||||
timePlanning: { with: { messages: true } },
|
||||
matches: true,
|
||||
},
|
||||
})
|
||||
.execute();
|
||||
|
||||
if (!guild)
|
||||
return new Response(JSON.stringify({ error: "No such guild found." }), {
|
||||
status: 404,
|
||||
});
|
||||
|
||||
return guild;
|
||||
};
|
||||
|
||||
export const DELETE = async ({ params }: APIEvent) => {
|
||||
const guildQuery = await db.query.guilds
|
||||
.findFirst({
|
||||
where: eq(guilds.id, params.guildId),
|
||||
with: {
|
||||
timePlanning: { with: { messages: true } },
|
||||
matches: true,
|
||||
},
|
||||
})
|
||||
.execute();
|
||||
|
||||
if (!guildQuery)
|
||||
return new Response(JSON.stringify({ error: "No such guild found." }), {
|
||||
status: 404,
|
||||
});
|
||||
|
||||
const guild = await db
|
||||
.delete(guilds)
|
||||
.where(eq(guilds.id, params.guildId))
|
||||
.returning()
|
||||
.execute();
|
||||
|
||||
return guild;
|
||||
};
|
|
@ -1,24 +0,0 @@
|
|||
import { APIEvent } from "@solidjs/start/server/types";
|
||||
import { eq } from "drizzle-orm";
|
||||
import db from "~/drizzle";
|
||||
import { guilds } from "~/drizzle/schema";
|
||||
|
||||
export const PUT = async ({ params }: APIEvent) => {
|
||||
const guild = await db.query.guilds
|
||||
.findFirst({
|
||||
where: eq(guilds.id, params.guildId),
|
||||
with: {
|
||||
timePlanning: { with: { messages: true } },
|
||||
matches: true,
|
||||
},
|
||||
})
|
||||
.execute();
|
||||
|
||||
if (!guild)
|
||||
return new Response(JSON.stringify({ error: "No such guild found." }), {
|
||||
status: 404,
|
||||
});
|
||||
|
||||
return "TODO";
|
||||
// return guild.timePlanning;
|
||||
};
|
|
@ -1,24 +0,0 @@
|
|||
import { APIEvent } from "@solidjs/start/server/types";
|
||||
import { eq } from "drizzle-orm";
|
||||
import db from "~/drizzle";
|
||||
import { guilds } from "~/drizzle/schema";
|
||||
|
||||
export const POST = async ({ params }: APIEvent) => {
|
||||
const guild = await db.query.guilds
|
||||
.findFirst({
|
||||
where: eq(guilds.id, params.guildId),
|
||||
with: {
|
||||
timePlanning: { with: { messages: true } },
|
||||
matches: true,
|
||||
},
|
||||
})
|
||||
.execute();
|
||||
|
||||
if (!guild)
|
||||
return new Response(JSON.stringify({ error: "No such guild found." }), {
|
||||
status: 404,
|
||||
});
|
||||
|
||||
return "TODO";
|
||||
// return guild.timePlanning;
|
||||
};
|
|
@ -1,24 +0,0 @@
|
|||
import { APIEvent } from "@solidjs/start/server/types";
|
||||
import { eq } from "drizzle-orm";
|
||||
import db from "~/drizzle";
|
||||
import { guilds } from "~/drizzle/schema";
|
||||
|
||||
export const GET = async ({ params }: APIEvent) => {
|
||||
const guild = await db.query.guilds
|
||||
.findFirst({
|
||||
where: eq(guilds.id, params.guildId),
|
||||
with: {
|
||||
timePlanning: { with: { messages: true } },
|
||||
matches: true,
|
||||
},
|
||||
})
|
||||
.execute();
|
||||
|
||||
if (!guild)
|
||||
return new Response(JSON.stringify({ error: "No such guild found." }), {
|
||||
status: 404,
|
||||
});
|
||||
|
||||
return "TODO";
|
||||
// return guild.timePlanning;
|
||||
};
|
|
@ -1,28 +0,0 @@
|
|||
import { APIEvent } from "@solidjs/start/server/types";
|
||||
import { eq } from "drizzle-orm";
|
||||
import db from "~/drizzle";
|
||||
import { guilds } from "~/drizzle/schema";
|
||||
|
||||
export const GET = async ({ params }: APIEvent) => {
|
||||
const guild = await db.query.guilds
|
||||
.findFirst({
|
||||
where: eq(guilds.id, params.guildId),
|
||||
with: {
|
||||
timePlanning: { with: { messages: true } },
|
||||
matches: true,
|
||||
},
|
||||
})
|
||||
.execute();
|
||||
|
||||
if (!guild)
|
||||
return new Response(JSON.stringify({ error: "No such guild found." }), {
|
||||
status: 404,
|
||||
});
|
||||
|
||||
return "TODO";
|
||||
// return guild.timePlanning;
|
||||
};
|
||||
|
||||
export const PUT = async () => {
|
||||
return "TODO";
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
import db from "~/drizzle";
|
||||
|
||||
export const GET = async () => {
|
||||
const guilds = await db.query.guilds
|
||||
.findMany({
|
||||
with: {
|
||||
timePlanning: { with: { messages: true } },
|
||||
matches: true,
|
||||
},
|
||||
})
|
||||
.execute();
|
||||
|
||||
return { guilds };
|
||||
};
|
15
src/types/authjs.d.ts
vendored
15
src/types/authjs.d.ts
vendored
|
@ -1,15 +0,0 @@
|
|||
import { DefaultSession as DSession } from "@auth/core/types"
|
||||
|
||||
declare module "@auth/core/types" {
|
||||
/**
|
||||
* Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
|
||||
*/
|
||||
interface Session extends DSession {
|
||||
user?: {
|
||||
id: string
|
||||
name?: string | null
|
||||
email?: string | null
|
||||
image?: string | null
|
||||
}
|
||||
}
|
||||
}
|
70
src/types/backend.d.ts
vendored
Normal file
70
src/types/backend.d.ts
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { HttpStatus } from "http-status";
|
||||
import { paths } from "./liljudd";
|
||||
|
||||
export type MyPaths = keyof paths;
|
||||
|
||||
export type Methods<Path extends MyPaths> = keyof paths[Path];
|
||||
|
||||
export type Responses<
|
||||
Path extends MyPaths,
|
||||
Method extends Methods<Path>,
|
||||
> = "responses" extends keyof paths[Path][Method]
|
||||
? paths[Path][Method]["responses"]
|
||||
: never;
|
||||
|
||||
type StatusCodes<P extends MyPaths, M extends Methods<P>> = {
|
||||
[CodeName in keyof HttpStatus]: HttpStatus[CodeName] extends number
|
||||
? HttpStatus[CodeName] extends keyof Responses<P, M>
|
||||
? CodeName
|
||||
: never
|
||||
: never;
|
||||
}[keyof HttpStatus];
|
||||
|
||||
export type ResponseSchemas<
|
||||
Path extends MyPaths,
|
||||
Method extends Methods<Path>,
|
||||
Code extends StatusCodes<Path, Method>,
|
||||
> = Code extends keyof HttpStatus
|
||||
? HttpStatus[Code] extends keyof Responses<Path, Method>
|
||||
? "content" extends keyof Responses<Path, Method>[HttpStatus[Code]]
|
||||
? "application/json" extends keyof Responses<
|
||||
Path,
|
||||
Method
|
||||
>[HttpStatus[Code]]["content"]
|
||||
? Responses<
|
||||
Path,
|
||||
Method
|
||||
>[HttpStatus[Code]]["content"]["application/json"] extends never
|
||||
? null
|
||||
: Responses<
|
||||
Path,
|
||||
Method
|
||||
>[HttpStatus[Code]]["content"]["application/json"]
|
||||
: never
|
||||
: never
|
||||
: never
|
||||
: never;
|
||||
|
||||
export type Parameters<
|
||||
Path extends MyPaths,
|
||||
Method extends Methods<Path>,
|
||||
> = "parameters" extends keyof paths[Path][Method]
|
||||
? "path" extends keyof paths[Path][Method]["parameters"]
|
||||
? paths[Path][Method]["parameters"]["path"]
|
||||
: never
|
||||
: never;
|
||||
|
||||
export type RequestBody<
|
||||
Path extends MyPaths,
|
||||
Method extends Methods<Path>,
|
||||
> = "requestBody" extends keyof paths[Path][Method]
|
||||
? "content" extends keyof paths[Path][Method]["requestBody"]
|
||||
? "application/json" extends keyof paths[Path][Method]["requestBody"]["content"]
|
||||
? paths[Path][Method]["requestBody"]["content"]["application/json"]
|
||||
: never
|
||||
: never
|
||||
: never;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export interface APIResponse<Path extends MyPaths, Method extends Methods<Path>>
|
||||
extends Response {}
|
0
src/global.d.ts → src/types/global.d.ts
vendored
0
src/global.d.ts → src/types/global.d.ts
vendored
325
src/types/liljudd.d.ts
vendored
325
src/types/liljudd.d.ts
vendored
|
@ -5,24 +5,48 @@
|
|||
|
||||
|
||||
export interface paths {
|
||||
"/api/config/{guildId}": {
|
||||
"/api/boot": {
|
||||
/**
|
||||
* Find guild config by ID
|
||||
* @description Returns a single guild config
|
||||
* Retrieve all guild's configs
|
||||
* @description Returns all guild's configs.
|
||||
*/
|
||||
get: operations["getGuildsForBoot"];
|
||||
};
|
||||
"/api/{guildId}/config": {
|
||||
/**
|
||||
* Find a guild's config by ID
|
||||
* @description Returns a single guild's config.
|
||||
*/
|
||||
get: operations["getGuildById"];
|
||||
/**
|
||||
* Deletes a guild config by ID
|
||||
* @description Delete a guild's config
|
||||
* 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/tp_messages/{guildId}": {
|
||||
"/api/{guildId}/tp_messages": {
|
||||
/**
|
||||
* Find guild by ID for it's tp_messages
|
||||
* Find the tp_messages of guild by ID
|
||||
* @description Returns tp_messages for a guild
|
||||
*/
|
||||
get: operations["getTp_messagesOfGuildById"];
|
||||
/**
|
||||
* Put new message IDs for tp_messages of guild by ID
|
||||
* @description Returns tp_messages for a guild
|
||||
*/
|
||||
put: operations["putTp_messagesOfGuildById"];
|
||||
};
|
||||
"/api/{guildId}/matches": {
|
||||
/**
|
||||
* Find all matches of guild by ID
|
||||
* @description Returns tp_messages for a guild
|
||||
*/
|
||||
get: operations["getMatchesOfGuildById"];
|
||||
/**
|
||||
* Save a new created match of guild by ID
|
||||
* @description Returns tp_messages for a guild
|
||||
*/
|
||||
post: operations["postMatchOfGuildById"];
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -32,66 +56,124 @@ export interface components {
|
|||
schemas: {
|
||||
guildConfig: {
|
||||
/**
|
||||
* Format: varchar(19)
|
||||
* Format: varchar(20)
|
||||
* @example 1234567890123456789
|
||||
*/
|
||||
guildID?: string;
|
||||
features?: {
|
||||
time_planning?: {
|
||||
guildId: string;
|
||||
/**
|
||||
* Format: varchar(19)
|
||||
* @example 1234567890123456789
|
||||
* Format: text
|
||||
* @example Europe/Berlin
|
||||
*/
|
||||
channelID?: string;
|
||||
/** @example 0 0 1 * * * 60o 1w */
|
||||
cron?: string;
|
||||
timezone: string;
|
||||
features: {
|
||||
timePlanning: {
|
||||
enabled: boolean;
|
||||
/**
|
||||
* Format: varchar(19)
|
||||
* Format: varchar(20)
|
||||
* @example 1234567890123456789
|
||||
*/
|
||||
isAvailableRoleId?: string;
|
||||
channelId: string | null;
|
||||
/** @example 0 */
|
||||
targetMinute: number;
|
||||
/** @example 1 */
|
||||
targetHour: number;
|
||||
/** @example 1 */
|
||||
targetDay: number;
|
||||
roles: {
|
||||
enabled: boolean;
|
||||
/**
|
||||
* Format: varchar(19)
|
||||
* Format: varchar(20)
|
||||
* @example 1234567890123456789
|
||||
*/
|
||||
wantsToBeNotifieRoledId?: string;
|
||||
isAvailableRoleId: string | null;
|
||||
/**
|
||||
* Format: varchar(20)
|
||||
* @example 1234567890123456789
|
||||
*/
|
||||
wantsToBeNotifieRoledId: string | null;
|
||||
};
|
||||
};
|
||||
matches?: components["schemas"]["match"][];
|
||||
};
|
||||
matches: components["schemas"]["match"][];
|
||||
checksum: string;
|
||||
};
|
||||
match: {
|
||||
/**
|
||||
* Format: varchar(19)
|
||||
* Format: varcharq(20)
|
||||
* @example 1234567890123456789
|
||||
*/
|
||||
channelID?: string;
|
||||
channelId: string;
|
||||
/**
|
||||
* Format: varchar(50)
|
||||
* @example Scrim
|
||||
*/
|
||||
matchType?: string;
|
||||
matchType: string;
|
||||
/**
|
||||
* Format: varchar(19)
|
||||
* Format: varchar(20)
|
||||
* @example 1234567890123456789
|
||||
*/
|
||||
createrId?: string;
|
||||
createrId: string;
|
||||
/**
|
||||
* Format: varchar(19)
|
||||
* Format: varchar(20)
|
||||
* @example 1234567890123456789
|
||||
*/
|
||||
roleId?: string;
|
||||
roleId: string;
|
||||
/**
|
||||
* Format: varchar(100)
|
||||
* @example ?
|
||||
*/
|
||||
opponentName?: string;
|
||||
opponentName: string;
|
||||
/**
|
||||
* Format: varchar(19)
|
||||
* Format: varchar(20)
|
||||
* @example 1234567890123456789
|
||||
*/
|
||||
messsageId?: string;
|
||||
/** @example 0 0 1 5 2 2023 60o */
|
||||
cron?: string;
|
||||
messageId: string;
|
||||
/** @example 2020-01-01T00:00:00Z */
|
||||
utc_ts: string;
|
||||
};
|
||||
tp_messages: {
|
||||
/**
|
||||
* Format: varchar(20)
|
||||
* @example 1234567890123456789
|
||||
*/
|
||||
channelId: string;
|
||||
messageIds: {
|
||||
/**
|
||||
* Format: varchar(20)
|
||||
* @example 1234567890123456789
|
||||
*/
|
||||
0: string | null;
|
||||
/**
|
||||
* Format: varchar(20)
|
||||
* @example 1234567890123456789
|
||||
*/
|
||||
1: string | null;
|
||||
/**
|
||||
* Format: varchar(20)
|
||||
* @example 1234567890123456789
|
||||
*/
|
||||
2: string | null;
|
||||
/**
|
||||
* Format: varchar(20)
|
||||
* @example 1234567890123456789
|
||||
*/
|
||||
3: string | null;
|
||||
/**
|
||||
* Format: varchar(20)
|
||||
* @example 1234567890123456789
|
||||
*/
|
||||
4: string | null;
|
||||
/**
|
||||
* Format: varchar(20)
|
||||
* @example 1234567890123456789
|
||||
*/
|
||||
5: string | null;
|
||||
/**
|
||||
* Format: varchar(20)
|
||||
* @example 1234567890123456789
|
||||
*/
|
||||
6: string | null;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: never;
|
||||
|
@ -108,8 +190,34 @@ export type external = Record<string, never>;
|
|||
export interface operations {
|
||||
|
||||
/**
|
||||
* Find guild config by ID
|
||||
* @description Returns a single guild config
|
||||
* Retrieve all guild's configs
|
||||
* @description Returns all guild's configs.
|
||||
*/
|
||||
getGuildsForBoot: {
|
||||
responses: {
|
||||
/** @description successful operation */
|
||||
200: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["guildConfig"][];
|
||||
};
|
||||
};
|
||||
/** @description Invalid ID supplied */
|
||||
400: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Unauthorized */
|
||||
401: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Guild not found */
|
||||
404: {
|
||||
content: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Find a guild's config by ID
|
||||
* @description Returns a single guild's config.
|
||||
*/
|
||||
getGuildById: {
|
||||
parameters: {
|
||||
|
@ -129,6 +237,10 @@ export interface operations {
|
|||
400: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Unauthorized */
|
||||
401: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Guild not found */
|
||||
404: {
|
||||
content: never;
|
||||
|
@ -136,8 +248,8 @@ export interface operations {
|
|||
};
|
||||
};
|
||||
/**
|
||||
* Deletes a guild config by ID
|
||||
* @description Delete a guild's config
|
||||
* Deletes a guild's config by ID
|
||||
* @description Delete a guild's config when the bot is removed from the guild.
|
||||
*/
|
||||
deleteGuildById: {
|
||||
parameters: {
|
||||
|
@ -155,6 +267,10 @@ export interface operations {
|
|||
400: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Unauthorized */
|
||||
401: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Guild not found */
|
||||
404: {
|
||||
content: never;
|
||||
|
@ -162,7 +278,7 @@ export interface operations {
|
|||
};
|
||||
};
|
||||
/**
|
||||
* Find guild by ID for it's tp_messages
|
||||
* Find the tp_messages of guild by ID
|
||||
* @description Returns tp_messages for a guild
|
||||
*/
|
||||
getTp_messagesOfGuildById: {
|
||||
|
@ -176,7 +292,7 @@ export interface operations {
|
|||
/** @description successful operation */
|
||||
200: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["guildConfig"];
|
||||
"application/json": components["schemas"]["tp_messages"];
|
||||
};
|
||||
};
|
||||
/** @description Time planning not enabled for this guild */
|
||||
|
@ -187,6 +303,137 @@ export interface operations {
|
|||
400: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Unauthorized */
|
||||
401: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Guild not found */
|
||||
404: {
|
||||
content: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Put new message IDs for tp_messages of guild by ID
|
||||
* @description Returns tp_messages for a guild
|
||||
*/
|
||||
putTp_messagesOfGuildById: {
|
||||
parameters: {
|
||||
path: {
|
||||
/** @description ID of guild's tp_messages to return */
|
||||
guildId: string;
|
||||
};
|
||||
};
|
||||
/** @description Put new message IDs for tp_messages in channel */
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["tp_messages"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description successful operation */
|
||||
204: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Invalid ID supplied */
|
||||
400: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Unauthorized */
|
||||
401: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Time planning not enabled for this guild */
|
||||
403: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Guild not found */
|
||||
404: {
|
||||
content: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Find all matches of guild by ID
|
||||
* @description Returns tp_messages for a guild
|
||||
*/
|
||||
getMatchesOfGuildById: {
|
||||
parameters: {
|
||||
path: {
|
||||
/** @description ID of guild's tp_messages to return */
|
||||
guildId: string;
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description successful operation */
|
||||
200: {
|
||||
content: {
|
||||
"application/json": {
|
||||
matches: components["schemas"]["match"][];
|
||||
/**
|
||||
* Format: text
|
||||
* @example Europe/Berlin
|
||||
*/
|
||||
timezone: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
/** @description Time planning not enabled for this guild */
|
||||
204: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Invalid ID supplied */
|
||||
400: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Unauthorized */
|
||||
401: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Guild not found */
|
||||
404: {
|
||||
content: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Save a new created match of guild by ID
|
||||
* @description Returns tp_messages for a guild
|
||||
*/
|
||||
postMatchOfGuildById: {
|
||||
parameters: {
|
||||
path: {
|
||||
/** @description ID of match's guild to set */
|
||||
guildId: string;
|
||||
};
|
||||
};
|
||||
/** @description Save a new created match in channel */
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
match: components["schemas"]["match"];
|
||||
/**
|
||||
* Format: text
|
||||
* @description Has to match guild tz
|
||||
* @example Europe/Berlin
|
||||
*/
|
||||
timezone: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description successful operation */
|
||||
204: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Invalid ID supplied */
|
||||
400: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Unauthorized */
|
||||
401: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Guild not found */
|
||||
404: {
|
||||
content: never;
|
||||
|
|
25
src/types/lucia-auth.d.ts
vendored
Normal file
25
src/types/lucia-auth.d.ts
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { PgColumn, PgTableWithColumns } from "drizzle-orm/pg-core";
|
||||
import { users } from "~/drizzle/schema";
|
||||
import { lucia } from "~/lib/auth";
|
||||
|
||||
declare module "lucia" {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
interface Register {
|
||||
Lucia: typeof lucia;
|
||||
DatabaseUserAttributes: DatabaseUserAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
type GetColumns<T> =
|
||||
T extends PgTableWithColumns<infer First> ? First["columns"] : never;
|
||||
|
||||
type ExtractDataTypes<T> = {
|
||||
[K in keyof T]: T[K] extends PgColumn<infer DataType, never, never>
|
||||
? DataType["data"]
|
||||
: never;
|
||||
};
|
||||
|
||||
interface DatabaseUserAttributes
|
||||
extends ExtractDataTypes<GetColumns<typeof users>> {
|
||||
warst: string;
|
||||
}
|
Loading…
Reference in a new issue