Compare commits
4 commits
712affc83d
...
c3bf31b3d4
Author | SHA1 | Date | |
---|---|---|---|
c3bf31b3d4 | |||
55b81fac91 | |||
a657906f4f | |||
2a1cf8114e |
43 changed files with 10948 additions and 3555 deletions
|
@ -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}}
|
||||
|
|
|
@ -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;
|
||||
|
|
24
package.json
24
package.json
|
@ -7,20 +7,30 @@
|
|||
"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": {
|
||||
"@auth/core": "^0.19.0",
|
||||
"@auth/drizzle-adapter": "^0.3.12",
|
||||
"@auth/solid-start": "0.1.2",
|
||||
"@solidjs/meta": "^0.29.3",
|
||||
"@solidjs/router": "^0.10.5",
|
||||
"@solidjs/start": "^0.4.2",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
||||
"@fortawesome/pro-duotone-svg-icons": "^6.5.1",
|
||||
"@fortawesome/pro-light-svg-icons": "^6.5.1",
|
||||
"@fortawesome/pro-regular-svg-icons": "^6.5.1",
|
||||
"@fortawesome/pro-solid-svg-icons": "^6.5.1",
|
||||
"@fortawesome/pro-thin-svg-icons": "^6.5.1",
|
||||
"@fortawesome/sharp-solid-svg-icons": "^6.5.1",
|
||||
"@solidjs/meta": "^0.29.2",
|
||||
"@solidjs/router": "^0.10.9",
|
||||
"@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.7",
|
||||
"solid-start": "^0.3.10",
|
||||
"vinxi": "0.0.62"
|
||||
"solid-js": "^1.8.11",
|
||||
"vinxi": "^0.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
||||
|
@ -30,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": {
|
||||
|
|
3723
pnpm-lock.yaml
3723
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
34
public/api/index.html
Normal file
34
public/api/index.html
Normal 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>
|
238
public/api/specs/liljudd.json
Normal file
238
public/api/specs/liljudd.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
@ -1,12 +1,10 @@
|
|||
// @refresh reload
|
||||
import "@fortawesome/fontawesome-svg-core/styles.css";
|
||||
import { Meta, MetaProvider, Title } from "@solidjs/meta";
|
||||
import { Router } from "@solidjs/router";
|
||||
import { FileRoutes } from "@solidjs/start";
|
||||
import { Suspense } from "solid-js";
|
||||
import Footer from "./components/Footer";
|
||||
import NavBar from "./components/NavBar";
|
||||
import "./styles/GlobalLayout.css";
|
||||
import "./styles/Layout.scss";
|
||||
import "./styles/global.scss";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
|
@ -18,9 +16,8 @@ export default function App() {
|
|||
content="The Splatoon Discord bot with unique features."
|
||||
/>
|
||||
<Title>li'l Judd - Your competitive Splatoon assistant</Title>
|
||||
<NavBar />
|
||||
|
||||
<Suspense>{props.children}</Suspense>
|
||||
<Footer />
|
||||
</MetaProvider>
|
||||
)}
|
||||
>
|
||||
|
|
151
src/components/FontAwesomeIcon.tsx
Normal file
151
src/components/FontAwesomeIcon.tsx
Normal file
|
@ -0,0 +1,151 @@
|
|||
import {
|
||||
FaSymbol,
|
||||
FlipProp,
|
||||
IconDefinition,
|
||||
IconProp,
|
||||
PullProp,
|
||||
RotateProp,
|
||||
SizeProp,
|
||||
Transform,
|
||||
} from "@fortawesome/fontawesome-svg-core";
|
||||
import { type JSX } from "solid-js";
|
||||
|
||||
export interface FontAwesomeIconProps
|
||||
extends Omit<
|
||||
JSX.SvgSVGAttributes<SVGSVGElement>,
|
||||
"children" | "mask" | "transform"
|
||||
> {
|
||||
icon: IconDefinition;
|
||||
mask?: IconProp;
|
||||
maskId?: string;
|
||||
color?: string;
|
||||
spin?: boolean;
|
||||
spinPulse?: boolean;
|
||||
spinReverse?: boolean;
|
||||
pulse?: boolean;
|
||||
beat?: boolean;
|
||||
fade?: boolean;
|
||||
beatFade?: boolean;
|
||||
bounce?: boolean;
|
||||
shake?: boolean;
|
||||
flash?: boolean;
|
||||
border?: boolean;
|
||||
fixedWidth?: boolean;
|
||||
inverse?: boolean;
|
||||
listItem?: boolean;
|
||||
flip?: FlipProp;
|
||||
size?: SizeProp;
|
||||
pull?: PullProp;
|
||||
rotation?: RotateProp;
|
||||
transform?: string | Transform;
|
||||
symbol?: FaSymbol;
|
||||
style?: JSX.CSSProperties;
|
||||
tabIndex?: number;
|
||||
title?: string;
|
||||
titleId?: string;
|
||||
swapOpacity?: boolean;
|
||||
}
|
||||
|
||||
const idPool = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
function nextUniqueId() {
|
||||
let size = 12;
|
||||
let id = "";
|
||||
|
||||
while (size-- > 0) {
|
||||
id += idPool[(Math.random() * 62) | 0];
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
function Path(props: { d: string | string[] }) {
|
||||
return (
|
||||
<>
|
||||
{typeof props.d === "string" ? (
|
||||
<path fill="currentColor" d={props.d} />
|
||||
) : (
|
||||
<>
|
||||
<path class="fa-secondary" fill="currentColor" d={props.d[0]} />
|
||||
<path class="fa-primary" fill="currentColor" d={props.d[1]} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function FontAwesomeIcon(props: FontAwesomeIconProps) {
|
||||
const titleId = () =>
|
||||
props.title
|
||||
? "svg-inline--fa-title-".concat(props.titleId || nextUniqueId())
|
||||
: undefined;
|
||||
// Get CSS class list from the props object
|
||||
function attributes() {
|
||||
const defaultClasses = {
|
||||
"svg-inline--fa": true,
|
||||
[`fa-${props.icon.iconName}`]: true,
|
||||
[props.class ?? ""]:
|
||||
typeof props.class !== "undefined" && props.class !== null,
|
||||
...props.classList,
|
||||
};
|
||||
|
||||
// map of CSS class names to properties
|
||||
const faClasses = {
|
||||
"fa-beat": props.beat,
|
||||
"fa-fade": props.fade,
|
||||
"fa-beat-fade": props.beatFade,
|
||||
"fa-bounce": props.bounce,
|
||||
"fa-shake": props.shake,
|
||||
"fa-flash": props.flash,
|
||||
"fa-spin": props.spin,
|
||||
"fa-spin-reverse": props.spinReverse,
|
||||
"fa-spin-pulse": props.spinPulse,
|
||||
"fa-pulse": props.pulse,
|
||||
"fa-fw": props.fixedWidth,
|
||||
"fa-inverse": props.inverse,
|
||||
"fa-border": props.border,
|
||||
"fa-li": props.listItem,
|
||||
"fa-flip": typeof props.flip !== "undefined" && props.flip !== null,
|
||||
"fa-flip-horizontal":
|
||||
props.flip === "horizontal" || props.flip === "both",
|
||||
"fa-flip-vertical": props.flip === "vertical" || props.flip === "both",
|
||||
[`fa-${props.size}`]:
|
||||
typeof props.size !== "undefined" && props.size !== null,
|
||||
[`fa-rotate-${props.rotation}`]:
|
||||
typeof props.rotation !== "undefined" && props.size !== null,
|
||||
[`fa-pull-${props.pull}`]:
|
||||
typeof props.pull !== "undefined" && props.pull !== null,
|
||||
"fa-swap-opacity": props.swapOpacity,
|
||||
};
|
||||
|
||||
const attributes = {
|
||||
focusable: !!props.title,
|
||||
"aria-hidden": !props.title,
|
||||
role: "img",
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
"aria-labelledby": titleId(),
|
||||
"data-prefix": props.icon.prefix,
|
||||
"data-icon": props.icon.iconName,
|
||||
"data-fa-transform": props.transform,
|
||||
"data-fa-mask": props.mask,
|
||||
"data-fa-mask-id": props.maskId,
|
||||
"data-fa-symbol": props.symbol,
|
||||
tabIndex: props.tabIndex,
|
||||
classList: { ...defaultClasses, ...faClasses },
|
||||
color: props.color,
|
||||
style: props.style,
|
||||
viewBox: `0 0 ${props.icon.icon[0]} ${props.icon.icon[1]}`,
|
||||
} as const;
|
||||
|
||||
// return the complete class list
|
||||
return attributes;
|
||||
}
|
||||
|
||||
return (
|
||||
<svg {...attributes()}>
|
||||
{/* <Show when={props.title}>
|
||||
<title id={titleId()}>{props.title}</title>
|
||||
</Show> */}
|
||||
<Path d={props.icon.icon[4]} />
|
||||
</svg>
|
||||
);
|
||||
}
|
18
src/components/Layout.tsx
Normal file
18
src/components/Layout.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { JSX, Suspense } from "solid-js";
|
||||
import "../styles/Layout.scss";
|
||||
import Footer from "./Footer";
|
||||
import NavBar from "./NavBar";
|
||||
|
||||
function Layout(props: { children: JSX.Element; site: string }) {
|
||||
return (
|
||||
<>
|
||||
<NavBar />
|
||||
<div class={props.site}>
|
||||
<Suspense>{props.children}</Suspense>
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Layout;
|
|
@ -1,35 +1,53 @@
|
|||
import { faCirclePlus } from "@fortawesome/pro-regular-svg-icons";
|
||||
import { JSX, Show, Suspense } from "solid-js";
|
||||
import "../styles/components/NavBar.scss";
|
||||
import { FontAwesomeIcon } from "./FontAwesomeIcon";
|
||||
import NavUser from "./NavUser";
|
||||
|
||||
export function Li(props: {
|
||||
href: string;
|
||||
action?: () => void;
|
||||
name?: string;
|
||||
children?: JSX.Element;
|
||||
}) {
|
||||
return (
|
||||
<li class="navElem flex-row thick">
|
||||
<a
|
||||
class="flex-row"
|
||||
href={props.href}
|
||||
onClick={() => props.action && props.action()}
|
||||
>
|
||||
{props.children ?? <></>}
|
||||
<Show when={props.name}>
|
||||
<span>{props.name}</span>
|
||||
</Show>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function NavBar() {
|
||||
return (
|
||||
<nav>
|
||||
<ul>
|
||||
<li class="navElem">
|
||||
<a class="textBx" href="/">
|
||||
<img id="logo" src="/assets/logox256.png" alt="The Bots Logo" />
|
||||
li'l Judd
|
||||
</a>
|
||||
</li>
|
||||
<li class="navElem">
|
||||
<a href="/features">Features</a>
|
||||
</li>
|
||||
<li class="navElem">
|
||||
<a href="/how-do-i">How do I...?</a>
|
||||
</li>
|
||||
<li class="navElem">
|
||||
<a href="/stack">The Stack</a>
|
||||
</li>
|
||||
<li class="navElem">
|
||||
<a href="/about">About</a>
|
||||
</li>
|
||||
<li class="navElem">
|
||||
<a
|
||||
<nav class="flex-row responsive">
|
||||
<ul class="flex-row responsive thick">
|
||||
<Li href="/" name="li'l Judd">
|
||||
<img src="/assets/logox256.png" alt="The Bots Logo" />
|
||||
</Li>
|
||||
<Li href="/features" name="Features" />
|
||||
<Li href="/how-do-i" name="How do I...?" />
|
||||
<Li href="/stack" name="The Stack" />
|
||||
<Li href="/about" name="About" />
|
||||
</ul>
|
||||
<ul class="flex-row responsive thick">
|
||||
<Li
|
||||
href="https://discord.com/api/oauth2/authorize?client_id=1024410658973941862&permissions=18977581952080&scope=bot"
|
||||
target="_blank"
|
||||
name="Invite to your server"
|
||||
>
|
||||
Invite to your server
|
||||
</a>
|
||||
</li>
|
||||
<FontAwesomeIcon class="lower" icon={faCirclePlus} size="xl" />
|
||||
</Li>
|
||||
<Suspense>
|
||||
<NavUser />
|
||||
</Suspense>
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
|
|
87
src/components/NavUser.tsx
Normal file
87
src/components/NavUser.tsx
Normal file
|
@ -0,0 +1,87 @@
|
|||
import { getSession } from "@auth/solid-start";
|
||||
import { signIn, signOut } from "@auth/solid-start/client";
|
||||
import {
|
||||
faArrowRightFromBracket,
|
||||
faArrowRightToBracket,
|
||||
faGear,
|
||||
} from "@fortawesome/pro-regular-svg-icons";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { Show, createResource } from "solid-js";
|
||||
import { getRequestEvent } from "solid-js/web";
|
||||
import db from "~/drizzle";
|
||||
import { users } from "~/drizzle/schema";
|
||||
import { authOptions } from "~/server/auth";
|
||||
import { FontAwesomeIcon } from "./FontAwesomeIcon";
|
||||
import { Li } from "./NavBar";
|
||||
|
||||
const initialUser = {
|
||||
id: "",
|
||||
name: null as string | null,
|
||||
email: "",
|
||||
emailVerified: null as Date | null,
|
||||
image: null as string | null,
|
||||
};
|
||||
|
||||
async function getUser() {
|
||||
"use server";
|
||||
|
||||
const event = getRequestEvent();
|
||||
if (!event)
|
||||
return { success: false, message: "No request event!", ...initialUser };
|
||||
|
||||
const session = await getSession(event.request, authOptions);
|
||||
if (!session?.user?.id)
|
||||
return { success: false, message: "No user with id!", ...initialUser };
|
||||
|
||||
const user = (
|
||||
await db
|
||||
.selectDistinct()
|
||||
.from(users)
|
||||
.where(eq(users.id, session.user?.id))
|
||||
.limit(1)
|
||||
.execute()
|
||||
)[0];
|
||||
|
||||
return { success: true, message: "", ...user };
|
||||
}
|
||||
|
||||
function NavUser() {
|
||||
const [user] = createResource(() =>
|
||||
getUser().then((e) => {
|
||||
if (!e.success) console.log(1, e.message);
|
||||
console.log(2, e);
|
||||
return e;
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={user()?.id}
|
||||
fallback={
|
||||
<Li
|
||||
href="#"
|
||||
name="Login"
|
||||
action={() => signIn("discord", { callbackUrl: "/config" })}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
class="secondary"
|
||||
icon={faArrowRightToBracket}
|
||||
size="xl"
|
||||
/>
|
||||
</Li>
|
||||
}
|
||||
>
|
||||
<Li href="/config">
|
||||
<div class="swap lower">
|
||||
<img class="primary" src={user()?.image ?? ""} alt="User pfp" />
|
||||
<FontAwesomeIcon class="secondary" icon={faGear} size="xl" />
|
||||
</div>
|
||||
</Li>
|
||||
<Li href="#" action={() => signOut({ callbackUrl: "/" })}>
|
||||
<FontAwesomeIcon icon={faArrowRightFromBracket} size="xl" />
|
||||
</Li>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
|
||||
export default NavUser;
|
|
@ -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;
|
||||
|
|
|
@ -12,23 +12,22 @@ import {
|
|||
} from "drizzle-orm/pg-core";
|
||||
|
||||
export const users = pgTable("user", {
|
||||
id: text("id").primaryKey(),
|
||||
id: text("id").notNull().primaryKey(),
|
||||
name: text("name"),
|
||||
email: text("email").notNull(),
|
||||
emailVerified: timestamp("email_verified", { mode: "date" }),
|
||||
emailVerified: timestamp("emailVerified", { mode: "date" }),
|
||||
image: text("image"),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
});
|
||||
|
||||
export const accounts = pgTable(
|
||||
"account",
|
||||
{
|
||||
userId: text("user_id")
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
type: text("type").$type<AdapterAccount["type"]>().notNull(),
|
||||
provider: text("provider").notNull(),
|
||||
providerAccountId: text("provider_account_id").notNull(),
|
||||
providerAccountId: text("providerAccountId").notNull(),
|
||||
refresh_token: text("refresh_token"),
|
||||
access_token: text("access_token"),
|
||||
expires_at: integer("expires_at"),
|
||||
|
@ -45,15 +44,15 @@ export const accounts = pgTable(
|
|||
);
|
||||
|
||||
export const sessions = pgTable("session", {
|
||||
sessionToken: text("session_token").primaryKey(),
|
||||
userId: text("user_id")
|
||||
sessionToken: text("sessionToken").notNull().primaryKey(),
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
});
|
||||
|
||||
export const verificationTokens = pgTable(
|
||||
"verification_token",
|
||||
"verificationToken",
|
||||
{
|
||||
identifier: text("identifier").notNull(),
|
||||
token: text("token").notNull(),
|
||||
|
@ -66,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" }),
|
||||
});
|
||||
|
@ -86,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 }) => ({
|
||||
|
@ -99,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],
|
||||
}),
|
||||
}));
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { createHandler, StartServer } from "@solidjs/start/server";
|
||||
import { createHandler } from "@solidjs/start/entry";
|
||||
import { StartServer } from "@solidjs/start/server";
|
||||
|
||||
export default createHandler(() => (
|
||||
<StartServer
|
||||
|
@ -7,7 +8,7 @@ export default createHandler(() => (
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/assets/favicon.ico" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
{assets}
|
||||
</head>
|
||||
<body id="app">
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { Title } from "@solidjs/meta";
|
||||
import { HttpStatusCode } from "@solidjs/start";
|
||||
import Layout from "~/components/Layout";
|
||||
import "../styles/pages/index.scss";
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<>
|
||||
<Layout site="index">
|
||||
<Title>Not Found</Title>
|
||||
<HttpStatusCode code={404} />
|
||||
<section class="index">
|
||||
|
@ -17,6 +18,6 @@ export default function NotFound() {
|
|||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import Layout from "~/components/Layout";
|
||||
import "../styles/pages/about.scss";
|
||||
|
||||
function about() {
|
||||
return (
|
||||
<>
|
||||
<div class="aboutdiv">
|
||||
<Layout site="about">
|
||||
<h1>About</h1>
|
||||
<section>
|
||||
<h2>Why does this bot exist?</h2>
|
||||
|
@ -12,19 +12,19 @@ function about() {
|
|||
<a href="/assets/screenshots/oldplanningmsg.png" target="_blank">
|
||||
these planning messages
|
||||
</a>{" "}
|
||||
and I thought that this should be automated. Some time later the
|
||||
first version of li'l Judd was born. Today the bot has more features
|
||||
and keeps getting more of them! It is designed to actually improve
|
||||
the Splatoon experience and not be the 10000th moderation and
|
||||
general utility bot with the same features as all bots.
|
||||
and I thought that this should be automated. Some time later the first
|
||||
version of li'l Judd was born. Today the bot has more features and
|
||||
keeps getting more of them! It is designed to actually improve the
|
||||
Splatoon experience and not be the 10000th moderation and general
|
||||
utility bot with the same features as all bots.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Who is behind this?</h2>
|
||||
<p>
|
||||
The bot is currently being developed by{" "}
|
||||
<a href="/contact">moonleay</a> (hey that's me!) with
|
||||
occasional help from his friends!
|
||||
<a href="/contact">moonleay</a> (hey that's me!) with occasional
|
||||
help from his friends!
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
|
@ -36,17 +36,17 @@ function about() {
|
|||
<a href="https://git.moonleay.net/DiscordBots/lilJudd">
|
||||
read the code
|
||||
</a>
|
||||
and if you still don't trust me, you can always host the bot
|
||||
yourself! A guide on how to do that can be found in the README of
|
||||
the git project.
|
||||
and if you still don't trust me, you can always host the bot yourself!
|
||||
A guide on how to do that can be found in the README of the git
|
||||
project.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Where is my data stored?</h2>
|
||||
<p>
|
||||
Your data is stored on a VPS from Contabo in Germany. The bot used
|
||||
to be hosted on a server in my basement, but I moved it to a VPS,
|
||||
because my internet connection was not stable enough.
|
||||
Your data is stored on a VPS from Contabo in Germany. The bot used to
|
||||
be hosted on a server in my basement, but I moved it to a VPS, because
|
||||
my internet connection was not stable enough.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
|
@ -66,13 +66,12 @@ function about() {
|
|||
<section>
|
||||
<h2>Hey, there is this really cool idea I have! Can you add it?</h2>
|
||||
<p>
|
||||
Just message me! I can't promise anything, but I am always open to
|
||||
new ideas and improvements! You can find ways to contact me{" "}
|
||||
Just message me! I can't promise anything, but I am always open to new
|
||||
ideas and improvements! You can find ways to contact me{" "}
|
||||
<a href="/contact">here</a>.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import Layout from "~/components/Layout";
|
||||
import "../styles/pages/acknowledgements.scss";
|
||||
|
||||
function acknowledgements() {
|
||||
return (
|
||||
<>
|
||||
<div class="acknowledgements">
|
||||
<Layout site="acknowledgements">
|
||||
<h1>Acknowledgements</h1>
|
||||
<section>
|
||||
<table>
|
||||
|
@ -142,10 +142,7 @@ function acknowledgements() {
|
|||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
href="https://github.com/JetBrains/Exposed"
|
||||
target="_blank"
|
||||
>
|
||||
<a href="https://github.com/JetBrains/Exposed" target="_blank">
|
||||
repo
|
||||
</a>
|
||||
</td>
|
||||
|
@ -192,8 +189,7 @@ function acknowledgements() {
|
|||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
import "../styles/pages/config.scss";
|
||||
|
||||
function config() {
|
||||
return (
|
||||
<div>
|
||||
<h3 class={"centered"}>Configure li'l Judd in</h3>
|
||||
<div class={"config"}>
|
||||
<div>
|
||||
<div class={"horizontal centered"}>
|
||||
<img class={"guildpfp"} src="https://cdn.discordapp.com/icons/1040502664506646548/bb5a51c4659cf47bdd942bb11e974da7.webp?size=240" alt="Server pfp" />
|
||||
<h1>li'l Judds home base</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article>
|
||||
<h2>Timezone</h2>
|
||||
<p>Set the timezone for your server.</p>
|
||||
<label>
|
||||
<select>
|
||||
<option value="-1">UTC-1:00</option>
|
||||
<option value="0">UTC+0:00</option>
|
||||
<option value="1">UTC+1:00</option>
|
||||
</select>
|
||||
</label>
|
||||
</article>
|
||||
<section>
|
||||
<div class={"centered"}>
|
||||
<h2>Features</h2>
|
||||
<p>Configure the features of the bot</p>
|
||||
</div>
|
||||
<article>
|
||||
<div class={"horizontal"}>
|
||||
<h3>Time Planning</h3>
|
||||
<input type="checkbox" id="time planning" />
|
||||
</div>
|
||||
<label class={"horizontal"}>
|
||||
<p class={"marg_right_5px"}>Target channel:</p>
|
||||
<select>
|
||||
<option value="id">Channel 1</option>
|
||||
<option value="id">Channel 2</option>
|
||||
<option value="id">Channel 3</option>
|
||||
<option value="id">Channel 4</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class={"horizontal"}>
|
||||
<h4>Enable pingable Roles</h4>
|
||||
<input type="checkbox" id="pingableroles" />
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
<button>Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default config;
|
271
src/routes/config/[guildId].tsx
Normal file
271
src/routes/config/[guildId].tsx
Normal file
|
@ -0,0 +1,271 @@
|
|||
import { getSession } from "@auth/solid-start";
|
||||
import { faToggleOff, faToggleOn } from "@fortawesome/pro-regular-svg-icons";
|
||||
import { useNavigate, useParams } from "@solidjs/router";
|
||||
import { eq } from "drizzle-orm";
|
||||
import moment from "moment-timezone";
|
||||
import createClient from "openapi-fetch";
|
||||
import { Index, createEffect, createResource, createSignal } from "solid-js";
|
||||
import { createStore } from "solid-js/store";
|
||||
import { getRequestEvent } from "solid-js/web";
|
||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon";
|
||||
import Layout from "~/components/Layout";
|
||||
import db from "~/drizzle";
|
||||
import { accounts } from "~/drizzle/schema";
|
||||
import { authOptions } from "~/server/auth";
|
||||
import { paths } from "~/types/discord";
|
||||
import "../../styles/pages/config.scss";
|
||||
|
||||
const guessTZ = () => Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
const initialValue = (params: ReturnType<typeof useParams>) => ({
|
||||
success: null as boolean | null,
|
||||
guild: {
|
||||
id: params.guildId,
|
||||
name: undefined as string | undefined,
|
||||
icon: undefined as string | null | undefined,
|
||||
},
|
||||
tzNames: [guessTZ()],
|
||||
});
|
||||
|
||||
const getPayload = async (
|
||||
id: string,
|
||||
): Promise<
|
||||
| { success: false; message: string }
|
||||
| (ReturnType<typeof initialValue> & { success: true })
|
||||
> => {
|
||||
"use server";
|
||||
const event = getRequestEvent();
|
||||
if (!event) return { success: false, message: "No request event!" };
|
||||
|
||||
const session = await getSession(event.request, authOptions);
|
||||
if (!session?.user?.id)
|
||||
return { success: false, message: "No user with id!" };
|
||||
|
||||
const { DISCORD_ACCESS_TOKEN } = (
|
||||
await db
|
||||
.selectDistinct({ DISCORD_ACCESS_TOKEN: accounts.access_token })
|
||||
.from(accounts)
|
||||
.where(eq(accounts.userId, session.user?.id))
|
||||
.limit(1)
|
||||
.execute()
|
||||
)[0];
|
||||
if (!DISCORD_ACCESS_TOKEN)
|
||||
return { success: false, message: "No discord access token!" };
|
||||
|
||||
// const guilds = await fetch("https://discord.com/api/users/@me/guilds", {
|
||||
// headers: { Authorization: `Bearer ${DISCORD_ACCESS_TOKEN}` },
|
||||
// }).then((res) => res.json());
|
||||
const { GET } = createClient<paths>({
|
||||
baseUrl: "https://discord.com/api/v10",
|
||||
});
|
||||
const { data: guilds, error } = await GET("/users/@me/guilds", {
|
||||
headers: { Authorization: `Bearer ${DISCORD_ACCESS_TOKEN}` },
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.log(error);
|
||||
return { success: false, message: "Error on discord api request!" };
|
||||
}
|
||||
|
||||
const guild = guilds?.find((e) => e.id === id);
|
||||
|
||||
if (!guild)
|
||||
return {
|
||||
success: false,
|
||||
message: "User is in no such guild with requested id!",
|
||||
};
|
||||
if (!(parseInt(guild.permissions) & (1 << 5)))
|
||||
return {
|
||||
success: false,
|
||||
message:
|
||||
"User is no MANAGE_GUILD permissions on this guild with requested id!",
|
||||
};
|
||||
|
||||
return {
|
||||
success: true,
|
||||
guild: {
|
||||
id: guild.id,
|
||||
name: guild.name,
|
||||
icon: guild.icon,
|
||||
},
|
||||
// guild: guilds
|
||||
// .filter((e: any) => e.permissions & (1 << 5))
|
||||
// .map((e: any) => e.name),
|
||||
tzNames: moment.tz.names(),
|
||||
};
|
||||
};
|
||||
|
||||
function config() {
|
||||
const params = useParams();
|
||||
const navigator = useNavigate();
|
||||
let timezoneRef: HTMLInputElement;
|
||||
let timePlanningRef: HTMLInputElement;
|
||||
let pingableRolesRef: HTMLInputElement;
|
||||
|
||||
const [timezone, setTimezone] = createSignal(guessTZ());
|
||||
const [payload] = createResource(params.guildId, async (id) => {
|
||||
const payload = await getPayload(id);
|
||||
|
||||
if (!payload.success) {
|
||||
console.log(payload.message, "No success");
|
||||
navigator("/config", { replace: false });
|
||||
return initialValue(params);
|
||||
}
|
||||
return payload;
|
||||
});
|
||||
const guild = () => payload()?.guild ?? initialValue(params).guild;
|
||||
const tzNames = () => payload()?.tzNames ?? [];
|
||||
const [config, setConfig] = createStore({
|
||||
features: {
|
||||
timePlanning: {
|
||||
enabled: false,
|
||||
pingableRoles: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
createEffect(() => console.log(payload()));
|
||||
createEffect(() => console.log("timezone", timezone()));
|
||||
createEffect(() =>
|
||||
console.log("timePlanning.enabled", config.features.timePlanning.enabled),
|
||||
);
|
||||
createEffect(() =>
|
||||
console.log(
|
||||
"timePlanning.pingableRoles",
|
||||
config.features.timePlanning.pingableRoles,
|
||||
),
|
||||
);
|
||||
|
||||
createEffect(() => (timezoneRef.value = timezone()));
|
||||
createEffect(
|
||||
() => (timePlanningRef.checked = config.features.timePlanning.enabled),
|
||||
);
|
||||
createEffect(
|
||||
() =>
|
||||
(pingableRolesRef.checked = config.features.timePlanning.pingableRoles),
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout site="config">
|
||||
<h3 class="text-center">Configure li'l Judd in</h3>
|
||||
<div>
|
||||
<div>
|
||||
<div class="flex-row centered">
|
||||
<img
|
||||
class="guildpfp"
|
||||
src={
|
||||
guild()?.icon
|
||||
? `https://cdn.discordapp.com/icons/${guild()?.id}/${guild()
|
||||
?.icon}.webp?size=240`
|
||||
: "https://cdn.discordapp.com/icons/1040502664506646548/bb5a51c4659cf47bdd942bb11e974da7.webp?size=240"
|
||||
}
|
||||
alt="Server pfp"
|
||||
/>
|
||||
<h1>{guild()?.name ?? "li'l Judds home base"}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h2>Guild</h2>
|
||||
<p>General settings for this guild.</p>
|
||||
<div class="flex-row">
|
||||
<label for="timezone">Timezone for your server:</label>
|
||||
<input
|
||||
type="text"
|
||||
list="timezones"
|
||||
id="timezone"
|
||||
ref={timezoneRef!}
|
||||
// disabled={!tzNames().find((e) => e === timezone())}
|
||||
onInput={(e) => setTimezone(e.target.value)}
|
||||
/>
|
||||
|
||||
<datalist id="timezones">
|
||||
<Index each={tzNames()}>
|
||||
{(zone) => <option value={zone()} />}
|
||||
</Index>
|
||||
</datalist>
|
||||
|
||||
<button
|
||||
disabled={guessTZ() === timezone()}
|
||||
title={"Detected: " + guessTZ()}
|
||||
onClick={() => setTimezone(guessTZ())}
|
||||
>
|
||||
Auto-detect
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Features</h2>
|
||||
<p>Configure the features of the bot</p>
|
||||
<label for="timePlanning" class="flex-row">
|
||||
<p>Time Planning </p>
|
||||
<FontAwesomeIcon
|
||||
icon={
|
||||
config.features.timePlanning.enabled ? faToggleOn : faToggleOff
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
hidden
|
||||
type="checkbox"
|
||||
id="timePlanning"
|
||||
ref={timePlanningRef!}
|
||||
onInput={(e) =>
|
||||
setConfig("features", "timePlanning", "enabled", e.target.checked)
|
||||
}
|
||||
/>
|
||||
<div
|
||||
class="sub"
|
||||
classList={{ disabled: !config.features.timePlanning.enabled }}
|
||||
>
|
||||
<div class="flex-row">
|
||||
<label>Target channel:</label>
|
||||
<select value={timezone()}>
|
||||
<optgroup label="--Select a Channel--">
|
||||
<Index each={tzNames()}>
|
||||
{(channel) => <option>{channel()}</option>}
|
||||
</Index>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-row">
|
||||
<label for="pingableRoles" class="flex-row">
|
||||
<p>Enable pingable Roles:</p>
|
||||
<FontAwesomeIcon
|
||||
icon={
|
||||
config.features.timePlanning.pingableRoles
|
||||
? faToggleOn
|
||||
: faToggleOff
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
hidden
|
||||
type="checkbox"
|
||||
id="pingableRoles"
|
||||
ref={pingableRolesRef!}
|
||||
onInput={(e) =>
|
||||
setConfig(
|
||||
"features",
|
||||
"timePlanning",
|
||||
"pingableRoles",
|
||||
e.target.checked,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<button>Apply</button>
|
||||
</section>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default config;
|
124
src/routes/config/index.tsx
Normal file
124
src/routes/config/index.tsx
Normal file
|
@ -0,0 +1,124 @@
|
|||
import { getSession } from "@auth/solid-start";
|
||||
import {
|
||||
faBadgeCheck,
|
||||
faCircleExclamation,
|
||||
faPlus,
|
||||
} from "@fortawesome/pro-regular-svg-icons";
|
||||
import { useNavigate } from "@solidjs/router";
|
||||
import { eq } from "drizzle-orm";
|
||||
import createClient from "openapi-fetch";
|
||||
import { For, createResource } from "solid-js";
|
||||
import { getRequestEvent } from "solid-js/web";
|
||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon";
|
||||
import Layout from "~/components/Layout";
|
||||
import db from "~/drizzle";
|
||||
import { accounts } from "~/drizzle/schema";
|
||||
import { authOptions } from "~/server/auth";
|
||||
import { paths } from "~/types/discord";
|
||||
import "../../styles/pages/config.scss";
|
||||
|
||||
const initialValue = () => ({
|
||||
success: null as boolean | null,
|
||||
guilds: [] as {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string | null | undefined;
|
||||
}[],
|
||||
});
|
||||
|
||||
const getPayload = async (): Promise<
|
||||
| { success: false; message: string }
|
||||
| (ReturnType<typeof initialValue> & { success: true })
|
||||
> => {
|
||||
("use server");
|
||||
const event = getRequestEvent();
|
||||
if (!event) return { success: false, message: "No request event!" };
|
||||
|
||||
const session = await getSession(event.request, authOptions);
|
||||
if (!session?.user?.id)
|
||||
return { success: false, message: "No user with id!" };
|
||||
|
||||
const { DISCORD_ACCESS_TOKEN } = (
|
||||
await db
|
||||
.selectDistinct({ DISCORD_ACCESS_TOKEN: accounts.access_token })
|
||||
.from(accounts)
|
||||
.where(eq(accounts.userId, session.user?.id))
|
||||
.limit(1)
|
||||
.execute()
|
||||
)[0];
|
||||
if (!DISCORD_ACCESS_TOKEN)
|
||||
return { success: false, message: "No discord access token!" };
|
||||
|
||||
const { GET } = createClient<paths>({
|
||||
baseUrl: "https://discord.com/api/v10",
|
||||
});
|
||||
const { data: guilds, error } = await GET("/users/@me/guilds", {
|
||||
headers: { Authorization: `Bearer ${DISCORD_ACCESS_TOKEN}` },
|
||||
});
|
||||
|
||||
console.log("guilds", guilds);
|
||||
|
||||
if (error) {
|
||||
console.log(error);
|
||||
return { success: false, message: "Error on discord api request!" };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
guilds:
|
||||
guilds
|
||||
?.filter((e) => parseInt(e.permissions) & (1 << 5))
|
||||
.map(({ id, name, icon }) => ({ id, name, icon })) ?? [],
|
||||
};
|
||||
};
|
||||
|
||||
function index() {
|
||||
const navigator = useNavigate();
|
||||
|
||||
const [payload] = createResource(async () => {
|
||||
const payload = await getPayload();
|
||||
|
||||
if (!payload.success) {
|
||||
console.log(payload.message, "No success");
|
||||
navigator("/", { replace: false });
|
||||
return initialValue();
|
||||
}
|
||||
console.log("success");
|
||||
return payload;
|
||||
});
|
||||
|
||||
const icons = [faPlus, faCircleExclamation, faBadgeCheck];
|
||||
const colors = [undefined, "orange", "green"];
|
||||
|
||||
return (
|
||||
<Layout site="config">
|
||||
<h3 class="text-center">Configure li'l Judd in</h3>
|
||||
<div>
|
||||
<For each={payload()?.guilds ?? []}>
|
||||
{(guild, i) => (
|
||||
<a href={`/config/${guild.id}`} class="flex-row centered">
|
||||
<img
|
||||
class="guildpfp"
|
||||
src={
|
||||
guild.icon
|
||||
? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.webp?size=240`
|
||||
: "https://cdn.discordapp.com/icons/1040502664506646548/bb5a51c4659cf47bdd942bb11e974da7.webp?size=240"
|
||||
}
|
||||
alt="Server pfp"
|
||||
/>
|
||||
<h1>{guild.name}</h1>
|
||||
<FontAwesomeIcon
|
||||
// beat={i() % 3 === 1}
|
||||
color={colors[i() % 3]}
|
||||
icon={icons[i() % 3]}
|
||||
size="xl"
|
||||
/>
|
||||
</a>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default index;
|
|
@ -1,19 +1,16 @@
|
|||
import Layout from "~/components/Layout";
|
||||
import "../styles/pages/contact.scss";
|
||||
|
||||
function contact() {
|
||||
return (
|
||||
<>
|
||||
<div class="contact">
|
||||
<Layout site="contact">
|
||||
<h1>Contact</h1>
|
||||
<section class="contact">
|
||||
<section>
|
||||
<a href="mailto:contact@moonleay.net" target="_blank">
|
||||
<img src="/assets/icons/email.svg" alt="Email" />
|
||||
contact@moonleay.net
|
||||
</a>
|
||||
<a
|
||||
href="https://discord.com/users/372703841151614976"
|
||||
target="_blank"
|
||||
>
|
||||
<a href="https://discord.com/users/372703841151614976" target="_blank">
|
||||
<img src="/assets/icons/discord.svg" alt="Discord" />
|
||||
@moonleay
|
||||
</a>
|
||||
|
@ -22,8 +19,7 @@ function contact() {
|
|||
li'l Judd's home base
|
||||
</a>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import ImageSection from "~/components/ImageSection";
|
||||
import Layout from "~/components/Layout";
|
||||
import "../styles/pages/features.scss";
|
||||
|
||||
function features() {
|
||||
return (
|
||||
<>
|
||||
<div class="features">
|
||||
<h1 class="title">Features</h1>
|
||||
<Layout site="features">
|
||||
<h1>Features</h1>
|
||||
<div class="gridlayout">
|
||||
<ImageSection
|
||||
imgUrl="/assets/screenshots/timeplanner.png"
|
||||
|
@ -44,8 +44,7 @@ function features() {
|
|||
note="If you have a specific feature request, you can contact me on Discord: @moonleay or email: contact at moonleay dot net"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import Layout from "~/components/Layout";
|
||||
import "../styles/pages/how-do-i.scss";
|
||||
|
||||
function howDoI() {
|
||||
return (
|
||||
<>
|
||||
<h1 class="hdi-title">How do I...?</h1>
|
||||
<section class="hdi-section">
|
||||
<Layout site="how-do-i">
|
||||
<h1>How do I...?</h1>
|
||||
<section>
|
||||
<h2>.. enable / disable certain features?</h2>
|
||||
<p>
|
||||
Features can be enabled and disables using the <code>/feature</code>
|
||||
|
@ -26,7 +27,7 @@ function howDoI() {
|
|||
{/* <p><code>/feature feature:Time Planning Feature set:Enable channel:#ich-kann-heute</code></p> */}
|
||||
</div>
|
||||
</section>
|
||||
<section class="hdi-section">
|
||||
<section>
|
||||
<h2>.. create a match?</h2>
|
||||
<p>
|
||||
You can create a match time using the <code>/match</code> command.
|
||||
|
@ -47,14 +48,14 @@ function howDoI() {
|
|||
{/* <p><code>/match match:Ladder Match timestamp:24.12.2069 04:20 opponent:Forbidden</code></p> */}
|
||||
</div>
|
||||
</section>
|
||||
<section class="hdi-footernotesection">
|
||||
<section class="note">
|
||||
<p>
|
||||
Is something missing here?
|
||||
<br />
|
||||
Please <a href="/contact">contact me</a>!
|
||||
</p>
|
||||
</section>
|
||||
</>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import Layout from "~/components/Layout";
|
||||
import "../styles/pages/imprint.scss";
|
||||
|
||||
function imprint() {
|
||||
return (
|
||||
<>
|
||||
<section class="imprint">
|
||||
<Layout site="imprint">
|
||||
<section>
|
||||
<h1>Imprint</h1>
|
||||
<section>
|
||||
<a href="/contact">
|
||||
|
@ -54,7 +55,7 @@ function imprint() {
|
|||
</h5>
|
||||
</section>
|
||||
</section>
|
||||
</>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import Layout from "~/components/Layout";
|
||||
import "../styles/pages/index.scss";
|
||||
|
||||
function index() {
|
||||
return (
|
||||
<>
|
||||
<section class="index">
|
||||
<Layout site="index">
|
||||
<section>
|
||||
<h1>li'l Judd</h1>
|
||||
<h5>The competetive Splatoon Bot</h5>
|
||||
<div>
|
||||
|
@ -13,7 +14,7 @@ function index() {
|
|||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import Layout from "~/components/Layout";
|
||||
import "../styles/pages/privacy-policy.scss";
|
||||
|
||||
function privacyPolicy() {
|
||||
return (
|
||||
<>
|
||||
<div class="privacyPolicy">
|
||||
<Layout site="privacyPolicy">
|
||||
<div>
|
||||
<h1>Privacy Policy for li'l Judd</h1>
|
||||
<h4>Last updated: 2023-12-05</h4>
|
||||
|
@ -57,8 +57,8 @@ function privacyPolicy() {
|
|||
</ul>
|
||||
<h3>3.2 Usage Data</h3>
|
||||
<p>
|
||||
We may collect information on how you interact with our bot,
|
||||
including but not limited to:
|
||||
We may collect information on how you interact with our bot, including
|
||||
but not limited to:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
|
@ -96,8 +96,8 @@ function privacyPolicy() {
|
|||
<ul>
|
||||
<li>
|
||||
<p>
|
||||
- Consent: You have given your consent for the processing of
|
||||
your personal data for one or more specific purposes.
|
||||
- Consent: You have given your consent for the processing of your
|
||||
personal data for one or more specific purposes.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
|
@ -111,9 +111,8 @@ function privacyPolicy() {
|
|||
<section>
|
||||
<h2>6. Data Sharing</h2>
|
||||
<p>
|
||||
We do not sell, trade, or otherwise transfer your personal
|
||||
information to third parties. However, we may share your information
|
||||
with:
|
||||
We do not sell, trade, or otherwise transfer your personal information
|
||||
to third parties. However, we may share your information with:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
|
@ -151,8 +150,8 @@ function privacyPolicy() {
|
|||
</li>
|
||||
<li>
|
||||
<p>
|
||||
- Right to erasure: You can request the deletion of your
|
||||
personal data.
|
||||
- Right to erasure: You can request the deletion of your personal
|
||||
data.
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -160,8 +159,8 @@ function privacyPolicy() {
|
|||
<section>
|
||||
<h2>9. Changes to this Privacy Policy</h2>
|
||||
<p>
|
||||
We may update this Privacy Policy to reflect changes in our
|
||||
practices. The updated version will be posted on
|
||||
We may update this Privacy Policy to reflect changes in our practices.
|
||||
The updated version will be posted on
|
||||
https://liljudd.ink/privacy-policy.
|
||||
</p>
|
||||
</section>
|
||||
|
@ -172,8 +171,7 @@ function privacyPolicy() {
|
|||
please contact us at contact@moonleay.net.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import Layout from "~/components/Layout";
|
||||
import "../styles/pages/stack.scss";
|
||||
|
||||
function stack() {
|
||||
return (
|
||||
<>
|
||||
<h1 class="stack-title">The Stack</h1>
|
||||
<section class="stack-section">
|
||||
<Layout site="stack">
|
||||
<h1>The Stack</h1>
|
||||
<section>
|
||||
<img src="/assets/logos/kotlin.svg" alt="Kotlin 'K' logo" />
|
||||
<div class="stackgrid_3 stackitm">
|
||||
<h1>The Kotlin programming language</h1>
|
||||
|
@ -14,14 +15,14 @@ function stack() {
|
|||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="stack-section">
|
||||
<section>
|
||||
<img src="/assets/logos/kord.png" alt="The Kord logo" />
|
||||
<div class="stackgrid_3 stackitm">
|
||||
<h1>The Kord library</h1>
|
||||
<p>A Kotlin library for creating Discord bots. Pretty bare bones.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="stack-section">
|
||||
<section>
|
||||
<img
|
||||
src="/assets/logos/kordextensions.png"
|
||||
alt="The Kord-Extensions logo"
|
||||
|
@ -31,7 +32,7 @@ function stack() {
|
|||
<p>A Kotlin library to improve the Kord experience.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="stack-section">
|
||||
<section>
|
||||
<img src="/assets/logos/pgelephant.png" alt="The PostgreSQL elephant" />
|
||||
<div class="stackgrid_3 stackitm">
|
||||
<h1>The PostgreSQL database</h1>
|
||||
|
@ -47,7 +48,7 @@ function stack() {
|
|||
<a href="/acknowledgements">Acknowledgements</a>.
|
||||
</p>
|
||||
</section>
|
||||
</>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import Layout from "~/components/Layout";
|
||||
import "../styles/pages/terms-of-service.scss";
|
||||
|
||||
function termsOfService() {
|
||||
return (
|
||||
<>
|
||||
<div class="termsOfService">
|
||||
<Layout site="termsOfService">
|
||||
<h1>Terms of Service</h1>
|
||||
<div>
|
||||
<h2>Usage Agreement</h2>
|
||||
<p>
|
||||
By inviting the bot and using its features (commands, planning
|
||||
system) are you agreeing to the below mentioned Terms and Privacy
|
||||
Policy (Policy) of the bot.
|
||||
By inviting the bot and using its features (commands, planning system)
|
||||
are you agreeing to the below mentioned Terms and Privacy Policy
|
||||
(Policy) of the bot.
|
||||
<br />
|
||||
You acknowledge that you have the privilege to use the bot freely on
|
||||
any Discord Server (Server) you share with it, that you can invite
|
||||
it to any Server that you have "Manage Server" rights for and that
|
||||
this privilege might get revoked for you, if you're subject of
|
||||
breaking the terms and/or policy of this bot, or the{" "}
|
||||
any Discord Server (Server) you share with it, that you can invite it
|
||||
to any Server that you have "Manage Server" rights for and that this
|
||||
privilege might get revoked for you, if you're subject of breaking the
|
||||
terms and/or policy of this bot, or the{" "}
|
||||
<a href="https://discord.com/terms" target="_blank">
|
||||
Terms of Service
|
||||
</a>
|
||||
|
@ -33,11 +33,11 @@ function termsOfService() {
|
|||
Discord Inc
|
||||
</a>
|
||||
.<br />
|
||||
Through Inviting the bot may it collect specific data as described
|
||||
in its Policy.
|
||||
Through Inviting the bot may it collect specific data as described in
|
||||
its Policy.
|
||||
<br />
|
||||
The intended usage of this data is for core functionalities of the
|
||||
bot such as command handling, guild-specific settings and the
|
||||
The intended usage of this data is for core functionalities of the bot
|
||||
such as command handling, guild-specific settings and the
|
||||
time-planning system.
|
||||
<br />
|
||||
</p>
|
||||
|
@ -45,11 +45,11 @@ function termsOfService() {
|
|||
<div>
|
||||
<h2>Intended Age</h2>
|
||||
<p>
|
||||
The bot may not be used by individuals under the minimal age
|
||||
described in Discord's Terms of Service.
|
||||
The bot may not be used by individuals under the minimal age described
|
||||
in Discord's Terms of Service.
|
||||
<br />
|
||||
Doing so will be seen as a violation of these terms and will result
|
||||
in a removal of the bot from any servers you own.
|
||||
Doing so will be seen as a violation of these terms and will result in
|
||||
a removal of the bot from any servers you own.
|
||||
<br />
|
||||
</p>
|
||||
</div>
|
||||
|
@ -60,8 +60,7 @@ function termsOfService() {
|
|||
<br />
|
||||
Any direct connection to Discord or any of its Trademark objects is
|
||||
purely coincidental. We do not claim to have the copyright ownership
|
||||
of any of Discord's assets, trademarks or other intellectual
|
||||
property.
|
||||
of any of Discord's assets, trademarks or other intellectual property.
|
||||
<br />
|
||||
</p>
|
||||
</div>
|
||||
|
@ -71,26 +70,25 @@ function termsOfService() {
|
|||
The owner of the bot may not be made liable for individuals breaking
|
||||
these Terms at any given time.
|
||||
<br />
|
||||
He has faith in the end users being truthfull about their
|
||||
information and not misusing this bot or the services of Discord Inc
|
||||
in a malicious way.
|
||||
He has faith in the end users being truthfull about their information
|
||||
and not misusing this bot or the services of Discord Inc in a
|
||||
malicious way.
|
||||
<br />
|
||||
We reserve the right to update these terms at our own discretion,
|
||||
giving you a 1-Week (7 days) period to opt out of these terms if
|
||||
you're not agreeing with the new changes. You may opt out by
|
||||
Removing the bot from any Server you have the rights for.
|
||||
you're not agreeing with the new changes. You may opt out by Removing
|
||||
the bot from any Server you have the rights for.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Contact</h2>
|
||||
<p>
|
||||
People may get in contact through e-mail at contact@moonleay.net, or
|
||||
through the official Support Discord of the Bot. Other ways of
|
||||
support may be provided but aren't guaranteed.
|
||||
through the official Support Discord of the Bot. Other ways of support
|
||||
may be provided but aren't guaranteed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -5,46 +5,73 @@ nav {
|
|||
backdrop-filter: blur(5px);
|
||||
position: sticky;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
|
||||
.navElem {
|
||||
margin: 0.5rem 1rem;
|
||||
transition: 0.5s;
|
||||
ul .navElem a {
|
||||
color: white;
|
||||
padding: 0 1rem;
|
||||
vertical-align: middle;
|
||||
|
||||
&:hover {
|
||||
color: rgb(96 59 255) !important;
|
||||
|
||||
.swap {
|
||||
svg path {
|
||||
transition: color 0.5s 0.5s;
|
||||
}
|
||||
|
||||
.textBx {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.primary {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#logo {
|
||||
.secondary {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img,
|
||||
.swap,
|
||||
.swap svg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 100%;
|
||||
max-width: initial;
|
||||
max-height: initial;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.swap {
|
||||
position: relative;
|
||||
|
||||
> * {
|
||||
transition: opacity 0.5s;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
img {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
span,
|
||||
> svg path {
|
||||
transition: color 0.5s;
|
||||
}
|
||||
|
||||
.lower {
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
ul {
|
||||
flex-direction: row;
|
||||
padding: 0 2rem;
|
||||
justify-content: space-between;
|
||||
|
||||
.navElem:last-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
ul {
|
||||
height: 72px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,3 +53,24 @@ a {
|
|||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
&.centered {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.thick {
|
||||
align-items: initial;
|
||||
}
|
||||
|
||||
&.responsive {
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
.aboutdiv {
|
||||
.about {
|
||||
max-width: 1100px;
|
||||
margin: 1rem auto;
|
||||
width: 100%;
|
||||
|
|
|
@ -1,38 +1,34 @@
|
|||
.guildpfp {
|
||||
.config {
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.guildpfp {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.horizontal {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
h1 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
p, h3, h4 {
|
||||
|
||||
label {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.marg_right_5px {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.config {
|
||||
article {
|
||||
section,
|
||||
a {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
max-width: 1100px;
|
||||
margin: 1rem auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sub {
|
||||
margin-left: 10px;
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
}
|
||||
text-align: center;
|
||||
|
||||
.contact {
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
text-align: center;
|
||||
margin-bottom: 1.2rem;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
.hdi-title {
|
||||
.how-do-i {
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
text-align: center;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hdi-section {
|
||||
section {
|
||||
max-width: 1100px;
|
||||
margin: 1rem auto;
|
||||
width: 100%;
|
||||
|
@ -39,9 +40,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hdi-footernotesection {
|
||||
.note {
|
||||
max-width: 1100px;
|
||||
margin: 1rem auto;
|
||||
width: 100%;
|
||||
|
@ -61,4 +62,5 @@
|
|||
color: rgb(96 59 255) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
.stack-title {
|
||||
.stack {
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
text-align: center;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.stack-section {
|
||||
section {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 4px;
|
||||
margin: 1rem;
|
||||
|
@ -33,9 +34,9 @@
|
|||
align-self: center;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stack-note {
|
||||
.note {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 4px;
|
||||
margin: 1rem;
|
||||
|
@ -58,4 +59,5 @@
|
|||
color: rgb(96 59 255) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
15
src/types/authjs.d.ts
vendored
Normal file
15
src/types/authjs.d.ts
vendored
Normal 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
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
16
src/types/env.d.ts
vendored
Normal 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
196
src/types/liljudd.d.ts
vendored
Normal 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;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue