feat: restructure into XFlow monorepo with Nuxt 4, Modular Onion Monolith, Android client, and Desktop CLI
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { requireAuth } from '~/server/utils/auth'
|
||||
import { saveCanvasToGitea } from '~/server/utils/giteaApi'
|
||||
import { canvases } from '~/server/utils/db'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = requireAuth(event)
|
||||
const body = await readBody(event)
|
||||
const { canvasId, autoSync = true } = body
|
||||
|
||||
if (!canvasId) {
|
||||
throw createError({ statusCode: 400, message: 'Canvas ID is required' })
|
||||
}
|
||||
|
||||
// Check if canvas exists and user has access
|
||||
const canvas = canvases.get(canvasId)
|
||||
if (!canvas) {
|
||||
throw createError({ statusCode: 404, message: 'Canvas not found' })
|
||||
}
|
||||
|
||||
// Check permissions
|
||||
if (canvas.ownerId !== user.id && !canvas.isPublic) {
|
||||
throw createError({ statusCode: 403, message: 'Access denied' })
|
||||
}
|
||||
|
||||
try {
|
||||
const ok = await saveCanvasToGitea(canvasId, canvas, `Auto-sync: ${canvas.name}`)
|
||||
return {
|
||||
success: ok,
|
||||
message: ok ? 'Canvas synchronized with Gitea' : 'Gitea sync failed',
|
||||
autoSyncEnabled: autoSync
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to sync canvas to Gitea:', error)
|
||||
throw createError({ statusCode: 500, message: 'Failed to sync canvas to Gitea' })
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,68 @@
|
||||
import { requireAuth } from '~/server/utils/auth'
|
||||
import { saveCanvasToGitea, loadCanvasFromGitea } from '~/server/utils/giteaApi'
|
||||
import { prisma } from '~/server/utils/prisma'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = requireAuth(event)
|
||||
const body = await readBody(event)
|
||||
const { canvasId, action, commitMessage } = body
|
||||
|
||||
if (!canvasId || !action) {
|
||||
throw createError({ statusCode: 400, message: 'Canvas ID and action are required' })
|
||||
}
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case 'init':
|
||||
case 'sync': {
|
||||
const canvas = await prisma.canvas.findUnique({ where: { id: canvasId } })
|
||||
if (!canvas) {
|
||||
throw createError({ statusCode: 404, message: 'Canvas not found' })
|
||||
}
|
||||
if (canvas.ownerId !== user.id && !canvas.isPublic) {
|
||||
throw createError({ statusCode: 403, message: 'Access denied' })
|
||||
}
|
||||
|
||||
const opts = { username: user.username, groupId: canvas.groupId, isPublic: canvas.isPublic }
|
||||
const ok = await saveCanvasToGitea(canvasId, canvas, opts, commitMessage)
|
||||
return {
|
||||
success: ok,
|
||||
message: ok
|
||||
? `Canvas ${canvasId} saved to Gitea`
|
||||
: `Failed to save canvas ${canvasId} to Gitea`
|
||||
}
|
||||
}
|
||||
|
||||
case 'load': {
|
||||
const existing = await prisma.canvas.findUnique({ where: { id: canvasId } })
|
||||
const opts = existing ? { username: user.username, groupId: existing.groupId, isPublic: existing.isPublic } : { username: user.username }
|
||||
const gitCanvas = await loadCanvasFromGitea(canvasId, opts)
|
||||
if (!gitCanvas) {
|
||||
return { success: false, message: 'Canvas not found in Gitea', needsInit: true }
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
await prisma.canvas.update({
|
||||
where: { id: canvasId },
|
||||
data: {
|
||||
nodes: gitCanvas.nodes || [],
|
||||
edges: gitCanvas.edges || []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return { success: true, canvas: gitCanvas, message: `Canvas ${canvasId} loaded from Gitea` }
|
||||
}
|
||||
|
||||
default:
|
||||
throw createError({ statusCode: 400, message: 'Invalid action' })
|
||||
}
|
||||
} catch (error: any) {
|
||||
const details = error.data || error.message || error
|
||||
console.error('Gitea sync error details:', details)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: typeof details === 'string' ? details : JSON.stringify(details)
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,47 @@
|
||||
import { requireAuth } from '~/server/utils/auth'
|
||||
import { saveCanvasToGitea } from '~/server/utils/giteaApi'
|
||||
import { canvases, userGroups } from '~/server/utils/db'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = requireAuth(event)
|
||||
const body = await readBody(event)
|
||||
const { canvasId, type } = body
|
||||
|
||||
if (!canvasId || !type) {
|
||||
throw createError({ statusCode: 400, message: 'Canvas ID and repository type are required' })
|
||||
}
|
||||
|
||||
// Check if canvas exists and user has access
|
||||
const canvas = canvases.get(canvasId)
|
||||
if (!canvas) {
|
||||
throw createError({ statusCode: 404, message: 'Canvas not found' })
|
||||
}
|
||||
|
||||
if (canvas.ownerId !== user.id && !canvas.isPublic) {
|
||||
// Check group access
|
||||
if (canvas.groupId) {
|
||||
const membership = Array.from(userGroups.values()).find(
|
||||
m => m.userId === user.id && m.groupId === canvas.groupId
|
||||
)
|
||||
if (!membership) {
|
||||
throw createError({ statusCode: 403, message: 'Access denied' })
|
||||
}
|
||||
} else {
|
||||
throw createError({ statusCode: 403, message: 'Access denied' })
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Initial save to Gitea creates the canvas file in the repo
|
||||
const ok = await saveCanvasToGitea(canvasId, canvas, `Init canvas ${canvasId}`)
|
||||
return {
|
||||
success: ok,
|
||||
message: ok
|
||||
? `Canvas ${canvasId} initialized in Gitea`
|
||||
: `Failed to initialize canvas ${canvasId} in Gitea`
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize canvas in Gitea:', error)
|
||||
throw createError({ statusCode: 500, message: 'Failed to initialize canvas in Gitea' })
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,54 @@
|
||||
import { requireAuth } from '~/server/utils/auth'
|
||||
import { resolveRepo } from '~/server/utils/giteaApi'
|
||||
import { prisma } from '~/server/utils/prisma'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = requireAuth(event)
|
||||
const canvasId = getRouterParam(event, 'canvasId')
|
||||
|
||||
if (!canvasId) {
|
||||
throw createError({ statusCode: 400, message: 'Canvas ID is required' })
|
||||
}
|
||||
|
||||
const canvas = await prisma.canvas.findUnique({ where: { id: canvasId } })
|
||||
|
||||
const opts = canvas
|
||||
? { username: user.username, groupId: canvas.groupId, isPublic: canvas.isPublic }
|
||||
: { username: user.username }
|
||||
|
||||
const { owner, repo } = resolveRepo(opts)
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const { giteaUrl: url, giteaToken: token } = config
|
||||
|
||||
try {
|
||||
const res = await $fetch<any>(`${url}/api/v1/repos/${owner}/${repo}`, {
|
||||
headers: {
|
||||
'Authorization': `token ${token}`,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
repository: {
|
||||
id: res.id,
|
||||
name: res.name,
|
||||
owner: res.owner.login,
|
||||
url: res.html_url,
|
||||
cloneUrl: res.clone_url,
|
||||
isPrivate: res.private,
|
||||
type: 'personal',
|
||||
ownerId: user.id,
|
||||
createdAt: res.created_at,
|
||||
updatedAt: res.updated_at
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 404) {
|
||||
return { success: false, repository: null, message: 'Repository not found' }
|
||||
}
|
||||
console.error('Failed to fetch repository:', error)
|
||||
throw createError({ statusCode: 500, message: 'Failed to fetch repository details from Gitea' })
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,23 @@
|
||||
import { requireAuth } from '~/server/utils/auth'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = requireAuth(event)
|
||||
const body = await readBody(event)
|
||||
const { canvasId, action, commitMessage } = body
|
||||
|
||||
if (!canvasId || !action) {
|
||||
throw createError({ statusCode: 400, message: 'Canvas ID and action are required' })
|
||||
}
|
||||
|
||||
try {
|
||||
// Git sync logic will be implemented here
|
||||
// For now, return success response
|
||||
return {
|
||||
success: true,
|
||||
message: `Git ${action} completed for canvas ${canvasId}`,
|
||||
commitMessage: commitMessage || `Auto-sync canvas ${canvasId}`
|
||||
}
|
||||
} catch (error) {
|
||||
throw createError({ statusCode: 500, message: 'Git sync failed' })
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,45 @@
|
||||
// Test endpoint: GET /api/git/test
|
||||
// Verifies Gitea connection and repo access
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const { giteaUrl, giteaToken, giteaOrg, giteaMindmapRepo } = config
|
||||
|
||||
if (!giteaUrl || !giteaToken) {
|
||||
return { success: false, message: 'GITEA_URL or GITEA_TOKEN not set in .env' }
|
||||
}
|
||||
|
||||
try {
|
||||
// Test 1: check repo exists
|
||||
const repo = await $fetch<any>(`${giteaUrl}/api/v1/repos/${giteaOrg}/${giteaMindmapRepo}`, {
|
||||
headers: { 'Authorization': `token ${giteaToken}` }
|
||||
})
|
||||
|
||||
// Test 2: check/create canvases folder by listing contents
|
||||
let folderExists = false
|
||||
try {
|
||||
await $fetch(`${giteaUrl}/api/v1/repos/${giteaOrg}/${giteaMindmapRepo}/contents/canvases`, {
|
||||
headers: { 'Authorization': `token ${giteaToken}` }
|
||||
})
|
||||
folderExists = true
|
||||
} catch {
|
||||
folderExists = false
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
repo: repo.full_name,
|
||||
private: repo.private,
|
||||
folderExists,
|
||||
message: folderExists
|
||||
? '✅ Gitea connected, canvases/ folder exists'
|
||||
: '✅ Gitea connected, canvases/ folder will be created on first save'
|
||||
}
|
||||
} catch (e: any) {
|
||||
return {
|
||||
success: false,
|
||||
message: `❌ Gitea connection failed: ${e?.message || e}`,
|
||||
hint: 'Check GITEA_URL, GITEA_TOKEN, GITEA_ORG, GITEA_MINDMAP_REPO in .env'
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user