mirror of
https://github.com/ArthurDanjou/website.git
synced 2026-01-14 12:14:42 +01:00
work
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
"@nuxt/content": "2.7.2",
|
||||
"@nuxt/image": "^0.7.1",
|
||||
"@pinia/nuxt": "0.4.11",
|
||||
"@prisma/client": "^5.0.0",
|
||||
"@prisma/client": "^5.1.1",
|
||||
"@trpc/client": "10.35.0",
|
||||
"@trpc/server": "^10.35.0",
|
||||
"@vercel/analytics": "^1.0.1",
|
||||
@@ -25,7 +25,7 @@
|
||||
"superjson": "^1.13.1",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"trpc-nuxt": "^0.10.6",
|
||||
"zod": "^3.21.4"
|
||||
"zod": "3.22.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "0.39.8",
|
||||
@@ -38,7 +38,7 @@
|
||||
"@vueuse/nuxt": "^10.2.1",
|
||||
"eslint": "^8.45.0",
|
||||
"nuxt": "^3.6.5",
|
||||
"prisma": "^5.0.0",
|
||||
"prisma": "^5.1.1",
|
||||
"typescript": "5.1.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,5 +57,13 @@ model Post {
|
||||
slug String @unique
|
||||
createdAt DateTime @default(now())
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ function findHashPosition(hash: any): { el: any; behavior: ScrollBehavior; top:
|
||||
const el = document.querySelector(hash)
|
||||
// vue-router does not incorporate scroll-margin-top on its own.
|
||||
if (el) {
|
||||
const top = parseFloat(getComputedStyle(el).scrollMarginTop)
|
||||
const top = Number.parseFloat(getComputedStyle(el).scrollMarginTop)
|
||||
|
||||
return {
|
||||
el: hash,
|
||||
|
||||
@@ -16,7 +16,7 @@ const year = computed(() => new Date().getFullYear())
|
||||
</div>
|
||||
<p class="text-subtitle flex items-center">
|
||||
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>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -14,13 +14,13 @@ function getColor() {
|
||||
</script>
|
||||
|
||||
<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">
|
||||
<h2 class="text-sm font-semibold" :class="getColor()">
|
||||
{{ title }}
|
||||
</h2>
|
||||
<div class="md:col-span-3">
|
||||
<ul class="space-y-16">
|
||||
<ul class="space-y-8">
|
||||
<slot />
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
31
src/composables/usePost.ts
Normal file
31
src/composables/usePost.ts
Normal 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),
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,15 @@ import type { Post } from '../../../types'
|
||||
const route = useRoute()
|
||||
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({
|
||||
title: `${postContent.value?.title} — Arthur Danjou's shelf`,
|
||||
})
|
||||
|
||||
function top() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
@@ -25,7 +30,7 @@ const router = useRouter()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section v-if="postContent">
|
||||
<section v-if="postContent && post">
|
||||
<div class="w-container lg:mt-24 mt-16">
|
||||
<div class="lg:relative">
|
||||
<div class="max-w-3xl space-y-8 mx-auto">
|
||||
@@ -49,7 +54,7 @@ const router = useRouter()
|
||||
<span>•</span>
|
||||
<div>{{ postContent.readingMins }} min</div>
|
||||
<span>•</span>
|
||||
<div>x views</div>
|
||||
<div>{{ views }} views</div>
|
||||
</div>
|
||||
</time>
|
||||
<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"
|
||||
:value="postContent || undefined"
|
||||
/>
|
||||
<footer class="my-8 space-y-4">
|
||||
<footer class="my-8 space-y-8">
|
||||
<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>
|
||||
</p>
|
||||
<div class="flex gap-4 flex-wrap">
|
||||
<UButton
|
||||
label="x"
|
||||
:label="`${likes} likes`"
|
||||
icon="i-ph-heart-bold"
|
||||
size="lg"
|
||||
variant="soft"
|
||||
@click.prevent=""
|
||||
@click.prevent="like()"
|
||||
/>
|
||||
<UButton
|
||||
label="Go to top"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { publicProcedure, router } from '~/server/trpc/trpc'
|
||||
|
||||
export default router({
|
||||
announcement: publicProcedure
|
||||
get: publicProcedure
|
||||
.query(async ({ ctx }) => {
|
||||
return await ctx.prisma.announcement.findFirst({
|
||||
orderBy: {
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import { z } from 'zod'
|
||||
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({
|
||||
hello: publicProcedure
|
||||
.input(z.object({
|
||||
name: z.string().optional(),
|
||||
}))
|
||||
.query(({ input }) => {
|
||||
return {
|
||||
greeting: `Hello ${input.name ?? 'World'}!`,
|
||||
}
|
||||
}),
|
||||
announcement,
|
||||
post,
|
||||
talents,
|
||||
maintenance,
|
||||
})
|
||||
|
||||
export type AppRouter = typeof appRouter
|
||||
|
||||
22
src/server/trpc/routers/maintenance.ts
Normal file
22
src/server/trpc/routers/maintenance.ts
Normal 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()
|
||||
}),
|
||||
})
|
||||
@@ -1,3 +1,68 @@
|
||||
export default defineEventHandler((event) => {
|
||||
return 'Hello post'
|
||||
import { z } from 'zod'
|
||||
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,
|
||||
},
|
||||
},
|
||||
})
|
||||
}),
|
||||
})
|
||||
|
||||
68
src/server/trpc/routers/talents.ts
Normal file
68
src/server/trpc/routers/talents.ts
Normal 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()
|
||||
}),
|
||||
})
|
||||
Reference in New Issue
Block a user