chore(repo): restructure as monorepo with server and client workspaces

- move NestJS app into server/ subdirectory
- add client/ React+TypeScript (Vite) app with Hello World
- update docker-compose to build and run both services
- add root package.json declaring npm workspaces
- update .gitignore to cover node_modules and dist at all depths
This commit is contained in:
2026-04-08 21:28:01 +03:00
parent afc4627353
commit 5cc16725fb
88 changed files with 12637 additions and 405 deletions
+184
View File
@@ -0,0 +1,184 @@
import {
Body,
Controller,
Delete,
Get,
HttpCode,
Param,
ParseIntPipe,
Patch,
Post,
Query,
} from "@nestjs/common";
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
import { ScenarioService } from "./scenario.service";
import { CreateScenarioDto } from "./dto/create-scenario.dto";
import { UpdateScenarioDto } from "./dto/update-scenario.dto";
import { CreateScenarioStepDto } from "./dto/create-scenario-step.dto";
import { UpdateScenarioStepDto } from "./dto/update-scenario-step.dto";
import { PaginationQueryDto } from "./dto/pagination-query.dto";
import { ScenarioOrderBy } from "./scenario.service";
import { RunsQueryDto } from "./dto/runs-query.dto";
import { ScenarioExportDto } from "./dto/scenario-export.dto";
@ApiTags("scenarios")
@Controller("scenarios")
export class ScenarioController {
constructor(private readonly scenarioService: ScenarioService) {}
// ── Scenarios ─────────────────────────────────────────────────────────────
@Post()
@ApiOperation({ summary: "Create a scenario" })
@ApiResponse({ status: 201, description: "Scenario created" })
create(@Body() dto: CreateScenarioDto) {
return this.scenarioService.create(dto);
}
@Post("import")
@ApiOperation({ summary: "Import a scenario from an export payload" })
@ApiResponse({ status: 201, description: "Scenario imported" })
importScenario(@Body() dto: ScenarioExportDto) {
return this.scenarioService.importScenario(dto);
}
@Get()
@ApiOperation({ summary: "List all scenarios (paginated)" })
@ApiResponse({ status: 200 })
findAll(@Query() query: PaginationQueryDto<ScenarioOrderBy>) {
return this.scenarioService.findAll(query);
}
@Get(":id")
@ApiOperation({ summary: "Get a scenario with its steps" })
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario not found" })
findOne(@Param("id", ParseIntPipe) id: number) {
return this.scenarioService.findOne(id);
}
@Patch(":id")
@ApiOperation({ summary: "Update a scenario" })
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario not found" })
update(
@Param("id", ParseIntPipe) id: number,
@Body() dto: UpdateScenarioDto,
) {
return this.scenarioService.update(id, dto);
}
@Delete(":id")
@HttpCode(204)
@ApiOperation({ summary: "Delete a scenario and all its steps" })
@ApiResponse({ status: 204 })
@ApiResponse({ status: 404, description: "Scenario not found" })
remove(@Param("id", ParseIntPipe) id: number) {
return this.scenarioService.remove(id);
}
@Get(":id/export")
@ApiOperation({ summary: "Export a scenario as a portable JSON payload" })
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario not found" })
exportScenario(@Param("id", ParseIntPipe) id: number) {
return this.scenarioService.exportScenario(id);
}
// ── Steps ─────────────────────────────────────────────────────────────────
@Post(":id/steps")
@ApiOperation({ summary: "Add a step to a scenario" })
@ApiResponse({ status: 201, description: "Step created" })
@ApiResponse({ status: 404, description: "Scenario not found" })
createStep(
@Param("id", ParseIntPipe) id: number,
@Body() dto: CreateScenarioStepDto,
) {
return this.scenarioService.createStep(id, dto);
}
@Get(":id/steps/:stepId")
@ApiOperation({ summary: "Get a single step" })
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario or step not found" })
findStep(
@Param("id", ParseIntPipe) id: number,
@Param("stepId", ParseIntPipe) stepId: number,
) {
return this.scenarioService.findStep(id, stepId);
}
@Patch(":id/steps/:stepId")
@ApiOperation({ summary: "Update a step" })
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario or step not found" })
updateStep(
@Param("id", ParseIntPipe) id: number,
@Param("stepId", ParseIntPipe) stepId: number,
@Body() dto: UpdateScenarioStepDto,
) {
return this.scenarioService.updateStep(id, stepId, dto);
}
@Delete(":id/steps/:stepId")
@HttpCode(204)
@ApiOperation({ summary: "Delete a step" })
@ApiResponse({ status: 204 })
@ApiResponse({ status: 404, description: "Scenario or step not found" })
removeStep(
@Param("id", ParseIntPipe) id: number,
@Param("stepId", ParseIntPipe) stepId: number,
) {
return this.scenarioService.removeStep(id, stepId);
}
// ── Runs ──────────────────────────────────────────────────────────────────
@Get(":id/runs")
@ApiOperation({
summary: "List runs for a scenario (paginated, filterable by status)",
})
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario not found" })
findRuns(
@Param("id", ParseIntPipe) id: number,
@Query() query: RunsQueryDto,
) {
return this.scenarioService.findRuns(id, query);
}
@Post(":id/run")
@ApiOperation({ summary: "Create a new run for a scenario" })
@ApiResponse({ status: 201, description: "Run created with step runs" })
@ApiResponse({ status: 404, description: "Scenario not found" })
createRun(@Param("id", ParseIntPipe) id: number) {
return this.scenarioService.createRun(id);
}
@Get(":id/run/:runId")
@ApiOperation({ summary: "Get a specific run with step runs and logs" })
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario or run not found" })
findRun(
@Param("id", ParseIntPipe) id: number,
@Param("runId", ParseIntPipe) runId: number,
) {
return this.scenarioService.findRun(id, runId);
}
@Post(":id/run/:runId/wait")
@HttpCode(200)
@ApiOperation({
summary:
"Block until the run reaches pass or fail (max 5 min), then return run with logs",
})
@ApiResponse({ status: 200 })
@ApiResponse({ status: 404, description: "Scenario or run not found" })
waitForRun(
@Param("id", ParseIntPipe) id: number,
@Param("runId", ParseIntPipe) runId: number,
) {
return this.scenarioService.waitForRun(id, runId);
}
}