import StatusCodes from 'http-status-codes' import { Request, Response, Router } from 'express' import { ApiError, notFoundError, paramMissingError } from '@shared/responseTypes' import BikePointDao from '@daos/BikePoint.ts/BikePointDao' import { IBikePointActivityMap, IBikePointDetails } from '@entities/BikePointDetails' import { bikeTripsCollectionName, dbClient, dbName } from '@server' const router = Router() const bikePointDao = new BikePointDao() const {NOT_FOUND, OK} = StatusCodes interface QueryParams { from: string to: string day: string } interface PathParam { bikePointId: string } interface IBikePointDetailsResponse { bikePointDetails: IBikePointDetails } /****************************************************************************** * Get Details about single Bike Point by id - "GET /api/bike-point-details/:bikePointId" /api/bike-point-details/311?from=1420329660&to=1421538420&selectedDay=0 ******************************************************************************/ router.get('/:bikePointId', async (req: Request, res: Response) => { const bikePointId = req.params.bikePointId const from = Number(req.query.from) const to = Number(req.query.to) /*0=Monday, 1=Tuesday, etc...*/ const selectedDay = Number(req.query.day) if (!bikePointId || (selectedDay === undefined) || !from || !to) { return res.status(StatusCodes.BAD_REQUEST).json({error: paramMissingError}) } const bikePoint = await bikePointDao.getById(bikePointId) if (!bikePoint) { return res.status(NOT_FOUND).json({error: notFoundError}) } const [rentalsAtHoursOfDay, returnsAtHoursOfDay] = await Promise.all([ dbClient.db(dbName).collection(bikeTripsCollectionName).aggregate<{ _id: number, count: number }>([ // check if trip was started at the desired bikepoint {$match: {startStationId: {$eq: bikePointId}}}, // check if trip was started within the time range {$match: {startDate: {$gte: from, $lt: to}}}, // convert unixtimestaamp to mongoDB-Date {$set: {startDate: {$toDate: {$multiply: ['$startDate', 1000]}}}}, {$set: {dayofWeek: {$dayOfWeek: '$startDate'}}}, //check if trip was started at desired day (e.g. Monday) {$match: {dayofWeek: {$eq: selectedDay + 1}}}, //group rentals by hour of day (0-23) { $group: { _id: {$hour: {date: '$startDate'}}, count: {$sum: 1} } }, {$sort: {_id: 1}}, ]).toArray(), dbClient.db(dbName).collection(bikeTripsCollectionName).aggregate<{ _id: number, count: number }>([ // check if trip was ended at the desired bikepoint {$match: {endStationId: {$eq: bikePointId}}}, // check if trip was started within the time range {$match: {endDate: {$gte: from, $lt: to}}}, // convert unixtimestaamp to mongoDB-Date {$set: {endDate: {$toDate: {$multiply: ['$endDate', 1000]}}}}, {$set: {dayofWeek: {$dayOfWeek: '$endDate'}}}, //check if trip was ended at desired day (e.g. Monday) {$match: {dayofWeek: {$eq: selectedDay + 1}}}, //group returns by hour of day (0-23) { $group: { _id: {$hour: {date: '$endDate'}}, count: {$sum: 1} } }, {$sort: {_id: 1}}, ]).toArray() ]) const countOfSelectedDay = countCertainDay(selectedDay, new Date(from * 1000), new Date(to * 1000)) const combinedData: IBikePointActivityMap = {} for (let i = 0; i < 24; i++) { const nbRentals = (rentalsAtHoursOfDay.find(entry => entry._id === i)?.count ?? 0) const nbReturns = (returnsAtHoursOfDay.find(entry => entry._id === i)?.count ?? 0) combinedData[i] = { avgNbRentals: nbRentals / countOfSelectedDay, avgNbReturns: nbReturns / countOfSelectedDay, avgNbTotal: (nbRentals + nbReturns) / countOfSelectedDay } } const bikePointDetails: IBikePointDetails = { commonName: bikePoint.commonName, id: bikePoint.id, installDate: Number(bikePoint.additionalProperties.find(additionalProperty => additionalProperty.key === 'InstallDate')?.value) / 1000, nbDocks: Number(bikePoint.additionalProperties.find(additionalProperty => additionalProperty.key === 'NbDocks')?.value), diagrammData: combinedData } return res.status(OK).json({bikePointDetails}) }) // count how often a certain day of week appears in given time range, where day can be 0 (Monday) up to 6 (Sunday) function countCertainDay(day: number, startDate: Date, endDate: Date) { const numberOfDays = 1 + Math.round((endDate.getTime() - startDate.getTime()) / (24 * 3600 * 1000)) return Math.floor((numberOfDays + (startDate.getDay() + 6 - day) % 7) / 7) } /****************************************************************************** * Export ******************************************************************************/ export default router