From adea7fe35ed87986e63c12b20b22723a26d8b53c Mon Sep 17 00:00:00 2001 From: Arthur DANJOU Date: Mon, 22 Dec 2025 19:39:10 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20ajouter=20de=20nouveaux=20gestionnaires?= =?UTF-8?q?=20d'=C3=A9v=C3=A9nements=20pour=20les=20API=20et=20supprimer?= =?UTF-8?q?=20les=20fichiers=20obsol=C3=A8tes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/api/activity.get.ts | 4 ++ server/api/chat.post.ts | 89 --------------------------------- server/api/contact.get.ts | 16 ++++++ server/api/education.get.ts | 24 +++++++++ server/api/experiences.get.ts | 27 ++++++++++ server/api/hobbies.get.ts | 15 ++++++ server/api/languages.get.ts | 16 ++++++ server/api/profile.get.ts | 16 ++++++ server/api/projects.get.ts | 16 ++++++ server/api/skills.get.ts | 16 ++++++ server/api/stats.get.ts | 57 +++++++++++++++++++++ server/api/status-page.get.ts | 7 +++ server/api/uses.get.ts | 33 ++++++++++++ server/routes/resumes/en.get.ts | 6 +++ server/routes/resumes/fr.get.ts | 6 +++ shared/utils/index.ts | 6 --- 16 files changed, 259 insertions(+), 95 deletions(-) create mode 100644 server/api/activity.get.ts delete mode 100644 server/api/chat.post.ts create mode 100644 server/api/contact.get.ts create mode 100644 server/api/education.get.ts create mode 100644 server/api/experiences.get.ts create mode 100644 server/api/hobbies.get.ts create mode 100644 server/api/languages.get.ts create mode 100644 server/api/profile.get.ts create mode 100644 server/api/projects.get.ts create mode 100644 server/api/skills.get.ts create mode 100644 server/api/stats.get.ts create mode 100644 server/api/status-page.get.ts create mode 100644 server/api/uses.get.ts create mode 100644 server/routes/resumes/en.get.ts create mode 100644 server/routes/resumes/fr.get.ts delete mode 100644 shared/utils/index.ts diff --git a/server/api/activity.get.ts b/server/api/activity.get.ts new file mode 100644 index 0000000..cefbaca --- /dev/null +++ b/server/api/activity.get.ts @@ -0,0 +1,4 @@ +export default defineEventHandler(async (event) => { + const { discord } = useRuntimeConfig(event) + return await $fetch(`https://api.lanyard.rest/v1/users/${discord.userId}`) +}) diff --git a/server/api/chat.post.ts b/server/api/chat.post.ts deleted file mode 100644 index c0beab6..0000000 --- a/server/api/chat.post.ts +++ /dev/null @@ -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()), - 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 }) -}) diff --git a/server/api/contact.get.ts b/server/api/contact.get.ts new file mode 100644 index 0000000..e4546cd --- /dev/null +++ b/server/api/contact.get.ts @@ -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' +}) diff --git a/server/api/education.get.ts b/server/api/education.get.ts new file mode 100644 index 0000000..6dc569c --- /dev/null +++ b/server/api/education.get.ts @@ -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' +}) diff --git a/server/api/experiences.get.ts b/server/api/experiences.get.ts new file mode 100644 index 0000000..2d1b2b2 --- /dev/null +++ b/server/api/experiences.get.ts @@ -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' +}) diff --git a/server/api/hobbies.get.ts b/server/api/hobbies.get.ts new file mode 100644 index 0000000..5f03a9b --- /dev/null +++ b/server/api/hobbies.get.ts @@ -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' +}) diff --git a/server/api/languages.get.ts b/server/api/languages.get.ts new file mode 100644 index 0000000..b957ab1 --- /dev/null +++ b/server/api/languages.get.ts @@ -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' +}) diff --git a/server/api/profile.get.ts b/server/api/profile.get.ts new file mode 100644 index 0000000..633053d --- /dev/null +++ b/server/api/profile.get.ts @@ -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' +}) diff --git a/server/api/projects.get.ts b/server/api/projects.get.ts new file mode 100644 index 0000000..3231a8b --- /dev/null +++ b/server/api/projects.get.ts @@ -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' +}) diff --git a/server/api/skills.get.ts b/server/api/skills.get.ts new file mode 100644 index 0000000..e42143c --- /dev/null +++ b/server/api/skills.get.ts @@ -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' +}) diff --git a/server/api/stats.get.ts b/server/api/stats.get.ts new file mode 100644 index 0000000..dee14b3 --- /dev/null +++ b/server/api/stats.get.ts @@ -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 + } +}) diff --git a/server/api/status-page.get.ts b/server/api/status-page.get.ts new file mode 100644 index 0000000..35c53f2 --- /dev/null +++ b/server/api/status-page.get.ts @@ -0,0 +1,7 @@ +export default defineCachedEventHandler(async (event) => { + const { statusPage } = useRuntimeConfig(event) + return await $fetch(statusPage) +}, { + maxAge: 60 * 60, + name: 'status-page' +}) diff --git a/server/api/uses.get.ts b/server/api/uses.get.ts new file mode 100644 index 0000000..fa0dff2 --- /dev/null +++ b/server/api/uses.get.ts @@ -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' +}) diff --git a/server/routes/resumes/en.get.ts b/server/routes/resumes/en.get.ts new file mode 100644 index 0000000..6356376 --- /dev/null +++ b/server/routes/resumes/en.get.ts @@ -0,0 +1,6 @@ +export default defineCachedEventHandler(async (event) => { + return sendRedirect(event, '/resumes/CV 2026 EN.pdf', 302) +}, { + maxAge: 60 * 60 * 24, + name: 'resume_en' +}) diff --git a/server/routes/resumes/fr.get.ts b/server/routes/resumes/fr.get.ts new file mode 100644 index 0000000..d610528 --- /dev/null +++ b/server/routes/resumes/fr.get.ts @@ -0,0 +1,6 @@ +export default defineCachedEventHandler(async (event) => { + return sendRedirect(event, '/resumes/CV 2026 FR.pdf', 302) +}, { + maxAge: 60 * 60 * 24, + name: 'resume_fr' +}) diff --git a/shared/utils/index.ts b/shared/utils/index.ts deleted file mode 100644 index 74c602e..0000000 --- a/shared/utils/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './tools/activity' -export * from './tools/read-resources' -export * from './tools/read-resume' -export * from './tools/stats' -export * from './tools/status-page' -export * from './tools/uses-by-category'