| |
| |
| |
| |
| |
| |
|
|
| import { Project, VirtualFile, CustomTemplate } from './types'; |
| import { Skill } from './skills/types'; |
| import type { ModelTemplate } from '@/lib/llm/models/assignment'; |
| import type { CustomConnection } from '@/lib/llm/providers/connection-record'; |
| import { EnhancedSyncStatusResponse } from './sync-types'; |
|
|
| export interface SyncResult { |
| success: boolean; |
| error?: string; |
| } |
|
|
| export interface ProjectSyncResult extends SyncResult { |
| project?: Project; |
| } |
|
|
| export interface FilesSyncResult extends SyncResult { |
| count?: number; |
| } |
|
|
| export interface ProjectListSyncResult extends SyncResult { |
| projects?: Project[]; |
| } |
|
|
| export interface FilesListSyncResult extends SyncResult { |
| files?: VirtualFile[]; |
| } |
|
|
| export interface SkillSyncResult extends SyncResult { |
| skill?: Skill; |
| action?: 'created' | 'updated'; |
| } |
|
|
| export interface SkillsListSyncResult extends SyncResult { |
| skills?: Skill[]; |
| created?: number; |
| updated?: number; |
| } |
|
|
| export interface TemplateSyncResult extends SyncResult { |
| template?: CustomTemplate; |
| action?: 'created' | 'updated'; |
| } |
|
|
| export interface TemplatesListSyncResult extends SyncResult { |
| templates?: CustomTemplate[]; |
| created?: number; |
| updated?: number; |
| } |
|
|
| export interface ModelTemplateSyncResult extends SyncResult { |
| modelTemplate?: ModelTemplate; |
| action?: 'created' | 'updated'; |
| } |
|
|
| export interface ModelTemplatesListSyncResult extends SyncResult { |
| modelTemplates?: ModelTemplate[]; |
| created?: number; |
| updated?: number; |
| } |
|
|
| export interface InterviewTemplateSyncResult extends SyncResult { |
| interviewTemplate?: import('@/lib/interview/types').InterviewTemplate; |
| action?: 'created' | 'updated'; |
| } |
| export interface InterviewTemplatesListSyncResult extends SyncResult { |
| interviewTemplates?: import('@/lib/interview/types').InterviewTemplate[]; |
| created?: number; |
| updated?: number; |
| } |
|
|
| export interface ConnectionSyncResult extends SyncResult { |
| connection?: CustomConnection; |
| action?: 'created' | 'updated'; |
| } |
|
|
| export interface ConnectionsListSyncResult extends SyncResult { |
| connections?: CustomConnection[]; |
| } |
|
|
| |
| function serializeFileContent(file: VirtualFile): VirtualFile & { _isBinaryBase64?: boolean } { |
| if (file.content instanceof ArrayBuffer) { |
| const bytes = new Uint8Array(file.content); |
| let binary = ''; |
| for (let i = 0; i < bytes.byteLength; i++) { |
| binary += String.fromCharCode(bytes[i]); |
| } |
| return { ...file, content: btoa(binary), _isBinaryBase64: true }; |
| } |
| return file; |
| } |
|
|
| |
| function deserializeFileContent(file: VirtualFile & { _isBinaryBase64?: boolean }): VirtualFile { |
| if (file._isBinaryBase64 && typeof file.content === 'string') { |
| const binaryString = atob(file.content); |
| const bytes = new Uint8Array(binaryString.length); |
| for (let i = 0; i < binaryString.length; i++) { |
| bytes[i] = binaryString.charCodeAt(i); |
| } |
| const { _isBinaryBase64, ...rest } = file; |
| return { ...rest, content: bytes.buffer }; |
| } |
| const { _isBinaryBase64, ...rest } = file; |
| return rest; |
| } |
|
|
| |
| |
| |
| export class SyncManager { |
| private baseUrl: string; |
| workspaceId?: string; |
|
|
| constructor(baseUrl = '') { |
| this.baseUrl = baseUrl; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| private getApiUrl(path: string): string { |
| if (this.workspaceId) { |
| return `/api/w/${this.workspaceId}${path}`; |
| } |
| return `/api${path}`; |
| } |
|
|
| |
| |
| |
| async pushProject(project: Project): Promise<ProjectSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl('/sync/projects')}`, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ project }), |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| const data = await response.json(); |
| return { |
| success: true, |
| project: data.project, |
| }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
| async pullProjects(): Promise<ProjectListSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl('/sync/projects')}`, { |
| method: 'GET', |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| const data = await response.json(); |
| return { |
| success: true, |
| projects: data.projects, |
| }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
| async pushFiles(projectId: string, files: VirtualFile[]): Promise<FilesSyncResult> { |
| try { |
| const serializedFiles = files.map(serializeFileContent); |
|
|
| const response = await fetch(`${this.baseUrl}${this.getApiUrl('/sync/files')}`, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ projectId, files: serializedFiles }), |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| const data = await response.json(); |
| return { |
| success: true, |
| count: data.count, |
| }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
| async pullFiles(projectId: string): Promise<FilesListSyncResult> { |
| try { |
| const response = await fetch( |
| `${this.baseUrl}${this.getApiUrl('/sync/files')}?projectId=${encodeURIComponent(projectId)}`, |
| { |
| method: 'GET', |
| } |
| ); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| const data = await response.json(); |
|
|
| return { |
| success: true, |
| files: (data.files || []).map(deserializeFileContent), |
| }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
| async pushProjectWithFiles( |
| project: Project, |
| files: VirtualFile[] |
| ): Promise<SyncResult> { |
| |
| const projectResult = await this.pushProject(project); |
| if (!projectResult.success) { |
| return projectResult; |
| } |
|
|
| |
| const filesResult = await this.pushFiles(project.id, files); |
| if (!filesResult.success) { |
| return filesResult; |
| } |
|
|
| return { success: true }; |
| } |
|
|
| |
| |
| |
| |
| async pullProjectWithFiles(projectId: string): Promise<{ |
| success: boolean; |
| error?: string; |
| project?: Project; |
| files?: VirtualFile[]; |
| }> { |
| |
| const projectsResult = await this.pullProjects(); |
| if (!projectsResult.success || !projectsResult.projects) { |
| return { |
| success: false, |
| error: projectsResult.error || 'Failed to pull projects', |
| }; |
| } |
|
|
| const project = projectsResult.projects.find((p) => p.id === projectId); |
| if (!project) { |
| return { |
| success: false, |
| error: `Project ${projectId} not found on server`, |
| }; |
| } |
|
|
| |
| const filesResult = await this.pullFiles(projectId); |
| if (!filesResult.success) { |
| return { |
| success: false, |
| error: filesResult.error || 'Failed to pull files', |
| }; |
| } |
|
|
| return { |
| success: true, |
| project, |
| files: filesResult.files || [], |
| }; |
| } |
|
|
| |
| |
| |
| async pushSingleProject(projectId: string, project: Project, files: VirtualFile[]): Promise<ProjectSyncResult> { |
| try { |
| const serializedFiles = files.map(serializeFileContent); |
|
|
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/projects/${projectId}`)}`, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ project, files: serializedFiles }), |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| const data = await response.json(); |
| return { |
| success: true, |
| project: data.project, |
| }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
| async pullSingleProject(projectId: string): Promise<{ |
| success: boolean; |
| error?: string; |
| project?: Project; |
| files?: VirtualFile[]; |
| }> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/projects/${projectId}`)}`, { |
| method: 'GET', |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| const data = await response.json(); |
|
|
| return { |
| success: true, |
| project: data.project, |
| files: (data.files || []).map(deserializeFileContent), |
| }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
| async getSyncStatus(): Promise<{ |
| success: boolean; |
| error?: string; |
| projects?: Array<{ id: string; updatedAt: string }>; |
| }> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl('/sync/status')}`, { |
| method: 'GET', |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| const data = await response.json(); |
| return { |
| success: true, |
| projects: data.projects || [], |
| }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| async pullSkills(): Promise<SkillsListSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl('/sync/skills')}`, { |
| method: 'GET', |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| const data = await response.json(); |
| return { |
| success: true, |
| skills: data.skills || [], |
| }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
| async pushSkills(skills: Skill[]): Promise<SkillsListSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl('/sync/skills')}`, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ skills }), |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| const data = await response.json(); |
| return { |
| success: data.success, |
| created: data.created, |
| updated: data.updated, |
| error: data.errors?.join(', '), |
| }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
| async pullSkill(id: string): Promise<SkillSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/skills/${encodeURIComponent(id)}`)}`, { |
| method: 'GET', |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| const data = await response.json(); |
| return { |
| success: true, |
| skill: data.skill, |
| }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
| async pushSkill(skill: Skill): Promise<SkillSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/skills/${encodeURIComponent(skill.id)}`)}`, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ skill }), |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| const data = await response.json(); |
| return { |
| success: true, |
| skill: data.skill, |
| action: data.action, |
| }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
| async deleteSkillFromServer(id: string): Promise<SyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/skills/${encodeURIComponent(id)}`)}`, { |
| method: 'DELETE', |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| return { success: true }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
|
|
| |
| async pullModelTemplates(): Promise<ModelTemplatesListSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl('/sync/model-templates')}`, { method: 'GET' }); |
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { success: false, error: errorData.error || `HTTP ${response.status}` }; |
| } |
| const data = await response.json(); |
| return { success: true, modelTemplates: data.modelTemplates || [] }; |
| } catch (error) { |
| return { success: false, error: error instanceof Error ? error.message : 'Network error' }; |
| } |
| } |
|
|
| |
| async pushModelTemplates(modelTemplates: ModelTemplate[]): Promise<ModelTemplatesListSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl('/sync/model-templates')}`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ modelTemplates }), |
| }); |
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { success: false, error: errorData.error || `HTTP ${response.status}` }; |
| } |
| const data = await response.json(); |
| return { success: data.success, created: data.created, updated: data.updated, error: data.errors?.join(', ') }; |
| } catch (error) { |
| return { success: false, error: error instanceof Error ? error.message : 'Network error' }; |
| } |
| } |
|
|
| |
| async pullModelTemplate(id: string): Promise<ModelTemplateSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/model-templates/${encodeURIComponent(id)}`)}`, { method: 'GET' }); |
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { success: false, error: errorData.error || `HTTP ${response.status}` }; |
| } |
| const data = await response.json(); |
| return { success: true, modelTemplate: data.modelTemplate }; |
| } catch (error) { |
| return { success: false, error: error instanceof Error ? error.message : 'Network error' }; |
| } |
| } |
|
|
| |
| async pushModelTemplate(modelTemplate: ModelTemplate): Promise<ModelTemplateSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/model-templates/${encodeURIComponent(modelTemplate.id)}`)}`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ modelTemplate }), |
| }); |
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { success: false, error: errorData.error || `HTTP ${response.status}` }; |
| } |
| const data = await response.json(); |
| return { success: true, modelTemplate: data.modelTemplate, action: data.action }; |
| } catch (error) { |
| return { success: false, error: error instanceof Error ? error.message : 'Network error' }; |
| } |
| } |
|
|
| |
| async deleteModelTemplateFromServer(id: string): Promise<SyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/model-templates/${encodeURIComponent(id)}`)}`, { method: 'DELETE' }); |
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { success: false, error: errorData.error || `HTTP ${response.status}` }; |
| } |
| return { success: true }; |
| } catch (error) { |
| return { success: false, error: error instanceof Error ? error.message : 'Network error' }; |
| } |
| } |
|
|
| |
| |
| |
|
|
| |
| async pullInterviewTemplates(): Promise<InterviewTemplatesListSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl('/sync/interview-templates')}`, { method: 'GET' }); |
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { success: false, error: errorData.error || `HTTP ${response.status}` }; |
| } |
| const data = await response.json(); |
| return { success: true, interviewTemplates: data.interviewTemplates || [] }; |
| } catch (error) { |
| return { success: false, error: error instanceof Error ? error.message : 'Network error' }; |
| } |
| } |
|
|
| |
| async pushInterviewTemplates(interviewTemplates: import('@/lib/interview/types').InterviewTemplate[]): Promise<InterviewTemplatesListSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl('/sync/interview-templates')}`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ interviewTemplates }), |
| }); |
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { success: false, error: errorData.error || `HTTP ${response.status}` }; |
| } |
| const data = await response.json(); |
| return { success: data.success, created: data.created, updated: data.updated, error: data.errors?.join(', ') }; |
| } catch (error) { |
| return { success: false, error: error instanceof Error ? error.message : 'Network error' }; |
| } |
| } |
|
|
| |
| async pullInterviewTemplate(id: string): Promise<InterviewTemplateSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/interview-templates/${encodeURIComponent(id)}`)}`, { method: 'GET' }); |
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { success: false, error: errorData.error || `HTTP ${response.status}` }; |
| } |
| const data = await response.json(); |
| return { success: true, interviewTemplate: data.interviewTemplate }; |
| } catch (error) { |
| return { success: false, error: error instanceof Error ? error.message : 'Network error' }; |
| } |
| } |
|
|
| |
| async pushInterviewTemplate(interviewTemplate: import('@/lib/interview/types').InterviewTemplate): Promise<InterviewTemplateSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/interview-templates/${encodeURIComponent(interviewTemplate.id)}`)}`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ interviewTemplate }), |
| }); |
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { success: false, error: errorData.error || `HTTP ${response.status}` }; |
| } |
| const data = await response.json(); |
| return { success: true, interviewTemplate: data.interviewTemplate, action: data.action }; |
| } catch (error) { |
| return { success: false, error: error instanceof Error ? error.message : 'Network error' }; |
| } |
| } |
|
|
| |
| async deleteInterviewTemplateFromServer(id: string): Promise<SyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/interview-templates/${encodeURIComponent(id)}`)}`, { method: 'DELETE' }); |
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { success: false, error: errorData.error || `HTTP ${response.status}` }; |
| } |
| return { success: true }; |
| } catch (error) { |
| return { success: false, error: error instanceof Error ? error.message : 'Network error' }; |
| } |
| } |
|
|
| |
| |
| |
|
|
| |
| async pullConnections(): Promise<ConnectionsListSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl('/sync/connections')}`, { method: 'GET' }); |
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { success: false, error: errorData.error || `HTTP ${response.status}` }; |
| } |
| const data = await response.json(); |
| return { success: true, connections: data.connections || [] }; |
| } catch (error) { |
| return { success: false, error: error instanceof Error ? error.message : 'Network error' }; |
| } |
| } |
|
|
| |
| async pushConnection(connection: CustomConnection): Promise<ConnectionSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/connections/${encodeURIComponent(connection.id)}`)}`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ connection }), |
| }); |
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { success: false, error: errorData.error || `HTTP ${response.status}` }; |
| } |
| const data = await response.json(); |
| return { success: true, connection: data.connection, action: data.action }; |
| } catch (error) { |
| return { success: false, error: error instanceof Error ? error.message : 'Network error' }; |
| } |
| } |
|
|
| |
| async deleteConnectionFromServer(id: string): Promise<SyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/connections/${encodeURIComponent(id)}`)}`, { method: 'DELETE' }); |
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { success: false, error: errorData.error || `HTTP ${response.status}` }; |
| } |
| return { success: true }; |
| } catch (error) { |
| return { success: false, error: error instanceof Error ? error.message : 'Network error' }; |
| } |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| async pullTemplates(): Promise<TemplatesListSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl('/sync/templates')}`, { |
| method: 'GET', |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| const data = await response.json(); |
| return { |
| success: true, |
| templates: data.templates || [], |
| }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
| async pushTemplates(templates: CustomTemplate[]): Promise<TemplatesListSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl('/sync/templates')}`, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ templates }), |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| const data = await response.json(); |
| return { |
| success: data.success, |
| created: data.created, |
| updated: data.updated, |
| error: data.errors?.join(', '), |
| }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
| async pullTemplate(id: string): Promise<TemplateSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/templates/${encodeURIComponent(id)}`)}`, { |
| method: 'GET', |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| const data = await response.json(); |
| return { |
| success: true, |
| template: data.template, |
| }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
| async pushTemplate(template: CustomTemplate): Promise<TemplateSyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/templates/${encodeURIComponent(template.id)}`)}`, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ template }), |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| const data = await response.json(); |
| return { |
| success: true, |
| template: data.template, |
| action: data.action, |
| }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
| async deleteTemplateFromServer(id: string): Promise<SyncResult> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/templates/${encodeURIComponent(id)}`)}`, { |
| method: 'DELETE', |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| return { success: true }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| async pushBackendFeatures( |
| projectId: string, |
| features?: { |
| edgeFunctions: import('./types').EdgeFunction[]; |
| serverFunctions: import('./types').ServerFunction[]; |
| secrets: import('./types').Secret[]; |
| scheduledFunctions: import('./types').ScheduledFunction[]; |
| } |
| ): Promise<{ success: boolean; error?: string }> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/backend-features/${projectId}`)}`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(features || { |
| edgeFunctions: [], |
| serverFunctions: [], |
| secrets: [], |
| scheduledFunctions: [], |
| }), |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json().catch(() => ({})); |
| return { success: false, error: errorData.error || `HTTP ${response.status}` }; |
| } |
|
|
| return { success: true }; |
| } catch (error) { |
| return { success: false, error: error instanceof Error ? error.message : 'Network error' }; |
| } |
| } |
|
|
| |
| |
| |
| |
| async pullBackendFeatures(projectId: string): Promise<{ success: boolean; error?: string }> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl(`/sync/backend-features/${projectId}`)}`, { |
| method: 'GET', |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json().catch(() => ({})); |
| return { success: false, error: errorData.error || `HTTP ${response.status}` }; |
| } |
|
|
| const data = await response.json() as { |
| edgeFunctions?: import('./types').EdgeFunction[]; |
| serverFunctions?: import('./types').ServerFunction[]; |
| secrets?: import('./types').Secret[]; |
| scheduledFunctions?: import('./types').ScheduledFunction[]; |
| }; |
|
|
| |
| try { |
| const { vfs } = await import('@/lib/vfs'); |
| const adapter = vfs.getStorageAdapter(); |
|
|
| |
| if (adapter.listEdgeFunctions && adapter.deleteEdgeFunction && adapter.createEdgeFunction) { |
| const existing = await adapter.listEdgeFunctions(projectId); |
| for (const fn of existing) { |
| await adapter.deleteEdgeFunction(fn.id); |
| } |
| for (const fn of data.edgeFunctions || []) { |
| await adapter.createEdgeFunction({ ...fn, projectId }); |
| } |
| } |
|
|
| if (adapter.listServerFunctions && adapter.deleteServerFunction && adapter.createServerFunction) { |
| const existing = await adapter.listServerFunctions(projectId); |
| for (const fn of existing) { |
| await adapter.deleteServerFunction(fn.id); |
| } |
| for (const fn of data.serverFunctions || []) { |
| await adapter.createServerFunction({ ...fn, projectId }); |
| } |
| } |
|
|
| if (adapter.listSecrets && adapter.deleteSecret && adapter.createSecret) { |
| const existing = await adapter.listSecrets(projectId); |
| for (const s of existing) { |
| await adapter.deleteSecret(s.id); |
| } |
| for (const s of data.secrets || []) { |
| await adapter.createSecret({ ...s, projectId }); |
| } |
| } |
|
|
| if (adapter.listScheduledFunctions && adapter.deleteScheduledFunction && adapter.createScheduledFunction) { |
| const existing = await adapter.listScheduledFunctions(projectId); |
| for (const fn of existing) { |
| await adapter.deleteScheduledFunction(fn.id); |
| } |
| for (const fn of data.scheduledFunctions || []) { |
| await adapter.createScheduledFunction({ ...fn, projectId }); |
| } |
| } |
| } catch (writeError) { |
| console.error('[SyncManager] Failed to write pulled backend features to IndexedDB:', writeError); |
| return { success: false, error: writeError instanceof Error ? writeError.message : 'Failed to write to IndexedDB' }; |
| } |
|
|
| return { success: true }; |
| } catch (error) { |
| return { success: false, error: error instanceof Error ? error.message : 'Network error' }; |
| } |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| async getEnhancedSyncStatus(): Promise<{ |
| success: boolean; |
| error?: string; |
| data?: EnhancedSyncStatusResponse; |
| }> { |
| try { |
| const response = await fetch(`${this.baseUrl}${this.getApiUrl('/sync/status')}`, { |
| method: 'GET', |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json(); |
| return { |
| success: false, |
| error: errorData.error || `HTTP ${response.status}`, |
| }; |
| } |
|
|
| const data = await response.json(); |
| return { |
| success: true, |
| data: data as EnhancedSyncStatusResponse, |
| }; |
| } catch (error) { |
| return { |
| success: false, |
| error: error instanceof Error ? error.message : 'Network error', |
| }; |
| } |
| } |
| } |
|
|
| |
| |
| |
| let syncManager: SyncManager | null = null; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export function getSyncManager(workspaceId?: string | null): SyncManager { |
| if (!syncManager) { |
| syncManager = new SyncManager(); |
| } |
| if (workspaceId !== undefined) { |
| syncManager.workspaceId = workspaceId ?? undefined; |
| } |
| return syncManager; |
| } |
|
|