diff --git a/src/application.ts b/src/application.ts index 2a64822..8adbb2b 100644 --- a/src/application.ts +++ b/src/application.ts @@ -17,6 +17,8 @@ import { CityController } from "@/modules/city/city.controller"; import { CityMapper } from "@/modules/city/city.mapper"; import { CountryController } from "@/modules/country/country.controller"; 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 { FlightMapper } from "@/modules/flight/flight.mapper"; 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 { PartnerMapper } from "@/modules/partner/partner.mapper"; 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 { TransportationMapper } from "@/modules/transportation/transportation.mapper"; import compression from "compression"; @@ -71,9 +75,13 @@ export class Application { const airlineMapper = new AirlineMapper(); const airportMapper = new AirportMapper(cityMapper); const flightMapper = new FlightMapper(airlineMapper, airportMapper); + const flightClassMapper = new FlightClassMapper(flightMapper); const hotelFacilityMapper = new HotelFacilityMapper(); const hotelMapper = new HotelMapper(cityMapper, hotelFacilityMapper); const transportationMapper = new TransportationMapper(); + const transportationClassMapper = new TransportationClassMapper( + transportationMapper, + ); const packageMapper = new PackageMapper( flightMapper, hotelMapper, @@ -104,6 +112,9 @@ export class Application { flightMapper, this._jwtService, ).buildRouter(); + const flightClassRouter = new FlightClassController( + flightClassMapper, + ).buildRouter(); const hotelFacilityRouter = new HotelFacilityController( hotelFacilityMapper, this._jwtService, @@ -118,6 +129,9 @@ export class Application { this._fileStorage, this._jwtService, ).buildRouter(); + const transportationClassRouter = new TransportationClassController( + transportationClassMapper, + ).buildRouter(); const packageRouter = new PackageController( packageMapper, this._fileStorage, @@ -147,9 +161,11 @@ export class Application { this._app.use("/airlines", airlineRouter); this._app.use("/airports", airportRouter); this._app.use("/flights", flightRouter); + this._app.use("/flight-classes", flightClassRouter); this._app.use("/hotel-facilities", hotelFacilityRouter); this._app.use("/hotels", hotelRouter); this._app.use("/transportations", transportationRouter); + this._app.use("/transportation-classes", transportationClassRouter); this._app.use("/packages", packageRouter); this._app.use("/admins", adminRouter); this._app.use("/partners", partnerRouter); diff --git a/src/modules/flight-class/flight-class.controller.ts b/src/modules/flight-class/flight-class.controller.ts new file mode 100644 index 0000000..14aa52d --- /dev/null +++ b/src/modules/flight-class/flight-class.controller.ts @@ -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); + } + + public buildRouter(): Router { + const router = Router(); + router.get("/", createOrmContextMiddleware, this.list.bind(this)); + + return router; + } +} diff --git a/src/modules/flight-class/flight-class.mapper.ts b/src/modules/flight-class/flight-class.mapper.ts new file mode 100644 index 0000000..da0104f --- /dev/null +++ b/src/modules/flight-class/flight-class.mapper.ts @@ -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, + }; + } +} diff --git a/src/modules/flight-class/flight-class.schemas.ts b/src/modules/flight-class/flight-class.schemas.ts new file mode 100644 index 0000000..a79cfaf --- /dev/null +++ b/src/modules/flight-class/flight-class.schemas.ts @@ -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."), +}); diff --git a/src/modules/flight-class/flight-class.types.ts b/src/modules/flight-class/flight-class.types.ts new file mode 100644 index 0000000..e08fbd7 --- /dev/null +++ b/src/modules/flight-class/flight-class.types.ts @@ -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; + +export type FlightClassQuery = z.infer; + +export type FlightClassResponse = { + id: string; + flight: FlightResponse; + class: string; + seat_layout: string; + baggage: number; + cabin_baggage: number; + created_at: Date; + updated_at: Date; +}; diff --git a/src/modules/flight/flight.controller.ts b/src/modules/flight/flight.controller.ts index f149902..22afc0f 100644 --- a/src/modules/flight/flight.controller.ts +++ b/src/modules/flight/flight.controller.ts @@ -397,38 +397,6 @@ export class FlightController extends Controller { } satisfies SingleResponse); } - 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); - } - async listClasses(req: Request, res: Response) { const parseQueryResult = paginationQuerySchema.safeParse(req.query); if (!parseQueryResult.success) { @@ -686,11 +654,6 @@ export class FlightController extends Controller { isAdminMiddleware(this.jwtService, [AdminPermission.createFlightClass]), this.createClass.bind(this), ); - router.get( - "/classes", - createOrmContextMiddleware, - this.listAllClasses.bind(this), - ); router.get( "/:id/classes", createOrmContextMiddleware, diff --git a/src/modules/transportation-class/transportation-class.controller.ts b/src/modules/transportation-class/transportation-class.controller.ts new file mode 100644 index 0000000..fe25487 --- /dev/null +++ b/src/modules/transportation-class/transportation-class.controller.ts @@ -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); + } + + public buildRouter(): Router { + const router = Router(); + router.get("/", createOrmContextMiddleware, this.list.bind(this)); + + return router; + } +} diff --git a/src/modules/transportation-class/transportation-class.mapper.ts b/src/modules/transportation-class/transportation-class.mapper.ts new file mode 100644 index 0000000..90c20ae --- /dev/null +++ b/src/modules/transportation-class/transportation-class.mapper.ts @@ -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, + }; + } +} diff --git a/src/modules/transportation-class/transportation-class.schemas.ts b/src/modules/transportation-class/transportation-class.schemas.ts new file mode 100644 index 0000000..3238dbc --- /dev/null +++ b/src/modules/transportation-class/transportation-class.schemas.ts @@ -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."), +}); diff --git a/src/modules/transportation-class/transportation-class.types.ts b/src/modules/transportation-class/transportation-class.types.ts new file mode 100644 index 0000000..1eb0814 --- /dev/null +++ b/src/modules/transportation-class/transportation-class.types.ts @@ -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; +}; diff --git a/src/modules/transportation/transportation.controller.ts b/src/modules/transportation/transportation.controller.ts index 481ae7e..be44b64 100644 --- a/src/modules/transportation/transportation.controller.ts +++ b/src/modules/transportation/transportation.controller.ts @@ -268,38 +268,6 @@ export class TransportationController extends Controller { } satisfies SingleResponse); } - 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); - } - async listClasses(req: Request, res: Response) { const parseQueryResult = paginationQuerySchema.safeParse(req.query); if (!parseQueryResult.success) { @@ -596,11 +564,6 @@ export class TransportationController extends Controller { ]), this.createClass.bind(this), ); - router.get( - "/classes", - createOrmContextMiddleware, - this.listAllClasses.bind(this), - ); router.get( "/:id/classes", createOrmContextMiddleware,