Skip to content

Commit e906171

Browse files
authored
Merge pull request #94 from Lazztech/shosh-file-module-updates
Shosh file module updates
2 parents 1ecc617 + 50cf09a commit e906171

27 files changed

Lines changed: 4331 additions & 2561 deletions

package-lock.json

Lines changed: 4030 additions & 2327 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
},
3030
"dependencies": {
3131
"@apollo/server": "^4.10.4",
32+
"@aws-sdk/client-s3": "^3.965.0",
3233
"@mikro-orm/better-sqlite": "^6.6.2",
3334
"@mikro-orm/core": "^6.6.2",
3435
"@mikro-orm/migrations": "^6.6.2",
@@ -46,11 +47,10 @@
4647
"@nestjs/platform-express": "^10.3.8",
4748
"@nestjs/schedule": "^4.0.2",
4849
"@nestjs/serve-static": "^4.0.2",
49-
"@ntegral/nestjs-sentry": "^4.0.0",
50+
"@ntegral/nestjs-sentry": "^4.0.0",
5051
"@sentry/node": "^7.55.2",
5152
"@sentry/tracing": "^7.55.2",
5253
"adorable-avatars": "^0.5.0",
53-
"aws-sdk": "^2.1397.0",
5454
"bcryptjs": "^2.4.3",
5555
"better-sqlite3": "^12.5.0",
5656
"class-transformer": "^0.5.1",
@@ -70,7 +70,7 @@
7070
"jpeg-js": "^0.4.4",
7171
"lodash": "^4.17.21",
7272
"nestjs-pino": "^3.3.0",
73-
"nestjs-s3": "^1.0.1",
73+
"nestjs-s3": "^3.0.1",
7474
"node-datetime": "^2.1.2",
7575
"nodemailer": "^6.9.3",
7676
"nodemailer-mailgun-transport": "^2.1.5",
@@ -105,7 +105,6 @@
105105
"@types/passport-local": "^1.0.35",
106106
"@types/sharp": "^0.31.1",
107107
"@types/supertest": "^2.0.12",
108-
"@types/uuid": "^9.0.2",
109108
"@types/web-push": "^3.6.3",
110109
"@typescript-eslint/eslint-plugin": "^5.59.11",
111110
"@typescript-eslint/parser": "^5.59.11",

src/auth/auth.service.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ import { JoinUserHub } from '../dal/entity/joinUserHub.entity';
1212
import { JwtModule } from '@nestjs/jwt';
1313
import { S3Module, S3ModuleOptions } from 'nestjs-s3';
1414
import { ImageFileService } from '../file/image-file/image-file.service';
15-
import { FILE_SERVICE } from '../file/file-service.token';
1615
import { getRepositoryToken } from '@mikro-orm/nestjs';
1716
import { EntityManager, EntityRepository } from '@mikro-orm/core';
1817
import { Block } from '../dal/entity/block.entity';
1918
import { File } from '../dal/entity/file.entity';
2019
import { JoinEventFile } from '../dal/entity/joinEventFile.entity';
2120
import { JoinHubFile } from '../dal/entity/joinHubFile.entity';
21+
import { FileService } from '../file/file-service.abstract';
2222

2323
describe('AuthService', () => {
2424
let service: AuthService;
@@ -55,7 +55,7 @@ describe('AuthService', () => {
5555
UserService,
5656
ImageFileService,
5757
{
58-
provide: FILE_SERVICE,
58+
provide: FileService,
5959
useClass: S3FileService,
6060
},
6161
{

src/dal/entity/shareableId.entity.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { BeforeCreate, Property } from '@mikro-orm/core';
22
import { Field, ObjectType } from '@nestjs/graphql';
3-
import { v4 as uuid } from 'uuid';
4-
3+
import { randomUUID } from 'crypto';
54
@ObjectType({ isAbstract: true })
65
export abstract class ShareableId {
76

@@ -12,7 +11,7 @@ export abstract class ShareableId {
1211
// Only fires is repostiory.create is used for before save
1312
@BeforeCreate()
1413
public addId(){
15-
this.shareableId = uuid();
14+
this.shareableId = randomUUID();
1615
}
1716

1817
@Property({ nullable: true })

src/event/event.resolver.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { Test, TestingModule } from '@nestjs/testing';
55
import { ImageFileService } from '../file/image-file/image-file.service';
66
import { Event } from '../dal/entity/event.entity';
77
import { JoinUserEvent } from '../dal/entity/joinUserEvent.entity';
8-
import { FILE_SERVICE } from '../file/file-service.token';
98
import { LocalFileService } from '../file/local-file/local-file.service';
109
import { EventResolver } from './event.resolver';
1110
import { EventService } from './event.service';
@@ -18,6 +17,7 @@ import { File } from '../dal/entity/file.entity';
1817
import { EventGeofenceService } from './event-geofence/event-geofence.service';
1918
import { JoinEventFile } from '../dal/entity/joinEventFile.entity';
2019
import { EntityManager } from '@mikro-orm/core';
20+
import { FileService } from '../file/file-service.abstract';
2121

2222
describe('EventResolver', () => {
2323
let resolver: EventResolver;
@@ -38,7 +38,7 @@ describe('EventResolver', () => {
3838
ImageFileService,
3939
NotificationService,
4040
{
41-
provide: FILE_SERVICE,
41+
provide: FileService,
4242
useClass: LocalFileService,
4343
},
4444
{

src/event/event.service.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { getRepositoryToken } from '@mikro-orm/nestjs';
22
import { EntityRepository } from '@mikro-orm/postgresql';
33
import { Test, TestingModule } from '@nestjs/testing';
4-
import { FILE_SERVICE } from '../file/file-service.token';
54
import { LocalFileService } from '../file/local-file/local-file.service';
65
import { Event } from '../dal/entity/event.entity';
76
import { JoinUserEvent } from '../dal/entity/joinUserEvent.entity';
@@ -16,6 +15,7 @@ import { UserDevice } from '../dal/entity/userDevice.entity';
1615
import { File } from '../dal/entity/file.entity';
1716
import { JoinEventFile } from '../dal/entity/joinEventFile.entity';
1817
import { EntityManager } from '@mikro-orm/core';
18+
import { FileService } from '../file/file-service.abstract';
1919

2020
describe('EventService', () => {
2121
let service: EventService;
@@ -34,7 +34,7 @@ describe('EventService', () => {
3434
ImageFileService,
3535
NotificationService,
3636
{
37-
provide: FILE_SERVICE,
37+
provide: FileService,
3838
useClass: LocalFileService,
3939
},
4040
{

src/event/event.service.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,21 @@ import { EntityManager, EntityRepository, QueryOrder } from '@mikro-orm/core';
22
import { InjectRepository } from '@mikro-orm/nestjs';
33
import { Inject, Injectable, Logger } from '@nestjs/common';
44
import { FileUpload } from 'src/file/interfaces/file-upload.interface';
5-
import { v4 as uuid } from 'uuid';
65
import { Event } from '../dal/entity/event.entity';
76
import { JoinUserEvent, RSVP } from '../dal/entity/joinUserEvent.entity';
87
import { User } from '../dal/entity/user.entity';
9-
import { FILE_SERVICE } from '../file/file-service.token';
10-
import { FileServiceInterface } from '../file/interfaces/file-service.interface';
118
import { NotificationService } from '../notification/notification.service';
129
import { JoinEventFile } from '../dal/entity/joinEventFile.entity';
10+
import { FileService } from '../file/file-service.abstract';
11+
import { randomUUID } from 'crypto';
1312

1413
@Injectable()
1514
export class EventService {
1615
private readonly logger = new Logger(EventService.name);
1716

1817
constructor(
19-
@Inject(FILE_SERVICE)
20-
private readonly fileService: FileServiceInterface,
18+
@Inject()
19+
private readonly fileService: FileService,
2120
@InjectRepository(JoinUserEvent)
2221
private readonly joinUserEventRepository: EntityRepository<JoinUserEvent>,
2322
@InjectRepository(Event)
@@ -182,7 +181,7 @@ export class EventService {
182181
if (createdBy?.id != userId) {
183182
throw new Error('Only the event creator can reset the shareable ID');
184183
}
185-
event.shareableId = uuid();
184+
event.shareableId = randomUUID();
186185
await this.em.persist(event).flush();
187186
return userEvent;
188187
}

src/file/controller/file.controller.spec.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,37 @@
11
import { ConfigService } from '@nestjs/config';
22
import { Test, TestingModule } from '@nestjs/testing';
3-
import { FILE_SERVICE } from '../file-service.token';
43
import { FileController } from './file.controller';
54
import { ImageFileService } from '../image-file/image-file.service';
65
import { LocalFileService } from '../local-file/local-file.service';
76
import { getRepositoryToken } from '@mikro-orm/nestjs';
87
import { File } from '../../dal/entity/file.entity';
98
import { EntityManager, EntityRepository } from '@mikro-orm/core';
9+
import { FileService } from '../file-service.abstract';
10+
import fs from 'fs';
11+
12+
jest.mock('fs');
1013

1114
describe('FileController', () => {
1215
let controller: FileController;
1316

1417
beforeEach(async () => {
18+
(fs.existsSync as jest.Mock).mockReturnValue(true);
19+
// eslint-disable-next-line @typescript-eslint/no-empty-function
20+
(fs.mkdirSync as jest.Mock).mockImplementation(() => {});
1521
const module: TestingModule = await Test.createTestingModule({
1622
controllers: [FileController],
1723
providers: [
1824
{
19-
provide: FILE_SERVICE,
25+
provide: FileService,
2026
useClass: LocalFileService,
2127
},
22-
ConfigService,
28+
{
29+
provide: ConfigService,
30+
useValue: {
31+
get: jest.fn(() => ''),
32+
getOrThrow: jest.fn(() => ''),
33+
},
34+
},
2335
ImageFileService,
2436
{
2537
provide: getRepositoryToken(File),

src/file/controller/file.controller.ts

Lines changed: 12 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,37 @@
11
import { Controller, Get, Header, Inject, Logger, Param, Res } from '@nestjs/common';
22
import { Response } from 'express';
3-
import { join } from 'path';
4-
import sharp from 'sharp';
5-
import { FILE_SERVICE } from '../file-service.token';
63
import { ImageFileService } from '../image-file/image-file.service';
7-
import { FileServiceInterface } from '../interfaces/file-service.interface';
4+
import { FileService } from '../file-service.abstract';
85

96
@Controller('file')
107
export class FileController {
118
private logger = new Logger(FileController.name);
129

1310
constructor(
14-
@Inject(FILE_SERVICE)
15-
private readonly fileService: FileServiceInterface,
11+
@Inject()
12+
private readonly fileService: FileService,
1613
private readonly imageService: ImageFileService,
1714
) {}
1815

1916
@Get(':fileName')
2017
@Header('Cache-Control', 'public, max-age=86400') // public for CDN, max-age= 24hrs in seconds
21-
get(@Param('fileName') fileName: string, @Res() response: Response) {
22-
this.fileService.get(fileName).on('error', (err) => {
18+
async get(@Param('fileName') fileName: string, @Res() response: Response) {
19+
(await this.fileService.get(fileName)).on('error', (err) => {
2320
this.logger.error(err);
2421
response.status(500).send(err);
2522
}).pipe(response);
2623
}
2724

28-
@Get('watermark/:shareableId')
25+
@Get('watermark/:shareableId')
2926
@Header('Cache-Control', 'public, max-age=86400') // public for CDN, max-age= 24hrs in seconds
3027
@Header('content-type', 'image/jpeg')
31-
async watermark(@Param('shareableId') shareableId: string, @Res() response: Response) {
32-
const watermark = await sharp(
33-
join(process.cwd(), 'public', 'assets', 'lazztech_icon.webp')
34-
).resize(150, 150)
35-
.extend({
36-
top: 0,
37-
bottom: 20,
38-
left: 20,
39-
right: 0,
40-
background: { r: 0, g: 0, b: 0, alpha: 0 }
41-
})
42-
.composite([
43-
{
44-
input: Buffer.from([0, 0, 0, 200]),
45-
raw: {
46-
width: 1,
47-
height: 1,
48-
channels: 4,
49-
},
50-
tile: true,
51-
blend: 'dest-in',
52-
}
53-
]).toBuffer();
28+
async watermark(
29+
@Param('shareableId') shareableId: string,
30+
@Res() response: Response,
31+
) {
5432
const fileStream = await this.fileService.getByShareableId(shareableId);
55-
fileStream.pipe(
56-
sharp()
57-
.jpeg()
58-
.resize(1080, 1080, { fit: sharp.fit.inside })
59-
.composite([
60-
{ input: watermark, gravity: 'southwest' },
61-
])
62-
).pipe(response).on('error', (err) => {
33+
const readable = await this.fileService.watermarkImage(fileStream);
34+
readable?.pipe(response).on('error', (err) => {
6335
this.logger.error(err);
6436
response.status(500).send(err);
6537
});

src/file/file-service.abstract.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Injectable, Logger } from '@nestjs/common';
2+
import { ConfigService } from '@nestjs/config';
3+
import { join } from 'path';
4+
import sharp from 'sharp';
5+
import { File } from 'src/dal/entity/file.entity';
6+
import Stream, { Readable } from 'stream';
7+
import { FileServiceInterface } from './interfaces/file-service.interface';
8+
import { FileUpload } from './interfaces/file-upload.interface';
9+
10+
@Injectable()
11+
export abstract class FileService implements FileServiceInterface {
12+
public logger = new Logger(FileService.name);
13+
14+
public watermark: Promise<Buffer<ArrayBufferLike>>;
15+
16+
constructor(readonly configService: ConfigService) {}
17+
18+
abstract storeImageFromFileUpload(
19+
upload: Promise<FileUpload> | FileUpload,
20+
userId: any,
21+
): Promise<File>;
22+
abstract delete(fileName: string): Promise<void>;
23+
abstract deleteById(fileId: any, userId: any): Promise<any>;
24+
abstract get(fileName: string): Promise<Readable>;
25+
abstract getByShareableId(shareableId: string): Promise<Readable>;
26+
27+
async getWatermark() {
28+
return sharp(
29+
join(
30+
process.cwd(),
31+
'public',
32+
'assets',
33+
this.configService.getOrThrow('ICON_NAME'),
34+
),
35+
)
36+
.resize(150, 150)
37+
.extend({
38+
top: 0,
39+
bottom: 20,
40+
left: 20,
41+
right: 0,
42+
background: { r: 0, g: 0, b: 0, alpha: 0 },
43+
})
44+
.composite([
45+
{
46+
input: Buffer.from([0, 0, 0, 200]),
47+
raw: {
48+
width: 1,
49+
height: 1,
50+
channels: 4,
51+
},
52+
tile: true,
53+
blend: 'dest-in',
54+
},
55+
])
56+
.toBuffer();
57+
}
58+
59+
async watermarkImage(
60+
fileStream: Stream.Readable | undefined,
61+
): Promise<Readable | undefined> {
62+
const watermark = await this.getWatermark();
63+
return fileStream?.pipe(
64+
sharp()
65+
.jpeg()
66+
.resize(1080, 1080, { fit: sharp.fit.inside })
67+
.composite([{ input: watermark, gravity: 'southwest' }]),
68+
);
69+
}
70+
}

0 commit comments

Comments
 (0)