|
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(); |
|
|