Skip to content

Instantly share code, notes, and snippets.

@WoH
Last active June 7, 2020 15:18
Show Gist options
  • Save WoH/9e8778bbaefa3c4e60cbc0a5ecd8aff2 to your computer and use it in GitHub Desktop.
Save WoH/9e8778bbaefa3c4e60cbc0a5ecd8aff2 to your computer and use it in GitHub Desktop.
Order Ships
import {
Controller,
Route,
Security,
Post,
Request,
Body,
Res,
TsoaResponse,
Tags,
Response,
} from "tsoa";
import { provideSingleton } from "../util/provideSingleton";
import { InjectUser } from "../util/userDecorator";
import { inject } from "inversify";
abstract class Bookable {
abstract isAvailable(id: UUID, start: Date, end: Date): Promise<boolean>;
abstract reserve(
id: UUID,
start: Date,
end: Date
): Promise<Reservation | null>;
abstract book(id: UUID, start: Date, end: Date): Promise<boolean>;
abstract exists(id: UUID): Promise<boolean>;
abstract calculatePrice(id: UUID, start: Date, end: Date): number;
}
abstract class CaptainService extends Bookable {
abstract reserve(
id: UUID,
start: Date,
end: Date
): Promise<CaptainReservation | null>;
}
abstract class BoatService extends Bookable {
abstract reserve(
id: UUID,
start: Date,
end: Date
): Promise<BoatReservation | null>;
abstract getAvailableBoatId(start: Date, end: Date): Promise<UUID | null>;
abstract reserveVests(
amount: number,
start: Date,
end: Date
): Promise<true | null>;
}
abstract class ShipService extends Bookable {
abstract reserve(
id: UUID,
start: Date,
end: Date
): Promise<ShipReservation | null>;
}
abstract class ChargeAccountService {
abstract get(chargeAccountId: UUID, userId: UUID): Promise<ChargeAccount>;
}
abstract class OrderService {
abstract book(
chargeAccountId: ChargeAccount,
reservations: Reservation[]
): Promise<Order | null>;
}
@Route("/orders")
@provideSingleton(OrdersController)
@Security("jwt")
@Tags("order")
@Response<UnauthorizedErrorMessage>(401, "Unauthorized")
@Response<ValidateErrorMessage>(422, "Validation failed")
export class OrdersController extends Controller {
constructor(
@inject(OrderService) private orderService: OrderService,
@inject(ShipService) private shipService: ShipService,
@inject(CaptainService) private captainService: CaptainService,
@inject(BoatService) private boatService: BoatService,
@inject(ChargeAccountService)
private chargeAccountService: ChargeAccountService
) {
super();
}
/**
* This endpoint is used to rent a boat or a ship.
* @summary Add a new rental order.
* @param badRequest Bad Request
* @param paymentRequired Insufficient funds available
* @param notFound Not Found
* @param requestBody The Create Order payload
*/
@Post()
public async createOrder(
@Request() @InjectUser() requestor: User,
@Res() badRequest: TsoaResponse<400, ErrorMessage>,
@Res() paymentRequired: TsoaResponse<402, ErrorMessage>,
@Res() notFound: TsoaResponse<404, ErrorMessage>,
@Body()
requestBody: CreateOrderBody
): Promise<Order> {
const isShipBooking = (
config: BoatConfiguration | ShipConfiguration
): config is ShipConfiguration => config.hasOwnProperty("shipId");
const { configuration, startTime, endTime, chargeAccountId } = requestBody;
let reservations: Reservation[] = [];
if (isShipBooking(configuration)) {
if (!(await this.shipService.exists(configuration.shipId))) {
return notFound(404, { message: "Ship with this id not exist" });
}
const shipReservation = await this.captainService.reserve(
configuration.shipId,
startTime,
endTime
);
if (!shipReservation) {
return badRequest(400, {
message: "The requested ship is not available at this time",
});
}
reservations.push(shipReservation);
if (configuration.captainId) {
if (!this.captainService.exists(configuration.captainId)) {
return notFound(404, {
message: "Captain with this id does not exist",
});
}
const captainReservation = await this.captainService.reserve(
configuration.captainId,
startTime,
endTime
);
if (!captainReservation) {
return badRequest(400, {
message: "The requested captain is not available at this time",
});
}
reservations.push(captainReservation);
}
} else {
const boatId = await this.boatService.getAvailableBoatId(
startTime,
endTime
);
if (!boatId) {
return badRequest(400, { message: "No boat available at that time" });
}
const boatReservation = await this.boatService.reserve(
boatId,
startTime,
endTime
);
configuration.lifevests &&
(await this.boatService.reserveVests(
configuration.lifevests,
startTime,
endTime
));
reservations.push(boatReservation);
}
const chargeAccount = await this.chargeAccountService.get(
requestor.id,
chargeAccountId
);
if (!chargeAccount) {
return notFound(404, { message: "ChargeAccount not found" });
}
const order = await this.orderService.book(chargeAccount, reservations);
if (!order) {
return paymentRequired(402, { message: "Missing funds" });
}
return order;
}
}
interface CreateOrderBody {
/**
* Time when the rental period begins
*/
startTime: Date;
/**
* Time at which the rentals are returned
*/
endTime: Date;
configuration: ShipConfiguration | BoatConfiguration;
/**
* uuid of the charge account used to pay for the rental
*/
chargeAccountId: UUID;
}
interface ShipConfiguration {
/**
* uuid of the ship to rent
* @isInt
*/
shipId: UUID;
/**
* uuid of the captain used to navigate the ship.
* In case the renter is allowed to navigate the ship, the capitainId should be explicitly set to null.
* @isInt
*/
captainId: UUID | null;
}
interface BoatConfiguration {
/**
* @isInt
* @maximum 8
* @minimum 0
*/
lifevests?: number;
}
interface Order {}
interface ChargeAccount {}
interface Ship {}
interface Captain {}
interface Boat {
lifevests: number;
}
interface Reservation {
type: string;
}
interface ShipReservation extends Reservation {
type: "ship";
ship: Ship;
}
interface CaptainReservation extends Reservation {
type: "captain";
captain: Captain;
}
interface BoatReservation extends Reservation {
type: "boat";
boat: Boat;
}
interface ErrorMessage {
message: string;
details?: string;
}
interface ValidateErrorMessage {
message: "Validation failed";
details: { [name: string]: unknown };
}
interface UnauthorizedErrorMessage extends ErrorMessage {
message: "Unauthorized";
}
/**
* Stringified UUIDv4.
* See [RFC 4112](https://tools.ietf.org/html/rfc4122)
* @pattern [0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}
* @example "52907745-7672-470e-a803-a2f8feb52944"
*/
export type UUID = string;
interface User {
id: UUID;
name: string;
email: string;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment