Skip to content

Commit 494e25e

Browse files
authored
Release 0.43.6
- don't log access denied errors - performance improvements by not returning all mission details on GET - improve error messages by verifying arguments passed to kleinkram - update dependencies
2 parents 774b623 + 55251f6 commit 494e25e

File tree

30 files changed

+393
-287
lines changed

30 files changed

+393
-287
lines changed

backend/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "kleinkram-backend",
3-
"version": "0.43.5",
3+
"version": "0.43.6",
44
"description": "",
55
"author": "",
66
"private": true,
@@ -84,7 +84,7 @@
8484
"@types/express": "^5.0.0",
8585
"@types/jest": "^29.5.2",
8686
"@types/node": "^22.13.10",
87-
"@types/supertest": "^6.0.0",
87+
"@types/supertest": "^6.0.3",
8888
"@typescript-eslint/eslint-plugin": "^8.28.0",
8989
"@typescript-eslint/parser": "^8.25.0",
9090
"eslint": "^9.22.0",
@@ -98,6 +98,6 @@
9898
"ts-loader": "^9.4.3",
9999
"ts-node": "^10.9.1",
100100
"tsconfig-paths": "^4.2.0",
101-
"typescript": "5.7.3"
101+
"typescript": "5.8.2"
102102
}
103103
}

backend/src/endpoints/action/action.controller.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,6 @@ export class ActionController {
134134
@Query() dto: ActionQuery,
135135
@AddUser() auth: AuthHeader,
136136
// TODO: bring back filter options
137-
/* @QuerySortBy('sortBy') sortBy: string,
138-
@QuerySortDirection('sortDirection') sortDirection: 'ASC' | 'DESC',
139-
@QueryOptionalString(
140-
'search',
141-
'Searchkey in name, state_cause or image_name',
142-
)
143-
search: string,*/
144137
): Promise<ActionsDto> {
145138
let missionUuid = dto.mission_uuid;
146139
if (auth.apikey) {
@@ -152,14 +145,9 @@ export class ActionController {
152145
auth.user.uuid,
153146
Number.parseInt((dto.skip ?? 0).toString()),
154147
Number.parseInt((dto.take ?? 0).toString()),
155-
'updatedAt',
156-
'ASC',
157-
'',
158-
/*
159-
sortBy,
160-
sortDirection,
161-
search,
162-
*/
148+
dto.sortBy ?? '',
149+
(dto.sortDirection as 'ASC' | 'DESC') ?? 'DESC',
150+
dto.search ?? '',
163151
);
164152
}
165153

backend/src/endpoints/project/project.controller.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import { ApiOkResponse, ApiResponse, OutputDto } from '../../decarators';
3333
import { DefaultRights } from '@common/api/types/access-control/default-rights';
3434
import { ResentProjectsDto } from '@common/api/types/project/recent-projects.dto';
3535
import { ProjectsDto } from '@common/api/types/project/projects.dto';
36-
import { ProjectWithMissionsDto } from '@common/api/types/project/project-with-missions.dto';
3736
import { AddUser, AuthHeader } from '../auth/parameter-decorator';
3837
import { DeleteProjectResponseDto } from '@common/api/types/project/delete-project-response.dto';
3938
import { UpdateTagTypesDto } from '@common/api/types/update-tag-types.dto';
@@ -44,6 +43,8 @@ import {
4443
ProjectAccessListDto,
4544
} from '@common/api/types/access-control/project-access.dto';
4645
import { AccessService } from '../../services/access.service';
46+
import { ProjectWithRequiredTags } from '@common/api/types/project/project-with-required-tags';
47+
import { ProjectDto } from '@common/api/types/project/base-project.dto';
4748

4849
@Controller(['project', 'projects']) // TODO: over time we will migrate to 'projects'
4950
export class ProjectController {
@@ -56,12 +57,12 @@ export class ProjectController {
5657
@CanCreate()
5758
@ApiOkResponse({
5859
description: 'Returns the updated project',
59-
type: ProjectWithMissionsDto,
60+
type: ProjectDto,
6061
})
6162
async createProject(
6263
@Body() dto: CreateProject,
6364
@AddUser() user: AuthHeader,
64-
): Promise<ProjectWithMissionsDto> {
65+
): Promise<ProjectDto> {
6566
return this.projectService.create(dto, user);
6667
}
6768

@@ -70,24 +71,24 @@ export class ProjectController {
7071
@CanReadProject()
7172
@ApiOkResponse({
7273
description: 'Returns the project',
73-
type: ProjectWithMissionsDto,
74+
type: ProjectWithRequiredTags,
7475
})
7576
async getProjectById(
7677
@ParameterUID('uuid') uuid: string,
77-
): Promise<ProjectWithMissionsDto> {
78+
): Promise<ProjectWithRequiredTags> {
7879
return this.projectService.findOne(uuid);
7980
}
8081

8182
@Put(':uuid')
8283
@CanWriteProject()
8384
@ApiOkResponse({
8485
description: 'Returns the updated project',
85-
type: ProjectWithMissionsDto,
86+
type: ProjectDto,
8687
})
8788
async updateProject(
8889
@ParameterUID('uuid') uuid: string,
8990
@Body() dto: CreateProject,
90-
): Promise<ProjectWithMissionsDto> {
91+
): Promise<ProjectDto> {
9192
return this.projectService.update(uuid, dto);
9293
}
9394

backend/src/routing/filters/global-error-filter.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {
22
ArgumentsHost,
33
BadRequestException,
4+
ForbiddenException,
5+
UnauthorizedException,
46
Catch,
57
ExceptionFilter,
68
} from '@nestjs/common';
@@ -23,8 +25,36 @@ export class GlobalErrorFilter implements ExceptionFilter {
2325
response.header('kleinkram-version', appVersion);
2426
response.header('Access-Control-Expose-Headers', 'kleinkram-version');
2527

28+
//////////////////////////////
29+
// Errors that don't get logged
30+
//////////////////////////////
31+
32+
if (exception.name === 'InvalidJwtTokenException') {
33+
response.status(401).json({
34+
statusCode: 401,
35+
message: 'Invalid JWT token. Are you logged in?',
36+
});
37+
return;
38+
}
39+
40+
if (
41+
exception instanceof ForbiddenException ||
42+
exception instanceof UnauthorizedException
43+
) {
44+
response.status(exception.getStatus()).json({
45+
statusCode: exception.getStatus(),
46+
message: exception.message,
47+
});
48+
return;
49+
}
50+
51+
//////////////////////////////
52+
// Errors that get logged
53+
//////////////////////////////
54+
55+
const request: Request = host.switchToHttp().getRequest();
2656
logger.error(
27-
`GlobalErrorFilter: ${exception.name} on kleinkram-version ${appVersion}`,
57+
`GlobalErrorFilter: ${exception.name} on kleinkram-version ${appVersion} on endpoint ${request.url} with method ${request.method}`,
2858
);
2959
logger.error(exception);
3060
logger.error(exception.stack);
@@ -54,14 +84,6 @@ export class GlobalErrorFilter implements ExceptionFilter {
5484
return;
5585
}
5686

57-
if (exception.name === 'InvalidJwtTokenException') {
58-
response.status(401).json({
59-
statusCode: 401,
60-
message: 'Invalid JWT token. Are you logged in?',
61-
});
62-
return;
63-
}
64-
6587
if (exception.name === 'PayloadTooLargeError') {
6688
response.status(413).json({
6789
statusCode: 413,

backend/src/serialization.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,24 @@ export const projectEntityToDtoWithMissionCount = (
260260
};
261261
};
262262

263+
export const projectEntityToDtoWithRequiredTags = (
264+
project: Project,
265+
missionCount: number,
266+
): ProjectWithMissionsDto => {
267+
if (project.creator === undefined) {
268+
throw new Error('Creator can never be undefined');
269+
}
270+
271+
return {
272+
...(projectEntityToDto(project) as ProjectWithMissionsDto),
273+
creator: userEntityToDto(project.creator),
274+
missionCount: missionCount,
275+
requiredTags: project.requiredTags.map((element) =>
276+
tagTypeEntityToDto(element),
277+
),
278+
};
279+
};
280+
263281
export const projectEntityToDtoWithMissions = (
264282
project: Project,
265283
): ProjectWithMissionsDto => {

backend/src/services/project.service.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from '@common/frontend_shared/enum';
1717
import {
1818
projectEntityToDtoWithMissionCount,
19-
projectEntityToDtoWithMissions,
19+
projectEntityToDtoWithRequiredTags,
2020
} from '../serialization';
2121
import TagType from '@common/entities/tagType/tag-type.entity';
2222
import ProjectAccess from '@common/entities/auth/project-access.entity';
@@ -27,9 +27,10 @@ import { DefaultRights } from '@common/api/types/access-control/default-rights';
2727
import { ResentProjectDto } from '@common/api/types/project/recent-projects.dto';
2828
import { DefaultRightDto } from '@common/api/types/access-control/default-right.dto';
2929
import { ProjectsDto } from '@common/api/types/project/projects.dto';
30-
import { ProjectWithMissionsDto } from '@common/api/types/project/project-with-missions.dto';
3130
import { AuthHeader } from '../endpoints/auth/parameter-decorator';
3231
import { SortOrder } from '@common/api/types/pagination';
32+
import { ProjectWithRequiredTags } from '@common/api/types/project/project-with-required-tags';
33+
import { ProjectDto } from '@common/api/types/project/base-project.dto';
3334

3435
const FIND_MANY_SORT_KEYS = {
3536
projectName: 'project.name',
@@ -188,23 +189,29 @@ export class ProjectService {
188189
};
189190
}
190191

191-
async findOne(uuid: string): Promise<ProjectWithMissionsDto> {
192-
const mission = await this.projectRepository
192+
async findOne(uuid: string): Promise<ProjectWithRequiredTags> {
193+
const missionPromise = this.projectRepository
193194
.createQueryBuilder('project')
194195
.where('project.uuid = :uuid', { uuid })
195196
.leftJoinAndSelect('project.creator', 'creator')
196-
.leftJoinAndSelect('project.missions', 'missions')
197-
// TODO: remove the following two joins, there are never used in the frontend...
198-
.leftJoinAndSelect('missions.creator', 'mission_creator')
199-
.leftJoinAndSelect('missions.project', 'mission_project')
200197
.leftJoinAndSelect('project.requiredTags', 'requiredTags')
201198
.leftJoinAndSelect('project.project_accesses', 'project_accesses')
202199
.leftJoinAndSelect('project_accesses.accessGroup', 'accessGroup')
203200
.leftJoinAndSelect('accessGroup.memberships', 'memberships')
204201
.leftJoinAndSelect('memberships.user', 'user')
205202
.getOneOrFail();
206203

207-
return projectEntityToDtoWithMissions(mission);
204+
const missionCountPromise = this.projectRepository
205+
.createQueryBuilder('project')
206+
.leftJoin('project.missions', 'missions')
207+
.where('project.uuid = :uuid', { uuid })
208+
.getCount();
209+
210+
const [mission, missionCount] = await Promise.all([
211+
missionPromise,
212+
missionCountPromise,
213+
]);
214+
return projectEntityToDtoWithRequiredTags(mission, missionCount);
208215
}
209216

210217
async getRecentProjects(
@@ -339,7 +346,7 @@ export class ProjectService {
339346
async create(
340347
project: CreateProject,
341348
auth: AuthHeader,
342-
): Promise<ProjectWithMissionsDto> {
349+
): Promise<ProjectDto> {
343350
const exists = await this.projectRepository.exists({
344351
where: { name: ILike(project.name) },
345352
});
@@ -424,13 +431,10 @@ export class ProjectService {
424431
);
425432
return (await this.projectRepository.findOneOrFail({
426433
where: { uuid: transactedProject.uuid },
427-
})) as unknown as ProjectWithMissionsDto;
434+
})) as unknown as ProjectDto;
428435
}
429436

430-
async update(
431-
uuid: string,
432-
project: CreateProject,
433-
): Promise<ProjectWithMissionsDto> {
437+
async update(uuid: string, project: CreateProject): Promise<ProjectDto> {
434438
const exists = await this.projectRepository.exists({
435439
where: { name: ILike(project.name), uuid: Not(uuid) },
436440
});
@@ -449,7 +453,7 @@ export class ProjectService {
449453
});
450454
return (await this.projectRepository.findOneOrFail({
451455
where: { uuid },
452-
})) as unknown as ProjectWithMissionsDto;
456+
})) as unknown as ProjectDto;
453457
}
454458

455459
async addTagType(uuid: string, tagTypeUUID: string): Promise<void> {

backend/yarn.lock

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3114,10 +3114,10 @@
31143114
"@types/node" "*"
31153115
form-data "^4.0.0"
31163116

3117-
"@types/supertest@^6.0.0":
3118-
version "6.0.2"
3119-
resolved "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.2.tgz"
3120-
integrity sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==
3117+
"@types/supertest@^6.0.3":
3118+
version "6.0.3"
3119+
resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-6.0.3.tgz#d736f0e994b195b63e1c93e80271a2faf927388c"
3120+
integrity sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==
31213121
dependencies:
31223122
"@types/methods" "^1.1.4"
31233123
"@types/superagent" "^8.1.0"
@@ -7949,6 +7949,11 @@ [email protected]:
79497949
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e"
79507950
integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==
79517951

7952+
7953+
version "5.8.2"
7954+
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4"
7955+
integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==
7956+
79527957
79537958
version "0.0.4"
79547959
resolved "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz"

cli/kleinkram/api/file_transfer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,10 @@ def download_file(
309309
if path.exists():
310310
local_hash = b64_md5(path)
311311
if local_hash != file.hash and not overwrite and file.hash is not None:
312-
return DownloadState.SKIPPED_INVALID_HASH
312+
return DownloadState.SKIPPED_INVALID_HASH, 0
313313

314314
elif local_hash == file.hash:
315-
return DownloadState.SKIPPED_OK
315+
return DownloadState.SKIPPED_OK, 0
316316

317317
# this has to be here
318318
if verbose:

0 commit comments

Comments
 (0)