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

This commit is contained in:
XFlow Deploy
2026-09-28 08:57:32 +05:00
parent edcd30c829
commit 0837b3f612
204 changed files with 24616 additions and 39 deletions
@@ -0,0 +1,67 @@
import { IUseCase } from '../../core/application/IUseCase';
import { Result } from '../../core/domain/Result';
import { DomainError, ValidationError } from '../../core/domain/DomainError';
import { IListRepository } from '../domain/IListRepository';
import { NoteList } from '../domain/NoteList';
import { ListItem } from '../domain/ListItem';
import { AddListItemDto } from './dtos/ListDtos';
export class AddListItemUseCase implements IUseCase<AddListItemDto, ListItem> {
constructor(private readonly listRepository: IListRepository) {}
public async execute(dto: AddListItemDto): Promise<Result<ListItem, DomainError>> {
if (!dto.title || dto.title.trim().length === 0) {
return Result.fail(new ValidationError('Item title is required'));
}
if (!dto.ownerId) {
return Result.fail(new ValidationError('Owner ID is required'));
}
let targetList: NoteList | null = null;
if (dto.listId) {
targetList = await this.listRepository.findListById(dto.listId);
} else if (dto.listTitle) {
targetList = await this.listRepository.findListByTitleAndOwner(dto.listTitle.trim(), dto.ownerId);
}
// Если список не найден по названию (например, первая запись фильма), создаем список автоматически
if (!targetList) {
const listTitle = dto.listTitle?.trim() || 'Общий список';
const category = dto.category || (listTitle.toLowerCase().includes('фильм') ? 'movies' : 'general');
targetList = new NoteList({
id: `list-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`,
title: listTitle,
category: category,
description: `Автоматически создан для ${listTitle}`,
ownerId: dto.ownerId,
createdAt: new Date(),
updatedAt: new Date(),
items: []
});
await this.listRepository.saveList(targetList);
}
const newItem = new ListItem({
id: `item-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`,
listId: targetList.id,
title: dto.title.trim(),
description: dto.description || '',
isCompleted: false,
status: targetList.category === 'movies' ? 'want_to_watch' : 'todo',
priority: dto.priority || 'normal',
metadata: dto.metadata || {},
dueDate: null,
createdAt: new Date(),
updatedAt: new Date()
});
await this.listRepository.saveItem(newItem);
targetList.addItem(newItem);
return Result.ok(newItem);
}
}
@@ -0,0 +1,23 @@
import { IUseCase } from '../../core/application/IUseCase';
import { Result } from '../../core/domain/Result';
import { DomainError, ValidationError } from '../../core/domain/DomainError';
import { IListRepository } from '../domain/IListRepository';
import { NoteList } from '../domain/NoteList';
export interface GetListsRequest {
ownerId: string;
category?: string;
}
export class GetListsUseCase implements IUseCase<GetListsRequest, NoteList[]> {
constructor(private readonly listRepository: IListRepository) {}
public async execute(req: GetListsRequest): Promise<Result<NoteList[], DomainError>> {
if (!req.ownerId) {
return Result.fail(new ValidationError('Owner ID is required'));
}
const lists = await this.listRepository.findListsByOwner(req.ownerId, req.category);
return Result.ok(lists);
}
}
@@ -0,0 +1,30 @@
import { IUseCase } from '../../core/application/IUseCase';
import { Result } from '../../core/domain/Result';
import { DomainError, NotFoundError, ValidationError } from '../../core/domain/DomainError';
import { IListRepository } from '../domain/IListRepository';
import { ListItem } from '../domain/ListItem';
import { ToggleListItemDto } from './dtos/ListDtos';
export class ToggleListItemUseCase implements IUseCase<ToggleListItemDto, ListItem> {
constructor(private readonly listRepository: IListRepository) {}
public async execute(dto: ToggleListItemDto): Promise<Result<ListItem, DomainError>> {
if (!dto.itemId) {
return Result.fail(new ValidationError('Item ID is required'));
}
const item = await this.listRepository.findItemById(dto.itemId);
if (!item) {
return Result.fail(new NotFoundError(`Item with ID ${dto.itemId} not found`));
}
if (dto.status) {
item.updateStatus(dto.status);
} else {
item.toggleCompleted();
}
await this.listRepository.saveItem(item);
return Result.ok(item);
}
}
@@ -0,0 +1,25 @@
export interface CreateListDto {
title: string;
category?: string;
description?: string;
color?: string;
icon?: string;
ownerId: string;
}
export interface AddListItemDto {
listId?: string;
listTitle?: string; // Если передано имя (например из голосового ввода Gemini или CLI "Фильмы")
category?: string; // Категория по умолчанию, если список создается на лету
title: string;
description?: string;
priority?: string;
metadata?: Record<string, any>;
ownerId: string;
}
export interface ToggleListItemDto {
itemId: string;
status?: string;
ownerId: string;
}