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/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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
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 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"
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
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) => {
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
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