feat: restructure into XFlow monorepo with Nuxt 4, Modular Onion Monolith, Android client, and Desktop CLI

This commit is contained in:
2026-09-28 10:39:19 +05:00
parent edcd30c829
commit be68f5e130
204 changed files with 24616 additions and 39 deletions
+147
View File
@@ -0,0 +1,147 @@
generator client {
provider = "prisma-client-js"
output = "../node_modules/.prisma/client"
}
datasource db {
provider = "postgresql"
}
model User {
id String @id @default(cuid())
email String @unique
username String @unique
password String
role String @default("user")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
activities Activity[]
canvases Canvas[]
following Follow[] @relation("follower")
followers Follow[] @relation("following")
groupMembers GroupMember[]
profile Profile?
noteLists NoteList[]
}
model Profile {
id String @id @default(cuid())
userId String @unique
displayName String
bio String @default("")
avatar String @default("")
canvasCount Int @default(0)
followerCount Int @default(0)
followingCount Int @default(0)
joinedAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Canvas {
id String @id
name String
ownerId String
groupId String?
isPublic Boolean @default(false)
nodes Json @default("[]")
edges Json @default("[]")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
group Group? @relation(fields: [groupId], references: [id])
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
}
model Group {
id String @id @default(cuid())
name String
description String @default("")
isPublic Boolean @default(true)
ownerId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
controlType String? @default("REMOTE")
canvases Canvas[]
members GroupMember[]
Kiosk Kiosk[]
}
model GroupMember {
id String @id @default(cuid())
userId String
groupId String
role String @default("member")
joinedAt DateTime @default(now())
group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, groupId])
}
model Activity {
id String @id @default(cuid())
userId String
action String
targetId String
targetType String
timestamp DateTime @default(now())
metadata String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Follow {
id String @id @default(cuid())
followerId String
followingId String
since DateTime @default(now())
follower User @relation("follower", fields: [followerId], references: [id], onDelete: Cascade)
following User @relation("following", fields: [followingId], references: [id], onDelete: Cascade)
@@unique([followerId, followingId])
}
model Kiosk {
id String @id
name String
location String?
groupId String
status String @default("offline")
lastSeen DateTime @default(now())
createdAt DateTime @default(now())
Group Group @relation(fields: [groupId], references: [id])
}
model NoteList {
id String @id @default(cuid())
title String
category String @default("general")
description String @default("")
color String?
icon String?
ownerId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
items ListItem[]
@@index([ownerId])
@@index([category])
}
model ListItem {
id String @id @default(cuid())
listId String
title String
description String @default("")
isCompleted Boolean @default(false)
status String @default("todo")
priority String @default("normal")
metadata Json?
dueDate DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
list NoteList @relation(fields: [listId], references: [id], onDelete: Cascade)
@@index([listId])
@@index([status])
}
+97
View File
@@ -0,0 +1,97 @@
import { PrismaClient } from '@prisma/client'
import { readdirSync, readFileSync } from 'fs'
import { join, resolve } from 'path'
import { Pool } from 'pg'
import { PrismaPg } from '@prisma/adapter-pg'
const connectionString = process.env.DATABASE_URL
const pool = new Pool({ connectionString })
const adapter = new PrismaPg(pool)
const prisma = new PrismaClient({ adapter })
async function main() {
console.log('Ensuring system user exists...')
let systemUser = await prisma.user.findUnique({ where: { id: 'system' } })
if (!systemUser) {
systemUser = await prisma.user.create({
data: {
id: 'system',
username: 'system',
email: 'system@localhost',
password: 'none',
role: 'admin'
}
})
}
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: 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()
})