180 lines
5.0 KiB
TypeScript
180 lines
5.0 KiB
TypeScript
import { AirportsRepository } from "$lib/domains/airport/data/repository";
|
|
import type {
|
|
FlightPriceDetails,
|
|
FlightTicket,
|
|
TicketSearchPayload,
|
|
} from "../data/entities";
|
|
import { TicketRepository } from "../data/repository";
|
|
import { getError, Logger } from "@pkg/logger";
|
|
import { env } from "$env/dynamic/private";
|
|
import { ScrapedTicketsDataSource } from "../data/scrape.data.source";
|
|
import { db } from "@pkg/db";
|
|
import { airportsDb } from "@pkg/airports-db";
|
|
import { ERROR_CODES, type Result } from "@pkg/result";
|
|
import { DiscountType, type CouponModel } from "@pkg/logic/domains/coupon/data";
|
|
import { convertAndFormatCurrency } from "$lib/domains/currency/view/currency.vm.svelte";
|
|
import { AmadeusTicketsAPIDataSource } from "../data/amadeusapi.data.source";
|
|
import { getRedisInstance } from "$lib/server/redis";
|
|
|
|
export class TicketController {
|
|
private repo: TicketRepository;
|
|
private airportsRepo: AirportsRepository;
|
|
private TICKET_SCRAPERS = ["kiwi"];
|
|
|
|
constructor(repo: TicketRepository, airportsRepo: AirportsRepository) {
|
|
this.repo = repo;
|
|
this.airportsRepo = airportsRepo;
|
|
}
|
|
|
|
async searchAirports(query: string) {
|
|
return this.airportsRepo.searchAirports(query);
|
|
}
|
|
|
|
async getAirportByCode(query: string) {
|
|
return this.airportsRepo.getAirportByCode(query);
|
|
}
|
|
async searchForTickets(
|
|
payload: TicketSearchPayload,
|
|
coupon: CouponModel | undefined,
|
|
): Promise<Result<FlightTicket[]>> {
|
|
const result = await this.repo.searchForTickets({
|
|
sessionId: payload.sessionId,
|
|
ticketSearchPayload: payload,
|
|
providers: this.TICKET_SCRAPERS,
|
|
});
|
|
|
|
if (result.error || !result.data) {
|
|
return result;
|
|
}
|
|
|
|
if (!coupon) {
|
|
return result;
|
|
}
|
|
|
|
Logger.info(`Auto-applying coupon ${coupon.code} to all search results`);
|
|
|
|
return {
|
|
data: result.data.map((ticket) => {
|
|
return this.applyDiscountToTicket(ticket, coupon);
|
|
}),
|
|
};
|
|
}
|
|
|
|
private applyDiscountToTicket(
|
|
ticket: FlightTicket,
|
|
coupon: CouponModel,
|
|
): FlightTicket {
|
|
// Calculate discount amount
|
|
let discountAmount = 0;
|
|
|
|
if (coupon.discountType === DiscountType.PERCENTAGE) {
|
|
discountAmount =
|
|
(ticket.priceDetails.displayPrice *
|
|
Number(coupon.discountValue)) /
|
|
100;
|
|
} else {
|
|
discountAmount = Number(coupon.discountValue);
|
|
}
|
|
|
|
// Apply maximum discount limit if specified
|
|
if (
|
|
coupon.maxDiscountAmount &&
|
|
discountAmount > Number(coupon.maxDiscountAmount)
|
|
) {
|
|
discountAmount = Number(coupon.maxDiscountAmount);
|
|
}
|
|
|
|
// Skip if minimum order value is not met
|
|
if (
|
|
coupon.minOrderValue &&
|
|
ticket.priceDetails.displayPrice < Number(coupon.minOrderValue)
|
|
) {
|
|
return ticket;
|
|
}
|
|
|
|
// Create a copy of the ticket with the discount applied
|
|
const discountedTicket = { ...ticket };
|
|
|
|
discountedTicket.priceDetails = {
|
|
...ticket.priceDetails,
|
|
discountAmount:
|
|
(ticket.priceDetails.discountAmount || 0) + discountAmount,
|
|
displayPrice: ticket.priceDetails.displayPrice - discountAmount,
|
|
appliedCoupon: coupon.code,
|
|
couponDescription:
|
|
coupon.description ||
|
|
`Coupon discount of ${coupon.discountType === DiscountType.PERCENTAGE ? coupon.discountValue + "%" : convertAndFormatCurrency(parseFloat(coupon.discountValue.toString()))}`,
|
|
};
|
|
|
|
return discountedTicket;
|
|
}
|
|
|
|
async cacheTicket(sid: string, payload: FlightTicket) {
|
|
Logger.info(
|
|
`Caching ticket for ${sid} | ${payload.departure}:${payload.arrival}`,
|
|
);
|
|
const refOIds = payload.refOIds;
|
|
if (!refOIds) {
|
|
// In this case we're not going for any of our fancy checkout jazz
|
|
return this.repo.createTicket(payload, true);
|
|
}
|
|
|
|
const areAnyLive =
|
|
await this.repo.areCheckoutAlreadyLiveForOrder(refOIds);
|
|
|
|
Logger.info(`Any of the OIds has checkout session live ?? ${areAnyLive}`);
|
|
|
|
if (areAnyLive) {
|
|
return {
|
|
error: getError({
|
|
code: ERROR_CODES.INTERNAL_SERVER_ERROR,
|
|
message: "This ticket offer has expired",
|
|
userHint:
|
|
"Please select another one or perform search again to fetch latest offers",
|
|
actionable: false,
|
|
detail: "Failed to ticket",
|
|
}),
|
|
};
|
|
}
|
|
Logger.info("nu'uh seems greenlight to make a ticket");
|
|
return this.repo.createTicket(payload, true);
|
|
}
|
|
|
|
async updateTicketPrices(tid: number, payload: FlightPriceDetails) {
|
|
return this.repo.updateTicketPrices(tid, payload);
|
|
}
|
|
|
|
async uncacheAndSaveTicket(tid: number) {
|
|
return this.repo.uncacheAndSaveTicket(tid);
|
|
}
|
|
|
|
async getCheckoutUrlByRefOId(id: number) {
|
|
return this.repo.getCheckoutUrlByRefOId(id);
|
|
}
|
|
|
|
async setRefOIdsForTicket(tid: number, oids: number[]) {
|
|
return this.repo.setRefOIdsForTicket(tid, oids);
|
|
}
|
|
|
|
async getTicketById(id: number) {
|
|
return this.repo.getTicketById(id);
|
|
}
|
|
}
|
|
|
|
export function getTC() {
|
|
const ds = new ScrapedTicketsDataSource(
|
|
env.TICKET_SCRAPER_API_URL,
|
|
env.API_KEY,
|
|
);
|
|
const amadeusApi = new AmadeusTicketsAPIDataSource(
|
|
env.AMADEUS_API_BASE_URL,
|
|
env.AMADEUS_API_KEY,
|
|
env.AMADEUS_API_SECRET,
|
|
getRedisInstance(),
|
|
);
|
|
return new TicketController(
|
|
new TicketRepository(db, ds, amadeusApi),
|
|
new AirportsRepository(airportsDb),
|
|
);
|
|
}
|