404 lines
14 KiB
TypeScript
404 lines
14 KiB
TypeScript
import type { AuthUser } from './auth'
|
||
|
||
export interface CanvasNode {
|
||
id: string
|
||
type: string
|
||
x: number
|
||
y: number
|
||
width: number
|
||
height: number
|
||
text?: string
|
||
file?: string
|
||
frontmatter?: Record<string, any>
|
||
state?: 'pending' | 'in_progress' | 'completed'
|
||
label?: string
|
||
assigned_to?: string
|
||
alarm?: string
|
||
script_id?: string
|
||
prompt?: string
|
||
url?: string
|
||
method?: string
|
||
repo?: string
|
||
branch?: string
|
||
message?: string
|
||
style?: Record<string, string>
|
||
}
|
||
|
||
export interface CanvasEdge {
|
||
id: string
|
||
fromNode: string
|
||
fromSide: string
|
||
toNode: string
|
||
toSide: string
|
||
}
|
||
|
||
export interface CanvasData {
|
||
id: string
|
||
name: string
|
||
nodes: CanvasNode[]
|
||
edges: CanvasEdge[]
|
||
createdAt: string
|
||
updatedAt: string
|
||
ownerId: string
|
||
groupId?: string
|
||
isPublic?: boolean
|
||
}
|
||
|
||
export interface UserGroup {
|
||
userId: string
|
||
groupId: string
|
||
role: 'admin' | 'member' | 'viewer'
|
||
joinedAt: string
|
||
}
|
||
|
||
export interface Group {
|
||
id: string
|
||
name: string
|
||
description: string
|
||
isPublic: boolean
|
||
createdAt: string
|
||
updatedAt: string
|
||
ownerId: string
|
||
}
|
||
|
||
export interface Profile {
|
||
userId: string
|
||
displayName: string
|
||
bio: string
|
||
avatar: string
|
||
joinedAt: string
|
||
canvasCount: number
|
||
followerCount: number
|
||
followingCount: number
|
||
}
|
||
|
||
export interface Activity {
|
||
id: string
|
||
userId: string
|
||
action: string
|
||
targetId: string
|
||
targetType: string
|
||
timestamp: string
|
||
metadata?: Record<string, any>
|
||
}
|
||
|
||
// In-memory stores
|
||
export const users = new Map<string, { password: string } & AuthUser>()
|
||
export const nodes = new Map<string, any>()
|
||
export const achievements = new Map<string, any[]>()
|
||
export const pushSubscriptions = new Map<string, any>()
|
||
export const canvases = new Map<string, CanvasData>()
|
||
export const userGroups = new Map<string, UserGroup>()
|
||
export const groups = new Map<string, Group>()
|
||
export const profiles = new Map<string, Profile>()
|
||
export const activities = new Map<string, Activity>()
|
||
export const follows = new Map<string, { followerId: string, followingId: string, since: string }>()
|
||
|
||
// Seed admins
|
||
const seedAdmins = [
|
||
{ id: '1', email: 'admin@xflow.local', username: 'admin' },
|
||
{ id: '2', email: 'almalexyz@gmail.com', username: 'almalexyz' },
|
||
{ id: '3', email: 'ormix16@gmail.com', username: 'ormix16' }
|
||
]
|
||
|
||
for (const admin of seedAdmins) {
|
||
users.set(admin.email, {
|
||
...admin,
|
||
password: 'admin123',
|
||
role: 'admin'
|
||
})
|
||
|
||
// Create profiles for admins
|
||
profiles.set(admin.id, {
|
||
userId: admin.id,
|
||
displayName: admin.username,
|
||
bio: 'XFlow Administrator',
|
||
avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${admin.id}`,
|
||
joinedAt: new Date().toISOString(),
|
||
canvasCount: 0,
|
||
followerCount: 0,
|
||
followingCount: 0
|
||
})
|
||
}
|
||
|
||
// Create demo groups
|
||
const demoGroups: Group[] = [
|
||
{ id: 'group-1', name: 'XFlow Team', description: 'Core development team', isPublic: true, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), ownerId: '1' },
|
||
{ id: 'group-2', name: 'Beta Testers', description: 'Early access users', isPublic: true, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), ownerId: '1' },
|
||
{ id: 'group-3', name: 'AI Research', description: 'AI/LLM integration research', isPublic: false, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), ownerId: '2' }
|
||
]
|
||
|
||
demoGroups.forEach(group => {
|
||
groups.set(group.id, group)
|
||
|
||
// Add admins to groups
|
||
seedAdmins.forEach(admin => {
|
||
userGroups.set(`${admin.id}-${group.id}`, {
|
||
userId: admin.id,
|
||
groupId: group.id,
|
||
role: admin.id === '1' ? 'admin' : 'member',
|
||
joinedAt: new Date().toISOString()
|
||
})
|
||
})
|
||
})
|
||
|
||
// Seed comprehensive XFlow project mind map
|
||
const xflowProjectCanvas: CanvasData = {
|
||
id: 'xflow-project',
|
||
name: 'Обзор Проекта XFlow',
|
||
nodes: [
|
||
// Core Architecture
|
||
{
|
||
id: 'arch-1',
|
||
type: 'text',
|
||
x: 100,
|
||
y: 100,
|
||
width: 320,
|
||
height: 180,
|
||
text: '# 🏗️ Основная Архитектура\n\n- **Nuxt 3** - Vue 3 фреймворк\n- **TailwindCSS** - Стилизация\n- **Vue Flow** - Движок канваса\n- **Firebase** - Аутентификация\n- **In-memory DB** - Хранение данных\n- **PWA** - Progressive Web App',
|
||
style: { background: '#1e293b', border: '#3b82f6' }
|
||
},
|
||
// Frontend Components
|
||
{
|
||
id: 'fe-1',
|
||
type: 'text',
|
||
x: 500,
|
||
y: 50,
|
||
width: 280,
|
||
height: 160,
|
||
text: '# 🎨 Фронтенд\n\n- **FlowEditor.vue** - Основной редактор\n- **FlowNodeRenderer.vue** - Отображение узлов\n- **CanvasViewer.vue** - Устаревший просмотр\n- **Админ Панель** - Управление интерфейсом\n- **Система Аутентификации** - Вход через Google',
|
||
style: { background: '#1e293b', border: '#10b981' }
|
||
},
|
||
// Backend APIs
|
||
{
|
||
id: 'be-1',
|
||
type: 'text',
|
||
x: 500,
|
||
y: 250,
|
||
width: 280,
|
||
height: 160,
|
||
text: '# ⚙️ Backend API\n\n- `/api/canvas/*` - CRUD операций с канвасами\n- `/api/nodes/*` - Управление узлами\n- `/api/auth/*` - Аутентификация\n- `/api/admin/*` - Административные операции\n- `/api/push/*` - Уведомления',
|
||
style: { background: '#1e293b', border: '#f59e0b' }
|
||
},
|
||
// Node Types
|
||
{
|
||
id: 'nodes-1',
|
||
type: 'text',
|
||
x: 100,
|
||
y: 320,
|
||
width: 320,
|
||
height: 200,
|
||
text: '# 📦 Типы Узлов\n\n- **Текстовая Заметка** - Markdown контент\n- **Ссылка на Файл** - Ссылки на файлы\n- **Узел Задачи** - Отслеживание состояния\n- **AI/LLM Узел** - AI обработка\n- **Webhook Узел** - HTTP триггеры\n- **Git Sync Узел** - Контроль версий',
|
||
style: { background: '#1e293b', border: '#8b5cf6' }
|
||
},
|
||
// Features
|
||
{
|
||
id: 'feat-1',
|
||
type: 'text',
|
||
x: 850,
|
||
y: 150,
|
||
width: 300,
|
||
height: 220,
|
||
text: '# ✨ Ключевые Функции\n\n- **Редактор в стиле Node-RED**\n - Палитра сбоку\n - Панель свойств\n - Drag & drop\n - Соединения проводами\n\n- **Совместимость с Obsidian**\n - Экспорт в .canvas формат\n - Импорт существующих карт\n - Поддержка Markdown\n\n- **Социальные Функции**\n - Группы пользователей\n - Общие канвасы\n - Лента активности',
|
||
style: { background: '#1e293b', border: '#ef4444' }
|
||
},
|
||
// Tech Stack
|
||
{
|
||
id: 'tech-1',
|
||
type: 'text',
|
||
x: 100,
|
||
y: 560,
|
||
width: 320,
|
||
height: 180,
|
||
text: '# 🛠️ Технологический Стек\n\n```json\n{\n "frontend": "Vue 3 + Nuxt 3",\n "styling": "TailwindCSS",\n "canvas": "Vue Flow",\n "auth": "Firebase Google",\n "storage": "In-memory",\n "pwa": "@vite-pwa/nuxt"\n}\n```',
|
||
style: { background: '#1e293b', border: '#06b6d4' }
|
||
},
|
||
// Future Plans
|
||
{
|
||
id: 'future-1',
|
||
type: 'text',
|
||
x: 500,
|
||
y: 450,
|
||
width: 280,
|
||
height: 180,
|
||
text: '# 🚀 Будущее Развитие\n\n- **Git Интеграция**\n - Commit/push/pull карт\n - История версий\n - Управление ветками\n\n- **AI Интеграция**\n - OpenClaw агенты\n - Автоматизация LLM узлов\n - Умные предложения\n\n- **Социальная Сеть**\n - Профили пользователей\n - Система подписок\n - Ленты активности',
|
||
style: { background: '#1e293b', border: '#ec4899' }
|
||
},
|
||
// Database Schema
|
||
{
|
||
id: 'db-1',
|
||
type: 'file',
|
||
x: 850,
|
||
y: 400,
|
||
width: 300,
|
||
height: 160,
|
||
file: 'app/server/utils/db.ts',
|
||
text: '## Схема Базы Данных\n\n- `users` - Пользовательские аккаунты\n- `canvases` - Ментальные карты\n- `nodes` - Отдельные узлы\n- `groups` - Группы пользователей\n- `profiles` - Профили пользователей\n- `activities` - Лента активности',
|
||
style: { background: '#1e293b', border: '#84cc16' }
|
||
},
|
||
// Deployment
|
||
{
|
||
id: 'deploy-1',
|
||
type: 'text',
|
||
x: 850,
|
||
y: 590,
|
||
width: 300,
|
||
height: 140,
|
||
text: '# 🚀 Развертывание\n\n- **Docker Compose**\n- **Nginx** - Обратный прокси\n- **Gitea** - Git сервер\n- **PostgreSQL** - База данных\n- **Certbot** - SSL сертификаты',
|
||
style: { background: '#1e293b', border: '#f97316' }
|
||
},
|
||
// Social Features
|
||
{
|
||
id: 'social-1',
|
||
type: 'text',
|
||
x: 100,
|
||
y: 780,
|
||
width: 320,
|
||
height: 160,
|
||
text: '# 👥 Social Network\n\n- **User Profiles**\n - Avatar & bio\n - Canvas statistics\n - Following/followers\n\n- **Groups System**\n - Public/private groups\n - Role-based permissions\n - Shared canvases',
|
||
style: { background: '#1e293b', border: '#a855f7' }
|
||
},
|
||
// File Structure
|
||
{
|
||
id: 'files-1',
|
||
type: 'file',
|
||
x: 500,
|
||
y: 660,
|
||
width: 280,
|
||
height: 140,
|
||
file: 'project-structure.md',
|
||
text: '## Project Structure\n\n```\napp/\n├── components/\n│ ├── FlowEditor.vue\n│ └── FlowNodeRenderer.vue\n├── pages/\n│ ├── canvas.vue\n│ └── admin.vue\n└── server/api/\n```',
|
||
style: { background: '#1e293b', border: '#0ea5e9' }
|
||
}
|
||
],
|
||
edges: [
|
||
{ id: 'e1', fromNode: 'arch-1', fromSide: 'right', toNode: 'fe-1', toSide: 'left' },
|
||
{ id: 'e2', fromNode: 'arch-1', fromSide: 'right', toNode: 'be-1', toSide: 'left' },
|
||
{ id: 'e3', fromNode: 'arch-1', fromSide: 'bottom', toNode: 'nodes-1', toSide: 'top' },
|
||
{ id: 'e4', fromNode: 'fe-1', fromSide: 'right', toNode: 'feat-1', toSide: 'left' },
|
||
{ id: 'e5', fromNode: 'be-1', fromSide: 'right', toNode: 'feat-1', toSide: 'left' },
|
||
{ id: 'e6', fromNode: 'nodes-1', fromSide: 'bottom', toNode: 'tech-1', toSide: 'top' },
|
||
{ id: 'e7', fromNode: 'be-1', fromSide: 'bottom', toNode: 'future-1', toSide: 'top' },
|
||
{ id: 'e8', fromNode: 'feat-1', fromSide: 'bottom', toNode: 'db-1', toSide: 'top' },
|
||
{ id: 'e9', fromNode: 'db-1', fromSide: 'bottom', toNode: 'deploy-1', toSide: 'top' },
|
||
{ id: 'e10', fromNode: 'tech-1', fromSide: 'right', toNode: 'future-1', toSide: 'left' },
|
||
{ id: 'e11', fromNode: 'tech-1', fromSide: 'bottom', toNode: 'social-1', toSide: 'top' },
|
||
{ id: 'e12', fromNode: 'future-1', fromSide: 'bottom', toNode: 'files-1', toSide: 'top' },
|
||
{ id: 'e13', fromNode: 'social-1', fromSide: 'right', toNode: 'files-1', toSide: 'left' }
|
||
],
|
||
createdAt: new Date().toISOString(),
|
||
updatedAt: new Date().toISOString(),
|
||
ownerId: '1',
|
||
isPublic: true
|
||
}
|
||
|
||
canvases.set('xflow-project', xflowProjectCanvas)
|
||
|
||
// Create shared canvases for groups
|
||
const sharedCanvases = [
|
||
{ id: 'team-roadmap', name: 'Team Roadmap 2025', groupId: 'group-1', ownerId: '1' },
|
||
{ id: 'beta-feedback', name: 'Beta Feedback Map', groupId: 'group-2', ownerId: '1' },
|
||
{ id: 'ai-research', name: 'AI Integration Ideas', groupId: 'group-3', ownerId: '2' }
|
||
]
|
||
|
||
sharedCanvases.forEach(canvas => {
|
||
const canvasData: CanvasData = {
|
||
id: canvas.id,
|
||
name: canvas.name,
|
||
nodes: [
|
||
{
|
||
id: `${canvas.id}-node-1`,
|
||
type: 'text',
|
||
x: 200,
|
||
y: 200,
|
||
width: 300,
|
||
height: 150,
|
||
text: `# ${canvas.name}\n\nThis is a shared canvas for the group.\n\nStart adding your ideas!`,
|
||
style: { background: '#1e293b', border: '#3b82f6' }
|
||
}
|
||
],
|
||
edges: [],
|
||
createdAt: new Date().toISOString(),
|
||
updatedAt: new Date().toISOString(),
|
||
ownerId: canvas.ownerId,
|
||
groupId: canvas.groupId,
|
||
isPublic: true
|
||
}
|
||
canvases.set(canvas.id, canvasData)
|
||
})
|
||
|
||
// Add demo activities
|
||
const demoActivities: Activity[] = [
|
||
{ id: 'activity-1', userId: '1', action: 'created_canvas', targetId: 'xflow-project', targetType: 'canvas', timestamp: new Date().toISOString() },
|
||
{ id: 'activity-2', userId: '2', action: 'joined_group', targetId: 'group-1', targetType: 'group', timestamp: new Date().toISOString() },
|
||
{ id: 'activity-3', userId: '3', action: 'created_canvas', targetId: 'ai-research', targetType: 'canvas', timestamp: new Date().toISOString() },
|
||
{ id: 'activity-4', userId: '1', action: 'followed_user', targetId: '2', targetType: 'user', timestamp: new Date().toISOString() }
|
||
]
|
||
|
||
demoActivities.forEach(activity => {
|
||
activities.set(activity.id, activity)
|
||
})
|
||
|
||
// Add demo follows
|
||
follows.set('1-2', { followerId: '1', followingId: '2', since: new Date().toISOString() })
|
||
follows.set('2-3', { followerId: '2', followingId: '3', since: new Date().toISOString() })
|
||
|
||
// Keep the old demo canvas for backward compatibility
|
||
canvases.set('demo', {
|
||
id: 'demo',
|
||
name: 'Demo Canvas',
|
||
nodes: [
|
||
{
|
||
id: 'node-1',
|
||
type: 'text',
|
||
x: 0,
|
||
y: 0,
|
||
width: 300,
|
||
height: 200,
|
||
text: '# Welcome\n\nЭто демо-нода с **Markdown** поддержкой.',
|
||
state: 'pending',
|
||
style: { background: '#1e293b', border: '#334155' }
|
||
},
|
||
{
|
||
id: 'node-2',
|
||
type: 'file',
|
||
x: 400,
|
||
y: 0,
|
||
width: 300,
|
||
height: 200,
|
||
file: 'tasks.md',
|
||
frontmatter: { alarm: '2025-04-24T09:00:00Z', script_id: 'notify', assigned_to: 'user' },
|
||
state: 'pending',
|
||
style: { background: '#1e293b', border: '#334155' }
|
||
},
|
||
{
|
||
id: 'node-3',
|
||
type: 'text',
|
||
x: 200,
|
||
y: 300,
|
||
width: 300,
|
||
height: 150,
|
||
text: '### Завершенная задача\n- [x] Настроить PWA',
|
||
state: 'completed',
|
||
style: { background: '#064e3b', border: '#059669' }
|
||
}
|
||
],
|
||
edges: [
|
||
{ id: 'edge-1', fromNode: 'node-1', fromSide: 'right', toNode: 'node-2', toSide: 'left' },
|
||
{ id: 'edge-2', fromNode: 'node-2', fromSide: 'bottom', toNode: 'node-3', toSide: 'top' }
|
||
],
|
||
createdAt: new Date().toISOString(),
|
||
updatedAt: new Date().toISOString(),
|
||
ownerId: '1'
|
||
})
|
||
|
||
export function getUserAchievements(userId: string) {
|
||
return achievements.get(userId) || []
|
||
}
|
||
|
||
export function addAchievement(userId: string, achievement: any) {
|
||
const list = achievements.get(userId) || []
|
||
list.push({ ...achievement, unlockedAt: new Date().toISOString() })
|
||
achievements.set(userId, list)
|
||
}
|