feat: gateway CRUD routes — conversations, projects, missions, tasks (P1-005/006) (#72)
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #72.
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
"dependencies": {
|
||||
"@mariozechner/pi-coding-agent": "~0.57.1",
|
||||
"@mosaic/auth": "workspace:^",
|
||||
"@mosaic/brain": "workspace:^",
|
||||
"@mosaic/db": "workspace:^",
|
||||
"@nestjs/common": "^11.0.0",
|
||||
"@nestjs/core": "^11.0.0",
|
||||
|
||||
@@ -2,11 +2,26 @@ import { Module } from '@nestjs/common';
|
||||
import { HealthController } from './health/health.controller.js';
|
||||
import { DatabaseModule } from './database/database.module.js';
|
||||
import { AuthModule } from './auth/auth.module.js';
|
||||
import { BrainModule } from './brain/brain.module.js';
|
||||
import { AgentModule } from './agent/agent.module.js';
|
||||
import { ChatModule } from './chat/chat.module.js';
|
||||
import { ConversationsModule } from './conversations/conversations.module.js';
|
||||
import { ProjectsModule } from './projects/projects.module.js';
|
||||
import { MissionsModule } from './missions/missions.module.js';
|
||||
import { TasksModule } from './tasks/tasks.module.js';
|
||||
|
||||
@Module({
|
||||
imports: [DatabaseModule, AuthModule, AgentModule, ChatModule],
|
||||
imports: [
|
||||
DatabaseModule,
|
||||
AuthModule,
|
||||
BrainModule,
|
||||
AgentModule,
|
||||
ChatModule,
|
||||
ConversationsModule,
|
||||
ProjectsModule,
|
||||
MissionsModule,
|
||||
TasksModule,
|
||||
],
|
||||
controllers: [HealthController],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
19
apps/gateway/src/brain/brain.module.ts
Normal file
19
apps/gateway/src/brain/brain.module.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { createBrain, type Brain } from '@mosaic/brain';
|
||||
import type { Db } from '@mosaic/db';
|
||||
import { DB } from '../database/database.module.js';
|
||||
|
||||
export const BRAIN = 'BRAIN';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [
|
||||
{
|
||||
provide: BRAIN,
|
||||
useFactory: (db: Db): Brain => createBrain(db),
|
||||
inject: [DB],
|
||||
},
|
||||
],
|
||||
exports: [BRAIN],
|
||||
})
|
||||
export class BrainModule {}
|
||||
83
apps/gateway/src/conversations/conversations.controller.ts
Normal file
83
apps/gateway/src/conversations/conversations.controller.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Inject,
|
||||
NotFoundException,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import type { Brain } from '@mosaic/brain';
|
||||
import { BRAIN } from '../brain/brain.module.js';
|
||||
import { AuthGuard } from '../auth/auth.guard.js';
|
||||
import { CurrentUser } from '../auth/current-user.decorator.js';
|
||||
import type {
|
||||
CreateConversationDto,
|
||||
UpdateConversationDto,
|
||||
SendMessageDto,
|
||||
} from './conversations.dto.js';
|
||||
|
||||
@Controller('api/conversations')
|
||||
@UseGuards(AuthGuard)
|
||||
export class ConversationsController {
|
||||
constructor(@Inject(BRAIN) private readonly brain: Brain) {}
|
||||
|
||||
@Get()
|
||||
async list(@CurrentUser() user: { id: string }) {
|
||||
return this.brain.conversations.findAll(user.id);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async findOne(@Param('id') id: string) {
|
||||
const conversation = await this.brain.conversations.findById(id);
|
||||
if (!conversation) throw new NotFoundException('Conversation not found');
|
||||
return conversation;
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@CurrentUser() user: { id: string }, @Body() dto: CreateConversationDto) {
|
||||
return this.brain.conversations.create({
|
||||
userId: user.id,
|
||||
title: dto.title,
|
||||
projectId: dto.projectId,
|
||||
});
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
async update(@Param('id') id: string, @Body() dto: UpdateConversationDto) {
|
||||
const conversation = await this.brain.conversations.update(id, dto);
|
||||
if (!conversation) throw new NotFoundException('Conversation not found');
|
||||
return conversation;
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
async remove(@Param('id') id: string) {
|
||||
const deleted = await this.brain.conversations.remove(id);
|
||||
if (!deleted) throw new NotFoundException('Conversation not found');
|
||||
}
|
||||
|
||||
@Get(':id/messages')
|
||||
async listMessages(@Param('id') id: string) {
|
||||
const conversation = await this.brain.conversations.findById(id);
|
||||
if (!conversation) throw new NotFoundException('Conversation not found');
|
||||
return this.brain.conversations.findMessages(id);
|
||||
}
|
||||
|
||||
@Post(':id/messages')
|
||||
async addMessage(@Param('id') id: string, @Body() dto: SendMessageDto) {
|
||||
const conversation = await this.brain.conversations.findById(id);
|
||||
if (!conversation) throw new NotFoundException('Conversation not found');
|
||||
return this.brain.conversations.addMessage({
|
||||
conversationId: id,
|
||||
role: dto.role,
|
||||
content: dto.content,
|
||||
metadata: dto.metadata,
|
||||
});
|
||||
}
|
||||
}
|
||||
15
apps/gateway/src/conversations/conversations.dto.ts
Normal file
15
apps/gateway/src/conversations/conversations.dto.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export interface CreateConversationDto {
|
||||
title?: string;
|
||||
projectId?: string;
|
||||
}
|
||||
|
||||
export interface UpdateConversationDto {
|
||||
title?: string;
|
||||
projectId?: string | null;
|
||||
}
|
||||
|
||||
export interface SendMessageDto {
|
||||
role: 'user' | 'assistant' | 'system';
|
||||
content: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
7
apps/gateway/src/conversations/conversations.module.ts
Normal file
7
apps/gateway/src/conversations/conversations.module.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConversationsController } from './conversations.controller.js';
|
||||
|
||||
@Module({
|
||||
controllers: [ConversationsController],
|
||||
})
|
||||
export class ConversationsModule {}
|
||||
60
apps/gateway/src/missions/missions.controller.ts
Normal file
60
apps/gateway/src/missions/missions.controller.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Inject,
|
||||
NotFoundException,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import type { Brain } from '@mosaic/brain';
|
||||
import { BRAIN } from '../brain/brain.module.js';
|
||||
import { AuthGuard } from '../auth/auth.guard.js';
|
||||
import type { CreateMissionDto, UpdateMissionDto } from './missions.dto.js';
|
||||
|
||||
@Controller('api/missions')
|
||||
@UseGuards(AuthGuard)
|
||||
export class MissionsController {
|
||||
constructor(@Inject(BRAIN) private readonly brain: Brain) {}
|
||||
|
||||
@Get()
|
||||
async list() {
|
||||
return this.brain.missions.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async findOne(@Param('id') id: string) {
|
||||
const mission = await this.brain.missions.findById(id);
|
||||
if (!mission) throw new NotFoundException('Mission not found');
|
||||
return mission;
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@Body() dto: CreateMissionDto) {
|
||||
return this.brain.missions.create({
|
||||
name: dto.name,
|
||||
description: dto.description,
|
||||
projectId: dto.projectId,
|
||||
status: dto.status,
|
||||
});
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
async update(@Param('id') id: string, @Body() dto: UpdateMissionDto) {
|
||||
const mission = await this.brain.missions.update(id, dto);
|
||||
if (!mission) throw new NotFoundException('Mission not found');
|
||||
return mission;
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
async remove(@Param('id') id: string) {
|
||||
const deleted = await this.brain.missions.remove(id);
|
||||
if (!deleted) throw new NotFoundException('Mission not found');
|
||||
}
|
||||
}
|
||||
14
apps/gateway/src/missions/missions.dto.ts
Normal file
14
apps/gateway/src/missions/missions.dto.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface CreateMissionDto {
|
||||
name: string;
|
||||
description?: string;
|
||||
projectId?: string;
|
||||
status?: 'planning' | 'active' | 'paused' | 'completed' | 'failed';
|
||||
}
|
||||
|
||||
export interface UpdateMissionDto {
|
||||
name?: string;
|
||||
description?: string | null;
|
||||
projectId?: string | null;
|
||||
status?: 'planning' | 'active' | 'paused' | 'completed' | 'failed';
|
||||
metadata?: Record<string, unknown> | null;
|
||||
}
|
||||
7
apps/gateway/src/missions/missions.module.ts
Normal file
7
apps/gateway/src/missions/missions.module.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MissionsController } from './missions.controller.js';
|
||||
|
||||
@Module({
|
||||
controllers: [MissionsController],
|
||||
})
|
||||
export class MissionsModule {}
|
||||
61
apps/gateway/src/projects/projects.controller.ts
Normal file
61
apps/gateway/src/projects/projects.controller.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Inject,
|
||||
NotFoundException,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import type { Brain } from '@mosaic/brain';
|
||||
import { BRAIN } from '../brain/brain.module.js';
|
||||
import { AuthGuard } from '../auth/auth.guard.js';
|
||||
import { CurrentUser } from '../auth/current-user.decorator.js';
|
||||
import type { CreateProjectDto, UpdateProjectDto } from './projects.dto.js';
|
||||
|
||||
@Controller('api/projects')
|
||||
@UseGuards(AuthGuard)
|
||||
export class ProjectsController {
|
||||
constructor(@Inject(BRAIN) private readonly brain: Brain) {}
|
||||
|
||||
@Get()
|
||||
async list() {
|
||||
return this.brain.projects.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async findOne(@Param('id') id: string) {
|
||||
const project = await this.brain.projects.findById(id);
|
||||
if (!project) throw new NotFoundException('Project not found');
|
||||
return project;
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@CurrentUser() user: { id: string }, @Body() dto: CreateProjectDto) {
|
||||
return this.brain.projects.create({
|
||||
name: dto.name,
|
||||
description: dto.description,
|
||||
status: dto.status,
|
||||
ownerId: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
async update(@Param('id') id: string, @Body() dto: UpdateProjectDto) {
|
||||
const project = await this.brain.projects.update(id, dto);
|
||||
if (!project) throw new NotFoundException('Project not found');
|
||||
return project;
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
async remove(@Param('id') id: string) {
|
||||
const deleted = await this.brain.projects.remove(id);
|
||||
if (!deleted) throw new NotFoundException('Project not found');
|
||||
}
|
||||
}
|
||||
12
apps/gateway/src/projects/projects.dto.ts
Normal file
12
apps/gateway/src/projects/projects.dto.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface CreateProjectDto {
|
||||
name: string;
|
||||
description?: string;
|
||||
status?: 'active' | 'paused' | 'completed' | 'archived';
|
||||
}
|
||||
|
||||
export interface UpdateProjectDto {
|
||||
name?: string;
|
||||
description?: string | null;
|
||||
status?: 'active' | 'paused' | 'completed' | 'archived';
|
||||
metadata?: Record<string, unknown> | null;
|
||||
}
|
||||
7
apps/gateway/src/projects/projects.module.ts
Normal file
7
apps/gateway/src/projects/projects.module.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ProjectsController } from './projects.controller.js';
|
||||
|
||||
@Module({
|
||||
controllers: [ProjectsController],
|
||||
})
|
||||
export class ProjectsModule {}
|
||||
79
apps/gateway/src/tasks/tasks.controller.ts
Normal file
79
apps/gateway/src/tasks/tasks.controller.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Inject,
|
||||
NotFoundException,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import type { Brain } from '@mosaic/brain';
|
||||
import { BRAIN } from '../brain/brain.module.js';
|
||||
import { AuthGuard } from '../auth/auth.guard.js';
|
||||
import type { CreateTaskDto, UpdateTaskDto } from './tasks.dto.js';
|
||||
|
||||
@Controller('api/tasks')
|
||||
@UseGuards(AuthGuard)
|
||||
export class TasksController {
|
||||
constructor(@Inject(BRAIN) private readonly brain: Brain) {}
|
||||
|
||||
@Get()
|
||||
async list(
|
||||
@Query('projectId') projectId?: string,
|
||||
@Query('missionId') missionId?: string,
|
||||
@Query('status') status?: string,
|
||||
) {
|
||||
if (projectId) return this.brain.tasks.findByProject(projectId);
|
||||
if (missionId) return this.brain.tasks.findByMission(missionId);
|
||||
if (status)
|
||||
return this.brain.tasks.findByStatus(
|
||||
status as Parameters<typeof this.brain.tasks.findByStatus>[0],
|
||||
);
|
||||
return this.brain.tasks.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async findOne(@Param('id') id: string) {
|
||||
const task = await this.brain.tasks.findById(id);
|
||||
if (!task) throw new NotFoundException('Task not found');
|
||||
return task;
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@Body() dto: CreateTaskDto) {
|
||||
return this.brain.tasks.create({
|
||||
title: dto.title,
|
||||
description: dto.description,
|
||||
status: dto.status,
|
||||
priority: dto.priority,
|
||||
projectId: dto.projectId,
|
||||
missionId: dto.missionId,
|
||||
assignee: dto.assignee,
|
||||
tags: dto.tags,
|
||||
dueDate: dto.dueDate ? new Date(dto.dueDate) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
async update(@Param('id') id: string, @Body() dto: UpdateTaskDto) {
|
||||
const task = await this.brain.tasks.update(id, {
|
||||
...dto,
|
||||
dueDate: dto.dueDate ? new Date(dto.dueDate) : dto.dueDate === null ? null : undefined,
|
||||
});
|
||||
if (!task) throw new NotFoundException('Task not found');
|
||||
return task;
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
async remove(@Param('id') id: string) {
|
||||
const deleted = await this.brain.tasks.remove(id);
|
||||
if (!deleted) throw new NotFoundException('Task not found');
|
||||
}
|
||||
}
|
||||
24
apps/gateway/src/tasks/tasks.dto.ts
Normal file
24
apps/gateway/src/tasks/tasks.dto.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export interface CreateTaskDto {
|
||||
title: string;
|
||||
description?: string;
|
||||
status?: 'not-started' | 'in-progress' | 'blocked' | 'done' | 'cancelled';
|
||||
priority?: 'critical' | 'high' | 'medium' | 'low';
|
||||
projectId?: string;
|
||||
missionId?: string;
|
||||
assignee?: string;
|
||||
tags?: string[];
|
||||
dueDate?: string;
|
||||
}
|
||||
|
||||
export interface UpdateTaskDto {
|
||||
title?: string;
|
||||
description?: string | null;
|
||||
status?: 'not-started' | 'in-progress' | 'blocked' | 'done' | 'cancelled';
|
||||
priority?: 'critical' | 'high' | 'medium' | 'low';
|
||||
projectId?: string | null;
|
||||
missionId?: string | null;
|
||||
assignee?: string | null;
|
||||
tags?: string[] | null;
|
||||
dueDate?: string | null;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
}
|
||||
7
apps/gateway/src/tasks/tasks.module.ts
Normal file
7
apps/gateway/src/tasks/tasks.module.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TasksController } from './tasks.controller.js';
|
||||
|
||||
@Module({
|
||||
controllers: [TasksController],
|
||||
})
|
||||
export class TasksModule {}
|
||||
Reference in New Issue
Block a user