Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
76246a7
security(backend): fix ReadActionGuard and secure actions endpoints
wp99cp Jun 24, 2026
c41f373
refactor(backend): pluralize controller routes and transition RPC sty…
wp99cp Jun 24, 2026
ff67cde
refactor(dto): add response DTOs and type tags/metadata endpoints
wp99cp Jun 24, 2026
9c74d76
refactor(cli): update routes and client queries to use plural REST en…
wp99cp Jun 24, 2026
0937fb1
refactor(frontend): update api queries and mutations to match plural …
wp99cp Jun 24, 2026
3c0d394
test(backend): update integration tests for restructured plural endpo…
wp99cp Jun 24, 2026
95535a0
refactor(backend): consolidate project, mission, and file guards into…
wp99cp Jun 24, 2026
3cea902
refactor(dto): rename tag-related DTOs and update packages/api-dto ex…
wp99cp Jun 24, 2026
9ebe903
refactor(backend): rename tag service and references to metadata service
wp99cp Jun 24, 2026
52ddb24
refactor(backend): rename remaining internal tag/tagService reference…
wp99cp Jun 24, 2026
bba9d29
refactor(backend): move validation logic from controllers to DTO clas…
wp99cp Jun 24, 2026
f36af81
refactor(api): transition file endpoints from RPC to RESTful resources
wp99cp Jun 24, 2026
ce313b2
doc(swagger): align Swagger response decorators with NestJS default s…
wp99cp Jun 24, 2026
637acc7
refactor(backend): use validation query DTO for topics route
wp99cp Jun 24, 2026
a248187
test(utils): enable schema synchronization and improve project edit t…
wp99cp Jun 24, 2026
0c35c47
refactor(api-dto): decorate DTOs with class-transformer for declarati…
wp99cp Jun 24, 2026
0932225
refactor(backend): use plainToInstance for declarative serialization …
wp99cp Jun 24, 2026
61c9a8d
refactor(api): complete pluralized REST API refactoring, fix routing …
wp99cp Jun 24, 2026
c6c8d5a
refactor(backend): break down FileService and AccessService God Classes
wp99cp Jun 24, 2026
b0951ea
refactor(backend): directly inject sub-services and remove facades
wp99cp Jun 24, 2026
f5ed9e1
fix(cli,backend,docker): fix CLI HTTP delete requests, resolve backen…
wp99cp Jun 24, 2026
d710c7f
fix(backend-tests): support loading generated endpoints from both .en…
wp99cp Jun 24, 2026
ab84b29
style: run prettier on generated api-modules.md
wp99cp Jun 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 20 additions & 17 deletions backend/api-modules.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
| Module | Path | Description |
| :---------------------------- | :----------------------- | :------------------------- |
| [`access`](access.md) | `/access` | Docs for access module |
| [`actions`](actions.md) | `/actions` | Docs for actions module |
| [`health`](health.md) | `/api/health` | Docs for health module |
| [`auth`](auth.md) | `/auth` | Docs for auth module |
| [`category`](category.md) | `/category` | Docs for category module |
| [`file`](file.md) | `/files` | Docs for file module |
| [`foxglove`](foxglove.md) | `/integrations/foxglove` | Docs for foxglove module |
| [`mission`](mission.md) | `/mission` | Docs for mission module |
| [`oldproject`](oldproject.md) | `/oldProject` | Docs for oldproject module |
| [`project`](project.md) | `/projects` | Docs for project module |
| [`tag`](tag.md) | `/tag` | Docs for tag module |
| [`templates`](templates.md) | `/templates` | Docs for templates module |
| [`topic`](topic.md) | `/topic` | Docs for topic module |
| [`user`](user.md) | `/user` | Docs for user module |
| [`worker`](worker.md) | `/worker` | Docs for worker module |
| Module | Path | Description |
| :-------------------------------- | :----------------------- | :--------------------------- |
| [`favicon`](favicon.md) | `//` | Docs for favicon module |
| [`access`](access.md) | `/access-groups` | Docs for access module |
| [`actions`](actions.md) | `/actions` | Docs for actions module |
| [`health`](health.md) | `/api/health` | Docs for health module |
| [`auth`](auth.md) | `/auth` | Docs for auth module |
| [`category`](category.md) | `/categories` | Docs for category module |
| [`file`](file.md) | `/files` | Docs for file module |
| [`hook`](hook.md) | `/hooks/actions` | Docs for hook module |
| [`foxglove`](foxglove.md) | `/integrations/foxglove` | Docs for foxglove module |
| [`metadata`](metadata.md) | `/metadata` | Docs for metadata module |
| [`metadatatype`](metadatatype.md) | `/metadata-types` | Docs for metadatatype module |
| [`mission`](mission.md) | `/missions` | Docs for mission module |
| [`project`](project.md) | `/projects` | Docs for project module |
| [`templates`](templates.md) | `/templates` | Docs for templates module |
| [`topic`](topic.md) | `/topics` | Docs for topic module |
| [`trigger`](trigger.md) | `/triggers` | Docs for trigger module |
| [`user`](user.md) | `/users` | Docs for user module |
| [`worker`](worker.md) | `/workers` | Docs for worker module |
2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"gen:docs": "node -r ts-node/register -r tsconfig-paths/register ./scripts/generate-openapi.ts"
},
"dependencies": {
"@aws-sdk/client-s3": "3.1010.0",
"@aws-sdk/client-sts": "3.1005.0",
"@kleinkram/api-dto": "workspace:*",
"@kleinkram/backend-common": "workspace:*",
Expand Down Expand Up @@ -80,7 +81,6 @@
"winston-loki": "^6.1.4"
},
"devDependencies": {
"@aws-sdk/client-s3": "3.1010.0",
"@jest/globals": "^30.2.0",
"@nestjs/cli": "^11.0.16",
"@nestjs/schematics": "^11.0.9",
Expand Down
4 changes: 2 additions & 2 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import { CategoryModule } from './endpoints/category/category.module';
import { FileModule } from './endpoints/file/file.module';
import { HealthModule } from './endpoints/health/health.module';
import { FoxgloveModule } from './endpoints/integrations/foxglove.module';
import { MetadataModule } from './endpoints/metadata/metadata.module';
import { MissionModule } from './endpoints/mission/mission.module';
import { ProjectModule } from './endpoints/project/project.module';
import { QueueModule } from './endpoints/queue/queue.module';
import { TagModule } from './endpoints/tag/tag.module';
import { TemplatesModule } from './endpoints/templates/templates.module';
import { TopicModule } from './endpoints/topic/topic.module';
import { TriggerModule } from './endpoints/trigger/trigger.module';
Expand Down Expand Up @@ -84,7 +84,7 @@ import { DBDumper } from './services/dbdumper.service';
PassportModule,
ActionModule,
TemplatesModule,
TagModule,
MetadataModule,
WorkerModule,
CategoryModule,
ScheduleModule.forRoot(),
Expand Down
9 changes: 9 additions & 0 deletions backend/src/decorators.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { applyDecorators, SetMetadata } from '@nestjs/common';
import {
ApiResponseCommonMetadata,
ApiCreatedResponse as SwaggerApiCreatedResponse,
ApiOkResponse as SwaggerApiOkResponse,
ApiResponse as SwaggerApiResponse,
} from '@nestjs/swagger';
Expand All @@ -21,6 +22,14 @@ export const ApiOkResponse = (
): ReturnType<typeof applyDecorators> =>
applyDecorators(OutputDto(options.type), SwaggerApiOkResponse(options));

export const ApiCreatedResponse = (
options: ApiResponseCommonMetadata,
): ReturnType<typeof applyDecorators> =>
applyDecorators(
OutputDto(options.type),
SwaggerApiCreatedResponse(options),
);

export const ApiResponse = (
options: ApiResponseCommonMetadata,
): ReturnType<typeof applyDecorators> =>
Expand Down
77 changes: 46 additions & 31 deletions backend/src/endpoints/access/access.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { ApiOkResponse, ApiResponse, OutputDto } from '@/decorators';
import { AccessService } from '@/services/access.service';
import {
ApiCreatedResponse,
ApiOkResponse,
ApiResponse,
OutputDto,
} from '@/decorators';
import { AccessModificationService } from '@/services/access-modification.service';
import { AccessQueryService } from '@/services/access-query.service';
import {
AccessGroupAuditLogsDto,
AccessGroupDto,
Expand All @@ -16,7 +22,6 @@ import {
SetAccessGroupUserExpirationDto,
SetAccessGroupUserPermissionsDto,
} from '@kleinkram/api-dto';
import { AccessGroupEntity } from '@kleinkram/backend-common';
import {
Body,
Controller,
Expand All @@ -39,9 +44,12 @@ import {
UserOnly,
} from '../auth/roles.decorator';

@Controller('access')
@Controller('access-groups')
export class AccessController {
constructor(private readonly accessService: AccessService) {}
constructor(
private readonly accessQueryService: AccessQueryService,
private readonly accessModificationService: AccessModificationService,
) {}

@ApiOperation({
summary: 'Get filtered AccessGroups',
Expand All @@ -58,7 +66,7 @@ export class AccessController {
async search(
@Query() query: GetFilteredAccessGroupsDto,
): Promise<AccessGroupsDto> {
return this.accessService.searchAccessGroup(
return this.accessQueryService.searchAccessGroup(
query.search,
query.type,
query.skip,
Expand Down Expand Up @@ -86,7 +94,7 @@ export class AccessController {
@ParameterUID('uuid', 'AccessGroup UUID') uuid: string,
@AddUser() user: AuthHeader,
): Promise<AccessGroupDto> {
return await this.accessService
return await this.accessQueryService
.getAccessGroup(uuid, user.user.uuid)
.catch((error: unknown) => {
if (error instanceof EntityNotFoundError) {
Expand All @@ -110,12 +118,12 @@ export class AccessController {
async getAuditLogs(
@ParameterUID('uuid', 'AccessGroup UUID') uuid: string,
): Promise<AccessGroupAuditLogsDto> {
return this.accessService.getAuditLogs(uuid);
return this.accessQueryService.getAuditLogs(uuid);
}

@Post()
@CanCreate()
@ApiOkResponse({
@ApiCreatedResponse({
type: AccessGroupDto,
description: 'Returns the created AccessGroup',
})
Expand All @@ -128,11 +136,12 @@ export class AccessController {
@Body() body: CreateAccessGroupDto,
@AddUser() user: AuthHeader,
): Promise<AccessGroupDto> {
const accessGroup = await this.accessService.createAccessGroup(
body.name,
user,
);
return this.accessService.getAccessGroup(
const accessGroup =
await this.accessModificationService.createAccessGroup(
body.name,
user,
);
return this.accessQueryService.getAccessGroup(
accessGroup.uuid,
user.user.uuid,
);
Expand All @@ -141,9 +150,8 @@ export class AccessController {
@ApiOperation({
summary: 'Add User to Access Group',
})
@ApiResponse({
status: 200,
type: AccessGroupEntity,
@ApiCreatedResponse({
type: AccessGroupDto,
description: 'The Access Group the user was added to.',
})
@ApiResponse({
Expand All @@ -153,13 +161,12 @@ export class AccessController {
})
@Post(':uuid/users')
@CanEditGroup()
@OutputDto(AccessGroupDto)
async addUserToAccessGroup(
@ParameterUID('uuid', 'UUID of AccessGroup') uuid: string,
@Body() body: AddUserToAccessGroupDto,
@AddUser() requestUser: AuthHeader,
): Promise<AccessGroupDto> {
await this.accessService
await this.accessModificationService
.addUserToAccessGroup(
uuid,
body.userUuid,
Expand All @@ -173,7 +180,10 @@ export class AccessController {
}
throw error;
});
return this.accessService.getAccessGroup(uuid, requestUser.user.uuid);
return this.accessQueryService.getAccessGroup(
uuid,
requestUser.user.uuid,
);
}

@ApiOperation({
Expand All @@ -192,12 +202,15 @@ export class AccessController {
@ParameterUID('userUuid', 'UUID of User to remove') userUuid: string,
@AddUser() requestUser: AuthHeader,
): Promise<AccessGroupDto> {
await this.accessService.removeUsersFromAccessGroup(
await this.accessModificationService.removeUsersFromAccessGroup(
uuid,
[userUuid],
requestUser,
);
return this.accessService.getAccessGroup(uuid, requestUser.user.uuid);
return this.accessQueryService.getAccessGroup(
uuid,
requestUser.user.uuid,
);
}

@ApiOperation({
Expand All @@ -216,32 +229,34 @@ export class AccessController {
@Body() body: RemoveUsersFromAccessGroupDto,
@AddUser() requestUser: AuthHeader,
): Promise<AccessGroupDto> {
await this.accessService.removeUsersFromAccessGroup(
await this.accessModificationService.removeUsersFromAccessGroup(
uuid,
body.userUuids,
requestUser,
);
return this.accessService.getAccessGroup(uuid, requestUser.user.uuid);
return this.accessQueryService.getAccessGroup(
uuid,
requestUser.user.uuid,
);
}

@ApiOperation({
summary: 'Add Access Group to Project',
description: 'Adds an Access Group to a Project with the given rights.',
})
@ApiOkResponse({
@ApiCreatedResponse({
description: 'Returns the Project',
type: ProjectDto,
})
@Post(':uuid/projects/:projectUuid')
@CanWriteProject()
@OutputDto(ProjectDto)
async addAccessGroupToProject(
@ParameterUID('uuid', 'UUID of AccessGroup') uuid: string,
@ParameterUID('projectUuid', 'UUID of Project') projectUuid: string,
@Body() body: AddAccessGroupToProjectDto,
@AddUser() user: AuthHeader,
): Promise<ProjectDto> {
return this.accessService.addAccessGroupToProject(
return this.accessModificationService.addAccessGroupToProject(
projectUuid,
uuid,
body.rights,
Expand All @@ -262,7 +277,7 @@ export class AccessController {
@ParameterUID('projectUuid', 'UUID of Project') projectUuid: string,
@AddUser() user: AuthHeader,
): Promise<RemoveAccessGroupFromProjectResponseDto> {
await this.accessService.removeAccessGroupFromProject(
await this.accessModificationService.removeAccessGroupFromProject(
projectUuid,
uuid,
user,
Expand All @@ -281,7 +296,7 @@ export class AccessController {
async deleteAccessGroup(
@ParameterUID('uuid', 'UUID of AccessGroup to be deleted') uuid: string,
): Promise<DeleteAccessGroupResponseDto> {
await this.accessService.deleteAccessGroup(uuid);
await this.accessModificationService.deleteAccessGroup(uuid);
return { success: true };
}

Expand All @@ -302,7 +317,7 @@ export class AccessController {
@Body() body: SetAccessGroupUserExpirationDto,
@AddUser() requestUser: AuthHeader,
): Promise<GroupMembershipDto> {
return this.accessService.setExpireDate(
return this.accessModificationService.setExpireDate(
uuid,
userUuid,
body.expireDate,
Expand All @@ -326,7 +341,7 @@ export class AccessController {
@Body() body: SetAccessGroupUserPermissionsDto,
@AddUser() requestUser: AuthHeader,
): Promise<GroupMembershipDto> {
return this.accessService.setCanEditGroup(
return this.accessModificationService.setCanEditGroup(
uuid,
userUuid,
body.canEditGroup,
Expand Down
15 changes: 12 additions & 3 deletions backend/src/endpoints/access/access.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AccessService } from '@/services/access.service';
import { AccessModificationService } from '@/services/access-modification.service';
import { AccessQueryService } from '@/services/access-query.service';
import {
AccessGroupAuditService,
AccessGroupEntity,
Expand All @@ -25,9 +26,17 @@ import { AccessController } from './access.controller';
]),
UserModule,
],
providers: [AccessService, AccessGroupAuditService],
providers: [
AccessQueryService,
AccessModificationService,
AccessGroupAuditService,
],
controllers: [AccessController],
exports: [AccessService],
exports: [
AccessQueryService,
AccessModificationService,
AccessGroupAuditService,
],
})
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class AccessModule {}
Loading
Loading