feat: restructure into XFlow monorepo with Nuxt 4, Modular Onion Monolith, Android client, and Desktop CLI
This commit is contained in:
@@ -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])
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
Reference in New Issue
Block a user