add auth and payment api
This commit is contained in:
627
src/modules/partner/partner.controller.ts
Normal file
627
src/modules/partner/partner.controller.ts
Normal file
@@ -0,0 +1,627 @@
|
||||
import { Controller } from "@/common/controller";
|
||||
import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware";
|
||||
import { isAdminMiddleware } from "@/common/middlewares/is-admin.middleware";
|
||||
import {
|
||||
isPartnerMiddleware,
|
||||
type PartnerRequestPlugin,
|
||||
} from "@/common/middlewares/is-partner.middleware";
|
||||
import { paginationQuerySchema } from "@/common/schemas";
|
||||
import type { AbstractEmailService } from "@/common/services/email-service/abstract.email-service";
|
||||
import type {
|
||||
AbstractFileStorage,
|
||||
FileResult,
|
||||
} from "@/common/services/file-storage/abstract.file-storage";
|
||||
import {
|
||||
JwtType,
|
||||
type AbstractJwtService,
|
||||
} from "@/common/services/jwt-service/abstract.jwt-service";
|
||||
import type {
|
||||
ErrorResponse,
|
||||
ListResponse,
|
||||
SingleResponse,
|
||||
} from "@/common/types";
|
||||
import { generateRandomCode } from "@/common/utils";
|
||||
import { Partner } from "@/database/entities/partner.entity";
|
||||
import { Verification } from "@/database/entities/verification.entity";
|
||||
import { AdminPermission } from "@/database/enums/admin-permission.enum";
|
||||
import { VerificationType } from "@/database/enums/verification-type.enum";
|
||||
import { orm } from "@/database/orm";
|
||||
import type { PartnerMapper } from "@/modules/partner/partner.mapper";
|
||||
import {
|
||||
partnerChangeEmailRequestSchema,
|
||||
partnerChangePasswordRequestSchema,
|
||||
partnerParamsSchema,
|
||||
partnerRequestSchema,
|
||||
partnerUpdateRequestSchema,
|
||||
partnerVerifyRequestSchema,
|
||||
} from "@/modules/partner/partner.schemas";
|
||||
import type { PartnerResponse } from "@/modules/partner/partner.types";
|
||||
import * as dateFns from "date-fns";
|
||||
import { Router, type Request, type Response } from "express";
|
||||
import { ulid } from "ulid";
|
||||
|
||||
export class PartnerController extends Controller {
|
||||
public constructor(
|
||||
private readonly mapper: PartnerMapper,
|
||||
private readonly fileStorage: AbstractFileStorage,
|
||||
private readonly emailService: AbstractEmailService,
|
||||
private readonly jwtService: AbstractJwtService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async create(req: Request, res: Response) {
|
||||
const parseBodyResult = partnerRequestSchema.safeParse(req.body);
|
||||
if (!parseBodyResult.success) {
|
||||
return this.handleZodError(parseBodyResult.error, res, "body");
|
||||
}
|
||||
const body = parseBodyResult.data;
|
||||
|
||||
let avatarFile: null | FileResult = null;
|
||||
if (body.avatar !== null) {
|
||||
avatarFile = await this.fileStorage.storeFile(
|
||||
Buffer.from(body.avatar, "base64"),
|
||||
);
|
||||
}
|
||||
|
||||
const verification = orm.em.create(Verification, {
|
||||
id: ulid(),
|
||||
code: generateRandomCode(6),
|
||||
type: VerificationType.createPartner,
|
||||
expiredAt: dateFns.addHours(new Date(), 1),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
const partner = orm.em.create(Partner, {
|
||||
id: ulid(),
|
||||
name: body.name,
|
||||
email: body.email,
|
||||
whatsapp: body.whatsapp,
|
||||
password: await Bun.password.hash(body.password),
|
||||
avatar: avatarFile?.name,
|
||||
verification,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
await orm.em.flush();
|
||||
|
||||
await this.emailService.sendVerificationEmail(
|
||||
partner.email,
|
||||
verification.code,
|
||||
);
|
||||
|
||||
return res.status(201).json({
|
||||
data: {
|
||||
message:
|
||||
"Partner created successfully. Please check your email for verification.",
|
||||
},
|
||||
errors: null,
|
||||
} satisfies SingleResponse);
|
||||
}
|
||||
|
||||
async login(req: Request, res: Response) {
|
||||
const parseBodyResult = partnerRequestSchema.safeParse(req.body);
|
||||
if (!parseBodyResult.success) {
|
||||
return this.handleZodError(parseBodyResult.error, res, "body");
|
||||
}
|
||||
const body = parseBodyResult.data;
|
||||
|
||||
const partner = await orm.em.findOne(Partner, { email: body.email });
|
||||
if (!partner) {
|
||||
return res.status(401).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
location: "body",
|
||||
message: "Incorrect email or password.",
|
||||
},
|
||||
],
|
||||
} satisfies ErrorResponse);
|
||||
}
|
||||
if (!(await Bun.password.verify(body.password, partner.password))) {
|
||||
return res.status(401).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
location: "body",
|
||||
message: "Incorrect email or password.",
|
||||
},
|
||||
],
|
||||
} satisfies ErrorResponse);
|
||||
}
|
||||
if (partner.verification !== null) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
message: "Partner is not verified.",
|
||||
},
|
||||
],
|
||||
} satisfies ErrorResponse);
|
||||
}
|
||||
|
||||
const access = await this.jwtService.createPartnerToken(
|
||||
partner,
|
||||
JwtType.access,
|
||||
);
|
||||
const refresh = await this.jwtService.createPartnerToken(
|
||||
partner,
|
||||
JwtType.refresh,
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
data: {
|
||||
access_token: access.token,
|
||||
access_token_expires_at: access.expiresAt,
|
||||
refresh_token: refresh.token,
|
||||
refresh_token_expires_at: refresh.expiresAt,
|
||||
},
|
||||
errors: null,
|
||||
} satisfies SingleResponse);
|
||||
}
|
||||
|
||||
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(Partner);
|
||||
|
||||
const partners = await orm.em.find(
|
||||
Partner,
|
||||
{
|
||||
verification: null,
|
||||
},
|
||||
{
|
||||
limit: query.per_page,
|
||||
offset: (query.page - 1) * query.per_page,
|
||||
orderBy: { createdAt: "DESC" },
|
||||
populate: ["*"],
|
||||
},
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
data: partners.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<PartnerResponse>);
|
||||
}
|
||||
|
||||
async view(req: Request, res: Response) {
|
||||
const parseParamsResult = partnerParamsSchema.safeParse(req.params);
|
||||
if (!parseParamsResult.success) {
|
||||
return this.handleZodError(parseParamsResult.error, res, "params");
|
||||
}
|
||||
const params = parseParamsResult.data;
|
||||
|
||||
const partner = await orm.em.findOne(
|
||||
Partner,
|
||||
{
|
||||
id: params.id,
|
||||
verification: null,
|
||||
},
|
||||
{
|
||||
populate: ["*"],
|
||||
},
|
||||
);
|
||||
if (!partner) {
|
||||
return res.status(404).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
path: "id",
|
||||
location: "params",
|
||||
message: "Partner not found.",
|
||||
},
|
||||
],
|
||||
} satisfies ErrorResponse);
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
data: this.mapper.mapEntityToResponse(partner),
|
||||
errors: null,
|
||||
} satisfies SingleResponse<PartnerResponse>);
|
||||
}
|
||||
|
||||
async update(req: Request, res: Response) {
|
||||
const parseParamsResult = partnerParamsSchema.safeParse(req.params);
|
||||
if (!parseParamsResult.success) {
|
||||
return this.handleZodError(parseParamsResult.error, res, "params");
|
||||
}
|
||||
const params = parseParamsResult.data;
|
||||
|
||||
const parseBodyResult = partnerUpdateRequestSchema.safeParse(req.body);
|
||||
if (!parseBodyResult.success) {
|
||||
return this.handleZodError(parseBodyResult.error, res, "body");
|
||||
}
|
||||
const body = parseBodyResult.data;
|
||||
|
||||
const partner = await orm.em.findOne(
|
||||
Partner,
|
||||
{
|
||||
id: params.id,
|
||||
verification: null,
|
||||
},
|
||||
{
|
||||
populate: ["*"],
|
||||
},
|
||||
);
|
||||
if (!partner) {
|
||||
return res.status(404).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
path: "id",
|
||||
location: "params",
|
||||
message: "Partner not found.",
|
||||
},
|
||||
],
|
||||
} satisfies ErrorResponse);
|
||||
}
|
||||
|
||||
if (body.avatar !== null) {
|
||||
await this.fileStorage.storeFile(
|
||||
Buffer.from(body.avatar, "base64"),
|
||||
partner.avatar ?? undefined,
|
||||
);
|
||||
} else if (partner.avatar !== null) {
|
||||
await this.fileStorage.removeFile(partner.avatar);
|
||||
}
|
||||
|
||||
partner.name = body.name;
|
||||
partner.verification = orm.em.create(Verification, {
|
||||
id: ulid(),
|
||||
code: generateRandomCode(6),
|
||||
type: VerificationType.updatePartner,
|
||||
expiredAt: dateFns.addHours(new Date(), 1),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
partner.updatedAt = new Date();
|
||||
|
||||
await orm.em.flush();
|
||||
|
||||
await this.emailService.sendVerificationEmail(
|
||||
partner.email,
|
||||
partner.verification.code,
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
data: {
|
||||
message:
|
||||
"Partner updated successfully. Please check your email for verification.",
|
||||
},
|
||||
errors: null,
|
||||
} satisfies SingleResponse);
|
||||
}
|
||||
|
||||
async changeEmail(req: Request, res: Response) {
|
||||
const parseParamsResult = partnerParamsSchema.safeParse(req.params);
|
||||
if (!parseParamsResult.success) {
|
||||
return this.handleZodError(parseParamsResult.error, res, "params");
|
||||
}
|
||||
const params = parseParamsResult.data;
|
||||
|
||||
const parseBodyResult = partnerChangeEmailRequestSchema.safeParse(req.body);
|
||||
if (!parseBodyResult.success) {
|
||||
return this.handleZodError(parseBodyResult.error, res, "body");
|
||||
}
|
||||
const body = parseBodyResult.data;
|
||||
|
||||
const partner = await orm.em.findOne(
|
||||
Partner,
|
||||
{
|
||||
id: params.id,
|
||||
verification: null,
|
||||
},
|
||||
{
|
||||
populate: ["*"],
|
||||
},
|
||||
);
|
||||
if (!partner) {
|
||||
return res.status(404).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
path: "id",
|
||||
location: "params",
|
||||
message: "Partner not found.",
|
||||
},
|
||||
],
|
||||
} satisfies ErrorResponse);
|
||||
}
|
||||
|
||||
partner.email = body.new_email;
|
||||
partner.verification = orm.em.create(Verification, {
|
||||
id: ulid(),
|
||||
code: generateRandomCode(6),
|
||||
type: VerificationType.changeEmailPartner,
|
||||
expiredAt: dateFns.addHours(new Date(), 1),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
partner.updatedAt = new Date();
|
||||
|
||||
await orm.em.flush();
|
||||
|
||||
await this.emailService.sendVerificationEmail(
|
||||
partner.email,
|
||||
partner.verification.code,
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
data: {
|
||||
message:
|
||||
"Partner's email changed successfully. Please check your email for verification.",
|
||||
},
|
||||
errors: null,
|
||||
} satisfies SingleResponse);
|
||||
}
|
||||
|
||||
async changePassword(req: Request, res: Response) {
|
||||
const parseParamsResult = partnerParamsSchema.safeParse(req.params);
|
||||
if (!parseParamsResult.success) {
|
||||
return this.handleZodError(parseParamsResult.error, res, "params");
|
||||
}
|
||||
const params = parseParamsResult.data;
|
||||
|
||||
const parseBodyResult = partnerChangePasswordRequestSchema.safeParse(
|
||||
req.body,
|
||||
);
|
||||
if (!parseBodyResult.success) {
|
||||
return this.handleZodError(parseBodyResult.error, res, "body");
|
||||
}
|
||||
const body = parseBodyResult.data;
|
||||
|
||||
const partner = await orm.em.findOne(
|
||||
Partner,
|
||||
{
|
||||
id: params.id,
|
||||
verification: null,
|
||||
},
|
||||
{
|
||||
populate: ["*"],
|
||||
},
|
||||
);
|
||||
if (!partner) {
|
||||
return res.status(404).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
path: "id",
|
||||
location: "params",
|
||||
message: "Partner not found.",
|
||||
},
|
||||
],
|
||||
} satisfies ErrorResponse);
|
||||
}
|
||||
|
||||
if (!(await Bun.password.verify(body.old_password, partner.password))) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
path: "old_password",
|
||||
location: "body",
|
||||
message: "Incorrect.",
|
||||
},
|
||||
],
|
||||
} satisfies ErrorResponse);
|
||||
}
|
||||
|
||||
partner.password = await Bun.password.hash(body.new_password);
|
||||
partner.verification = orm.em.create(Verification, {
|
||||
id: ulid(),
|
||||
code: generateRandomCode(6),
|
||||
type: VerificationType.changePasswordPartner,
|
||||
expiredAt: dateFns.addHours(new Date(), 1),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
partner.updatedAt = new Date();
|
||||
|
||||
await orm.em.flush();
|
||||
|
||||
await this.emailService.sendVerificationEmail(
|
||||
partner.email,
|
||||
partner.verification.code,
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
data: {
|
||||
message:
|
||||
"Partner's password changed successfully. Please check your email for verification.",
|
||||
},
|
||||
errors: null,
|
||||
} satisfies SingleResponse);
|
||||
}
|
||||
|
||||
async verify(req: Request, res: Response) {
|
||||
const parseParamsResult = partnerParamsSchema.safeParse(req.params);
|
||||
if (!parseParamsResult.success) {
|
||||
return this.handleZodError(parseParamsResult.error, res, "params");
|
||||
}
|
||||
const params = parseParamsResult.data;
|
||||
|
||||
const parseBodyResult = partnerVerifyRequestSchema.safeParse(req.body);
|
||||
if (!parseBodyResult.success) {
|
||||
return this.handleZodError(parseBodyResult.error, res, "body");
|
||||
}
|
||||
const body = parseBodyResult.data;
|
||||
|
||||
const partner = await orm.em.findOne(
|
||||
Partner,
|
||||
{ id: params.id },
|
||||
{
|
||||
populate: ["*"],
|
||||
},
|
||||
);
|
||||
if (!partner) {
|
||||
return res.status(404).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
path: "id",
|
||||
location: "params",
|
||||
message: "Partner not found.",
|
||||
},
|
||||
],
|
||||
} satisfies ErrorResponse);
|
||||
}
|
||||
|
||||
if (partner.verification === null) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
message: "Partner is already verified.",
|
||||
},
|
||||
],
|
||||
} satisfies ErrorResponse);
|
||||
}
|
||||
|
||||
if (partner.verification.code !== body.code) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
path: "code",
|
||||
location: "body",
|
||||
message: "Incorrect.",
|
||||
},
|
||||
],
|
||||
} satisfies ErrorResponse);
|
||||
}
|
||||
|
||||
orm.em.remove(partner.verification);
|
||||
|
||||
partner.verification = null;
|
||||
partner.updatedAt = new Date();
|
||||
|
||||
await orm.em.flush();
|
||||
|
||||
return res.status(200).json({
|
||||
data: this.mapper.mapEntityToResponse(partner),
|
||||
errors: null,
|
||||
} satisfies SingleResponse<PartnerResponse>);
|
||||
}
|
||||
|
||||
async refresh(_req: Request, res: Response) {
|
||||
const req = _req as Request & PartnerRequestPlugin;
|
||||
|
||||
const access = await this.jwtService.createPartnerToken(
|
||||
req.partner,
|
||||
JwtType.access,
|
||||
);
|
||||
const refresh = await this.jwtService.createPartnerToken(
|
||||
req.partner,
|
||||
JwtType.refresh,
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
data: {
|
||||
access_token: access.token,
|
||||
access_token_expires_at: access.expiresAt,
|
||||
refresh_token: refresh.token,
|
||||
refresh_token_expires_at: refresh.expiresAt,
|
||||
},
|
||||
errors: null,
|
||||
} satisfies SingleResponse);
|
||||
}
|
||||
|
||||
async delete(req: Request, res: Response) {
|
||||
const parseParamsResult = partnerParamsSchema.safeParse(req.params);
|
||||
if (!parseParamsResult.success) {
|
||||
return this.handleZodError(parseParamsResult.error, res, "params");
|
||||
}
|
||||
const params = parseParamsResult.data;
|
||||
|
||||
const partner = await orm.em.findOne(
|
||||
Partner,
|
||||
{ id: params.id, verification: null },
|
||||
{
|
||||
populate: ["*"],
|
||||
},
|
||||
);
|
||||
if (!partner) {
|
||||
return res.status(404).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
path: "id",
|
||||
location: "params",
|
||||
message: "Partner not found.",
|
||||
},
|
||||
],
|
||||
} satisfies ErrorResponse);
|
||||
}
|
||||
|
||||
if (partner.avatar !== null) {
|
||||
await this.fileStorage.removeFile(partner.avatar);
|
||||
}
|
||||
|
||||
await orm.em.removeAndFlush(partner);
|
||||
|
||||
return res.status(204).send();
|
||||
}
|
||||
|
||||
public buildRouter(): Router {
|
||||
const router = Router();
|
||||
router.post(
|
||||
"/",
|
||||
createOrmContextMiddleware,
|
||||
isAdminMiddleware(this.jwtService, [AdminPermission.createPartner]),
|
||||
this.create.bind(this),
|
||||
);
|
||||
router.post("/login", createOrmContextMiddleware, this.login.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.updatePartner]),
|
||||
this.update.bind(this),
|
||||
);
|
||||
router.put(
|
||||
"/:id/email",
|
||||
createOrmContextMiddleware,
|
||||
isAdminMiddleware(this.jwtService, [AdminPermission.updatePartner]),
|
||||
this.changeEmail.bind(this),
|
||||
);
|
||||
router.put(
|
||||
"/:id/password",
|
||||
createOrmContextMiddleware,
|
||||
isAdminMiddleware(this.jwtService, [AdminPermission.updatePartner]),
|
||||
this.changePassword.bind(this),
|
||||
);
|
||||
router.put(
|
||||
"/:id/verify",
|
||||
createOrmContextMiddleware,
|
||||
isAdminMiddleware(this.jwtService, [AdminPermission.updatePartner]),
|
||||
this.verify.bind(this),
|
||||
);
|
||||
router.put(
|
||||
"/refresh",
|
||||
createOrmContextMiddleware,
|
||||
isPartnerMiddleware(this.jwtService),
|
||||
this.refresh.bind(this),
|
||||
);
|
||||
router.delete(
|
||||
"/:id",
|
||||
createOrmContextMiddleware,
|
||||
isAdminMiddleware(this.jwtService, [AdminPermission.deletePartner]),
|
||||
this.delete.bind(this),
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user