Untitled

mail@pastecode.io avatar
unknown
javascript
12 days ago
21 kB
1
Indexable
Never
// session.ts
import { NextFunction, Response } from "express";
import { v4 as uuid } from "uuid";
import redis from "../controllers/redis";
import { Req } from "../utils/types";

export default async function session(req: Req, res: Response, next: NextFunction) {
  try {
    const sessionToken = req?.cookies?.[process.env.SESSION_COOKIE_KEY!];
    if (sessionToken) {
      req.session = sessionToken;
      return next();
    }
    const token = uuid();
    res.cookie(process.env.SESSION_COOKIE_KEY!, token, {
      httpOnly: true,
      secure: process.env.MODE === "development" ? false : true,
      sameSite: "strict",
      priority: "high",
      expires: new Date(Date.now() + 60 * 24 * 60 * 60 * 1000)
      // domain: process.env.OWN_DOMAIN!,
    });
    let sessions = await redis.getValue("sessions");
    if (!sessions) sessions = [token];
    else sessions = [...JSON.parse(sessions), token];
    await redis.setValue("sessions", JSON.stringify(sessions));
    req.session = token;
    return next();
  } catch (err) {
    console.error(err);
    return res.status(500).send({ status: 500, message: "Internal Server Error" });
  }
}


// booking.ts
import { Response } from "express";
import { v4 as uuid } from "uuid";
import redis from "../../controllers/redis";
import { find, findOne, remove, update } from "../../db/operations";
import calculatePrice from "../../utils/calculatePrice";
import secondsToDhms from "../../utils/secondsToDhms";
import { BookingPayload, Req } from "../../utils/types";
import Room from "../room/model";
import Booking from "./model";

const CREATE_ALLOWED = new Set([
  "name",
  "phone",
  "email",
  "numberOfPerson",
  "totalPrice",
  "paid",
  "startDate",
  "endDate",
  "note",
  "rooms",
  "status",
  "trackingId"
]);

const ALLOWED_QUERY = new Set([
  "name",
  "phone",
  "email",
  "startDate",
  "endDate",
  "status",
  "page",
  "limit",
  "paginate",
  "sortBy",
  "trackingId",
  "$in"
]);

const populate = { path: "rooms approvedBy", populate: { path: "room", select: "name" } };

export const createBooking = async (req: Req, res: Response) => {
  try {
    const isValid = Object.keys(req.body).every(k => CREATE_ALLOWED.has(k));
    if (!isValid) return res.status(400).send({ status: 400, messae: "Invalid parameters provided" });
    req.body.trackingId = uuid();
    let data = req.body as BookingPayload;
    const { startDate, endDate, rooms } = data;
    if (!rooms || rooms.length === 0) return res.status(400).send({ status: 400, message: "No Rooms Selected" });
    const start = new Date(startDate);
    const end = new Date(endDate);
    const today = new Date().setHours(0, 0, 0, 0);
    if (isNaN(start.getTime()) || isNaN(end.getTime())) {
      return res.status(400).send({ status: 400, message: "Invalid date format" });
    }
    if (start.getTime() < today) {
      return res.status(400).send({ message: "Start date cannot be in the past date" });
    }
    if (end < start) {
      return res.status(400).send({ message: "End date cannot be before start date" });
    }
    const overlappingBooking = await Booking.find({
      "rooms.room": { $in: rooms.map(room => room.room) },
      startDate: { $eq: new Date(startDate).toISOString().split("T")[0] + "T00:00:00.000Z" }
    }).populate({ path: "rooms", populate: { path: "room", select: "name" } });

    if (overlappingBooking.length > 0) {
      let unavailableRooms = overlappingBooking.flatMap(booking => booking.rooms).map(room => room);
      return res.status(400).send({ status: 400, message: "Some rooms are not available in the given date range", unavailableRooms });
    }
    const roomDocs = await find({
      table: Room,
      key: { query: { $in: rooms.map(room => room.room), paginate: "false" }, allowedQuery: ALLOWED_QUERY }
    });
    data.totalPrice = calculatePrice(roomDocs, data.rooms);
    data.numberOfPerson = data.rooms.reduce((total, room) => room.guests + total, 0);
    data.startDate = new Date(new Date(data.startDate).setUTCHours(0, 0, 0, 0)).toISOString();
    data.endDate = new Date(new Date(data.endDate).setUTCHours(0, 0, 0, 0)).toISOString();
    const booking = new Booking(data);
    await booking.save();
    await booking.populate({ path: "rooms", populate: { path: "room" } });
    let holded = await redis.getValue(req.session!);
    holded = JSON.parse(holded);
    if (holded) {
      holded = holded.filter((h: string) => !rooms.map(r => r.room).includes(h));
      await redis.setValue(req.session!, JSON.stringify(holded));
      await Promise.all(rooms.map(async room => await redis.removeValue(room.room)));
    }
    if (!booking) return res.status(503).send({ status: 503, message: "Failed to save booking, please try again later" });
    return res.status(201).send({
      status: 201,
      message: "Booking created successfully",
      data: { booking, trackingUrl: `${process.env.TRACKING_URL!}?trackingId=${booking.trackingId}` }
    });
  } catch (err) {
    console.error(err);
    return res.status(500).send({ status: 500, message: "Internal Server Error" });
  }
};

export const updateBooking = async (req: Req, res: Response) => {
  try {
    console.log(req.body);
    const isValid = Object.keys(req.body).every(k => CREATE_ALLOWED.has(k));
    if (!isValid) return res.status(400).send({ status: 400, message: "Invalid Parameters provided" });
    const { id } = req.params;
    if (id === "undefined" || !id || id.trim().length === 0) return res.status(400).send({ status: 400, message: "ID is required" });
    let booking = await findOne({ table: Booking, key: { id } });
    if (!booking) return res.status(404).send({ status: 404, message: "Booking not found" });
    req.body.approvedBy = req?.user?.id;
    booking = await update({ table: Booking, key: { id, body: req.body, populate } });
    if (!booking) return res.status(503).send({ status: 503, message: "Failed to update booking,please try again later." });
    return res.status(200).send({ status: 200, message: "Booking updated successfully", data: booking });
  } catch (err) {
    console.error(err);
    return res.status(500).send({ status: 500, message: "Internal Server Error" });
  }
};

export const getOneBooking = async (req: Req, res: Response) => {
  try {
    const { id } = req.params;
    if (id === "undefined" || !id || id.trim().length === 0) return res.status(400).send({ status: 400, message: "ID is required" });
    const booking = await findOne({ table: Booking, key: { id, populate } });
    if (!booking) return res.status(404).send({ status: 404, message: "Booking not found" });
    return res.status(200).send({ status: 200, message: "Booking retrieved successfully", data: booking });
  } catch (err) {
    console.error(err);
    return res.status(500).send({ status: 500, message: "Internal Server Error" });
  }
};

export const getAllBookings = async (req: Req, res: Response) => {
  try {
    const isValidQuery = Object.keys(req.query).every(k => ALLOWED_QUERY.has(k));
    if (!isValidQuery) return res.status(400).send({ status: 400, message: "Query Validation Failed" });
    const bookings = await find({ table: Booking, key: { query: req.query, allowedQuery: ALLOWED_QUERY, populate } });
    return res.status(200).send({ status: 200, message: "Bookings retrieved successfully", data: bookings });
  } catch (err) {
    console.error(err);
    return res.status(500).send({ status: 500, message: "Internal Server Error" });
  }
};

export const deleteBooking = async (req: Req, res: Response) => {
  try {
    const { id } = req.params;
    if (id === "undefined" || !id || id.trim().length === 0) return res.status(400).send({ status: 400, message: "ID is required" });
    const booking = await findOne({ table: Booking, key: { id } });
    if (!booking) return res.status(404).send({ status: 404, message: "Booking not found" });
    await remove({ table: Booking, key: { id } });
    return res.status(200).send({ status: 200, message: "Booking deleted successfully", data: booking });
  } catch (err) {
    console.error(err);
    return res.status(500).send({ status: 500, message: "Internal Server Error" });
  }
};

export const trackBooking = async (req: Req, res: Response) => {
  try {
    const { trackingId } = req.params;
    if (!trackingId || trackingId === "") return res.status(400).send({ status: 400, message: "Tracking Id is required" });
    const booking = await findOne({ table: Booking, key: { trackingId, populate } });
    if (!booking) return res.status(404).send({ status: 404, message: "Booking not found" });
    return res.status(200).send({ status: 200, message: "Booking retrieved successfully", data: booking });
  } catch (err) {
    console.error(err);
    return res.status(500).send({ status: 500, message: "Internal Server Error" });
  }
};

export const tempHold = async (req: Req, res: Response) => {
  try {
    if (!req?.body?.date) return res.status(400).send({ status: 400, message: "Date is required" });
    const key = req.session;
    if (Object.keys(req.body).length === 0) return res.status(400).send({ status: 400, message: "Body is empty" });
    let rooms = req.body.rooms;
    if (rooms.length === 0) return res.status(400).send({ status: 400, message: "No room id provided" });
    const dateKey = new Date(new Date(req.body.date as string)).toISOString().split("T")[0] + "T00:00:00.000Z";
    let roomsByDate = await redis.getValue(dateKey);
    if (roomsByDate && JSON.parse(roomsByDate).length > 0) {
      roomsByDate = Array.from(new Set([...JSON.parse(roomsByDate), ...rooms]));
      await redis.setValue(dateKey, JSON.stringify(roomsByDate), 24 * 60 * 60);
    } else await redis.setValue(dateKey, JSON.stringify(rooms), 24 * 60 * 60);
    let holded = await redis.getValue(key!);
    if (holded && JSON.parse(holded).length > 0) {
      holded = JSON.parse(holded);
      holded.forEach(async (h: string, index: number) => {
        if (rooms.includes(h)) {
          await redis.removeValue(h);
          rooms = [];
          holded.splice(index, 1);
        }
      });
      if (rooms.length === 0 && holded.length === 0)
        return res.status(200).send({ status: 200, message: "Holded room updated successfully", data: [] });
      const holdExpirations = await Promise.all(
        holded.map(async (hold: string) => ({
          id: hold,
          expiration: await redis.getExpirationTime(hold)
        }))
      );
      holded = holdExpirations.filter(h => h.expiration > 0).map(h => h.id);
      holded = Array.from(new Set([...holded, ...rooms]));
    } else holded = [...rooms];
    await redis.setValue(key!, JSON.stringify(holded));
    await Promise.all(rooms.map(async (room: string) => await redis.setValue(room, room, 15 * 60)));
    const holdedWithTime = await Promise.all(
      holded.map(async (hold: string) => ({ id: hold, time: secondsToDhms(await redis.getExpirationTime(hold)) }))
    );
    return res.status(200).send({ status: 200, message: "Holded room updated successfully", data: holdedWithTime });
  } catch (err) {
    console.error(err);
    return res.status(500).send({ status: 500, message: "Internal Server Error" });
  }
};

export const getHoldRooms = async (req: Req, res: Response) => {
  try {
    if (!req.query.date) return res.status(400).send({ status: 400, message: "Date is required" });
    const session = req.session;
    const dateKey = new Date(new Date(req.query.date as string)).toISOString().split("T")[0] + "T00:00:00.000Z";
    let roomsByDate = await redis.getValue(dateKey);
    if (roomsByDate) roomsByDate = JSON.parse(roomsByDate);
    else roomsByDate = [];
    const allSessions = JSON.parse(await redis.getValue("sessions"));
    let holds = await Promise.all(allSessions.map(async (s: string) => await redis.getValue(s)));
    holds = holds.filter(h => h !== null);
    holds = holds.map(h => JSON.parse(h)).flat();
    holds = Array.from(new Set([...holds.map(h => h)]));
    const holdExpirations = await Promise.all(
      holds.map(async (hold: string) => ({
        id: hold,
        expiration: await redis.getExpirationTime(hold)
      }))
    );
    holds = holdExpirations.filter(h => h.expiration > 0).map(h => h.id);
    holds = holds.filter(h => roomsByDate.includes(h));
    let ownHold = await redis.getValue(session!);
    if (ownHold) {
      ownHold = JSON.parse(ownHold);
      ownHold = ownHold.filter((hold: string) => new Set(holds).has(hold));
      ownHold = await Promise.all(ownHold.map(async (h: string) => ({ id: h, time: secondsToDhms(await redis.getExpirationTime(h)) })));
      ownHold = ownHold.filter((hold: any) => roomsByDate.includes(hold.id));
    } else ownHold = [];
    return res.status(200).send({ status: 200, message: "Holds data retrieved", data: { totalHold: holds, holdByCurrentUser: ownHold } });
  } catch (err) {
    console.error(err);
    return res.status(500).send({ status: 500, message: "Internal Server Error" });
  }
};


// room.ts
import { Response } from "express";
import cloudy from "../../controllers/cloudinary";
import { create, find, findOne, remove, update } from "../../db/operations";
import { Req } from "../../utils/types";
import Booking from "../booking/model";
import Room from "./model";

const CREATE_ALLOWED = new Set(["name", "description", "price", "pricePerPerson", "maxCapacity", "images", "videos", "discount", "status"]);

const ALLOWED_QUERY = new Set(["name", "limit", "page", "paginate", "sort", "sortBy"]);

export const createRoom = async (req: Req, res: Response) => {
  try {
    let data = JSON.parse(req.body?.data || "{}");
    if (typeof data === "string") data = JSON.parse(data);
    if (data?.imageType) delete data.imageType;
    if (Object.keys(data).length === 0 && !req?.files?.images) return res.status(400).send({ status: 400, message: "Body is empty" });
    const isValid = Object.keys(data).every(k => CREATE_ALLOWED.has(k));
    if (!isValid) return res.status(400).send({ status: 400, message: "Invalid Fields provided" });
    if (req?.files?.images) {
      if (!(req.files.images instanceof Array)) req.files.images = [req.files.images];
      if (req.files.images.length > 10) return res.status(400).send({ status: 400, message: "Don't provide more than 10 files at a time" });
      const imgRes = await cloudy.uploadImages(req.files.images.map((img: any) => img.path));
      data.images = imgRes.map(img => img.secure_url).filter(Boolean);
    }
    const room = await create({ table: Room, key: { ...data } });
    if (!room) return res.status(503).send({ status: 503, message: "Failed  to create room,try again later" });
    return res.status(200).send({ status: 200, message: "Room created successfully", data: room });
  } catch (err) {
    console.error(err);
    return res.status(500).send({ status: 500, message: "Internal Server Error" });
  }
};

export const updateRoom = async (req: Req, res: Response) => {
  try {
    const id = req.params.id;
    if (id === "undefined" || !id || id.trim().length === 0) return res.status(400).send({ status: 400, message: "ID is required" });
    let data = JSON.parse(req.body?.data || "{}");
    if (typeof data === "string") data = JSON.parse(data);
    if (data?.imageType) delete data.imageType;
    if (Object.keys(data).length === 0 && !req?.files?.images) return res.status(400).send({ status: 400, message: "Body is empty" });
    const isValid = Object.keys(data).every(k => CREATE_ALLOWED.has(k));
    if (!isValid) return res.status(400).send({ status: 400, message: "Invalid Fields provided" });
    let room = await findOne({ table: Room, key: { id } });
    if (!room) return res.status(404).send({ status: 404, message: "Room not found" });

    if (data?.images && room?.images) {
      const existingImages = room.images.filter((img: string) => img.includes("cloudinary.com"));
      const filteredImages = data?.images.filter((img: string) => img.includes("cloudinary.com"));
      const imagesToDelete = existingImages.filter((img: string) => !filteredImages.includes(img));
      if (imagesToDelete.length > 0) {
        await cloudy.deleteImages(imagesToDelete);
      }
    }
    if (req?.files?.images) {
      if (!(req.files.images instanceof Array)) req.files.images = [req.files.images];
      if (req.files.images.length > 10) return res.status(400).send({ status: 400, message: "Don't provide more than 10 files at a time" });
      const imgRes = await cloudy.uploadImages(req.files.images.map((img: any) => img.path));
      const imgUrls = imgRes.map(img => img.secure_url).filter(Boolean);
      if (data?.images) data.images = [...data.images, ...imgUrls];
      else data.images = imgUrls;
    }

    room = await update({ table: Room, key: { id, body: data } });
    if (!room) return res.status(503).send({ status: 503, message: "Failed to update room,try again later" });
    return res.status(200).send({ status: 200, message: "Room updated successfully", data: room });
  } catch (err) {
    console.error(err);
    return res.status(500).send({ status: 500, message: "Internal Server Error" });
  }
};

export const getAllRooms = async (req: Req, res: Response) => {
  try {
    const isValidQuery = Object.keys(req.query).every(k => ALLOWED_QUERY.has(k));
    if (!isValidQuery) return res.status(400).send({ status: 400, message: "Invalid query parameter" });
    const rooms = await find({ table: Room, key: { query: req.query, allowedQuery: ALLOWED_QUERY } });
    return res.status(200).send({ status: 200, message: "Rooms retrieved successfully", data: rooms });
  } catch (err) {
    console.error(err);
    return res.status(500).send({ status: 500, message: "Internal Server Error" });
  }
};

export const getOneRoom = async (req: Req, res: Response) => {
  try {
    const { id } = req.params;
    if (id === "undefined" || !id || id.trim().length === 0) return res.status(400).send({ status: 400, message: "ID is required" });
    const room = await findOne({ table: Room, key: { id } });
    if (!room) return res.status(404).send({ status: 404, message: "Room not found" });
    return res.status(200).send({ status: 200, message: "Room retrieved successfully", data: room });
  } catch (err) {
    console.error(err);
    return res.status(500).send({ status: 500, message: "Internal Server Error" });
  }
};

export const deleteRoom = async (req: Req, res: Response) => {
  try {
    const { id } = req.params;
    if (id === "undefined" || !id || id.trim().length === 0) return res.status(400).send({ status: 400, message: "ID is required" });
    const room = await findOne({ table: Room, key: { id } });
    if (!room) return res.status(404).send({ status: 404, message: "Room not found" });
    await remove({ table: Room, key: { id } });
    return res.status(200).send({ status: 200, message: "Room deleted successfully", data: room });
  } catch (err) {
    console.error(err);
    return res.status(500).send({ status: 500, message: "Internal Server Error" });
  }
};

export const getAllRoomByDate = async (req: Req, res: Response) => {
  try {
    const { startDate, endDate } = req.query;
    const today = new Date().setHours(0, 0, 0, 0);
    let start = startDate ? new Date(startDate as string).setHours(0, 0, 0, 0) : today;
    if (start < today || isNaN(start)) return res.status(400).send({ status: 400, message: "Invalid Start Date" });
    let end = endDate ? new Date(endDate as string).setHours(0, 0, 0, 0) : start;
    if (end < start || isNaN(end)) return res.status(400).send({ status: 400, message: "Invalid End Date" });
    const bookings = await Booking.find({
      startDate: { $eq: new Date(startDate as string).toISOString().split("T")[0] + "T00:00:00.000Z" }
      // endDate: { $gte: start }
    })
      .select("rooms")
      .lean()
      .populate({ path: "rooms", populate: { path: "room", select: "name" } })
      .lean();
    const bookedRoomIds = bookings.flatMap(booking => booking.rooms.map(room => room?.room?.id.toString()));
    const availableRooms = await Room.find({ _id: { $nin: bookedRoomIds }, status: "active" });
    return res.status(200).send({ status: 200, message: "Available Rooms retrieved successfully", data: availableRooms });
  } catch (err) {
    console.error(err);
    return res.status(500).send({ status: 500, message: "Internal Server Error" });
  }
};

Leave a Comment