feat(gateway): WorkspaceService + ProjectBootstrapService + TeamsService (P8-015)
- WorkspaceService: path resolution, git init/clone, directory lifecycle (create/delete/exists), user and team root provisioning - ProjectBootstrapService: orchestrates DB record creation (via Brain) + workspace directory init in a single call - TeamsService: isMember, canAccessProject, findAll, findById, listMembers via Drizzle DB queries - WorkspaceController: POST /api/workspaces — auth-guarded project bootstrap endpoint - TeamsController: GET /api/teams, /:teamId, /:teamId/members, /:teamId/members/:userId - WorkspaceModule wired into AppModule - workspace.service.spec.ts: 5 unit tests for resolvePath (user, team, fallback, env var, default) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
63
apps/gateway/src/workspace/project-bootstrap.service.ts
Normal file
63
apps/gateway/src/workspace/project-bootstrap.service.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import type { Brain } from '@mosaic/brain';
|
||||
import { BRAIN } from '../brain/brain.tokens.js';
|
||||
import { WorkspaceService } from './workspace.service.js';
|
||||
|
||||
export interface BootstrapProjectParams {
|
||||
name: string;
|
||||
description?: string;
|
||||
userId: string;
|
||||
teamId?: string;
|
||||
repoUrl?: string;
|
||||
}
|
||||
|
||||
export interface BootstrapProjectResult {
|
||||
projectId: string;
|
||||
workspacePath: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ProjectBootstrapService {
|
||||
private readonly logger = new Logger(ProjectBootstrapService.name);
|
||||
|
||||
constructor(
|
||||
@Inject(BRAIN) private readonly brain: Brain,
|
||||
private readonly workspace: WorkspaceService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Bootstrap a new project: create DB record + workspace directory.
|
||||
* Returns the created project with its workspace path.
|
||||
*/
|
||||
async bootstrap(params: BootstrapProjectParams): Promise<BootstrapProjectResult> {
|
||||
const ownerType: 'user' | 'team' = params.teamId ? 'team' : 'user';
|
||||
|
||||
this.logger.log(
|
||||
`Bootstrapping project "${params.name}" for ${ownerType} ${params.teamId ?? params.userId}`,
|
||||
);
|
||||
|
||||
// 1. Create DB record
|
||||
const project = await this.brain.projects.create({
|
||||
name: params.name,
|
||||
description: params.description,
|
||||
ownerId: params.userId,
|
||||
teamId: params.teamId ?? null,
|
||||
ownerType,
|
||||
});
|
||||
|
||||
// 2. Create workspace directory
|
||||
const workspacePath = await this.workspace.create(
|
||||
{
|
||||
id: project.id,
|
||||
ownerType,
|
||||
userId: params.userId,
|
||||
teamId: params.teamId ?? null,
|
||||
},
|
||||
params.repoUrl,
|
||||
);
|
||||
|
||||
this.logger.log(`Project ${project.id} bootstrapped at ${workspacePath}`);
|
||||
|
||||
return { projectId: project.id, workspacePath };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user