add testimony api
This commit is contained in:
@@ -38,6 +38,8 @@ import { PartnerMapper } from "@/modules/partner/partner.mapper";
|
||||
import { StaticController } from "@/modules/static/static.controller";
|
||||
import { TagController } from "@/modules/tag/tag.controller";
|
||||
import { TagMapper } from "@/modules/tag/tag.mapper";
|
||||
import { TestimonyController } from "@/modules/testimony/testimony.controller";
|
||||
import { TestimonyMapper } from "@/modules/testimony/testimony.mapper";
|
||||
import { TransportationClassController } from "@/modules/transportation-class/transportation-class.controller";
|
||||
import { TransportationClassMapper } from "@/modules/transportation-class/transportation-class.mapper";
|
||||
import { TransportationController } from "@/modules/transportation/transportation.controller";
|
||||
@@ -100,6 +102,7 @@ export class Application {
|
||||
);
|
||||
const adminMapper = new AdminMapper();
|
||||
const orderMapper = new OrderMapper(packageMapper, partnerMapper);
|
||||
const testimonyMapper = new TestimonyMapper();
|
||||
const tagMapper = new TagMapper();
|
||||
const articleMapper = new ArticleMapper(tagMapper);
|
||||
|
||||
@@ -170,6 +173,10 @@ export class Application {
|
||||
const whatsAppRouter = new WhatsAppController(
|
||||
this._whatsAppService,
|
||||
).buildRouter();
|
||||
const testimonyRouter = new TestimonyController(
|
||||
testimonyMapper,
|
||||
this._jwtService,
|
||||
).buildRouter();
|
||||
const tagRouter = new TagController(
|
||||
tagMapper,
|
||||
this._jwtService,
|
||||
@@ -196,6 +203,7 @@ export class Application {
|
||||
this._app.use("/orders", orderRouter);
|
||||
this._app.use("/statics", staticRouter);
|
||||
this._app.use("/whatsapp", whatsAppRouter);
|
||||
this._app.use("/testimonies", testimonyRouter);
|
||||
this._app.use("/tags", tagRouter);
|
||||
this._app.use("/articles", articleRouter);
|
||||
}
|
||||
|
||||
29
src/database/entities/testimony.entity.ts
Normal file
29
src/database/entities/testimony.entity.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Entity, PrimaryKey, Property } from "@mikro-orm/core";
|
||||
|
||||
@Entity()
|
||||
export class Testimony {
|
||||
@PrimaryKey({ type: "varchar", length: 30 })
|
||||
id!: string;
|
||||
|
||||
@Property({ type: "varchar", length: 200 })
|
||||
review!: string;
|
||||
|
||||
@Property({ type: "varchar", length: 100 })
|
||||
reviewer!: string;
|
||||
|
||||
@Property({ type: "int" })
|
||||
rating!: number;
|
||||
|
||||
@Property({
|
||||
type: "timestamp",
|
||||
onCreate: () => new Date(),
|
||||
})
|
||||
createdAt!: Date;
|
||||
|
||||
@Property({
|
||||
type: "timestamp",
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
})
|
||||
updatedAt!: Date;
|
||||
}
|
||||
@@ -55,6 +55,10 @@ export enum AdminPermission {
|
||||
createPartner = "partner:create",
|
||||
updatePartner = "partner:update",
|
||||
deletePartner = "partner:delete",
|
||||
// Testimony permissions
|
||||
createTestimony = "testimony:create",
|
||||
updateTestimony = "testimony:update",
|
||||
deleteTestimony = "testimony:delete",
|
||||
// Tag permissions
|
||||
createTag = "tag:create",
|
||||
updateTag = "tag:update",
|
||||
|
||||
210
src/modules/testimony/testimony.controller.ts
Normal file
210
src/modules/testimony/testimony.controller.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
import { Controller } from "@/common/controller";
|
||||
import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware";
|
||||
import { isAdminMiddleware } from "@/common/middlewares/is-admin.middleware";
|
||||
import { paginationQuerySchema } from "@/common/schemas";
|
||||
import type { AbstractJwtService } from "@/common/services/jwt-service/abstract.jwt-service";
|
||||
import type {
|
||||
ErrorResponse,
|
||||
ListResponse,
|
||||
SingleResponse,
|
||||
} from "@/common/types";
|
||||
import { Testimony } from "@/database/entities/testimony.entity";
|
||||
import { AdminPermission } from "@/database/enums/admin-permission.enum";
|
||||
import { orm } from "@/database/orm";
|
||||
import type { TestimonyMapper } from "@/modules/testimony/testimony.mapper";
|
||||
import {
|
||||
testimonyParamsSchema,
|
||||
testimonyRequestSchema,
|
||||
} from "@/modules/testimony/testimony.schemas";
|
||||
import type { TestimonyResponse } from "@/modules/testimony/testimony.types";
|
||||
import { Router, type Request, type Response } from "express";
|
||||
import { ulid } from "ulid";
|
||||
|
||||
export class TestimonyController extends Controller {
|
||||
public constructor(
|
||||
private readonly mapper: TestimonyMapper,
|
||||
private readonly jwtService: AbstractJwtService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async create(req: Request, res: Response) {
|
||||
const parseBodyResult = testimonyRequestSchema.safeParse(req.body);
|
||||
if (!parseBodyResult.success) {
|
||||
return this.handleZodError(parseBodyResult.error, res, "body");
|
||||
}
|
||||
const body = parseBodyResult.data;
|
||||
|
||||
const testimony = orm.em.create(Testimony, {
|
||||
id: ulid(),
|
||||
review: body.review,
|
||||
reviewer: body.reviewer,
|
||||
rating: body.rating,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
await orm.em.flush();
|
||||
|
||||
return res.status(201).json({
|
||||
data: this.mapper.mapEntityToResponse(testimony),
|
||||
errors: null,
|
||||
} satisfies SingleResponse<TestimonyResponse>);
|
||||
}
|
||||
|
||||
async list(req: Request, res: Response) {
|
||||
const parseQueryResult = paginationQuerySchema.safeParse(req.query);
|
||||
if (!parseQueryResult.success) {
|
||||
return this.handleZodError(parseQueryResult.error, res, "query");
|
||||
}
|
||||
const query = parseQueryResult.data;
|
||||
|
||||
const count = await orm.em.count(Testimony);
|
||||
|
||||
const testimonies = await orm.em.find(
|
||||
Testimony,
|
||||
{},
|
||||
{
|
||||
limit: query.per_page,
|
||||
offset: (query.page - 1) * query.per_page,
|
||||
orderBy: { createdAt: "DESC" },
|
||||
},
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
data: testimonies.map(this.mapper.mapEntityToResponse.bind(this.mapper)),
|
||||
errors: null,
|
||||
meta: {
|
||||
page: query.page,
|
||||
per_page: query.per_page,
|
||||
total_pages: Math.ceil(count / query.per_page),
|
||||
total_items: count,
|
||||
},
|
||||
} satisfies ListResponse<TestimonyResponse>);
|
||||
}
|
||||
|
||||
async view(req: Request, res: Response) {
|
||||
const parseParamsResult = testimonyParamsSchema.safeParse(req.params);
|
||||
if (!parseParamsResult.success) {
|
||||
return this.handleZodError(parseParamsResult.error, res, "params");
|
||||
}
|
||||
const params = parseParamsResult.data;
|
||||
|
||||
const testimony = await orm.em.findOne(Testimony, { id: params.id });
|
||||
if (!testimony) {
|
||||
return res.status(404).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
path: "id",
|
||||
location: "params",
|
||||
message: "Testimony not found.",
|
||||
},
|
||||
],
|
||||
} satisfies ErrorResponse);
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
data: this.mapper.mapEntityToResponse(testimony),
|
||||
errors: null,
|
||||
} satisfies SingleResponse<TestimonyResponse>);
|
||||
}
|
||||
|
||||
async update(req: Request, res: Response) {
|
||||
const parseParamsResult = testimonyParamsSchema.safeParse(req.params);
|
||||
if (!parseParamsResult.success) {
|
||||
return this.handleZodError(parseParamsResult.error, res, "params");
|
||||
}
|
||||
const params = parseParamsResult.data;
|
||||
|
||||
const parseBodyResult = testimonyRequestSchema.safeParse(req.body);
|
||||
if (!parseBodyResult.success) {
|
||||
return this.handleZodError(parseBodyResult.error, res, "body");
|
||||
}
|
||||
const body = parseBodyResult.data;
|
||||
|
||||
const testimony = await orm.em.findOne(Testimony, { id: params.id });
|
||||
if (!testimony) {
|
||||
return res.status(404).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
path: "id",
|
||||
location: "params",
|
||||
message: "Testimony not found.",
|
||||
},
|
||||
],
|
||||
} satisfies ErrorResponse);
|
||||
}
|
||||
|
||||
testimony.review = body.review;
|
||||
testimony.reviewer = body.reviewer;
|
||||
testimony.rating = body.rating;
|
||||
testimony.updatedAt = new Date();
|
||||
|
||||
await orm.em.flush();
|
||||
|
||||
return res.status(200).json({
|
||||
data: this.mapper.mapEntityToResponse(testimony),
|
||||
errors: null,
|
||||
} satisfies SingleResponse<TestimonyResponse>);
|
||||
}
|
||||
|
||||
async delete(req: Request, res: Response) {
|
||||
const parseParamsResult = testimonyParamsSchema.safeParse(req.params);
|
||||
if (!parseParamsResult.success) {
|
||||
return this.handleZodError(parseParamsResult.error, res, "params");
|
||||
}
|
||||
const params = parseParamsResult.data;
|
||||
|
||||
const testimony = await orm.em.findOne(
|
||||
Testimony,
|
||||
{ id: params.id },
|
||||
{
|
||||
populate: ["*"],
|
||||
},
|
||||
);
|
||||
if (!testimony) {
|
||||
return res.status(404).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
path: "id",
|
||||
location: "params",
|
||||
message: "Testimony not found.",
|
||||
},
|
||||
],
|
||||
} satisfies ErrorResponse);
|
||||
}
|
||||
|
||||
await orm.em.removeAndFlush(testimony);
|
||||
|
||||
return res.status(204).send();
|
||||
}
|
||||
|
||||
public buildRouter(): Router {
|
||||
const router = Router();
|
||||
router.post(
|
||||
"/",
|
||||
createOrmContextMiddleware,
|
||||
isAdminMiddleware(this.jwtService, [AdminPermission.createTestimony]),
|
||||
this.create.bind(this),
|
||||
);
|
||||
router.get("/", createOrmContextMiddleware, this.list.bind(this));
|
||||
router.get("/:id", createOrmContextMiddleware, this.view.bind(this));
|
||||
router.put(
|
||||
"/:id",
|
||||
createOrmContextMiddleware,
|
||||
isAdminMiddleware(this.jwtService, [AdminPermission.updateTestimony]),
|
||||
this.update.bind(this),
|
||||
);
|
||||
router.delete(
|
||||
"/:id",
|
||||
createOrmContextMiddleware,
|
||||
isAdminMiddleware(this.jwtService, [AdminPermission.deleteTestimony]),
|
||||
this.delete.bind(this),
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
}
|
||||
15
src/modules/testimony/testimony.mapper.ts
Normal file
15
src/modules/testimony/testimony.mapper.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { Testimony } from "@/database/entities/testimony.entity";
|
||||
import type { TestimonyResponse } from "@/modules/testimony/testimony.types";
|
||||
|
||||
export class TestimonyMapper {
|
||||
public mapEntityToResponse(testimony: Testimony): TestimonyResponse {
|
||||
return {
|
||||
id: testimony.id,
|
||||
review: testimony.review,
|
||||
reviewer: testimony.reviewer,
|
||||
rating: testimony.rating,
|
||||
created_at: testimony.createdAt,
|
||||
updated_at: testimony.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
24
src/modules/testimony/testimony.schemas.ts
Normal file
24
src/modules/testimony/testimony.schemas.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import z from "zod";
|
||||
|
||||
export const testimonyRequestSchema = z.object({
|
||||
review: z
|
||||
.string("Must be string.")
|
||||
.nonempty("Must not empty.")
|
||||
.max(200, "Max 200 characters."),
|
||||
reviewer: z
|
||||
.string("Must be string.")
|
||||
.nonempty("Must not empty.")
|
||||
.max(100, "Max 100 characters."),
|
||||
rating: z
|
||||
.number("Must be number.")
|
||||
.int("Must be integer.")
|
||||
.min(1, "Min 1.")
|
||||
.max(5, "Max 5."),
|
||||
});
|
||||
|
||||
export const testimonyParamsSchema = z.object({
|
||||
id: z
|
||||
.ulid("Must be ulid string.")
|
||||
.nonempty("Must not empty.")
|
||||
.max(30, "Max 30 characters."),
|
||||
});
|
||||
18
src/modules/testimony/testimony.types.ts
Normal file
18
src/modules/testimony/testimony.types.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type {
|
||||
testimonyParamsSchema,
|
||||
testimonyRequestSchema,
|
||||
} from "@/modules/testimony/testimony.schemas";
|
||||
import z from "zod";
|
||||
|
||||
export type TestimonyRequest = z.infer<typeof testimonyRequestSchema>;
|
||||
|
||||
export type TestimonyParams = z.infer<typeof testimonyParamsSchema>;
|
||||
|
||||
export type TestimonyResponse = {
|
||||
id: string;
|
||||
review: string;
|
||||
reviewer: string;
|
||||
rating: number;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
};
|
||||
Reference in New Issue
Block a user