// Gitea API integration for XFlow canvas sync // Structure: // Personal canvases → {username}/MM (personal user repo, created on first save) // Group canvases → AVTEX/group-{groupId} (created on group creation) // Public canvases → AVTEX/MMAVTEX (shared org repo) interface GiteaFile { content: string // base64 sha?: string } const getConfig = () => { const config = useRuntimeConfig() return { url: config.giteaUrl, token: config.giteaToken, org: config.giteaOrg, mindmapRepo: config.giteaMindmapRepo, projectRepo: config.giteaProjectRepo, } } const apiHeaders = (token: string) => ({ 'Authorization': `token ${token}`, 'Content-Type': 'application/json', 'Accept': 'application/json' }) // Resolve which repo to use based on canvas ownership // Returns { owner, repo } to support both org and personal repos export function resolveRepo(opts: { username?: string; groupId?: string; isPublic?: boolean }): { owner: string; repo: string } { const { org, mindmapRepo } = getConfig() if (opts.groupId) return { owner: org, repo: `group-${opts.groupId}` } if (opts.username && !opts.isPublic) return { owner: opts.username, repo: 'MM' } return { owner: org, repo: mindmapRepo } } // Ensure repository exists, create if not (supports personal and org repos) export async function ensureRepo(owner: string, repoName: string, description = ''): Promise { const { url, token, org } = getConfig() const h = apiHeaders(token) // Check if exists try { await $fetch(`${url}/api/v1/repos/${owner}/${repoName}`, { headers: h }) return true } catch { } // Create — personal repo or org repo try { const isOrg = owner === org const endpoint = isOrg ? `${url}/api/v1/orgs/${org}/repos` : `${url}/api/v1/user/repos` await $fetch(endpoint, { method: 'POST', headers: h, body: { name: repoName, description: description || `XFlow canvas repo: ${repoName}`, private: true, auto_init: true, default_branch: 'main' } }) return true } catch (e: any) { console.error(`Gitea ensureRepo(${owner}/${repoName}) error:`, e.data || e) throw new Error(`ensureRepo failed: ${e.message}`) } } // Get file from Gitea (returns null if not found) export async function giteaGetFile(path: string, owner?: string, repo?: string): Promise { const { url, token, org, mindmapRepo } = getConfig() const repoOwner = owner || org const repoName = repo || mindmapRepo try { const res = await $fetch(`${url}/api/v1/repos/${repoOwner}/${repoName}/contents/${path}`, { headers: apiHeaders(token) }) return { content: res.content, sha: res.sha } } catch { return null } } // Create or update file in Gitea export async function giteaPutFile(path: string, content: string, message: string, sha?: string, owner?: string, repo?: string): Promise { const { url, token, org, mindmapRepo } = getConfig() const repoOwner = owner || org const repoName = repo || mindmapRepo const encoded = Buffer.from(content).toString('base64') try { await $fetch(`${url}/api/v1/repos/${repoOwner}/${repoName}/contents/${path}`, { method: sha ? 'PUT' : 'POST', headers: apiHeaders(token), body: { message, content: encoded, ...(sha ? { sha } : {}) } }) return true } catch (e: any) { console.error('Gitea putFile error:', e.data || e) throw new Error(`giteaPutFile failed: ${e.message}`) } } // Save canvas to correct repo based on ownership export async function saveCanvasToGitea( canvasId: string, canvasData: any, opts: { username?: string; groupId?: string; isPublic?: boolean } = {}, commitMessage?: string ): Promise { const { owner, repo } = resolveRepo(opts) const path = `${canvasId}.canvas` const content = JSON.stringify(canvasData, null, 2) const message = commitMessage || `Update ${canvasId} at ${new Date().toLocaleString('ru-RU')}` await ensureRepo(owner, repo) const existing = await giteaGetFile(path, owner, repo) return giteaPutFile(path, content, message, existing?.sha, owner, repo) } // Load canvas from Gitea export async function loadCanvasFromGitea( canvasId: string, opts: { username?: string; groupId?: string; isPublic?: boolean } = {} ): Promise { const { owner, repo } = resolveRepo(opts) const file = await giteaGetFile(`${canvasId}.canvas`, owner, repo) if (!file) return null try { return JSON.parse(Buffer.from(file.content, 'base64').toString('utf-8')) } catch { return null } } // List canvases in a repo export async function listCanvasesFromGitea(owner?: string, repo?: string): Promise { const { url, token, org, mindmapRepo } = getConfig() const repoOwner = owner || org const repoName = repo || mindmapRepo try { const res = await $fetch(`${url}/api/v1/repos/${repoOwner}/${repoName}/contents/`, { headers: apiHeaders(token) }) return res .filter((f: any) => f.name.endsWith('.canvas')) .map((f: any) => f.name.replace('.canvas', '')) } catch { return [] } }