import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'; import { dirname } from 'path'; export interface AccountLink { githubUserId: string; githubLogin: string; huggingfaceUsername: string; huggingfaceAccessToken?: string; linkedAt: string; lastUpdated: string; } export interface AccountLinksData { links: AccountLink[]; metadata: { version: string; createdAt: string; lastModified: string; }; } export class AccountLinkingService { private filePath: string; constructor() { this.filePath = process.env.ACCOUNT_LINKS_FILE || '/tmp/account-links.json'; this.ensureFileExists(); } private ensureFileExists(): void { const dir = dirname(this.filePath); // Create directory if it doesn't exist if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); } // Create file if it doesn't exist if (!existsSync(this.filePath)) { const initialData: AccountLinksData = { links: [], metadata: { version: '1.0.0', createdAt: new Date().toISOString(), lastModified: new Date().toISOString(), }, }; this.writeData(initialData); } } private readData(): AccountLinksData { try { const content = readFileSync(this.filePath, 'utf-8'); return JSON.parse(content); } catch (error) { console.error('Error reading account links file:', error); // Return default structure if file is corrupted return { links: [], metadata: { version: '1.0.0', createdAt: new Date().toISOString(), lastModified: new Date().toISOString(), }, }; } } private writeData(data: AccountLinksData): void { try { data.metadata.lastModified = new Date().toISOString(); writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf-8'); } catch (error) { console.error('Error writing account links file:', error); throw new Error('Failed to save account links'); } } /** * Create a new account link */ createLink(githubUserId: string, githubLogin: string, huggingfaceUsername: string, huggingfaceAccessToken?: string): AccountLink { const data = this.readData(); const now = new Date().toISOString(); // Check for existing links const existingGithubLink = data.links.find(link => link.githubUserId === githubUserId); const existingHfLink = data.links.find(link => link.huggingfaceUsername === huggingfaceUsername); if (existingGithubLink) { throw new Error(`GitHub user ${githubLogin} is already linked to HuggingFace user ${existingGithubLink.huggingfaceUsername}`); } if (existingHfLink) { throw new Error(`HuggingFace user ${huggingfaceUsername} is already linked to GitHub user ${existingHfLink.githubLogin}`); } const newLink: AccountLink = { githubUserId, githubLogin, huggingfaceUsername, huggingfaceAccessToken, linkedAt: now, lastUpdated: now, }; data.links.push(newLink); this.writeData(data); console.log(`✅ Created account link: ${githubLogin} ↔ ${huggingfaceUsername}`); return newLink; } /** * Find account link by GitHub user */ findByGitHubUser(githubUserId: string): AccountLink | null { const data = this.readData(); return data.links.find(link => link.githubUserId === githubUserId) || null; } /** * Find account link by HuggingFace user */ findByHuggingFaceUser(huggingfaceUsername: string): AccountLink | null { const data = this.readData(); return data.links.find(link => link.huggingfaceUsername === huggingfaceUsername) || null; } /** * Update an existing account link */ updateLink(githubUserId: string, updates: Partial>): AccountLink { const data = this.readData(); const linkIndex = data.links.findIndex(link => link.githubUserId === githubUserId); if (linkIndex === -1) { throw new Error(`No account link found for GitHub user ID: ${githubUserId}`); } const existingLink = data.links[linkIndex]; const updatedLink: AccountLink = { ...existingLink, ...updates, lastUpdated: new Date().toISOString(), }; data.links[linkIndex] = updatedLink; this.writeData(data); console.log(`✅ Updated account link for GitHub user: ${existingLink.githubLogin}`); return updatedLink; } /** * Update HuggingFace access token for an existing account link */ updateAccessToken(githubUserId: string, accessToken: string): AccountLink { return this.updateLink(githubUserId, { huggingfaceAccessToken: accessToken }); } /** * Remove an account link */ removeLink(githubUserId: string): boolean { const data = this.readData(); const initialLength = data.links.length; data.links = data.links.filter(link => link.githubUserId !== githubUserId); if (data.links.length < initialLength) { this.writeData(data); console.log(`✅ Removed account link for GitHub user ID: ${githubUserId}`); return true; } return false; } /** * Get all account links */ getAllLinks(): AccountLink[] { const data = this.readData(); return data.links; } /** * Get linking statistics */ getStats(): { totalLinks: number; lastModified: string } { const data = this.readData(); return { totalLinks: data.links.length, lastModified: data.metadata.lastModified, }; } /** * Check if accounts can be linked (no conflicts) */ canLink(githubUserId: string, huggingfaceUsername: string): { canLink: boolean; reason?: string } { const data = this.readData(); const existingGithubLink = data.links.find(link => link.githubUserId === githubUserId); if (existingGithubLink) { return { canLink: false, reason: `GitHub account is already linked to HuggingFace user: ${existingGithubLink.huggingfaceUsername}`, }; } const existingHfLink = data.links.find(link => link.huggingfaceUsername === huggingfaceUsername); if (existingHfLink) { return { canLink: false, reason: `HuggingFace account is already linked to GitHub user: ${existingHfLink.githubLogin}`, }; } return { canLink: true }; } } // Singleton instance export const accountLinkingService = new AccountLinkingService();