Skip to content

Commit 7fda857

Browse files
committed
feat(dashboard): actual settings page
can't save yet though, still have to deal with cors
1 parent f45b39a commit 7fda857

File tree

12 files changed

+459
-177
lines changed

12 files changed

+459
-177
lines changed

api/package.json

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
{
2-
"name": "@chatr/api",
3-
"type": "module",
4-
"version": "0.1.0",
5-
"scripts": {
6-
"dev": "bun with-env bun --watch src/index.ts --dev",
7-
"with-env": "dotenv -e ../.env --"
8-
},
9-
"dependencies": {
10-
"cors": "^2.8.5",
11-
"cron": "^3.1.7",
12-
"express": "^4.19.2",
13-
"jsonwebtoken": "^9.0.2",
14-
"mysql2": "^3.10.3"
15-
},
16-
"devDependencies": {
17-
"@types/bun": "latest",
18-
"@types/cors": "^2.8.17",
19-
"@types/express": "^4.17.21",
20-
"@types/jsonwebtoken": "^9.0.7",
21-
"dotenv-cli": "^7.4.2"
22-
}
23-
}
2+
"name": "@chatr/api",
3+
"type": "module",
4+
"version": "0.1.0",
5+
"scripts": {
6+
"dev": "bun with-env bun --watch src/index.ts --dev",
7+
"with-env": "dotenv -e ../.env --"
8+
},
9+
"dependencies": {
10+
"cors": "^2.8.5",
11+
"cron": "^3.1.7",
12+
"express": "^4.19.2",
13+
"jsonwebtoken": "^9.0.2",
14+
"mysql2": "^3.10.3"
15+
},
16+
"devDependencies": {
17+
"@types/bun": "latest",
18+
"@types/cors": "^2.8.17",
19+
"@types/express": "^4.17.21",
20+
"@types/jsonwebtoken": "^9.0.7",
21+
"dotenv-cli": "^7.4.2"
22+
}
23+
}

api/src/db/queries/guilds.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { pool } from "..";
55
export interface Guild {
66
id: string;
77
name: string;
8-
icon: string;
8+
icon?: string;
99
members: number;
1010
cooldown: number;
1111
updates_enabled: 0 | 1;

api/src/db/queries/oauth-users.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ export interface OAuthUser {
1212
expires_at: Date;
1313
}
1414

15+
export type OAuthUserWithoutTokens = Without<
16+
OAuthUser,
17+
"access_token" | "refresh_token" | "expires_at"
18+
>;
19+
20+
type Without<T, K> = {
21+
[L in keyof T]: L extends K ? undefined : T[L];
22+
};
23+
1524
export function getOAuthUser(
1625
id: string
1726
): Promise<[QueryError, null] | [null, OAuthUser]> {

api/src/index.ts

Lines changed: 164 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -834,129 +834,159 @@ app.get("/auth/callback", async (req, res) => {
834834
res.redirect(`${WEBSITE_URL}/dashboard`);
835835
});
836836

837-
app.get(
838-
"/auth/user",
837+
const userRouter = express.Router();
838+
839+
userRouter.use(
839840
cors({
840841
origin: ["http://localhost:56413", "https://chatr.fun"],
841842
credentials: true,
842-
}),
843-
async (req, res) => {
844-
const user = await getUserFromRequest(req);
843+
})
844+
);
845+
846+
userRouter.get("/", async (req, res) => {
847+
const user = await getUserFromRequest(req);
848+
849+
if (!user) return res.status(401).json({ message: "Unauthorized" });
845850

846-
if (!user) return res.status(401).json({ message: "Unauthorized" });
851+
res.json({
852+
...user,
853+
access_token: undefined,
854+
refresh_token: undefined,
855+
expires_at: undefined,
856+
});
857+
});
847858

848-
res.json(user);
859+
userRouter.delete("/", async (req, res) => {
860+
if (!(await getUserFromRequest(req))) {
861+
return res.status(401).json({ message: "Unauthorized" });
849862
}
850-
);
851863

852-
app.post(
853-
"/auth/logout",
854-
cors({
855-
origin: ["http://localhost:56413", "https://chatr.fun"],
856-
credentials: true,
857-
}),
858-
async (req, res) => {
859-
if (!(await getUserFromRequest(req))) {
860-
return res.status(401).json({ message: "Unauthorized" });
864+
res.clearCookie("token");
865+
866+
return res.sendStatus(200);
867+
});
868+
869+
app.use("/user/me", userRouter);
870+
871+
app.get("/auth/user/guilds", async (req, res) => {
872+
const user = await getUserFromRequest(req);
873+
874+
if (!user) return res.status(401).json({ message: "Unauthorized" });
875+
876+
const botGuildsResponse = await fetch(
877+
"https://discord.com/api/users/@me/guilds",
878+
{
879+
headers: {
880+
Authorization: `Bot ${process.env.DISCORD_TOKEN_DEV ?? process.env.DISCORD_TOKEN}`,
881+
},
882+
}
883+
);
884+
const botGuilds = await botGuildsResponse.json();
885+
886+
const [err, accessToken] = await getAccessToken(user);
887+
888+
if (err) return res.status(500).json({ message: err });
889+
890+
const userGuildsResponse = await fetch(
891+
"https://discord.com/api/users/@me/guilds",
892+
{
893+
headers: {
894+
Authorization: `Bearer ${accessToken}`,
895+
},
861896
}
897+
);
898+
const userGuilds = await userGuildsResponse.json();
862899

863-
res.clearCookie("token");
900+
const filteredGuilds = userGuilds.filter(
901+
(guild: any) => guild.owner || (guild.permissions & 0x20) === 0x20
902+
);
864903

865-
return res.sendStatus(200);
866-
}
867-
);
904+
res.json(
905+
filteredGuilds
906+
.map((guild: any) => ({
907+
...guild,
908+
icon: guild.icon
909+
? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.webp`
910+
: null,
911+
botIsInGuild: botGuilds.some(
912+
(botGuild: any) => botGuild.id === guild.id
913+
),
914+
}))
915+
.sort((a: any, b: any) => {
916+
if (a.botIsInGuild === b.botIsInGuild) {
917+
return a.name.localeCompare(b.name);
918+
}
868919

869-
app.get(
870-
"/auth/user/guilds",
920+
return Number(b.botIsInGuild) - Number(a.botIsInGuild);
921+
})
922+
);
923+
});
924+
925+
app.options(
926+
"/auth/update-guild",
927+
cors({
928+
origin: ["http://localhost:56413", "https://chatr.fun"],
929+
credentials: true,
930+
})
931+
);
932+
app.put(
933+
"/auth/update-guild",
871934
cors({
872935
origin: ["http://localhost:56413", "https://chatr.fun"],
873936
credentials: true,
874937
}),
875938
async (req, res) => {
876-
const user = await getUserFromRequest(req);
877-
878-
if (!user) return res.status(401).json({ message: "Unauthorized" });
939+
if (!(await getUserFromRequest(req)))
940+
return res.status(401).json({ message: "Unauthorized" });
879941

880-
let accessToken = user.access_token;
942+
const body = req.body;
943+
const { guild } = req.body;
881944

882-
if (new Date().getTime() > user.expires_at.getTime()) {
883-
const body = new URLSearchParams();
945+
if (!guild) return res.status(400).json({ message: "Illegal request" });
884946

885-
body.append("client_id", process.env.DISCORD_CLIENT_ID!);
886-
body.append("client_secret", process.env.DISCORD_CLIENT_SECRET!);
887-
body.append("grant_type", "refresh_token");
888-
body.append("refresh_token", user.refresh_token);
889-
body.append("scope", "identify guilds");
947+
if (body.cooldown) {
948+
await setCooldown(guild, body.cooldown);
949+
}
890950

891-
const tokenResponse = await fetch(
892-
"https://discord.com/api/oauth2/token",
893-
{
894-
method: "POST",
895-
body,
896-
headers: {
897-
"Content-Type": "application/x-www-form-urlencoded",
898-
},
899-
}
900-
);
951+
if (body.updates.enabled === true) {
952+
await enableUpdates(guild);
953+
} else if (body.updates.enabled === false) {
954+
await disableUpdates(guild);
955+
}
901956

902-
if (tokenResponse.status !== 200) {
903-
console.error("Error fetching token:", tokenResponse);
957+
if (body.updates.channel) {
958+
await setUpdatesChannel(guild, body.updates.channel);
959+
}
904960

905-
return res
906-
.status(500)
907-
.json({ message: "Internal server error" });
908-
}
961+
return res.sendStatus(204);
962+
}
963+
);
909964

910-
const tokenData = await tokenResponse.json();
965+
// TODO: fetch from the bot itself using discord.js
966+
// (would allow us to do permission filtering)
967+
app.get("/channels/:guild", authMiddleware, async (req, res) => {
968+
const { guild } = req.params;
911969

912-
accessToken = tokenData.access_token;
970+
const channelsResponse = await fetch(
971+
`https://discord.com/api/v10/guilds/${guild}/channels`,
972+
{
973+
headers: {
974+
Authorization: `Bot ${process.env.DISCORD_TOKEN_DEV ?? process.env.DISCORD_TOKEN}`,
975+
},
913976
}
977+
);
978+
const channelsData = await channelsResponse.json();
914979

915-
const botGuildsResponse = await fetch(
916-
`https://discord.com/api/users/@me/guilds`,
917-
{
918-
headers: {
919-
Authorization: `Bot ${process.env.DISCORD_TOKEN_DEV ?? process.env.DISCORD_TOKEN}`,
920-
},
921-
}
922-
);
923-
const botGuilds = await botGuildsResponse.json();
924-
925-
const userGuildsResponse = await fetch(
926-
`https://discord.com/api/users/@me/guilds`,
927-
{
928-
headers: {
929-
Authorization: `Bearer ${accessToken}`,
930-
},
931-
}
932-
);
933-
const userGuilds = await userGuildsResponse.json();
980+
if (channelsData.code === 50007) {
981+
return res.status(404).json({ message: "Guild not found" });
982+
}
934983

935-
const filteredGuilds = userGuilds.filter(
936-
(guild: any) => guild.owner || (guild.permissions & 0x20) === 0x20
937-
);
984+
const channels = channelsData
985+
.filter((channel: any) => channel.type === 0)
986+
.sort((a: any, b: any) => a.position - b.position);
938987

939-
res.json(
940-
filteredGuilds
941-
.map((guild: any) => ({
942-
...guild,
943-
icon: guild.icon
944-
? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.webp`
945-
: null,
946-
botIsInGuild: botGuilds.some(
947-
(botGuild: any) => botGuild.id === guild.id
948-
),
949-
}))
950-
.sort((a: any, b: any) => {
951-
if (a.botIsInGuild === b.botIsInGuild) {
952-
return a.name.localeCompare(b.name);
953-
}
954-
955-
return Number(b.botIsInGuild) - Number(a.botIsInGuild);
956-
})
957-
);
958-
}
959-
);
988+
res.json(channels);
989+
});
960990

961991
app.get("/invite", (req, res) => {
962992
const guildId = req.query.guild_id;
@@ -1084,6 +1114,45 @@ async function getUserFromRequest(req: Request): Promise<OAuthUser | null> {
10841114

10851115
return user;
10861116
}
1117+
1118+
async function getAccessToken(
1119+
user: OAuthUser
1120+
): Promise<[string, null] | [null, string]> {
1121+
let accessToken = user.access_token;
1122+
1123+
if (new Date().getTime() > user.expires_at.getTime()) {
1124+
const body = new URLSearchParams();
1125+
1126+
body.append("client_id", process.env.DISCORD_CLIENT_ID!);
1127+
body.append("client_secret", process.env.DISCORD_CLIENT_SECRET!);
1128+
body.append("grant_type", "refresh_token");
1129+
body.append("refresh_token", user.refresh_token);
1130+
body.append("scope", "identify guilds");
1131+
1132+
const tokenResponse = await fetch(
1133+
"https://discord.com/api/oauth2/token",
1134+
{
1135+
method: "POST",
1136+
body,
1137+
headers: {
1138+
"Content-Type": "application/x-www-form-urlencoded",
1139+
},
1140+
}
1141+
);
1142+
1143+
if (tokenResponse.status !== 200) {
1144+
console.error("Error fetching token:", tokenResponse);
1145+
1146+
return ["Internal server error", null];
1147+
}
1148+
1149+
const tokenData = await tokenResponse.json();
1150+
1151+
accessToken = tokenData.access_token;
1152+
}
1153+
1154+
return [null, accessToken];
1155+
}
10871156
//#endregion
10881157

10891158
// TODO: actually implement this in a real way

bun.lockb

896 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)