Sifa professional network API (Fastify, AT Protocol, Jetstream) sifa.id/
at main 71 lines 2.0 kB view raw
1import type { Database } from '../db/index.js'; 2import { canonicalSkills, unresolvedSkills } from '../db/schema/index.js'; 3import { eq, sql } from 'drizzle-orm'; 4import { logger } from '../logger.js'; 5 6/** Normalize a skill name for matching: lowercase, trim, collapse whitespace */ 7export function normalizeSkillName(name: string): string { 8 return name.toLowerCase().trim().replace(/\s+/g, ' '); 9} 10 11/** Create a URL-safe slug from a skill name */ 12export function createSlug(name: string): string { 13 return name 14 .toLowerCase() 15 .trim() 16 .replace(/c\+\+/gi, 'c-plus-plus') 17 .replace(/c#/gi, 'c-sharp') 18 .replace(/\.net/gi, 'dot-net') 19 .replace(/[^a-z0-9]+/g, '-') 20 .replace(/-+/g, '-') 21 .replace(/^-|-$/g, ''); 22} 23 24/** 25 * Resolve a user-entered skill name to a canonical skill. 26 * Pipeline: normalize -> check slug match -> check aliases -> queue as unresolved. 27 * Returns the canonical skill row if matched, null if unresolved. 28 */ 29export async function resolveSkill( 30 db: Database, 31 rawName: string, 32): Promise<typeof canonicalSkills.$inferSelect | null> { 33 const normalized = normalizeSkillName(rawName); 34 35 // 1. Exact match on slug 36 const bySlug = await db 37 .select() 38 .from(canonicalSkills) 39 .where(eq(canonicalSkills.slug, createSlug(rawName))) 40 .limit(1); 41 if (bySlug[0]) { 42 return bySlug[0]; 43 } 44 45 // 2. Check aliases array (any canonical_skills row where normalized name is in aliases) 46 const byAlias = await db 47 .select() 48 .from(canonicalSkills) 49 .where(sql`${normalized} = ANY(${canonicalSkills.aliases})`) 50 .limit(1); 51 if (byAlias[0]) { 52 return byAlias[0]; 53 } 54 55 // 3. No match -- add to unresolved queue 56 await db 57 .insert(unresolvedSkills) 58 .values({ 59 rawName, 60 normalizedName: normalized, 61 }) 62 .onConflictDoUpdate({ 63 target: unresolvedSkills.normalizedName, 64 set: { 65 occurrences: sql`${unresolvedSkills.occurrences} + 1`, 66 }, 67 }); 68 69 logger.info({ rawName, normalized }, 'Skill queued as unresolved'); 70 return null; 71}