diff --git a/docs/cdn.md b/docs/cdn.md new file mode 100644 index 0000000..9b0b537 --- /dev/null +++ b/docs/cdn.md @@ -0,0 +1,25 @@ +# CDN Url Structure + +Base URL: `https://crss.fra1.cdn.digitaloceanspaces.com/` + +## Users + +### Avatars + +`users/[id]/avatar/[hash].png` + +### Banners + +`users/[id]/banner/[hash].png` + +## Nations + +### Flags + +`nations/[id]/flag/[hash].svg` + +## Gallery + +### Images + +`gallery/[id]/[hash].png` \ No newline at end of file diff --git a/interfaces/user.ts b/interfaces/user.ts index f3ae404..cd57f0a 100644 --- a/interfaces/user.ts +++ b/interfaces/user.ts @@ -14,7 +14,9 @@ export interface User { accentColor?: number; - discordId: BigInt; + discordId?: BigInt; + + subscription: number; permissions: PermissionNamed[]; badges: BadgeNamed[]; diff --git a/lib/Database.ts b/lib/Database.ts index 6a31f45..bdedb55 100644 --- a/lib/Database.ts +++ b/lib/Database.ts @@ -58,6 +58,8 @@ class Database { discordId: row.discord_id as BigInt, + subscription: row.subscription, + permissions: getPermissions(row.permissions), badges: getBadges(row.badges), @@ -96,6 +98,8 @@ class Database { discordId: row.discord_id as BigInt, + subscription: row.subscription, + permissions: getPermissions(row.permissions), badges: getBadges(row.badges), @@ -127,6 +131,8 @@ class Database { discordId: row.discord_id as BigInt, + subscription: row.subscription, + permissions: getPermissions(row.permissions), badges: getBadges(row.badges), @@ -170,6 +176,8 @@ class Database { discordId: user.discord as BigInt, + subscription: user.subscription, + permissions: getPermissions(user.permissions), badges: getBadges(user.badges), @@ -223,6 +231,20 @@ class Database { return token; } + async getSession(token: string): Promise { + const [ rows ] = await this.mysqlPool!.query('SELECT * FROM user_sessions WHERE token = ?', [ token ]); + + if((rows as any[]).length === 0) + return undefined; + + const row = (rows as any[])[0]; + + if(new Date(row.expires_at) < new Date()) + return undefined; + + return row; + } + // Meta ---------------- async getTeam(): Promise<(TeamMember | undefined)[]> { diff --git a/lib/Storage.ts b/lib/Storage.ts new file mode 100644 index 0000000..15cfebf --- /dev/null +++ b/lib/Storage.ts @@ -0,0 +1,38 @@ +import { ObjectCannedACL, PutObjectCommand, PutObjectCommandOutput, S3Client } from '@aws-sdk/client-s3'; + +import { Readable } from 'node:stream'; + +class S3Storage { + private client; + + // change this if it's different for you :3 + private bucket: string = 'crss'; + + constructor() { + this.client = new S3Client({ + forcePathStyle: false, + + region: process.env.S3_REGION!, + endpoint: process.env.S3_ENDPOINT!, + + credentials: { + accessKeyId: process.env.S3_ACCESS_KEY!, + secretAccessKey: process.env.S3_SECRET_KEY! + } + }); + } + + async uploadFile(key: string, file: (string | Uint8Array | Buffer | Readable), contetnType: string): Promise { + const res = await this.client.send(new PutObjectCommand({ + Bucket: this.bucket, + Key: key, + Body: file, + ContentType: contetnType, + ACL: ObjectCannedACL.public_read + })); + + return res; + } +} + +export default S3Storage; \ No newline at end of file diff --git a/pages/api/v1/auth.ts b/pages/api/v1/auth.ts index 1de3897..0b0276b 100644 --- a/pages/api/v1/auth.ts +++ b/pages/api/v1/auth.ts @@ -1,4 +1,6 @@ import Database from '@/lib/Database'; +import Discord from '@/lib/Discord'; +import S3Storage from '@/lib/Storage'; import type { NextApiRequest, NextApiResponse } from 'next'; export default async function handler( @@ -6,6 +8,7 @@ export default async function handler( res: NextApiResponse, ) { const db = new Database(); + const s3 = new S3Storage(); const { code, state } = req.query; @@ -29,7 +32,7 @@ export default async function handler( }); const json = await data.json(); - const token = await db.newSession(json, req.headers['user-agent']!, req.socket.remoteAddress!); + const token = await db.newSession(json, req.headers['user-agent']!, (req.headers['x-forwarded-for'] as string || req.socket.remoteAddress!)); if (!token) { return res.status(500).json({ @@ -41,6 +44,29 @@ export default async function handler( res.status(200).json({ token }); + + try { + const dc = new Discord(json.access_token); + const dcUser = await dc.user(); + + const user = await db.getUser((await db.getSession(token)).user_id); + + if (dcUser.data.avatar) { + const fetchData = await fetch(`https://cdn.discordapp.com/avatars/${dcUser.data.id}/${dcUser.data.avatar}.png?size=1024`); + const buffer = await fetchData.arrayBuffer(); + + await s3.uploadFile(`users/${user?.id}/avatar/${dcUser.data.avatar}.png`, Buffer.from(buffer), 'image/png'); + } + + if (dcUser.data.banner) { + const fetchData = await fetch(`https://cdn.discordapp.com/banners/${dcUser.data.id}/${dcUser.data.banner}.png?size=1024`); + const buffer = await fetchData.arrayBuffer(); + + await s3.uploadFile(`users/${user?.id}/banner/${dcUser.data.banner}.png`, Buffer.from(buffer), 'image/png'); + } + } catch (e) { + console.error(e); + } } catch (e) { console.error(e); diff --git a/pages/api/v1/user/@me.ts b/pages/api/v1/user/@me.ts new file mode 100644 index 0000000..f540fd1 --- /dev/null +++ b/pages/api/v1/user/@me.ts @@ -0,0 +1,45 @@ +import { ErrorResponse, User } from '@/interfaces'; +import Database from '@/lib/Database'; + +import type { NextApiRequest, NextApiResponse } from 'next'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + const db = new Database(); + + const { authorization } = req.headers; + + const token = authorization?.split(' ')!; + + if (!token) + return res.status(401).json({ + code: 401, + message: 'Unauthorized' + }); + + if (token[0] !== 'Bearer' || !token[1]) + return res.status(401).json({ + code: 401, + message: 'Unauthorized' + }); + + const session = await db.getSession(token[1]); + + if (!session) + return res.status(401).json({ + code: 401, + message: 'Unauthorized' + }); + + const user = await db.getUser(session.user_id); + + if (!user) + return res.status(401).json({ + code: 401, + message: 'Unauthorized' + }); + + res.status(200).json(user); +} \ No newline at end of file diff --git a/pages/api/v1/user/@me/session/[id].ts b/pages/api/v1/user/@me/session/[id].ts new file mode 100644 index 0000000..e69de29 diff --git a/pages/api/v1/user/@me/sessions.ts b/pages/api/v1/user/@me/sessions.ts new file mode 100644 index 0000000..e69de29 diff --git a/pages/api/v1/user/[id].ts b/pages/api/v1/user/[id].ts new file mode 100644 index 0000000..fb8bf97 --- /dev/null +++ b/pages/api/v1/user/[id].ts @@ -0,0 +1,53 @@ +import { ErrorResponse, User } from '@/interfaces'; + +import Database from '@/lib/Database'; + +import type { NextApiRequest, NextApiResponse } from 'next'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + const db = new Database(); + + const { id } = req.query; + const { authorization } = req.headers; + + if ((/^\d+$/).test(id as string)) { + let user = await db.getUser((id as string)); + + if (!user) + return res.status(404).json({ + code: 404, + message: 'User Not Found' + }); + + // TODO: check if user is admin or itself and show email and discordId + user = { + ...user, + + email: undefined, + discordId: undefined, + }; + + res.status(200).json(user); + } else { + let user: any = await db.getUserUsername(id as string); + + if (!user) + return res.status(404).json({ + code: 404, + message: 'User Not Found' + }); + + // TODO: check if user is admin or itself and show email and discordId + user = { + ...user, + + email: undefined, + discordId: undefined + }; + + res.status(200).json(user); + } +} \ No newline at end of file