separate flight class api and transportation class api into different module

This commit is contained in:
ItsMalma
2025-11-29 12:06:39 +07:00
parent 1fd90fdeab
commit 84a04da1e6
11 changed files with 271 additions and 74 deletions

View File

@@ -17,6 +17,8 @@ import { CityController } from "@/modules/city/city.controller";
import { CityMapper } from "@/modules/city/city.mapper"; import { CityMapper } from "@/modules/city/city.mapper";
import { CountryController } from "@/modules/country/country.controller"; import { CountryController } from "@/modules/country/country.controller";
import { CountryMapper } from "@/modules/country/country.mapper"; import { CountryMapper } from "@/modules/country/country.mapper";
import { FlightClassController } from "@/modules/flight-class/flight-class.controller";
import { FlightClassMapper } from "@/modules/flight-class/flight-class.mapper";
import { FlightController } from "@/modules/flight/flight.controller"; import { FlightController } from "@/modules/flight/flight.controller";
import { FlightMapper } from "@/modules/flight/flight.mapper"; import { FlightMapper } from "@/modules/flight/flight.mapper";
import { HotelFacilityController } from "@/modules/hotel-facility/hotel-facility.controller"; import { HotelFacilityController } from "@/modules/hotel-facility/hotel-facility.controller";
@@ -30,6 +32,8 @@ import { PackageMapper } from "@/modules/package/package.mapper";
import { PartnerController } from "@/modules/partner/partner.controller"; import { PartnerController } from "@/modules/partner/partner.controller";
import { PartnerMapper } from "@/modules/partner/partner.mapper"; import { PartnerMapper } from "@/modules/partner/partner.mapper";
import { StaticController } from "@/modules/static/static.controller"; import { StaticController } from "@/modules/static/static.controller";
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"; import { TransportationController } from "@/modules/transportation/transportation.controller";
import { TransportationMapper } from "@/modules/transportation/transportation.mapper"; import { TransportationMapper } from "@/modules/transportation/transportation.mapper";
import compression from "compression"; import compression from "compression";
@@ -71,9 +75,13 @@ export class Application {
const airlineMapper = new AirlineMapper(); const airlineMapper = new AirlineMapper();
const airportMapper = new AirportMapper(cityMapper); const airportMapper = new AirportMapper(cityMapper);
const flightMapper = new FlightMapper(airlineMapper, airportMapper); const flightMapper = new FlightMapper(airlineMapper, airportMapper);
const flightClassMapper = new FlightClassMapper(flightMapper);
const hotelFacilityMapper = new HotelFacilityMapper(); const hotelFacilityMapper = new HotelFacilityMapper();
const hotelMapper = new HotelMapper(cityMapper, hotelFacilityMapper); const hotelMapper = new HotelMapper(cityMapper, hotelFacilityMapper);
const transportationMapper = new TransportationMapper(); const transportationMapper = new TransportationMapper();
const transportationClassMapper = new TransportationClassMapper(
transportationMapper,
);
const packageMapper = new PackageMapper( const packageMapper = new PackageMapper(
flightMapper, flightMapper,
hotelMapper, hotelMapper,
@@ -104,6 +112,9 @@ export class Application {
flightMapper, flightMapper,
this._jwtService, this._jwtService,
).buildRouter(); ).buildRouter();
const flightClassRouter = new FlightClassController(
flightClassMapper,
).buildRouter();
const hotelFacilityRouter = new HotelFacilityController( const hotelFacilityRouter = new HotelFacilityController(
hotelFacilityMapper, hotelFacilityMapper,
this._jwtService, this._jwtService,
@@ -118,6 +129,9 @@ export class Application {
this._fileStorage, this._fileStorage,
this._jwtService, this._jwtService,
).buildRouter(); ).buildRouter();
const transportationClassRouter = new TransportationClassController(
transportationClassMapper,
).buildRouter();
const packageRouter = new PackageController( const packageRouter = new PackageController(
packageMapper, packageMapper,
this._fileStorage, this._fileStorage,
@@ -147,9 +161,11 @@ export class Application {
this._app.use("/airlines", airlineRouter); this._app.use("/airlines", airlineRouter);
this._app.use("/airports", airportRouter); this._app.use("/airports", airportRouter);
this._app.use("/flights", flightRouter); this._app.use("/flights", flightRouter);
this._app.use("/flight-classes", flightClassRouter);
this._app.use("/hotel-facilities", hotelFacilityRouter); this._app.use("/hotel-facilities", hotelFacilityRouter);
this._app.use("/hotels", hotelRouter); this._app.use("/hotels", hotelRouter);
this._app.use("/transportations", transportationRouter); this._app.use("/transportations", transportationRouter);
this._app.use("/transportation-classes", transportationClassRouter);
this._app.use("/packages", packageRouter); this._app.use("/packages", packageRouter);
this._app.use("/admins", adminRouter); this._app.use("/admins", adminRouter);
this._app.use("/partners", partnerRouter); this._app.use("/partners", partnerRouter);

View File

@@ -0,0 +1,57 @@
import { Controller } from "@/common/controller";
import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware";
import { paginationQuerySchema } from "@/common/schemas";
import type { ListResponse } from "@/common/types";
import { FlightClass } from "@/database/entities/flight-class.entity";
import { Flight } from "@/database/entities/flight.entity";
import { orm } from "@/database/orm";
import type { FlightClassMapper } from "@/modules/flight-class/flight-class.mapper";
import type { FlightClassResponse } from "@/modules/flight-class/flight-class.types";
import { Router, type Request, type Response } from "express";
export class FlightClassController extends Controller {
public constructor(private readonly mapper: FlightClassMapper) {
super();
}
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(Flight);
const flightClasses = await orm.em.find(
FlightClass,
{},
{
limit: query.per_page,
offset: (query.page - 1) * query.per_page,
orderBy: { createdAt: "DESC" },
populate: ["*"],
},
);
return res.status(200).json({
data: flightClasses.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<FlightClassResponse>);
}
public buildRouter(): Router {
const router = Router();
router.get("/", createOrmContextMiddleware, this.list.bind(this));
return router;
}
}

View File

@@ -0,0 +1,20 @@
import type { FlightClass } from "@/database/entities/flight-class.entity";
import type { FlightMapper } from "@/modules/flight/flight.mapper";
import type { FlightClassResponse } from "@/modules/flight/flight.types";
export class FlightClassMapper {
public constructor(private readonly flightMapper: FlightMapper) {}
public mapEntityToResponse(flightClass: FlightClass): FlightClassResponse {
return {
id: flightClass.id,
flight: this.flightMapper.mapEntityToResponse(flightClass.flight),
class: flightClass.class,
seat_layout: flightClass.seatLayout,
baggage: flightClass.baggage,
cabin_baggage: flightClass.cabinBaggage,
created_at: flightClass.createdAt,
updated_at: flightClass.updatedAt,
};
}
}

View File

@@ -0,0 +1,27 @@
import z from "zod";
export const flightClassRequestSchema = z.object({
class: z
.string("Must be string.")
.nonempty("Must not empty.")
.max(100, "Max 100 characters."),
seat_layout: z
.string("Must be string.")
.nonempty("Must not empty.")
.max(10, "Max 10 characters."),
baggage: z
.number("Must be number.")
.int("Must be integer.")
.positive("Must be positive."),
cabin_baggage: z
.number("Must be number.")
.int("Must be integer.")
.positive("Must be positive."),
});
export const flightClassParamsSchema = z.object({
id: z
.ulid("Must be ulid string.")
.nonempty("Must not empty.")
.max(30, "Max 30 characters."),
});

View File

@@ -0,0 +1,21 @@
import type {
flightClassParamsSchema,
flightClassRequestSchema,
} from "@/modules/flight-class/flight-class.schemas";
import type { FlightResponse } from "@/modules/flight/flight.types";
import z from "zod";
export type FlightClassRequest = z.infer<typeof flightClassRequestSchema>;
export type FlightClassQuery = z.infer<typeof flightClassParamsSchema>;
export type FlightClassResponse = {
id: string;
flight: FlightResponse;
class: string;
seat_layout: string;
baggage: number;
cabin_baggage: number;
created_at: Date;
updated_at: Date;
};

View File

@@ -397,38 +397,6 @@ export class FlightController extends Controller {
} satisfies SingleResponse<FlightClassResponse>); } satisfies SingleResponse<FlightClassResponse>);
} }
async listAllClasses(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(FlightClass);
const classes = await orm.em.find(
FlightClass,
{},
{
limit: query.per_page,
offset: (query.page - 1) * query.per_page,
orderBy: { createdAt: "DESC" },
populate: ["*"],
},
);
return res.status(200).json({
data: classes.map(this.mapper.mapClassEntityToResponse.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<FlightClassResponse>);
}
async listClasses(req: Request, res: Response) { async listClasses(req: Request, res: Response) {
const parseQueryResult = paginationQuerySchema.safeParse(req.query); const parseQueryResult = paginationQuerySchema.safeParse(req.query);
if (!parseQueryResult.success) { if (!parseQueryResult.success) {
@@ -686,11 +654,6 @@ export class FlightController extends Controller {
isAdminMiddleware(this.jwtService, [AdminPermission.createFlightClass]), isAdminMiddleware(this.jwtService, [AdminPermission.createFlightClass]),
this.createClass.bind(this), this.createClass.bind(this),
); );
router.get(
"/classes",
createOrmContextMiddleware,
this.listAllClasses.bind(this),
);
router.get( router.get(
"/:id/classes", "/:id/classes",
createOrmContextMiddleware, createOrmContextMiddleware,

View File

@@ -0,0 +1,56 @@
import { Controller } from "@/common/controller";
import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware";
import { paginationQuerySchema } from "@/common/schemas";
import type { ListResponse } from "@/common/types";
import { TransportationClass } from "@/database/entities/transportation-class.entity";
import { orm } from "@/database/orm";
import type { TransportationClassMapper } from "@/modules/transportation-class/transportation-class.mapper";
import type { TransportationClassResponse } from "@/modules/transportation-class/transportation-class.types";
import { Router, type Request, type Response } from "express";
export class TransportationClassController extends Controller {
public constructor(private readonly mapper: TransportationClassMapper) {
super();
}
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(TransportationClass);
const transportationClasses = await orm.em.find(
TransportationClass,
{},
{
limit: query.per_page,
offset: (query.page - 1) * query.per_page,
orderBy: { createdAt: "DESC" },
populate: ["*"],
},
);
return res.status(200).json({
data: transportationClasses.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<TransportationClassResponse>);
}
public buildRouter(): Router {
const router = Router();
router.get("/", createOrmContextMiddleware, this.list.bind(this));
return router;
}
}

View File

@@ -0,0 +1,25 @@
import type { TransportationClass } from "@/database/entities/transportation-class.entity";
import type { TransportationMapper } from "@/modules/transportation/transportation.mapper";
import type { TransportationClassResponse } from "@/modules/transportation/transportation.types";
export class TransportationClassMapper {
public constructor(
private readonly transportationMapper: TransportationMapper,
) {}
public mapEntityToResponse(
transportationClass: TransportationClass,
): TransportationClassResponse {
return {
id: transportationClass.id,
transportation: this.transportationMapper.mapEntityToResponse(
transportationClass.transportation,
),
class: transportationClass.class,
total_seats: transportationClass.totalSeats,
images: transportationClass.images.getItems().map((image) => image.src),
created_at: transportationClass.createdAt,
updated_at: transportationClass.updatedAt,
};
}
}

View File

@@ -0,0 +1,25 @@
import z from "zod";
export const transportationClassRequestSchema = z.object({
class: z
.string("Must be string.")
.nonempty("Must not empty.")
.max(100, "Max 100 characters."),
total_seats: z
.number("Must be number.")
.int("Must be integer.")
.positive("Must be positive."),
images: z
.array(
z.base64("Must be base64 string.").nonempty("Must not empty."),
"Must be array.",
)
.nonempty("Must not empty."),
});
export const transportationClassParamsSchema = z.object({
id: z
.ulid("Must be ulid string.")
.nonempty("Must not empty.")
.max(30, "Max 30 characters."),
});

View File

@@ -0,0 +1,24 @@
import type {
transportationClassParamsSchema,
transportationClassRequestSchema,
} from "@/modules/transportation-class/transportation-class.schemas";
import type { TransportationResponse } from "@/modules/transportation/transportation.types";
import z from "zod";
export type TransportationClassRequest = z.infer<
typeof transportationClassRequestSchema
>;
export type TransportationClassParams = z.infer<
typeof transportationClassParamsSchema
>;
export type TransportationClassResponse = {
id: string;
transportation: TransportationResponse;
class: string;
total_seats: number;
images: string[];
created_at: Date;
updated_at: Date;
};

View File

@@ -268,38 +268,6 @@ export class TransportationController extends Controller {
} satisfies SingleResponse<TransportationClassResponse>); } satisfies SingleResponse<TransportationClassResponse>);
} }
async listAllClasses(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(TransportationClass);
const classes = await orm.em.find(
TransportationClass,
{},
{
limit: query.per_page,
offset: (query.page - 1) * query.per_page,
orderBy: { createdAt: "DESC" },
populate: ["*"],
},
);
return res.status(200).json({
data: classes.map(this.mapper.mapClassEntityToResponse.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<TransportationClassResponse>);
}
async listClasses(req: Request, res: Response) { async listClasses(req: Request, res: Response) {
const parseQueryResult = paginationQuerySchema.safeParse(req.query); const parseQueryResult = paginationQuerySchema.safeParse(req.query);
if (!parseQueryResult.success) { if (!parseQueryResult.success) {
@@ -596,11 +564,6 @@ export class TransportationController extends Controller {
]), ]),
this.createClass.bind(this), this.createClass.bind(this),
); );
router.get(
"/classes",
createOrmContextMiddleware,
this.listAllClasses.bind(this),
);
router.get( router.get(
"/:id/classes", "/:id/classes",
createOrmContextMiddleware, createOrmContextMiddleware,