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