laundree/laundree

View on GitHub
src/api/controllers/bookings.js

Summary

Maintainability
C
1 day
Test Coverage
// @flow

import BookingHandler from '../../handlers/booking'
import * as api from '../helper'
import { StatusError } from '../../utils/error'

async function listBookingsAsync (since, pageSize, subjects, params) {
  const {from, to} = api.assertSubjects({from: params.from, to: params.to})
  const filter: { from: *, to: *, _id?: *, machine?: * } = {
    from: {$gte: from},
    to: {$lt: to}
  }
  if (since) {
    filter._id = {$gt: since}
  }
  const {machine} = api.assertSubjects({machine: subjects.machine})
  filter.machine = machine.model._id
  const bookings = await BookingHandler.lib.find(filter, {limit: pageSize, sort: {_id: 1}})
  const summaries = bookings.map(BookingHandler.restSummary)
  return {summaries, linkBase: `/api/machines/${machine.model.id}/bookings`}
}

async function createBookingAsync (subjects, params) {
  const {machine, laundry, currentUser, createBookingBody} = api
    .assertSubjects({
      machine: subjects.machine,
      currentUser: subjects.currentUser,
      laundry: subjects.laundry,
      createBookingBody: params.createBookingBody
    })
  if (machine.model.broken) {
    throw new StatusError('Machine is broken', 400)
  }
  const {from, to} = createBookingBody
  if (!laundry.validateDateObject(from) || !laundry.validateDateObject(to)) {
    throw new StatusError('Invalid date', 400)
  }
  let fromDate = laundry.dateFromObject(from)
  let toDate = laundry.dateFromObject(to)
  if (fromDate >= toDate) { // Test that from is before to
    throw new StatusError('From must be before to', 400)
  }
  if (fromDate.getTime() <= (Date.now() + 10 * 60 * 1000)) { // Test that booking is after now
    throw new StatusError('Too soon', 400)
  }
  if (!laundry.checkTimeLimit(from, to)) { // Test that booking meets time limit
    throw new StatusError('Time limit violation', 400)
  }
  if (to.hour < 24 && !laundry.isSameDay(from, to)) { // Test that booking isn't cross day
    throw new StatusError('From and to must be same day', 400)
  }
  const [dailyResult, limitResult] = await Promise
    .all([
      laundry.checkDailyLimit(currentUser, from, to), // Test that daily limit isn't violated
      laundry.checkLimit(currentUser, from, to) // Test that limit isn't violated
    ])
  if (!dailyResult) {
    throw new StatusError('Daily limit violation', 400)
  }
  if (!limitResult) {
    throw new StatusError('Limit violation', 400)
  }
  let [booking] = await machine.fetchBookings(fromDate, toDate)
  if (booking) { // Test for existing booking
    throw new StatusError('Machine not available', 409, {Location: booking.restUrl})
  }
  const newBooking = await machine.createAndMergeBooking(currentUser, fromDate, toDate)
  return newBooking.toRest()
}

function fetchBookingAsync (subjects) {
  const {booking} = api.assertSubjects({booking: subjects.booking})
  return booking.toRest()
}

async function deleteBookingAsync (subjects) {
  const {booking} = api.assertSubjects({booking: subjects.booking})
  await booking.deleteBooking()
}

async function updateBookingAsync (subjects, params) {
  const {booking, laundry, currentUser, updateBookingBody} = api
    .assertSubjects({
      booking: subjects.booking,
      currentUser: subjects.currentUser,
      laundry: subjects.laundry,
      updateBookingBody: params.updateBookingBody
    })
  const {from, to} = updateBookingBody
  if (!from && !to) {
    return
  }
  let newFrom = booking.model.from
  let newTo = booking.model.to
  if (from) {
    const fromDate = laundry.dateFromObject(from)
    if (fromDate < booking.model.from) {
      throw new StatusError('Invalid input', 400)
    }
    newFrom = fromDate
  }
  if (to) {
    const toDate = laundry.dateFromObject(to)
    if (toDate > booking.model.to) {
      throw new StatusError('Invalid input', 400)
    }
    newTo = toDate
  }

  if (newTo.getTime() <= newFrom.getTime()) {
    throw new StatusError('Invalid input', 400)
  }

  await booking.updateTime(currentUser, newFrom, newTo)
  return booking.toRest()
}

export const listBookings = api.wrap(api.paginate(listBookingsAsync), api.securityLaundryUser, api.securityAdministrator)
export const createBooking = api.wrap(createBookingAsync, api.securityLaundryUser)
export const fetchBooking = api.wrap(fetchBookingAsync, api.securityLaundryUser, api.securityAdministrator)
export const deleteBooking = api.wrap(deleteBookingAsync, api.securityLaundryOwner, api.securityBookingCreator)
export const updateBooking = api.wrap(updateBookingAsync, api.securityBookingCreator)