All pages now in Nuxt, work next on nav.
DB connected, check issues with writing. Signed-off-by: Louis Hollingworth <louis@hollingworth.ch>
This commit is contained in:
parent
f9e7e5480c
commit
14659122c3
50
README.md
50
README.md
|
@ -1,49 +1,3 @@
|
|||
# Welcome to [Astro](https://astro.build)
|
||||
# Friends Best / Vänner Bästa
|
||||
|
||||
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
![basics](https://user-images.githubusercontent.com/4677417/186188965-73453154-fdec-4d6b-9c34-cb35c248ae5b.png)
|
||||
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```
|
||||
/
|
||||
├── public/
|
||||
│ └── favicon.svg
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ │ └── Card.astro
|
||||
│ ├── layouts/
|
||||
│ │ └── Layout.astro
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
|
||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :--------------------- | :------------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` |
|
||||
| `npm run astro --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||
A young royals fan website made for the [Young Royals](https://discord.gg/youngroyals) Discord guild.
|
||||
|
|
32
components/NavHeader.vue
Normal file
32
components/NavHeader.vue
Normal file
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<div class="w-screen grid-cols-4 grid-flow-col">
|
||||
<div class="px-4 col-span-1" v-if="user">
|
||||
<span>Hej {{ user.user_metadata.full_name }} </span> |
|
||||
<button class="underline" @click="logout" >Logout</button>
|
||||
</div>
|
||||
<div class="nav" v-if="links">
|
||||
<a href="/">Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
links: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
nav {
|
||||
margin: 0 auto;
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
2
middleware/auth.ts
Normal file
2
middleware/auth.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export default defineNuxtRouteMiddleware((to, from) => {
|
||||
})
|
|
@ -4,7 +4,14 @@ export default defineNuxtConfig({
|
|||
//plugins: ['~/server/index.ts'],
|
||||
},
|
||||
runtimeConfig: {
|
||||
mongoUri: process.env.MONGO_URI || 'mongodb://localhost:27017/friends-best',
|
||||
dbUri: process.env.DATABASE_URL || '',
|
||||
discordClientId: process.env.DISCORD_CID || '',
|
||||
discordClientSecret: process.env.DISCORD_CS || '',
|
||||
discordRedirectUri: process.env.DISCORD_REDIRECT_URI || 'http://localhost:3000/auth/v1/callback',
|
||||
discordGuildId: process.env.DISCORD_GUILD || '',
|
||||
discordAdminRoleId: process.env.DISCORD_ADMIN_ROLE || '',
|
||||
discordStaffRoleId: process.env.DISCORD_STAFF_ROLE || '',
|
||||
jwtSecret: process.env.JWT_SECRET || 'not_so_secret',
|
||||
},
|
||||
modules: ["@nuxtjs/tailwindcss", "@nuxtjs/color-mode"],
|
||||
colorMode: {
|
||||
|
|
|
@ -10,12 +10,15 @@
|
|||
"devDependencies": {
|
||||
"@nuxtjs/color-mode": "3.2.0",
|
||||
"@nuxtjs/tailwindcss": "6.6.7",
|
||||
"@types/jsonwebtoken": "9.0.2",
|
||||
"nuxt": "3.4.3",
|
||||
"prisma": "4.14.0",
|
||||
"tailwindcss": "3.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "4.14.0",
|
||||
"scss": "0.2.4"
|
||||
"jose": "4.14.4",
|
||||
"scss": "0.2.4",
|
||||
"zod": "3.21.4"
|
||||
}
|
||||
}
|
||||
|
|
22
pages/dash/index.vue
Normal file
22
pages/dash/index.vue
Normal file
|
@ -0,0 +1,22 @@
|
|||
<script setup lang="ts">
|
||||
const router = useRouter()
|
||||
definePageMeta({
|
||||
middleware: 'auth'
|
||||
})
|
||||
|
||||
async function logout() {
|
||||
router.push('/login')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid h-screen place-items-center">
|
||||
<div class="grid place-items-center">
|
||||
<h1 class="text-6xl font-bold underline">Vänner Bästa | Dash</h1>
|
||||
<div class="mt-10 mx-4 place-items-center">
|
||||
<p>Logged in as {{ user?.user_metadata?.full_name }}</p>
|
||||
<button @click="logout" >Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<div class="grid h-screen place-items-center">
|
||||
<NavHeader :links="false" class="h-screen-1/20" />
|
||||
<div class="grid h-screen-19/20 place-items-center">
|
||||
<div class="grid place-items-center">
|
||||
<h1 class="text-6xl font-bold underline">Vänner Bästa</h1>
|
||||
<div class="mt-10 mx-4 place-items-center">
|
||||
|
@ -14,6 +15,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
const links: Array<{href: string, title: string}> = [
|
||||
{
|
||||
href: "https://netflix.com",
|
||||
|
@ -34,6 +36,10 @@ const links: Array<{href: string, title: string}> = [
|
|||
{
|
||||
href: "/utilities",
|
||||
title: "Discord Utilities"
|
||||
},
|
||||
{
|
||||
href: "/dash",
|
||||
title: "Dashboard"
|
||||
}
|
||||
]
|
||||
</script>
|
13
pages/login.vue
Normal file
13
pages/login.vue
Normal file
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
<div class="grid h-screen place-items-center">
|
||||
<div class="grid place-items-center">
|
||||
<h1 class="text-6xl font-bold underline">Vänner Bästa | Login</h1>
|
||||
<div class="mt-10 mx-4 place-items-center">
|
||||
<a href="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" >Login with Discord</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="grid h-screen place-items-center">
|
||||
<NavHeader class="h-screen-1/20" />
|
||||
<div class="grid h-screen-19/20 place-items-center">
|
||||
<div class="grid place-items-center">
|
||||
<h1 class="text-6xl font-bold underline p-4">Birthdays</h1>
|
||||
<p class="p-4">
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="grid h-screen place-items-center">
|
||||
<NavHeader class="h-screen-1/20" />
|
||||
<div class="grid h-screen-19/20 place-items-center">
|
||||
<div class="grid place-items-center">
|
||||
<h1 class="text-6xl font-bold underline">Vänner Bästa</h1>
|
||||
<div class="grid grid-cols-2 mt-10">
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="grid h-screen place-items-center">
|
||||
<NavHeader class="h-screen-1/20" />
|
||||
<div class="grid h-screen-19/20 place-items-center">
|
||||
<div class="grid place-items-center">
|
||||
<h1 class="text-6xl font-bold underline p-4">Time Utilities</h1>
|
||||
<p class="p-4" >Your current timezone: {{ timezone }}</p>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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) => {
|
||||
};
|
228
server/routes/auth/v1/callback.ts
Normal file
228
server/routes/auth/v1/callback.ts
Normal file
|
@ -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')
|
||||
})
|
5
server/routes/auth/v1/discord.ts
Normal file
5
server/routes/auth/v1/discord.ts
Normal file
|
@ -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)
|
||||
})
|
|
@ -2,6 +2,16 @@ import type { Config } from 'tailwindcss'
|
|||
|
||||
export default <Partial<Config>>{
|
||||
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}`,
|
||||
|
|
Loading…
Reference in a new issue