Fix: Finished Backend

This commit is contained in:
Aron Malcher 2024-02-26 21:46:33 +01:00
parent 6b388729d9
commit ffaf8d989e
Signed by: aronmal
GPG key ID: 816B7707426FC612
30 changed files with 1478 additions and 873 deletions

View file

@ -0,0 +1,73 @@
import { APIEvent } from "@solidjs/start/server/types";
import { eq } from "drizzle-orm";
import db from "~/drizzle";
import { guilds } from "~/drizzle/schema";
import { BasicAuth } from "~/lib/auth";
import { buildConfig } from "~/lib/responseBuilders";
import { ErrorResponse, Res } from "~/lib/responses";
import { zodId } from "~/lib/zod";
import { APIResponse } from "~/types/backend";
type Path = "/api/{guildId}/config";
export const GET = async (
event: APIEvent,
): Promise<APIResponse<Path, "get">> => {
switch (event.request.headers.get("authorization")) {
case BasicAuth.unencoded:
case BasicAuth.encoded:
break;
default:
return ErrorResponse("UNAUTHORIZED");
}
try {
zodId.parse(event.params.guildId);
} catch (e) {
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
}
const guildQuery = await db.query.guilds
.findFirst({
where: eq(guilds.id, event.params.guildId),
with: { tpMessages: true, matches: true },
})
.execute();
if (!guildQuery) return ErrorResponse("NOT_FOUND");
return Res("OK", buildConfig(guildQuery));
};
export const DELETE = async (
event: APIEvent,
): Promise<APIResponse<Path, "delete">> => {
switch (event.request.headers.get("authorization")) {
case BasicAuth.unencoded:
case BasicAuth.encoded:
break;
default:
return ErrorResponse("UNAUTHORIZED");
}
try {
zodId.parse(event.params.guildId);
} catch (e) {
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
}
const guildQuery = await db.query.guilds
.findFirst({
where: eq(guilds.id, event.params.guildId),
with: { tpMessages: true, matches: true },
})
.execute();
if (!guildQuery) return ErrorResponse("NOT_FOUND");
await db.delete(guilds).where(eq(guilds.id, event.params.guildId)).execute();
return Res("NO_CONTENT", null);
};

View file

@ -0,0 +1,93 @@
import { APIEvent } from "@solidjs/start/server/types";
import { eq } from "drizzle-orm";
import db from "~/drizzle";
import { guilds, matches } from "~/drizzle/schema";
import { BasicAuth } from "~/lib/auth";
import { buildMatches } from "~/lib/responseBuilders";
import { ErrorResponse, Res } from "~/lib/responses";
import { zodMatch } from "~/lib/zod";
import { APIResponse, RequestBody } from "~/types/backend";
type Path = "/api/{guildId}/matches";
export const GET = async (
event: APIEvent,
): Promise<APIResponse<Path, "get">> => {
switch (event.request.headers.get("authorization")) {
case BasicAuth.unencoded:
case BasicAuth.encoded:
break;
default:
return ErrorResponse("UNAUTHORIZED");
}
const guild = await db.query.guilds
.findFirst({
where: eq(guilds.id, event.params.guildId),
with: {
matches: true,
},
})
.execute();
console.log(event.params.guildId, guild);
if (!guild) return ErrorResponse("NOT_FOUND");
if (guild.matches.length < 1) return Res("NO_CONTENT", null);
return Res("OK", {
matches: buildMatches(guild.matches),
timezone: guild.timezone,
});
};
export const POST = async (
event: APIEvent,
): Promise<APIResponse<Path, "post">> => {
switch (event.request.headers.get("authorization")) {
case BasicAuth.unencoded:
case BasicAuth.encoded:
break;
default:
return ErrorResponse("UNAUTHORIZED");
}
const guild = await db.query.guilds
.findFirst({
where: eq(guilds.id, event.params.guildId),
with: {
matches: true,
},
})
.execute();
console.log(event.params.guildId, guild);
if (!guild) return ErrorResponse("NOT_FOUND");
const unparsedBody = await new Response(event.request.body).json();
let body: RequestBody<Path, "post">;
try {
body = zodMatch.parse(unparsedBody);
} catch (e) {
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
}
if (body.timezone !== guild.timezone)
return ErrorResponse(
"BAD_REQUEST",
"Match's timezone is different from guild's timezone",
);
await db.insert(matches).values({
...body.match,
guildId: guild.id,
utc_ts: new Date(body.match.utc_ts),
});
return Res("NO_CONTENT", null);
};

View file

@ -0,0 +1,114 @@
import { APIEvent } from "@solidjs/start/server/types";
import { and, eq } from "drizzle-orm";
import db from "~/drizzle";
import { guilds, tpMessages } from "~/drizzle/schema";
import { BasicAuth } from "~/lib/auth";
import { ErrorResponse, Res } from "~/lib/responses";
import { zodTpMessages } from "~/lib/zod";
import { APIResponse, RequestBody } from "~/types/backend";
type Path = "/api/{guildId}/tp_messages";
const DayKeys = ["0", "1", "2", "3", "4", "5", "6"] as const;
type DayKeys = (typeof DayKeys)[number];
type Messages = Record<DayKeys, string | null>;
export const GET = async (
event: APIEvent,
): Promise<APIResponse<Path, "get">> => {
switch (event.request.headers.get("authorization")) {
case BasicAuth.unencoded:
case BasicAuth.encoded:
break;
default:
return ErrorResponse("UNAUTHORIZED");
}
const guild = await db.query.guilds.findFirst({
where: eq(guilds.id, event.params.guildId),
with: {
tpMessages: true,
},
});
if (!guild) return ErrorResponse("NOT_FOUND");
if (!guild.tpEnabled || !guild.tpChannelId) return Res("NO_CONTENT", null);
const tpMessages = guild.tpMessages.reduce(
(acc, message) => {
const day = message.day.toString() as DayKeys;
if (!/^[0-6]$/.test(day)) return acc;
acc[day] = message.messageId;
return acc;
},
{
"0": null,
"1": null,
"2": null,
"3": null,
"4": null,
"5": null,
"6": null,
} as Messages,
);
return Res("OK", {
channelId: guild.tpChannelId,
messageIds: tpMessages,
});
};
export const PUT = async (
event: APIEvent,
): Promise<APIResponse<Path, "put">> => {
switch (event.request.headers.get("authorization")) {
case BasicAuth.unencoded:
case BasicAuth.encoded:
break;
default:
return ErrorResponse("UNAUTHORIZED");
}
const guild = await db.query.guilds
.findFirst({
where: eq(guilds.id, event.params.guildId),
with: { tpMessages: true },
})
.execute();
if (!guild) return ErrorResponse("NOT_FOUND");
if (!guild.tpEnabled) return ErrorResponse("FORBIDDEN");
const unparsedBody = await new Response(event.request.body).json();
let body: RequestBody<Path, "put">;
try {
body = zodTpMessages.parse(unparsedBody);
} catch (e) {
return ErrorResponse("BAD_REQUEST", JSON.stringify(e));
}
if (guild.tpChannelId !== body.channelId)
await db
.update(guilds)
.set({ tpChannelId: body.channelId })
.where(eq(guilds.id, guild.id))
.execute();
await Promise.all(
DayKeys.map(async (dayStr) => {
const day = parseInt(dayStr);
await db
.update(tpMessages)
.set({ messageId: body.messageIds[dayStr] })
.where(and(eq(tpMessages.guildId, guild.id), eq(tpMessages.day, day)))
.execute();
}),
);
return Res("NO_CONTENT", null);
};

View file

@ -2,6 +2,7 @@ import { createId } from "@paralleldrive/cuid2";
import { APIEvent } from "@solidjs/start/server/types";
import { OAuth2RequestError } from "arctic";
import { eq } from "drizzle-orm";
import httpStatus from "http-status";
import createClient from "openapi-fetch";
import { getCookie, setCookie } from "vinxi/http";
import db from "~/drizzle";
@ -20,20 +21,20 @@ export async function GET(event: APIEvent): Promise<Response> {
switch (error) {
case "access_denied":
return new Response(null, {
status: 302,
status: httpStatus.FOUND,
headers: { Location: "/" },
});
default:
console.log("Discord oauth error:", error_description);
return new Response(decodeURI(error_description ?? ""), {
status: 400,
status: httpStatus.BAD_REQUEST,
});
}
const storedState = getCookie("discord_oauth_state") ?? null;
if (!code || !state || !storedState || state !== storedState) {
return new Response(null, {
status: 400,
status: httpStatus.BAD_REQUEST,
});
}
@ -77,7 +78,7 @@ export async function GET(event: APIEvent): Promise<Response> {
.execute();
return new Response(null, {
status: 302,
status: httpStatus.FOUND,
headers: { Location: "/config" },
});
}
@ -112,7 +113,7 @@ export async function GET(event: APIEvent): Promise<Response> {
sessionCookie.attributes,
);
return new Response(null, {
status: 302,
status: httpStatus.FOUND,
headers: { Location: "/config" },
});
} catch (e) {
@ -120,13 +121,13 @@ export async function GET(event: APIEvent): Promise<Response> {
if (e instanceof OAuth2RequestError) {
// invalid code
return new Response(null, {
status: 400,
status: httpStatus.BAD_REQUEST,
});
}
console.error("Unknown error on callback.");
console.error(e);
return new Response(null, {
status: 500,
status: httpStatus.INTERNAL_SERVER_ERROR,
});
}
}

View file

@ -1,5 +1,6 @@
import { APIEvent } from "@solidjs/start/server/types";
import { generateState } from "arctic";
import httpStatus from "http-status";
import { setCookie } from "vinxi/http";
import { discord } from "~/lib/auth";
@ -18,7 +19,7 @@ export async function GET(event: APIEvent) {
});
return new Response(null, {
status: 302,
status: httpStatus.FOUND,
headers: { Location: url.toString() },
});
}

View file

@ -1,4 +1,5 @@
import { APIEvent } from "@solidjs/start/server/types";
import httpStatus from "http-status";
import { appendHeader } from "vinxi/http";
import { lucia } from "~/lib/auth";
@ -13,7 +14,7 @@ export const GET = async (event: APIEvent) => {
lucia.createBlankSessionCookie().serialize(),
);
return new Response(null, {
status: 302,
status: httpStatus.FOUND,
headers: { Location: "/" },
});
};

35
src/routes/api/boot.ts Normal file
View file

@ -0,0 +1,35 @@
import { APIEvent } from "@solidjs/start/server/types";
import { eq } from "drizzle-orm";
import db from "~/drizzle";
import { guilds } from "~/drizzle/schema";
import { BasicAuth } from "~/lib/auth";
import { buildConfig } from "~/lib/responseBuilders";
import { ErrorResponse, Res } from "~/lib/responses";
import { APIResponse } from "~/types/backend";
type Path = "/api/boot";
export const GET = async (
event: APIEvent,
): Promise<APIResponse<Path, "get">> => {
switch (event.request.headers.get("authorization")) {
case BasicAuth.unencoded:
case BasicAuth.encoded:
break;
default:
return ErrorResponse("UNAUTHORIZED");
}
const guildQuery = await db.query.guilds
.findMany({
where: eq(guilds.id, event.params.guildId),
with: { tpMessages: true, matches: true },
})
.execute();
return Res(
"OK",
guildQuery.map((e) => buildConfig(e)),
);
};

View file

@ -1,61 +0,0 @@
import { APIEvent } from "@solidjs/start/server/types";
import { eq } from "drizzle-orm";
import db from "~/drizzle";
import { guilds } from "~/drizzle/schema";
export const GET = async ({ params }: APIEvent) => {
if (params.guildId === "boot") {
const guilds = await db.query.guilds
.findMany({
with: {
timePlanning: { with: { messages: true } },
matches: true,
},
})
.execute();
return { guilds };
}
const guild = await db.query.guilds
.findFirst({
where: eq(guilds.id, params.guildId),
with: {
timePlanning: { with: { messages: true } },
matches: true,
},
})
.execute();
if (!guild)
return new Response(JSON.stringify({ error: "No such guild found." }), {
status: 404,
});
return guild;
};
export const DELETE = async ({ params }: APIEvent) => {
const guildQuery = await db.query.guilds
.findFirst({
where: eq(guilds.id, params.guildId),
with: {
timePlanning: { with: { messages: true } },
matches: true,
},
})
.execute();
if (!guildQuery)
return new Response(JSON.stringify({ error: "No such guild found." }), {
status: 404,
});
const guild = await db
.delete(guilds)
.where(eq(guilds.id, params.guildId))
.returning()
.execute();
return guild;
};

View file

@ -1,24 +0,0 @@
import { APIEvent } from "@solidjs/start/server/types";
import { eq } from "drizzle-orm";
import db from "~/drizzle";
import { guilds } from "~/drizzle/schema";
export const PUT = async ({ params }: APIEvent) => {
const guild = await db.query.guilds
.findFirst({
where: eq(guilds.id, params.guildId),
with: {
timePlanning: { with: { messages: true } },
matches: true,
},
})
.execute();
if (!guild)
return new Response(JSON.stringify({ error: "No such guild found." }), {
status: 404,
});
return "TODO";
// return guild.timePlanning;
};

View file

@ -1,24 +0,0 @@
import { APIEvent } from "@solidjs/start/server/types";
import { eq } from "drizzle-orm";
import db from "~/drizzle";
import { guilds } from "~/drizzle/schema";
export const POST = async ({ params }: APIEvent) => {
const guild = await db.query.guilds
.findFirst({
where: eq(guilds.id, params.guildId),
with: {
timePlanning: { with: { messages: true } },
matches: true,
},
})
.execute();
if (!guild)
return new Response(JSON.stringify({ error: "No such guild found." }), {
status: 404,
});
return "TODO";
// return guild.timePlanning;
};

View file

@ -1,24 +0,0 @@
import { APIEvent } from "@solidjs/start/server/types";
import { eq } from "drizzle-orm";
import db from "~/drizzle";
import { guilds } from "~/drizzle/schema";
export const GET = async ({ params }: APIEvent) => {
const guild = await db.query.guilds
.findFirst({
where: eq(guilds.id, params.guildId),
with: {
timePlanning: { with: { messages: true } },
matches: true,
},
})
.execute();
if (!guild)
return new Response(JSON.stringify({ error: "No such guild found." }), {
status: 404,
});
return "TODO";
// return guild.timePlanning;
};

View file

@ -1,28 +0,0 @@
import { APIEvent } from "@solidjs/start/server/types";
import { eq } from "drizzle-orm";
import db from "~/drizzle";
import { guilds } from "~/drizzle/schema";
export const GET = async ({ params }: APIEvent) => {
const guild = await db.query.guilds
.findFirst({
where: eq(guilds.id, params.guildId),
with: {
timePlanning: { with: { messages: true } },
matches: true,
},
})
.execute();
if (!guild)
return new Response(JSON.stringify({ error: "No such guild found." }), {
status: 404,
});
return "TODO";
// return guild.timePlanning;
};
export const PUT = async () => {
return "TODO";
};

View file

@ -1,14 +0,0 @@
import db from "~/drizzle";
export const GET = async () => {
const guilds = await db.query.guilds
.findMany({
with: {
timePlanning: { with: { messages: true } },
matches: true,
},
})
.execute();
return { guilds };
};