Compare commits

..

2 commits

Author SHA1 Message Date
b28ceb8659
Fix: Mistakes in openapi specs 2024-02-11 20:57:48 +01:00
4e02e51fca
Fix: Working Frontend without bugs 2024-02-11 19:31:21 +01:00
5 changed files with 369 additions and 198 deletions

View file

@ -22,7 +22,7 @@
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/guildConfig" "$ref": "#/components/schemas/bootConfig"
} }
} }
} }
@ -382,6 +382,20 @@
}, },
"components": { "components": {
"schemas": { "schemas": {
"bootConfig": {
"type": "object",
"properties": {
"guilds": {
"type": "array",
"items": {
"$ref": "#/components/schemas/guildConfig"
}
},
"accessToken": {
"type": "string"
}
}
},
"guildConfig": { "guildConfig": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -506,17 +520,17 @@
"type": "array", "type": "array",
"items": { "items": {
"type": "string", "type": "string",
"format": "varchar(20)", "format": "varchar(20)"
"example": [ },
"1234567890123456789", "example": [
"1234567890123456789", "1234567890123456789",
"1234567890123456789", "1234567890123456789",
"1234567890123456789", "1234567890123456789",
"1234567890123456789", "1234567890123456789",
"1234567890123456789", "1234567890123456789",
"1234567890123456789" "1234567890123456789",
] "1234567890123456789"
} ]
} }
} }
} }

View file

@ -0,0 +1,111 @@
import { getSession } from "@auth/solid-start";
import { APIEvent } from "@solidjs/start/server/types";
import { eq } from "drizzle-orm";
import moment from "moment-timezone";
import createClient from "openapi-fetch";
import db from "~/drizzle";
import { accounts } from "~/drizzle/schema";
import { authOptions } from "~/server/auth";
import { paths } from "~/types/discord";
type data = {
success: boolean | null;
guild: {
id: string;
name: string | undefined;
icon: string | null | undefined;
channel: string;
channels: {
id: string;
name: string;
}[];
};
tzNames: string[];
};
export const GET = async (
event: APIEvent,
): Promise<
{ success: false; message: string } | (data & { success: true })
> => {
const id = event.params.guildId;
// const id = "598539452343648256";
// const location = useLocation();
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 guildsRequest = await GET("/users/@me/guilds", {
headers: { Authorization: `Bearer ${DISCORD_ACCESS_TOKEN}` },
});
const channelsRequest = await GET("/guilds/{guild_id}/channels", {
params: {
path: {
guild_id: id,
},
},
headers: { Authorization: `Bot ${import.meta.env.VITE_DISCORD_BOT_TOKEN}` },
});
if (guildsRequest.error || channelsRequest.error) {
console.log(guildsRequest.error, channelsRequest.error, location.pathname);
return {
success: false,
message: "Error on one of the discord api requests!",
};
}
const guild = guildsRequest.data?.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!",
};
let channels: data["guild"]["channels"] = [];
channelsRequest.data?.forEach((channel) => {
if (channel.type !== 0) return;
channels.push({
id: channel.id,
name: channel.name,
});
});
console.log("done");
return {
success: true,
guild: {
id: guild.id,
name: guild.name,
icon: guild.icon,
// channel: "1162917335275950180",
channel: "",
channels,
},
tzNames: moment.tz.names(),
};
};

View file

@ -0,0 +1,62 @@
import { getSession } from "@auth/solid-start";
import { APIEvent } from "@solidjs/start/server/types";
import { eq } from "drizzle-orm";
import createClient from "openapi-fetch";
import db from "~/drizzle";
import { accounts } from "~/drizzle/schema";
import { authOptions } from "~/server/auth";
import { paths } from "~/types/discord";
type data = {
success: boolean | null;
guilds: {
id: string;
name: string;
icon: string | null | undefined;
}[];
};
export const GET = async (
event: APIEvent,
): Promise<
{ success: false; message: string } | (data & { success: true })
> => {
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 })) ?? [],
};
};

View file

@ -1,18 +1,15 @@
import { getSession } from "@auth/solid-start";
import { faToggleOff, faToggleOn } from "@fortawesome/pro-regular-svg-icons"; import { faToggleOff, faToggleOn } from "@fortawesome/pro-regular-svg-icons";
import { useNavigate, useParams } from "@solidjs/router"; import { useLocation, useNavigate, useParams } from "@solidjs/router";
import { eq } from "drizzle-orm"; import {
import moment from "moment-timezone"; For,
import createClient from "openapi-fetch"; Index,
import { Index, createEffect, createResource, createSignal } from "solid-js"; createEffect,
createResource,
createSignal,
} from "solid-js";
import { createStore } from "solid-js/store"; import { createStore } from "solid-js/store";
import { getRequestEvent } from "solid-js/web";
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"; import { FontAwesomeIcon } from "~/components/FontAwesomeIcon";
import Layout from "~/components/Layout"; 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"; import "../../styles/pages/config.scss";
const guessTZ = () => Intl.DateTimeFormat().resolvedOptions().timeZone; const guessTZ = () => Intl.DateTimeFormat().resolvedOptions().timeZone;
@ -23,113 +20,75 @@ const initialValue = (params: ReturnType<typeof useParams>) => ({
id: params.guildId, id: params.guildId,
name: undefined as string | undefined, name: undefined as string | undefined,
icon: undefined as string | null | undefined, icon: undefined as string | null | undefined,
channel: "",
channels: [] as { id: string; name: string }[],
}, },
tzNames: [guessTZ()], 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() { function config() {
const params = useParams(); const params = useParams();
const navigator = useNavigate(); const navigator = useNavigate();
let [timezoneRef, setTimezoneRef] = createSignal<HTMLInputElement>(); const location = useLocation();
let [timePlanningRef, setTimePlanningRef] = createSignal<HTMLInputElement>(); const [timezoneRef, setTimezoneRef] = createSignal<HTMLInputElement>();
let [pingableRolesRef, setPingableRolesRef] = const [timePlanningRef, setTimePlanningRef] =
createSignal<HTMLInputElement>();
const [channelRef, setChannelRef] = createSignal<HTMLSelectElement>();
const [pingableRolesRef, setPingableRolesRef] =
createSignal<HTMLInputElement>(); createSignal<HTMLInputElement>();
const [timezone, setTimezone] = createSignal(guessTZ()); const [timezone, setTimezone] = createSignal(guessTZ());
const [payload] = createResource(params.guildId, async (id) => { const [payload] = createResource(
const payload = await getPayload(id); params.guildId,
async (id) => {
const payload = await fetch(`http://localhost:3000/api/config/${id}`)
.then(
(res) =>
res.json() as Promise<
| {
success: false;
message: string;
}
| (ReturnType<typeof initialValue> & {
success: true;
})
>,
)
.catch((e) => console.warn(e, id));
if (!payload.success) { if (!payload) return initialValue(params);
console.log(payload.message, "No success");
navigator("/config", { replace: false }); if (!payload.success) {
return initialValue(params); console.log(payload);
} console.log(location.pathname, payload.message, "No success");
return payload; // navigator("/config", { replace: false });
}); return initialValue(params);
const guild = () => payload()?.guild ?? initialValue(params).guild; }
const tzNames = () => payload()?.tzNames ?? []; return payload;
},
{ initialValue: initialValue(params) },
);
const [config, setConfig] = createStore({ const [config, setConfig] = createStore({
features: { features: {
timePlanning: { timePlanning: {
enabled: false, enabled: false,
channelId: "833442323160891452",
pingableRoles: false, pingableRoles: false,
}, },
}, },
}); });
createEffect(() => console.log(payload())); createEffect(() => console.log(payload.loading, payload()));
createEffect(() => console.log("timezone", timezone())); createEffect(() => console.log("timezone", timezone()));
createEffect(() => createEffect(() =>
console.log("timePlanning.enabled", config.features.timePlanning.enabled), console.log("timePlanning.enabled", config.features.timePlanning.enabled),
); );
createEffect(() =>
console.log(
"timePlanning.channelId",
config.features.timePlanning.channelId,
),
);
createEffect(() => createEffect(() =>
console.log( console.log(
"timePlanning.pingableRoles", "timePlanning.pingableRoles",
@ -147,11 +106,42 @@ function config() {
if (!ref) return; if (!ref) return;
ref.checked = config.features.timePlanning.enabled; ref.checked = config.features.timePlanning.enabled;
}); });
createEffect(() => {
const channelId = payload().guild.channel;
setConfig("features", "timePlanning", "channelId", channelId);
console.log(channelId, payload());
const ref = channelRef();
if (!ref) return;
if (
!ref ||
!channelId ||
!payload().guild.channels.find((e) => e.id === channelId)
)
return;
ref.value = channelId;
});
createEffect(() => { createEffect(() => {
const ref = pingableRolesRef(); const ref = pingableRolesRef();
if (!ref) return; if (!ref) return;
ref.checked = config.features.timePlanning.pingableRoles; ref.checked = config.features.timePlanning.pingableRoles;
}); });
createEffect(() => {
const ref = timezoneRef();
if (!ref) return;
ref.value = timezone();
});
createEffect(() => {
const ref = timePlanningRef();
if (!ref) return;
ref.checked = config.features.timePlanning.enabled;
});
createEffect(() => {
const ref = pingableRolesRef();
if (!ref) return;
ref.checked = config.features.timePlanning.pingableRoles;
});
// console.log(payload());
return ( return (
<Layout site="config"> <Layout site="config">
@ -162,15 +152,15 @@ function config() {
<img <img
class="guildpfp" class="guildpfp"
src={ src={
guild()?.icon payload().guild.icon
? `https://cdn.discordapp.com/icons/${guild()?.id}/${ ? `https://cdn.discordapp.com/icons/${payload().guild.id}/${
guild()?.icon payload().guild.icon
}.webp?size=240` }.webp?size=240`
: "https://cdn.discordapp.com/icons/1040502664506646548/bb5a51c4659cf47bdd942bb11e974da7.webp?size=240" : "https://cdn.discordapp.com/icons/1040502664506646548/bb5a51c4659cf47bdd942bb11e974da7.webp?size=240"
} }
alt="Server pfp" alt="Server pfp"
/> />
<h1>{guild()?.name ?? "li'l Judds home base"}</h1> <h1>{payload().guild.name}</h1>
</div> </div>
</div> </div>
@ -189,7 +179,7 @@ function config() {
/> />
<datalist id="timezones"> <datalist id="timezones">
<Index each={tzNames()}> <Index each={payload().tzNames}>
{(zone) => <option value={zone()} />} {(zone) => <option value={zone()} />}
</Index> </Index>
</datalist> </datalist>
@ -231,12 +221,28 @@ function config() {
> >
<div class="flex-row"> <div class="flex-row">
<label>Target channel:</label> <label>Target channel:</label>
<select value={timezone()}> <select
<optgroup label="--Select a Channel--"> ref={(e) => setChannelRef(e)}
<Index each={tzNames()}> onInput={(e) =>
{(channel) => <option>{channel()}</option>} setConfig(
</Index> "features",
</optgroup> "timePlanning",
"channelId",
e.target.value,
)
}
>
<option
disabled={!!config.features.timePlanning.channelId}
value=""
>
--Select a Channel--
</option>
<For each={payload().guild.channels}>
{(channel) => (
<option value={channel.id}>{channel.name}</option>
)}
</For>
</select> </select>
</div> </div>
<div class="flex-row"> <div class="flex-row">
@ -271,6 +277,7 @@ function config() {
<section> <section>
<button>Apply</button> <button>Apply</button>
<button onClick={() => navigator("/config")}>Back</button>
</section> </section>
</div> </div>
</Layout> </Layout>

View file

@ -1,20 +1,12 @@
import { getSession } from "@auth/solid-start";
import { import {
faBadgeCheck, faBadgeCheck,
faCircleExclamation, faCircleExclamation,
faPlus, faPlus,
} from "@fortawesome/pro-regular-svg-icons"; } from "@fortawesome/pro-regular-svg-icons";
import { useNavigate } from "@solidjs/router"; import { useLocation, useNavigate } from "@solidjs/router";
import { eq } from "drizzle-orm"; import { For, Suspense, createEffect, createResource } from "solid-js";
import createClient from "openapi-fetch";
import { For, createResource } from "solid-js";
import { getRequestEvent } from "solid-js/web";
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"; import { FontAwesomeIcon } from "~/components/FontAwesomeIcon";
import Layout from "~/components/Layout"; 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"; import "../../styles/pages/config.scss";
const initialValue = () => ({ const initialValue = () => ({
@ -26,66 +18,40 @@ const initialValue = () => ({
}[], }[],
}); });
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() { function index() {
const navigator = useNavigate(); const navigator = useNavigate();
const location = useLocation();
const [payload] = createResource(async () => { const [payload] = createResource(async () => {
const payload = await getPayload(); const payload = await fetch("http://localhost:3000/api/config")
.then(
(res) =>
res.json() as Promise<
| {
success: false;
message: string;
}
| (ReturnType<typeof initialValue> & {
success: true;
})
>,
)
.catch((e) => console.warn(e));
if (!payload) return;
if (!payload.success) { if (!payload.success) {
console.log(payload.message, "No success"); console.log(location.pathname, payload.message, "No success");
navigator("/", { replace: false }); navigator("/", { replace: false });
return initialValue(); return initialValue();
} }
console.log("success"); console.log(location.pathname, "success");
return payload; return payload;
}); });
createEffect(() => console.log(payload()?.guilds, payload()?.guilds.length));
// createRenderEffect(() =>
// console.log(payload()?.guilds, payload()?.guilds.length),
// );
const icons = [faPlus, faCircleExclamation, faBadgeCheck]; const icons = [faPlus, faCircleExclamation, faBadgeCheck];
const colors = [undefined, "orange", "green"]; const colors = [undefined, "orange", "green"];
@ -94,28 +60,39 @@ function index() {
<Layout site="config"> <Layout site="config">
<h3 class="text-center">Configure li&apos;l Judd in</h3> <h3 class="text-center">Configure li&apos;l Judd in</h3>
<div> <div>
<For each={payload()?.guilds ?? []}> <Suspense>
{(guild, i) => ( <For each={payload()?.guilds}>
<a href={`/config/${guild.id}`} class="flex-row centered"> {(guild, i) => {
<img return (
class="guildpfp" <a
src={ href={
guild.icon i() % 3 === 0
? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.webp?size=240` ? `/config/${guild.id}`
: "https://cdn.discordapp.com/icons/1040502664506646548/bb5a51c4659cf47bdd942bb11e974da7.webp?size=240" : `https://discord.com/api/oauth2/authorize?client_id=${import.meta.env.VITE_DISCORD_CLIENT_ID}&permissions=${import.meta.env.VITE_DISCORD_BOT_PERMISSIONS}&scope=bot&guild_id=${guild.id}`
} }
alt="Server pfp" class="flex-row centered"
/> >
<h1>{guild.name}</h1> <img
<FontAwesomeIcon class="guildpfp"
// beat={i() % 3 === 1} src={
color={colors[i() % 3]} guild.icon
icon={icons[i() % 3]} ? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.webp?size=240`
size="xl" : "https://cdn.discordapp.com/icons/1040502664506646548/bb5a51c4659cf47bdd942bb11e974da7.webp?size=240"
/> }
</a> alt="Server pfp"
)} />
</For> <h1>{guild.name}</h1>
<FontAwesomeIcon
// beat={i() % 3 === 1}
color={colors[i() % 3]}
icon={icons[i() % 3]}
size="xl"
/>
</a>
);
}}
</For>
</Suspense>
</div> </div>
</Layout> </Layout>
); );