| 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); |
| |
| |
| if (!existsSync(dir)) { |
| mkdirSync(dir, { recursive: true }); |
| } |
|
|
| |
| 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 { |
| 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'); |
| } |
| } |
|
|
| |
| |
| |
| createLink(githubUserId: string, githubLogin: string, huggingfaceUsername: string, huggingfaceAccessToken?: string): AccountLink { |
| const data = this.readData(); |
| const now = new Date().toISOString(); |
|
|
| |
| 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; |
| } |
|
|
| |
| |
| |
| findByGitHubUser(githubUserId: string): AccountLink | null { |
| const data = this.readData(); |
| return data.links.find(link => link.githubUserId === githubUserId) || null; |
| } |
|
|
| |
| |
| |
| findByHuggingFaceUser(huggingfaceUsername: string): AccountLink | null { |
| const data = this.readData(); |
| return data.links.find(link => link.huggingfaceUsername === huggingfaceUsername) || null; |
| } |
|
|
| |
| |
| |
| updateLink(githubUserId: string, updates: Partial<Omit<AccountLink, 'githubUserId' | 'linkedAt'>>): 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; |
| } |
| |
| |
| |
| |
| updateAccessToken(githubUserId: string, accessToken: string): AccountLink { |
| return this.updateLink(githubUserId, { huggingfaceAccessToken: accessToken }); |
| } |
|
|
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| getAllLinks(): AccountLink[] { |
| const data = this.readData(); |
| return data.links; |
| } |
|
|
| |
| |
| |
| getStats(): { totalLinks: number; lastModified: string } { |
| const data = this.readData(); |
| return { |
| totalLinks: data.links.length, |
| lastModified: data.metadata.lastModified, |
| }; |
| } |
|
|
| |
| |
| |
| 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 }; |
| } |
| } |
|
|
| |
| export const accountLinkingService = new AccountLinkingService(); |
|
|