Skip to content

Commit 8d7b2bb

Browse files
authored
Merge pull request #45 from hivetown/feat/carrosProdutores
Feat/carros produtores
2 parents ef9dfe2 + e158272 commit 8d7b2bb

File tree

8 files changed

+461
-5
lines changed

8 files changed

+461
-5
lines changed

src/controllers/producers.ts

Lines changed: 221 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Joi, validate } from 'express-validation';
44
import { container } from '..';
55
import type { ProducerProductOptions } from '../interfaces/ProducerProductOptions';
66
import { Controller, Delete, Get, Params, Post, Put, Request, Response } from '@decorators/express';
7-
import { Producer, ProducerProduct, ProductionUnit, ShipmentEvent, ShipmentStatus, User } from '../entities';
7+
import { Carrier, Image, Producer, ProducerProduct, ProductionUnit, ShipmentEvent, ShipmentStatus, User } from '../entities';
88
import { authenticationMiddleware, authorizationMiddleware } from '../middlewares';
99
import { UniqueConstraintViolationException } from '@mikro-orm/core';
1010
import { ConflictError } from '../errors/ConflictError';
@@ -984,4 +984,224 @@ export class ProducersController {
984984

985985
return res.status(201).json(shipment);
986986
}
987+
988+
@Get('/:producerId/carriers', [
989+
validate({
990+
params: Joi.object({
991+
producerId: Joi.number().min(1).required()
992+
}),
993+
query: Joi.object({
994+
page: Joi.number().min(1).optional(),
995+
pageSize: Joi.number().min(1).optional()
996+
})
997+
}),
998+
authenticationMiddleware,
999+
authorizationMiddleware({
1000+
permissions: Permission.READ_OTHER_PRODUCER,
1001+
otherValidations: [
1002+
(user, req) =>
1003+
user.id === Number(req.params.producerId) ||
1004+
throwError(
1005+
new ForbiddenError("User may not interact with others' production units", {
1006+
user: user.id,
1007+
producer: Number(req.params.producerId)
1008+
})
1009+
)
1010+
]
1011+
})
1012+
])
1013+
public async getCarriersOfProducer(@Request() req: Express.Request, @Response() res: Express.Response, @Params('producerId') producerId: number) {
1014+
const producer = await container.producerGateway.findById(producerId);
1015+
if (!producer) throw new NotFoundError('Producer not found');
1016+
1017+
const options: PaginatedOptions = {
1018+
page: Number(req.query.page) || -1,
1019+
size: Number(req.query.pageSize) || -1
1020+
};
1021+
1022+
const carriers = await container.carrierGateway.findAllByProducerId(producer.user.id, options);
1023+
1024+
return res.status(200).json(carriers);
1025+
}
1026+
1027+
@Post('/:producerId/carriers', [
1028+
validate({
1029+
params: Joi.object({
1030+
producerId: Joi.number().min(1).required()
1031+
}),
1032+
body: Joi.object({
1033+
licensePlate: Joi.string().required(),
1034+
image: Joi.object({
1035+
name: Joi.string().required(),
1036+
url: Joi.string().required(),
1037+
alt: Joi.string().required()
1038+
}).required(),
1039+
productionUnitId: Joi.number().min(1).required()
1040+
})
1041+
}),
1042+
authenticationMiddleware,
1043+
authorizationMiddleware({
1044+
permissions: Permission.WRITE_OTHER_PRODUCER,
1045+
otherValidations: [
1046+
(user, req) =>
1047+
user.id === Number(req.params.producerId) ||
1048+
throwError(
1049+
new ForbiddenError("User may not interact with others' production units", {
1050+
user: user.id,
1051+
producer: Number(req.params.producerId)
1052+
})
1053+
)
1054+
]
1055+
})
1056+
])
1057+
public async createCarrier(@Request() req: Express.Request, @Response() res: Express.Response, @Params('producerId') producerId: number) {
1058+
const producer = await container.producerGateway.findById(producerId);
1059+
if (!producer) throw new NotFoundError('Producer not found');
1060+
1061+
const productionUnit = await container.productionUnitGateway.findOneFromProducer(producer.user.id, req.body.productionUnitId);
1062+
if (!productionUnit) throw new NotFoundError('Production unit not found');
1063+
1064+
const carrier = new Carrier(req.body.licensePlate, productionUnit, new Image(req.body.image.name, req.body.image.url, req.body.image.alt));
1065+
await container.carrierGateway.createOrUpdate(carrier);
1066+
1067+
return res.status(201).json(carrier);
1068+
}
1069+
1070+
@Get('/:producerId/carriers/:carrierId', [
1071+
validate({
1072+
params: Joi.object({
1073+
producerId: Joi.number().min(1).required(),
1074+
carrierId: Joi.number().min(1).required()
1075+
})
1076+
}),
1077+
authenticationMiddleware,
1078+
authorizationMiddleware({
1079+
permissions: Permission.READ_OTHER_PRODUCER,
1080+
otherValidations: [
1081+
(user, req) =>
1082+
user.id === Number(req.params.producerId) ||
1083+
throwError(
1084+
new ForbiddenError("User may not interact with others' production units", {
1085+
user: user.id,
1086+
producer: Number(req.params.producerId)
1087+
})
1088+
)
1089+
]
1090+
})
1091+
])
1092+
public async getCarrierOfProducer(
1093+
@Response() res: Express.Response,
1094+
@Params('producerId') producerId: number,
1095+
@Params('carrierId') carrierId: number
1096+
) {
1097+
const producer = await container.producerGateway.findById(producerId);
1098+
if (!producer) throw new NotFoundError('Producer not found');
1099+
1100+
const carrier = await container.carrierGateway.findOneOfProducer(producer.user.id, carrierId);
1101+
if (!carrier) throw new NotFoundError('Carrier not found');
1102+
1103+
return res.status(200).json({
1104+
id: carrier.id,
1105+
licensePlate: carrier.licensePlate,
1106+
status: carrier.status,
1107+
image: carrier.image,
1108+
productionUnit: carrier.productionUnit,
1109+
lastShipmentEvent: carrier.getLastShipmentEvent()
1110+
});
1111+
}
1112+
1113+
@Put('/:producerId/carriers/:carrierId', [
1114+
validate({
1115+
params: Joi.object({
1116+
producerId: Joi.number().min(1).required(),
1117+
carrierId: Joi.number().min(1).required()
1118+
}),
1119+
body: Joi.object({
1120+
image: Joi.object({
1121+
name: Joi.string().required(),
1122+
url: Joi.string().required(),
1123+
alt: Joi.string().required()
1124+
}).required(),
1125+
productionUnitId: Joi.number().min(1).required(),
1126+
status: Joi.string().valid(CarrierStatus.Available, CarrierStatus.Unavailable).required()
1127+
})
1128+
}),
1129+
authenticationMiddleware,
1130+
authorizationMiddleware({
1131+
permissions: Permission.WRITE_OTHER_PRODUCER,
1132+
otherValidations: [
1133+
(user, req) =>
1134+
user.id === Number(req.params.producerId) ||
1135+
throwError(
1136+
new ForbiddenError("User may not interact with others' production units", {
1137+
user: user.id,
1138+
producer: Number(req.params.producerId)
1139+
})
1140+
)
1141+
]
1142+
})
1143+
])
1144+
public async updateCarrierOfProducer(
1145+
@Request() req: Express.Request,
1146+
@Response() res: Express.Response,
1147+
@Params('producerId') producerId: number,
1148+
@Params('carrierId') carrierId: number
1149+
) {
1150+
const producer = await container.producerGateway.findById(producerId);
1151+
if (!producer) throw new NotFoundError('Producer not found');
1152+
1153+
const carrier = await container.carrierGateway.findOneOfProducer(producer.user.id, carrierId);
1154+
if (!carrier) throw new NotFoundError('Carrier not found');
1155+
1156+
const productionUnit = await container.productionUnitGateway.findOneFromProducer(producer.user.id, req.body.productionUnitId);
1157+
if (!productionUnit) throw new NotFoundError('Production unit not found');
1158+
1159+
carrier.image = new Image(req.body.image.name, req.body.image.url, req.body.image.alt);
1160+
carrier.productionUnit = productionUnit;
1161+
carrier.status = req.body.status;
1162+
await container.carrierGateway.createOrUpdate(carrier);
1163+
1164+
return res.status(200).json(carrier);
1165+
}
1166+
1167+
@Delete('/:producerId/carriers/:carrierId', [
1168+
validate({
1169+
params: Joi.object({
1170+
producerId: Joi.number().min(1).required(),
1171+
carrierId: Joi.number().min(1).required()
1172+
})
1173+
}),
1174+
authenticationMiddleware,
1175+
authorizationMiddleware({
1176+
permissions: Permission.WRITE_OTHER_PRODUCER,
1177+
otherValidations: [
1178+
(user, req) =>
1179+
user.id === Number(req.params.producerId) ||
1180+
throwError(
1181+
new ForbiddenError("User may not interact with others' production units", {
1182+
user: user.id,
1183+
producer: Number(req.params.producerId)
1184+
})
1185+
)
1186+
]
1187+
})
1188+
])
1189+
public async deleteCarrierOfProducer(
1190+
@Response() res: Express.Response,
1191+
@Params('producerId') producerId: number,
1192+
@Params('carrierId') carrierId: number
1193+
) {
1194+
const producer = await container.producerGateway.findById(producerId);
1195+
if (!producer) throw new NotFoundError('Producer not found');
1196+
1197+
const carrier = await container.carrierGateway.findOneOfProducer(producer.user.id, carrierId);
1198+
if (!carrier) throw new NotFoundError('Carrier not found');
1199+
1200+
if (carrier.status === CarrierStatus.Unavailable) throw new ForbiddenError('Carrier is unavailable so it cannot be deleted');
1201+
1202+
carrier.deletedAt = new Date();
1203+
await container.carrierGateway.createOrUpdate(carrier);
1204+
1205+
return res.status(204).send();
1206+
}
9871207
}

src/entities/Carrier.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ProductionUnit } from './ProductionUnit';
33
import { CarrierStatus } from '../enums/CarrierStatus';
44
import { Shipment } from './Shipment';
55
import type { Image } from './Image';
6+
import type { ShipmentEvent } from './ShipmentEvent';
67

78
@Entity()
89
export class Carrier {
@@ -23,4 +24,25 @@ export class Carrier {
2324

2425
@OneToOne({ eager: true })
2526
public image?: Image;
27+
28+
@Property({ nullable: true })
29+
public deletedAt?: Date;
30+
31+
public constructor(licensePlate: string, productionUnit: ProductionUnit, image: Image) {
32+
this.licensePlate = licensePlate;
33+
this.productionUnit = productionUnit;
34+
this.image = image;
35+
this.status = CarrierStatus.Available;
36+
}
37+
38+
public getLastShipmentEvent(): ShipmentEvent | null {
39+
if (this.shipments.length === 0) {
40+
return null;
41+
}
42+
const lastShipment = this.shipments[this.shipments.length - 1];
43+
if (lastShipment.events.length === 0) {
44+
return null;
45+
}
46+
return lastShipment.getLastEvent();
47+
}
2648
}

src/gateways/CarrierGateway.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ export class CarrierGateway {
1515
const pagination = paginate(options);
1616
const [carriers, totalResults] = await Promise.all([
1717
this.repository.find(
18-
{ productionUnit: productionUnitId, status: 'UNAVAILABLE' },
18+
{ productionUnit: productionUnitId, status: 'UNAVAILABLE', deletedAt: null },
1919
{
2020
populate: ['productionUnit'],
2121
limit: pagination.limit,
2222
offset: pagination.offset
2323
}
2424
),
25-
this.repository.count({ productionUnit: productionUnitId, status: 'UNAVAILABLE' })
25+
this.repository.count({ productionUnit: productionUnitId, status: 'UNAVAILABLE', deletedAt: null })
2626
]);
2727
return {
2828
items: carriers,
@@ -32,4 +32,41 @@ export class CarrierGateway {
3232
pageSize: carriers.length
3333
};
3434
}
35+
36+
public async findAllByProducerId(producerId: number, options: PaginatedOptions): Promise<BaseItems<Carrier>> {
37+
const paginataion = paginate(options);
38+
const [carriers, totalResults] = await Promise.all([
39+
this.repository.find(
40+
{ productionUnit: { producer: { user: producerId } }, deletedAt: null },
41+
{
42+
populate: ['productionUnit'],
43+
limit: paginataion.limit,
44+
offset: paginataion.offset
45+
}
46+
),
47+
this.repository.count({ productionUnit: { producer: { user: producerId } }, deletedAt: null })
48+
]);
49+
50+
return {
51+
items: carriers,
52+
totalItems: totalResults,
53+
totalPages: Math.ceil(totalResults / paginataion.limit),
54+
page: Math.ceil(paginataion.offset / paginataion.limit) + 1,
55+
pageSize: carriers.length
56+
};
57+
}
58+
59+
public async findOneOfProducer(producerId: number, carrierId: number): Promise<Carrier | null> {
60+
const carrier = await this.repository.findOne(
61+
{ productionUnit: { producer: { user: producerId } }, id: carrierId, deletedAt: null },
62+
{ populate: ['productionUnit', 'productionUnit.address', 'shipments.events', 'shipments.events.address'] }
63+
);
64+
65+
return carrier;
66+
}
67+
68+
public async createOrUpdate(carrier: Carrier): Promise<Carrier> {
69+
await this.repository.persistAndFlush(carrier);
70+
return carrier;
71+
}
3572
}

src/gateways/ProductionUnitGateway.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,12 @@ export class ProductionUnitGateway {
9191
);
9292
return productionUnits;
9393
}
94+
95+
public async findOneFromProducer(producerId: number, productionUnitId: number): Promise<ProductionUnit | null> {
96+
const productionUnit = await this.repository.findOne({
97+
id: productionUnitId,
98+
producer: { user: producerId }
99+
});
100+
return productionUnit;
101+
}
94102
}

src/migrations/.snapshot-hivetown.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,7 @@
933933
"autoincrement": false,
934934
"primary": false,
935935
"nullable": false,
936+
"default": "'AVAILABLE'",
936937
"enumItems": ["AVAILABLE", "UNAVAILABLE"],
937938
"mappedType": "enum"
938939
},
@@ -944,6 +945,16 @@
944945
"primary": false,
945946
"nullable": true,
946947
"mappedType": "integer"
948+
},
949+
"deleted_at": {
950+
"name": "deleted_at",
951+
"type": "datetime",
952+
"unsigned": false,
953+
"autoincrement": false,
954+
"primary": false,
955+
"nullable": true,
956+
"length": 0,
957+
"mappedType": "datetime"
947958
}
948959
},
949960
"name": "carrier",

0 commit comments

Comments
 (0)