Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/add missing actions #123

Merged
merged 4 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { type UpdateResourcePathParams } from '@common/contracts';

import {
deleteResourceResponseBodyDTOSchema,
type DeleteResourceResponseBodyDTO,
Expand Down Expand Up @@ -35,9 +37,15 @@ import {
} from './schemas/findUserBucketsSchema.js';
import { type ResourceMetadataDTO } from './schemas/resourceMetadataDTO.js';
import {
type UploadResourcesResponseBodyDTO,
type UpdateResourceBodyDTO,
updateResourceBodyDTOSchema,
updateResourcePathParamsDTOSchema,
updateResourcesResponseBodyDTOSchema,
} from './schemas/updateResourceSchema.js';
import {
type UploadResourceResponseBodyDTO,
type UploadResourcesPathParamsDTO,
uploadResourcesResponseBodyDTOSchema,
uploadResourceResponseBodyDTOSchema,
uploadResourcesPathParamsDTOSchema,
} from './schemas/uploadResourceSchema.js';
import { OperationNotValidError } from '../../../../../common/errors/common/operationNotValidError.js';
Expand All @@ -56,6 +64,7 @@ import { SecurityMode } from '../../../../../common/types/http/securityMode.js';
import { type AccessControlService } from '../../../../authModule/application/services/accessControlService/accessControlService.js';
import { type FindUserBucketsQueryHandler } from '../../../../userModule/application/queryHandlers/findUserBucketsQueryHandler/findUserBucketsQueryHandler.js';
import { type DeleteResourceCommandHandler } from '../../../application/commandHandlers/deleteResourceCommandHandler/deleteResourceCommandHandler.js';
import { type UpdateResourceCommandHandler } from '../../../application/commandHandlers/updateResourceCommandHandler/updateResourceCommandHandler.js';
import { type UploadResourcesCommandHandler } from '../../../application/commandHandlers/uploadResourcesCommandHandler/uploadResourcesCommandHandler.js';
import { type DownloadResourcesQueryHandler } from '../../../application/queryHandlers/downloadResourcesQueryHandler/downloadResourcesQueryHandler.js';
import { type DownloadVideoPreviewQueryHandler } from '../../../application/queryHandlers/downloadVideoPreviewQueryHandler/downloadVideoPreviewQueryHandler.js';
Expand All @@ -70,6 +79,7 @@ export class ResourceHttpController implements HttpController {
private readonly findResourcesMetadataQueryHandler: FindResourcesMetadataQueryHandler,
private readonly uploadResourceCommandHandler: UploadResourcesCommandHandler,
private readonly downloadResourcesQueryHandler: DownloadResourcesQueryHandler,
private readonly updateResourceCommandHandler: UpdateResourceCommandHandler,
private readonly downloadVideoPreviewQueryHandler: DownloadVideoPreviewQueryHandler,
private readonly findUserBucketsQueryHandler: FindUserBucketsQueryHandler,
private readonly accessControlService: AccessControlService,
Expand Down Expand Up @@ -125,7 +135,7 @@ export class ResourceHttpController implements HttpController {
},
response: {
[HttpStatusCode.created]: {
schema: uploadResourcesResponseBodyDTOSchema,
schema: uploadResourceResponseBodyDTOSchema,
description: 'Resources uploaded',
},
},
Expand Down Expand Up @@ -154,6 +164,26 @@ export class ResourceHttpController implements HttpController {
tags: ['Resource'],
description: "Export bucket's resources",
}),
new HttpRoute({
method: HttpMethodName.post,
path: ':bucketName/resource/:resourceId/rename',
description: 'Rename resource',
schema: {
request: {
body: updateResourceBodyDTOSchema,
pathParams: updateResourcePathParamsDTOSchema,
},
response: {
[HttpStatusCode.ok]: {
schema: updateResourcesResponseBodyDTOSchema,
description: 'Resource updated.',
},
},
},
handler: this.renameResource.bind(this),
tags: ['Resource'],
securityMode: SecurityMode.bearer,
}),
new HttpRoute({
method: HttpMethodName.get,
path: ':bucketName/resources/:resourceId/previews',
Expand Down Expand Up @@ -279,9 +309,34 @@ export class ResourceHttpController implements HttpController {
};
}

private async renameResource(
request: HttpRequest<UpdateResourceBodyDTO, undefined, UpdateResourcePathParams>,
): Promise<HttpOkResponse<UploadResourceResponseBodyDTO>> {
const { role, userId } = await this.accessControlService.verifyBearerToken({
authorizationHeader: request.headers['authorization'],
});

const { resourceName } = request.body;

const { bucketName, resourceId } = request.pathParams;

await this.updateResourceCommandHandler.execute({
bucketName,
resourceId,
resourceName,
userId,
userRole: role,
});

return {
statusCode: HttpStatusCode.ok,
body: null,
};
}

private async uploadResources(
request: HttpRequest<undefined, undefined, UploadResourcesPathParamsDTO>,
): Promise<HttpCreatedResponse<UploadResourcesResponseBodyDTO>> {
): Promise<HttpCreatedResponse<UploadResourceResponseBodyDTO>> {
const { userId } = await this.accessControlService.verifyBearerToken({
authorizationHeader: request.headers['authorization'],
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { type Static, Type } from '@sinclair/typebox';

import type * as contracts from '@common/contracts';

import { type TypeExtends } from '../../../../../../common/types/schemaExtends.js';

export const updateResourcePathParamsDTOSchema = Type.Object({
bucketName: Type.String(),
resourceId: Type.String({ format: 'uuid' }),
});

export type UpdateResourcePathParamsDTO = TypeExtends<
Static<typeof updateResourceBodyDTOSchema>,
contracts.UpdateResourcePathParams
>;

export const updateResourceBodyDTOSchema = Type.Object({
resourceName: Type.String(),
});

export type UpdateResourceBodyDTO = TypeExtends<
Static<typeof updateResourceBodyDTOSchema>,
contracts.UpdateResourceBody
>;

export const updateResourcesResponseBodyDTOSchema = Type.Null();

export type UpdateResourcesResponseBodyDTO = Static<typeof updateResourcesResponseBodyDTOSchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ export type UploadResourcesPathParamsDTO = TypeExtends<
contracts.UploadResourcesPathParams
>;

export const uploadResourcesResponseBodyDTOSchema = Type.Null();
export const uploadResourceResponseBodyDTOSchema = Type.Null();

export type UploadResourcesResponseBodyDTO = Static<typeof uploadResourcesResponseBodyDTOSchema>;
export type UploadResourceResponseBodyDTO = Static<typeof uploadResourceResponseBodyDTOSchema>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { type UserRole } from '@common/contracts';

import { type CommandHandler } from '../../../../../common/types/commandHandler.js';

export interface UpdateResourceCommandHandlerPayload {
readonly userId: string;
readonly userRole: UserRole;
readonly bucketName: string;
readonly resourceId: string;
readonly resourceName: string;
}

export type UpdateResourceCommandHandler = CommandHandler<UpdateResourceCommandHandlerPayload, void>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { UserRole } from '@common/contracts';

import {
type UpdateResourceCommandHandlerPayload,
type UpdateResourceCommandHandler,
} from './updateResourceCommandHandler.js';
import { type UuidService } from '../../../../../libs/uuid/services/uuidService/uuidService.js';
import { ForbiddenAccessError } from '../../../../authModule/application/errors/forbiddenAccessError.js';
import { type FindUserBucketsQueryHandler } from '../../../../userModule/application/queryHandlers/findUserBucketsQueryHandler/findUserBucketsQueryHandler.js';
import { type ResourceBlobService } from '../../../domain/services/resourceBlobService/resourceBlobService.js';

export class UpdateResourceCommandHandlerImpl implements UpdateResourceCommandHandler {
public constructor(
private readonly resourceBlobService: ResourceBlobService,
private readonly findUserBucketsQueryHandler: FindUserBucketsQueryHandler,
private readonly uuidService: UuidService,
) {}

public async execute(payload: UpdateResourceCommandHandlerPayload): Promise<void> {
const { resourceName, resourceId, bucketName, userId, userRole } = payload;

const { buckets } = await this.findUserBucketsQueryHandler.execute({ userId });

if (!buckets.some((bucket) => bucket.name === bucketName) && userRole === UserRole.user) {
throw new ForbiddenAccessError({
reason: 'User does not have access to this bucket.',
userId,
bucketName,
userBuckets: buckets,
});
}

const newResourceId = this.uuidService.generateUuid();

const previewsBucketName = bucketName + '-previews';

await this.resourceBlobService.updateResource({
...payload,
newResourceId,
});

await Promise.all([
this.resourceBlobService.updateResource({
bucketName,
resourceId,
resourceName,
newResourceId,
}),
this.resourceBlobService.updateResource({
bucketName: bucketName + '-previews',
resourceId,
resourceName: previewsBucketName,
newResourceId,
}),
]);

await Promise.all([
this.resourceBlobService.deleteResource({
bucketName,
resourceId,
}),
this.resourceBlobService.deleteResource({
bucketName: previewsBucketName,
resourceId,
}),
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { type ResourceBlobService } from '../../../domain/services/resourceBlobS

export class FindResourcesMetadataQueryHandlerImpl implements FindResourcesMetadataQueryHandler {
public constructor(
private readonly resourceBlobSerice: ResourceBlobService,
private readonly resourceBlobService: ResourceBlobService,
private readonly loggerService: LoggerService,
private readonly findUserBucketsQueryHandler: FindUserBucketsQueryHandler,
) {}
Expand All @@ -34,7 +34,7 @@ export class FindResourcesMetadataQueryHandlerImpl implements FindResourcesMetad
});
}

const bucketExists = await this.resourceBlobSerice.bucketExists({ bucketName });
const bucketExists = await this.resourceBlobService.bucketExists({ bucketName });

if (!bucketExists) {
throw new OperationNotValidError({
Expand All @@ -52,7 +52,7 @@ export class FindResourcesMetadataQueryHandlerImpl implements FindResourcesMetad
pageSize,
});

const { items: resourcesMetadata, totalPages } = await this.resourceBlobSerice.getResourcesMetadata({
const { items: resourcesMetadata, totalPages } = await this.resourceBlobService.getResourcesMetadata({
bucketName,
page,
pageSize,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ export interface UploadResourcePayload {
readonly contentType: string;
}

export interface RenameResourcePayload {
bucketName: string;
resourceName: string;
resourceId: string;
newResourceId: string;
}

export interface DownloadResourcePayload {
readonly resourceId: string;
readonly bucketName: string;
Expand Down Expand Up @@ -47,6 +54,7 @@ export interface DeleteResourcePayload {

export interface ResourceBlobService {
uploadResource(payload: UploadResourcePayload): Promise<void>;
updateResource(payload: RenameResourcePayload): Promise<void>;
downloadResource(payload: DownloadResourcePayload): Promise<Resource>;
getResourcesMetadata(payload: GetResourcesMetadataPayload): Promise<GetResourcesMetadataResult>;
getResourcesIds(payload: GetResourcesIdsPayload): Promise<string[]>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/naming-convention */

import {
CopyObjectCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadObjectCommand,
Expand All @@ -25,6 +26,7 @@ import {
type GetResourcesMetadataResult,
type GetResourcesIdsPayload,
type UploadResourcePayload,
type RenameResourcePayload,
} from '../../../domain/services/resourceBlobService/resourceBlobService.js';

export class ResourceBlobServiceImpl implements ResourceBlobService {
Expand All @@ -41,6 +43,7 @@ export class ResourceBlobServiceImpl implements ResourceBlobService {
Body: data,
Metadata: {
actualname: encodeURIComponent(resourceName),
resourceId,
},
ContentType: contentType,
ContentDisposition: `attachment; filename=${resourceName}`,
Expand All @@ -50,6 +53,23 @@ export class ResourceBlobServiceImpl implements ResourceBlobService {
await upload.done();
}

public async updateResource(payload: RenameResourcePayload): Promise<void> {
const { bucketName, resourceName, resourceId, newResourceId } = payload;

const changeObjectCommand = new CopyObjectCommand({
Bucket: bucketName,
CopySource: `/${bucketName}/${resourceId}`,
Key: newResourceId,
Metadata: {
actualname: encodeURIComponent(resourceName),
resourceId: newResourceId,
},
MetadataDirective: 'REPLACE',
});

await this.s3Client.send(changeObjectCommand);
}

public async downloadResource(payload: DownloadResourcePayload): Promise<Resource> {
const { resourceId, bucketName } = payload;

Expand Down
13 changes: 13 additions & 0 deletions apps/backend/src/modules/resourceModule/resourceModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { type DeleteBucketCommandHandler } from './application/commandHandlers/d
import { DeleteBucketCommandHandlerImpl } from './application/commandHandlers/deleteBucketCommandHandler/deleteBucketCommandHandlerImpl.js';
import { type DeleteResourceCommandHandler } from './application/commandHandlers/deleteResourceCommandHandler/deleteResourceCommandHandler.js';
import { DeleteResourceCommandHandlerImpl } from './application/commandHandlers/deleteResourceCommandHandler/deleteResourceCommandHandlerImpl.js';
import { type UpdateResourceCommandHandler } from './application/commandHandlers/updateResourceCommandHandler/updateResourceCommandHandler.js';
import { UpdateResourceCommandHandlerImpl } from './application/commandHandlers/updateResourceCommandHandler/updateResourceCommandHandlerImpl.js';
import { type UploadResourcesCommandHandler } from './application/commandHandlers/uploadResourcesCommandHandler/uploadResourcesCommandHandler.js';
import { UploadResourcesCommandHandlerImpl } from './application/commandHandlers/uploadResourcesCommandHandler/uploadResourcesCommandHandlerImpl.js';
import { type DownloadResourcesQueryHandler } from './application/queryHandlers/downloadResourcesQueryHandler/downloadResourcesQueryHandler.js';
Expand Down Expand Up @@ -114,6 +116,16 @@ export class ResourceModule implements DependencyInjectionModule {
),
);

container.bind<UpdateResourceCommandHandler>(
symbols.updateResourceCommandHandler,
() =>
new UpdateResourceCommandHandlerImpl(
container.get<ResourceBlobService>(symbols.resourceBlobService),
container.get<FindUserBucketsQueryHandler>(userSymbols.findUserBucketsQueryHandler),
container.get<UuidService>(coreSymbols.uuidService),
),
);

container.bind<ResourceHttpController>(
symbols.resourceHttpController,
() =>
Expand All @@ -122,6 +134,7 @@ export class ResourceModule implements DependencyInjectionModule {
container.get<FindResourcesMetadataQueryHandler>(symbols.findResourcesMetadataQueryHandler),
container.get<UploadResourcesCommandHandler>(symbols.uploadResourcesCommandHandler),
container.get<DownloadResourcesQueryHandler>(symbols.downloadResourcesQueryHandler),
container.get<UpdateResourceCommandHandler>(symbols.updateResourceCommandHandler),
container.get<DownloadVideoPreviewQueryHandler>(symbols.downloadVideoPreviewQueryHandler),
container.get<FindUserBucketsQueryHandler>(userSymbols.findUserBucketsQueryHandler),
container.get<AccessControlService>(authSymbols.accessControlService),
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/modules/resourceModule/symbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const symbols = {
findBucketsQueryHandler: Symbol('findBucketsQueryHandler'),
createBucketCommandHandler: Symbol('createBucketCommandHandler'),
deleteBucketCommandHandler: Symbol('deleteBucketCommandHandler'),
updateResourceCommandHandler: Symbol('updateResourceCommandHandler'),

resourceHttpController: Symbol('resourceHttpController'),
adminResourceHttpController: Symbol('adminResourceHttpController'),
Expand Down
10 changes: 10 additions & 0 deletions apps/frontend/src/modules/bucket/stores/bucketStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { create } from 'zustand';
import { BucketState } from './bucketStoreState';

export const useBucketStore = create<BucketState>((set) => ({
bucket: undefined,
removeBucket: (): void => {
set({});
},
setBucket: (bucket) => set({ bucket })
}));
7 changes: 7 additions & 0 deletions apps/frontend/src/modules/bucket/stores/bucketStoreState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Bucket } from '@common/contracts';

export interface BucketState {
bucket?: Bucket;
setBucket: (bucket: Bucket) => void;
removeBucket: () => void;
}
Loading
Loading