fix: caching to prevent rate limiting
This commit is contained in:
parent
136468b4bd
commit
04fb5709a1
5 changed files with 139 additions and 61 deletions
|
@ -11,6 +11,12 @@ import {
|
|||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
export const cache = pgTable("cache", {
|
||||
key: varchar("key").primaryKey(),
|
||||
value: varchar("value").notNull(),
|
||||
createdAt: timestamp("created_at").notNull(),
|
||||
});
|
||||
|
||||
export const users = pgTable("user", {
|
||||
id: varchar("id", { length: 24 }).primaryKey(),
|
||||
discord_id: text("discord_id").notNull(),
|
||||
|
|
124
src/lib/cachedDiscord.ts
Normal file
124
src/lib/cachedDiscord.ts
Normal file
|
@ -0,0 +1,124 @@
|
|||
import { and, eq, gt } from "drizzle-orm";
|
||||
import createClient from "openapi-fetch";
|
||||
import db from "~/drizzle";
|
||||
import { cache } from "~/drizzle/schema";
|
||||
import { paths } from "~/types/discord";
|
||||
import getAccessToken from "./accessToken";
|
||||
|
||||
type Guilds =
|
||||
paths["/users/@me/guilds"]["get"]["responses"]["200"]["content"]["application/json"];
|
||||
|
||||
type Channels =
|
||||
paths["/guilds/{guild_id}/channels"]["get"]["responses"]["200"]["content"]["application/json"];
|
||||
|
||||
export const { GET: discordApi } = createClient<paths>({
|
||||
baseUrl: "https://discord.com/api/v10",
|
||||
});
|
||||
|
||||
export async function userGuilds(userId: string) {
|
||||
const path = "/users/@me/guilds";
|
||||
const key = `${path}:${userId}`;
|
||||
const consoleKey = `${path.green}:${userId.yellow}`;
|
||||
|
||||
const tokens = await getAccessToken(userId);
|
||||
if (!tokens) {
|
||||
console.log("No discord access token!");
|
||||
return null;
|
||||
}
|
||||
|
||||
const currentDate = new Date();
|
||||
const fifteenMinutesAgo = new Date(currentDate.getTime() - 15 * 60 * 1000);
|
||||
|
||||
let guilds: Guilds | undefined = JSON.parse(
|
||||
(
|
||||
await db.query.cache
|
||||
.findFirst({
|
||||
where: and(
|
||||
eq(cache.key, key),
|
||||
gt(cache.createdAt, fifteenMinutesAgo),
|
||||
),
|
||||
})
|
||||
.execute()
|
||||
)?.value ?? "null",
|
||||
);
|
||||
|
||||
if (!guilds) {
|
||||
const { data, error } = await discordApi(path, {
|
||||
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
||||
});
|
||||
|
||||
if (error) console.log("Discord api error:", { error });
|
||||
|
||||
guilds = data;
|
||||
|
||||
const set = {
|
||||
key,
|
||||
value: JSON.stringify(data),
|
||||
createdAt: currentDate,
|
||||
};
|
||||
await db.insert(cache).values(set).onConflictDoUpdate({
|
||||
set,
|
||||
target: cache.key,
|
||||
});
|
||||
console.log("To cache written.", consoleKey);
|
||||
} else {
|
||||
console.log("Cache value used!", consoleKey);
|
||||
}
|
||||
|
||||
return guilds;
|
||||
}
|
||||
|
||||
export async function guildChannels(guildId: bigint) {
|
||||
const path = "/guilds/{guild_id}/channels";
|
||||
const key = `${path}:${String(guildId)}`;
|
||||
const consoleKey = `${path.green}:${String(guildId).yellow}`;
|
||||
|
||||
const currentDate = new Date();
|
||||
const fifteenMinutesAgo = new Date(currentDate.getTime() - 15 * 60 * 1000);
|
||||
|
||||
let channels: Channels | undefined = JSON.parse(
|
||||
(
|
||||
await db.query.cache
|
||||
.findFirst({
|
||||
where: and(
|
||||
eq(cache.key, key),
|
||||
gt(cache.createdAt, fifteenMinutesAgo),
|
||||
),
|
||||
})
|
||||
.execute()
|
||||
)?.value ?? "null",
|
||||
);
|
||||
|
||||
if (!channels) {
|
||||
const { data, error } = await discordApi(path, {
|
||||
params: {
|
||||
path: {
|
||||
guild_id: String(guildId),
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bot ${import.meta.env.VITE_DISCORD_BOT_TOKEN}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (error) console.log("Discord api error:", { error });
|
||||
|
||||
channels = data;
|
||||
|
||||
const set = {
|
||||
key,
|
||||
value: JSON.stringify(data),
|
||||
createdAt: currentDate,
|
||||
};
|
||||
await db.insert(cache).values(set).onConflictDoUpdate({
|
||||
set,
|
||||
target: cache.key,
|
||||
});
|
||||
|
||||
console.log("To cache written.", consoleKey);
|
||||
} else {
|
||||
console.log("Cache value used!", consoleKey);
|
||||
}
|
||||
|
||||
return channels;
|
||||
}
|
|
@ -3,12 +3,11 @@ import { APIEvent } from "@solidjs/start/server";
|
|||
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";
|
||||
import { discordTokens, users } from "~/drizzle/schema";
|
||||
import { discord, lucia } from "~/lib/auth";
|
||||
import { paths } from "~/types/discord";
|
||||
import { discordApi } from "~/lib/cachedDiscord";
|
||||
|
||||
export async function GET(event: APIEvent): Promise<Response> {
|
||||
const url = new URL(event.request.url);
|
||||
|
@ -39,10 +38,7 @@ export async function GET(event: APIEvent): Promise<Response> {
|
|||
|
||||
try {
|
||||
const tokens = await discord.validateAuthorizationCode(code);
|
||||
const { GET } = createClient<paths>({
|
||||
baseUrl: "https://discord.com/api/v10",
|
||||
});
|
||||
const discordUserResponse = await GET("/users/@me", {
|
||||
const discordUserResponse = await discordApi("/users/@me", {
|
||||
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
||||
});
|
||||
if (discordUserResponse.error) throw discordUserResponse.error;
|
||||
|
|
|
@ -3,7 +3,6 @@ import { useLocation, useNavigate, useParams } from "@solidjs/router";
|
|||
import { eq } from "drizzle-orm";
|
||||
import { PgUpdateSetSource } from "drizzle-orm/pg-core";
|
||||
import moment from "moment-timezone";
|
||||
import createClient from "openapi-fetch";
|
||||
import {
|
||||
For,
|
||||
Index,
|
||||
|
@ -20,10 +19,9 @@ import { FontAwesomeIcon } from "~/components/FontAwesomeIcon";
|
|||
import Layout from "~/components/Layout";
|
||||
import db from "~/drizzle";
|
||||
import { guilds } from "~/drizzle/schema";
|
||||
import getAccessToken from "~/lib/accessToken";
|
||||
import { guildChannels, userGuilds } from "~/lib/cachedDiscord";
|
||||
import { combineInterval, splitInterval } from "~/lib/responseBuilders";
|
||||
import { zodBigIntId } from "~/lib/zod";
|
||||
import { paths } from "~/types/discord";
|
||||
import "../../styles/pages/config.scss";
|
||||
|
||||
if (typeof import.meta.env.VITE_DISCORD_BOT_TOKEN === "undefined")
|
||||
|
@ -65,42 +63,10 @@ const getPayload = async (
|
|||
const event = getRequestEvent();
|
||||
if (!event) return { success: false, message: "No request event!" };
|
||||
|
||||
const pathname = new URL(event.request.url).pathname;
|
||||
const { user } = event.nativeEvent.context;
|
||||
if (!user) return { success: false, message: "User not logged in!" };
|
||||
|
||||
const tokens = await getAccessToken(user.id);
|
||||
if (!tokens) return { success: false, message: "No discord access token!" };
|
||||
|
||||
const { GET } = createClient<paths>({
|
||||
baseUrl: "https://discord.com/api/v10",
|
||||
});
|
||||
const { data: guildsData, error } = await GET("/users/@me/guilds", {
|
||||
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
||||
});
|
||||
const { data: channelsData, error: error2 } = await GET(
|
||||
"/guilds/{guild_id}/channels",
|
||||
{
|
||||
params: {
|
||||
path: {
|
||||
guild_id: String(guildId),
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bot ${import.meta.env.VITE_DISCORD_BOT_TOKEN}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (error || error2) {
|
||||
console.log("Discord api error:", { error, error2 });
|
||||
console.log(error, error2, pathname);
|
||||
return {
|
||||
success: false,
|
||||
message: "Error on one of the discord api requests!",
|
||||
};
|
||||
}
|
||||
|
||||
const guildsData = await userGuilds(user.id);
|
||||
const guild = guildsData?.find((e) => e.id === String(guildId));
|
||||
|
||||
if (!guild)
|
||||
|
@ -115,6 +81,8 @@ const getPayload = async (
|
|||
"User is no MANAGE_GUILD permissions on this guild with requested id!",
|
||||
};
|
||||
|
||||
const channelsData = await guildChannels(guildId);
|
||||
|
||||
const channels: {
|
||||
id: string;
|
||||
name: string;
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
import { faPlusCircle, faWrench } from "@fortawesome/pro-regular-svg-icons";
|
||||
import { useLocation, useNavigate } from "@solidjs/router";
|
||||
import createClient from "openapi-fetch";
|
||||
import { For, Show, 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 { guilds } from "~/drizzle/schema";
|
||||
import getAccessToken from "~/lib/accessToken";
|
||||
import { paths } from "~/types/discord";
|
||||
import { userGuilds } from "~/lib/cachedDiscord";
|
||||
import "../../styles/pages/config.scss";
|
||||
|
||||
if (typeof import.meta.env.VITE_DISCORD_CLIENT_ID === "undefined")
|
||||
|
@ -38,31 +36,17 @@ const getPayload = async (): Promise<
|
|||
const event = getRequestEvent();
|
||||
if (!event) return { success: false, message: "No request event!" };
|
||||
|
||||
const pathname = new URL(event.request.url).pathname;
|
||||
const { user } = event.nativeEvent.context;
|
||||
if (!user) return { success: false, message: "User not logged in!" };
|
||||
|
||||
const tokens = await getAccessToken(user.id);
|
||||
if (!tokens) return { success: false, message: "No discord access token!" };
|
||||
|
||||
const { GET } = createClient<paths>({
|
||||
baseUrl: "https://discord.com/api/v10",
|
||||
});
|
||||
const { data, error } = await GET("/users/@me/guilds", {
|
||||
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
||||
});
|
||||
const configs = await db.select().from(guilds).execute();
|
||||
|
||||
if (error) {
|
||||
console.log("Discord api error:", { error });
|
||||
return { success: false, message: "Error on discord api request!" };
|
||||
}
|
||||
console.log(pathname, "success");
|
||||
const guildData = await userGuilds(user.id);
|
||||
|
||||
const joined: Guild[] = [];
|
||||
const canJoin: Guild[] = [];
|
||||
|
||||
data
|
||||
guildData
|
||||
?.filter((e) => parseInt(e.permissions) & (1 << 5))
|
||||
.forEach(({ id, name, icon }) => {
|
||||
configs.map((e) => e.id.toString()).includes(id)
|
||||
|
|
Loading…
Reference in a new issue