diff --git a/src/application/posts/commands/create-post/__tests__/create-post-command.test.ts b/src/application/posts/commands/create-post/__tests__/create-post-command.test.ts deleted file mode 100644 index 82667a0..0000000 --- a/src/application/posts/commands/create-post/__tests__/create-post-command.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { ValidationException } from '@application/common/exceptions'; -import { PostsRepository } from '@application/common/interfaces'; - -import { makeCreatePostCommand } from '../create-post-command'; - -describe('createPostCommand', () => { - function setup() { - const postsRepository = jest.mocked>({ - create: jest.fn(), - }) as jest.Mocked; - - const createPostCommand = makeCreatePostCommand({ postsRepository }); - - return { - createPostCommand, - postsRepository, - }; - } - - describe('given an invalid command', () => { - it('should throw a validation exception', async () => { - // Arrange - const { createPostCommand } = setup(); - - // Act - const result = createPostCommand({ - title: '', // Cannot be empty - }); - - // Assert - await expect(result).rejects.toThrow(ValidationException); - }); - }); - - describe('given a valid command', () => { - it('should create post and return the generated id', async () => { - // Arrange - const { createPostCommand, postsRepository } = setup(); - postsRepository.create.mockResolvedValueOnce({ id: '907b0a0b-9a12-4073-ab27-3ad5927955e9' }); - - // Act - const result = await createPostCommand({ - title: 'My first post', - }); - - // Assert - expect(result).toEqual({ id: '907b0a0b-9a12-4073-ab27-3ad5927955e9' }); - }); - }); -}); diff --git a/src/application/posts/commands/create-post/create-post-command-validator.ts b/src/application/posts/commands/create-post/create-post-command-validator.ts deleted file mode 100644 index 0942399..0000000 --- a/src/application/posts/commands/create-post/create-post-command-validator.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ValidationException } from '@application/common/exceptions'; -import { ZodError, z } from 'zod'; - -import { CreatePostCommand } from './create-post-command'; - -export async function validate(command: CreatePostCommand) { - try { - const schema: z.ZodType = z.object({ - title: z.string().min(1).max(150), - }); - - await schema.parseAsync(command); - } catch (error) { - throw new ValidationException(error as ZodError); - } -} diff --git a/src/application/posts/commands/create-post/create-post-command.ts b/src/application/posts/commands/create-post/create-post-command.ts deleted file mode 100644 index 3d1860d..0000000 --- a/src/application/posts/commands/create-post/create-post-command.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Post } from '@domain/entities'; - -import { validate } from './create-post-command-validator'; - -export type CreatePostCommand = Readonly<{ - title: string; -}>; - -export function makeCreatePostCommand({ postsRepository }: Pick) { - return async function createPostCommand(command: CreatePostCommand) { - await validate(command); - - const post = new Post({ - createdAt: new Date(), - title: command.title, - }); - - const { id } = await postsRepository.create({ post }); - - return { - id, - }; - }; -} diff --git a/src/application/posts/commands/create-post/index.ts b/src/application/posts/commands/create-post/index.ts deleted file mode 100644 index feded64..0000000 --- a/src/application/posts/commands/create-post/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './create-post-command'; diff --git a/src/application/posts/commands/delete-post/__tests__/delete-post-command.test.ts b/src/application/posts/commands/delete-post/__tests__/delete-post-command.test.ts deleted file mode 100644 index cf658c9..0000000 --- a/src/application/posts/commands/delete-post/__tests__/delete-post-command.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ValidationException } from '@application/common/exceptions'; -import { PostsRepository } from '@application/common/interfaces'; -import { Post } from '@domain/entities'; - -import { makeDeletePostCommand } from '../delete-post-command'; - -describe('deletePostCommand', () => { - function setup() { - const postsRepository = jest.mocked>({ - delete: jest.fn(), - getById: jest.fn(), - }) as jest.Mocked; - - const deletePostCommand = makeDeletePostCommand({ postsRepository }); - - return { - deletePostCommand, - postsRepository, - }; - } - - describe('given an invalid command', () => { - it('should throw a validation exception', async () => { - // Arrange - const { deletePostCommand } = setup(); - - // Act - const result = deletePostCommand({ - id: 'invalid-uuid', - }); - - // Assert - await expect(result).rejects.toThrow(ValidationException); - }); - }); - - describe('given a valid command', () => { - it('should delete the post', async () => { - // Arrange - const { deletePostCommand, postsRepository } = setup(); - - postsRepository.getById.mockResolvedValue( - new Post({ - id: '907b0a0b-9a12-4073-ab27-3ad5927955e9', - createdAt: new Date(2022, 1, 1), - title: 'Mock post', - }), - ); - - // Act - await deletePostCommand({ id: '907b0a0b-9a12-4073-ab27-3ad5927955e9' }); - - // Assert - expect(postsRepository.delete).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/src/application/posts/commands/delete-post/delete-post-command-validator.ts b/src/application/posts/commands/delete-post/delete-post-command-validator.ts deleted file mode 100644 index 49dfe0d..0000000 --- a/src/application/posts/commands/delete-post/delete-post-command-validator.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ValidationException } from '@application/common/exceptions'; -import { ZodError, z } from 'zod'; - -import { DeletePostCommand } from './delete-post-command'; - -export async function validate(command: DeletePostCommand) { - try { - const schema: z.ZodType = z.object({ - id: z.string().uuid(), - }); - - await schema.parseAsync(command); - } catch (error) { - throw new ValidationException(error as ZodError); - } -} diff --git a/src/application/posts/commands/delete-post/delete-post-command.ts b/src/application/posts/commands/delete-post/delete-post-command.ts deleted file mode 100644 index a59dc67..0000000 --- a/src/application/posts/commands/delete-post/delete-post-command.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { validate } from './delete-post-command-validator'; - -export type DeletePostCommand = Readonly<{ - id: string; -}>; - -export function makeDeletePostCommand({ postsRepository }: Pick) { - return async function deletePostCommand(command: DeletePostCommand) { - await validate(command); - - const { id } = command; - - await postsRepository.delete({ id }); - }; -} diff --git a/src/application/posts/commands/delete-post/index.ts b/src/application/posts/commands/delete-post/index.ts deleted file mode 100644 index 9b752be..0000000 --- a/src/application/posts/commands/delete-post/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './delete-post-command'; diff --git a/src/application/posts/index.ts b/src/application/posts/index.ts deleted file mode 100644 index 8321776..0000000 --- a/src/application/posts/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { makeCreatePostCommand } from './commands/create-post'; -import { makeDeletePostCommand } from './commands/delete-post'; -import { makeGetPostQuery } from './queries/get-post'; -import { makeListPostsQuery } from './queries/list-posts'; - -export function makePostsUseCases(dependencies: Dependencies) { - return { - commands: { - createPost: makeCreatePostCommand(dependencies), - deletePost: makeDeletePostCommand(dependencies), - }, - queries: { - getPost: makeGetPostQuery(dependencies), - listPosts: makeListPostsQuery(dependencies), - }, - }; -} diff --git a/src/application/posts/queries/get-post/__tests__/__snapshots__/get-post-query.test.ts.snap b/src/application/posts/queries/get-post/__tests__/__snapshots__/get-post-query.test.ts.snap deleted file mode 100644 index babff73..0000000 --- a/src/application/posts/queries/get-post/__tests__/__snapshots__/get-post-query.test.ts.snap +++ /dev/null @@ -1,8 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`getPostQuery given the post exists should return the post 1`] = ` -{ - "id": "907b0a0b-9a12-4073-ab27-3ad5927955e9", - "title": "Mock post", -} -`; diff --git a/src/application/posts/queries/get-post/__tests__/get-post-query.test.ts b/src/application/posts/queries/get-post/__tests__/get-post-query.test.ts deleted file mode 100644 index 343e58e..0000000 --- a/src/application/posts/queries/get-post/__tests__/get-post-query.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { ValidationException } from '@application/common/exceptions'; -import { PostsRepository } from '@application/common/interfaces'; -import { Post } from '@domain/entities'; - -import { makeGetPostQuery } from '../get-post-query'; - -describe('getPostQuery', () => { - function setup() { - const postsRepository = jest.mocked>({ - getById: jest.fn(), - }) as jest.Mocked; - - const getPostQuery = makeGetPostQuery({ postsRepository: postsRepository as PostsRepository }); - - return { - getPostQuery, - postsRepository, - }; - } - - describe('given an invalid query', () => { - it('should throw a validation exception', async () => { - // Arrange - const { getPostQuery } = setup(); - - // Act - const result = getPostQuery({ id: 'invalid-id' }); - - // Assert - await expect(result).rejects.toThrow(ValidationException); - }); - }); - - describe('given the post does not exist', () => { - it('should return null', async () => { - // Arrange - const { getPostQuery, postsRepository } = setup(); - postsRepository.getById.mockResolvedValue(null); - - // Act - const result = await getPostQuery({ id: '907b0a0b-9a12-4073-ab27-3ad5927955e9' }); - - // Assert - await expect(result).toEqual(null); - }); - }); - - describe('given the post exists', () => { - it('should return the post', async () => { - // Arrange - const { getPostQuery, postsRepository } = setup(); - - postsRepository.getById.mockResolvedValueOnce( - new Post({ - id: '907b0a0b-9a12-4073-ab27-3ad5927955e9', - createdAt: new Date(2022, 1, 1), - title: 'Mock post', - }), - ); - - // Act - const result = await getPostQuery({ id: '907b0a0b-9a12-4073-ab27-3ad5927955e9' }); - - // Assert - expect(result).toMatchSnapshot(); - }); - }); -}); diff --git a/src/application/posts/queries/get-post/get-post-query-mapper.ts b/src/application/posts/queries/get-post/get-post-query-mapper.ts deleted file mode 100644 index 3caeedf..0000000 --- a/src/application/posts/queries/get-post/get-post-query-mapper.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Post } from '@domain/entities'; - -export function toDto(post: Post) { - return { - id: post.id, - title: post.title, - }; -} diff --git a/src/application/posts/queries/get-post/get-post-query-validator.ts b/src/application/posts/queries/get-post/get-post-query-validator.ts deleted file mode 100644 index b6a2c5a..0000000 --- a/src/application/posts/queries/get-post/get-post-query-validator.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ValidationException } from '@application/common/exceptions'; -import { ZodError, z } from 'zod'; - -import { GetPostQuery } from './get-post-query'; - -export async function validate(query: GetPostQuery) { - try { - const schema: z.ZodType = z.object({ - id: z.string().uuid(), - }); - - await schema.parseAsync(query); - } catch (error) { - throw new ValidationException(error as ZodError); - } -} diff --git a/src/application/posts/queries/get-post/get-post-query.ts b/src/application/posts/queries/get-post/get-post-query.ts deleted file mode 100644 index 94929f2..0000000 --- a/src/application/posts/queries/get-post/get-post-query.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { toDto } from './get-post-query-mapper'; -import { validate } from './get-post-query-validator'; - -export type GetPostQuery = Readonly<{ - id: string; -}>; - -export function makeGetPostQuery({ postsRepository }: Pick) { - return async function getPostQuery({ id }: GetPostQuery) { - await validate({ id }); - - const post = await postsRepository.getById({ id }); - - if (!post) { - return null; - } - - return toDto(post); - }; -} diff --git a/src/application/posts/queries/get-post/index.ts b/src/application/posts/queries/get-post/index.ts deleted file mode 100644 index 47093f4..0000000 --- a/src/application/posts/queries/get-post/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './get-post-query'; diff --git a/src/application/posts/queries/list-posts/__tests__/__snapshots__/list-post-query.test.ts.snap b/src/application/posts/queries/list-posts/__tests__/__snapshots__/list-post-query.test.ts.snap deleted file mode 100644 index a8c8e9d..0000000 --- a/src/application/posts/queries/list-posts/__tests__/__snapshots__/list-post-query.test.ts.snap +++ /dev/null @@ -1,18 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`listPostsQuery given a valid query should list posts 1`] = ` -{ - "count": 11, - "hasNextPage": false, - "hasPreviousPage": true, - "pageNumber": 2, - "pageSize": 10, - "posts": [ - { - "id": "907b0a0b-9a12-4073-ab27-3ad5927955e9", - "title": "Mock post", - }, - ], - "totalPages": 2, -} -`; diff --git a/src/application/posts/queries/list-posts/__tests__/list-post-query.test.ts b/src/application/posts/queries/list-posts/__tests__/list-post-query.test.ts deleted file mode 100644 index da74bd7..0000000 --- a/src/application/posts/queries/list-posts/__tests__/list-post-query.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { ValidationException } from '@application/common/exceptions'; -import { PostsRepository } from '@application/common/interfaces'; -import { Post } from '@domain/entities'; - -import { makeListPostsQuery } from '../list-posts-query'; - -describe('listPostsQuery', () => { - function setup() { - const postsRepository = jest.mocked>({ - list: jest.fn(), - }) as jest.Mocked; - - const listPostQuery = makeListPostsQuery({ postsRepository }); - - return { - listPostQuery, - postsRepository, - }; - } - - describe('given an invalid query', () => { - it('should throw a validation exception', async () => { - // Arrange - const { listPostQuery } = setup(); - - // Act - const result = listPostQuery({ - pageNumber: 1, - pageSize: 1000, // Cannot exceed 50 - }); - - // Assert - await expect(result).rejects.toThrow(ValidationException); - }); - }); - - describe('given a valid query', () => { - it('should list posts', async () => { - // Arrange - const { listPostQuery, postsRepository } = setup(); - - postsRepository.list.mockResolvedValueOnce({ - count: 11, - posts: [ - new Post({ - id: '907b0a0b-9a12-4073-ab27-3ad5927955e9', - createdAt: new Date(2022, 1, 1), - title: 'Mock post', - }), - ], - }); - - // Act - const result = await listPostQuery({ - pageNumber: 2, - pageSize: 10, - }); - - // Assert - expect(result).toMatchSnapshot(); - }); - }); -}); diff --git a/src/application/posts/queries/list-posts/index.ts b/src/application/posts/queries/list-posts/index.ts deleted file mode 100644 index 37d03a2..0000000 --- a/src/application/posts/queries/list-posts/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './list-posts-query'; diff --git a/src/application/posts/queries/list-posts/list-posts-query-mapper.ts b/src/application/posts/queries/list-posts/list-posts-query-mapper.ts deleted file mode 100644 index abc6035..0000000 --- a/src/application/posts/queries/list-posts/list-posts-query-mapper.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Post } from '@domain/entities'; - -export function toDto({ - count, - pageNumber, - pageSize, - posts, -}: { - count: number; - pageNumber: number; - pageSize: number; - posts: Array; -}) { - const totalPages = Math.ceil(count / pageSize); - - return { - count, - hasPreviousPage: pageNumber > 1, - hasNextPage: pageNumber < totalPages, - pageNumber, - pageSize, - posts: posts.map((post) => ({ - id: post.id, - title: post.title, - })), - totalPages, - }; -} diff --git a/src/application/posts/queries/list-posts/list-posts-query-validator.ts b/src/application/posts/queries/list-posts/list-posts-query-validator.ts deleted file mode 100644 index 413f3ad..0000000 --- a/src/application/posts/queries/list-posts/list-posts-query-validator.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ValidationException } from '@application/common/exceptions'; -import { ZodError, z } from 'zod'; - -import { ListPostsQuery } from './list-posts-query'; - -export async function validate(query: ListPostsQuery) { - try { - const schema: z.ZodType = z.object({ - pageNumber: z.number().int().min(1), - pageSize: z.number().int().min(1).max(50), - }); - - await schema.parseAsync(query); - } catch (error) { - throw new ValidationException(error as ZodError); - } -} diff --git a/src/application/posts/queries/list-posts/list-posts-query.ts b/src/application/posts/queries/list-posts/list-posts-query.ts deleted file mode 100644 index 63299b7..0000000 --- a/src/application/posts/queries/list-posts/list-posts-query.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { toDto } from './list-posts-query-mapper'; -import { validate } from './list-posts-query-validator'; - -export type ListPostsQuery = Readonly<{ - pageNumber: number; - pageSize: number; -}>; - -export function makeListPostsQuery({ postsRepository }: Pick) { - return async function listPostsQuery(query: ListPostsQuery) { - await validate(query); - - const { pageNumber, pageSize } = query; - - const { count, posts } = await postsRepository.list({ pageNumber, pageSize }); - - return toDto({ - count, - pageNumber, - pageSize, - posts, - }); - }; -} diff --git a/src/domain/entities/post.ts b/src/domain/entities/post.ts deleted file mode 100644 index ee5172e..0000000 --- a/src/domain/entities/post.ts +++ /dev/null @@ -1,11 +0,0 @@ -export class Post { - public id?: string; - public createdAt: Date; - public title: string; - - constructor(post: { id?: string; createdAt: Date; title: string }) { - this.id = post.id; - this.createdAt = post.createdAt; - this.title = post.title; - } -} diff --git a/src/infrastructure/di.ts b/src/infrastructure/di.ts index 877af8c..acbb684 100644 --- a/src/infrastructure/di.ts +++ b/src/infrastructure/di.ts @@ -10,7 +10,6 @@ export type Dependencies = { config: Interfaces.ApplicationConfig; db: PrismaClient; logger: Interfaces.Logger; - postsRepository: Interfaces.PostsRepository; pagesRepository: Interfaces.PagesRepository; sectionsRepository: Interfaces.SectionsRepository; componentsRepository: Interfaces.ComponentsRepository; @@ -41,7 +40,6 @@ export function makeInfrastructureDependencies(): { config: asValue(config), db: asValue(db), logger: asValue(logger), - postsRepository: asFunction(repositories.makePostsRepository).singleton(), pagesRepository: asFunction(repositories.makePagesRepository).singleton(), sectionsRepository: asFunction(repositories.makeSectionsRepository).singleton(), componentsRepository: asFunction(repositories.makeComponentsRepository).singleton(), diff --git a/src/infrastructure/repositories/posts-repository.ts b/src/infrastructure/repositories/posts-repository.ts deleted file mode 100644 index dc51128..0000000 --- a/src/infrastructure/repositories/posts-repository.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { PostsRepository } from '@application/common/interfaces'; -import { Post } from '@domain/entities'; -import { Post as PostModel } from '@prisma/client'; - -export function makePostsRepository({ db }: Dependencies): PostsRepository { - return { - async create({ post }) { - const { id } = await db.post.create({ - data: { - createdAt: post.createdAt, - title: post.title, - }, - }); - - return { - id, - }; - }, - async delete({ id }) { - await db.post.delete({ where: { id } }); - }, - async getById({ id }) { - const post = await db.post.findFirst({ where: { id } }); - - if (!post) { - return null; - } - - return toEntity(post); - }, - async list({ pageNumber, pageSize }) { - const count = await db.post.count(); - - const posts = await db.post.findMany({ - skip: (pageNumber - 1) * pageSize, - take: pageSize, - }); - - return { - count, - posts: posts.map(toEntity), - }; - }, - }; - - function toEntity(post: PostModel) { - return new Post({ - id: post.id, - createdAt: post.createdAt, - title: post.title, - }); - } -} diff --git a/src/web/routes/feedbacks.ts b/src/web/routes/feedbacks.ts index 2faa1a3..1a586a5 100644 --- a/src/web/routes/feedbacks.ts +++ b/src/web/routes/feedbacks.ts @@ -1,27 +1,28 @@ import { FastifyRequest } from 'fastify'; -import { makeComponentsUseCases } from '@application/components'; +import { makeFeedbacksUseCases } from '@application/feedbacks'; import ResponseBase from '@application/common/response-base'; - -interface ListComponentsByPageQuery { +interface ListFeedbacksByPageQuery { language: string - section_id: string + size?: number + page?: number } export default async function feedbackRoutes(fastify: FastifyRouteInstance) { - const components = makeComponentsUseCases(fastify.diContainer.cradle); + const feedbacks = makeFeedbacksUseCases(fastify.diContainer.cradle); fastify.route({ method: 'POST', - url: '/api/component/get-by-section', + url: '/api/feedback/all', schema: { body: { type: 'object', properties: { language: { type: 'string', description: 'Language code (e.g., vi, en, etc.)' }, - section_id: { type: 'string', description: 'ID of pages database' } + size: { type: 'number', description: 'Size number of records (e.g., 10.)' }, + page: { type: 'number', description: 'Page number of database (e.g., 1.)' } }, - required: ['language', 'section_id'], + required: ['language', 'size', 'page'], }, response: { 200: { @@ -36,13 +37,8 @@ export default async function feedbackRoutes(fastify: FastifyRouteInstance) { id: { type: 'integer' }, title: { type: 'string' }, content: { type: 'string' }, + description: { type: 'string' }, image: { type: 'string' }, - image_2: { type: 'string' }, - image_3: { type: 'string' }, - image_4: { type: 'string' }, - image_5: { type: 'string' }, - image_6: { type: 'string' }, - image_7: { type: 'string' }, order: { type: 'integer' }, status: { type: 'integer' }, created_at: { type: 'string', format: 'date-time' }, @@ -52,37 +48,50 @@ export default async function feedbackRoutes(fastify: FastifyRouteInstance) { }, }, }, - message: { type: 'string', example: 'Components fetched successfully' }, + total: { type: 'integer', }, + per_page: { type: 'integer', }, + current_page: { type: 'integer', }, + last_page: { type: 'integer', }, + message: { type: 'string', example: 'Classes fetched successfully' }, }, }, 400: { $ref: 'ExceptionResponse#' }, }, - tags: ['sections'], + tags: ['classes'], }, async handler( - req: FastifyRequest<{ Body: ListComponentsByPageQuery }>, + req: FastifyRequest<{ Body: ListFeedbacksByPageQuery }>, res ) { try { - const componentsList = await components.queries.listComponentsBySection({ + const { data, total, per_page, current_page, last_page } = await feedbacks.queries.listFeedbacks({ language: req.body.language, - section_id: req.body.section_id + size: req.body.size, + page: req.body.page, }); - const response = ResponseBase.formatBaseResponse( + const response = ResponseBase.formatPaginationResponse( 200, - componentsList, - 'Components fetched successfully', + data, + total ?? 0, + per_page ?? 0, + current_page ?? 0, + last_page ?? 0, + 'Feedbacks fetched successfully', ); res.status(200).send(response); } catch (error) { fastify.log.error(error); - const errorResponse = ResponseBase.formatBaseResponse( + const errorResponse = ResponseBase.formatPaginationResponse( 400, null, - 'Failed to fetch components', + 0, + 0, + 0, + 0, + 'Failed to fetch feedbacks', ); res.status(400).send(errorResponse); diff --git a/src/web/routes/health.ts b/src/web/routes/health.ts deleted file mode 100644 index 26e9354..0000000 --- a/src/web/routes/health.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { FastifyInstance } from 'fastify'; - -export default async function healthRoutes(fastify: FastifyInstance) { - fastify.route({ - method: 'GET', - url: '/health', - schema: { - response: { - 200: { - type: 'object', - properties: { - status: { type: 'string' }, - }, - }, - 500: { $ref: 'ExceptionResponse#' }, - }, - tags: ['health'], - }, - handler() { - return { status: 'ok' }; - }, - }); -} diff --git a/src/web/routes/lecturers.ts b/src/web/routes/lecturers.ts index 40dfc6e..9e660f1 100644 --- a/src/web/routes/lecturers.ts +++ b/src/web/routes/lecturers.ts @@ -1,27 +1,29 @@ import { FastifyRequest } from 'fastify'; -import { makeComponentsUseCases } from '@application/components'; +import { makeLecturersUseCases } from '@application/lecturers'; import ResponseBase from '@application/common/response-base'; -interface ListComponentsByPageQuery { +interface ListLecturersByPageQuery { language: string - section_id: string + size?: number + page?: number } export default async function lecturerRoutes(fastify: FastifyRouteInstance) { - const components = makeComponentsUseCases(fastify.diContainer.cradle); + const lecturers = makeLecturersUseCases(fastify.diContainer.cradle); fastify.route({ method: 'POST', - url: '/api/component/get-by-section', + url: '/api/lecturer/all', schema: { body: { type: 'object', properties: { language: { type: 'string', description: 'Language code (e.g., vi, en, etc.)' }, - section_id: { type: 'string', description: 'ID of pages database' } + size: { type: 'number', description: 'Size number of records (e.g., 10.)' }, + page: { type: 'number', description: 'Page number of database (e.g., 1.)' } }, - required: ['language', 'section_id'], + required: ['language', 'size', 'page'], }, response: { 200: { @@ -36,13 +38,8 @@ export default async function lecturerRoutes(fastify: FastifyRouteInstance) { id: { type: 'integer' }, title: { type: 'string' }, content: { type: 'string' }, + description: { type: 'string' }, image: { type: 'string' }, - image_2: { type: 'string' }, - image_3: { type: 'string' }, - image_4: { type: 'string' }, - image_5: { type: 'string' }, - image_6: { type: 'string' }, - image_7: { type: 'string' }, order: { type: 'integer' }, status: { type: 'integer' }, created_at: { type: 'string', format: 'date-time' }, @@ -52,37 +49,50 @@ export default async function lecturerRoutes(fastify: FastifyRouteInstance) { }, }, }, - message: { type: 'string', example: 'Components fetched successfully' }, + total: { type: 'integer', }, + per_page: { type: 'integer', }, + current_page: { type: 'integer', }, + last_page: { type: 'integer', }, + message: { type: 'string', example: 'Classes fetched successfully' }, }, }, 400: { $ref: 'ExceptionResponse#' }, }, - tags: ['sections'], + tags: ['classes'], }, async handler( - req: FastifyRequest<{ Body: ListComponentsByPageQuery }>, + req: FastifyRequest<{ Body: ListLecturersByPageQuery }>, res ) { try { - const componentsList = await components.queries.listComponentsBySection({ + const { data, total, per_page, current_page, last_page } = await lecturers.queries.listLecturers({ language: req.body.language, - section_id: req.body.section_id + size: req.body.size, + page: req.body.page, }); - const response = ResponseBase.formatBaseResponse( + const response = ResponseBase.formatPaginationResponse( 200, - componentsList, - 'Components fetched successfully', + data, + total ?? 0, + per_page ?? 0, + current_page ?? 0, + last_page ?? 0, + 'Lecturers fetched successfully', ); res.status(200).send(response); } catch (error) { fastify.log.error(error); - const errorResponse = ResponseBase.formatBaseResponse( + const errorResponse = ResponseBase.formatPaginationResponse( 400, null, - 'Failed to fetch components', + 0, + 0, + 0, + 0, + 'Failed to fetch lecturers', ); res.status(400).send(errorResponse); diff --git a/src/web/routes/lectures.ts b/src/web/routes/lectures.ts index 6abee13..0244e5a 100644 --- a/src/web/routes/lectures.ts +++ b/src/web/routes/lectures.ts @@ -1,27 +1,26 @@ import { FastifyRequest } from 'fastify'; -import { makeComponentsUseCases } from '@application/components'; -import ResponseBase from '@application/common/response-base'; +import { makeLecturesUseCases } from '@application/lectures'; +import { GetByLectureTypeQuery } from '@application/lectures/queries/get-by-lecture-type'; +import { GetByIdQuery } from '@application/documents/queries/get-by-id'; -interface ListComponentsByPageQuery { - language: string - section_id: string -} +import ResponseBase from '@application/common/response-base'; export default async function lectureRoutes(fastify: FastifyRouteInstance) { - const components = makeComponentsUseCases(fastify.diContainer.cradle); + const lectures = makeLecturesUseCases(fastify.diContainer.cradle); fastify.route({ method: 'POST', - url: '/api/component/get-by-section', + url: '/api/lecture/get-by-lecture-type', schema: { body: { type: 'object', properties: { language: { type: 'string', description: 'Language code (e.g., vi, en, etc.)' }, - section_id: { type: 'string', description: 'ID of pages database' } + size: { type: 'number', description: 'Size number of records (e.g., 10.)' }, + page: { type: 'number', description: 'Page number of database (e.g., 1.)' } }, - required: ['language', 'section_id'], + required: ['language', 'size', 'page'], }, response: { 200: { @@ -36,13 +35,8 @@ export default async function lectureRoutes(fastify: FastifyRouteInstance) { id: { type: 'integer' }, title: { type: 'string' }, content: { type: 'string' }, + description: { type: 'string' }, image: { type: 'string' }, - image_2: { type: 'string' }, - image_3: { type: 'string' }, - image_4: { type: 'string' }, - image_5: { type: 'string' }, - image_6: { type: 'string' }, - image_7: { type: 'string' }, order: { type: 'integer' }, status: { type: 'integer' }, created_at: { type: 'string', format: 'date-time' }, @@ -52,37 +46,126 @@ export default async function lectureRoutes(fastify: FastifyRouteInstance) { }, }, }, - message: { type: 'string', example: 'Components fetched successfully' }, + total: { type: 'integer', }, + per_page: { type: 'integer', }, + current_page: { type: 'integer', }, + last_page: { type: 'integer', }, + message: { type: 'string', example: 'Classes fetched successfully' }, + }, + }, + 400: { $ref: 'ExceptionResponse#' }, + }, + tags: ['lectures'], + }, + async handler( + req: FastifyRequest<{ Body: GetByLectureTypeQuery }>, + res + ) { + try { + const { data, total, per_page, current_page, last_page } = await lectures.queries.getByLectureType({ + language: req.body.language, + lecture_type_id: req.body.lecture_type_id, + size: req.body.size, + page: req.body.page, + }); + + const response = ResponseBase.formatPaginationResponse( + 200, + data, + total ?? 0, + per_page ?? 0, + current_page ?? 0, + last_page ?? 0, + 'Lectures fetched successfully', + ); + + res.status(200).send(response); + } catch (error) { + fastify.log.error(error); + + const errorResponse = ResponseBase.formatPaginationResponse( + 400, + null, + 0, + 0, + 0, + 0, + 'Failed to fetch lectures', + ); + + res.status(400).send(errorResponse); + } + }, + }); + + fastify.route({ + method: 'POST', + url: '/api/lecture/get-by-id', + schema: { + body: { + type: 'object', + properties: { + language: { type: 'string', description: 'Language code (e.g., vi, en, etc.)' }, + id: { type: 'number', description: 'Id of lecture (e.g., 1.)' } + }, + required: ['language', 'id'], + }, + response: { + 200: { + type: 'object', + properties: { + status_code: { type: 'integer', example: 200 }, + data: { + type: 'object', + properties: { + id: { type: 'integer' }, + title: { type: 'string' }, + content: { type: 'string' }, + description: { type: 'string' }, + image: { type: 'string' }, + order: { type: 'integer' }, + status: { type: 'integer' }, + created_at: { type: 'string', format: 'date-time' }, + updated_at: { type: 'string', format: 'date-time' }, + created_by: { type: 'string' }, + updated_by: { type: 'string' }, + }, + }, + message: { type: 'string', example: 'Class fetched successfully' }, }, }, 400: { $ref: 'ExceptionResponse#' }, }, - tags: ['sections'], + tags: ['lectures'], }, async handler( - req: FastifyRequest<{ Body: ListComponentsByPageQuery }>, + req: FastifyRequest<{ Body: GetByIdQuery }>, res ) { try { - const componentsList = await components.queries.listComponentsBySection({ + const data = await lectures.queries.getById({ language: req.body.language, - section_id: req.body.section_id + id: req.body.id, }); const response = ResponseBase.formatBaseResponse( 200, - componentsList, - 'Components fetched successfully', + data, + 'Lecture fetched successfully', ); res.status(200).send(response); } catch (error) { fastify.log.error(error); - const errorResponse = ResponseBase.formatBaseResponse( + const errorResponse = ResponseBase.formatPaginationResponse( 400, null, - 'Failed to fetch components', + 0, + 0, + 0, + 0, + 'Failed to fetch lecture', ); res.status(400).send(errorResponse); diff --git a/src/web/routes/news-categories.ts b/src/web/routes/news-categories.ts index 94d8d90..5968d8a 100644 --- a/src/web/routes/news-categories.ts +++ b/src/web/routes/news-categories.ts @@ -1,27 +1,23 @@ import { FastifyRequest } from 'fastify'; -import { makeComponentsUseCases } from '@application/components'; -import ResponseBase from '@application/common/response-base'; +import { makeNewsCategoriesUseCases } from '@application/news-categories'; +import { ListNewsCategoriesQuery } from '@application/news-categories/queries/list-news-categories'; -interface ListComponentsByPageQuery { - language: string - section_id: string -} +import ResponseBase from '@application/common/response-base'; export default async function newsCategoryRoutes(fastify: FastifyRouteInstance) { - const components = makeComponentsUseCases(fastify.diContainer.cradle); + const newsCategories = makeNewsCategoriesUseCases(fastify.diContainer.cradle); fastify.route({ method: 'POST', - url: '/api/component/get-by-section', + url: '/api/news-category/all', schema: { body: { type: 'object', properties: { language: { type: 'string', description: 'Language code (e.g., vi, en, etc.)' }, - section_id: { type: 'string', description: 'ID of pages database' } }, - required: ['language', 'section_id'], + required: ['language'], }, response: { 200: { @@ -35,44 +31,30 @@ export default async function newsCategoryRoutes(fastify: FastifyRouteInstance) properties: { id: { type: 'integer' }, title: { type: 'string' }, - content: { type: 'string' }, - image: { type: 'string' }, - image_2: { type: 'string' }, - image_3: { type: 'string' }, - image_4: { type: 'string' }, - image_5: { type: 'string' }, - image_6: { type: 'string' }, - image_7: { type: 'string' }, order: { type: 'integer' }, - status: { type: 'integer' }, - created_at: { type: 'string', format: 'date-time' }, - updated_at: { type: 'string', format: 'date-time' }, - created_by: { type: 'string' }, - updated_by: { type: 'string' }, }, }, }, - message: { type: 'string', example: 'Components fetched successfully' }, + message: { type: 'string', example: 'Class fetched successfully' }, }, }, 400: { $ref: 'ExceptionResponse#' }, }, - tags: ['sections'], + tags: ['new-categories'], }, async handler( - req: FastifyRequest<{ Body: ListComponentsByPageQuery }>, + req: FastifyRequest<{ Body: ListNewsCategoriesQuery }>, res ) { try { - const componentsList = await components.queries.listComponentsBySection({ + const data = await newsCategories.queries.list({ language: req.body.language, - section_id: req.body.section_id }); const response = ResponseBase.formatBaseResponse( 200, - componentsList, - 'Components fetched successfully', + data, + 'News categories fetched successfully', ); res.status(200).send(response); @@ -82,7 +64,7 @@ export default async function newsCategoryRoutes(fastify: FastifyRouteInstance) const errorResponse = ResponseBase.formatBaseResponse( 400, null, - 'Failed to fetch components', + 'Failed to fetch new categories', ); res.status(400).send(errorResponse); diff --git a/src/web/routes/news.ts b/src/web/routes/news.ts index defbc3b..dc777a5 100644 --- a/src/web/routes/news.ts +++ b/src/web/routes/news.ts @@ -1,27 +1,28 @@ import { FastifyRequest } from 'fastify'; -import { makeComponentsUseCases } from '@application/components'; +import { makeNewsUseCases } from '@application/news'; import ResponseBase from '@application/common/response-base'; - -interface ListComponentsByPageQuery { - language: string - section_id: string -} +import { GetRelatedQuery } from '@application/news/queries/get-related'; +import { GetByCategoryQuery } from '@application/news/queries/get-by-category'; +import { GetBySlugQuery } from '@application/news/queries/get-by-slug'; export default async function newsRoutes(fastify: FastifyRouteInstance) { - const components = makeComponentsUseCases(fastify.diContainer.cradle); + const news = makeNewsUseCases(fastify.diContainer.cradle); fastify.route({ method: 'POST', - url: '/api/component/get-by-section', + url: '/api/news/get-related', schema: { body: { type: 'object', properties: { language: { type: 'string', description: 'Language code (e.g., vi, en, etc.)' }, - section_id: { type: 'string', description: 'ID of pages database' } + id: { type: 'number', description: 'Id of records (e.g., 10.)' }, + news_category_id: { type: 'number', description: 'News category id of records (e.g., 10.)' }, + size: { type: 'number', description: 'Size number of records (e.g., 10.)' }, + page: { type: 'number', description: 'Page number of database (e.g., 1.)' } }, - required: ['language', 'section_id'], + required: ['language', 'news_category_id', 'id', 'size', 'page'], }, response: { 200: { @@ -36,13 +37,8 @@ export default async function newsRoutes(fastify: FastifyRouteInstance) { id: { type: 'integer' }, title: { type: 'string' }, content: { type: 'string' }, + description: { type: 'string' }, image: { type: 'string' }, - image_2: { type: 'string' }, - image_3: { type: 'string' }, - image_4: { type: 'string' }, - image_5: { type: 'string' }, - image_6: { type: 'string' }, - image_7: { type: 'string' }, order: { type: 'integer' }, status: { type: 'integer' }, created_at: { type: 'string', format: 'date-time' }, @@ -52,27 +48,203 @@ export default async function newsRoutes(fastify: FastifyRouteInstance) { }, }, }, - message: { type: 'string', example: 'Components fetched successfully' }, + total: { type: 'integer', }, + per_page: { type: 'integer', }, + current_page: { type: 'integer', }, + last_page: { type: 'integer', }, + message: { type: 'string', example: 'Classes fetched successfully' }, + }, + }, + 400: { $ref: 'ExceptionResponse#' }, + }, + tags: ['classes'], + }, + async handler( + req: FastifyRequest<{ Body: GetRelatedQuery }>, + res + ) { + try { + const { data, total, per_page, current_page, last_page } = await news.queries.getRelated({ + language: req.body.language, + news_category_id: req.body.news_category_id, + id: req.body.id, + size: req.body.size, + page: req.body.page, + }); + + const response = ResponseBase.formatPaginationResponse( + 200, + data, + total ?? 0, + per_page ?? 0, + current_page ?? 0, + last_page ?? 0, + 'News related fetched successfully', + ); + + res.status(200).send(response); + } catch (error) { + fastify.log.error(error); + + const errorResponse = ResponseBase.formatPaginationResponse( + 400, + null, + 0, + 0, + 0, + 0, + 'Failed to fetch news related', + ); + + res.status(400).send(errorResponse); + } + }, + }); + + fastify.route({ + method: 'POST', + url: '/api/news/get-by-category', + schema: { + body: { + type: 'object', + properties: { + language: { type: 'string', description: 'Language code (e.g., vi, en, etc.)' }, + news_category_id: { type: 'number', description: 'News category id of records (e.g., 10.)' }, + size: { type: 'number', description: 'Size number of records (e.g., 10.)' }, + page: { type: 'number', description: 'Page number of database (e.g., 1.)' } + }, + required: ['language', 'news_category_id', 'size', 'page'], + }, + response: { + 200: { + type: 'object', + properties: { + status_code: { type: 'integer', example: 200 }, + data: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'integer' }, + title: { type: 'string' }, + content: { type: 'string' }, + description: { type: 'string' }, + image: { type: 'string' }, + order: { type: 'integer' }, + status: { type: 'integer' }, + created_at: { type: 'string', format: 'date-time' }, + updated_at: { type: 'string', format: 'date-time' }, + created_by: { type: 'string' }, + updated_by: { type: 'string' }, + }, + }, + }, + total: { type: 'integer', }, + per_page: { type: 'integer', }, + current_page: { type: 'integer', }, + last_page: { type: 'integer', }, + message: { type: 'string', example: 'Classes fetched successfully' }, + }, + }, + 400: { $ref: 'ExceptionResponse#' }, + }, + tags: ['classes'], + }, + async handler( + req: FastifyRequest<{ Body: GetByCategoryQuery }>, + res + ) { + try { + const { data, total, per_page, current_page, last_page } = await news.queries.getByCategory({ + language: req.body.language, + news_category_id: req.body.news_category_id, + size: req.body.size, + page: req.body.page, + }); + + const response = ResponseBase.formatPaginationResponse( + 200, + data, + total ?? 0, + per_page ?? 0, + current_page ?? 0, + last_page ?? 0, + 'News by category fetched successfully', + ); + + res.status(200).send(response); + } catch (error) { + fastify.log.error(error); + + const errorResponse = ResponseBase.formatPaginationResponse( + 400, + null, + 0, + 0, + 0, + 0, + 'Failed to fetch news by category', + ); + + res.status(400).send(errorResponse); + } + }, + }); + + fastify.route({ + method: 'POST', + url: '/api/class/get-by-slug', + schema: { + body: { + type: 'object', + properties: { + language: { type: 'string', description: 'Language code (e.g., vi, en, etc.)' }, + slug: { type: 'string', description: 'Slug of news (e.g., abc-xyz)' }, + }, + required: ['language', 'slug'], + }, + response: { + 200: { + type: 'object', + properties: { + status_code: { type: 'integer', example: 200 }, + data: { + type: 'object', + properties: { + id: { type: 'integer' }, + title: { type: 'string' }, + content: { type: 'string' }, + description: { type: 'string' }, + image: { type: 'string' }, + order: { type: 'integer' }, + status: { type: 'integer' }, + created_at: { type: 'string', format: 'date-time' }, + updated_at: { type: 'string', format: 'date-time' }, + created_by: { type: 'string' }, + updated_by: { type: 'string' }, + }, + }, + message: { type: 'string', example: 'Class fetched successfully' }, }, }, 400: { $ref: 'ExceptionResponse#' }, }, - tags: ['sections'], + tags: ['classes'], }, async handler( - req: FastifyRequest<{ Body: ListComponentsByPageQuery }>, + req: FastifyRequest<{ Body: GetBySlugQuery }>, res ) { try { - const componentsList = await components.queries.listComponentsBySection({ + const data = await news.queries.getBySlug({ language: req.body.language, - section_id: req.body.section_id + slug: req.body.slug }); const response = ResponseBase.formatBaseResponse( 200, - componentsList, - 'Components fetched successfully', + data, + 'News get by slug successfully', ); res.status(200).send(response); @@ -82,7 +254,7 @@ export default async function newsRoutes(fastify: FastifyRouteInstance) { const errorResponse = ResponseBase.formatBaseResponse( 400, null, - 'Failed to fetch components', + 'Failed to fetch news get by slug', ); res.status(400).send(errorResponse); diff --git a/src/web/routes/posts.ts b/src/web/routes/posts.ts deleted file mode 100644 index 40815be..0000000 --- a/src/web/routes/posts.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { makePostsUseCases } from '@application/posts'; - -export default async function postRoutes(fastify: FastifyRouteInstance) { - const posts = makePostsUseCases(fastify.diContainer.cradle); - - fastify.route({ - method: 'POST', - url: '/api/v1/posts', - schema: { - body: { - type: 'object', - properties: { - title: { type: 'string' }, - }, - required: ['title'], - }, - response: { - 201: { - type: 'object', - properties: { - id: { type: 'string' }, - }, - }, - }, - 400: { $ref: 'ExceptionResponse#' }, - tags: ['posts'], - }, - async handler(req, res) { - const post = await posts.commands.createPost(req.body); - - res.status(201).send(post); - }, - }); - - fastify.route({ - method: 'DELETE', - url: '/api/v1/posts/:id', - schema: { - params: { - type: 'object', - properties: { - id: { type: 'string' }, - }, - required: ['id'], - }, - response: { - 200: {}, - 400: { $ref: 'ExceptionResponse#' }, - }, - tags: ['posts'], - }, - async handler(req, res) { - await posts.commands.deletePost({ id: req.params.id }); - - res.status(200).send({}); - }, - }); - - fastify.route({ - method: 'GET', - url: '/api/v1/posts/:id', - schema: { - params: { - type: 'object', - properties: { - id: { type: 'string' }, - }, - required: ['id'], - }, - response: { - 200: { - type: 'object', - properties: { - id: { type: 'string' }, - title: { type: 'string' }, - }, - }, - 400: { $ref: 'ExceptionResponse#' }, - 404: { - type: 'object', - properties: { - message: { type: 'string' }, - }, - }, - }, - tags: ['posts'], - }, - async handler(req, res) { - const post = await posts.queries.getPost({ id: req.params.id }); - - if (!post) { - res.status(404).send({ message: `Post with id ${req.params.id} not found` }); - return; - } - - res.status(200).send(post); - }, - }); - - fastify.route({ - method: 'GET', - url: '/api/v1/posts', - schema: { - querystring: { - type: 'object', - properties: { - pageNumber: { type: 'integer' }, - pageSize: { type: 'integer' }, - }, - required: ['pageNumber', 'pageSize'], - }, - response: { - 200: { - type: 'object', - properties: { - count: { type: 'integer' }, - hasPreviousPage: { type: 'boolean' }, - hasNextPage: { type: 'boolean' }, - pageNumber: { type: 'integer' }, - pageSize: { type: 'integer' }, - posts: { - type: 'array', - items: { - type: 'object', - properties: { - id: { type: 'string' }, - title: { type: 'string' }, - }, - }, - }, - totalPages: { type: 'integer' }, - }, - }, - 400: { $ref: 'ExceptionResponse#' }, - }, - tags: ['posts'], - }, - async handler(req, res) { - const postList = await posts.queries.listPosts({ - pageNumber: req.query.pageNumber, - pageSize: req.query.pageSize, - }); - - res.status(200).send(postList); - }, - }); -}