From 777e0b6fc6a55d255b82339e33809544dd54e3b1 Mon Sep 17 00:00:00 2001 From: Abdou TOP Date: Tue, 12 May 2026 23:01:59 +0000 Subject: [PATCH 1/5] Add logging logic to capture clean logs and errors across the application --- api/auth.ts | 15 +++++++++ api/fix-query.ts | 20 +++++++++++- api/lib/functions.ts | 21 ++++++++++--- api/lib/json_store.ts | 18 ++++++++--- api/lib/logger.ts | 73 +++++++++++++++++++++++++++++++++++++++++++ api/lmdb-store.ts | 35 ++++++++++++++++++--- api/picture.ts | 42 ++++++++++++++++++------- api/routes.ts | 72 +++++++++++++++++++++++++++++++----------- api/server.ts | 6 ++-- api/sql.ts | 17 +++++++--- api/user.ts | 7 +++-- 11 files changed, 273 insertions(+), 53 deletions(-) create mode 100644 api/lib/logger.ts diff --git a/api/auth.ts b/api/auth.ts index 58e6f59..0ec9d3c 100644 --- a/api/auth.ts +++ b/api/auth.ts @@ -11,6 +11,7 @@ import { respond } from '@01edu/api/response' import { authenticateOauthUser } from '/api/user.ts' import { savePicture } from '/api/picture.ts' import type { RequestContext } from '@01edu/api/context' +import { log } from '/api/lib/logger.ts' interface GoogleTokens { access_token: string @@ -46,6 +47,7 @@ const GOOGLE_CONFIG = { export function initiateGoogleAuth() { const { state } = generateStateToken() const authUrl = getGoogleAuthUrl(state) + log.info('oauth-redirect-initiated', { state }) return new Response(null, { status: 302, headers: { 'Location': authUrl }, @@ -59,6 +61,7 @@ export async function handleGoogleCallback( const state = ctx.url.searchParams.get('state') if (!code) { + log.warn('oauth-callback-missing-code') throw new respond.BadRequestError({ message: 'Missing authorization code', details: 'The authorization code from Google OAuth is required', @@ -67,6 +70,7 @@ export async function handleGoogleCallback( // Verify the state parameter if (!verifyState(state || undefined)) { + log.warn('oauth-callback-invalid-state', { state }) throw new respond.UnauthorizedError({ message: 'Invalid state parameter', details: 'The state parameter is invalid or has expired', @@ -89,6 +93,11 @@ export async function handleGoogleCallback( }) if (!tokenResponse.ok) { + const errorBody = await tokenResponse.text().catch(() => 'unknown') + log.error('oauth-token-exchange-failed', { + status: tokenResponse.status, + body: errorBody, + }) throw new respond.UnauthorizedError({ message: 'Failed to exchange authorization code', details: 'Could not obtain access token from Google', @@ -103,6 +112,12 @@ export async function handleGoogleCallback( userInfo.picture &&= await savePicture(userInfo.picture) const sessionId = await authenticateOauthUser(userInfo) + log.info('oauth-login-success', { + userId: userInfo.sub, + email: userInfo.email, + domain: userInfo.hd, + }) + // Return response with session cookie return new Response(null, { status: 302, diff --git a/api/fix-query.ts b/api/fix-query.ts index 310bcb4..8097acb 100644 --- a/api/fix-query.ts +++ b/api/fix-query.ts @@ -2,6 +2,7 @@ import { render } from '@deno/gfm' import { promptTemplate } from '/api/fix-query-prompt.ts' import { GEMINI_API_KEY, GEMINI_MODEL } from '/api/lib/env.ts' import { AIAnalysisCacheCollection } from '/api/schema.ts' +import { log } from '/api/lib/logger.ts' const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:streamGenerateContent?alt=json&key=${GEMINI_API_KEY}` @@ -29,6 +30,11 @@ function cacheKey(deployment: string, metric: Metric) { async function callGemini(payload: string, thinkingLevel: string) { const prompt = promptTemplate.replace('{{QUERY_DETAILS_JSON}}', payload) + log.info('gemini-request-start', { + thinkingLevel, + promptLength: prompt.length, + }) + const res = await fetch(GEMINI_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -40,6 +46,7 @@ async function callGemini(payload: string, thinkingLevel: string) { if (!res.ok) { const body = await res.text() + log.error('gemini-request-failed', { status: res.status, body }) throw new Error(`Gemini API error ${res.status}: ${body}`) } @@ -65,7 +72,16 @@ export async function analyzeQueryWithAI( ) { const key = await cacheKey(deployment, metric) const cached = !forceRefresh && AIAnalysisCacheCollection.get(key) - if (cached) return cached.analysis + if (cached) { + log.info('ai-analysis-cache-hit', { deployment, query: metric.query }) + return cached.analysis + } + + log.info('ai-analysis-start', { + deployment, + query: metric.query, + forceRefresh, + }) const payload = JSON.stringify({ schema, metrics: [metric] }, null, 2) const analysis = await callGemini(payload, forceRefresh ? 'HIGH' : 'MINIMAL') @@ -76,5 +92,7 @@ export async function analyzeQueryWithAI( } else { AIAnalysisCacheCollection.insert({ cacheKey: key, analysis }) } + + log.info('ai-analysis-complete', { deployment, query: metric.query }) return analysis } diff --git a/api/lib/functions.ts b/api/lib/functions.ts index 0cec74d..6b7be0b 100644 --- a/api/lib/functions.ts +++ b/api/lib/functions.ts @@ -1,6 +1,7 @@ import { batch } from '/api/lib/json_store.ts' import { join } from '@std/path' import { ensureDir } from '@std/fs' +import { log } from '/api/lib/logger.ts' // Define the function signatures export type FunctionContext = { @@ -45,7 +46,7 @@ export async function init() { } async function loadAll() { - console.info('Loading project functions...') + log.info('loading-project-functions') for await (const entry of Deno.readDir(functionsDir)) { if (entry.isDirectory) { await reloadProjectFunctions(entry.name) @@ -73,7 +74,11 @@ async function reloadProjectFunctions(slug: string) { loaded.push({ name: entry.name, module: fns }) } } catch (e) { - console.error(`Failed to import ${entry.name} for ${slug}:`, e) + log.error('failed-to-import-function', { + file: entry.name, + project: slug, + error: e instanceof Error ? e.message : String(e), + }) } } }) @@ -83,13 +88,19 @@ async function reloadProjectFunctions(slug: string) { if (loaded.length > 0) { functionsMap.set(slug, loaded) - console.info(`Loaded ${loaded.length} functions for project: ${slug}`) + log.info('loaded-project-functions', { + count: loaded.length, + project: slug, + }) } else { functionsMap.delete(slug) } } catch (err) { if (!(err instanceof Deno.errors.NotFound)) { - console.error(`Failed to load functions for ${slug}:`, err) + log.error('failed-to-load-functions', { + project: slug, + error: err instanceof Error ? err.message : String(err), + }) } functionsMap.delete(slug) } @@ -97,7 +108,7 @@ async function reloadProjectFunctions(slug: string) { function startWatcher() { if (watcher) return - console.info(`Starting function watcher on ${functionsDir}`) + log.info('starting-function-watcher', { dir: functionsDir }) watcher = Deno.watchFs(functionsDir, { recursive: true }) // Process events ;(async () => { for await (const event of watcher!) { diff --git a/api/lib/json_store.ts b/api/lib/json_store.ts index 53c2701..2ceeed8 100644 --- a/api/lib/json_store.ts +++ b/api/lib/json_store.ts @@ -2,6 +2,7 @@ import { join } from '@std/path' import { APP_ENV } from '@01edu/api/env' import { ensureDir } from '@std/fs' +import { log } from '/api/lib/logger.ts' const DB_DIR = APP_ENV === 'test' ? './db_test' : './db' @@ -49,7 +50,7 @@ export async function createCollection< const rec = JSON.parse(await Deno.readTextFile(recordFile(id))) as T records.set(id, rec) } catch { - // corrupted file, ignore + log.warn('json-store-corrupted-file', { collection: name, id }) } }) @@ -65,7 +66,10 @@ export async function createCollection< async insert(data: T) { const id = data[primaryKey] if (!id) throw Error(`Missing primary key ${primaryKey}`) - if (records.has(id)) throw Error(`${id} already exists`) + if (records.has(id)) { + log.warn('json-store-insert-duplicate', { collection: name, id }) + throw Error(`${id} already exists`) + } Object.assign(data, { createdAt: Date.now(), updatedAt: Date.now() }) records.set(id, data) await saveRecord(data) @@ -89,7 +93,10 @@ export async function createCollection< records.values().filter(predicate).toArray() as (T & BaseRecord)[], async update(id: T[K], changes: Partial>) { const record = records.get(id) - if (!record) throw new Deno.errors.NotFound(`record ${id} not found`) + if (!record) { + log.warn('json-store-update-not-found', { collection: name, id }) + throw new Deno.errors.NotFound(`record ${id} not found`) + } const updated = { ...record, ...changes, updatedAt: Date.now() } as T records.set(id, updated) await saveRecord(updated) @@ -98,7 +105,10 @@ export async function createCollection< async delete(id: T[K]) { const record = records.get(id) - if (!record) return false + if (!record) { + log.debug('json-store-delete-not-found', { collection: name, id }) + return false + } records.delete(id) await Deno.remove(recordFile(id)) return true diff --git a/api/lib/logger.ts b/api/lib/logger.ts new file mode 100644 index 0000000..7f39500 --- /dev/null +++ b/api/lib/logger.ts @@ -0,0 +1,73 @@ +import type { Log } from '@01edu/api/log' +import { getContext } from '@01edu/api/context' +import { insertLogs } from '/api/clickhouse-client.ts' + +type LogLevel = 'debug' | 'info' | 'warn' | 'error' + +type LogFunction = ( + level: LogLevel, + event: string, + props?: Record, +) => void + +const severityMap: Record = { + debug: 5, + info: 9, + warn: 13, + error: 17, +} + +const push = ( + serviceName: string, + level: LogLevel, + event: string, + props?: Record, +) => { + const ctx = getContext() + const traceId = ctx?.trace ?? Date.now() / 1000 + const spanId = ctx?.span ?? traceId + + insertLogs(serviceName, { + timestamp: Date.now(), + trace_id: traceId, + span_id: spanId, + severity_number: severityMap[level], + event_name: event, + attributes: props ?? {}, + service_version: undefined, + service_instance_id: undefined, + }).catch((err) => { + console.error('log-insert-failed', { + error: err instanceof Error ? err.message : String(err), + event, + level, + }) + }) +} + +export function createLogger(serviceName: string): Log { + const log: LogFunction = (level, event, props) => { + const consoleMethod = level === 'error' + ? console.error + : level === 'warn' + ? console.warn + : level === 'debug' + ? console.debug + : console.info + consoleMethod(event, props) + push(serviceName, level, event, props) + } + + return Object.assign(log, { + error: (event: string, props?: Record) => + log('error', event, props), + debug: (event: string, props?: Record) => + log('debug', event, props), + warn: (event: string, props?: Record) => + log('warn', event, props), + info: (event: string, props?: Record) => + log('info', event, props), + }) +} + +export const log = createLogger('devtools') diff --git a/api/lmdb-store.ts b/api/lmdb-store.ts index ad9b295..982aa15 100644 --- a/api/lmdb-store.ts +++ b/api/lmdb-store.ts @@ -1,4 +1,5 @@ import { STORE_SECRET, STORE_URL } from '/api/lib/env.ts' +import { log } from '/api/lib/logger.ts' const headers = { authorization: `Bearer ${STORE_SECRET}` } export const getOne = async ( @@ -6,9 +7,21 @@ export const getOne = async ( id: string, ): Promise => { const url = `${STORE_URL}/${path}/${encodeURIComponent(String(id))}` - const res = await fetch(url, { headers }) - if (res.status === 404) return null - return res.json() + try { + const res = await fetch(url, { headers }) + if (res.status === 404) return null + if (!res.ok) { + log.error('store-get-one-failed', { path, id, status: res.status }) + } + return res.json() + } catch (err) { + log.error('store-get-one-error', { + path, + id, + error: err instanceof Error ? err.message : String(err), + }) + throw err + } } export const get = async ( @@ -16,6 +29,18 @@ export const get = async ( params?: { q?: string; limit?: number; from?: number }, ): Promise => { const q = new URLSearchParams(params as unknown as Record) - const res = await fetch(`${STORE_URL}/${path}/?${q}`, { headers }) - return res.json() + const url = `${STORE_URL}/${path}/?${q}` + try { + const res = await fetch(url, { headers }) + if (!res.ok) { + log.error('store-get-failed', { path, status: res.status }) + } + return res.json() + } catch (err) { + log.error('store-get-error', { + path, + error: err instanceof Error ? err.message : String(err), + }) + throw err + } } diff --git a/api/picture.ts b/api/picture.ts index 8c43268..ff0de0c 100644 --- a/api/picture.ts +++ b/api/picture.ts @@ -2,6 +2,7 @@ import { crypto } from '@std/crypto/crypto' import { encodeBase64Url } from '@std/encoding/base64url' import { ensureDirSync, exists } from '@std/fs' import { PICTURE_DIR } from '/api/lib/env.ts' +import { log } from '/api/lib/logger.ts' ensureDirSync(PICTURE_DIR) @@ -12,18 +13,37 @@ export const savePicture = async (url?: string) => { const hash = encodeBase64Url(bytes) const file = `${PICTURE_DIR}/${hash}` if (await exists(file)) return hash - const req = await fetch(url) - const data = await req.arrayBuffer() - await Deno.writeFile(file, new Uint8Array(data)) - return hash + try { + const req = await fetch(url) + if (!req.ok) { + log.warn('picture-fetch-failed', { url, status: req.status }) + return + } + const data = await req.arrayBuffer() + await Deno.writeFile(file, new Uint8Array(data)) + return hash + } catch (err) { + log.error('picture-save-failed', { + url, + error: err instanceof Error ? err.message : String(err), + }) + } } export const getPicture = async (hash: string) => { - const picture = await Deno.open(`${PICTURE_DIR}/${hash}`) - return new Response(picture.readable, { - headers: { - 'content-type': 'image/png', - 'cache-control': 'public, max-age=86400, no-transform', - }, - }) + try { + const picture = await Deno.open(`${PICTURE_DIR}/${hash}`) + return new Response(picture.readable, { + headers: { + 'content-type': 'image/png', + 'cache-control': 'public, max-age=86400, no-transform', + }, + }) + } catch (err) { + log.error('picture-read-failed', { + hash, + error: err instanceof Error ? err.message : String(err), + }) + throw err + } } diff --git a/api/routes.ts b/api/routes.ts index b49bb5d..e7e9820 100644 --- a/api/routes.ts +++ b/api/routes.ts @@ -39,8 +39,8 @@ import { SQLQueryError, updateTableData, } from '/api/sql.ts' -import { Log } from '@01edu/api/log' import { get, getOne } from './lmdb-store.ts' +import { log } from '/api/lib/logger.ts' import { analyzeQueryWithAI } from '/api/fix-query.ts' const MetricSchema = OBJ({ @@ -71,24 +71,39 @@ const MetricSchema = OBJ({ const withUserSession = async ({ cookies }: RequestContext) => { const session = await decodeSession(cookies.session) - if (!session) throw Error('Missing user session') + if (!session) { + log.warn('auth-missing-session') + throw Error('Missing user session') + } const admin = AdminsCollection.get(session.id) return { ...session, isAdmin: !!admin } } const withAdminSession = async (ctx: RequestContext) => { const session = await withUserSession(ctx) - if (!session || !session.isAdmin) throw Error('Admin access required') + if (!session || !session.isAdmin) { + log.warn('auth-admin-required', { userId: session?.id }) + throw Error('Admin access required') + } } const withDeploymentSession = async (ctx: RequestContext) => { const token = ctx.req.headers.get('Authorization')?.replace(/^Bearer /i, '') - if (!token) throw Error('Missing token') + if (!token) { + log.warn('deployment-auth-missing-token') + throw Error('Missing token') + } const message = await decryptMessage(token) - if (!message) throw Error('Invalid token') + if (!message) { + log.warn('deployment-auth-invalid-token') + throw Error('Invalid token') + } const data = JSON.parse(message) const dep = DeploymentsCollection.get(data?.url) - if (!dep || dep.tokenSalt !== data?.tokenSalt) throw Error('Invalid token') + if (!dep || dep.tokenSalt !== data?.tokenSalt) { + log.warn('deployment-auth-token-mismatch', { url: data?.url }) + throw Error('Invalid token') + } return dep } @@ -292,7 +307,10 @@ const defs = { }), 'POST/api/project': route({ authorize: withAdminSession, - fn: (_ctx, project) => ProjectsCollection.insert(project), + fn: (_ctx, project) => { + log.info('project-created', { slug: project.slug, name: project.name }) + return ProjectsCollection.insert(project) + }, input: OBJ({ slug: STR('The unique identifier for the project'), name: STR('The name of the project'), @@ -333,6 +351,7 @@ const defs = { const project = ProjectsCollection.get(slug) if (!project) throw respond.NotFound({ message: 'Project not found' }) ProjectsCollection.delete(slug) + log.info('project-deleted', { slug }) return true }, input: OBJ({ slug: STR('The slug of the project') }), @@ -388,6 +407,10 @@ const defs = { ...input, tokenSalt, }) + log.info('deployment-created', { + url: deployment.url, + projectId: deployment.projectId, + }) const token = await encryptMessage( JSON.stringify({ url: deployment.url, tokenSalt }), ) @@ -405,6 +428,7 @@ const defs = { fn: async (_ctx, input) => { const { tokenSalt, ...deployment } = await DeploymentsCollection .update(input.url, input) + log.info('deployment-updated', { url: input.url }) const token = await encryptMessage( JSON.stringify({ url: deployment.url, tokenSalt }), ) @@ -423,6 +447,7 @@ const defs = { const dep = DeploymentsCollection.get(url) if (!dep) throw respond.NotFound() const tokenSalt = performance.now().toString() + log.info('deployment-token-regenerated', { url }) const { tokenSalt: _, ...deployment } = await DeploymentsCollection .update(url, { ...dep, tokenSalt }) @@ -478,6 +503,7 @@ const defs = { const dep = DeploymentsCollection.get(input) if (!dep) throw respond.NotFound() await DeploymentsCollection.delete(input) + log.info('deployment-deleted', { url: input }) return respond.NoContent() }, input: STR(), @@ -487,6 +513,8 @@ const defs = { authorize: withDeploymentSession, fn: (ctx, logs) => { if (!ctx.session.url) throw respond.InternalServerError() + const count = Array.isArray(logs) ? logs.length : 1 + log.debug('logs-ingested', { deployment: ctx.session.url, count }) return insertLogs(ctx.session.url, logs) }, input: LogsInputSchema, @@ -560,7 +588,7 @@ const defs = { columnsMap, ) } catch (err) { - console.error('fetchTablesData-error', { stack: (err as Error)?.stack }) + log.error('fetchTablesData-error', { stack: (err as Error)?.stack }) throw err } }, @@ -598,6 +626,7 @@ const defs = { authorize: withUserSession, fn: async (ctx, { deployment, table, data }) => { const dep = await withDeploymentTableAccess(ctx, deployment) + log.info('table-row-inserted', { deployment, table }) return insertTableData(dep, table, data) }, input: OBJ({ @@ -611,6 +640,7 @@ const defs = { authorize: withUserSession, fn: async (ctx, { deployment, table, pk, data }) => { const dep = await withDeploymentTableAccess(ctx, deployment) + log.info('table-row-updated', { deployment, table, pkKey: pk.key }) return updateTableData(dep, table, pk, data) }, input: OBJ({ @@ -643,20 +673,23 @@ const defs = { try { const startTime = performance.now() const data = await runSQL(sqlEndpoint, sqlToken, sql) - - return { - duration: (performance.now() - startTime) / 1000, // in seconds - rows: data, - } + const duration = (performance.now() - startTime) / 1000 + log.info('sql-query-executed', { deployment, duration }) + return { duration, rows: data } } catch (error) { if (error instanceof SQLQueryError) { const { type, sqlMessage } = error + log.error('sql-query-failed', { deployment, type, sqlMessage }) throw new respond.BadRequestError({ type, message: sqlMessage || `SQL query error: ${type || 'Unknown error'}`, }) } + log.error('sql-query-unexpected-error', { + deployment, + error: error instanceof Error ? error.message : String(error), + }) throw new respond.InternalServerErrorError({ message: error instanceof Error ? error.message : 'Unexpected error', }) @@ -699,7 +732,7 @@ const defs = { fn: async (_ctx, { deployment }) => { const dep = DeploymentsCollection.get(deployment) if (!dep) throw respond.NotFound({ message: 'Deployment not found' }) - console.log('Fetching API documentation from deployment', { + log.info('fetching-api-doc', { url: dep.url, }) try { @@ -712,7 +745,7 @@ const defs = { if (!res.ok) throw new Error(`Status ${res.status}`) return await res.json() } catch (_err) { - console.error('Error fetching API documentation', { + log.error('fetch-api-doc-error', { error: _err instanceof Error ? _err.stack : String(_err), }) throw respond.InternalServerError({ @@ -740,6 +773,11 @@ const defs = { ) return { id, analysis } } catch (err) { + log.error('fix-query-failed', { + deployment, + metricId: id, + error: err instanceof Error ? err.message : String(err), + }) throw respond.InternalServerError({ message: err instanceof Error ? err.message : String(err), }) @@ -761,6 +799,4 @@ const defs = { } as const export type RouteDefinitions = typeof defs -export const routeHandler = makeRouter(defs, { - log: console as unknown as Log, -}) +export const routeHandler = makeRouter(defs, { log }) diff --git a/api/server.ts b/api/server.ts index 3e5500c..596cef5 100644 --- a/api/server.ts +++ b/api/server.ts @@ -1,16 +1,16 @@ import { serveDir } from '@std/http/file-server' import { APP_ENV } from '@01edu/api/env' import { server } from '@01edu/api/server' -import { Log } from '@01edu/api/log' import { routeHandler } from '/api/routes.ts' import { PORT } from './lib/env.ts' import { init } from '/api/lib/functions.ts' import { startSchemaRefreshLoop } from './sql.ts' +import { log } from '/api/lib/logger.ts' await init() startSchemaRefreshLoop() -const fetch = server({ log: console as unknown as Log, routeHandler }) +const fetch = server({ log, routeHandler }) export default { fetch(req: Request) { return fetch(req, new URL(req.url)) @@ -32,5 +32,5 @@ if (APP_ENV === 'prod') { return new Response(indexHtml, htmlContent) }) } else { - console.info('server-start') + log.info('server-start') } diff --git a/api/sql.ts b/api/sql.ts index 0b58626..a2baa6d 100644 --- a/api/sql.ts +++ b/api/sql.ts @@ -10,6 +10,7 @@ import { applyWriteTransformers, getProjectFunctions, } from '/api/lib/functions.ts' +import { log } from '/api/lib/logger.ts' export class SQLQueryError extends Error { constructor(message: string, body: string) { @@ -63,7 +64,7 @@ async function detectDialect(endpoint: string, token: string): Promise { for (const d of DETECTION_QUERIES) { try { const rows = await runSQL(endpoint, token, d.sql) - console.debug('dialect-detection', { dialect: d.name, rows }) + log.debug('dialect-detection', { dialect: d.name, rows }) if (rows.length) { const text = JSON.stringify(rows[0]) if (d.matcher.test(text)) return d.name @@ -199,13 +200,13 @@ export async function refreshOneSchema( } else { await DatabaseSchemasCollection.insert(payload) } - console.info('schema-refreshed', { + log.info('schema-refreshed', { deployment: dep.url, dialect, tables: tables.length, }) } catch (err) { - console.error('schema-refresh-failed', { deployment: dep.url, err }) + log.error('schema-refresh-failed', { deployment: dep.url, err }) } } @@ -223,7 +224,7 @@ export function startSchemaRefreshLoop() { intervalHandle = setInterval(() => { refreshAllSchemas() }, DB_SCHEMA_REFRESH_MS) as unknown as number - console.info('schema-refresh-loop-started', { everyMs: DB_SCHEMA_REFRESH_MS }) + log.info('schema-refresh-loop-started', { everyMs: DB_SCHEMA_REFRESH_MS }) } type FetchTablesParams = { @@ -350,6 +351,10 @@ export const insertTableData = async ( ) => { const { sqlEndpoint, sqlToken } = deployment if (!sqlToken || !sqlEndpoint) { + log.error('insert-table-data-missing-config', { + deployment: deployment.url, + table, + }) throw Error('Missing SQL endpoint or token') } const projectFunctions = getProjectFunctions(deployment.projectId) @@ -390,6 +395,10 @@ export const updateTableData = async ( const { sqlEndpoint, sqlToken } = deployment if (!sqlToken || !sqlEndpoint) { + log.error('update-table-data-missing-config', { + deployment: deployment.url, + table, + }) throw Error('Missing SQL endpoint or token') } const projectFunctions = getProjectFunctions(deployment.projectId) diff --git a/api/user.ts b/api/user.ts index e401845..14d8f20 100644 --- a/api/user.ts +++ b/api/user.ts @@ -2,6 +2,7 @@ import { decodeBase64Url, encodeBase64Url } from '@std/encoding/base64url' import { SECRET } from '/api/lib/env.ts' import { getOne } from './lmdb-store.ts' import { GoogleUserInfo } from './auth.ts' +import { log } from '/api/lib/logger.ts' const encoder = new TextEncoder() const decoder = new TextDecoder() @@ -60,8 +61,10 @@ export async function decodeSession(sessionCode?: string) { fullName: user.name.fullName, picture: user.thumbnailPhotoUrl, } - } catch { - // Invalid session code + } catch (err) { + log.debug('session-decode-failed', { + error: err instanceof Error ? err.message : String(err), + }) } } From 2e80274a02790fe2dacb84e3060f8e7fcb26afc4 Mon Sep 17 00:00:00 2001 From: Abdou TOP Date: Tue, 12 May 2026 23:22:15 +0000 Subject: [PATCH 2/5] Refactor logging logic to improve batch processing and error handling --- api/lib/logger.ts | 79 +++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/api/lib/logger.ts b/api/lib/logger.ts index 7f39500..850bded 100644 --- a/api/lib/logger.ts +++ b/api/lib/logger.ts @@ -4,12 +4,6 @@ import { insertLogs } from '/api/clickhouse-client.ts' type LogLevel = 'debug' | 'info' | 'warn' | 'error' -type LogFunction = ( - level: LogLevel, - event: string, - props?: Record, -) => void - const severityMap: Record = { debug: 5, info: 9, @@ -17,56 +11,53 @@ const severityMap: Record = { error: 17, } -const push = ( - serviceName: string, - level: LogLevel, - event: string, - props?: Record, -) => { - const ctx = getContext() - const traceId = ctx?.trace ?? Date.now() / 1000 - const spanId = ctx?.span ?? traceId +export function createLogger(serviceName: string): Log { + const batch: Record[] = [] - insertLogs(serviceName, { - timestamp: Date.now(), - trace_id: traceId, - span_id: spanId, - severity_number: severityMap[level], - event_name: event, - attributes: props ?? {}, - service_version: undefined, - service_instance_id: undefined, - }).catch((err) => { - console.error('log-insert-failed', { - error: err instanceof Error ? err.message : String(err), - event, - level, + const flush = () => { + if (batch.length === 0) return + const logs = batch.splice(0, batch.length) + insertLogs(serviceName, logs as never[]).catch((err) => { + console.error('flush-failed', err) }) - }) -} + } -export function createLogger(serviceName: string): Log { - const log: LogFunction = (level, event, props) => { - const consoleMethod = level === 'error' + setInterval(flush, 5000) + + const log = ( + level: LogLevel, + event: string, + props?: Record, + ) => { + const ctx = getContext() + batch.push({ + timestamp: Date.now(), + trace_id: ctx?.trace ?? Date.now() / 1000, + span_id: ctx?.span ?? ctx?.trace ?? Date.now() / 1000, + severity_number: severityMap[level], + event_name: event, + attributes: props ?? {}, + service_version: null, + service_instance_id: null, + }) + + const m = level === 'error' ? console.error : level === 'warn' ? console.warn : level === 'debug' ? console.debug : console.info - consoleMethod(event, props) - push(serviceName, level, event, props) + m(event, props) + + if (batch.length >= 50) flush() } return Object.assign(log, { - error: (event: string, props?: Record) => - log('error', event, props), - debug: (event: string, props?: Record) => - log('debug', event, props), - warn: (event: string, props?: Record) => - log('warn', event, props), - info: (event: string, props?: Record) => - log('info', event, props), + error: (e: string, p?: Record) => log('error', e, p), + debug: (e: string, p?: Record) => log('debug', e, p), + warn: (e: string, p?: Record) => log('warn', e, p), + info: (e: string, p?: Record) => log('info', e, p), }) } From a5e441d66cfbb0840b02bb458a1fee0cf88c97c7 Mon Sep 17 00:00:00 2001 From: Abdou TOP Date: Wed, 13 May 2026 09:22:43 +0000 Subject: [PATCH 3/5] Add missing environment variables for store and Gemini API --- .env.test | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.env.test b/.env.test index 018d6a3..7551177 100644 --- a/.env.test +++ b/.env.test @@ -8,5 +8,9 @@ CLIENT_SECRET=GOC...test GOOGLE_CLIENT_ID=...test.apps.googleusercontent.com PORT=3021 REDIRECT_URI=http://localhost:7737/api/auth/google +STORE_URL=http://localhost:3021 +STORE_SECRET=store_secret_test +GEMINI_API_KEY=gemini_api_key_test +GEMINI_API_URL=https://gemini.api.url/test # Get values from `deno task dev:env` # then copy missing variables and replace APP_ENV to the matching values From d5c3191f58719e915840c819a74ee1dd4ef4dd1e Mon Sep 17 00:00:00 2001 From: Abdou TOP Date: Thu, 14 May 2026 14:29:44 +0000 Subject: [PATCH 4/5] Refactor code structure for improved readability and maintainability --- api/lib/logger.ts | 22 ++-- api/user.ts | 4 +- deno.json | 12 ++- deno.lock | 257 ++++++++++++++++++++-------------------------- 4 files changed, 132 insertions(+), 163 deletions(-) diff --git a/api/lib/logger.ts b/api/lib/logger.ts index 850bded..dd9babe 100644 --- a/api/lib/logger.ts +++ b/api/lib/logger.ts @@ -11,6 +11,8 @@ const severityMap: Record = { error: 17, } +type Params = Record + export function createLogger(serviceName: string): Log { const batch: Record[] = [] @@ -27,7 +29,7 @@ export function createLogger(serviceName: string): Log { const log = ( level: LogLevel, event: string, - props?: Record, + props?: Params, ) => { const ctx = getContext() batch.push({ @@ -41,23 +43,17 @@ export function createLogger(serviceName: string): Log { service_instance_id: null, }) - const m = level === 'error' - ? console.error - : level === 'warn' - ? console.warn - : level === 'debug' - ? console.debug - : console.info - m(event, props) + const c = console[level] || console.info + c(event, props) if (batch.length >= 50) flush() } return Object.assign(log, { - error: (e: string, p?: Record) => log('error', e, p), - debug: (e: string, p?: Record) => log('debug', e, p), - warn: (e: string, p?: Record) => log('warn', e, p), - info: (e: string, p?: Record) => log('info', e, p), + error: (e: string, p?: Params) => log('error', e, p), + debug: (e: string, p?: Params) => log('debug', e, p), + warn: (e: string, p?: Params) => log('warn', e, p), + info: (e: string, p?: Params) => log('info', e, p), }) } diff --git a/api/user.ts b/api/user.ts index 14d8f20..e2b84dd 100644 --- a/api/user.ts +++ b/api/user.ts @@ -49,7 +49,7 @@ export async function decodeSession(sessionCode?: string) { const json = await decryptMessage(sessionCode) const googleUser = JSON.parse(json) as GoogleUserInfo const user = await getOne< - { name: { fullName: string }; thumbnailPhotoUrl: string } + { name: { fullName: string } } >( 'google/user', googleUser.sub, @@ -59,7 +59,7 @@ export async function decodeSession(sessionCode?: string) { id: googleUser.sub, email: googleUser.email, fullName: user.name.fullName, - picture: user.thumbnailPhotoUrl, + picture: googleUser.picture || '', } } catch (err) { log.debug('session-decode-failed', { diff --git a/deno.json b/deno.json index 16054c6..2974c97 100644 --- a/deno.json +++ b/deno.json @@ -33,9 +33,11 @@ "./": "./", "/": "./", "@01edu/api": "jsr:@01edu/api@^0.2.7", + "@01edu/api": "jsr:@01edu/api@^0.2.7", "@01edu/api-client": "jsr:@01edu/api-client@^0.2.6", "@01edu/api-proxy": "jsr:@01edu/api-proxy@^0.2.1", "@01edu/signal-router": "npm:@01edu/signal-router@^0.2.3", + "@01edu/signal-router": "npm:@01edu/signal-router@^0.2.3", "@01edu/time": "jsr:@01edu/time@^0.1.0", "@deno/vite-plugin": "npm:@deno/vite-plugin@^2.0.2", "@std/assert": "jsr:@std/assert@^1.0.19", @@ -46,15 +48,15 @@ "@std/http": "jsr:@std/http@^1.1.0", "@std/path": "jsr:@std/path@^1.1.4", "@std/testing": "jsr:@std/testing@^1.0.18", - "vite": "npm:vite@^8.0.10", + "vite": "npm:vite@^8.0.13", "preact": "npm:preact@^10.29.1", "@preact/preset-vite": "npm:@preact/preset-vite@^2.10.5", "@preact/signals": "npm:@preact/signals@^2.9.0", - "@clickhouse/client": "npm:@clickhouse/client@^1.18.4", - "@tailwindcss/vite": "npm:@tailwindcss/vite@^4.2.4", - "tailwindcss": "npm:tailwindcss@^4.2.4", + "@clickhouse/client": "npm:@clickhouse/client@^1.18.5", + "@tailwindcss/vite": "npm:@tailwindcss/vite@^4.3.0", + "tailwindcss": "npm:tailwindcss@^4.3.0", "daisyui": "npm:daisyui@^5.5.19", - "lucide-preact": "npm:lucide-preact@^1.14.0", + "lucide-preact": "npm:lucide-preact@^1.16.0", "@deno/gfm": "jsr:@deno/gfm@0.12.0" }, "fmt": { diff --git a/deno.lock b/deno.lock index ab5d7da..4a6d588 100644 --- a/deno.lock +++ b/deno.lock @@ -4,21 +4,17 @@ "jsr:@01edu/api-client@~0.2.6": "0.2.6", "jsr:@01edu/api-proxy@~0.2.1": "0.2.1", "jsr:@01edu/api@~0.2.7": "0.2.7", + "jsr:@01edu/api@~0.2.7": "0.2.7", "jsr:@01edu/time@0.1": "0.1.0", - "jsr:@01edu/types@~0.2.6": "0.2.6", - "jsr:@cd/sqlite@~0.13.1": "0.13.1", "jsr:@deno/gfm@0.12.0": "0.12.0", "jsr:@denosaurs/emoji@~0.3.1": "0.3.1", - "jsr:@denosaurs/plug@1": "1.1.0", "jsr:@std/assert@^1.0.19": "1.0.19", "jsr:@std/cli@^1.0.29": "1.0.29", "jsr:@std/crypto@^1.1.0": "1.1.0", - "jsr:@std/encoding@1": "1.0.10", + "jsr:@std/data-structures@^1.0.11": "1.0.11", "jsr:@std/encoding@^1.0.10": "1.0.10", - "jsr:@std/fmt@1": "1.0.10", "jsr:@std/fmt@^1.0.10": "1.0.10", "jsr:@std/fmt@^1.0.9": "1.0.10", - "jsr:@std/fs@1": "1.0.23", "jsr:@std/fs@^1.0.23": "1.0.23", "jsr:@std/html@^1.0.6": "1.0.6", "jsr:@std/http@^1.0.25": "1.1.0", @@ -27,22 +23,20 @@ "jsr:@std/internal@^1.0.13": "1.0.13", "jsr:@std/media-types@^1.1.0": "1.1.0", "jsr:@std/net@^1.0.6": "1.0.6", - "jsr:@std/path@1": "1.1.4", - "jsr:@std/path@1.0": "1.0.9", "jsr:@std/path@^1.1.4": "1.1.4", "jsr:@std/streams@^1.1.0": "1.1.0", "jsr:@std/testing@^1.0.18": "1.0.18", "npm:@01edu/signal-router@~0.2.3": "0.2.3_@preact+signals@2.9.0__preact@10.29.1_preact@10.29.1", - "npm:@clickhouse/client@^1.18.4": "1.18.4", - "npm:@deno/vite-plugin@^2.0.2": "2.0.2_vite@8.0.10", - "npm:@preact/preset-vite@^2.10.5": "2.10.5_@babel+core@7.29.0_vite@8.0.10_preact@10.29.1", + "npm:@clickhouse/client@^1.18.5": "1.18.5", + "npm:@deno/vite-plugin@^2.0.2": "2.0.2_vite@8.0.13", + "npm:@preact/preset-vite@^2.10.5": "2.10.5_vite@8.0.13", "npm:@preact/signals@^2.9.0": "2.9.0_preact@10.29.1", - "npm:@tailwindcss/vite@^4.2.4": "4.2.4_vite@8.0.10", + "npm:@tailwindcss/vite@^4.3.0": "4.3.0_vite@8.0.13", "npm:daisyui@^5.5.19": "5.5.19", "npm:github-slugger@2": "2.0.0", "npm:he@^1.2.0": "1.2.0", "npm:katex@0.16": "0.16.45", - "npm:lucide-preact@^1.14.0": "1.14.0_preact@10.29.1", + "npm:lucide-preact@^1.16.0": "1.16.0_preact@10.29.1", "npm:marked-alert@^2.1.2": "2.1.2_marked@17.0.6", "npm:marked-footnote@^1.4.0": "1.4.0_marked@17.0.6", "npm:marked-gfm-heading-id@^4.1.3": "4.1.4_marked@17.0.6", @@ -50,16 +44,16 @@ "npm:preact@^10.29.1": "10.29.1", "npm:prismjs@^1.30.0": "1.30.0", "npm:sanitize-html@^2.17.0": "2.17.3", - "npm:tailwindcss@^4.2.4": "4.2.4", - "npm:vite@^8.0.10": "8.0.10", - "npm:vite@^8.0.3": "8.0.10" + "npm:tailwindcss@^4.3.0": "4.3.0", + "npm:vite@^8.0.13": "8.0.13" }, "jsr": { + "@01edu/api@0.2.7": { + "integrity": "17198ab087829f38dafc17e08cd7fd72ce04118a0058019d6af055f5ab3ea244", "@01edu/api@0.2.7": { "integrity": "17198ab087829f38dafc17e08cd7fd72ce04118a0058019d6af055f5ab3ea244", "dependencies": [ "jsr:@01edu/time", - "jsr:@01edu/types", "jsr:@std/fmt@^1.0.9", "jsr:@std/http@^1.0.25" ] @@ -67,32 +61,15 @@ "@01edu/api-client@0.2.6": { "integrity": "b5cd8e30259735734c2345312f335ada0ba4dfe28fc03e33f3ae478b7c810173", "dependencies": [ - "jsr:@01edu/types", "npm:@preact/signals" ] }, "@01edu/api-proxy@0.2.1": { - "integrity": "ea188b029a324c920c22dfe52f6cd389fcd3764124dc405874cdfc9f4d78f271", - "dependencies": [ - "npm:vite@^8.0.3" - ] + "integrity": "ea188b029a324c920c22dfe52f6cd389fcd3764124dc405874cdfc9f4d78f271" }, "@01edu/time@0.1.0": { "integrity": "638ea7d2d00bfbf487e5262b4d6207de7f2101793d0d27a63d492e113a07dcb2" }, - "@01edu/types@0.2.6": { - "integrity": "a4ef7157a43b1fce692687c131a456957395a2ab94ff2d0c593ad0e8d805232f", - "dependencies": [ - "jsr:@cd/sqlite" - ] - }, - "@cd/sqlite@0.13.1": { - "integrity": "b1399921167e28857f15ad2728759d83b28471d6571a4a9ef8cb434fa0a8a9fe", - "dependencies": [ - "jsr:@denosaurs/plug", - "jsr:@std/path@1.0" - ] - }, "@deno/gfm@0.12.0": { "integrity": "9b2d8f3e3d5673da5b2e8613d36cf38619ac5e3bdeb895d35472a16870fb147a", "dependencies": [ @@ -111,15 +88,6 @@ "@denosaurs/emoji@0.3.1": { "integrity": "b0aed5f55dec99e83da7c9637fe0a36d1d6252b7c99deaaa3fc5dea3fcf3da8b" }, - "@denosaurs/plug@1.1.0": { - "integrity": "eb2f0b7546c7bca2000d8b0282c54d50d91cf6d75cb26a80df25a6de8c4bc044", - "dependencies": [ - "jsr:@std/encoding@1", - "jsr:@std/fmt@1", - "jsr:@std/fs@1", - "jsr:@std/path@1" - ] - }, "@std/assert@1.0.19": { "integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e", "dependencies": [ @@ -142,7 +110,7 @@ "integrity": "3ecbae4ce4fee03b180fa710caff36bb5adb66631c46a6460aaad49515565a37", "dependencies": [ "jsr:@std/internal@^1.0.12", - "jsr:@std/path@^1.1.4" + "jsr:@std/path" ] }, "@std/html@1.0.6": { @@ -152,13 +120,13 @@ "integrity": "265cd9a589fea924c5bb0bbed8bebb4bb2fa19129f760bd014e78dbd7a365a51", "dependencies": [ "jsr:@std/cli", - "jsr:@std/encoding@^1.0.10", + "jsr:@std/encoding", "jsr:@std/fmt@^1.0.10", - "jsr:@std/fs@^1.0.23", + "jsr:@std/fs", "jsr:@std/html", "jsr:@std/media-types", "jsr:@std/net", - "jsr:@std/path@^1.1.4", + "jsr:@std/path", "jsr:@std/streams" ] }, @@ -171,9 +139,6 @@ "@std/net@1.0.6": { "integrity": "110735f93e95bb9feb95790a8b1d1bf69ec0dc74f3f97a00a76ea5efea25500c" }, - "@std/path@1.0.9": { - "integrity": "260a49f11edd3db93dd38350bf9cd1b4d1366afa98e81b86167b4e3dd750129e" - }, "@std/path@1.1.4": { "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", "dependencies": [ @@ -187,11 +152,16 @@ "integrity": "d3152f57b11666bf6358d0e127c7e3488e91178b0c2d8fbf0793e1c53cd13cb1", "dependencies": [ "jsr:@std/assert", - "jsr:@std/internal@^1.0.13" + "jsr:@std/data-structures", + "jsr:@std/fs", + "jsr:@std/internal@^1.0.13", + "jsr:@std/path" ] } }, "npm": { + "@01edu/signal-router@0.2.3_@preact+signals@2.9.0__preact@10.29.1_preact@10.29.1": { + "integrity": "sha512-Eg2ORuigaA8i3vM+Lr4k0P7+A53vZ3mKb2wsVnbBslkItaebEFwPUDxXiz2zDCJYHd06wLdvd64r/VRQQw6XFw==", "@01edu/signal-router@0.2.3_@preact+signals@2.9.0__preact@10.29.1_preact@10.29.1": { "integrity": "sha512-Eg2ORuigaA8i3vM+Lr4k0P7+A53vZ3mKb2wsVnbBslkItaebEFwPUDxXiz2zDCJYHd06wLdvd64r/VRQQw6XFw==", "dependencies": [ @@ -353,16 +323,16 @@ "@babel/helper-validator-identifier" ] }, - "@clickhouse/client-common@1.18.4": { - "integrity": "sha512-kPPtv8yQmplNAxfrAJvwBJq5dd+IWRewEbXSpUvtyEJXlrB8lt/ZH63jUS81Nmd+lK5MRvpOFXPoN3iogkvg+A==" + "@clickhouse/client-common@1.18.5": { + "integrity": "sha512-g9LwcS1dvkatKDsIjT1PwUHldsiYzwdKAB0nXfd9APLd+t4PrNJa+my+dXcqJdmcWyhWjKLP/2/ztBwgxp+sbQ==" }, - "@clickhouse/client@1.18.4": { - "integrity": "sha512-jjCrddI+e2OVXGh/MQY92K9r8Z/iwqaZtUXNI/MfZ/y9VGYwfbQsXRzp4Jv6w4Hgxvr4sLcz9YwIvkCBQ6X/mw==", + "@clickhouse/client@1.18.5": { + "integrity": "sha512-4FfoyMkFWhsdNMuXsoEL6l3c12svA63BBJBtDo9SrxRZ14RdmN6jLr/rF3f84BK8cFoxETZCSeKlsbk6NNYebw==", "dependencies": [ "@clickhouse/client-common" ] }, - "@deno/vite-plugin@2.0.2_vite@8.0.10": { + "@deno/vite-plugin@2.0.2_vite@8.0.13": { "integrity": "sha512-bzuKApn9Jr2x1jSrbuJEJzy++8LUwjFVOAopAbepcE3RgYzdcPEWd36PSp7P5dNMQlNnQlgtm3MeNbcKZ/Eh/Q==", "dependencies": [ "@deno/loader@npm:@jsr/deno__loader@0.5.0", @@ -453,10 +423,10 @@ "@tybys/wasm-util" ] }, - "@oxc-project/types@0.127.0": { - "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==" + "@oxc-project/types@0.130.0": { + "integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==" }, - "@preact/preset-vite@2.10.5_@babel+core@7.29.0_vite@8.0.10_preact@10.29.1": { + "@preact/preset-vite@2.10.5_vite@8.0.13": { "integrity": "sha512-p0vJpxiVO7KWWazWny3LUZ+saXyZKWv6Ju0bYMWNJRp2YveufRPgSUB1C4MTqGJfz07EehMgfN+AJNwQy+w6Iw==", "dependencies": [ "@babel/core", @@ -473,8 +443,8 @@ "zimmerframe" ] }, - "@preact/signals-core@1.14.1": { - "integrity": "sha512-vxPpfXqrwUe9lpjqfYNjAF/0RF/eFGeLgdJzdmIIZjpOnTmGmAB4BjWone562mJGMRP4frU6iZ6ei3PDsu52Ng==" + "@preact/signals-core@1.14.2": { + "integrity": "sha512-RZHdBj9ZF4n40Rp4jS052EHHjBWf96P9oNdXPfhQTovCuWY9iQn3Gq+gOTJSgBO9A/JBuPfMOWsSX/lIU9Pc/A==" }, "@preact/signals@2.9.0_preact@10.29.1": { "integrity": "sha512-hYrY0KyUqkDgOl1qba/JGn6y81pXnurn21PMaxfcMwdncdZ3M/oVdmpTvEnsGjh48dIwDVc7bjWHqIsngSjYug==", @@ -495,7 +465,7 @@ "@prefresh/utils@1.2.1": { "integrity": "sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw==" }, - "@prefresh/vite@2.4.12_preact@10.29.1_vite@8.0.10": { + "@prefresh/vite@2.4.12_preact@10.29.1_vite@8.0.13": { "integrity": "sha512-FY1fzXpUjiuosznMV0YM7XAOPZjB5FIdWS0W24+XnlxYkt9hNAwwsiKYn+cuTEoMtD/ZVazS5QVssBr9YhpCQA==", "dependencies": [ "@babel/core", @@ -507,68 +477,68 @@ "vite" ] }, - "@rolldown/binding-android-arm64@1.0.0-rc.17": { - "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "@rolldown/binding-android-arm64@1.0.1": { + "integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==", "os": ["android"], "cpu": ["arm64"] }, - "@rolldown/binding-darwin-arm64@1.0.0-rc.17": { - "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "@rolldown/binding-darwin-arm64@1.0.1": { + "integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==", "os": ["darwin"], "cpu": ["arm64"] }, - "@rolldown/binding-darwin-x64@1.0.0-rc.17": { - "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "@rolldown/binding-darwin-x64@1.0.1": { + "integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==", "os": ["darwin"], "cpu": ["x64"] }, - "@rolldown/binding-freebsd-x64@1.0.0-rc.17": { - "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "@rolldown/binding-freebsd-x64@1.0.1": { + "integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==", "os": ["freebsd"], "cpu": ["x64"] }, - "@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17": { - "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "@rolldown/binding-linux-arm-gnueabihf@1.0.1": { + "integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==", "os": ["linux"], "cpu": ["arm"] }, - "@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17": { - "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "@rolldown/binding-linux-arm64-gnu@1.0.1": { + "integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==", "os": ["linux"], "cpu": ["arm64"] }, - "@rolldown/binding-linux-arm64-musl@1.0.0-rc.17": { - "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "@rolldown/binding-linux-arm64-musl@1.0.1": { + "integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==", "os": ["linux"], "cpu": ["arm64"] }, - "@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17": { - "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "@rolldown/binding-linux-ppc64-gnu@1.0.1": { + "integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==", "os": ["linux"], "cpu": ["ppc64"] }, - "@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17": { - "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "@rolldown/binding-linux-s390x-gnu@1.0.1": { + "integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==", "os": ["linux"], "cpu": ["s390x"] }, - "@rolldown/binding-linux-x64-gnu@1.0.0-rc.17": { - "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", + "@rolldown/binding-linux-x64-gnu@1.0.1": { + "integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==", "os": ["linux"], "cpu": ["x64"] }, - "@rolldown/binding-linux-x64-musl@1.0.0-rc.17": { - "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", + "@rolldown/binding-linux-x64-musl@1.0.1": { + "integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==", "os": ["linux"], "cpu": ["x64"] }, - "@rolldown/binding-openharmony-arm64@1.0.0-rc.17": { - "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "@rolldown/binding-openharmony-arm64@1.0.1": { + "integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==", "os": ["openharmony"], "cpu": ["arm64"] }, - "@rolldown/binding-wasm32-wasi@1.0.0-rc.17": { - "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "@rolldown/binding-wasm32-wasi@1.0.1": { + "integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==", "dependencies": [ "@emnapi/core", "@emnapi/runtime", @@ -576,18 +546,18 @@ ], "cpu": ["wasm32"] }, - "@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17": { - "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "@rolldown/binding-win32-arm64-msvc@1.0.1": { + "integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==", "os": ["win32"], "cpu": ["arm64"] }, - "@rolldown/binding-win32-x64-msvc@1.0.0-rc.17": { - "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", + "@rolldown/binding-win32-x64-msvc@1.0.1": { + "integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==", "os": ["win32"], "cpu": ["x64"] }, - "@rolldown/pluginutils@1.0.0-rc.17": { - "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==" + "@rolldown/pluginutils@1.0.1": { + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==" }, "@rollup/pluginutils@4.2.1": { "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", @@ -604,8 +574,8 @@ "picomatch@4.0.4" ] }, - "@tailwindcss/node@4.2.4": { - "integrity": "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==", + "@tailwindcss/node@4.3.0": { + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", "dependencies": [ "@jridgewell/remapping", "enhanced-resolve", @@ -616,67 +586,67 @@ "tailwindcss" ] }, - "@tailwindcss/oxide-android-arm64@4.2.4": { - "integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==", + "@tailwindcss/oxide-android-arm64@4.3.0": { + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", "os": ["android"], "cpu": ["arm64"] }, - "@tailwindcss/oxide-darwin-arm64@4.2.4": { - "integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==", + "@tailwindcss/oxide-darwin-arm64@4.3.0": { + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", "os": ["darwin"], "cpu": ["arm64"] }, - "@tailwindcss/oxide-darwin-x64@4.2.4": { - "integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==", + "@tailwindcss/oxide-darwin-x64@4.3.0": { + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", "os": ["darwin"], "cpu": ["x64"] }, - "@tailwindcss/oxide-freebsd-x64@4.2.4": { - "integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==", + "@tailwindcss/oxide-freebsd-x64@4.3.0": { + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", "os": ["freebsd"], "cpu": ["x64"] }, - "@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4": { - "integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==", + "@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0": { + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", "os": ["linux"], "cpu": ["arm"] }, - "@tailwindcss/oxide-linux-arm64-gnu@4.2.4": { - "integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==", + "@tailwindcss/oxide-linux-arm64-gnu@4.3.0": { + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", "os": ["linux"], "cpu": ["arm64"] }, - "@tailwindcss/oxide-linux-arm64-musl@4.2.4": { - "integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==", + "@tailwindcss/oxide-linux-arm64-musl@4.3.0": { + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", "os": ["linux"], "cpu": ["arm64"] }, - "@tailwindcss/oxide-linux-x64-gnu@4.2.4": { - "integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==", + "@tailwindcss/oxide-linux-x64-gnu@4.3.0": { + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", "os": ["linux"], "cpu": ["x64"] }, - "@tailwindcss/oxide-linux-x64-musl@4.2.4": { - "integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==", + "@tailwindcss/oxide-linux-x64-musl@4.3.0": { + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", "os": ["linux"], "cpu": ["x64"] }, - "@tailwindcss/oxide-wasm32-wasi@4.2.4": { - "integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==", + "@tailwindcss/oxide-wasm32-wasi@4.3.0": { + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", "cpu": ["wasm32"] }, - "@tailwindcss/oxide-win32-arm64-msvc@4.2.4": { - "integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==", + "@tailwindcss/oxide-win32-arm64-msvc@4.3.0": { + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", "os": ["win32"], "cpu": ["arm64"] }, - "@tailwindcss/oxide-win32-x64-msvc@4.2.4": { - "integrity": "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==", + "@tailwindcss/oxide-win32-x64-msvc@4.3.0": { + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", "os": ["win32"], "cpu": ["x64"] }, - "@tailwindcss/oxide@4.2.4": { - "integrity": "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==", + "@tailwindcss/oxide@4.3.0": { + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", "optionalDependencies": [ "@tailwindcss/oxide-android-arm64", "@tailwindcss/oxide-darwin-arm64", @@ -692,8 +662,8 @@ "@tailwindcss/oxide-win32-x64-msvc" ] }, - "@tailwindcss/vite@4.2.4_vite@8.0.10": { - "integrity": "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==", + "@tailwindcss/vite@4.3.0_vite@8.0.13": { + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", "dependencies": [ "@tailwindcss/node", "@tailwindcss/oxide", @@ -799,8 +769,8 @@ "electron-to-chromium@1.5.349": { "integrity": "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==" }, - "enhanced-resolve@5.21.0": { - "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==", + "enhanced-resolve@5.21.3": { + "integrity": "sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==", "dependencies": [ "graceful-fs", "tapable" @@ -860,8 +830,8 @@ "is-plain-object@5.0.0": { "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" }, - "jiti@2.6.1": { - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "jiti@2.7.0": { + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "bin": true }, "js-tokens@4.0.0": { @@ -965,8 +935,8 @@ "yallist" ] }, - "lucide-preact@1.14.0_preact@10.29.1": { - "integrity": "sha512-d3N5MpFSukscLyPPtZEeYvSIWohLEj/2OJvjrYovWOTKa40bsHaALX9sIDS6skSu+J6bwYRqfwX6GAxlErfSmQ==", + "lucide-preact@1.16.0_preact@10.29.1": { + "integrity": "sha512-Sa1XSig6iWCFhdrqS+vT1EFJH4Yos2Q7H/EMawql1K+D9qjNRzjMutPySak0iMYto6PneHYAWoxnDBBlryjngw==", "dependencies": [ "preact" ] @@ -1049,8 +1019,8 @@ "prismjs@1.30.0": { "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==" }, - "rolldown@1.0.0-rc.17": { - "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", + "rolldown@1.0.1": { + "integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==", "dependencies": [ "@oxc-project/types", "@rolldown/pluginutils" @@ -1104,8 +1074,8 @@ "stack-trace@1.0.0": { "integrity": "sha512-H6D7134xi6qONvh7ZHKgviXf+rd3vhGBSvebPZCaUkd8zvQ+7PtDw6CljPTe4cXWNf2IKZGNqw6VJXSb9IgBpA==" }, - "tailwindcss@4.2.4": { - "integrity": "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==" + "tailwindcss@4.3.0": { + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==" }, "tapable@2.3.3": { "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==" @@ -1129,7 +1099,7 @@ ], "bin": true }, - "vite-prerender-plugin@0.5.13_vite@8.0.10": { + "vite-prerender-plugin@0.5.13_vite@8.0.13": { "integrity": "sha512-IKSpYkzDBsKAxa05naRbj7GvNVMSdww/Z/E89oO3xndz+gWnOBOKOAbEXv7qDhktY/j3vHgJmoV1pPzqU2tx9g==", "dependencies": [ "kolorist", @@ -1141,8 +1111,8 @@ "vite" ] }, - "vite@8.0.10": { - "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", + "vite@8.0.13": { + "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==", "dependencies": [ "lightningcss", "picomatch@4.0.4", @@ -1170,6 +1140,7 @@ "jsr:@01edu/api-client@~0.2.6", "jsr:@01edu/api-proxy@~0.2.1", "jsr:@01edu/api@~0.2.7", + "jsr:@01edu/api@~0.2.7", "jsr:@01edu/time@0.1", "jsr:@deno/gfm@0.12.0", "jsr:@std/assert@^1.0.19", @@ -1181,16 +1152,16 @@ "jsr:@std/path@^1.1.4", "jsr:@std/testing@^1.0.18", "npm:@01edu/signal-router@~0.2.3", - "npm:@clickhouse/client@^1.18.4", + "npm:@clickhouse/client@^1.18.5", "npm:@deno/vite-plugin@^2.0.2", "npm:@preact/preset-vite@^2.10.5", "npm:@preact/signals@^2.9.0", - "npm:@tailwindcss/vite@^4.2.4", + "npm:@tailwindcss/vite@^4.3.0", "npm:daisyui@^5.5.19", - "npm:lucide-preact@^1.14.0", + "npm:lucide-preact@^1.16.0", "npm:preact@^10.29.1", - "npm:tailwindcss@^4.2.4", - "npm:vite@^8.0.10" + "npm:tailwindcss@^4.3.0", + "npm:vite@^8.0.13" ] } } From e8f5220ce449dd3c5cf7143aa19c44d24c0aa471 Mon Sep 17 00:00:00 2001 From: Abdou TOP Date: Fri, 15 May 2026 14:50:36 +0000 Subject: [PATCH 5/5] Update dependencies in deno.lock for version consistency and improvements --- deno.lock | 62 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/deno.lock b/deno.lock index 4a6d588..744d22d 100644 --- a/deno.lock +++ b/deno.lock @@ -4,7 +4,6 @@ "jsr:@01edu/api-client@~0.2.6": "0.2.6", "jsr:@01edu/api-proxy@~0.2.1": "0.2.1", "jsr:@01edu/api@~0.2.7": "0.2.7", - "jsr:@01edu/api@~0.2.7": "0.2.7", "jsr:@01edu/time@0.1": "0.1.0", "jsr:@deno/gfm@0.12.0": "0.12.0", "jsr:@denosaurs/emoji@~0.3.1": "0.3.1", @@ -29,13 +28,13 @@ "npm:@01edu/signal-router@~0.2.3": "0.2.3_@preact+signals@2.9.0__preact@10.29.1_preact@10.29.1", "npm:@clickhouse/client@^1.18.5": "1.18.5", "npm:@deno/vite-plugin@^2.0.2": "2.0.2_vite@8.0.13", - "npm:@preact/preset-vite@^2.10.5": "2.10.5_vite@8.0.13", + "npm:@preact/preset-vite@^2.10.5": "2.10.5_@babel+core@7.29.0_vite@8.0.13_preact@10.29.1", "npm:@preact/signals@^2.9.0": "2.9.0_preact@10.29.1", "npm:@tailwindcss/vite@^4.3.0": "4.3.0_vite@8.0.13", "npm:daisyui@^5.5.19": "5.5.19", "npm:github-slugger@2": "2.0.0", "npm:he@^1.2.0": "1.2.0", - "npm:katex@0.16": "0.16.45", + "npm:katex@0.16": "0.16.46", "npm:lucide-preact@^1.16.0": "1.16.0_preact@10.29.1", "npm:marked-alert@^2.1.2": "2.1.2_marked@17.0.6", "npm:marked-footnote@^1.4.0": "1.4.0_marked@17.0.6", @@ -43,13 +42,11 @@ "npm:marked@^17.0.1": "17.0.6", "npm:preact@^10.29.1": "10.29.1", "npm:prismjs@^1.30.0": "1.30.0", - "npm:sanitize-html@^2.17.0": "2.17.3", + "npm:sanitize-html@^2.17.0": "2.17.4", "npm:tailwindcss@^4.3.0": "4.3.0", "npm:vite@^8.0.13": "8.0.13" }, "jsr": { - "@01edu/api@0.2.7": { - "integrity": "17198ab087829f38dafc17e08cd7fd72ce04118a0058019d6af055f5ab3ea244", "@01edu/api@0.2.7": { "integrity": "17198ab087829f38dafc17e08cd7fd72ce04118a0058019d6af055f5ab3ea244", "dependencies": [ @@ -100,6 +97,9 @@ "@std/crypto@1.1.0": { "integrity": "b8d6d0a6377a32b213af2661ed7bf1062d94feac0c57def5526a8e74a95c3ec8" }, + "@std/data-structures@1.0.11": { + "integrity": "53b98ed7efa61f107dfc14244bd2ec5557f7f7ee0bbaef6d449d7937facacb89" + }, "@std/encoding@1.0.10": { "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" }, @@ -160,8 +160,6 @@ } }, "npm": { - "@01edu/signal-router@0.2.3_@preact+signals@2.9.0__preact@10.29.1_preact@10.29.1": { - "integrity": "sha512-Eg2ORuigaA8i3vM+Lr4k0P7+A53vZ3mKb2wsVnbBslkItaebEFwPUDxXiz2zDCJYHd06wLdvd64r/VRQQw6XFw==", "@01edu/signal-router@0.2.3_@preact+signals@2.9.0__preact@10.29.1_preact@10.29.1": { "integrity": "sha512-Eg2ORuigaA8i3vM+Lr4k0P7+A53vZ3mKb2wsVnbBslkItaebEFwPUDxXiz2zDCJYHd06wLdvd64r/VRQQw6XFw==", "dependencies": [ @@ -426,7 +424,7 @@ "@oxc-project/types@0.130.0": { "integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==" }, - "@preact/preset-vite@2.10.5_vite@8.0.13": { + "@preact/preset-vite@2.10.5_@babel+core@7.29.0_vite@8.0.13_preact@10.29.1": { "integrity": "sha512-p0vJpxiVO7KWWazWny3LUZ+saXyZKWv6Ju0bYMWNJRp2YveufRPgSUB1C4MTqGJfz07EehMgfN+AJNwQy+w6Iw==", "dependencies": [ "@babel/core", @@ -456,8 +454,8 @@ "@prefresh/babel-plugin@0.5.3": { "integrity": "sha512-57LX2SHs4BX2s1IwCjNzTE2OJeEepRCNf1VTEpbNcUyHfMO68eeOWGDIt4ob9aYlW6PEWZ1SuwNikuoIXANDtQ==" }, - "@prefresh/core@1.5.9_preact@10.29.1": { - "integrity": "sha512-IKBKCPaz34OFVC+adiQ2qaTF5qdztO2/4ZPf4KsRTgjKosWqxVXmEbxCiUydYZRY8GVie+DQlKzQr9gt6HQ+EQ==", + "@prefresh/core@1.5.10_preact@10.29.1": { + "integrity": "sha512-7yPTFbG56sutaFu8krp3B4a200KOFUvrtlllKWRuLjsYXo9UUucHOZRcer+gtgMkFTpv6ob8TGcTwA32bSwa1w==", "dependencies": [ "preact" ] @@ -677,8 +675,8 @@ "tslib" ] }, - "@types/estree@1.0.8": { - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + "@types/estree@1.0.9": { + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==" }, "babel-plugin-transform-hook-names@1.0.2_@babel+core@7.29.0": { "integrity": "sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==", @@ -686,8 +684,8 @@ "@babel/core" ] }, - "baseline-browser-mapping@2.10.27": { - "integrity": "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==", + "baseline-browser-mapping@2.10.29": { + "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", "bin": true }, "boolbase@1.0.0": { @@ -704,8 +702,8 @@ ], "bin": true }, - "caniuse-lite@1.0.30001791": { - "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==" + "caniuse-lite@1.0.30001792": { + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==" }, "commander@8.3.0": { "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" @@ -729,6 +727,9 @@ "daisyui@5.5.19": { "integrity": "sha512-pbFAkl1VCEh/MPCeclKL61I/MqRIFFhNU7yiXoDDRapXN4/qNCoMxeCCswyxEEhqL5eiTTfwHvucFtOE71C9sA==" }, + "dayjs@1.11.20": { + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==" + }, "debug@4.4.3": { "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dependencies": [ @@ -766,8 +767,8 @@ "domhandler" ] }, - "electron-to-chromium@1.5.349": { - "integrity": "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==" + "electron-to-chromium@1.5.355": { + "integrity": "sha512-LUPZhKzZPYSPme1jEYohpkA+ybYCJztr1quAdBd7E7h3+VOBVcKkwwtBJu41nrjawrRzfb8mtMfzWozoaK0ZIQ==" }, "enhanced-resolve@5.21.3": { "integrity": "sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==", @@ -845,8 +846,8 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "bin": true }, - "katex@0.16.45": { - "integrity": "sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==", + "katex@0.16.46": { + "integrity": "sha512-WHy4Coo+bGZyH7NwJKHkS04YFsFcarWbAEOAC3EMndzdN6VSZqklLLIgfxzyaW9jDoeGYJX9SWbJPKpecox0Uw==", "dependencies": [ "commander" ], @@ -855,6 +856,12 @@ "kolorist@1.8.0": { "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==" }, + "launder@1.7.1": { + "integrity": "sha512-mU6WRz5EusL9ZZuiZ5SO4Y6C0P9PAUR9iwdb6bzj4KDihm28DiHFw+/yk9DBH4f+Pv1wuzQ4e2jV3oQ7mkIqvw==", + "dependencies": [ + "dayjs" + ] + }, "lightningcss-android-arm64@1.32.0": { "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", "os": ["android"], @@ -984,8 +991,8 @@ "he" ] }, - "node-releases@2.0.38": { - "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==" + "node-releases@2.0.44": { + "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==" }, "nth-check@2.1.1": { "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", @@ -1044,13 +1051,14 @@ ], "bin": true }, - "sanitize-html@2.17.3": { - "integrity": "sha512-Kn4srCAo2+wZyvCNKCSyB2g8RQ8IkX/gQs2uqoSRNu5t9I2qvUyAVvRDiFUVAiX3N3PNuwStY0eNr+ooBHVWEg==", + "sanitize-html@2.17.4": { + "integrity": "sha512-2HW7v2ol/uAM7sX4hbD8Z59OGWmAPrvjL8E71UWlBcj6m+kcF6ilQBLny+cIgY214QJeJT5tQuxKKqX0SQqjGQ==", "dependencies": [ "deepmerge", "escape-string-regexp", "htmlparser2", "is-plain-object", + "launder", "parse-srcset", "postcss" ] @@ -1132,15 +1140,11 @@ "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==" } }, - "remote": { - "https://gistcdn.githack.com/kigiri/21df06d173fcdced5281b86ba6ac1382/raw/crypto.js": "e85976e655898538dbade9d87b05ca0a6bb167b3128cd4098622000a582f5f6d" - }, "workspace": { "dependencies": [ "jsr:@01edu/api-client@~0.2.6", "jsr:@01edu/api-proxy@~0.2.1", "jsr:@01edu/api@~0.2.7", - "jsr:@01edu/api@~0.2.7", "jsr:@01edu/time@0.1", "jsr:@deno/gfm@0.12.0", "jsr:@std/assert@^1.0.19",