diff --git a/server/db.sqlite3 b/server/db.sqlite3 index 998c3ea8..0d958f37 100644 Binary files a/server/db.sqlite3 and b/server/db.sqlite3 differ diff --git a/server/src/application/comment/create/create-comment.request.ts b/server/src/application/comment/create/create-comment.request.ts index a3431c04..11da123b 100644 --- a/server/src/application/comment/create/create-comment.request.ts +++ b/server/src/application/comment/create/create-comment.request.ts @@ -1,19 +1,12 @@ - import { UseCaseRequest } from '@application/shared'; import { TriggeredBy } from '@domain/shared/entities/triggered-by'; -// import { InvalidParameterException } from '@domain/shared/exceptions'; class CreateCommentRequest extends UseCaseRequest { - readonly postId: string; readonly content: string; // Constructor Section - constructor( - triggeredBy: TriggeredBy, - postId: string, - content: string, - ) { + constructor(triggeredBy: TriggeredBy, postId: string, content: string) { super(triggeredBy); this.postId = postId; this.content = content; @@ -24,15 +17,13 @@ class CreateCommentRequest extends UseCaseRequest { postId: string, content: string, ): CreateCommentRequest { - return new CreateCommentRequest( - triggeredBy, - postId, - content, - ); - } + return new CreateCommentRequest(triggeredBy, postId, content); + } - // Validate here using EnsureClass protected validatePayload(): void { + if (!this.postId || !this.content || !this.triggeredBy) { + throw new Error('Invalid request payload provided'); + } } } diff --git a/server/src/application/comment/create/create-comment.usecase.ts b/server/src/application/comment/create/create-comment.usecase.ts index d050f8d8..80ba27de 100644 --- a/server/src/application/comment/create/create-comment.usecase.ts +++ b/server/src/application/comment/create/create-comment.usecase.ts @@ -1,17 +1,39 @@ import { BaseUseCase, UseCase } from '@application/shared'; import { CreateCommentRequest } from './create-comment.request'; -import { CommentDetailsResponseDto } from '@contracts/dtos/comments'; - -@UseCase() -class CreateCommentUseCase extends BaseUseCase { +import { CommentResponseDto } from '@contracts/dtos/comments'; +import { Comment, CommentRepository } from '@domain/entities'; +import { Long } from 'typeorm'; +import { Logger } from '@domain/shared'; - constructor() { - super(); +@UseCase() +class CreateCommentUseCase extends BaseUseCase< + CreateCommentRequest, + CommentResponseDto +> { + private readonly _commentRepository: CommentRepository; + constructor(commentRepository: CommentRepository) { + super(); + this._commentRepository = commentRepository; } - public async performOperation({ }: CreateCommentRequest): Promise { - throw new Error('Method not implemented.'); + public async performOperation( + request: CreateCommentRequest, + ): Promise { + const comment: Comment = Comment.create( + null, + request.postId, + null, + request.triggeredBy.toString(), + null, + request.content, + new Date(), + new Date(), + null, + ); + + const createdComment = await this._commentRepository.create(comment); + return CommentResponseDto.fromEntity(createdComment); } } -export { CreateCommentUseCase }; \ No newline at end of file +export { CreateCommentUseCase }; diff --git a/server/src/application/comment/delete/delete-comment.usecase.ts b/server/src/application/comment/delete/delete-comment.usecase.ts index 96fe5d10..496195b7 100644 --- a/server/src/application/comment/delete/delete-comment.usecase.ts +++ b/server/src/application/comment/delete/delete-comment.usecase.ts @@ -1,16 +1,15 @@ import { BaseUseCase, UseCase } from '@application/shared'; import { DeleteCommentRequest } from './delete-comment.request'; - -@UseCase() -class DeleteCommentUseCase extends BaseUseCase { +@UseCase() +class DeleteCommentUseCase extends BaseUseCase { constructor() { - super(); + super(); } - public async performOperation({ }: DeleteCommentRequest): Promise { - throw new Error('Method not implemented.'); + public async performOperation({}: DeleteCommentRequest): Promise { + throw new Error('Method not implemented.'); } } -export { DeleteCommentUseCase }; \ No newline at end of file +export { DeleteCommentUseCase }; diff --git a/server/src/application/comment/edit/edit-comment.usecase.ts b/server/src/application/comment/edit/edit-comment.usecase.ts index 15d767e9..956a63c1 100644 --- a/server/src/application/comment/edit/edit-comment.usecase.ts +++ b/server/src/application/comment/edit/edit-comment.usecase.ts @@ -1,16 +1,19 @@ import { BaseUseCase, UseCase } from '@application/shared'; import { EditCommentRequest } from './edit-comment.request'; - -@UseCase() -class EditCommentUseCase extends BaseUseCase { +import { CommentDetailsResponseDto } from '@contracts/dtos/comments'; +@UseCase() +class EditCommentUseCase extends BaseUseCase< + EditCommentRequest, + CommentDetailsResponseDto +> { constructor() { - super(); + super(); } - public async performOperation({ }: EditCommentRequest): Promise { - throw new Error('Method not implemented.'); + public async performOperation({}: EditCommentRequest): Promise { + throw new Error('Method not implemented.'); } } -export { EditCommentUseCase }; \ No newline at end of file +export { EditCommentUseCase }; diff --git a/server/src/application/comment/get-by-post-id/get-by-post-id-comment.request.ts b/server/src/application/comment/get-by-post-id/get-by-post-id-comment.request.ts index fb71ca0c..fdb3bc5b 100644 --- a/server/src/application/comment/get-by-post-id/get-by-post-id-comment.request.ts +++ b/server/src/application/comment/get-by-post-id/get-by-post-id-comment.request.ts @@ -1,10 +1,8 @@ - import { UseCaseRequest } from '@application/shared'; import { TriggeredBy } from '@domain/shared/entities/triggered-by'; // import { InvalidParameterException } from '@domain/shared/exceptions'; -class GetByPostIdCommentRequest extends UseCaseRequest { - +class FindCommentsByPostIdRequest extends UseCaseRequest { readonly postId: string; readonly pageSize: number; readonly pageNumber: number; @@ -27,18 +25,17 @@ class GetByPostIdCommentRequest extends UseCaseRequest { postId: string, pageSize: number, pageNumber: number, - ): GetByPostIdCommentRequest { - return new GetByPostIdCommentRequest( + ): FindCommentsByPostIdRequest { + return new FindCommentsByPostIdRequest( triggeredBy, postId, pageSize, pageNumber, ); - } + } // Validate here using EnsureClass - protected validatePayload(): void { - } + protected validatePayload(): void {} } -export { GetByPostIdCommentRequest }; +export { FindCommentsByPostIdRequest }; diff --git a/server/src/application/comment/get-by-post-id/get-by-post-id-comment.usecase.ts b/server/src/application/comment/get-by-post-id/get-by-post-id-comment.usecase.ts index 80523958..3004d630 100644 --- a/server/src/application/comment/get-by-post-id/get-by-post-id-comment.usecase.ts +++ b/server/src/application/comment/get-by-post-id/get-by-post-id-comment.usecase.ts @@ -1,16 +1,30 @@ import { BaseUseCase, UseCase } from '@application/shared'; -import { GetByPostIdCommentRequest } from './get-by-post-id-comment.request'; - -@UseCase() -class GetByPostIdCommentUseCase extends BaseUseCase { +import { FindCommentsByPostIdRequest } from './get-by-post-id-comment.request'; +import { CommentResponseDto } from '@contracts/dtos/comments'; +import { CommentRepository } from '@domain/entities'; +import { Long } from 'typeorm'; +import { Logger } from '@domain/shared'; - constructor() { - super(); +@UseCase() +class FindCommentsByPostIdUseCase extends BaseUseCase< + FindCommentsByPostIdRequest, + CommentResponseDto[] +> { + private readonly _commentRepository: CommentRepository; + constructor(commentRepository: CommentRepository) { + super(); + this._commentRepository = commentRepository; } - public async performOperation({ }: GetByPostIdCommentRequest): Promise { - throw new Error('Method not implemented.'); + public async performOperation( + request: FindCommentsByPostIdRequest, + ): Promise { + Logger.info('Get comments by post id'); + const comments = await this._commentRepository.findByPostId(request.postId); + Logger.info('Get comments by post id', comments); + + return comments.map(comment => CommentResponseDto.fromEntity(comment)); } } -export { GetByPostIdCommentUseCase }; \ No newline at end of file +export { FindCommentsByPostIdUseCase }; diff --git a/server/src/application/comment/get-detail/get-detail-comment.usecase.ts b/server/src/application/comment/get-detail/get-detail-comment.usecase.ts index 9838af53..0120cf22 100644 --- a/server/src/application/comment/get-detail/get-detail-comment.usecase.ts +++ b/server/src/application/comment/get-detail/get-detail-comment.usecase.ts @@ -1,16 +1,19 @@ import { BaseUseCase, UseCase } from '@application/shared'; import { GetDetailCommentRequest } from './get-detail-comment.request'; - -@UseCase() -class GetDetailCommentUseCase extends BaseUseCase { +import { CommentDetailsResponseDto } from '@contracts/dtos/comments'; +@UseCase() +class GetDetailCommentUseCase extends BaseUseCase< + GetDetailCommentRequest, + CommentDetailsResponseDto +> { constructor() { - super(); + super(); } - public async performOperation({ }: GetDetailCommentRequest): Promise { - throw new Error('Method not implemented.'); + public async performOperation({}: GetDetailCommentRequest): Promise { + throw new Error('Method not implemented.'); } } -export { GetDetailCommentUseCase }; \ No newline at end of file +export { GetDetailCommentUseCase }; diff --git a/server/src/application/post/create/create-post.use-case.ts b/server/src/application/post/create/create-post.use-case.ts index 1b9daefb..f174c7f5 100644 --- a/server/src/application/post/create/create-post.use-case.ts +++ b/server/src/application/post/create/create-post.use-case.ts @@ -3,7 +3,6 @@ import { PostRepository } from '@domain/entities/posts/post.repository'; import { PostDetailsResponseDto } from '@contracts/dtos/posts/post-details.response'; import { CreatePostRequest } from './create-post.request'; import { Post } from '@domain/entities'; -import { Logger } from '@domain/shared'; @UseCase() class CreatePostUseCase extends BaseUseCase< @@ -12,14 +11,11 @@ class CreatePostUseCase extends BaseUseCase< > { private readonly _postRepository: PostRepository; - constructor( - postRepository: PostRepository, - ) { + constructor(postRepository: PostRepository) { super(); this._postRepository = postRepository; } - public async performOperation( request: CreatePostRequest, ): Promise { @@ -36,9 +32,7 @@ class CreatePostUseCase extends BaseUseCase< null, null, ); - Logger.info('CreatePostUseCase.performOperation', post); const createdPost = await this._postRepository.createPost(post); - Logger.info('Done', createdPost); if (createdPost) { return PostDetailsResponseDto.fromEntity(createdPost); } diff --git a/server/src/application/post/find-all/find-all-post.usecase.ts b/server/src/application/post/find-all/find-all-post.usecase.ts index cc4fb779..b21f5ff9 100644 --- a/server/src/application/post/find-all/find-all-post.usecase.ts +++ b/server/src/application/post/find-all/find-all-post.usecase.ts @@ -2,8 +2,6 @@ import { BaseUseCase, UseCase } from '@application/shared'; import { FindAllPostRequest } from './find-all-post.request'; import { PostResponseDto } from '@contracts/dtos/posts'; import { PostRepository } from '@domain/entities/posts/post.repository'; -import { log } from 'console'; -import { Logger } from '@domain/shared'; @UseCase() class FindAllPostUseCase extends BaseUseCase< @@ -19,21 +17,17 @@ class FindAllPostUseCase extends BaseUseCase< public async performOperation( request: FindAllPostRequest, ): Promise { - Logger.info('FindAllPostUseCase.performOperation'); const { pageSize, pageNumber } = request; const posts = await this._postRepository.findAll( pageSize || 10, pageNumber || 1, ); - Logger.info('posts from usecase', posts); const postDtos = await Promise.all( posts.map(async post => { return await PostResponseDto.fromEntity(post); }), ); - Logger.info('postsDtos from usecase', postDtos); - return postDtos; } } diff --git a/server/src/application/shared/base-usecase.ts b/server/src/application/shared/base-usecase.ts index 9e4f6e22..2d3c60a3 100644 --- a/server/src/application/shared/base-usecase.ts +++ b/server/src/application/shared/base-usecase.ts @@ -1,9 +1,8 @@ import { performance } from 'node:perf_hooks'; import { UseCaseRequest } from './usecase.request'; -import { injectable } from 'inversify'; +import { Logger } from '@domain/shared'; -@injectable() abstract class BaseUseCase { public async execute(request: IRequest): Promise { try { @@ -12,6 +11,7 @@ abstract class BaseUseCase { const response = await this.performOperation(request); const endTime = performance.now(); const useCaseExecutionTime = endTime - startTime; + Logger.debug(`Use case execution time: ${useCaseExecutionTime}ms`); return response; } catch (error) { throw error; diff --git a/server/src/contracts/dtos/comments/comment.response.ts b/server/src/contracts/dtos/comments/comment.response.ts index 0a674eca..d3b44f25 100644 --- a/server/src/contracts/dtos/comments/comment.response.ts +++ b/server/src/contracts/dtos/comments/comment.response.ts @@ -1,23 +1,27 @@ -import { Comment } from '@domain/entities'; -import { Nullable } from '@domain/shared/types'; +import { Comment, User } from '@domain/entities'; +import { Nullable } from '@domain/shared'; import { UserResponseDto } from '@dtos/users'; export class CommentResponseDto { constructor( public id: string, public content: string, - public author: UserResponseDto, + public authorId: string, + public author: Nullable, public postId: string, - public createdAt: Nullable, + public createdAt: Date, + public updatedAt: Date, ) {} public static fromEntity(entity: Comment): CommentResponseDto { return new CommentResponseDto( - entity.id!, + entity.id as string, entity.content, - UserResponseDto.fromEntity(entity.author as any), + entity.authorId, + UserResponseDto.fromEntity(entity.author as User), entity.postId, entity.createdAt, + entity.updatedAt, ); } } diff --git a/server/src/domain/entities/comments/comment.repository.ts b/server/src/domain/entities/comments/comment.repository.ts index fad48e89..16051a26 100644 --- a/server/src/domain/entities/comments/comment.repository.ts +++ b/server/src/domain/entities/comments/comment.repository.ts @@ -1,12 +1,12 @@ import { Comment } from '@domain/entities'; -interface ICommentRepository { - create(comment: Comment): Promise; - findByPostId(postId: string): Promise; - findById(commentId: string): Promise; - findByAuthor(authorId: string): Promise; - delete(commentId: string): Promise; - update(comment: Comment): Promise; +abstract class CommentRepository { + abstract create(comment: Comment): Promise; + abstract findByPostId(postId: string): Promise; + abstract findById(commentId: string): Promise; + abstract findByAuthor(authorId: string): Promise; + abstract delete(commentId: string): Promise; + abstract update(comment: Comment): Promise; } -export { ICommentRepository }; +export { CommentRepository }; diff --git a/server/src/domain/entities/comments/comment.ts b/server/src/domain/entities/comments/comment.ts index 2cf50e17..9976bde1 100644 --- a/server/src/domain/entities/comments/comment.ts +++ b/server/src/domain/entities/comments/comment.ts @@ -15,9 +15,37 @@ export class Comment extends AuditableBaseEntity { public replies: Reply[], public likes: LikeComment[], public createdAt: Date, - public updatedAt: Nullable, + public updatedAt: Date, public deletedAt: Nullable, ) { super(id, createdAt, authorId, updatedAt, authorId, deletedAt, authorId); } + + public static create( + id: Nullable, + postId: string, + post: Nullable, + authorId: string, + author: Nullable, + content: string, + createdAt: Date, + updatedAt: Date, + deletedAt: Nullable, + replies?: Reply[], + likes?: LikeComment[], + ): Comment { + return new Comment( + id, + postId, + post, + authorId, + author, + content, + replies || [], + likes || [], + createdAt, + updatedAt, + deletedAt, + ); + } } diff --git a/server/src/domain/entities/tokens/token.ts b/server/src/domain/entities/tokens/token.ts new file mode 100644 index 00000000..d2c86e1e --- /dev/null +++ b/server/src/domain/entities/tokens/token.ts @@ -0,0 +1,52 @@ +import { UserRole } from '../users'; + +enum TokenType { + ACCESS_TOKEN = 'access_token', +} + +interface TokenFlattened { + type: TokenType; + sessionUuid: string; + value: string; + expiresAt: Date; + userUuid: string; + username: string; + email: string; + roles: string[]; +} + +abstract class Token { + readonly type: TokenType; + + readonly value: string; + + readonly userUuid: string; + + readonly username: string; + + readonly email: string; + + readonly roles: UserRole[]; + + constructor( + type: TokenType, + value: string, + userUuid: string, + username: string, + email: string, + roles: UserRole[], + ) { + this.type = type; + this.value = value; + this.userUuid = userUuid; + this.username = username; + this.email = email; + this.roles = roles; + } + + public toString(): string { + return JSON.stringify(this); + } +} + +export { Token, TokenFlattened, TokenType }; diff --git a/server/src/infrastructure/comments/comment.repository.imp.ts b/server/src/infrastructure/comments/comment.repository.imp.ts deleted file mode 100644 index 2a3a84da..00000000 --- a/server/src/infrastructure/comments/comment.repository.imp.ts +++ /dev/null @@ -1,3 +0,0 @@ -class CommentRepository { } - -export { CommentRepository }; \ No newline at end of file diff --git a/server/src/infrastructure/comments/comment.repository.impl.ts b/server/src/infrastructure/comments/comment.repository.impl.ts new file mode 100644 index 00000000..e1971954 --- /dev/null +++ b/server/src/infrastructure/comments/comment.repository.impl.ts @@ -0,0 +1,59 @@ +import { Comment, CommentRepository } from '@domain/entities'; +import { RepositoryDec } from '@infrastructure/shared/persistence/repository.decorator'; +import { Repository } from 'typeorm'; +import { CommentPersistence } from './comment.persistence'; +import { appDataSource } from '@infrastructure/shared/persistence/data-source'; +import { CommentMapper } from './comment.mapper'; + +@RepositoryDec({ type: CommentRepository }) +class CommentRepositoryImp implements CommentRepository { + private _repository: Repository = + appDataSource.getRepository(CommentPersistence); + + constructor() { + this._repository = appDataSource.getRepository(CommentPersistence); + } + async create(comment: Comment): Promise { + const commentPersistence = CommentMapper.toPersistence(comment); + const createdComment = await this._repository.save(commentPersistence); + return CommentMapper.toDomain(createdComment, { + user: await createdComment.user, + }); + } + async findByPostId(postId: string): Promise { + const comments = await this._repository.find({ where: { postId } }); + const mappedComments = await Promise.all( + comments.map(async comment => { + const user = await comment.user; // Assuming `comment.user` is a Promise + return CommentMapper.toDomain(comment, { user }); + }), + ); + return mappedComments; + } + async findById(commentId: string): Promise { + return this._repository + .findOne({ where: { id: commentId } }) + .then(comment => (comment ? CommentMapper.toDomain(comment) : null)); + } + async findByAuthor(authorId: string): Promise { + const comments = this._repository.find({ where: { userId: authorId } }); + return comments.then(comments => + comments.map(comment => CommentMapper.toDomain(comment)), + ); + } + async delete(commentId: string): Promise { + return this._repository + .delete({ id: commentId }) + .then(result => result.affected === 1); + } + async update(comment: Comment): Promise { + const commentPersistence = CommentMapper.toPersistence(comment); + const updatedComment = await this._repository.save(commentPersistence); + + return CommentMapper.toDomain(updatedComment, { + user: await updatedComment.user, + }); + } +} + +export { CommentRepositoryImp }; diff --git a/server/src/infrastructure/comments/index.ts b/server/src/infrastructure/comments/index.ts index 4ea5d408..3f61d5b9 100644 --- a/server/src/infrastructure/comments/index.ts +++ b/server/src/infrastructure/comments/index.ts @@ -1,3 +1,3 @@ -export * from './comment.mapper' -export * from './comment.persistence' -export * from './comment.repository.imp' \ No newline at end of file +export * from './comment.mapper'; +export * from './comment.persistence'; +export * from './comment.repository.impl'; diff --git a/server/src/infrastructure/posts/post.repository.impl.ts b/server/src/infrastructure/posts/post.repository.impl.ts index a1c8c6cb..1c195e68 100644 --- a/server/src/infrastructure/posts/post.repository.impl.ts +++ b/server/src/infrastructure/posts/post.repository.impl.ts @@ -9,19 +9,16 @@ import { RepositoryDec } from '@infrastructure/shared/persistence/repository.dec import { Logger } from '@domain/shared'; @RepositoryDec({ type: PostRepository }) -export class PostRepositoryImp implements PostRepository { - private _repository: Repository = appDataSource.getRepository( - PostPersistence, - ); +export class PostRepositoryImpl implements PostRepository { + private _repository: Repository = + appDataSource.getRepository(PostPersistence); async findByAuthor(authorId: string): Promise { const post = await this._repository.findOne({ where: { authorId } }); return post ? PostMapper.toDomain(post) : null; } async createPost(post: Post): Promise { - Logger.info('PostRepositoryImp.createPost', post); const postPersistence = PostMapper.toPersistence(post); - Logger.info('PostRepositoryImp.createPost', postPersistence); const createdPost = await this._repository.save(postPersistence); return PostMapper.toDomain(createdPost, { @@ -33,10 +30,10 @@ export class PostRepositoryImp implements PostRepository { const post = await this._repository.findOne({ where: { id: postId } }); return post ? PostMapper.toDomain(post, { - author: await post.author, - comments: await post.comments, - likes: await post.likes, - }) + author: await post.author, + comments: await post.comments, + likes: await post.likes, + }) : null; } @@ -56,10 +53,7 @@ export class PostRepositoryImp implements PostRepository { } async findAll(limit: number, page: number): Promise { - Logger.info('PostRepositoryImp.findAll'); const [posts, count] = await this._repository.findAndCount(); - - Logger.info('posts from repo', posts); const postPromises = posts.map(async post => PostMapper.toDomain(post, { author: await post.author, diff --git a/server/src/infrastructure/shared/authentication/authentication.ts b/server/src/infrastructure/shared/authentication/authentication.ts index ef3a43d3..6e5c7f82 100644 --- a/server/src/infrastructure/shared/authentication/authentication.ts +++ b/server/src/infrastructure/shared/authentication/authentication.ts @@ -1,8 +1,10 @@ +import { Nullable } from '@domain/shared'; + export class Authentication { constructor( - public userUuid: string, - public username: string, - public email: string, + public userUuid: Nullable, + public username: Nullable, + public email: Nullable, public roles: string[], ) {} @@ -14,4 +16,8 @@ export class Authentication { ): Authentication { return new Authentication(userUuid, username, email, roles); } + + public static createEmpty(): Authentication { + return new Authentication(null, null, null, []); + } } diff --git a/server/src/infrastructure/shared/jwt/jwt-token-provider.domain-service.ts b/server/src/infrastructure/shared/jwt/jwt-token-provider.domain-service.ts index 7281cdcf..54de381e 100644 --- a/server/src/infrastructure/shared/jwt/jwt-token-provider.domain-service.ts +++ b/server/src/infrastructure/shared/jwt/jwt-token-provider.domain-service.ts @@ -1,7 +1,6 @@ import jwt from 'jsonwebtoken'; -import { DateTime } from 'luxon'; -import { Logger, Nullable } from '@domain/shared'; +import { Nullable } from '@domain/shared'; import { GlobalConfig } from '@infrastructure/shared/config'; import { DomainService } from '@domain/shared/services/domain-service.decorator'; import { TokenProviderDomainService } from '@domain/shared/services/token-provider.domain-service'; @@ -16,10 +15,7 @@ class JwtTokenProvider extends TokenProviderDomainService { }); } public verifyAccessToken(token: string): Nullable { - const decoded = jwt.verify(token, this.jwtSecret) as JwtPayload; - Logger.debug('JwtTokenProvider.verifyAccessToken', decoded); try { - Logger.debug('JwtTokenProvider.verifyAccessToken', jwt.verify(token, this.jwtSecret) as JwtPayload); return jwt.verify(token, this.jwtSecret) as JwtPayload; } catch { return null; @@ -33,7 +29,6 @@ class JwtTokenProvider extends TokenProviderDomainService { private readonly jwtExpiration: number = GlobalConfig.JWT_EXPIRATION; // private readonly jwtRefreshExpiration: number = GlobalConfig.JWT_REFRESH_EXPIRATION; - } export { JwtTokenProvider }; diff --git a/server/src/web/rest/controllers/comments/all-comments.api-response.ts b/server/src/web/rest/controllers/comments/all-comments.api-response.ts new file mode 100644 index 00000000..105eb780 --- /dev/null +++ b/server/src/web/rest/controllers/comments/all-comments.api-response.ts @@ -0,0 +1,149 @@ +import { CommentResponseDto } from '@contracts/dtos/comments'; +import { Property } from '@tsed/schema'; +import { AllPostsApiResponse } from '../posts/all-posts.api-response'; +import { UserResponseDto } from '@contracts/dtos/users'; +import { Nullable } from '@domain/shared'; + +class CommentAuthor { + @Property() + id: string; + + @Property() + firstName: string; + + @Property() + lastName: string; + + @Property() + username: string; + + @Property() + email: string; + + @Property() + pictureProfile: string; + + @Property() + roles: string[]; + + constructor( + id: string, + firstName: string, + lastName: string, + username: string, + email: string, + roles: string[], + pictureProfile: string, + ) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.username = username; + this.email = email; + this.roles = roles; + this.pictureProfile = pictureProfile || ''; + } + + public static create( + id: string, + firstName: string, + lastName: string, + username: string, + email: string, + roles: string[], + pictureProfile?: string, + ): CommentAuthor { + return new CommentAuthor( + id, + firstName, + lastName, + username, + email, + roles, + pictureProfile || '', + ); + } + + public static fromUserResponse(dto: UserResponseDto): CommentAuthor { + return new CommentAuthor( + dto.id, + dto.firstName, + dto.lastName, + dto.username, + dto.email, + dto.roles || [], + dto.pictureProfile || '', + ); + } +} +class AllCommentsApiResponse { + @Property() + id: string; + + @Property() + content: string; + + @Property() + userId: string; + + @Property() + user: Nullable; + + @Property() + postId: string; + + @Property() + post: AllPostsApiResponse; + + @Property() + createdAt: Date; + + constructor( + id: string, + content: string, + userId: string, + author: Nullable, + postId: string, + createdAt: Date, + ) { + this.id = id; + this.content = content; + this.userId = userId; + this.user = author; + this.postId = postId; + this.user = author; + this.createdAt = createdAt; + } + public static create( + id: string, + content: string, + userId: string, + author: Nullable, + postId: string, + createdAt: Date, + ): AllCommentsApiResponse { + return new AllCommentsApiResponse( + id, + content, + userId, + author, + postId, + createdAt, + ); + } + + public static fromCommentResponseDto( + response: CommentResponseDto, + ): AllCommentsApiResponse { + return new AllCommentsApiResponse( + response.id, + response.content, + response.authorId, + CommentAuthor.fromUserResponse(response.author as UserResponseDto), + response.postId, + response.createdAt, + ); + } +} + +export { AllCommentsApiResponse }; diff --git a/server/src/web/rest/controllers/comments/comments.controller.ts b/server/src/web/rest/controllers/comments/comments.controller.ts new file mode 100644 index 00000000..4efbf898 --- /dev/null +++ b/server/src/web/rest/controllers/comments/comments.controller.ts @@ -0,0 +1,79 @@ +import { RestController } from '../../infrastructure/rest-controller.decorator'; +import { Description, Get, Post, Returns, Summary, Title } from '@tsed/schema'; +import { StatusCodes } from 'http-status-codes'; +import { BodyParams, Context, RawQueryParams } from '@tsed/common'; +import { WithAuth } from '@web/rest/shared/with-auth.decorator'; +import { AppConfig } from '@web/rest/config'; +import { ApiResponse } from '@web/rest/infrastructure/api-response-wrapper'; +import { + CreateCommentRequest, + CreateCommentUseCase, + FindCommentsByPostIdRequest, + FindCommentsByPostIdUseCase, +} from '@application/comment'; +import { CreatedCommentApiResponse } from './created-comment.api-response'; +import { AllCommentsApiResponse } from './all-comments.api-response'; +import { TriggeredByUser } from '@domain/shared/entities'; +import { Logger } from '@domain/shared'; + +@RestController('/comments') +export class CommentsController { + private readonly _createCommentUseCase: CreateCommentUseCase; + private readonly _findCommentsByPostIdUseCase: FindCommentsByPostIdUseCase; + + constructor( + createCommentUseCase: CreateCommentUseCase, + findCommentsByPostId: FindCommentsByPostIdUseCase, + ) { + this._createCommentUseCase = createCommentUseCase; + this._findCommentsByPostIdUseCase = findCommentsByPostId; + } + + @Post('/') + @WithAuth() + @Title('Create a comment') + @Summary('Create a comment') + @Description('Endpoint to create a new comment') + @Returns(StatusCodes.OK, ApiResponse) + public async create( + @BodyParams('content') content: string, + @BodyParams('postId') postId: string, + @Context() ctx: Context, + ): Promise> { + const ctxBody = ctx.get(AppConfig.AUTHENTICATION_CONTEXT_KEY); + const request = CreateCommentRequest.create( + ctxBody.userUuid, + postId, + content, + ); + const response = await this._createCommentUseCase.execute(request); + return ApiResponse.success( + CreatedCommentApiResponse.fromCommentResponseDto(response), + ); + } + + @Get('/') + @Title('Find all comments') + @Summary('Find all comments') + @Returns(StatusCodes.OK, ApiResponse) + public async findAll( + @RawQueryParams('pageSize') pageSize: string, + @RawQueryParams('pageNumber') pageNumber: string, + @RawQueryParams('postId') postId: string, + ): Promise> { + const request = FindCommentsByPostIdRequest.create( + new TriggeredByUser('123', []), + postId, + parseInt(pageSize), + parseInt(pageNumber), + ); + + const result = await this._findCommentsByPostIdUseCase.execute(request); + Logger.info('CommentsController.findAll', result); + return ApiResponse.success( + result.map(comment => + AllCommentsApiResponse.fromCommentResponseDto(comment), + ), + ); + } +} diff --git a/server/src/web/rest/controllers/comments/created-comment.api-response.ts b/server/src/web/rest/controllers/comments/created-comment.api-response.ts new file mode 100644 index 00000000..4b33db55 --- /dev/null +++ b/server/src/web/rest/controllers/comments/created-comment.api-response.ts @@ -0,0 +1,151 @@ +import { CommentResponseDto } from '@contracts/dtos/comments'; +import { Property, Put } from '@tsed/schema'; +import { AllPostsApiResponse } from '../posts/all-posts.api-response'; +import { publish } from 'rxjs'; +import { UserResponseDto } from '@contracts/dtos/users'; + +class CommentAuthor { + @Property() + id: string; + + @Property() + firstName: string; + + @Property() + lastName: string; + + @Property() + username: string; + + @Property() + email: string; + + @Property() + pictureProfile: string; + + @Property() + roles: string[]; + + constructor( + id: string, + firstName: string, + lastName: string, + username: string, + email: string, + roles: string[], + pictureProfile: string, + ) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.username = username; + this.email = email; + this.roles = roles; + this.pictureProfile = pictureProfile || ''; + } + + public static create( + id: string, + firstName: string, + lastName: string, + username: string, + email: string, + roles: string[], + pictureProfile?: string, + ): CommentAuthor { + return new CommentAuthor( + id, + firstName, + lastName, + username, + email, + roles, + pictureProfile || '', + ); + } + + public static fromUserResponse(dto: UserResponseDto): CommentAuthor { + return new CommentAuthor( + dto.id, + dto.firstName, + dto.lastName, + dto.username, + dto.email, + dto.roles || [], + dto.pictureProfile || '', + ); + } +} +class CreatedCommentApiResponse { + @Property() + id: string; + + @Property() + content: string; + + @Property() + userId: string; + + @Property() + user: CommentAuthor; + + @Property() + postId: string; + + @Property() + post: AllPostsApiResponse; + + @Property() + createdAt: Date; + + constructor( + id: string, + content: string, + userId: string, + author: CommentAuthor, + postId: string, + createdAt: Date, + ) { + this.id = id; + this.content = content; + this.userId = userId; + this.user = author; + this.postId = postId; + this.user = author; + this.createdAt = createdAt; + } + public static create( + id: string, + content: string, + userId: string, + author: CommentAuthor, + postId: string, + createdAt: Date, + ): CreatedCommentApiResponse { + return new CreatedCommentApiResponse( + id, + content, + userId, + author, + postId, + createdAt, + ); + } + + public static fromCommentResponseDto( + response: CommentResponseDto, + ): CreatedCommentApiResponse { + return new CreatedCommentApiResponse( + response.id, + response.content, + response.authorId, + CommentAuthor.fromUserResponse( + response.author ?? ({} as UserResponseDto), + ), + response.postId, + response.createdAt, + ); + } +} + +export { CreatedCommentApiResponse }; diff --git a/server/src/web/rest/controllers/index.ts b/server/src/web/rest/controllers/index.ts index dc81cd07..e69de29b 100644 --- a/server/src/web/rest/controllers/index.ts +++ b/server/src/web/rest/controllers/index.ts @@ -1,2 +0,0 @@ -export * from './auth.controller'; -export * from './base.controller'; diff --git a/server/src/web/rest/controllers/posts/posts.controller.ts b/server/src/web/rest/controllers/posts/posts.controller.ts index 0052c8c0..533dd4b0 100644 --- a/server/src/web/rest/controllers/posts/posts.controller.ts +++ b/server/src/web/rest/controllers/posts/posts.controller.ts @@ -1,19 +1,9 @@ import { CreatePostRequest, CreatePostUseCase } from '@application/post'; -import { TriggeredByAnonymous, TriggeredByUser } from '@domain/shared/entities'; +import { TriggeredBy, TriggeredByUser } from '@domain/shared/entities'; import { FindAllPostRequest } from '@application/post/find-all/find-all-post.request'; import { RestController } from '../../infrastructure/rest-controller.decorator'; import { FindAllPostUseCase } from '@application/post/find-all/find-all-post.usecase'; -import { - Description, - Example, - Get, - Post, - Returns, - Status, - Summary, - Tags, - Title, -} from '@tsed/schema'; +import { Description, Get, Post, Returns, Summary, Title } from '@tsed/schema'; import { StatusCodes } from 'http-status-codes'; import { AllPostsApiResponse } from './all-posts.api-response'; import { BodyParams, Context, RawQueryParams } from '@tsed/common'; @@ -21,6 +11,7 @@ import { WithAuth } from '@web/rest/shared/with-auth.decorator'; import { AppConfig } from '@web/rest/config'; import { CreatedPostApiResponse } from './created-post.api-response'; import { ApiResponse } from '@web/rest/infrastructure/api-response-wrapper'; +import { Logger } from '@web/rest/logger'; @RestController('/posts') export class PostsController { @@ -45,9 +36,8 @@ export class PostsController { @BodyParams('title') title: string, @BodyParams('content') content: string, @Context() ctx: Context, - ): Promise { - const ctxBody = ctx.get(AppConfig.AUTHENTICATION_CONTEXT_KEY) + const ctxBody = ctx.get(AppConfig.AUTHENTICATION_CONTEXT_KEY); const request = CreatePostRequest.create( new TriggeredByUser(ctxBody.userUuid, []), title!, @@ -58,7 +48,7 @@ export class PostsController { ); const result = await this._createPostUseCase.execute(request); return CreatedPostApiResponse.fromCreatedPost(result); - }; + } @Get('/') @Title('Find all posts') @@ -67,22 +57,24 @@ export class PostsController { public async findAll( @RawQueryParams('pageSize') pageSize: string, @RawQueryParams('pageNumber') pageNumber: string, - @Context() ctx: Context, + @Context(AppConfig.TRIGGERED_BY_CONTEXT_KEY) triggeredBy: TriggeredBy, ): Promise> { + const request = FindAllPostRequest.create( + triggeredBy, + parseInt(pageSize), + parseInt(pageNumber), + ); try { - const request = FindAllPostRequest.create( - new TriggeredByUser("123", []), - parseInt(pageSize), - parseInt(pageNumber) - ); - const result = await this._findAllPostUseCase.execute(request); - const responseData = result.map(post => AllPostsApiResponse.fromPostResponse(post)); + const responseData = result.map(post => + AllPostsApiResponse.fromPostResponse(post), + ); return ApiResponse.success(responseData, 'Posts retrieved successfully'); } catch (error) { - // Log error if necessary - return ApiResponse.failure('Failed to retrieve posts', []); + return ApiResponse.failure( + 'Failed to retrieve posts', + ); } } } diff --git a/server/src/web/rest/infrastructure/api-response-wrapper.ts b/server/src/web/rest/infrastructure/api-response-wrapper.ts index a8049b30..ce21e55a 100644 --- a/server/src/web/rest/infrastructure/api-response-wrapper.ts +++ b/server/src/web/rest/infrastructure/api-response-wrapper.ts @@ -1,28 +1,32 @@ -import { Property } from "@tsed/schema"; +import { Nullable } from '@domain/shared'; +import { Property } from '@tsed/schema'; class ApiResponse { - @Property() - success: boolean; + @Property() + success: boolean; - @Property() - message: string; + @Property() + message: string; - @Property() - data: T; + @Property() + data: Nullable; - constructor(success: boolean, message: string, data: T) { - this.success = success; - this.message = message; - this.data = data; - } + constructor(success: boolean, message: string, data: Nullable = null) { + this.success = success; + this.message = message; + this.data = data; + } - static success(data: T, message: string = 'Operation successful'): ApiResponse { - return new ApiResponse(true, message, data); - } + static success( + data: T, + message: string = 'Operation successful', + ): ApiResponse { + return new ApiResponse(true, message, data); + } - static failure(message: string, data: T): ApiResponse { - return new ApiResponse(false, message, data); - } + static failure(message: string): ApiResponse { + return new ApiResponse(false, message); + } } -export { ApiResponse }; \ No newline at end of file +export { ApiResponse }; diff --git a/server/src/web/rest/infrastructure/request.utils.ts b/server/src/web/rest/infrastructure/request.utils.ts index c3495b90..cc5e3605 100644 --- a/server/src/web/rest/infrastructure/request.utils.ts +++ b/server/src/web/rest/infrastructure/request.utils.ts @@ -17,13 +17,13 @@ const RequestUtils = { : accessTokenHeader || accessTokenCookie || null; }, - getRefreshToken: (request: Req): Nullable => { - const refreshTokenHeader = request.get(AppConfig.REFRESH_TOKEN_HEADER_NAME); - const refreshTokenCookie = - request.cookies[AppConfig.REFRESH_TOKEN_COOKIE_NAME]; + // getRefreshToken: (request: Req): Nullable => { + // const refreshTokenHeader = request.get(AppConfig.REFRESH_TOKEN_HEADER_NAME); + // const refreshTokenCookie = + // request.cookies[AppConfig.REFRESH_TOKEN_COOKIE_NAME]; - return refreshTokenHeader || refreshTokenCookie || null; - }, + // return refreshTokenHeader || refreshTokenCookie || null; + // }, }; export { RequestUtils }; diff --git a/server/src/web/rest/middlewares/authentication.middleware.ts b/server/src/web/rest/middlewares/authentication.middleware.ts index 4271129d..93250d6c 100644 --- a/server/src/web/rest/middlewares/authentication.middleware.ts +++ b/server/src/web/rest/middlewares/authentication.middleware.ts @@ -6,98 +6,117 @@ import { TriggeredByUser } from '@domain/shared/entities/triggered-by'; import { JwtTokenProvider } from '@infrastructure/shared/jwt/jwt-token-provider.domain-service'; import { ForbiddenException } from '../expections'; import { AppConfig } from '../config'; -import { Logger } from '@domain/shared'; import { User, UserRepository } from '@domain/entities'; type UserRoles = 'admin' | 'user'; @Middleware() class AuthenticationMiddleware implements MiddlewareMethods { - constructor(private jwtTokenProvider: JwtTokenProvider, private userRepository: UserRepository) { } - - public async use(@Req() request: Req, @Res() response: Res, @Context() context: Context): Promise { - Logger.debug('AuthenticationMiddleware.use'); - const token = this.getTokenFromRequest(request); - if (!token) { - Logger.error('Token not found'); - throw new ForbiddenException(); - } - - const payload = this.jwtTokenProvider.verifyAccessToken(token); - Logger.debug('Payload', payload); - if (!payload) { - throw new ForbiddenException(); - } - - const validatedSessionResponse = { accessToken: payload }; - - this.ensureUserHasPrivileges(context, validatedSessionResponse); - this.attachMetadataToContext(context, validatedSessionResponse); + constructor( + private jwtTokenProvider: JwtTokenProvider, + private userRepository: UserRepository, + ) {} + + public async use( + @Req() request: Req, + @Res() response: Res, + @Context() context: Context, + ): Promise { + const token = this.getTokenFromRequest(request); + if (!token) { + throw new ForbiddenException(); } - private getTokenFromRequest(request: Req): string | null { - Logger.debug('AuthenticationMiddleware.getTokenFromRequest', request.headers); - const authHeader = request.headers['authorization']; - if (!authHeader) { - Logger.error('Authorization header not found'); - return null; - } - const parts = authHeader.split(' '); - if (parts.length !== 2 || parts[0] !== 'Bearer') { - return null; - } - Logger.debug('Token found', parts[1]); - return parts[1]; + const payload = this.jwtTokenProvider.verifyAccessToken(token); + if (!payload) { + throw new ForbiddenException(); } - private ensureUserHasPrivileges(context: Context, validatedSessionResponse: { accessToken: JwtPayload }): void { - const userRoles = validatedSessionResponse.accessToken?.roles.map(role => role.value.toLowerCase()) ?? []; - const { roles: allowedRoles = [] } = context.endpoint.get(AuthenticationMiddleware) || {}; + const validatedSessionResponse = { accessToken: payload }; - const userHasPrivileges = - allowedRoles.length === 0 || allowedRoles.some((role: UserRoles) => userRoles.includes(role.toLowerCase())); + this.ensureUserHasPrivileges(context, validatedSessionResponse); + this.attachMetadataToContext(context, validatedSessionResponse); + } - if (!userHasPrivileges) { - throw new ForbiddenException(); - } + private getTokenFromRequest(request: Req): string | null { + const authHeader = request.headers['authorization']; + if (!authHeader) { + return null; } - - private attachMetadataToContext(context: Context, validatedSessionResponse: { accessToken: JwtPayload }): void { - const { accessToken } = validatedSessionResponse; - - if (accessToken != null) { - const userRoles = accessToken.roles.map(role => role.value); - - const authentication = Authentication.create( - accessToken.userUuid.value, - accessToken.username.value, - accessToken.email.value, - userRoles - ); - context.set(AppConfig.AUTHENTICATION_CONTEXT_KEY, authentication); - AuthenticationUtils.setAuthentication(authentication); - - const triggeredBy = new TriggeredByUser(accessToken.username.value, userRoles); - context.set(AppConfig.TRIGGERED_BY_CONTEXT_KEY, triggeredBy); - - // assign user to context - this.ensureUserExists(context, validatedSessionResponse).then(user => { - context.set(AppConfig.AUTHENTICATION_CONTEXT_KEY, user); - }); - } + const parts = authHeader.split(' '); + if (parts.length !== 2 || parts[0] !== 'Bearer') { + return null; } - - // ensure user exists - - private async ensureUserExists(context: Context, validatedSessionResponse: { accessToken: JwtPayload }): Promise { - const { accessToken } = validatedSessionResponse; - const user = await this.userRepository.findById(accessToken.userUuid.value); - if (!user) { - throw new ForbiddenException(); - } - - return user; + return parts[1]; + } + + private ensureUserHasPrivileges( + context: Context, + validatedSessionResponse: { accessToken: JwtPayload }, + ): void { + const userRoles = + validatedSessionResponse.accessToken?.roles.map(role => + role.value.toLowerCase(), + ) ?? []; + const { roles: allowedRoles = [] } = + context.endpoint.get(AuthenticationMiddleware) || {}; + + const userHasPrivileges = + allowedRoles.length === 0 || + allowedRoles.some((role: UserRoles) => + userRoles.includes(role.toLowerCase()), + ); + + if (!userHasPrivileges) { + throw new ForbiddenException(); } + } + + private attachMetadataToContext( + context: Context, + validatedSessionResponse: { accessToken: JwtPayload }, + ): void { + const { accessToken } = validatedSessionResponse; + + if (accessToken != null) { + const userRoles = accessToken.roles.map(role => role.value); + + const authentication = Authentication.create( + accessToken.userUuid.value, + accessToken.username.value, + accessToken.email.value, + userRoles, + ); + context.set(AppConfig.AUTHENTICATION_CONTEXT_KEY, authentication); + AuthenticationUtils.setAuthentication(authentication); + + const triggeredBy = new TriggeredByUser( + accessToken.username.value, + userRoles, + ); + context.set(AppConfig.TRIGGERED_BY_CONTEXT_KEY, triggeredBy); + + // assign user to context + this.ensureUserExists(context, validatedSessionResponse).then(user => { + context.set(AppConfig.AUTHENTICATION_CONTEXT_KEY, user); + }); + } + } + + // ensure user exists + + private async ensureUserExists( + context: Context, + validatedSessionResponse: { accessToken: JwtPayload }, + ): Promise { + const { accessToken } = validatedSessionResponse; + const user = await this.userRepository.findById(accessToken.userUuid.value); + if (!user) { + throw new ForbiddenException(); + } + + return user; + } } export { AuthenticationMiddleware }; diff --git a/server/src/web/rest/middlewares/error.mw.ts b/server/src/web/rest/middlewares/error.mw.ts index 88312d45..91b912cf 100644 --- a/server/src/web/rest/middlewares/error.mw.ts +++ b/server/src/web/rest/middlewares/error.mw.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from 'express'; -import { LOGGER } from '../logger'; import { ApplicationError } from '@contracts/errors/application.error'; +import { Logger } from '../logger'; export const errorHandlerMiddleware = ( err: ApplicationError, @@ -8,7 +8,7 @@ export const errorHandlerMiddleware = ( res: Response, _next: NextFunction, ) => { - LOGGER.error(err); + Logger.error(err); return res.status(err.code || 500).json({ success: false, error: err.message || 'BOOM', diff --git a/server/src/web/rest/middlewares/index.ts b/server/src/web/rest/middlewares/index.ts index a59c766c..6670fdf3 100644 --- a/server/src/web/rest/middlewares/index.ts +++ b/server/src/web/rest/middlewares/index.ts @@ -1,5 +1,4 @@ export * from './error-handler.middleware'; export * from './error.mw'; -export * from './authern'; export * from './not-found.middleware'; export * from './metadata.middleware'; diff --git a/server/src/web/rest/middlewares/metadata.middleware.ts b/server/src/web/rest/middlewares/metadata.middleware.ts index 84deba12..55852165 100644 --- a/server/src/web/rest/middlewares/metadata.middleware.ts +++ b/server/src/web/rest/middlewares/metadata.middleware.ts @@ -1,64 +1,62 @@ -// import { Token, TokenProviderDomainService } from '@domain/sessions/tokens'; -// import { Nullable } from '@domain/shared'; -// import { Authentication } from '@infrastructure/shared/authentication'; -// import { AuthenticationUtils } from '@infrastructure/shared/authentication/authentication-utils'; -// import { AppConfig } from '@web/rest/config'; -// import { RequestUtils } from '@web/rest/infrastructure/request.utils'; - -// import { -// TriggeredByAnonymous, -// TriggeredByUser, -// } from '@domain/shared/entities/triggered-by'; - -// import { -// Context, -// Middleware, -// MiddlewareMethods, -// OnResponse, -// Req, -// } from '@tsed/common'; - -// @Middleware() -// class MetadataMiddleware implements MiddlewareMethods, OnResponse { -// private tokenProviderDomainService: TokenProviderDomainService; - -// constructor(tokenProviderDomainService: TokenProviderDomainService) { -// this.tokenProviderDomainService = tokenProviderDomainService; -// } - -// public use(@Req() request: Req, @Context() context: Context): void { -// let triggeredBy = new TriggeredByAnonymous(); -// let authentication = Authentication.createEmpty(); - -// const accessTokenString = RequestUtils.getAccessToken(request); -// const refreshTokenString = RequestUtils.getRefreshToken(request); -// const accessToken = accessTokenString -// ? this.tokenProviderDomainService.parseAccessToken(accessTokenString) -// : null; -// const refreshToken = refreshTokenString -// ? this.tokenProviderDomainService.parseRefreshToken(refreshTokenString) -// : null; -// const token: Nullable = accessToken || refreshToken; - -// if (token) { -// const userRoles = token.roles.map(role => role.value); -// triggeredBy = new TriggeredByUser(token.username.value, userRoles); -// authentication = Authentication.create( -// token.userUuid.value, -// token.username.value, -// token.email.value, -// userRoles, -// ); -// } - -// AuthenticationUtils.setAuthentication(authentication); -// context.set(AppConfig.AUTHENTICATION_CONTEXT_KEY, authentication); -// context.set(AppConfig.TRIGGERED_BY_CONTEXT_KEY, triggeredBy); -// } - -// public $onResponse(): void { -// AuthenticationUtils.clearAuthentication(); -// } -// } - -// export { MetadataMiddleware }; +import { Nullable } from '@domain/shared'; +import { Authentication } from '@infrastructure/shared/authentication'; +import { AuthenticationUtils } from '@infrastructure/shared/authentication/authentication-utils'; +import { AppConfig } from '@web/rest/config'; +import { RequestUtils } from '@web/rest/infrastructure/request.utils'; + +import { + TriggeredByAnonymous, + TriggeredByUser, +} from '@domain/shared/entities/triggered-by'; + +import { + Context, + Middleware, + MiddlewareMethods, + OnResponse, + Req, +} from '@tsed/common'; +import { TokenProviderDomainService } from '@domain/shared/services/token-provider.domain-service'; +import { JwtPayload } from '@contracts/services/IJwt'; +import { Logger } from '../logger'; + +@Middleware() +class MetadataMiddleware implements MiddlewareMethods, OnResponse { + private tokenProviderDomainService: TokenProviderDomainService; + + constructor(tokenProviderDomainService: TokenProviderDomainService) { + this.tokenProviderDomainService = tokenProviderDomainService; + } + + public use(@Req() request: Req, @Context() context: Context): void { + let triggeredBy = new TriggeredByAnonymous(); + let authentication = Authentication.createEmpty(); + + const accessTokenString = RequestUtils.getAccessToken(request); + const accessToken = accessTokenString; + const payload: Nullable = accessToken + ? this.tokenProviderDomainService.verifyAccessToken(accessToken) + : null; + + if (payload) { + const userRoles = payload.roles.map(role => role.value); + triggeredBy = new TriggeredByUser(payload.username.value, userRoles); + authentication = Authentication.create( + payload.userUuid.value, + payload.username.value, + payload.email.value, + userRoles, + ); + } + + AuthenticationUtils.setAuthentication(authentication); + context.set(AppConfig.AUTHENTICATION_CONTEXT_KEY, authentication); + context.set(AppConfig.TRIGGERED_BY_CONTEXT_KEY, triggeredBy); + } + + public $onResponse(): void { + AuthenticationUtils.clearAuthentication(); + } +} + +export { MetadataMiddleware }; diff --git a/server/src/web/rest/server.ts b/server/src/web/rest/server.ts index 2f4cab19..12b939f5 100644 --- a/server/src/web/rest/server.ts +++ b/server/src/web/rest/server.ts @@ -21,11 +21,10 @@ import { CacheConfig, GlobalConfig } from '@infrastructure/shared/config'; import { AppConfig, AppInfo } from './config'; import { // LoggerMiddleware, - // MetadataMiddleware, + MetadataMiddleware, NotFoundMiddleware, } from './middlewares'; import { LoggerMiddleware } from './middlewares/logger.middleware'; -import { log } from 'console'; class Server { @Inject() @@ -133,7 +132,7 @@ class Server { .use(cookieParser()) .use(compression({})) .use(methodOverride()) - // .use(MetadataMiddleware); + .use(MetadataMiddleware) .use(LoggerMiddleware); }