From f933c17a90a6a2625d18c4fa4f27f4ed30826d2d Mon Sep 17 00:00:00 2001 From: AlexIves16 Date: Tue, 28 Apr 2026 16:10:46 +0500 Subject: [PATCH] fix: add seed script and repository check endpoint --- .gitea/workflows/deploy.yml | 2 +- .../api/git/repository/[canvasId].get.ts | 50 ++++ package-lock.json | 234 +++++++++++++++++- package.json | 8 +- prisma/seed.ts | 77 ++++++ 5 files changed, 367 insertions(+), 4 deletions(-) create mode 100644 app/server/api/git/repository/[canvasId].get.ts create mode 100644 prisma/seed.ts diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 65399f4..bd68eca 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -62,7 +62,7 @@ jobs: docker compose -f /opt/xflow/docker-compose.yml up -d --no-deps xflow echo "⏳ Waiting for container to start..." sleep 5 - docker exec xflow sh -c "cd /app && export DATABASE_URL=postgresql://xflow:xflow_pass@postgres:5432/xflow && npx prisma migrate deploy --config prisma.config.ts" + docker exec xflow sh -c "cd /app && export DATABASE_URL=postgresql://xflow:xflow_pass@postgres:5432/xflow && npx prisma migrate deploy --config prisma.config.ts && npx prisma db seed" echo "✅ XFlow deployed at $(date)" env: FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }} diff --git a/app/server/api/git/repository/[canvasId].get.ts b/app/server/api/git/repository/[canvasId].get.ts new file mode 100644 index 0000000..490897a --- /dev/null +++ b/app/server/api/git/repository/[canvasId].get.ts @@ -0,0 +1,50 @@ +import { requireAuth } from '~/server/utils/auth' +import { resolveRepo, ensureRepo } from '~/server/utils/giteaApi' + +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' }) + } + + // We need to resolve the repo. For now, assume it's a personal repo. + // In a real scenario, we'd check the DB to see if it's a group canvas. + const opts = { username: user.username } + const { owner, repo } = resolveRepo(opts) + + const config = useRuntimeConfig() + const { giteaUrl: url, giteaToken: token } = config + + try { + const res = await $fetch(`${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' }) + } +}) diff --git a/package-lock.json b/package-lock.json index 2771590..6dc9a64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "hasInstallScript": true, "dependencies": { "@nuxtjs/tailwindcss": "^6.12.0", + "@prisma/adapter-pg": "^7.8.0", "@vite-pwa/nuxt": "^0.10.0", "@vue-flow/background": "^1.3.2", "@vue-flow/controls": "^1.1.2", @@ -19,6 +20,7 @@ "jwt-decode": "^4.0.0", "markdown-it": "^14.1.0", "nuxt": "^3.16.0", + "pg": "^8.12.0", "vue": "^3.5.0", "vue-router": "^4.5.0", "zod": "^3.24.0" @@ -28,6 +30,7 @@ "@types/jsonwebtoken": "^9.0.10", "@types/markdown-it": "^14.1.0", "prisma": "^7.8.0", + "tsx": "^4.21.0", "typescript": "^5.6.0" } }, @@ -5141,6 +5144,18 @@ "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", "license": "MIT" }, + "node_modules/@prisma/adapter-pg": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.8.0.tgz", + "integrity": "sha512-ygb3UkerK3v8MDpXVgCISdRNDozpxh6+JVJgiIGbSr5KBgz10LLf5ejUskPGoXlsIjxsOu6nuy1JVQr2EKGSlg==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/driver-adapter-utils": "7.8.0", + "@types/pg": "^8.16.0", + "pg": "^8.16.3", + "postgres-array": "3.0.4" + } + }, "node_modules/@prisma/client": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.8.0.tgz", @@ -5190,7 +5205,6 @@ "version": "7.8.0", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.8.0.tgz", "integrity": "sha512-p+QZReysDUqXC+mk17q9a+Y/qzh4c2KYliDK30buYUyfrGeTGSyfmc0AIrJRhZJrLHhRiJa9Au/J72h3C+szvA==", - "dev": true, "license": "Apache-2.0" }, "node_modules/@prisma/dev": { @@ -5226,6 +5240,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@prisma/driver-adapter-utils": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.8.0.tgz", + "integrity": "sha512-/Q13o0ZT0rjc1Xk0Q9KhZYwuq2EW/vSbWUBKfgEKkaCuB/Sg6bqnjmTZqC5cD4d6y1vfFAEwBRzfzoSMIVJ55A==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.8.0" + } + }, "node_modules/@prisma/engines": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.8.0.tgz", @@ -6206,6 +6229,17 @@ "undici-types": "~7.19.0" } }, + "node_modules/@types/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/react": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", @@ -10120,6 +10154,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/giget": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/giget/-/giget-3.2.0.tgz", @@ -13340,6 +13387,104 @@ "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", "license": "MIT" }, + "node_modules/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.12.0", + "pg-pool": "^3.13.0", + "pg-protocol": "^1.13.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.3.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", + "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz", + "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz", + "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", + "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-types/node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -14032,6 +14177,45 @@ "url": "https://github.com/sponsors/porsager" } }, + "node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/powershell-utils": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", @@ -14681,6 +14865,16 @@ "node": ">= 0.6" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -15451,6 +15645,15 @@ "deprecated": "Please use @jridgewell/sourcemap-codec instead", "license": "MIT" }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sqlstring": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", @@ -16393,6 +16596,26 @@ "node": ">=0.6.x" } }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-fest": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz", @@ -18813,6 +19036,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 5e912e5..318dcd9 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@nuxtjs/tailwindcss": "^6.12.0", + "@prisma/adapter-pg": "^7.8.0", "@vite-pwa/nuxt": "^0.10.0", "@vue-flow/background": "^1.3.2", "@vue-flow/controls": "^1.1.2", @@ -22,7 +23,6 @@ "jwt-decode": "^4.0.0", "markdown-it": "^14.1.0", "nuxt": "^3.16.0", - "@prisma/adapter-pg": "^7.8.0", "pg": "^8.12.0", "vue": "^3.5.0", "vue-router": "^4.5.0", @@ -33,6 +33,10 @@ "@types/jsonwebtoken": "^9.0.10", "@types/markdown-it": "^14.1.0", "prisma": "^7.8.0", + "tsx": "^4.21.0", "typescript": "^5.6.0" + }, + "prisma": { + "seed": "tsx prisma/seed.ts" } -} \ No newline at end of file +} diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..7ef1c82 --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,77 @@ +import { PrismaClient } from '@prisma/client' +import { readdirSync, readFileSync } from 'fs' +import { join, resolve } from 'path' + +const prisma = new PrismaClient() + +async function main() { + console.log('Seeding initial maps from .canvas files...') + + const rootDir = resolve(process.cwd()) + const files = readdirSync(rootDir).filter(f => f.endsWith('.canvas')) + + // We'll assign these to the first admin user + const adminUser = await prisma.user.findFirst({ where: { role: 'admin' } }) + const ownerId = adminUser?.id || '1' + + for (const file of files) { + try { + const filePath = join(rootDir, file) + const data = JSON.parse(readFileSync(filePath, 'utf-8')) + const id = file.replace('.canvas', '') + + const existing = await prisma.canvas.findUnique({ where: { id } }) + + if (!existing) { + await prisma.canvas.create({ + data: { + id, + name: data.name || id, + ownerId, + isPublic: true, // Make public so anyone can view/edit + nodes: data.nodes || [], + edges: data.edges || [] + } + }) + console.log(`✅ Seeded map: ${id}`) + } else { + console.log(`⏩ Map ${id} already exists, skipping.`) + } + } catch (e) { + console.error(`❌ Failed to read or seed ${file}:`, e) + } + } + + // Also seed the xflow-project from db.ts manually if it's not in the files + try { + const { canvases } = await import('../app/server/utils/db.js') + const xflowProject = canvases.get('xflow-project') + if (xflowProject) { + const existing = await prisma.canvas.findUnique({ where: { id: 'xflow-project' } }) + if (!existing) { + await prisma.canvas.create({ + data: { + id: xflowProject.id, + name: xflowProject.name, + ownerId: xflowProject.ownerId || ownerId, + isPublic: true, + nodes: xflowProject.nodes as any, + edges: xflowProject.edges as any + } + }) + console.log(`✅ Seeded map: xflow-project (from db.ts)`) + } + } + } catch (e) { + console.error('Could not seed xflow-project from db.ts', e) + } +} + +main() + .catch(e => { + console.error(e) + process.exit(1) + }) + .finally(async () => { + await prisma.$disconnect() + })