feat: complete last commit

This commit is contained in:
Aron Malcher 2024-01-16 20:24:48 +01:00
parent 55b81fac91
commit c3bf31b3d4
Signed by: aronmal
GPG key ID: 816B7707426FC612
14 changed files with 8344 additions and 58 deletions

View file

@ -13,6 +13,11 @@ 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}}

View file

@ -1,5 +1,5 @@
import "dotenv/config"
import type { Config } from "drizzle-kit"
import "dotenv/config";
import type { Config } from "drizzle-kit";
export default {
schema: "./src/drizzle/schema.ts",
@ -8,4 +8,4 @@ export default {
dbCredentials: {
connectionString: process.env.DATABASE_URL ?? "",
},
} satisfies Config
} satisfies Config;

View file

@ -7,6 +7,8 @@
"start": "vinxi start",
"lint": "eslint --fix \"**/*.{ts,tsx,js,jsx}\"",
"push": "drizzle-kit push:pg",
"discord-openapi-gen": "pnpm openapi-typescript https://raw.githubusercontent.com/discord/discord-api-spec/main/specs/openapi.json -o ./src/types/discord.d.ts",
"liljudd-openapi-gen": "pnpm openapi-typescript ./public/api/specs/liljudd.json -o ./src/types/liljudd.d.ts",
"typecheck": "tsc --noEmit --checkJs false --skipLibCheck"
},
"dependencies": {
@ -25,6 +27,7 @@
"@solidjs/start": "^0.4.9",
"drizzle-orm": "^0.29.2",
"moment-timezone": "^0.5.44",
"openapi-fetch": "^0.8.2",
"postgres": "^3.4.3",
"solid-js": "^1.8.11",
"vinxi": "^0.1.2"
@ -37,10 +40,12 @@
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-solid": "^0.13.1",
"openapi-typescript": "^6.7.3",
"pg": "^8.11.3",
"prettier": "^3.1.1",
"prettier-plugin-organize-imports": "^3.2.4",
"sass": "^1.69.6",
"typescript": "^5.3.3",
"zod": "3.22.4"
},
"engines": {

View file

@ -50,6 +50,9 @@ dependencies:
moment-timezone:
specifier: ^0.5.44
version: 0.5.44
openapi-fetch:
specifier: ^0.8.2
version: 0.8.2
postgres:
specifier: ^3.4.3
version: 3.4.3
@ -82,6 +85,9 @@ devDependencies:
eslint-plugin-solid:
specifier: ^0.13.1
version: 0.13.1(eslint@8.56.0)(typescript@5.3.3)
openapi-typescript:
specifier: ^6.7.3
version: 6.7.3
pg:
specifier: ^8.11.3
version: 8.11.3
@ -94,6 +100,9 @@ devDependencies:
sass:
specifier: ^1.69.6
version: 1.69.7
typescript:
specifier: ^5.3.3
version: 5.3.3
zod:
specifier: 3.22.4
version: 3.22.4
@ -2038,7 +2047,6 @@ packages:
/@fastify/busboy@2.1.0:
resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==}
engines: {node: '>=14'}
dev: false
/@fortawesome/fontawesome-common-types@6.5.1:
resolution: {integrity: sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==, tarball: https://npm.fontawesome.com/@fortawesome/fontawesome-common-types/-/6.5.1/fontawesome-common-types-6.5.1.tgz}
@ -3185,7 +3193,6 @@ packages:
/ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
dev: false
/ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
@ -5910,6 +5917,16 @@ packages:
is-wsl: 2.2.0
dev: false
/openapi-fetch@0.8.2:
resolution: {integrity: sha512-4g+NLK8FmQ51RW6zLcCBOVy/lwYmFJiiT+ckYZxJWxUxH4XFhsNcX2eeqVMfVOi+mDNFja6qDXIZAz2c5J/RVw==}
dependencies:
openapi-typescript-helpers: 0.0.5
dev: false
/openapi-typescript-helpers@0.0.5:
resolution: {integrity: sha512-MRffg93t0hgGZbYTxg60hkRIK2sRuEOHEtCUgMuLgbCC33TMQ68AmxskzUlauzZYD47+ENeGV/ElI7qnWqrAxA==}
dev: false
/openapi-typescript@6.7.3:
resolution: {integrity: sha512-es3mGcDXV6TKPo6n3aohzHm0qxhLyR39MhF6mkD1FwFGjhxnqMqfSIgM0eCpInZvqatve4CxmXcMZw3jnnsaXw==}
hasBin: true
@ -5920,7 +5937,6 @@ packages:
supports-color: 9.4.0
undici: 5.28.2
yargs-parser: 21.1.1
dev: false
/optionator@0.9.3:
resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
@ -6880,7 +6896,6 @@ packages:
/supports-color@9.4.0:
resolution: {integrity: sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==}
engines: {node: '>=12'}
dev: false
/supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
@ -7073,7 +7088,6 @@ packages:
engines: {node: '>=14.0'}
dependencies:
'@fastify/busboy': 2.1.0
dev: false
/unenv@1.9.0:
resolution: {integrity: sha512-QKnFNznRxmbOF1hDgzpqrlIf6NC5sbZ2OJ+5Wl3OX8uM+LUJXbj4TXvLJCtwbPTmbMHCLIz6JLKNinNsMShK9g==}
@ -7676,7 +7690,6 @@ packages:
/yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
dev: false
/yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}

34
public/api/index.html Normal file
View file

@ -0,0 +1,34 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="SwaggerUI" />
<title>SwaggerUI</title>
<link
rel="stylesheet"
href="https://unpkg.com/swagger-ui-dist@4.5.0/swagger-ui.css"
/>
</head>
<body>
<div id="swagger-ui"></div>
<script
src="https://unpkg.com/swagger-ui-dist@4.5.0/swagger-ui-bundle.js"
crossorigin
></script>
<script
src="https://unpkg.com/swagger-ui-dist@4.5.0/swagger-ui-standalone-preset.js"
crossorigin
></script>
<script>
window.onload = () => {
window.ui = SwaggerUIBundle({
url: "/api/specs/liljudd.json",
dom_id: "#swagger-ui",
presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
layout: "StandaloneLayout",
});
};
</script>
</body>
</html>

View file

@ -0,0 +1,238 @@
{
"openapi": "3.0.0",
"info": {
"title": "li'l Judd - OpenAPI 3.0",
"description": "None yet",
"termsOfService": "https://liljudd.ink/terms-of-service/",
"contact": {
"url": "https://liljudd.ink/contact/"
},
"version": "0.0.0"
},
"paths": {
"/api/config/{guildId}": {
"get": {
"tags": [
"Guild configs"
],
"summary": "Find guild config by ID",
"description": "Returns a single guild config",
"operationId": "getGuildById",
"parameters": [
{
"name": "guildId",
"in": "path",
"description": "ID of guild config to return",
"required": true,
"schema": {
"type": "string",
"format": "varchar(19)"
}
}
],
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/guildConfig"
}
}
}
},
"400": {
"description": "Invalid ID supplied"
},
"404": {
"description": "Guild not found"
}
},
"security": [
{
"api_key": []
}
]
},
"delete": {
"tags": [
"Guild configs"
],
"summary": "Deletes a guild config by ID",
"description": "Delete a guild's config",
"operationId": "deleteGuildById",
"parameters": [
{
"name": "guildId",
"in": "path",
"description": "ID of guild config to delete",
"required": true,
"schema": {
"type": "string",
"format": "varchar(19)"
}
}
],
"responses": {
"204": {
"description": "successful operation"
},
"400": {
"description": "Invalid ID supplied"
},
"404": {
"description": "Guild not found"
}
},
"security": [
{
"api_key": []
}
]
}
},
"/api/tp_messages/{guildId}": {
"get": {
"tags": [
"Time planning messages"
],
"summary": "Find guild by ID for it's tp_messages",
"description": "Returns tp_messages for a guild",
"operationId": "getTp_messagesOfGuildById",
"parameters": [
{
"name": "guildId",
"in": "path",
"description": "ID of guild's tp_messages to return",
"required": true,
"schema": {
"type": "string",
"format": "varchar(19)"
}
}
],
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/guildConfig"
}
}
}
},
"204": {
"description": "Time planning not enabled for this guild"
},
"400": {
"description": "Invalid ID supplied"
},
"404": {
"description": "Guild not found"
}
},
"security": [
{
"api_key": []
}
]
}
}
},
"components": {
"schemas": {
"guildConfig": {
"type": "object",
"properties": {
"guildID": {
"type": "string",
"format": "varchar(19)",
"example": "1234567890123456789"
},
"features": {
"type": "object",
"properties": {
"time_planning": {
"type": "object",
"properties": {
"channelID": {
"type": "string",
"format": "varchar(19)",
"example": "1234567890123456789"
},
"cron": {
"type": "string",
"example": "0 0 1 * * * 60o 1w"
},
"isAvailableRoleId": {
"type": "string",
"format": "varchar(19)",
"example": "1234567890123456789"
},
"wantsToBeNotifieRoledId": {
"type": "string",
"format": "varchar(19)",
"example": "1234567890123456789"
}
}
}
}
},
"matches": {
"type": "array",
"items": {
"$ref": "#/components/schemas/match"
}
}
}
},
"match": {
"type": "object",
"properties": {
"channelID": {
"type": "string",
"format": "varchar(19)",
"example": "1234567890123456789"
},
"matchType": {
"type": "string",
"format": "varchar(50)",
"example": "Scrim"
},
"createrId": {
"type": "string",
"format": "varchar(19)",
"example": "1234567890123456789"
},
"roleId": {
"type": "string",
"format": "varchar(19)",
"example": "1234567890123456789"
},
"opponentName": {
"type": "string",
"format": "varchar(100)",
"example": "?"
},
"messsageId": {
"type": "string",
"format": "varchar(19)",
"example": "1234567890123456789"
},
"cron": {
"type": "string",
"example": "0 0 1 5 2 2023 60o"
}
}
}
},
"securitySchemes": {
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "header"
}
}
}
}

View file

@ -1,10 +1,10 @@
import { drizzle } from "drizzle-orm/postgres-js"
import postgres from "postgres"
import * as schema from "./schema"
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "./schema";
const queryClient = postgres(process.env.DATABASE_URL ?? "")
const queryClient = postgres(import.meta.env.VITE_DATABASE_URL ?? "");
const db = drizzle(queryClient, {
schema,
})
});
export default db
export default db;

View file

@ -65,14 +65,14 @@ export const verificationTokens = pgTable(
export const matchPlannings = pgTable("match_planning", {
id: serial("id").primaryKey(),
channelId: varchar("channel_id", { length: 19 }).notNull(),
matchtype: varchar("matchtype", { length: 50 }).notNull(),
createrId: varchar("creater_id", { length: 19 }).notNull(),
roleId: varchar("role_id", { length: 19 }).notNull(),
channelId: varchar("channel_id", { length: 20 }).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(),
messageId: varchar("message_id", { length: 19 }).notNull(),
plannedFor: timestamp("planned_for", { precision: 3 }).notNull(),
guildId: varchar("guild_id", { length: 19 })
messageId: varchar("message_id", { length: 20 }).notNull(),
ts: timestamp("ts").notNull(),
guildId: varchar("guild_id", { length: 20 })
.notNull()
.references(() => guilds.id, { onDelete: "cascade" }),
});
@ -85,7 +85,8 @@ export const matchPlanningsRelations = relations(matchPlannings, ({ one }) => ({
}));
export const guilds = pgTable("guild", {
id: varchar("id", { length: 19 }).primaryKey(),
id: varchar("id", { length: 20 }).primaryKey(),
timezone: text("timezone").notNull(),
});
export const guildsRelations = relations(guilds, ({ one, many }) => ({
@ -98,41 +99,42 @@ export const guildsRelations = relations(guilds, ({ one, many }) => ({
export const timePlannings = pgTable("time_planning", {
id: serial("id").primaryKey(),
guildId: varchar("guild_id", { length: 19 })
guildId: varchar("guild_id", { length: 20 })
.notNull()
.unique()
.references(() => guilds.id, {
onDelete: "cascade",
})
.notNull()
.unique(),
channelId: varchar("channel_id", { length: 19 }).notNull(),
targetWeekday: smallint("target_weekday").notNull(),
targetHour: smallint("target_hour").notNull(),
targetMinute: smallint("target_minute").notNull(),
isAvailableRoleId: varchar("is_available_role_id", { length: 19 }),
}),
channelId: varchar("channel_id", { length: 20 }).notNull(),
target_interval: smallint("target_interval").notNull(),
isAvailableRoleId: varchar("is_available_role_id", { length: 20 }),
wantsToBeNotifieRoledId: varchar("wants_to_be_notified_role_id", {
length: 19,
length: 20,
}),
});
export const timePlanningsRelations = relations(
timePlannings,
({ one, many }) => ({
guild: one(tpMessages),
guild: one(guilds, {
fields: [timePlannings.guildId],
references: [guilds.id],
}),
messages: many(tpMessages),
}),
);
export const tpMessages = pgTable("tp_message", {
messageId: varchar("message_id", { length: 19 }).primaryKey(),
messageId: varchar("message_id", { length: 20 }).primaryKey(),
day: smallint("day").notNull(),
planId: varchar("plan_id", { length: 19 })
planId: integer("plan_id")
.notNull()
.references(() => timePlannings.guildId, { onDelete: "cascade" }),
.references(() => timePlannings.id, { onDelete: "cascade" }),
});
export const tpMessagesRelations = relations(tpMessages, ({ one }) => ({
timePlanning: one(timePlannings, {
fields: [tpMessages.messageId],
references: [timePlannings.channelId],
plan: one(timePlannings, {
fields: [tpMessages.planId],
references: [timePlannings.id],
}),
}));

View file

@ -7,15 +7,23 @@ export const authOptions: SolidAuthConfig = {
providers: [
{
...Discord({
clientId: process.env.DISCORD_CLIENT_ID,
clientSecret: process.env.DISCORD_CLIENT_SECRET,
clientId: import.meta.env.VITE_DISCORD_CLIENT_ID,
clientSecret: import.meta.env.VITE_DISCORD_CLIENT_SECRET,
}),
authorization:
"https://discord.com/api/oauth2/authorize?scope=identify+email+guilds+guilds.members.read",
},
],
adapter: DrizzleAdapter(db),
secret: process.env.AUTH_SECRET,
secret: import.meta.env.VITE_AUTH_SECRET,
callbacks: {
session: ({ session, user }) => {
if (session?.user) {
session.user.id = user.id;
}
return session;
},
},
pages: {
// signIn: "/signin",
// signOut: "/signout",
@ -23,4 +31,7 @@ export const authOptions: SolidAuthConfig = {
// verifyRequest: '/auth/verify-request', // (used for check email message)
// newUser: '/auth/new-user' // New users will be directed here on first sign in (leave the property out if not of interest)
},
redirectProxyUrl: import.meta.env.DEV
? import.meta.env.VITE_AUTH_REDIRECT_PROXY_URL
: undefined,
};

15
src/types/authjs.d.ts vendored Normal file
View file

@ -0,0 +1,15 @@
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
}
}
}

7751
src/types/discord.d.ts vendored Normal file

File diff suppressed because it is too large Load diff

16
src/types/env.d.ts vendored Normal file
View file

@ -0,0 +1,16 @@
/// <reference types="vinxi/client" />
interface ImportMetaEnv {
readonly VITE_DISCORD_CLIENT: string;
readonly VITE_DISCORD_CLIENT_SECRET: string;
readonly VITE_DISCORD_BOT_TOKEN: string;
readonly VITE_AUTH_SECRET: string;
readonly VITE_AUTH_REDIRECT_PROXY_URL: string | undefined;
readonly VITE_DATABASE_URL: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

196
src/types/liljudd.d.ts vendored Normal file
View file

@ -0,0 +1,196 @@
/**
* This file was auto-generated by openapi-typescript.
* Do not make direct changes to the file.
*/
export interface paths {
"/api/config/{guildId}": {
/**
* Find guild config by ID
* @description Returns a single guild config
*/
get: operations["getGuildById"];
/**
* Deletes a guild config by ID
* @description Delete a guild's config
*/
delete: operations["deleteGuildById"];
};
"/api/tp_messages/{guildId}": {
/**
* Find guild by ID for it's tp_messages
* @description Returns tp_messages for a guild
*/
get: operations["getTp_messagesOfGuildById"];
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
guildConfig: {
/**
* Format: varchar(19)
* @example 1234567890123456789
*/
guildID?: string;
features?: {
time_planning?: {
/**
* Format: varchar(19)
* @example 1234567890123456789
*/
channelID?: string;
/** @example 0 0 1 * * * 60o 1w */
cron?: string;
/**
* Format: varchar(19)
* @example 1234567890123456789
*/
isAvailableRoleId?: string;
/**
* Format: varchar(19)
* @example 1234567890123456789
*/
wantsToBeNotifieRoledId?: string;
};
};
matches?: components["schemas"]["match"][];
};
match: {
/**
* Format: varchar(19)
* @example 1234567890123456789
*/
channelID?: string;
/**
* Format: varchar(50)
* @example Scrim
*/
matchType?: string;
/**
* Format: varchar(19)
* @example 1234567890123456789
*/
createrId?: string;
/**
* Format: varchar(19)
* @example 1234567890123456789
*/
roleId?: string;
/**
* Format: varchar(100)
* @example ?
*/
opponentName?: string;
/**
* Format: varchar(19)
* @example 1234567890123456789
*/
messsageId?: string;
/** @example 0 0 1 5 2 2023 60o */
cron?: string;
};
};
responses: never;
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
}
export type $defs = Record<string, never>;
export type external = Record<string, never>;
export interface operations {
/**
* Find guild config by ID
* @description Returns a single guild config
*/
getGuildById: {
parameters: {
path: {
/** @description ID of guild config to return */
guildId: string;
};
};
responses: {
/** @description successful operation */
200: {
content: {
"application/json": components["schemas"]["guildConfig"];
};
};
/** @description Invalid ID supplied */
400: {
content: never;
};
/** @description Guild not found */
404: {
content: never;
};
};
};
/**
* Deletes a guild config by ID
* @description Delete a guild's config
*/
deleteGuildById: {
parameters: {
path: {
/** @description ID of guild config to delete */
guildId: string;
};
};
responses: {
/** @description successful operation */
204: {
content: never;
};
/** @description Invalid ID supplied */
400: {
content: never;
};
/** @description Guild not found */
404: {
content: never;
};
};
};
/**
* Find guild by ID for it's tp_messages
* @description Returns tp_messages for a guild
*/
getTp_messagesOfGuildById: {
parameters: {
path: {
/** @description ID of guild's tp_messages to return */
guildId: string;
};
};
responses: {
/** @description successful operation */
200: {
content: {
"application/json": components["schemas"]["guildConfig"];
};
};
/** @description Time planning not enabled for this guild */
204: {
content: never;
};
/** @description Invalid ID supplied */
400: {
content: never;
};
/** @description Guild not found */
404: {
content: never;
};
};
};
}

View file

@ -1,19 +1,19 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"allowJs": true,
"strict": true,
"noEmit": true,
"types": ["vinxi/client"],
"isolatedModules": true,
"paths": {
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"allowJs": true,
"strict": true,
"noEmit": true,
"types": ["vinxi/client"],
"isolatedModules": true,
"paths": {
"~/*": ["./src/*"]
}
}
}
}