diff --git a/backend/src/constants/FindOptionsFields.ts b/backend/src/constants/FindOptionsFields.ts index 490c7a8..5742566 100644 --- a/backend/src/constants/FindOptionsFields.ts +++ b/backend/src/constants/FindOptionsFields.ts @@ -2,6 +2,7 @@ import { FindOptions } from 'typeorm'; import { Property } from '../entities/Property'; import { User } from '../entities/User'; import { WorkOrder } from '../entities/WorkOrder'; +import { PropertySector } from '../entities/PropertySector'; const PropertyFields : FindOptions = { relations: ['activityStatus', 'propertyType', 'user'], @@ -51,6 +52,7 @@ const UserFields : FindOptions = { id: true, type: true, }, + phoneNumber: true, }, }; @@ -130,5 +132,15 @@ const WorkOrderFieldsNoProperty : FindOptions = { }, }; +const PROPERTY_SECTOR_FIELDS : FindOptions = { + relations: ['sector'], + select: { + id: true, + sector: { + id: true, + }, + }, +}; + export { PropertyFields, UserFields, WorkOrderFields, PropertyFieldsNoUser, - WorkOrderFieldsNoProperty }; + WorkOrderFieldsNoProperty, PROPERTY_SECTOR_FIELDS }; diff --git a/backend/src/controllers/PropertyController.ts b/backend/src/controllers/PropertyController.ts index d069c41..3afcb90 100644 --- a/backend/src/controllers/PropertyController.ts +++ b/backend/src/controllers/PropertyController.ts @@ -1,11 +1,19 @@ -import express, {Request, Response } from 'express'; +import express, { Request, Response } from 'express'; import { PropertyService } from '../services/PropertyService'; import { PropertyMapper } from '../entity_mappers/PropertyMapper'; import auth from '../middleware/auth'; -import handleError from '../utils/HttpUtils'; +import { handleError } from '../utils/HttpUtils'; +import { validateArrayBody } from '../middleware/requestValidation'; +import { PROPERTY_SECTOR_FIELDS } from '../constants/BodyFields'; +import { PropertySectorDTO } from '../dtos/PropertySectorDTO'; +import { PropertySector } from '../entities/PropertySector'; +import { PropertySectorService } from '../services/PropertySectorService'; +import { PropertySectorMapper } from '../entity_mappers/PropertySectorMapper'; const propertyService = new PropertyService(); const propertyMapper = new PropertyMapper(); +const propertySectorService = new PropertySectorService(); +const propertySectorMapper = new PropertySectorMapper(); const propertyController = express.Router(); @@ -15,8 +23,8 @@ propertyController.get('/:id', auth, async(req: Request, res: Response) => { return res.status(200).json(propertyMapper.toDTO(property)); } catch (err) { return handleError(err, res); - } -}) + } +}); propertyController.patch('/:id', auth, async(req: Request, res: Response) => { try { @@ -24,7 +32,25 @@ propertyController.patch('/:id', auth, async(req: Request, res: Response) => { return res.status(204).end(); } catch (err) { return handleError(err, res); - } -}) + } +}); + +propertyController.post( + '/:propertyId/sectors', validateArrayBody(PROPERTY_SECTOR_FIELDS.createFields), auth, + async (req: Request, res: Response) => { + try { + const { propertyId } = req.params; + const propertySectorDTOs : PropertySectorDTO[] = req.body as PropertySectorDTO[]; + const propertySectors : PropertySector[] = await propertySectorService + .createPropertySectors( + Number(propertyId), + propertySectorDTOs.map(propertySectorDTO => + propertySectorMapper.fromDTO(propertySectorDTO))); + return res.status(200).json(propertySectors.map(propertySector => + propertySectorMapper.toDTO(propertySector))); + } catch (err) { + return handleError(err, res); + } + }); export { propertyController }; diff --git a/backend/src/controllers/PropertySectorsController.ts b/backend/src/controllers/PropertySectorsController.ts index bb52537..f9f3212 100644 --- a/backend/src/controllers/PropertySectorsController.ts +++ b/backend/src/controllers/PropertySectorsController.ts @@ -2,31 +2,25 @@ import express, { Request, Response } from 'express'; import { PropertySectorService } from '../services/PropertySectorService'; import { PropertySectorMapper } from '../entity_mappers/PropertySectorMapper'; import auth from '../middleware/auth'; -import handleError from '../utils/HttpUtils'; -import validateBody from '../middleware/requestValidation'; +import { handleError } from '../utils/HttpUtils'; import { PropertySectorDTO } from '../dtos/PropertySectorDTO'; -import { PropertySector } from '../entities/PropertySector'; -import { PROPERTY_SECTOR_FIELDS } from '../constants/BodyFields'; const propertySectorService = new PropertySectorService(); const propertySectorMapper = new PropertySectorMapper(); const propertySectorsController = express.Router({ mergeParams: true }); -propertySectorsController.post('/', validateBody(PROPERTY_SECTOR_FIELDS.createFields), auth, - async (req: Request, res: Response) => { - try { - const propertySectorDTOs : PropertySectorDTO[] = req.body as PropertySectorDTO[]; - const propertySectors : PropertySector[] = await propertySectorService - .createPropertySectors( - Number(req.params.propertyId), - propertySectorDTOs.map(propertySectorDTO => - propertySectorMapper.fromDTO(propertySectorDTO))); - return res.status(200).json(propertySectors.map(propertySector => - propertySectorMapper.toDTO(propertySector))); - } catch (err) { - return handleError(err, res); - } - }); +propertySectorsController.patch('/:propertySectorId', auth, async (req: Request, res: Response) => { + try { + const { propertySectorId } = req.params; + const propertySectorDTO : PropertySectorDTO = req.body as PropertySectorDTO; + await propertySectorService.update( + Number(propertySectorId), + propertySectorMapper.fromDTO(propertySectorDTO)); + return res.status(204).end(); + } catch (err) { + return handleError(err, res); + } +}) export { propertySectorsController }; diff --git a/backend/src/controllers/PropertyWorkOrdersController.ts b/backend/src/controllers/PropertyWorkOrdersController.ts index ac35683..2042bf5 100644 --- a/backend/src/controllers/PropertyWorkOrdersController.ts +++ b/backend/src/controllers/PropertyWorkOrdersController.ts @@ -3,8 +3,8 @@ import { WorkOrderService } from '../services/WorkOrderService'; import { WorkOrderMapper } from '../entity_mappers/WorkOrderMapper'; import { WorkOrderDTO } from '../dtos/WorkOrderDTO'; import auth from '../middleware/auth'; -import handleError from '../utils/HttpUtils'; -import validateBody from '../middleware/requestValidation'; +import { handleError } from '../utils/HttpUtils'; +import { validateBody } from '../middleware/requestValidation'; import { WorkOrderFields } from '../constants/BodyFields'; const propertyWorkOrdersController = express.Router({mergeParams: true}); diff --git a/backend/src/controllers/UserController.ts b/backend/src/controllers/UserController.ts index 090f776..ac96799 100644 --- a/backend/src/controllers/UserController.ts +++ b/backend/src/controllers/UserController.ts @@ -2,8 +2,8 @@ import express, {Request, Response} from 'express'; import { UserService } from '../services/UserService'; import { UserMapper } from '../entity_mappers/UserMapper'; import auth from '../middleware/auth'; -import handleError from '../utils/HttpUtils'; -import validateBody from '../middleware/requestValidation'; +import { handleError } from '../utils/HttpUtils'; +import { validateBody } from '../middleware/requestValidation'; import { UserFields } from '../constants/BodyFields'; import { UserDTO } from 'src/dtos/UserDTO'; diff --git a/backend/src/controllers/UserPropertiesController.ts b/backend/src/controllers/UserPropertiesController.ts index f2ab7f2..c359446 100644 --- a/backend/src/controllers/UserPropertiesController.ts +++ b/backend/src/controllers/UserPropertiesController.ts @@ -3,8 +3,8 @@ import { PropertyService } from '../services/PropertyService'; import { PropertyDTO } from '../dtos/PropertyDTO'; import { PropertyMapper } from '../entity_mappers/PropertyMapper'; import auth from '../middleware/auth'; -import handleError from '../utils/HttpUtils'; -import validateBody from '../middleware/requestValidation'; +import { handleError } from '../utils/HttpUtils'; +import { validateBody } from '../middleware/requestValidation'; import { PropertyFields } from '../constants/BodyFields'; const userPropertiesController = express.Router({mergeParams: true}); diff --git a/backend/src/controllers/WorkOrderController.ts b/backend/src/controllers/WorkOrderController.ts index 03beef6..8f4046c 100644 --- a/backend/src/controllers/WorkOrderController.ts +++ b/backend/src/controllers/WorkOrderController.ts @@ -2,8 +2,8 @@ import express, {Request, Response} from 'express'; import { WorkOrderService } from '../services/WorkOrderService'; import { WorkOrderMapper } from '../entity_mappers/WorkOrderMapper'; import auth from '../middleware/auth'; -import handleError from '../utils/HttpUtils'; -import {WorkOrderDTO} from 'src/dtos/WorkOrderDTO'; +import { handleError } from '../utils/HttpUtils'; +import { WorkOrderDTO } from 'src/dtos/WorkOrderDTO'; const workOrderService = new WorkOrderService(); const workOrderMapper = new WorkOrderMapper(); diff --git a/backend/src/controllers/index.ts b/backend/src/controllers/index.ts index 89b8c30..476e714 100644 --- a/backend/src/controllers/index.ts +++ b/backend/src/controllers/index.ts @@ -16,7 +16,7 @@ class Router { this.router.use('/api/properties', propertyController); this.router.use('/api/workOrders', workOrderController); this.router.use('/api/properties/:propertyId/workOrders', propertyWorkOrdersController); - this.router.use('/api/properties/:propertyId/sectors', propertySectorsController); + this.router.use('/api/propertySectors', propertySectorsController); } getRouter() { diff --git a/backend/src/dtos/PropertySectorDTO.ts b/backend/src/dtos/PropertySectorDTO.ts index 0b963d4..697a791 100644 --- a/backend/src/dtos/PropertySectorDTO.ts +++ b/backend/src/dtos/PropertySectorDTO.ts @@ -7,4 +7,5 @@ export class PropertySectorDTO { property: PropertyDTO; sector: SectorDTO; sectorKind: string; + status: string; } diff --git a/backend/src/entities/PropertySector.ts b/backend/src/entities/PropertySector.ts index 1f25a50..277683f 100644 --- a/backend/src/entities/PropertySector.ts +++ b/backend/src/entities/PropertySector.ts @@ -1,4 +1,4 @@ -import { Entity, PrimaryGeneratedColumn, ManyToOne, JoinColumn } from 'typeorm'; +import { Entity, PrimaryGeneratedColumn, ManyToOne, JoinColumn, Column } from 'typeorm'; import { Property } from './Property'; import { Sector } from './Sector'; @@ -19,4 +19,7 @@ export class PropertySector { name: 'sector_id', }) sector: Sector; + + @Column() + status: string; } diff --git a/backend/src/entity_mappers/PropertySectorMapper.ts b/backend/src/entity_mappers/PropertySectorMapper.ts index 57fec35..88cb28c 100644 --- a/backend/src/entity_mappers/PropertySectorMapper.ts +++ b/backend/src/entity_mappers/PropertySectorMapper.ts @@ -1,18 +1,17 @@ import { ObjectMapper } from './ObjectMapper'; -import { Sector } from '../entities/Sector'; import { SectorDTO } from '../dtos/SectorDTO'; -import { SectorType as SectorTypeEnum } from '../enums/SectorType'; -import { SectorKind as SectorKindEnum } from '../enums/SectorKind'; -import { BadRequestError } from '../errors/BadRequestError'; -import {PropertySector} from "../entities/PropertySector"; -import {PropertySectorDTO} from "../dtos/PropertySectorDTO"; -import {PropertyMapper} from "./PropertyMapper"; -import {SectorMapper} from "./SectorTypeMapper"; +import { PropertySector } from '../entities/PropertySector'; +import { PropertySectorDTO } from '../dtos/PropertySectorDTO'; +import { PropertyMapper } from './PropertyMapper'; +import { SectorMapper } from './SectorTypeMapper'; +import { ActivityStatusMapper } from './ActivityStatusMapper'; +import { ActivityStatusDTO } from '../dtos/ActivityStatusDTO'; class PropertySectorMapper implements ObjectMapper { private propertyMapper : PropertyMapper = new PropertyMapper(); private sectorMapper : SectorMapper = new SectorMapper(); + private activityStatusMapper : ActivityStatusMapper = new ActivityStatusMapper(); toDTO(propertySector: PropertySector) : PropertySectorDTO { @@ -24,6 +23,7 @@ class PropertySectorMapper implements ObjectMapper (req: Request, res: Response, next: NextFunction) => { - var missingParameters = []; - for(const parameter of parameters) { - if(!req.body[parameter]) { +export const validateBody = (parameters: any) => ( + req: Request, res: Response, next: NextFunction) => { + const missingParameters = []; + for (const parameter of parameters) { + if (!req.body[parameter]) { missingParameters.push(parameter); } - } + } if (missingParameters.length > 0) { res.status(422).json(new UnprocessableEntity( - `Request body missing [${missingParameters.toString()}].`)) + `Request body missing [${missingParameters.toString()}].`)); } else { next(); } -} +}; -export default validateBody; +export const validateArrayBody = (parameters: any) => ( + req: Request, res: Response, next: NextFunction) => { + const missingParameters = new Set(); + for (const object of req.body) { + for (const parameter of parameters) { + if (!object[parameter]) { + missingParameters.add(parameter); + } + } + } + if (missingParameters.size > 0) { + res.status(422).json(new UnprocessableEntity( + 'At least one request object is missing parameter ' + + `[${Array.from(missingParameters).toString()}].`)); + } else { + next(); + } +} diff --git a/backend/src/repositories/PropertySectorRepository.ts b/backend/src/repositories/PropertySectorRepository.ts index fc11514..641e98e 100644 --- a/backend/src/repositories/PropertySectorRepository.ts +++ b/backend/src/repositories/PropertySectorRepository.ts @@ -1,11 +1,30 @@ import { PropertySector } from '../entities/PropertySector'; import { BaseRepository } from './BaseRepository'; +import { Property } from '../entities/Property'; +import { PROPERTY_SECTOR_FIELDS } from '../constants/FindOptionsFields'; +import { FindOptions } from 'typeorm'; +import { Sector } from '../entities/Sector'; class PropertySectorRepository extends BaseRepository { async save(propertySectors: PropertySector[]) { return await this.getRepositoryConnection(PropertySector).save(propertySectors); } + + async update(id: number, propertySector: PropertySector) { + return await this.getRepositoryConnection(PropertySector).update( + { id }, propertySector); + } + + async getSectorsByProperty(property: Property) { + const findOptions : FindOptions = PROPERTY_SECTOR_FIELDS; + findOptions.where = { property }; + return await this.getRepositoryConnection(PropertySector).find(findOptions); + } + + async getPropertySectorById(id: number) { + return await this.getRepositoryConnection(PropertySector).findOne({ id }); + } } export { PropertySectorRepository }; diff --git a/backend/src/services/PropertySectorService.ts b/backend/src/services/PropertySectorService.ts index dd13d56..76e6448 100644 --- a/backend/src/services/PropertySectorService.ts +++ b/backend/src/services/PropertySectorService.ts @@ -2,23 +2,88 @@ import { PropertySectorRepository } from '../repositories/PropertySectorReposito import { PropertySector } from '../entities/PropertySector'; import { BadRequestError } from '../errors/BadRequestError'; import { Property } from '../entities/Property'; +import { SectorService } from './SectorService'; +import { ActivityStatus } from '../enums/ActivityStatus'; +import { Sector } from '../entities/Sector'; +import { ResourceNotFoundError } from '../errors/ResourceNotFoundError'; +import { PropertyService } from './PropertyService'; class PropertySectorService { private propertySectorRepository : PropertySectorRepository = new PropertySectorRepository(); + private sectorService : SectorService = new SectorService(); + private propertyService : PropertyService = new PropertyService(); async createPropertySectors(propertyId: number, propertySectors: PropertySector[]) { try { - const property : Property = new Property(); - property.id = propertyId; + const property : Property = await this.propertyService.getPropertyById(propertyId); + if (!property) { + throw new ResourceNotFoundError(`Property with id ${propertyId} does not exist.`); + } + for (const propertySector of propertySectors) { propertySector.property = property; + propertySector.sector = await this.sectorService.getSectorByKind( + propertySector.sector.kind); + propertySector.status = ActivityStatus.ACTIVE; + } + + const filteredList : PropertySector[] = await this + .removeExistentPropertySectorsFromList(propertySectors); + + if (filteredList.length === 0) { + throw new BadRequestError('Property already includes all listed sectors.'); } - return await this.propertySectorRepository.save(propertySectors); + return await this.propertySectorRepository.save(filteredList); } catch (err) { throw new BadRequestError(err.message); } } + + + async getSectorsByProperty(property: Property) { + return await this.propertySectorRepository.getSectorsByProperty(property); + } + + async update(id: number, propertySector: PropertySector) { + + if (Object.keys(propertySector).length === 0) { + throw new BadRequestError('Object must include at least one parameter.'); + } + + const savedPropertySector : PropertySector = await this.getPropertySectorById(id); + if (!savedPropertySector) { + throw new ResourceNotFoundError(`Property sector id ${id} does not exist.`); + } + + return await this.propertySectorRepository.update(id, propertySector); + } + + async getPropertySectorById(id: number) { + return await this.propertySectorRepository.getPropertySectorById(id); + } + + // Assumes the property is same for each property sector in the list. + private async removeExistentPropertySectorsFromList(propertySectors: PropertySector[]) { + const savedPropertySectors : PropertySector[] = await this + .getSectorsByProperty(propertySectors[0].property); + console.log(savedPropertySectors); + const savedSectors : number[] = []; + + savedPropertySectors.forEach((savedPropertySector) => { + savedSectors.push(savedPropertySector.sector.id); + }); + + const filteredList : PropertySector[] = []; + + propertySectors.forEach((propertySector) => { + if (savedSectors.indexOf(propertySector.sector.id) === -1) { + filteredList.push(propertySector); + } + }); + + return filteredList; + } } export { PropertySectorService }; diff --git a/backend/src/utils/HttpUtils.ts b/backend/src/utils/HttpUtils.ts index 3e79b5f..d616784 100644 --- a/backend/src/utils/HttpUtils.ts +++ b/backend/src/utils/HttpUtils.ts @@ -1,10 +1,9 @@ -import { Response } from "express"; -import { HttpError } from "../errors/HttpError"; +import { Response } from 'express'; +import { HttpError } from '../errors/HttpError'; -export default function handleError(err: HttpError, res: Response) { +export const handleError = (err: HttpError, res: Response) => { if (err instanceof HttpError) { return res.status(err.statusCode).json(err); - } else { - return res.status(500).json(err); } -} + return res.status(500).json(err); +}; diff --git a/schema/schema.sql b/schema/schema.sql index e66174f..0426fa4 100644 --- a/schema/schema.sql +++ b/schema/schema.sql @@ -62,8 +62,10 @@ create table property_sectors( id bigint AUTO_INCREMENT PRIMARY KEY, property_id bigint NOT NULL, sector_id bigint NOT NULL, + status ENUM('ACTIVE', 'INACTIVE') NOT NULL, FOREIGN KEY (property_id) REFERENCES properties(id), - FOREIGN KEY (sector_id) REFERENCES sectors(id) + FOREIGN KEY (sector_id) REFERENCES sectors(id), + UNIQUE(property_id, sector_id) ); create table work_orders(