Skip to content

Commit

Permalink
feat: delete previous cover after updating (#266)
Browse files Browse the repository at this point in the history
* feat: delete previous cover after updating

* reafctore: some changes

* rmoved log

* fix tests, refactro condition
  • Loading branch information
slaveeks authored Jul 3, 2024
1 parent 9433672 commit 17be90d
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 5 deletions.
4 changes: 2 additions & 2 deletions src/domain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,12 @@ export function init(repositories: Repositories, appConfig: AppConfig): DomainSe
repositories.userSessionRepository
);
const editorToolsService = new EditorToolsService(repositories.editorToolsRepository);
const fileUploaderService = new FileUploaderService(repositories.objectStorageRepository, repositories.fileRepository);

const sharedServices = {
editorTools: editorToolsService,
note: noteService,
fileUploader: fileUploaderService,
/**
* @todo find a way how to resolve circular dependency
*/
Expand All @@ -85,8 +87,6 @@ export function init(repositories: Repositories, appConfig: AppConfig): DomainSe
const noteSettingsService = new NoteSettingsService(repositories.noteSettingsRepository, repositories.teamRepository, sharedServices);
const aiService = new AIService(repositories.aiRepository);

const fileUploaderService = new FileUploaderService(repositories.objectStorageRepository, repositories.fileRepository);

return {
fileUploaderService,
noteService,
Expand Down
29 changes: 29 additions & 0 deletions src/domain/service/fileUploader.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type FileRepository from '@repository/file.repository.js';
import type ObjectRepository from '@repository/object.repository.js';
import { DomainError } from '@domain/entities/DomainError.js';
import mime from 'mime';
import { isEmpty } from '@infrastructure/utils/empty.js';

/**
* File data for upload
Expand Down Expand Up @@ -143,6 +144,34 @@ export default class FileUploaderService {
return fileData;
}

/**
* Delete file
* @param key - file key
*/
public async deleteFile(key: UploadedFile['key']): Promise<void> {
const fileData = await this.fileRepository.getByKey(key);

if (isEmpty(fileData)) {
throw new DomainError('File not found');
}

/**
* Define file type and bucket
*/
const fileType = this.defineFileType(fileData.location);
const bucket = this.defineBucketByFileType(fileType);

/**
* Delete file from object storage and database
*/
const isRemovedFromObjectStorage = await this.objectRepository.delete(key, bucket);
const isRemovedFromDatabase = await this.fileRepository.deleteByKey(key);

if (isRemovedFromObjectStorage === false || isRemovedFromDatabase === false) {
throw new DomainError('Cannot delete file');
}
}

/**
* Define file type by location
* @param location - file location
Expand Down
8 changes: 8 additions & 0 deletions src/domain/service/noteSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type User from '@domain/entities/user.js';
import { createInvitationHash } from '@infrastructure/utils/invitationHash.js';
import { DomainError } from '@domain/entities/DomainError.js';
import type { SharedDomainMethods } from './shared/index.js';
import { notEmpty } from '@infrastructure/utils/empty.js';

/**
* Service responsible for Note Settings
Expand Down Expand Up @@ -107,6 +108,13 @@ export default class NoteSettingsService {
throw new DomainError(`Note settings not found`);
}

/**
* In this case we need to remove previous cover
*/
if (notEmpty(data.cover) && notEmpty(noteSettings.cover)) {
await this.shared.fileUploader.deleteFile(noteSettings.cover);
}

return await this.noteSettingsRepository.patchNoteSettingsById(noteSettings.id, data);
}

Expand Down
13 changes: 13 additions & 0 deletions src/domain/service/shared/fileUploader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type UploadedFile from '@domain/entities/file.js';

/**
* Which methods of Domain can be used by other domains
* Uses to decouple domains from each other
*/
export default interface FileUploaderServiceSharedMethods {
/**
* Delete file
* @param key - file key
*/
deleteFile: (key: UploadedFile['key']) => Promise<void>;
}
3 changes: 3 additions & 0 deletions src/domain/service/shared/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type EditorToolsServiceSharedMethods from './editorTools.js';
import type FileUploaderServiceSharedMethods from './fileUploader.js';
import type NoteServiceSharedMethods from './note.js';

export type SharedDomainMethods = {
editorTools: EditorToolsServiceSharedMethods;

note: NoteServiceSharedMethods;

fileUploader: FileUploaderServiceSharedMethods;
};
1 change: 0 additions & 1 deletion src/presentation/http/router/noteSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,6 @@ describe('NoteSettings API', () => {
await global.db.insertNoteSetting({
noteId: note.id,
isPublic: true,
cover: 'image.png',
});

/** Create test team if user is in team */
Expand Down
9 changes: 9 additions & 0 deletions src/repository/file.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,13 @@ export default class FileRepository {
public async getFileLocationByKey<T extends FileType>(type: T, key: UploadedFile['key']): Promise<FileLocationByType[T] | null> {
return await this.storage.getFileLocationByKey(type, key);
};

/**
* Delete file by key
* @param key - file unique key
* @returns true if file deleted
*/
public async deleteByKey(key: UploadedFile['key']): Promise<boolean> {
return await this.storage.delete(key);
}
}
9 changes: 9 additions & 0 deletions src/repository/object.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,13 @@ export default class ObjectStorageRepository {
public async insert(objectData: Buffer, key: string, bucket: string): Promise<string | null> {
return await this.storage.uploadFile(bucket, key, objectData);
}

/**
* Delete object
* @param key - object key
* @param bucket - bucket name
*/
public async delete(key: string, bucket: string): Promise<boolean> {
return await this.storage.removeFile(bucket, key);
}
}
18 changes: 18 additions & 0 deletions src/repository/storage/postgres/orm/sequelize/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,22 @@ export default class FileSequelizeStorage {

return res.location as FileLocationByType[T];
}

/**
* Delete file
* @param key - file key
* @returns true if file deleted
*/
public async delete(key: UploadedFile['key']): Promise<boolean> {
const affectedRows = await this.model.destroy({
where: {
key,
},
});

/**
* If file not found return false
*/
return affectedRows > 0;
}
}
23 changes: 22 additions & 1 deletion src/repository/storage/s3/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getLogger } from '@infrastructure/logging/index.js';
import { S3Client, GetObjectCommand, PutObjectCommand, CreateBucketCommand } from '@aws-sdk/client-s3';
import { S3Client, GetObjectCommand, PutObjectCommand, CreateBucketCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
import { Buffer } from 'buffer';
import { Readable } from 'stream';
import { streamToBuffer } from '@infrastructure/utils/streamToBuffer.js';
Expand Down Expand Up @@ -88,6 +88,27 @@ export class S3Storage {
}
}

/**
* Remove file from bucket
* @param bucket - bucket name
* @param key - file key
* @returns true if object was deleted
*/
public async removeFile(bucket: string, key: string): Promise<boolean> {
try {
await this.s3.send(new DeleteObjectCommand({
Bucket: bucket,
Key: key,
}))

return true;
} catch (error) {
s3StorageLogger.error(error)

return false;
}
}

/**
* Method to create bucket in object storage, return its location
* @param name - bucket name
Expand Down
2 changes: 1 addition & 1 deletion src/tests/utils/database-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ export default class DatabaseHelpers {
public async insertNoteSetting(noteSettings: NoteSettingsMockCreationAttributes): Promise<NoteSettingsMockCreationAttributes> {
const customHostname = noteSettings.customHostname ?? null;
const invitationHash = noteSettings.invitationHash ?? createInvitationHash();
const cover = noteSettings.cover ?? null;
const cover = noteSettings.cover ?? '';

noteSettings.invitationHash = invitationHash;

Expand Down

0 comments on commit 17be90d

Please sign in to comment.