feat: support personal user repos for canvas storage
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
// Gitea API integration for XFlow canvas sync
|
// Gitea API integration for XFlow canvas sync
|
||||||
// Structure:
|
// Structure:
|
||||||
// Personal canvases → AVTEX/user-{username} (created on first save)
|
// Personal canvases → {username}/MM (personal user repo, created on first save)
|
||||||
// Group canvases → AVTEX/group-{groupId} (created on group creation)
|
// Group canvases → AVTEX/group-{groupId} (created on group creation)
|
||||||
// Public canvases → AVTEX/MMAVTEX (shared org repo)
|
// Public canvases → AVTEX/MMAVTEX (shared org repo)
|
||||||
|
|
||||||
@@ -27,50 +27,56 @@ const apiHeaders = (token: string) => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Resolve which repo to use based on canvas ownership
|
// Resolve which repo to use based on canvas ownership
|
||||||
export function resolveRepo(opts: { username?: string; groupId?: string; isPublic?: boolean }): string {
|
// 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()
|
const { org, mindmapRepo } = getConfig()
|
||||||
if (opts.groupId) return `group-${opts.groupId}`
|
if (opts.groupId) return { owner: org, repo: `group-${opts.groupId}` }
|
||||||
if (opts.username && !opts.isPublic) return `user-${opts.username}`
|
if (opts.username && !opts.isPublic) return { owner: opts.username, repo: 'MM' }
|
||||||
return mindmapRepo
|
return { owner: org, repo: mindmapRepo }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure repository exists in org, create if not
|
// Ensure repository exists, create if not (supports personal and org repos)
|
||||||
export async function ensureRepo(repoName: string, description = ''): Promise<boolean> {
|
export async function ensureRepo(owner: string, repoName: string, description = ''): Promise<boolean> {
|
||||||
const { url, token, org } = getConfig()
|
const { url, token, org } = getConfig()
|
||||||
const h = apiHeaders(token)
|
const h = apiHeaders(token)
|
||||||
|
|
||||||
// Check if exists
|
// Check if exists
|
||||||
try {
|
try {
|
||||||
await $fetch(`${url}/api/v1/repos/${org}/${repoName}`, { headers: h })
|
await $fetch(`${url}/api/v1/repos/${owner}/${repoName}`, { headers: h })
|
||||||
return true
|
return true
|
||||||
} catch { }
|
} catch { }
|
||||||
|
|
||||||
// Create
|
// Create — personal repo or org repo
|
||||||
try {
|
try {
|
||||||
await $fetch(`${url}/api/v1/orgs/${org}/repos`, {
|
const isOrg = owner === org
|
||||||
|
const endpoint = isOrg
|
||||||
|
? `${url}/api/v1/orgs/${org}/repos`
|
||||||
|
: `${url}/api/v1/user/repos`
|
||||||
|
await $fetch(endpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: h,
|
headers: h,
|
||||||
body: {
|
body: {
|
||||||
name: repoName,
|
name: repoName,
|
||||||
description: description || `XFlow canvas repo: ${repoName}`,
|
description: description || `XFlow canvas repo: ${repoName}`,
|
||||||
private: !repoName.startsWith('group-') && repoName !== getConfig().mindmapRepo,
|
private: true,
|
||||||
auto_init: true,
|
auto_init: true,
|
||||||
default_branch: 'main'
|
default_branch: 'main'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Gitea ensureRepo(${repoName}) error:`, e)
|
console.error(`Gitea ensureRepo(${owner}/${repoName}) error:`, e)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get file from Gitea (returns null if not found)
|
// Get file from Gitea (returns null if not found)
|
||||||
export async function giteaGetFile(path: string, repo?: string): Promise<GiteaFile | null> {
|
export async function giteaGetFile(path: string, owner?: string, repo?: string): Promise<GiteaFile | null> {
|
||||||
const { url, token, org, mindmapRepo } = getConfig()
|
const { url, token, org, mindmapRepo } = getConfig()
|
||||||
|
const repoOwner = owner || org
|
||||||
const repoName = repo || mindmapRepo
|
const repoName = repo || mindmapRepo
|
||||||
try {
|
try {
|
||||||
const res = await $fetch<any>(`${url}/api/v1/repos/${org}/${repoName}/contents/${path}`, {
|
const res = await $fetch<any>(`${url}/api/v1/repos/${repoOwner}/${repoName}/contents/${path}`, {
|
||||||
headers: apiHeaders(token)
|
headers: apiHeaders(token)
|
||||||
})
|
})
|
||||||
return { content: res.content, sha: res.sha }
|
return { content: res.content, sha: res.sha }
|
||||||
@@ -80,13 +86,14 @@ export async function giteaGetFile(path: string, repo?: string): Promise<GiteaFi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create or update file in Gitea
|
// Create or update file in Gitea
|
||||||
export async function giteaPutFile(path: string, content: string, message: string, sha?: string, repo?: string): Promise<boolean> {
|
export async function giteaPutFile(path: string, content: string, message: string, sha?: string, owner?: string, repo?: string): Promise<boolean> {
|
||||||
const { url, token, org, mindmapRepo } = getConfig()
|
const { url, token, org, mindmapRepo } = getConfig()
|
||||||
|
const repoOwner = owner || org
|
||||||
const repoName = repo || mindmapRepo
|
const repoName = repo || mindmapRepo
|
||||||
const encoded = Buffer.from(content).toString('base64')
|
const encoded = Buffer.from(content).toString('base64')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await $fetch(`${url}/api/v1/repos/${org}/${repoName}/contents/${path}`, {
|
await $fetch(`${url}/api/v1/repos/${repoOwner}/${repoName}/contents/${path}`, {
|
||||||
method: sha ? 'PUT' : 'POST',
|
method: sha ? 'PUT' : 'POST',
|
||||||
headers: apiHeaders(token),
|
headers: apiHeaders(token),
|
||||||
body: {
|
body: {
|
||||||
@@ -109,14 +116,14 @@ export async function saveCanvasToGitea(
|
|||||||
opts: { username?: string; groupId?: string; isPublic?: boolean } = {},
|
opts: { username?: string; groupId?: string; isPublic?: boolean } = {},
|
||||||
commitMessage?: string
|
commitMessage?: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const repo = resolveRepo(opts)
|
const { owner, repo } = resolveRepo(opts)
|
||||||
const path = `${canvasId}.canvas`
|
const path = `${canvasId}.canvas`
|
||||||
const content = JSON.stringify(canvasData, null, 2)
|
const content = JSON.stringify(canvasData, null, 2)
|
||||||
const message = commitMessage || `Update ${canvasId} at ${new Date().toLocaleString('ru-RU')}`
|
const message = commitMessage || `Update ${canvasId} at ${new Date().toLocaleString('ru-RU')}`
|
||||||
|
|
||||||
await ensureRepo(repo)
|
await ensureRepo(owner, repo)
|
||||||
const existing = await giteaGetFile(path, repo)
|
const existing = await giteaGetFile(path, owner, repo)
|
||||||
return giteaPutFile(path, content, message, existing?.sha, repo)
|
return giteaPutFile(path, content, message, existing?.sha, owner, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load canvas from Gitea
|
// Load canvas from Gitea
|
||||||
@@ -124,8 +131,8 @@ export async function loadCanvasFromGitea(
|
|||||||
canvasId: string,
|
canvasId: string,
|
||||||
opts: { username?: string; groupId?: string; isPublic?: boolean } = {}
|
opts: { username?: string; groupId?: string; isPublic?: boolean } = {}
|
||||||
): Promise<any | null> {
|
): Promise<any | null> {
|
||||||
const repo = resolveRepo(opts)
|
const { owner, repo } = resolveRepo(opts)
|
||||||
const file = await giteaGetFile(`${canvasId}.canvas`, repo)
|
const file = await giteaGetFile(`${canvasId}.canvas`, owner, repo)
|
||||||
if (!file) return null
|
if (!file) return null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -136,11 +143,12 @@ export async function loadCanvasFromGitea(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// List canvases in a repo
|
// List canvases in a repo
|
||||||
export async function listCanvasesFromGitea(repo?: string): Promise<string[]> {
|
export async function listCanvasesFromGitea(owner?: string, repo?: string): Promise<string[]> {
|
||||||
const { url, token, org, mindmapRepo } = getConfig()
|
const { url, token, org, mindmapRepo } = getConfig()
|
||||||
|
const repoOwner = owner || org
|
||||||
const repoName = repo || mindmapRepo
|
const repoName = repo || mindmapRepo
|
||||||
try {
|
try {
|
||||||
const res = await $fetch<any[]>(`${url}/api/v1/repos/${org}/${repoName}/contents/`, {
|
const res = await $fetch<any[]>(`${url}/api/v1/repos/${repoOwner}/${repoName}/contents/`, {
|
||||||
headers: apiHeaders(token)
|
headers: apiHeaders(token)
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
|
|||||||
Reference in New Issue
Block a user