Skip to content

Commit

Permalink
Merge pull request #6 from cieslarmichal/feature/posts
Browse files Browse the repository at this point in the history
add posts
  • Loading branch information
cieslarmichal committed Mar 16, 2024
2 parents e1fb27b + 6b1bc93 commit 6980ffd
Show file tree
Hide file tree
Showing 44 changed files with 1,574 additions and 8 deletions.
2 changes: 2 additions & 0 deletions apps/backend/src/core/httpServer/httpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { SecurityMode } from '../../common/types/http/securityMode.js';
import { type DependencyInjectionContainer } from '../../libs/dependencyInjection/dependencyInjectionContainer.js';
import { type LoggerService } from '../../libs/logger/services/loggerService/loggerService.js';
import { type GroupHttpController } from '../../modules/groupModule/api/httpControllers/groupHttpController/groupHttpController.js';
import { type PostHttpController } from '../../modules/groupModule/api/httpControllers/postHttpController/postHttpController.js';
import { groupSymbols } from '../../modules/groupModule/symbols.js';
import { type UserGroupHttpController } from '../../modules/userGroupModule/api/httpControllers/userGroupHttpController/userGroupHttpController.js';
import { userGroupSymbols } from '../../modules/userGroupModule/symbols.js';
Expand Down Expand Up @@ -51,6 +52,7 @@ export class HttpServer {
this.container.get<ApplicationHttpController>(symbols.applicationHttpController),
this.container.get<GroupHttpController>(groupSymbols.groupHttpController),
this.container.get<UserGroupHttpController>(userGroupSymbols.userGroupHttpController),
this.container.get<PostHttpController>(groupSymbols.postHttpController),
];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class GroupHttpController implements HttpController {
},
},
},
securityMode: SecurityMode.basicAuth,
securityMode: SecurityMode.bearerToken,
}),
new HttpRoute({
description: 'Update Group name.',
Expand All @@ -101,7 +101,7 @@ export class GroupHttpController implements HttpController {
},
},
},
securityMode: SecurityMode.basicAuth,
securityMode: SecurityMode.bearerToken,
path: ':id/name',
}),
new HttpRoute({
Expand Down Expand Up @@ -178,7 +178,7 @@ export class GroupHttpController implements HttpController {
private async createGroup(
request: HttpRequest<CreateGroupBodyDTO>,
): Promise<HttpCreatedResponse<CreateGroupResponseBodyDTO>> {
this.accessControlService.verifyBasicAuth({
this.accessControlService.verifyBearerToken({
authorizationHeader: request.headers['authorization'],
});

Expand All @@ -198,7 +198,7 @@ export class GroupHttpController implements HttpController {
private async updateGroupName(
request: HttpRequest<UpdateGroupNameBodyDTO, null, UpdateGroupNamePathParamsDTO>,
): Promise<HttpOkResponse<UpdateGroupNameResponseBodyDTO>> {
this.accessControlService.verifyBasicAuth({
this.accessControlService.verifyBearerToken({
authorizationHeader: request.headers['authorization'],
});

Expand All @@ -220,7 +220,7 @@ export class GroupHttpController implements HttpController {
private async deleteGroup(
request: HttpRequest<null, null, DeleteGroupPathParamsDTO>,
): Promise<HttpNoContentResponse<DeleteGroupResponseBodyDTO>> {
this.accessControlService.verifyBasicAuth({
this.accessControlService.verifyBearerToken({
authorizationHeader: request.headers['authorization'],
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import {
createPostBodyDTOSchema,
createPostResponseBodyDTOSchema,
type CreatePostBodyDTO,
type CreatePostResponseBodyDTO,
type CreatePostPathParamsDTO,
} from './schema/createPostSchema.js';
import {
deletePostPathParamsDTOSchema,
deletePostResponseBodyDTOSchema,
type DeletePostPathParamsDTO,
type DeletePostResponseBodyDTO,
} from './schema/deletePostSchema.js';
import {
findPostsResponseBodyDTOSchema,
type FindPostsResponseBodyDTO,
type FindPostsPathParamsDTO,
} from './schema/findPostsSchema.js';
import { type PostDTO } from './schema/postDTO.js';
import {
updatePostBodyDTOSchema,
updatePostPathParamsDTOSchema,
updatePostResponseBodyDTOSchema,
type UpdatePostBodyDTO,
type UpdatePostPathParamsDTO,
type UpdatePostResponseBodyDTO,
} from './schema/updatePostSchema.js';
import { type HttpController } from '../../../../../common/types/http/httpController.js';
import { HttpMethodName } from '../../../../../common/types/http/httpMethodName.js';
import { type HttpRequest } from '../../../../../common/types/http/httpRequest.js';
import {
type HttpOkResponse,
type HttpCreatedResponse,
type HttpNoContentResponse,
} from '../../../../../common/types/http/httpResponse.js';
import { HttpRoute } from '../../../../../common/types/http/httpRoute.js';
import { HttpStatusCode } from '../../../../../common/types/http/httpStatusCode.js';
import { SecurityMode } from '../../../../../common/types/http/securityMode.js';
import { type AccessControlService } from '../../../../authModule/application/services/accessControlService/accessControlService.js';
import { type CreatePostCommandHandler } from '../../../application/commandHandlers/createPostCommandHandler/createPostCommandHandler.js';
import { type DeletePostCommandHandler } from '../../../application/commandHandlers/deletePostCommandHandler/deletePostCommandHandler.js';
import { type UpdatePostCommandHandler } from '../../../application/commandHandlers/updatePostCommandHandler/updatePostCommandHandler.js';
import { type FindPostsQueryHandler } from '../../../application/queryHandlers/findPostsQueryHandler/findPostsQueryHandler.js';
import { type Post } from '../../../domain/entities/post/post.js';

export class PostHttpController implements HttpController {
public basePath = '/api/groups/:groupId/posts';
public tags = ['Post'];

public constructor(
private readonly createPostCommandHandler: CreatePostCommandHandler,
private readonly updatePostCommandHandler: UpdatePostCommandHandler,
private readonly deletePostCommandHandler: DeletePostCommandHandler,
private readonly findPostsQueryHandler: FindPostsQueryHandler,
private readonly accessControlService: AccessControlService,
) {}

public getHttpRoutes(): HttpRoute[] {
return [
new HttpRoute({
description: 'Create post.',
handler: this.createPost.bind(this),
method: HttpMethodName.post,
schema: {
request: {
body: createPostBodyDTOSchema,
},
response: {
[HttpStatusCode.created]: {
description: 'Post created.',
schema: createPostResponseBodyDTOSchema,
},
},
},
securityMode: SecurityMode.bearerToken,
}),
new HttpRoute({
description: 'Update Post content.',
handler: this.updatePost.bind(this),
method: HttpMethodName.patch,
schema: {
request: {
pathParams: updatePostPathParamsDTOSchema,
body: updatePostBodyDTOSchema,
},
response: {
[HttpStatusCode.ok]: {
description: 'Post content updated.',
schema: updatePostResponseBodyDTOSchema,
},
},
},
securityMode: SecurityMode.bearerToken,
path: ':id/name',
}),
new HttpRoute({
description: 'Delete post.',
handler: this.deletePost.bind(this),
method: HttpMethodName.delete,
schema: {
request: {
pathParams: deletePostPathParamsDTOSchema,
},
response: {
[HttpStatusCode.noContent]: {
description: 'Post deleted.',
schema: deletePostResponseBodyDTOSchema,
},
},
},
path: ':id',
}),
new HttpRoute({
description: 'Find posts.',
handler: this.findPosts.bind(this),
method: HttpMethodName.get,
schema: {
request: {},
response: {
[HttpStatusCode.ok]: {
description: 'Posts found.',
schema: findPostsResponseBodyDTOSchema,
},
},
},
securityMode: SecurityMode.bearerToken,
}),
];
}

private async createPost(
request: HttpRequest<CreatePostBodyDTO, undefined, CreatePostPathParamsDTO>,
): Promise<HttpCreatedResponse<CreatePostResponseBodyDTO>> {
const { userId } = await this.accessControlService.verifyBearerToken({
authorizationHeader: request.headers['authorization'],
});

const { groupId } = request.pathParams;

const { content } = request.body;

const { post } = await this.createPostCommandHandler.execute({
userId,
groupId,
content,
});

return {
body: this.mapPostToDTO(post),
statusCode: HttpStatusCode.created,
};
}

private async updatePost(
request: HttpRequest<UpdatePostBodyDTO, null, UpdatePostPathParamsDTO>,
): Promise<HttpOkResponse<UpdatePostResponseBodyDTO>> {
this.accessControlService.verifyBearerToken({
authorizationHeader: request.headers['authorization'],
});

const { id } = request.pathParams;

const { content } = request.body;

const { post } = await this.updatePostCommandHandler.execute({
id,
content,
});

return {
body: this.mapPostToDTO(post),
statusCode: HttpStatusCode.ok,
};
}

private async deletePost(
request: HttpRequest<null, null, DeletePostPathParamsDTO>,
): Promise<HttpNoContentResponse<DeletePostResponseBodyDTO>> {
this.accessControlService.verifyBearerToken({
authorizationHeader: request.headers['authorization'],
});

const { id } = request.pathParams;

await this.deletePostCommandHandler.execute({ id });

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

// TODO: check if user is a member of this group
private async findPosts(
request: HttpRequest<undefined, undefined, FindPostsPathParamsDTO>,
): Promise<HttpOkResponse<FindPostsResponseBodyDTO>> {
await this.accessControlService.verifyBearerToken({
authorizationHeader: request.headers['authorization'],
});

const { groupId } = request.pathParams;

const { posts } = await this.findPostsQueryHandler.execute({ groupId });

return {
body: {
data: posts.map(this.mapPostToDTO),
},
statusCode: HttpStatusCode.ok,
};
}

private mapPostToDTO(post: Post): PostDTO {
return {
id: post.getId(),
groupId: post.getGroupId(),
userId: post.getUserId(),
content: post.getContent(),
createdAt: post.getCreatedAt().toISOString(),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { type Static, Type } from '@sinclair/typebox';

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

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

export const createPostPathParamsDTOSchema = Type.Object({
groupId: Type.String({ format: 'uuid' }),
});

export type CreatePostPathParamsDTO = TypeExtends<
contracts.CreatePostPathParams,
Static<typeof createPostPathParamsDTOSchema>
>;

export const createPostBodyDTOSchema = Type.Object({
content: Type.String({
minLength: 1,
maxLength: 256,
}),
});

export type CreatePostBodyDTO = TypeExtends<contracts.CreatePostRequestBody, Static<typeof createPostBodyDTOSchema>>;

export const createPostResponseBodyDTOSchema = postDTO;

export type CreatePostResponseBodyDTO = TypeExtends<
contracts.CreatePostResponseBody,
Static<typeof createPostResponseBodyDTOSchema>
>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { type Static, Type } from '@sinclair/typebox';

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

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

export const deletePostPathParamsDTOSchema = Type.Object({
id: Type.String({ format: 'uuid' }),
});

export type DeletePostPathParamsDTO = TypeExtends<
contracts.DeletePostPathParams,
Static<typeof deletePostPathParamsDTOSchema>
>;

export const deletePostResponseBodyDTOSchema = Type.Null();

export type DeletePostResponseBodyDTO = Static<typeof deletePostResponseBodyDTOSchema>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { type Static, Type } from '@sinclair/typebox';

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

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

export const findPostsPathParamsDTOSchema = Type.Object({
groupId: Type.String({ format: 'uuid' }),
});

export type FindPostsPathParamsDTO = TypeExtends<
contracts.FindPostsPathParams,
Static<typeof findPostsPathParamsDTOSchema>
>;

export const findPostsResponseBodyDTOSchema = Type.Object({
data: Type.Array(postDTO),
});

export type FindPostsResponseBodyDTO = TypeExtends<
Static<typeof findPostsResponseBodyDTOSchema>,
contracts.FindPostsResponseBody
>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { type Static, Type } from '@sinclair/typebox';

export const postDTO = Type.Object({
id: Type.String(),
userId: Type.String(),
groupId: Type.String(),
content: Type.String(),
createdAt: Type.String(),
});

export type PostDTO = Static<typeof postDTO>;
Loading

0 comments on commit 6980ffd

Please sign in to comment.