feat: implement category management API with CRUD operations for categories.
This commit is contained in:
parent
1caeac6226
commit
4b335b6b49
5 changed files with 220 additions and 2 deletions
45
Backend/src/controllers/CategoriesController.ts
Normal file
45
Backend/src/controllers/CategoriesController.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { Get, Body, Post, Route, Tags, SuccessResponse, Response, Delete, Controller, Security, Request, Put, Path } from 'tsoa';
|
||||||
|
import { ValidationError } from '../middleware/errorHandler';
|
||||||
|
import { CategoryService } from '../services/categories.service';
|
||||||
|
import { createCategory, createCategoryResponse, deleteCategoryResponse, updateCategory, updateCategoryResponse, listCategoriesResponse } from '../types/categories.type';
|
||||||
|
|
||||||
|
@Route('api/admin/categories')
|
||||||
|
@Tags('Admin/Categories')
|
||||||
|
export class CategoriesController extends Controller {
|
||||||
|
private categoryService = new CategoryService();
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@Security('jwt', ['admin'])
|
||||||
|
@SuccessResponse('200', 'Categories fetched successfully')
|
||||||
|
@Response('401', 'Invalid or expired token')
|
||||||
|
public async listCategories(): Promise<listCategoriesResponse> {
|
||||||
|
return await this.categoryService.listCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@Security('jwt', ['admin'])
|
||||||
|
@SuccessResponse('200', 'Category created successfully')
|
||||||
|
@Response('401', 'Invalid or expired token')
|
||||||
|
public async createCategory(@Request() request: any, @Body() body: createCategory): Promise<createCategoryResponse> {
|
||||||
|
const token = request.headers.authorization?.replace('Bearer ', '') || '';
|
||||||
|
return await this.categoryService.createCategory(token, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('{id}')
|
||||||
|
@Security('jwt', ['admin'])
|
||||||
|
@SuccessResponse('200', 'Category updated successfully')
|
||||||
|
@Response('401', 'Invalid or expired token')
|
||||||
|
public async updateCategory(@Request() request: any, @Body() body: updateCategory): Promise<updateCategoryResponse> {
|
||||||
|
const token = request.headers.authorization?.replace('Bearer ', '') || '';
|
||||||
|
return await this.categoryService.updateCategory(token, body.id, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('{id}')
|
||||||
|
@Security('jwt', ['admin'])
|
||||||
|
@SuccessResponse('200', 'Category deleted successfully')
|
||||||
|
@Response('401', 'Invalid or expired token')
|
||||||
|
public async deleteCategory(@Request() request: any, @Path() id: number): Promise<deleteCategoryResponse> {
|
||||||
|
const token = request.headers.authorization?.replace('Bearer ', '') || '';
|
||||||
|
return await this.categoryService.deleteCategory(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { Get, Body, Post, Route, Tags, SuccessResponse, Response, Example, Controller, Security, Request, Put } from 'tsoa';
|
import { Get, Body, Post, Route, Tags, SuccessResponse, Response, Example, Controller, Security, Request, Put } from 'tsoa';
|
||||||
|
import { ValidationError } from '../middleware/errorHandler';
|
||||||
import { UserService } from '../services/user.service';
|
import { UserService } from '../services/user.service';
|
||||||
import {
|
import {
|
||||||
UserResponse,
|
UserResponse,
|
||||||
|
|
@ -10,7 +11,6 @@ import {
|
||||||
} from '../types/user.types';
|
} from '../types/user.types';
|
||||||
import { ChangePassword } from '../types/auth.types';
|
import { ChangePassword } from '../types/auth.types';
|
||||||
import { profileUpdateSchema, changePasswordSchema } from "../validators/user.validator";
|
import { profileUpdateSchema, changePasswordSchema } from "../validators/user.validator";
|
||||||
import { ValidationError } from '../middleware/errorHandler';
|
|
||||||
|
|
||||||
@Route('api/user')
|
@Route('api/user')
|
||||||
@Tags('User')
|
@Tags('User')
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { ListUsersResponse, GetUserResponse, ActivateAccountResponse, Deactivate
|
||||||
import { getUserByIdValidator, updateUserRoleValidator } from '../validators/usermanagement.validator';
|
import { getUserByIdValidator, updateUserRoleValidator } from '../validators/usermanagement.validator';
|
||||||
|
|
||||||
@Route('api/admin/usermanagement')
|
@Route('api/admin/usermanagement')
|
||||||
@Tags('UserManagement')
|
@Tags('Admin/UserManagement')
|
||||||
export class UserManagementController {
|
export class UserManagementController {
|
||||||
|
|
||||||
private userManagementService = new UserManagementService();
|
private userManagementService = new UserManagementService();
|
||||||
|
|
|
||||||
84
Backend/src/services/categories.service.ts
Normal file
84
Backend/src/services/categories.service.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { prisma } from '../config/database';
|
||||||
|
import { Prisma } from '@prisma/client';
|
||||||
|
import { config } from '../config';
|
||||||
|
import { logger } from '../config/logger';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import { createCategory, createCategoryResponse, deleteCategoryResponse, updateCategory, updateCategoryResponse, listCategoriesResponse, Category } from '../types/categories.type';
|
||||||
|
import { UnauthorizedError, ValidationError, ForbiddenError } from '../middleware/errorHandler';
|
||||||
|
|
||||||
|
export class CategoryService {
|
||||||
|
async listCategories(): Promise<listCategoriesResponse> {
|
||||||
|
try {
|
||||||
|
const categories = await prisma.category.findMany();
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'Categories fetched successfully',
|
||||||
|
data: {
|
||||||
|
categories: categories as unknown as Category[],
|
||||||
|
total: categories.length
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to list categories', { error });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createCategory(token: string, category: createCategory): Promise<createCategoryResponse> {
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(token, config.jwt.secret) as { id: number; username: string; email: string; roleCode: string };
|
||||||
|
const newCategory = await prisma.category.create({
|
||||||
|
data: category
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'Category created successfully',
|
||||||
|
data: {
|
||||||
|
id: newCategory.id,
|
||||||
|
name: newCategory.name as { th: string; en: string },
|
||||||
|
slug: newCategory.slug,
|
||||||
|
description: newCategory.description as { th: string; en: string },
|
||||||
|
created_by: decoded.id,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to create category', { error });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateCategory(token: string, id: number, category: updateCategory): Promise<updateCategoryResponse> {
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(token, config.jwt.secret) as { id: number; username: string; email: string; roleCode: string };
|
||||||
|
const updatedCategory = await prisma.category.update({
|
||||||
|
where: { id },
|
||||||
|
data: category
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
id: updatedCategory.id,
|
||||||
|
name: updatedCategory.name as { th: string; en: string },
|
||||||
|
slug: updatedCategory.slug,
|
||||||
|
description: updatedCategory.description as { th: string; en: string },
|
||||||
|
updated_by: decoded.id,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to update category', { error });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteCategory(id: number): Promise<deleteCategoryResponse> {
|
||||||
|
try {
|
||||||
|
const deletedCategory = await prisma.category.delete({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'Category deleted successfully',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to delete category', { error });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
89
Backend/src/types/categories.type.ts
Normal file
89
Backend/src/types/categories.type.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
export interface Category {
|
||||||
|
id: number;
|
||||||
|
name: {
|
||||||
|
th: string;
|
||||||
|
en: string;
|
||||||
|
};
|
||||||
|
slug: string;
|
||||||
|
description: {
|
||||||
|
th: string;
|
||||||
|
en: string;
|
||||||
|
};
|
||||||
|
icon: string | null;
|
||||||
|
sort_order: number;
|
||||||
|
is_active: boolean;
|
||||||
|
created_at: Date;
|
||||||
|
updated_at: Date | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface listCategoriesResponse {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data: {
|
||||||
|
categories: Category[];
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface createCategory {
|
||||||
|
name: {
|
||||||
|
th: string;
|
||||||
|
en: string;
|
||||||
|
};
|
||||||
|
slug: string;
|
||||||
|
description: {
|
||||||
|
th: string;
|
||||||
|
en: string;
|
||||||
|
};
|
||||||
|
created_by: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface createCategoryResponse {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data: {
|
||||||
|
id: number;
|
||||||
|
name: {
|
||||||
|
en: string;
|
||||||
|
th: string;
|
||||||
|
};
|
||||||
|
slug: string;
|
||||||
|
description: {
|
||||||
|
en: string;
|
||||||
|
th: string;
|
||||||
|
};
|
||||||
|
created_by: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface updateCategory {
|
||||||
|
id: number;
|
||||||
|
name: {
|
||||||
|
th: string;
|
||||||
|
en: string;
|
||||||
|
};
|
||||||
|
slug: string;
|
||||||
|
description: {
|
||||||
|
th: string;
|
||||||
|
en: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface updateCategoryResponse {
|
||||||
|
id: number;
|
||||||
|
name: {
|
||||||
|
th: string;
|
||||||
|
en: string;
|
||||||
|
};
|
||||||
|
slug: string;
|
||||||
|
description: {
|
||||||
|
th: string;
|
||||||
|
en: string;
|
||||||
|
};
|
||||||
|
updated_by: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface deleteCategoryResponse {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue