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:
Louis Hollingworth 2023-05-16 20:17:33 +01:00
parent f9e7e5480c
commit 14659122c3
Signed by: lucxjo
GPG key ID: A11415CB3DC7809B
17 changed files with 375 additions and 56 deletions

View file

@ -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
View 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
View file

@ -0,0 +1,2 @@
export default defineNuxtRouteMiddleware((to, from) => {
})

View file

@ -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: {

View file

@ -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
View 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>

View file

@ -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
View 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>

View file

@ -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">

View file

@ -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">

View file

@ -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>

View file

@ -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

View file

@ -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)
}

View file

@ -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) => {
};

View 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')
})

View 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)
})

View file

@ -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}`,