Working on oauth

This commit is contained in:
2023-12-10 00:25:57 +01:00
parent ce84fa376b
commit 7fe980e478
13 changed files with 190 additions and 85 deletions

View File

@@ -11,7 +11,7 @@ datasource db {
model Maintenance { model Maintenance {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
reason String reason String @default("")
beginAt DateTime @default(now()) beginAt DateTime @default(now())
endAt DateTime @default(now()) endAt DateTime @default(now())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@ -21,24 +21,24 @@ model Maintenance {
model Announcement { model Announcement {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
content String content String @default("")
} }
model Category { model Category {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
slug String slug String @default("")
name String name String @default("")
talents CategoriesOnTalents[] talents CategoriesOnTalents[]
} }
model Talent { model Talent {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
logo String logo String @default("")
name String @unique name String @unique @default("")
website String website String @default("")
work String work String @default("")
favorite Boolean @default(false) favorite Boolean @default(false)
categories CategoriesOnTalents[] categories CategoriesOnTalents[]
} }
@@ -56,24 +56,34 @@ model CategoriesOnTalents {
model Post { model Post {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
slug String @unique slug String @unique @default("")
createdAt DateTime @default(now()) createdAt DateTime @default(now())
views Int @default(0) views Int @default(0)
likes Int @default(0) likes Int @default(0)
} }
model Suggestion { model Suggestion {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
author String @unique email String @unique @default("")
content String content String @default("")
added Boolean @default(false) added Boolean @default(false)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
} }
model Form { model Form {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
name String name String @default("")
email String email String @default("")
content String content String @default("")
createdAt DateTime @default(now())
}
model GuestbookMessage {
id Int @id @default(autoincrement())
message String @default("")
email String @unique @default("")
image String @default("")
username String @default("")
updatedAt DateTime @updatedAt
createdAt DateTime @default(now()) createdAt DateTime @default(now())
} }

View File

@@ -36,9 +36,9 @@ const navs = [
icon: 'i-ph-shooting-star-bold', icon: 'i-ph-shooting-star-bold',
}, },
{ {
label: 'Bookmarks', label: 'Guestbook',
to: '/bookmarks', to: '/guestbook',
icon: 'i-ph-bookmarks-bold', icon: 'i-material-symbols-book-2-outline',
}, },
{ {
label: 'Contact', label: 'Contact',

View File

@@ -1,32 +0,0 @@
export async function usePost(slug: string) {
const {
data: post,
refresh: refreshPost,
} = useFetch('/api/article', {
method: 'POST',
body: {
slug,
},
})
const likes = ref(post.value?.likes)
const like = async () => {
const { data } = await useFetch('/api/like', { method: 'PUT' })
likes.value = data
}
const views = ref(post.value!.views)
const view = async () => {
const { data } = await useFetch('/api/view', { method: 'PUT' })
likes.value = data
}
return {
post,
like,
view,
refreshPost,
likes: computed(() => likes.value),
views: computed(() => views.value),
}
}

View File

@@ -4,6 +4,37 @@ import { providers } from '~~/types'
useHead({ useHead({
title: 'Sign my guestbook • Arthur Danjou', title: 'Sign my guestbook • Arthur Danjou',
}) })
const { loggedIn, clear , user} = useUserSession()
const { data: messages, refresh } = useFetch('/api/messages', { method: 'get' })
const toast = useToast()
const messageContent = ref<string>('')
async function sign() {
if (messageContent.value.length < 7 || messageContent.value.length > 58)
return
await $fetch('/api/message', {
method: 'post',
body: {
message: messageContent.value,
},
}).then(() => {
toast.add({
title: `Thank's for leaving a message!`,
icon: 'i-material-symbols-check-circle-outline-rounded',
timeout: 4000,
})
}).catch(() => {
toast.add({
title: 'An error occured when signing the book!',
color: 'red',
})
})
messageContent.value = ''
await refresh()
}
</script> </script>
<template> <template>
@@ -20,10 +51,35 @@ useHead({
<div class="flex items-center gap-2 mb-4"> <div class="flex items-center gap-2 mb-4">
<UIcon name="i-ph-circle-wavy-question-bold" class="text-subtitle text-xl" /> <UIcon name="i-ph-circle-wavy-question-bold" class="text-subtitle text-xl" />
<h1 class="text-lg font-bold"> <h1 class="text-lg font-bold">
Login to sign my book Want to sign my book ?
</h1> </h1>
</div> </div>
<div class="flex gap-2"> <div v-if="loggedIn" class="flex items-center justify-between gap-4">
<div class="w-full relative flex items-center">
<input
v-model="messageContent"
type="text"
required
min="7"
max="58"
class="w-full rounded-lg p-2 h-10 focus:outline-none bg-gray-100 dark:bg-stone-800"
placeholder="Leave a message"
>
<UButton
class="absolute right-1 top-1 text-gray-900 dark:text-white rounded-md"
label="Send"
:disabled="messageContent.trim().length < 7 || messageContent.trim().length > 58"
variant="soft"
@click.prevent="sign()"
/>
</div>
<UButton
@click.prevent="clear()"
>
Logout
</UButton>
</div>
<div v-else class="flex gap-2">
<UButton <UButton
v-for="provider in providers" v-for="provider in providers"
:key="provider.slug" :key="provider.slug"
@@ -31,9 +87,37 @@ useHead({
:color="provider.color" :color="provider.color"
variant="solid" variant="solid"
:icon="provider.icon" :icon="provider.icon"
:to="provider.link"
external
/> />
</div> </div>
</div> </div>
{{ user }}
<div v-if="messages" class="columns-1 md:columns-2 lg:columns-4 gap-8 space-y-8">
<div
v-for="message in messages"
:key="message.id"
class="overflow-hidden sm:p-6 px-4 py-5 border border-zinc-100 p-6 dark:border-zinc-700/40 rounded-lg"
>
<p class="text-sm text-subtitle">
{{ message.message }}
</p>
<div class="flex items-center gap-4 mt-4">
<div class="h-8 w-8 rounded-full">
<NuxtImg class="w-full h-full rounded-full" :src="message.image" alt="Nature" placeholder />
</div>
<p class="font-bold">
{{ message.username }}
</p>
</div>
</div>
</div>
<div v-else class="my-4 text-subtitle">
<div class="flex gap-2 items-center">
<UIcon name="i-eos-icons-loading" />
<p>The messages are loading...</p>
</div>
</div>
</section> </section>
</template> </template>

View File

@@ -1,4 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Category, Suggestion, Talent } from '@prisma/client'
import { useTalentsStore } from '~/store/talents' import { useTalentsStore } from '~/store/talents'
import { providers } from '~~/types' import { providers } from '~~/types'
@@ -13,7 +14,7 @@ const { loggedIn, clear } = useUserSession()
const { const {
data: talents, data: talents,
pending, pending,
} = await useFetch('/api/talents', { } = await useFetch<Array<Talent>>('/api/talents', {
method: 'get', method: 'get',
query: { query: {
favorite: isFavorite, favorite: isFavorite,
@@ -28,9 +29,8 @@ function isCategory(category: string) {
const { const {
data: getCategories, data: getCategories,
} = await useFetch('/api/categories', { method: 'GET' }) } = await useFetch<Array<Category>>('/api/categories', { method: 'GET' })
getCategories.value!.forEach((category: any) => categories.value.push({ label: category.name, slug: category.slug }))
getCategories.value?.forEach((category: any) => categories.value.push({ label: category.name, slug: category.slug }))
const appConfig = useAppConfig() const appConfig = useAppConfig()
function getColor() { function getColor() {
@@ -43,7 +43,7 @@ async function suggest() {
if (suggestContent.value.trim().length < 4) if (suggestContent.value.trim().length < 4)
return return
await $fetch('/api/suggestion', { await $fetch<Suggestion>('/api/suggestion', {
method: 'post', method: 'post',
body: { body: {
content: suggestContent.value, content: suggestContent.value,

View File

@@ -1,10 +1,30 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Post as PrismaPost } from '@prisma/client'
import type { Post } from '~~/types' import type { Post } from '~~/types'
const appConfig = useAppConfig() const appConfig = useAppConfig()
const route = useRoute() const route = useRoute()
const { data: postContent } = await useAsyncData<Post>(`writing:${route.params.slug}`, () => queryContent<Post>(`/writing/${route.params.slug}`).findOne()) const { data: postContent } = await useAsyncData<Post>(`writing:${route.params.slug}`, () => queryContent<Post>(`/writing/${route.params.slug}`).findOne())
const {
data: post,
} = await useFetch<PrismaPost>('/api/article', {
method: 'post',
body: {
slug: route.params.slug.toString(),
},
})
const likes = ref(post.value?.likes)
async function like() {
const data = await $fetch<PrismaPost>('/api/like', {
method: 'PUT',
body: {
slug: post.value?.slug,
},
})
likes.value = data.likes
}
if (!postContent.value) { if (!postContent.value) {
throw showError({ throw showError({
@@ -13,10 +33,7 @@ if (!postContent.value) {
}) })
} }
const { post, view, like, likes, views } = await usePost(route.params.slug.toString())
const format = (date: string) => useDateFormat(date, 'D MMMM YYYY').value.replaceAll('"', '') const format = (date: string) => useDateFormat(date, 'D MMMM YYYY').value.replaceAll('"', '')
onMounted(() => view())
useHead({ useHead({
title: `${postContent.value?.title} • Arthur Danjou's shelf`, title: `${postContent.value?.title} • Arthur Danjou's shelf`,
}) })
@@ -38,10 +55,6 @@ const likeCookie = useCookie<boolean>(`post:like:${postContent.value.slug}`, {
maxAge: 604_800, maxAge: 604_800,
}) })
const isLiked = computed(() => {
return likeCookie.value === true
})
async function handleLike() { async function handleLike() {
await like() await like()
likeCookie.value = true likeCookie.value = true
@@ -72,7 +85,7 @@ async function handleLike() {
<span></span> <span></span>
<div>{{ postContent.readingMins }} min</div> <div>{{ postContent.readingMins }} min</div>
<span></span> <span></span>
<div>{{ views }} {{ views > 1 ? 'views' : 'view' }}</div> <div>{{ post.views }} {{ post.views > 1 ? 'views' : 'view' }}</div>
</div> </div>
</time> </time>
<h1 class="text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl"> <h1 class="text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl">
@@ -107,15 +120,7 @@ async function handleLike() {
</p> </p>
<div class="flex gap-4 flex-wrap"> <div class="flex gap-4 flex-wrap">
<UButton <UButton
v-if="isLiked" :label="`${likes} ${likes! > 1 ? 'likes' : 'like'}`"
:label="`${likes} ${likes > 1 ? 'likes' : 'like'}`"
icon="i-ph-heart-bold"
size="lg"
variant="solid"
/>
<UButton
v-else
:label="`${likes} ${likes > 1 ? 'likes' : 'like'}`"
icon="i-ph-heart-bold" icon="i-ph-heart-bold"
size="lg" size="lg"
variant="soft" variant="soft"

View File

@@ -8,7 +8,11 @@ export default defineEventHandler(async (event) => {
where: { where: {
slug, slug,
}, },
update: {}, update: {
views: {
increment: 1,
},
},
create: { create: {
slug, slug,
}, },

View File

@@ -0,0 +1,24 @@
import { z } from 'zod'
const MessageValidator = z.object({
message: z.string(),
}).parse
export default defineEventHandler(async (event) => {
const { message } = await readValidatedBody(event, MessageValidator)
const { user } = await requireUserSession(event)
return await usePrisma().guestbookMessage.upsert({
where: {
email: user.email,
},
update: {
message,
},
create: {
email: user.email,
image: user.picture,
username: user.username,
message,
},
})
})

View File

@@ -0,0 +1,7 @@
export default defineEventHandler(async () => {
return await usePrisma().guestbookMessage.findMany({
orderBy: {
updatedAt: 'desc',
},
})
})

View File

@@ -9,13 +9,13 @@ export default defineEventHandler(async (event) => {
const { user } = await requireUserSession(event) const { user } = await requireUserSession(event)
return await usePrisma().suggestion.upsert({ return await usePrisma().suggestion.upsert({
where: { where: {
author: user.email, email: user.email,
}, },
update: { update: {
content, content,
}, },
create: { create: {
author: user.email, email: user.email,
content, content,
}, },
}) })

View File

@@ -1,10 +1,13 @@
export default oauth.githubEventHandler({ export default oauth.googleEventHandler({
config: {
redirectUrl: '/talents',
},
async onSuccess(event: any, { user }: any) { async onSuccess(event: any, { user }: any) {
await setUserSession(event, { await setUserSession(event, {
user: { user: {
email: user.email, email: user.email,
picture: user.photoUrl, picture: user.picture,
username: String(user.displayName).trim(), username: String(user.name).trim(),
}, },
}) })
return sendRedirect(event, '/') return sendRedirect(event, '/')

View File

@@ -9,6 +9,7 @@ https://atinux.com/
https://yael.dev/ https://yael.dev/
https://esm.dev/ https://esm.dev/
https://antfu.me/ https://antfu.me/
tom lienard
Categories: Categories:
BRAND BRAND

View File

@@ -1,4 +1,3 @@
import exp from 'node:constants'
import type { MarkdownParsedContent, ParsedContent } from '@nuxt/content/dist/runtime/types' import type { MarkdownParsedContent, ParsedContent } from '@nuxt/content/dist/runtime/types'
export enum ColorsTheme { export enum ColorsTheme {