This commit is contained in:
2023-08-16 23:07:33 +02:00
parent d97278c344
commit dc4107ac82
13 changed files with 1544 additions and 2258 deletions

View File

@@ -14,7 +14,7 @@
"@nuxt/content": "2.7.2", "@nuxt/content": "2.7.2",
"@nuxt/image": "^0.7.1", "@nuxt/image": "^0.7.1",
"@pinia/nuxt": "0.4.11", "@pinia/nuxt": "0.4.11",
"@prisma/client": "^5.0.0", "@prisma/client": "^5.1.1",
"@trpc/client": "10.35.0", "@trpc/client": "10.35.0",
"@trpc/server": "^10.35.0", "@trpc/server": "^10.35.0",
"@vercel/analytics": "^1.0.1", "@vercel/analytics": "^1.0.1",
@@ -25,7 +25,7 @@
"superjson": "^1.13.1", "superjson": "^1.13.1",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"trpc-nuxt": "^0.10.6", "trpc-nuxt": "^0.10.6",
"zod": "^3.21.4" "zod": "3.22.1"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "0.39.8", "@antfu/eslint-config": "0.39.8",
@@ -38,7 +38,7 @@
"@vueuse/nuxt": "^10.2.1", "@vueuse/nuxt": "^10.2.1",
"eslint": "^8.45.0", "eslint": "^8.45.0",
"nuxt": "^3.6.5", "nuxt": "^3.6.5",
"prisma": "^5.0.0", "prisma": "^5.1.1",
"typescript": "5.1.6" "typescript": "5.1.6"
} }
} }

View File

@@ -57,5 +57,13 @@ model Post {
slug String @unique slug String @unique
createdAt DateTime @default(now()) createdAt DateTime @default(now())
views Int @default(0) views Int @default(0)
title String likes Int @default(0)
}
model Form {
id Int @id @default(autoincrement())
name String
email String
content String
createdAt DateTime @default(now())
} }

View File

@@ -4,7 +4,7 @@ function findHashPosition(hash: any): { el: any; behavior: ScrollBehavior; top:
const el = document.querySelector(hash) const el = document.querySelector(hash)
// vue-router does not incorporate scroll-margin-top on its own. // vue-router does not incorporate scroll-margin-top on its own.
if (el) { if (el) {
const top = parseFloat(getComputedStyle(el).scrollMarginTop) const top = Number.parseFloat(getComputedStyle(el).scrollMarginTop)
return { return {
el: hash, el: hash,

View File

@@ -16,7 +16,7 @@ const year = computed(() => new Date().getFullYear())
</div> </div>
<p class="text-subtitle flex items-center"> <p class="text-subtitle flex items-center">
Made with Made with
<UButton variant="link" color="green" label="Nuxt 3" to="https://nuxt.com/" target="_blank" icon="i-vscode-icons-file-type-nuxt" trailing/> <UButton variant="link" color="green" label="Nuxt 3" to="https://nuxt.com/" target="_blank" icon="i-vscode-icons-file-type-nuxt" trailing />
</p> </p>
</div> </div>
</footer> </footer>

View File

@@ -14,13 +14,13 @@ function getColor() {
</script> </script>
<template> <template>
<section class="md:border-l md:border-zinc-100 md:pl-6 md:dark:border-zinc-700/40 mb-16"> <section class="md:border-l md:border-zinc-100 md:pl-6 md:dark:border-zinc-700/40 mb-24">
<div class="grid max-w-3xl grid-cols-1 items-baseline gap-y-8 md:grid-cols-4"> <div class="grid max-w-3xl grid-cols-1 items-baseline gap-y-8 md:grid-cols-4">
<h2 class="text-sm font-semibold" :class="getColor()"> <h2 class="text-sm font-semibold" :class="getColor()">
{{ title }} {{ title }}
</h2> </h2>
<div class="md:col-span-3"> <div class="md:col-span-3">
<ul class="space-y-16"> <ul class="space-y-8">
<slot /> <slot />
</ul> </ul>
</div> </div>

View File

@@ -0,0 +1,31 @@
export async function usePost(slug: string) {
const { $trpc } = useNuxtApp()
const {
data: post,
refresh: refreshPost,
} = await useAsyncData(`blog:post-db:${slug}`, async () => await $trpc.post.createOrUpdate.mutate({
slug,
}))
const likes = ref(post.value!.likes)
const like = async () => {
const data = await $trpc.post.like.mutate({ slug })
likes.value = data.likes
}
const views = ref(post.value!.views)
const view = async () => {
const data = await $trpc.post.view.mutate({ slug })
views.value = data.views
}
return {
post,
like,
view,
refreshPost,
likes: computed(() => likes.value),
views: computed(() => views.value),
}
}

View File

@@ -4,10 +4,15 @@ import type { Post } from '../../../types'
const route = useRoute() const route = useRoute()
const { data: postContent } = await useAsyncData<Post>(`writing:${route.params.slug}`, async () => await queryContent<Post>(`/writing/${route.params.slug}`).findOne()) const { data: postContent } = await useAsyncData<Post>(`writing:${route.params.slug}`, async () => await queryContent<Post>(`/writing/${route.params.slug}`).findOne())
const { post, view, like, likes, views } = await usePost(route.params.slug.toString())
onMounted(() => {
view()
})
useHead({ useHead({
title: `${postContent.value?.title} — Arthur Danjou's shelf`, title: `${postContent.value?.title} — Arthur Danjou's shelf`,
}) })
function top() { function top() {
window.scrollTo({ window.scrollTo({
top: 0, top: 0,
@@ -25,7 +30,7 @@ const router = useRouter()
</script> </script>
<template> <template>
<section v-if="postContent"> <section v-if="postContent && post">
<div class="w-container lg:mt-24 mt-16"> <div class="w-container lg:mt-24 mt-16">
<div class="lg:relative"> <div class="lg:relative">
<div class="max-w-3xl space-y-8 mx-auto"> <div class="max-w-3xl space-y-8 mx-auto">
@@ -49,7 +54,7 @@ const router = useRouter()
<span></span> <span></span>
<div>{{ postContent.readingMins }} min</div> <div>{{ postContent.readingMins }} min</div>
<span></span> <span></span>
<div>x views</div> <div>{{ views }} views</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">
@@ -67,17 +72,17 @@ const router = useRouter()
dark:prose dark:prose-invert dark:leading-6 dark:max-w-none dark:prose-table:w-full dark:md:prose-table:w-3/4 dark:lg:prose-table:w-2/5" dark:prose dark:prose-invert dark:leading-6 dark:max-w-none dark:prose-table:w-full dark:md:prose-table:w-3/4 dark:lg:prose-table:w-2/5"
:value="postContent || undefined" :value="postContent || undefined"
/> />
<footer class="my-8 space-y-4"> <footer class="my-8 space-y-8">
<p class="text-subtitle"> <p class="text-subtitle">
Thanks for reading this post! If you liked it, please consider sharing it with your friends. <strong>Don't forget to leave a like!</strong> Thanks for reading this post! If you liked it, please consider sharing it with your friends. <strong>Don't forget to leave a like!</strong>
</p> </p>
<div class="flex gap-4 flex-wrap"> <div class="flex gap-4 flex-wrap">
<UButton <UButton
label="x" :label="`${likes} likes`"
icon="i-ph-heart-bold" icon="i-ph-heart-bold"
size="lg" size="lg"
variant="soft" variant="soft"
@click.prevent="" @click.prevent="like()"
/> />
<UButton <UButton
label="Go to top" label="Go to top"

View File

@@ -1,7 +1,7 @@
import { publicProcedure, router } from '~/server/trpc/trpc' import { publicProcedure, router } from '~/server/trpc/trpc'
export default router({ export default router({
announcement: publicProcedure get: publicProcedure
.query(async ({ ctx }) => { .query(async ({ ctx }) => {
return await ctx.prisma.announcement.findFirst({ return await ctx.prisma.announcement.findFirst({
orderBy: { orderBy: {

View File

@@ -1,18 +1,14 @@
import { z } from 'zod'
import announcement from './announcement' import announcement from './announcement'
import { publicProcedure, router } from '~/server/trpc/trpc' import maintenance from './maintenance'
import talents from './talents'
import post from './post'
import { router } from '~/server/trpc/trpc'
export const appRouter = router({ export const appRouter = router({
hello: publicProcedure
.input(z.object({
name: z.string().optional(),
}))
.query(({ input }) => {
return {
greeting: `Hello ${input.name ?? 'World'}!`,
}
}),
announcement, announcement,
post,
talents,
maintenance,
}) })
export type AppRouter = typeof appRouter export type AppRouter = typeof appRouter

View File

@@ -0,0 +1,22 @@
import { publicProcedure, router } from '~/server/trpc/trpc'
export default router({
get: publicProcedure
.query(async ({ ctx }) => {
return await ctx.prisma.maintenance.findFirst({
orderBy: {
createdAt: 'desc',
},
})
}),
is: publicProcedure
.query(async ({ ctx }) => {
const maintenance = await ctx.prisma.maintenance.findFirst({
orderBy: {
createdAt: 'desc',
},
})
const today = new Date()
return !!maintenance && maintenance.beginAt.getTime() < today.getTime() && maintenance.endAt.getTime() > today.getTime()
}),
})

View File

@@ -1,3 +1,68 @@
export default defineEventHandler((event) => { import { z } from 'zod'
return 'Hello post' import { publicProcedure, router } from '~/server/trpc/trpc'
const PostSchema = z.object({
slug: z.string(),
})
export default router({
createOrUpdate: publicProcedure
.input(PostSchema)
.mutation(async ({ ctx, input }) => {
return await ctx.prisma.post.upsert({
where: {
slug: input.slug,
},
update: {},
create: {
slug: input.slug,
},
})
}),
getTotalViews: publicProcedure
.query(async ({ ctx }) => {
const views = await ctx.prisma.post.aggregate({
_sum: {
views: true,
},
})
return views._sum.views || 0
}),
view: publicProcedure
.input(PostSchema)
.mutation(async ({ ctx, input }) => {
return await ctx.prisma.post.update({
where: {
slug: input.slug,
},
data: {
views: {
increment: 1,
},
},
})
}),
getTotalLikes: publicProcedure
.query(async ({ ctx }) => {
const likes = await ctx.prisma.post.aggregate({
_sum: {
likes: true,
},
})
return likes._sum.likes || 0
}),
like: publicProcedure
.input(PostSchema)
.mutation(async ({ ctx, input }) => {
return await ctx.prisma.post.update({
where: {
slug: input.slug,
},
data: {
likes: {
increment: 1,
},
},
})
}),
}) })

View File

@@ -0,0 +1,68 @@
import { z } from 'zod'
import { publicProcedure, router } from '~/server/trpc/trpc'
export default router({
getTalents: publicProcedure
.input(z.object({
favorite: z.boolean(),
category: z.union([z.string(), z.literal('all')]),
}))
.query(async ({ ctx, input }) => {
if (input.favorite) {
return input.category === 'all'
? await ctx.prisma.talent.findMany({
orderBy: {
createdAt: 'desc',
},
include: {
categories: true,
},
where: {
favorite: true,
categories: { every: { category: {} } },
},
})
: await ctx.prisma.talent.findMany({
orderBy: {
createdAt: 'desc',
},
include: {
categories: true,
},
where: {
favorite: true,
categories: { some: { category: { slug: input.category } } },
},
})
}
else {
return input.category === 'all'
? await ctx.prisma.talent.findMany({
orderBy: {
createdAt: 'desc',
},
include: {
categories: true,
},
where: {
categories: { every: { category: {} } },
},
})
: await ctx.prisma.talent.findMany({
orderBy: {
createdAt: 'desc',
},
include: {
categories: true,
},
where: {
categories: { some: { category: { slug: input.category } } },
},
})
}
}),
getCategories: publicProcedure
.query(async ({ ctx }) => {
return await ctx.prisma.category.findMany()
}),
})

3551
yarn.lock

File diff suppressed because it is too large Load Diff