Skip to content

Commit af21ec0

Browse files
committed
tests: add tests for new routes
1 parent 30f3ac3 commit af21ec0

34 files changed

+504
-89
lines changed

prisma/schema.prisma

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,10 @@ model ImageMedia {
171171
height Int @db.UnsignedInt
172172
uploadedAt DateTime @default(now())
173173
isPublic Boolean @default(false)
174-
uploaderId String
174+
uploaderId String?
175175
preset ImageMediaPreset
176176
177-
uploader User @relation(fields: [uploaderId], references: [id])
177+
uploader User? @relation(fields: [uploaderId], references: [id])
178178
avatarForUsers UserInfos[]
179179
logoForAssos Asso[] @relation("logo")
180180
descriptionForAssos Asso[] @relation("descriptionImages")

src/app.module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { ImageMediaModule } from './media/image/imagemedia.module';
2222
ConfigModule,
2323
HttpModule,
2424
PrismaModule,
25+
ImageMediaModule,
2526
SemesterModule,
2627
AuthModule,
2728
ProfileModule,
@@ -30,7 +31,6 @@ import { ImageMediaModule } from './media/image/imagemedia.module';
3031
TimetableModule,
3132
BranchModule,
3233
AssosModule,
33-
ImageMediaModule,
3434
],
3535
// The providers below are used for all the routes of the api.
3636
// For example, the JwtGuard is used for all the routes and checks whether the user is authenticated.

src/assos/assos.controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ export class AssosController {
8181
if (!(await this.assosService.hasSomeAssoPermission(asso, user.id, 'manage_infos')))
8282
throw new AppException(ERROR_CODE.FORBIDDEN_ASSOS_PERMISSIONS, asso.id, 'manage_infos');
8383
for (const key in body.description)
84-
if (!isValidLexicalContent(body.description[key]))
84+
if (body.description[key] && !isValidLexicalContent(body.description[key]))
8585
throw new AppException(ERROR_CODE.PARAM_LEXICAL_ILLEGAL, `description.${key}`);
8686
for (const key in body.descriptionShort)
87-
if (!isValidLexicalContent(body.descriptionShort[key]))
87+
if (body.descriptionShort[key] && !isValidLexicalContent(body.descriptionShort[key]))
8888
throw new AppException(ERROR_CODE.PARAM_LEXICAL_ILLEGAL, `descriptionShort.${key}`);
8989
if (body.logo) {
9090
const media = await this.mediaService.getMedia(body.logo);

src/assos/assos.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Module } from '@nestjs/common';
22
import { AssosController } from './assos.controller';
33
import { AssosService } from './assos.service';
4+
import { ImageMediaModule } from '../media/image/imagemedia.module';
45
import UsersService from '../users/users.service';
56

67
/**
@@ -10,5 +11,6 @@ import UsersService from '../users/users.service';
1011
@Module({
1112
controllers: [AssosController],
1213
providers: [AssosService, UsersService],
14+
imports: [ImageMediaModule],
1315
})
1416
export class AssosModule {}

src/assos/assos.service.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export class AssosService {
9797
});
9898
if (update.description || update.descriptionShort) {
9999
// Cleanup unused images
100-
const regex = /"src": "https:\/\/[^"]+\/media\/image\/([^/]+)\.webp"/g;
100+
const regex = /"src":"https:\/\/[^"]+\/media\/image\/([^/]+)\.webp"/g;
101101
const imagesInUse = new Set<string>();
102102
for (const field in updated.descriptionTranslation)
103103
for (const match of (<string>updated.descriptionTranslation[field]).matchAll(regex)) imagesInUse.add(match[1]);
@@ -112,18 +112,25 @@ export class AssosService {
112112
})
113113
).map((m) => m.id),
114114
);
115-
const deletions = currentImages.difference(imagesInUse);
116-
const additions = imagesInUse.difference(currentImages);
117-
if (deletions.size > 0 || additions.size > 0)
118-
// don't wait for this to complete
119-
this.prisma.$transaction([
115+
const deletions = [...currentImages].filter((x) => !imagesInUse.has(x));
116+
const additions = [...imagesInUse].filter((x) => !currentImages.has(x));
117+
const existingAdditionIds = new Set(
118+
(
119+
await this.prisma.imageMedia.findMany({
120+
where: { id: { in: additions } },
121+
select: { id: true },
122+
})
123+
).map((m) => m.id),
124+
);
125+
if (deletions.length > 0 || additions.length > 0)
126+
await this.prisma.$transaction([
120127
...Array.from(deletions).map((id) =>
121128
this.prisma.imageMedia.update({
122129
where: { id },
123130
data: { descriptionForAssos: { disconnect: { id: assoId } } },
124131
}),
125132
),
126-
...Array.from(additions).map((id) =>
133+
...Array.from(additions.filter((x) => existingAdditionIds.has(x))).map((id) =>
127134
this.prisma.imageMedia.update({
128135
where: { id },
129136
data: { descriptionForAssos: { connect: { id: assoId } } },

src/assos/dto/req/assos-update-req.dto.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
IsUUID,
1010
ValidateNested,
1111
} from 'class-validator';
12-
import { TranslatedTextDto } from 'src/utils';
12+
import { TranslatedTextDto } from '../../../utils';
1313

1414
export default class AssosUpdateReqDto {
1515
@IsOptional()

src/media/image/dto/req/imagemedia-upload-req.dto.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,41 @@
11
import { ImageMediaPreset } from '@prisma/client';
2+
import { Type } from 'class-transformer';
23
import { IsBoolean, IsEnum, IsInt, IsOptional, IsString, Max, Min } from 'class-validator';
34

45
export default class ImageMediaUploadReqDto {
56
@IsOptional()
67
@IsInt()
78
@Min(100)
89
@Max(1920)
10+
@Type(() => Number)
911
width?: number;
1012

1113
@IsOptional()
1214
@IsInt()
1315
@Min(100)
1416
@Max(1080)
17+
@Type(() => Number)
1518
height?: number;
1619

1720
@IsOptional()
1821
@IsInt()
1922
@Min(1)
2023
@Max(100)
24+
@Type(() => Number)
2125
quality?: number;
2226

2327
@IsOptional()
2428
@IsInt()
2529
@Min(0)
2630
@Max(6)
31+
@Type(() => Number)
2732
effort?: number;
2833

2934
@IsOptional()
3035
@IsInt()
3136
@Min(0)
3237
@Max(3)
38+
@Type(() => Number)
3339
rotation?: 0 | 1 | 2 | 3;
3440

3541
@IsOptional()
@@ -40,5 +46,6 @@ export default class ImageMediaUploadReqDto {
4046
/** By default, images are NOT public and only accessible to logged users. */
4147
@IsOptional()
4248
@IsBoolean()
49+
@Type(() => Boolean)
4350
public?: boolean;
4451
}

src/media/image/imagemedia.controller.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ export class ImageMediaController {
2828
const media = await this.imageMediaService.getMedia(mediaId);
2929
if (!media) throw new AppException(ERROR_CODE.NO_SUCH_MEDIA, mediaId);
3030
if (!media.isPublic && !user) throw new AppException(ERROR_CODE.NOT_LOGGED_IN);
31-
try {
32-
const stream = this.imageMediaService.readMediaFromDisk(mediaId);
33-
response.setHeader('Content-Type', 'image/webp');
34-
stream.pipe(response);
35-
} catch {
36-
throw new AppException(ERROR_CODE.SERVER_DISK_ERROR);
37-
}
31+
const stream = this.imageMediaService.readMediaFromDisk(mediaId);
32+
response.setHeader('Content-Type', 'image/webp');
33+
stream.pipe(response);
34+
stream.on('error', () => {
35+
stream.close();
36+
const exception = new AppException(ERROR_CODE.SERVER_DISK_ERROR);
37+
response.status(exception.getStatus()).json(exception.getResponse());
38+
});
3839
}
3940

4041
@Post('/')
@@ -55,9 +56,9 @@ export class ImageMediaController {
5556
const media = await this.imageMediaService.convertMedia(multer, options);
5657
const savedMedia = await this.imageMediaService.registerMedia(media, user, options.public ?? false);
5758
try {
58-
this.imageMediaService.writeMediaToDisk(savedMedia.id, multer.multer.buffer);
59+
await this.imageMediaService.writeMediaToDisk(savedMedia.id, multer.multer.buffer);
5960
} catch {
60-
this.imageMediaService.unRegisterMedia(savedMedia.id);
61+
await this.imageMediaService.unRegisterMedia(savedMedia.id);
6162
throw new AppException(ERROR_CODE.SERVER_DISK_ERROR);
6263
}
6364
this.imageMediaService.cleanup();

src/media/image/imagemedia.module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ import { ImageMediaService } from './imagemedia.service';
55
@Module({
66
controllers: [ImageMediaController],
77
providers: [ImageMediaService],
8+
exports: [ImageMediaService],
89
})
910
export class ImageMediaModule {}

src/media/image/imagemedia.service.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export class ImageMediaService {
3232
if (!(options.preset in presets)) options.preset = ImageMediaPreset.CUSTOM;
3333
if (options.preset) Object.assign(options, presets[options.preset]);
3434
let instructions = sharp(file.multer.buffer);
35-
const metadata = await instructions.metadata();
35+
let metadata = await instructions.metadata();
3636
const size = [metadata.width, metadata.height];
3737
if (options.rotation) {
3838
instructions = instructions.rotate(options.rotation * 90);
@@ -43,16 +43,16 @@ export class ImageMediaService {
4343
if (options.width && options.height)
4444
instructions = instructions.resize(options.width, options.height, { fit: 'cover' });
4545
file.mime = 'image/webp';
46-
file.multer.buffer = await instructions
47-
.webp({
48-
quality: options.quality,
49-
effort: options.effort,
50-
nearLossless: true,
51-
smartSubsample: true,
52-
alphaQuality: options.quality,
53-
})
54-
.toBuffer();
55-
return { width: size[0], height: size[1], size: file.multer.buffer.length, preset: options.preset };
46+
instructions = instructions.webp({
47+
quality: options.quality,
48+
effort: options.effort,
49+
nearLossless: true,
50+
smartSubsample: true,
51+
alphaQuality: options.quality,
52+
});
53+
file.multer.buffer = await instructions.toBuffer();
54+
metadata = await sharp(file.multer.buffer).metadata();
55+
return { width: metadata.width, height: metadata.height, size: file.multer.buffer.length, preset: options.preset };
5656
}
5757

5858
async registerMedia(metaData: ImageMetadata, uploader: User, isPublic: boolean): Promise<ImageMedia> {

0 commit comments

Comments
 (0)