feat: prepare db stuff for the new api
All checks were successful
Lint Codebase / lint (push) Successful in 56s
All checks were successful
Lint Codebase / lint (push) Successful in 56s
This commit is contained in:
parent
57e5f69bc8
commit
36ac57378a
200
lib/Database.ts
200
lib/Database.ts
|
@ -1,58 +1,8 @@
|
|||
import { TeamRole } from '@/utils/permissions';
|
||||
import mysql, { Pool, QueryResult } from 'mysql2/promise';
|
||||
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
interface DiscordTokenData {
|
||||
token_type: string;
|
||||
access_token: string;
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
scope: string;
|
||||
id_token: string;
|
||||
}
|
||||
|
||||
interface UserTable {
|
||||
id: number;
|
||||
did: string;
|
||||
username: string;
|
||||
global_name: string;
|
||||
email: string;
|
||||
avatar?: string;
|
||||
banner?: string;
|
||||
accent_color?: number;
|
||||
permissions: number;
|
||||
}
|
||||
|
||||
interface UserTableWithoutEmail {
|
||||
id: number;
|
||||
did: string;
|
||||
username: string;
|
||||
global_name: string;
|
||||
avatar?: string;
|
||||
banner?: string;
|
||||
accent_color?: number;
|
||||
permissions: number;
|
||||
}
|
||||
|
||||
interface TeamMember {
|
||||
id: number;
|
||||
uid: number;
|
||||
did: string;
|
||||
username: string;
|
||||
global_name: string;
|
||||
email: string;
|
||||
avatar: string;
|
||||
banner: string;
|
||||
accent_color: number;
|
||||
permissions: number;
|
||||
role: TeamRole;
|
||||
}
|
||||
|
||||
class Database {
|
||||
static instance: (Database | null) = null;
|
||||
|
||||
mysqlPool: (Pool | null) = null;
|
||||
private mysqlPool: (Pool | null) = null;
|
||||
|
||||
constructor() {
|
||||
if(Database.instance)
|
||||
|
@ -71,154 +21,6 @@ class Database {
|
|||
|
||||
Database.instance = this;
|
||||
}
|
||||
|
||||
// Auth Stuff
|
||||
|
||||
async createUser(user: any): Promise<number> {
|
||||
const [ result ] = await this.mysqlPool!.execute('SELECT * FROM users WHERE did = ?', [ user.id ]);
|
||||
|
||||
if ((result as any).length > 0) {
|
||||
return (result as any)[0].id;
|
||||
}
|
||||
|
||||
const [ res ] = await this.mysqlPool!.execute('INSERT INTO users (did, username, global_name, email, avatar, banner, accent_color) VALUES (?, ?, ?, ?, ?, ?, ?)', [
|
||||
user.id,
|
||||
user.username,
|
||||
(user.global_name || user.username),
|
||||
user.email,
|
||||
user.avatar,
|
||||
user.banner,
|
||||
user.accent_color
|
||||
]);
|
||||
|
||||
return (res as any).insertId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a session for the user.
|
||||
*
|
||||
* Should only be called by backend!
|
||||
*
|
||||
* @param userData The data returned from the `/oauth2/token` endpoint.
|
||||
* @param userAgent The user agent of the user.
|
||||
*
|
||||
* @returns `string` The session token.
|
||||
*/
|
||||
async createSession(userData: DiscordTokenData, userAgent?: string): Promise<string | null> {
|
||||
const res = await fetch(`${process.env.DISCORD_API}/users/@me`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${userData.access_token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const user = await res.json();
|
||||
const uid = await this.createUser(user);
|
||||
|
||||
const sum = crypto.createHmac('sha256', process.env.AUTH_SECRET!);
|
||||
const base = Buffer.from(user.id).toString('base64').replaceAll('=', '');
|
||||
const date = Buffer.from((Date.now() - 1688940000000).toString()).toString('base64').replaceAll('=', '');
|
||||
|
||||
sum.update(userData.access_token);
|
||||
|
||||
const sid = base + '.' + date + '.' + sum.digest('hex');
|
||||
|
||||
const [ result ] = await this.mysqlPool!.execute('INSERT INTO sessions (sid, uid, access_token, refresh_token, id_token, user_agent, expires) VALUES (?, ?, ?, ?, ?, ?, ?)', [
|
||||
sid,
|
||||
uid,
|
||||
userData.access_token,
|
||||
userData.refresh_token,
|
||||
userData.id_token,
|
||||
userAgent,
|
||||
new Date(Date.now() + (userData.expires_in * 1000)),
|
||||
]);
|
||||
|
||||
return sid;
|
||||
} else {
|
||||
throw new Error('Error Fetching Discord User Data');
|
||||
}
|
||||
}
|
||||
|
||||
async deleteSession(sid: string): Promise<void> {
|
||||
await this.mysqlPool!.execute('DELETE FROM sessions WHERE sid = ?', [ sid ]);
|
||||
}
|
||||
|
||||
async getSession(sid: string): Promise<any> {
|
||||
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM sessions WHERE sid = ?', [ sid ]);
|
||||
|
||||
return (rows as any)[0];
|
||||
}
|
||||
|
||||
// End of Auth Stuff
|
||||
|
||||
async getUser(id: number): Promise<UserTable> {
|
||||
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM users WHERE id = ?', [ id ]);
|
||||
|
||||
return (rows as UserTable[])[0];
|
||||
}
|
||||
|
||||
async getUsers(): Promise<UserTableWithoutEmail[]> {
|
||||
const [ rows ] = await this.mysqlPool!.execute('SELECT id, did, username, global_name, avatar, banner, accent_color, permissions FROM users');
|
||||
|
||||
return rows as UserTableWithoutEmail[];
|
||||
}
|
||||
|
||||
async getUserUsername(username: string): Promise<UserTable> {
|
||||
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM users WHERE username = ?', [ username ]);
|
||||
|
||||
return (rows as UserTable[])[0];
|
||||
}
|
||||
|
||||
async getUserSessions(uid: number): Promise<any> {
|
||||
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM sessions WHERE uid = ?', [ uid ]);
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
async getTeam(): Promise<TeamMember[]> {
|
||||
const [ rows ] = await this.mysqlPool!.execute('SELECT team_members.id AS tid, team_members.uid AS uid, users.did, users.username, users.global_name, users.email, users.avatar, users.banner, users.accent_color, users.permissions, team_members.role FROM team_members JOIN users ON team_members.uid = users.id;');
|
||||
|
||||
return (rows as any).map((row: any) => {
|
||||
return {
|
||||
id: row.tid,
|
||||
uid: row.uid,
|
||||
did: row.did,
|
||||
username: row.username,
|
||||
global_name: row.global_name,
|
||||
email: row.email,
|
||||
avatar: row.avatar,
|
||||
banner: row.banner,
|
||||
accent_color: row.accent_color,
|
||||
permissions: row.permissions,
|
||||
role: (row.role as TeamRole),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async getNations(): Promise<any> {
|
||||
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM nations');
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
async getNationCode(code: string): Promise<any> {
|
||||
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM nations WHERE code = ?', [ code ]);
|
||||
|
||||
return (rows as any)[0];
|
||||
}
|
||||
|
||||
async getCompaies(nid: number): Promise<any> {
|
||||
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM companies WHERE nid = ?', [ nid ]);
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
async updateUserPermissions(id: number, permissions: any) {
|
||||
await this.mysqlPool!.execute('UPDATE users SET permissions = ? WHERE id = ?', [ permissions, id ]);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export default Database;
|
22
lib/Discord.ts
Normal file
22
lib/Discord.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
class Discord {
|
||||
private token: string;
|
||||
|
||||
constructor(token: string) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
async user(): Promise<{ ok: boolean, data?: any }> {
|
||||
const res = await fetch(`${process.env.DISCORD_API}/users/@me`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
ok: res.ok,
|
||||
data: res.ok ? await res.json() : null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default Discord;
|
|
@ -19,6 +19,7 @@
|
|||
"lucide-react": "^0.429.0",
|
||||
"mysql2": "^3.11.0",
|
||||
"next": "14.2.6",
|
||||
"nodejs-snowflake": "^2.0.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"showdown": "^2.1.0",
|
||||
|
|
|
@ -38,6 +38,9 @@ importers:
|
|||
next:
|
||||
specifier: 14.2.6
|
||||
version: 14.2.6(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
|
||||
nodejs-snowflake:
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1
|
||||
react:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
|
@ -2470,6 +2473,9 @@ packages:
|
|||
node-releases@2.0.18:
|
||||
resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==}
|
||||
|
||||
nodejs-snowflake@2.0.1:
|
||||
resolution: {integrity: sha512-zMfDorNNsJm1OWWx/OUUGVT0bQ3TwC2ti4URD8UjnpffVLtLpy3eBDIaV5TnLs91YrRhwBD6L0StqU6YtnZt9A==}
|
||||
|
||||
nopt@5.0.0:
|
||||
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -5525,7 +5531,7 @@ snapshots:
|
|||
eslint: 8.57.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0)
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
|
||||
eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0)
|
||||
eslint-plugin-react: 7.35.0(eslint@8.57.0)
|
||||
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0)
|
||||
|
@ -5556,7 +5562,7 @@ snapshots:
|
|||
is-bun-module: 1.1.0
|
||||
is-glob: 4.0.3
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
|
||||
transitivePeerDependencies:
|
||||
- '@typescript-eslint/parser'
|
||||
- eslint-import-resolver-node
|
||||
|
@ -5574,7 +5580,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0):
|
||||
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0):
|
||||
dependencies:
|
||||
array-includes: 3.1.8
|
||||
array.prototype.findlastindex: 1.2.5
|
||||
|
@ -6260,6 +6266,8 @@ snapshots:
|
|||
|
||||
node-releases@2.0.18: {}
|
||||
|
||||
nodejs-snowflake@2.0.1: {}
|
||||
|
||||
nopt@5.0.0:
|
||||
dependencies:
|
||||
abbrev: 1.1.1
|
||||
|
|
8
utils/snowflake.ts
Normal file
8
utils/snowflake.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { Snowflake } from 'nodejs-snowflake';
|
||||
|
||||
const snowflake = new Snowflake({
|
||||
custom_epoch: new Date('2023-07-10T00:00:00Z').getTime(),
|
||||
instance_id: 1
|
||||
});
|
||||
|
||||
export default snowflake;
|
52
utils/token_util.ts
Normal file
52
utils/token_util.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import crypto from 'node:crypto';
|
||||
|
||||
import snowflake from './snowflake';
|
||||
|
||||
import Discord from '@/lib/Discord';
|
||||
|
||||
interface DiscordTokenData {
|
||||
token_type: string;
|
||||
access_token: string;
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
scope: string;
|
||||
id_token: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a session token.
|
||||
*
|
||||
* @param oauthData The data returned from the `/oauth2/token` endpoint.
|
||||
*
|
||||
* @returns The session token.
|
||||
*/
|
||||
export async function createSessionToken(oauthData: DiscordTokenData): Promise<string> {
|
||||
const discord = new Discord(oauthData.access_token);
|
||||
const res = await discord.user();
|
||||
|
||||
if (!res.ok)
|
||||
throw new Error('Failed to fetch user data from Discord.');
|
||||
|
||||
const dcUser = res.data;
|
||||
|
||||
const hmac = crypto
|
||||
.createHmac('sha256', process.env.AUTH_SECRET!)
|
||||
.update(dcUser.access_token)
|
||||
.digest('hex');
|
||||
|
||||
const tuid = Buffer
|
||||
.from(dcUser.id)
|
||||
.toString('base64')
|
||||
.replaceAll('=', '');
|
||||
|
||||
const tdid = Buffer
|
||||
.from(
|
||||
snowflake
|
||||
.getUniqueID()
|
||||
.toString()
|
||||
)
|
||||
.toString('base64')
|
||||
.replaceAll('=', '');
|
||||
|
||||
return `${tuid}.${tdid}.${hmac}`;
|
||||
}
|
Loading…
Reference in a new issue