feat: ajouter de nouveaux gestionnaires d'événements pour les API et supprimer les fichiers obsolètes

This commit is contained in:
2025-12-22 19:39:10 +01:00
parent ed8ffedde5
commit adea7fe35e
16 changed files with 259 additions and 95 deletions

View File

@@ -0,0 +1,4 @@
export default defineEventHandler(async (event) => {
const { discord } = useRuntimeConfig(event)
return await $fetch(`https://api.lanyard.rest/v1/users/${discord.userId}`)
})

View File

@@ -1,89 +0,0 @@
import { defineEventHandler, readValidatedBody } from 'h3'
import { z } from 'zod'
import {
convertToModelMessages,
createUIMessageStream,
createUIMessageStreamResponse,
streamText
} from 'ai'
import type { UIMessage } from 'ai'
import { createWorkersAI } from 'workers-ai-provider'
import { statsTool } from '~~/shared/utils/tools/stats'
import { activityTool } from '~~/shared/utils/tools/activity'
import { resumesTool } from '~~/shared/utils/tools/read-resume'
import { resourceTool } from '~~/shared/utils/tools/read-resources'
import { statusPageTool } from '~~/shared/utils/tools/status-page'
import { usesByCategoryTool } from '~~/shared/utils/tools/uses-by-category'
export default defineEventHandler(async (event) => {
const { messages, lang } = await readValidatedBody(event, z.object({
messages: z.array(z.custom<UIMessage>()),
lang: z.string().optional()
}).parse)
const { AI } = event.context.cloudflare.env || {}
if (!AI) {
throw createError({
statusCode: 500,
statusMessage: 'Cloudflare AI Binding not found. Check wrangler.json.'
})
}
const validCategories = ['contact', 'education', 'experiences', 'hobbies', 'languages', 'profile', 'projects', 'skills', 'uses'].join(', ')
const workersAI = createWorkersAI({ binding: AI })
const stream = createUIMessageStream({
execute: async ({ writer }) => {
const result = streamText({
model: workersAI('@cf/meta/llama-3-8b-instruct'),
system: `You are Arthur Danjou's personal portfolio assistant (Data Science Student).
### TOOL USAGE GUIDE - FOLLOW STRICTLY:
1. **readResources(category)**: YOUR PRIMARY BRAIN.
- Use this for ANY question about Arthur's life, background, or work.
- Categories: ${validCategories}.
- Example: "What are his skills?" -> call readResources('skills').
- Example: "Where did he study?" -> call readResources('education').
2. **readResume()**: THE FILE DISPENSER.
- **WARNING:** This tool ONLY returns a direct DOWNLOAD LINK (URL). It does NOT contain text.
- **WHEN TO USE:** ONLY when the user explicitly asks to "download", "get", "see", or "have" the CV/Resume file.
- **DO NOT USE:** If the user asks "What is on his resume?" or "Describe his experience", DO NOT use this. Use 'readResources' instead.
3. **stats()**: GITHUB METRICS.
- Use this for questions about coding volume, commit streaks, languages used percentages, or GitHub activity.
4. **activity()**: LIVE STATUS.
- Use this to know what Arthur is doing RIGHT NOW (e.g., "Is he coding?", "What is he listening to on Spotify?").
5. **statusPage()**: INFRASTRUCTURE HEALTH.
- Use this if the user asks about the website's uptime, server status, or if services are down.
6. **usesByCategory()**: TECH STACK & GEAR.
- Use this for specific questions about his hardware (Mac vs PC), software, developer tools, or desk setup.
### GENERAL RULES:
- If you don't call a tool, you know NOTHING. Do not hallucinate.
- Always answer in ${lang || 'English'}.
- Be concise and professional.
`,
tools: {
stats: statsTool,
activity: activityTool,
readResume: resumesTool,
readResources: resourceTool,
statusPage: statusPageTool,
usesByCategory: usesByCategoryTool
},
messages: await convertToModelMessages(messages)
})
writer.merge(result.toUIMessageStream())
}
})
return createUIMessageStreamResponse({ stream })
})

16
server/api/contact.get.ts Normal file
View File

@@ -0,0 +1,16 @@
import { queryCollection } from '@nuxt/content/server'
export default defineCachedEventHandler(async (event) => {
const result = await queryCollection(event, 'contact')
.where('extension', '=', 'json')
.first()
if (!result) {
throw createError({ statusCode: 404, statusMessage: 'Contact information not found' })
}
return result.body
}, {
maxAge: 60 * 60 * 24,
name: 'contact'
})

View File

@@ -0,0 +1,24 @@
import { queryCollection } from '@nuxt/content/server'
export default defineCachedEventHandler(async (event) => {
const result = await queryCollection(event, 'education')
.where('extension', '=', 'md')
.all()
if (result.length === 0) {
throw createError({ statusCode: 404, statusMessage: 'Education records not found' })
}
return result
.sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime())
.map(edu => ({
degree: edu.degree,
institution: edu.institution,
startDate: edu.startDate,
endDate: edu.endDate,
location: edu.location
}))
}, {
maxAge: 60 * 60 * 24,
name: 'education'
})

View File

@@ -0,0 +1,27 @@
import { queryCollection } from '@nuxt/content/server'
export default defineCachedEventHandler(async (event) => {
const result = await queryCollection(event, 'experiences')
.where('extension', '=', 'md')
.all()
if (result.length === 0) {
throw createError({ statusCode: 404, statusMessage: 'Experience records not found' })
}
return result
.sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime())
.map(exp => ({
title: exp.title,
company: exp.company,
companyUrl: exp.companyUrl,
startDate: exp.startDate,
endDate: exp.endDate,
location: exp.location,
description: exp.description
}))
},
{
maxAge: 60 * 60 * 24,
name: 'experiences'
})

15
server/api/hobbies.get.ts Normal file
View File

@@ -0,0 +1,15 @@
import { queryCollection } from '@nuxt/content/server'
export default defineCachedEventHandler(async (event) => {
const result = await queryCollection(event, 'hobbies')
.where('extension', '=', 'md')
.first()
if (!result) {
throw createError({ statusCode: 404, statusMessage: 'Hobbies not found' })
}
return result.body
}, {
maxAge: 60 * 60 * 24,
name: 'hobbies'
})

View File

@@ -0,0 +1,16 @@
import { queryCollection } from '@nuxt/content/server'
export default defineCachedEventHandler(async (event) => {
const result = await queryCollection(event, 'languages')
.where('extension', '=', 'json')
.first()
if (!result) {
throw createError({ statusCode: 404, statusMessage: 'Languages not found' })
}
return result.body
}, {
maxAge: 60 * 60 * 24,
name: 'languages'
})

16
server/api/profile.get.ts Normal file
View File

@@ -0,0 +1,16 @@
import { queryCollection } from '@nuxt/content/server'
export default defineCachedEventHandler(async (event) => {
const result = await queryCollection(event, 'profile')
.where('extension', '=', 'md')
.first()
if (!result) {
throw createError({ statusCode: 404, statusMessage: 'Profile not found' })
}
return result
}, {
maxAge: 60 * 60 * 24,
name: 'profile'
})

View File

@@ -0,0 +1,16 @@
import { queryCollection } from '@nuxt/content/server'
export default defineCachedEventHandler(async (event) => {
const result = await queryCollection(event, 'projects')
.where('extension', '=', 'md')
.all()
if (result.length === 0) {
throw createError({ statusCode: 404, statusMessage: 'Projects not found' })
}
return result
}, {
maxAge: 60 * 60 * 24,
name: 'projects'
})

16
server/api/skills.get.ts Normal file
View File

@@ -0,0 +1,16 @@
import { queryCollection } from '@nuxt/content/server'
export default defineCachedEventHandler(async (event) => {
const result = await queryCollection(event, 'skills')
.where('extension', '=', 'json')
.first()
if (!result) {
throw createError({ statusCode: 404, statusMessage: 'Skills not found' })
}
return result.body
}, {
maxAge: 60 * 60 * 24,
name: 'skills'
})

57
server/api/stats.get.ts Normal file
View File

@@ -0,0 +1,57 @@
import type { H3Event } from 'h3'
const cachedWakatimeCoding = defineCachedFunction(async (event: H3Event) => {
const config = useRuntimeConfig(event)
return await $fetch<{ data: unknown[] }>(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.coding}.json`)
}, {
maxAge: 24 * 60 * 60,
name: 'wakatime',
getKey: () => 'coding'
})
const cachedWakatimeEditors = defineCachedFunction(async (event: H3Event) => {
const config = useRuntimeConfig(event)
return await $fetch<{ data: unknown[] }>(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.editors}.json`)
}, {
maxAge: 24 * 60 * 60,
name: 'wakatime',
getKey: () => 'editors'
})
const cachedWakatimeOs = defineCachedFunction(async (event: H3Event) => {
const config = useRuntimeConfig(event)
return await $fetch<{ data: unknown[] }>(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.os}.json`)
}, {
maxAge: 24 * 60 * 60,
name: 'wakatime',
getKey: () => 'os'
})
const cachedWakatimeLanguages = defineCachedFunction(async (event: H3Event) => {
const config = useRuntimeConfig(event)
return await $fetch<{ data: unknown[] }>(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.languages}.json`)
}, {
maxAge: 24 * 60 * 60,
name: 'wakatime',
getKey: () => 'languages'
})
export default defineEventHandler(async (event) => {
const [coding, editors, os, languages] = await Promise.all([
cachedWakatimeCoding(event),
cachedWakatimeEditors(event),
cachedWakatimeOs(event),
cachedWakatimeLanguages(event)
])
return {
coding: coding.data,
editors: editors.data,
os: os.data,
languages: languages.data
}
})

View File

@@ -0,0 +1,7 @@
export default defineCachedEventHandler(async (event) => {
const { statusPage } = useRuntimeConfig(event)
return await $fetch(statusPage)
}, {
maxAge: 60 * 60,
name: 'status-page'
})

33
server/api/uses.get.ts Normal file
View File

@@ -0,0 +1,33 @@
import { queryCollection } from '@nuxt/content/server'
export default defineCachedEventHandler(async (event) => {
const categories = await queryCollection(event, 'usesCategories')
.where('extension', '=', 'md')
.all()
if (categories.length === 0) {
throw createError({ statusCode: 404, statusMessage: 'Uses categories not found' })
}
const uses = await queryCollection(event, 'uses')
.where('extension', '=', 'md')
.all()
if (uses.length === 0) {
throw createError({ statusCode: 404, statusMessage: 'Uses not found' })
}
const uses_by_categories = []
for (const category of categories) {
uses_by_categories.push({
category: category,
uses: uses.filter((use: { category: unknown }) => use.category === category.slug)
})
}
return uses_by_categories
},
{
maxAge: 60 * 60 * 24,
name: 'uses'
})