Merge pull request #12 from TTTaevas/sql2
Use Bun's `SQL` over the `mongodb` package
This commit is contained in:
commit
5e09b7ba77
8 changed files with 93 additions and 109 deletions
|
@ -24,6 +24,7 @@ Set the environment variable IBM_TELEMETRY_DISABLED to true
|
|||
|
||||
This website makes use of several online APIs in order to deliver the `Infos` that are available on the right side of the main page. Accessing most of these APIs requires a key (or similar), which can be set through the following environment variables (with dotenv support for development):
|
||||
|
||||
- `URL_POSTGRESQL`
|
||||
- `API_GITHUB`
|
||||
- `API_GITLAB`
|
||||
- `API_KITSUCLUB`
|
||||
|
@ -32,4 +33,3 @@ This website makes use of several online APIs in order to deliver the `Infos` th
|
|||
- `API_WANIKANI`
|
||||
- `USERNAME_UMAMI`
|
||||
- `PASSWORD_UMAMI`
|
||||
- `URL_MONGODB`
|
||||
|
|
|
@ -1,17 +1,10 @@
|
|||
import * as osu from "osu-api-v2-js";
|
||||
import {type OsuInfo} from "#Infos/Gaming/Osu.tsx";
|
||||
import {MongoClient} from "mongodb";
|
||||
import {type Token} from "./token.tsx";
|
||||
import type { Handler } from "../index.ts";
|
||||
import { db, getToken } from "../database.ts";
|
||||
|
||||
export const gaming_osu: Handler = async (params) => {
|
||||
const client = new MongoClient(process.env["URL_MONGODB"]!);
|
||||
await client.connect();
|
||||
|
||||
const db = client.db("tokens");
|
||||
const collection = db.collection<Token>("osu");
|
||||
const token = await collection.findOne();
|
||||
void client.close();
|
||||
const token = await getToken(db, "osu");
|
||||
|
||||
let ruleset = params.has("ruleset") ? Number(params.get("ruleset")) : undefined;
|
||||
if (ruleset && isNaN(ruleset)) {ruleset = undefined;}
|
||||
|
|
100
api/token.ts
100
api/token.ts
|
@ -1,95 +1,43 @@
|
|||
import {MongoClient, type InsertOneResult} from "mongodb";
|
||||
import { addToken, createTables, db, getToken, removeExpiredTokens } from "../database";
|
||||
import {API} from "osu-api-v2-js";
|
||||
import type { Handler } from "..";
|
||||
|
||||
const allowed_services = ["osu", "umami"];
|
||||
|
||||
export interface Token {
|
||||
access_token: string;
|
||||
expires: Date;
|
||||
expires: number;
|
||||
service: string;
|
||||
}
|
||||
|
||||
export const token: Handler = async (params) => {
|
||||
const service = params.get("service");
|
||||
if (!service) {
|
||||
if (!service || !allowed_services.includes(service)) {
|
||||
return new Response("Bad Request", {status: 400});
|
||||
}
|
||||
|
||||
const client = new MongoClient(process.env["URL_MONGODB"]!);
|
||||
await client.connect();
|
||||
|
||||
const db = client.db("tokens");
|
||||
const collection = db.collection<Token>(service);
|
||||
const tokens = await collection.find().toArray();
|
||||
|
||||
const now = new Date();
|
||||
const token = tokens.find((t) => t.expires > now);
|
||||
const expiredTokens = tokens.filter((t) => now > t.expires);
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
await createTables(db);
|
||||
removeExpiredTokens(db);
|
||||
const token = await getToken(db, service);
|
||||
|
||||
if (!token) {
|
||||
const collections = await db.listCollections().toArray();
|
||||
if (!collections.find((c) => c.name === service)) {client.close(); return new Response("Not Found", {status: 404});}
|
||||
if (service === "osu") {
|
||||
const api = await API.createAsync(11451, process.env["API_OSU"]!);
|
||||
await addToken(db, {access_token: api.access_token, service: "osu", expires: api.expires});
|
||||
}
|
||||
|
||||
promises.push(new Promise(async (resolve, reject) => {
|
||||
console.log(`Setting a new token for ${service}...`);
|
||||
let insertion: InsertOneResult;
|
||||
|
||||
if (service === "osu") {
|
||||
const api = await API.createAsync(11451, process.env["API_OSU"]!);
|
||||
insertion = await collection.insertOne({
|
||||
access_token: api.access_token,
|
||||
expires: api.expires,
|
||||
});
|
||||
}
|
||||
|
||||
else if (service === "umami") {
|
||||
const response = await fetch("https://visitors.taevas.xyz/api/auth/login", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
},
|
||||
body: `username=${process.env["USERNAME_UMAMI"]}&password=${process.env["PASSWORD_UMAMI"]}`
|
||||
});
|
||||
const json: {token: string} = await response.json();
|
||||
|
||||
// Assume it expires in one day
|
||||
const date = new Date();
|
||||
date.setHours(date.getHours() + 24);
|
||||
insertion = await collection.insertOne({
|
||||
access_token: json.token,
|
||||
expires: date,
|
||||
});
|
||||
}
|
||||
|
||||
else {
|
||||
console.error(`Service "${service}" doesn't exist! Unable to set a new token...`);
|
||||
return reject();
|
||||
}
|
||||
|
||||
console.log(`New ${service} token in the database: ${insertion.insertedId.toString()}`);
|
||||
resolve();
|
||||
}));
|
||||
if (service === "umami") {
|
||||
const response = await fetch("https://visitors.taevas.xyz/api/auth/login", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
},
|
||||
body: `username=${process.env["USERNAME_UMAMI"]}&password=${process.env["PASSWORD_UMAMI"]}`
|
||||
});
|
||||
const json: {token: string} = await response.json();
|
||||
await addToken(db, {access_token: json.token, service: "umami"});
|
||||
}
|
||||
}
|
||||
|
||||
if (expiredTokens.length) {
|
||||
promises.push(new Promise(async (resolve) => {
|
||||
console.log(`Deleting old tokens for ${service}...`);
|
||||
await Promise.all(expiredTokens.map(async (t) => {
|
||||
return new Promise<void>(async (resolve) => {
|
||||
const deletion = await collection.deleteOne({_id: t._id});
|
||||
if (deletion.deletedCount) {
|
||||
console.log(`Old ${service} token deleted from the database: ${t._id.toString()}`);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
}));
|
||||
resolve();
|
||||
}));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
void client.close();
|
||||
|
||||
return new Response(null, {status: 200});
|
||||
};
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
import { MongoClient } from "mongodb";
|
||||
import type { Handler } from "../index.ts";
|
||||
import type { UmamiInfo } from "#Infos/Website/Umami.tsx";
|
||||
import type { Token } from "./token.ts";
|
||||
import { db, getToken } from "../database.ts";
|
||||
|
||||
export const website_umami: Handler = async () => {
|
||||
const client = new MongoClient(process.env["URL_MONGODB"]!);
|
||||
await client.connect();
|
||||
|
||||
const db = client.db("tokens");
|
||||
const collection = db.collection<Token>("umami");
|
||||
const token = await collection.findOne();
|
||||
void client.close();
|
||||
const token = await getToken(db, "umami");
|
||||
|
||||
const api_server = "https://visitors.taevas.xyz/api";
|
||||
const website_id = "f196d626-e609-4841-9a80-0dc60f523ed5";
|
||||
|
|
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
53
database.ts
Normal file
53
database.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { SQL } from "bun";
|
||||
import type { Token } from "./api/token";
|
||||
|
||||
export const db = new SQL({url: process.env["URL_POSTGRESQL"]});
|
||||
|
||||
export const createTables = async (database: SQL): Promise<void> => {
|
||||
return await database.begin(sql => sql`
|
||||
CREATE TABLE IF NOT EXISTS tokens (
|
||||
access_token text NOT NULL,
|
||||
service text NOT NULL,
|
||||
expires bigserial NOT NULL
|
||||
)
|
||||
`);
|
||||
};
|
||||
|
||||
export const removeExpiredTokens = async (database: SQL): Promise<number> => {
|
||||
const now = new Date();
|
||||
const deleted_tokens: Token[] = await database.begin(sql => sql`
|
||||
DELETE FROM tokens
|
||||
WHERE expires <= ${Number(now)}
|
||||
RETURNING *
|
||||
`);
|
||||
|
||||
deleted_tokens.forEach(token => console.log("(DATABASE)", token.service, "token had expired on", new Date(Number(token.expires)), "and has been removed!"));
|
||||
return deleted_tokens.length;
|
||||
};
|
||||
|
||||
export const addToken = async (database: SQL, token: {access_token: string, service: string, expires?: Date}): Promise<Token> => {
|
||||
if (!token.expires) {
|
||||
// Assume it expires in one day
|
||||
token.expires = new Date();
|
||||
token.expires.setHours(token.expires.getHours() + 24);
|
||||
}
|
||||
|
||||
const returned: Token[] = await database.begin(sql => sql`
|
||||
INSERT INTO tokens (access_token, service, expires)
|
||||
VALUES (${token.access_token}, ${token.service}, ${Number(token.expires)})
|
||||
RETURNING *
|
||||
`);
|
||||
|
||||
returned.forEach(token => console.log("(DATABASE)", token.service, "token has been added"));
|
||||
return returned[0];
|
||||
};
|
||||
|
||||
export const getToken = async (database: SQL, service: string): Promise<Token> => {
|
||||
const now = new Date();
|
||||
const tokens: Token[] = await database.begin(sql => sql`
|
||||
SELECT * FROM tokens
|
||||
WHERE service = ${service}
|
||||
AND expires > ${Number(now)}
|
||||
`);
|
||||
return tokens[Math.floor(Math.random() * tokens.length)];
|
||||
};
|
2
index.ts
2
index.ts
|
@ -51,7 +51,6 @@ const api_endpoints: Handler[] = [
|
|||
|
||||
const servers: Server[] = ports.map((port) => Bun.serve({
|
||||
idleTimeout: 30,
|
||||
// @ts-expect-error https://github.com/oven-sh/bun/issues/17772
|
||||
tls: port !== 80 ? tls : undefined,
|
||||
port,
|
||||
fetch: async (req) => {
|
||||
|
@ -106,4 +105,3 @@ const servers: Server[] = ports.map((port) => Bun.serve({
|
|||
|
||||
servers.forEach((server) => console.log(`Listening on ${server.hostname}:${server.port}`));
|
||||
console.log("\n\n--------\n\n");
|
||||
|
||||
|
|
23
package.json
23
package.json
|
@ -7,35 +7,34 @@
|
|||
"start": "bun getready && bun run index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bachmacintosh/wanikani-api-types": "^1.7.0",
|
||||
"@carbon/icons-react": "^11.56.0",
|
||||
"@bachmacintosh/wanikani-api-types": "^1.8.0",
|
||||
"@carbon/icons-react": "^11.57.0",
|
||||
"@gitbeaker/rest": "^42.1.0",
|
||||
"@octokit/rest": "^20.1.2",
|
||||
"mongodb": "^6.14.2",
|
||||
"osu-api-v2-js": "^1.1.1",
|
||||
"osu-api-v2-js": "^1.1.2",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"timeago.js": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.0",
|
||||
"@eslint/js": "^9.22.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^9.23.0",
|
||||
"@stylistic/eslint-plugin": "^3.1.0",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/postcss": "^4.0.13",
|
||||
"@tailwindcss/postcss": "^4.0.15",
|
||||
"@types/bun": "latest",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react": "^19.0.12",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"dotenv": "^16.4.7",
|
||||
"eslint": "^9.22.0",
|
||||
"eslint": "^9.23.0",
|
||||
"eslint-config-xo-typescript": "^7.0.0",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"postcss": "^8.5.3",
|
||||
"react-animate-height": "^3.2.3",
|
||||
"tailwindcss": "^4.0.13",
|
||||
"tailwindcss": "^4.0.15",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.26.1",
|
||||
"vite": "^6.2.1"
|
||||
"typescript-eslint": "^8.27.0",
|
||||
"vite": "^6.2.2"
|
||||
},
|
||||
"imports": {
|
||||
"#Main/*": "./src/Main/*",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue