Birthdays
diff --git a/pages/utilities/index.vue b/pages/utilities/index.vue
index c665ed3..d300ed7 100644
--- a/pages/utilities/index.vue
+++ b/pages/utilities/index.vue
@@ -1,6 +1,7 @@
-
+
+
Vänner Bästa
diff --git a/pages/utilities/time.vue b/pages/utilities/time.vue
index e0a9f95..f1aabbe 100644
--- a/pages/utilities/time.vue
+++ b/pages/utilities/time.vue
@@ -1,6 +1,7 @@
-
+
+
Time Utilities
Your current timezone: {{ timezone }}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b54ba65..18ea990 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4,9 +4,15 @@ dependencies:
'@prisma/client':
specifier: 4.14.0
version: 4.14.0(prisma@4.14.0)
+ jose:
+ specifier: 4.14.4
+ version: 4.14.4
scss:
specifier: 0.2.4
version: 0.2.4
+ zod:
+ specifier: 3.21.4
+ version: 3.21.4
devDependencies:
'@nuxtjs/color-mode':
@@ -15,6 +21,9 @@ devDependencies:
'@nuxtjs/tailwindcss':
specifier: 6.6.7
version: 6.6.7(webpack@5.82.0)
+ '@types/jsonwebtoken':
+ specifier: 9.0.2
+ version: 9.0.2
nuxt:
specifier: 3.4.3
version: 3.4.3(@types/node@20.1.1)
@@ -1072,6 +1081,12 @@ packages:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true
+ /@types/jsonwebtoken@9.0.2:
+ resolution: {integrity: sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==}
+ dependencies:
+ '@types/node': 20.1.1
+ dev: true
+
/@types/node@20.1.1:
resolution: {integrity: sha512-uKBEevTNb+l6/aCQaKVnUModfEMjAl98lw2Si9P5y4hLu9tm6AlX2ZIoXZX6Wh9lJueYPrGPKk5WMCNHg/u6/A==}
dev: true
@@ -3186,6 +3201,10 @@ packages:
hasBin: true
dev: true
+ /jose@4.14.4:
+ resolution: {integrity: sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==}
+ dev: false
+
/js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
dev: true
@@ -5890,3 +5909,7 @@ packages:
compress-commons: 4.1.1
readable-stream: 3.6.2
dev: true
+
+ /zod@3.21.4:
+ resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
+ dev: false
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index f40347c..b44ba8b 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -49,3 +49,14 @@ model sites {
site_url String @db.VarChar
pages pages[]
}
+
+model user {
+ id String @id @default(uuid())
+ name String
+ discord_id String @unique
+ created_at DateTime @default(now())
+ updated_at DateTime @updatedAt
+ is_admin Boolean @default(false)
+ is_staff Boolean @default(false)
+ is_member Boolean @default(false)
+}
\ No newline at end of file
diff --git a/server/index.ts b/server/index.ts
index 883edbe..6a85cdb 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -1,8 +1,8 @@
-const runtimeConfig = useRuntimeConfig();
+export const runtimeConfig = useRuntimeConfig();
import { PrismaClient } from '@prisma/client';
import { Nitro } from 'nitropack';
-const prisma = new PrismaClient();
+export const prisma = new PrismaClient();
export default async (_nitroApp: Nitro) => {
};
\ No newline at end of file
diff --git a/server/routes/auth/v1/callback.ts b/server/routes/auth/v1/callback.ts
new file mode 100644
index 0000000..925c0b8
--- /dev/null
+++ b/server/routes/auth/v1/callback.ts
@@ -0,0 +1,228 @@
+import { runtimeConfig, prisma } from '~/server/index'
+import * as jose from 'jose'
+import { z } from 'zod';
+
+const PartialUserGuildSchema = z.object({
+ id: z.string(),
+ name: z.string(),
+ icon: z.string().nullable(),
+ owner: z.boolean(),
+ permissions: z.number(),
+ features: z.array(z.string()),
+});
+
+const UserGuildSchema = z.object({
+ avatar: z.string().nullable(),
+ communication_disabled_until: z.string().nullable(),
+ flags: z.number(),
+ joined_at: z.string(),
+ nick: z.string().nullable(),
+ pending: z.boolean(),
+ premium_since: z.string().nullable(),
+ roles: z.array(z.string()),
+ user: z.object({
+ id: z.string(),
+ username: z.string(),
+ global_name: z.string().nullable(),
+ avatar: z.string().nullable(),
+ discriminator: z.string(),
+ public_flags: z.number(),
+ avatar_decoration: z.string().nullable(),
+ }),
+ mute: z.boolean(),
+ deaf: z.boolean(),
+});
+
+export default defineEventHandler(async (event) => {
+ const { code } = getQuery(event)
+ let create_user = false
+
+ if (!code) {
+ return sendRedirect(event, '/')
+ }
+
+ const response = await fetch("https://discord.com/api/oauth2/token", {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Accept-Encoding': 'application/x-www-form-urlencoded'
+ },
+ body: new URLSearchParams({
+ client_id: '1105533416377688174',
+ client_secret: '7FdJcfSNvhXpsR_46N-ZnbNoyjV3bl1o',
+ grant_type: 'authorization_code',
+ code: (code as string),
+ redirect_uri: 'http://localhost:3000/auth/v1/callback'
+ })
+ });
+
+ const data: {
+ access_token: string,
+ expires_in: number,
+ refresh_token: string,
+ scope: string,
+ token_type: string
+ } = await response.json();
+
+ const userReq = await fetch("https://discord.com/api/users/@me", {
+ headers: {
+ Authorization: `${data.token_type} ${data.access_token}`,
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Accept-Encoding': 'application/x-www-form-urlencoded'
+ }
+ });
+
+ const user: {
+ id: string,
+ username: string,
+ discriminator: string,
+ } = await userReq.json();
+
+ create_user = await prisma.user.findUnique({
+ where: {
+ discord_id: user.id
+ }
+ }).then((u) => create_user = !u)
+
+ const guilds = await fetch("https://discord.com/api/users/@me/guilds", {
+ headers: {
+ Authorization: `${data.token_type} ${data.access_token}`,
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Accept-Encoding': 'application/x-www-form-urlencoded'
+ }
+ }).then(async (res) => PartialUserGuildSchema.array().parse(await res.json()));
+
+ const guild = guilds.find((g) => g.id === runtimeConfig.discordGuildId);
+
+ if (guild) {
+ prisma.user.update({
+ where: {
+ discord_id: user.id
+ },
+ data: {
+ is_member: true
+ }
+ })
+ const member = await fetch(`https://discord.com/api/users/@me/guilds/${guild.id}/member`, {
+ headers: {
+ Authorization: `${data.token_type} ${data.access_token}`,
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Accept-Encoding': 'application/x-www-form-urlencoded'
+ }
+ }).then((res) => res.json()).then((res) => UserGuildSchema.parse(res))
+
+ if (member.roles.find((r) => r === runtimeConfig.discordAdminRoleId)) {
+ console.log('admin')
+
+ if (create_user) {
+ prisma.user.create({
+ data: {
+ discord_id: user.id,
+ name: user.username + '#' + user.discriminator,
+ is_admin: true,
+ is_staff: true,
+ is_member: true
+ }
+ })
+ } else {
+ prisma.user.update({
+ where: {
+ discord_id: user.id
+ },
+ data: {
+ is_admin: true,
+ is_staff: true,
+ is_member: true
+ }
+ })
+ }
+ } else if (member.roles.find((r) => r === runtimeConfig.discordStaffRoleId)) {
+ console.log('staff')
+
+ if (create_user) {
+ prisma.user.create({
+ data: {
+ discord_id: user.id,
+ name: user.username + '#' + user.discriminator,
+ is_admin: false,
+ is_staff: true,
+ is_member: true
+ }
+ })
+ } else {
+ prisma.user.update({
+ where: {
+ discord_id: user.id
+ },
+ data: {
+ is_admin: false,
+ is_staff: true,
+ is_member: true
+ }
+ })
+ }
+ } else {
+ if (create_user) {
+ prisma.user.create({
+ data: {
+ discord_id: user.id,
+ name: user.username + '#' + user.discriminator,
+ is_admin: false,
+ is_staff: false,
+ is_member: true
+ }
+ })
+ } else {
+ prisma.user.update({
+ where: {
+ discord_id: user.id
+ },
+ data: {
+ is_admin: false,
+ is_staff: false,
+ is_member: true
+ }
+ })
+ }
+ }
+ } else {
+ console.log('not member')
+
+ if (create_user) {
+ prisma.user.create({
+ data: {
+ discord_id: user.id,
+ name: user.username + '#' + user.discriminator,
+ is_admin: false,
+ is_staff: false,
+ is_member: false
+ }
+ })
+ } else {
+ prisma.user.update({
+ where: {
+ discord_id: user.id
+ },
+ data: {
+ is_admin: false,
+ is_staff: false,
+ is_member: false
+ }
+ })
+ }
+ }
+
+ const secret = new TextEncoder().encode(runtimeConfig.jwtSecret)
+
+ const token = await new jose.SignJWT({ sub: user.id })
+ .setProtectedHeader({ alg: 'HS512' })
+ .setIssuedAt()
+ .setIssuer('https://vannerba.st')
+ .setAudience('https://vannerba.st')
+ .setExpirationTime('5h')
+ .sign(secret)
+
+ setCookie(event, 'token', token)
+
+ return sendRedirect(event, '/dash')
+})
\ No newline at end of file
diff --git a/server/routes/auth/v1/discord.ts b/server/routes/auth/v1/discord.ts
new file mode 100644
index 0000000..6f1cfd7
--- /dev/null
+++ b/server/routes/auth/v1/discord.ts
@@ -0,0 +1,5 @@
+const url = `https://discord.com/api/oauth2/authorize?client_id=1105533416377688174&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fv1%2Fcallback&response_type=code&scope=identify%20guilds%20guilds.members.read`
+
+export default defineEventHandler((event) => {
+ return sendRedirect(event, url)
+})
\ No newline at end of file
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 589f5ae..d5d4d81 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -2,6 +2,16 @@ import type { Config } from 'tailwindcss'
export default
>{
theme: {
+ extend: {
+ spacing: {
+ 'screen-4/5': '80vh',
+ 'screen-1/5': '20vh',
+ 'screen-1/10': '10vh',
+ 'screen-9/10': '90vh',
+ 'screen-1/20': '5vh',
+ 'screen-19/20': '95vh',
+ },
+ }
},
content: [
`./components/**/*.{vue,js,ts}`,