Skip to content

Commit 3d4d3d9

Browse files
fix: saving property for one jurisdiction (#5894)
1 parent 7f50f73 commit 3d4d3d9

15 files changed

+173
-105
lines changed

api/src/controllers/property.controller.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ import {
1111
ValidationPipe,
1212
Delete,
1313
UseGuards,
14+
Request,
1415
} from '@nestjs/common';
1516
import {
1617
ApiExtraModels,
1718
ApiOkResponse,
1819
ApiOperation,
1920
ApiTags,
2021
} from '@nestjs/swagger';
22+
import { Request as ExpressRequest } from 'express';
2123
import { PermissionTypeDecorator } from '../decorators/permission-type.decorator';
2224
import { PaginatedPropertyDto } from '../dtos/properties/paginated-property.dto';
2325
import { PropertyQueryParams } from '../dtos/properties/property-query-params.dto';
@@ -30,11 +32,13 @@ import { SuccessDTO } from '../dtos/shared/success.dto';
3032
import { defaultValidationPipeOptions } from '../utilities/default-validation-pipe-options';
3133
import { PaginationMeta } from '../dtos/shared/pagination.dto';
3234
import { JwtAuthGuard } from '../guards/jwt.guard';
33-
import { PermissionGuard } from '../guards/permission.guard';
3435
import { PermissionAction } from '../decorators/permission-action.decorator';
3536
import { permissionActions } from '../enums/permissions/permission-actions-enum';
3637
import { ApiKeyGuard } from '../guards/api-key.guard';
3738
import { PropertyFilterParams } from '../dtos/properties/property-filter-params.dto';
39+
import { mapTo } from '../utilities/mapTo';
40+
import { User } from '../../src/dtos/users/user.dto';
41+
import { PermissionGuard } from '../../src/guards/permission.guard';
3842

3943
@Controller('properties')
4044
@ApiTags('properties')
@@ -48,7 +52,7 @@ import { PropertyFilterParams } from '../dtos/properties/property-filter-params.
4852
IdDTO,
4953
)
5054
@PermissionTypeDecorator('properties')
51-
@UseGuards(ApiKeyGuard, JwtAuthGuard, PermissionGuard)
55+
@UseGuards(ApiKeyGuard, JwtAuthGuard)
5256
export class PropertyController {
5357
constructor(private readonly propertyService: PropertyService) {}
5458

@@ -59,6 +63,7 @@ export class PropertyController {
5963
})
6064
@UsePipes(new ValidationPipe(defaultValidationPipeOptions))
6165
@ApiOkResponse({ type: PaginatedPropertyDto })
66+
@UseGuards(PermissionGuard)
6267
public async getPaginatedSet(
6368
@Query() queryParams: PropertyQueryParams,
6469
): Promise<PaginatedPropertyDto> {
@@ -71,6 +76,7 @@ export class PropertyController {
7176
operationId: 'getById',
7277
})
7378
@ApiOkResponse({ type: Property })
79+
@UseGuards(PermissionGuard)
7480
public async getPropertyById(
7581
@Param('id', new ParseUUIDPipe({ version: '4' })) propertyId: string,
7682
): Promise<Property> {
@@ -85,6 +91,7 @@ export class PropertyController {
8591
@PermissionAction(permissionActions.read)
8692
@UsePipes(new ValidationPipe(defaultValidationPipeOptions))
8793
@ApiOkResponse({ type: PaginatedPropertyDto })
94+
@UseGuards(PermissionGuard)
8895
public async getFiltrablePaginatedSet(
8996
@Body() queryParams: PropertyQueryParams,
9097
): Promise<PaginatedPropertyDto> {
@@ -100,8 +107,12 @@ export class PropertyController {
100107
@ApiOkResponse({ type: Property })
101108
public async addProperty(
102109
@Body() propertyDto: PropertyCreate,
110+
@Request() req: ExpressRequest,
103111
): Promise<Property> {
104-
return await this.propertyService.create(propertyDto);
112+
return await this.propertyService.create(
113+
propertyDto,
114+
mapTo(User, req['user']),
115+
);
105116
}
106117

107118
@Put()
@@ -113,8 +124,12 @@ export class PropertyController {
113124
@ApiOkResponse({ type: Property })
114125
public async updateProperty(
115126
@Body() propertyDto: PropertyUpdate,
127+
@Request() req: ExpressRequest,
116128
): Promise<Property> {
117-
return await this.propertyService.update(propertyDto);
129+
return await this.propertyService.update(
130+
propertyDto,
131+
mapTo(User, req['user']),
132+
);
118133
}
119134

120135
@Delete()
@@ -124,7 +139,13 @@ export class PropertyController {
124139
})
125140
@UsePipes(new ValidationPipe(defaultValidationPipeOptions))
126141
@ApiOkResponse({ type: SuccessDTO })
127-
public async deleteById(@Body() idDto: IdDTO): Promise<SuccessDTO> {
128-
return await this.propertyService.deleteOne(idDto.id);
142+
public async deleteById(
143+
@Body() idDto: IdDTO,
144+
@Request() req: ExpressRequest,
145+
): Promise<SuccessDTO> {
146+
return await this.propertyService.deleteOne(
147+
idDto.id,
148+
mapTo(User, req['user']),
149+
);
129150
}
130151
}

api/src/permission-configs/permission_policy.csv

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ p, supportAdmin, properties, true, read
2828
p, jurisdictionAdmin, properties, true, read
2929
p, limitedJurisdictionAdmin, properties, true, read
3030
p, partner, properties, true, read
31-
p, anonymous, properties, true, read
3231

3332
p, admin, agency, true, .*
3433
p, supportAdmin, agency, true, read
@@ -54,18 +53,6 @@ p, jurisdictionAdmin, listingEvent, true, .*
5453
p, limitedJurisdictionAdmin, listingEvent, true, .*
5554
p, partner, listingEvent, true, read
5655

57-
p, admin, property, true, .*
58-
p, supportAdmin, property, true, .*
59-
p, jurisdictionAdmin, property, true, .*
60-
p, limitedJurisdictionAdmin, property, true, .*
61-
p, partner, property, true, read
62-
63-
p, admin, propertyGroup, true, .*
64-
p, supportAdmin, propertyGroup, true, .*
65-
p, jurisdictionAdmin, propertyGroup, true, .*
66-
p, limitedJurisdictionAdmin, propertyGroup, true, .*
67-
p, partner, propertyGroup, true, read
68-
6956
p, admin, amiChart, true, .*
7057
p, supportAdmin, amiChart, true, .*
7158
p, jurisdictionAdmin, amiChart, true, .*

api/src/services/property.service.ts

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,19 @@ import { Property } from '../dtos/properties/property.dto';
1515
import { PaginatedPropertyDto } from '../dtos/properties/paginated-property.dto';
1616
import PropertyCreate from '../dtos/properties/property-create.dto';
1717
import { PropertyUpdate } from '../dtos/properties/property-update.dto';
18+
import { User } from '../dtos/users/user.dto';
19+
import { permissionActions } from '../enums/permissions/permission-actions-enum';
1820
import { SuccessDTO } from '../dtos/shared/success.dto';
1921
import { Prisma } from '@prisma/client';
2022
import { buildFilter } from '../utilities/build-filter';
23+
import { PermissionService } from './permission.service';
2124

2225
@Injectable()
2326
export class PropertyService {
24-
constructor(private prisma: PrismaService) {}
27+
constructor(
28+
private prisma: PrismaService,
29+
private permissionService: PermissionService,
30+
) {}
2531

2632
/**
2733
* Returns a paginated list of properties matching the provided query parameters.
@@ -99,7 +105,7 @@ export class PropertyService {
99105
* @throws {BadRequestException} If a jurisdiction is not provided.
100106
* @throws {NotFoundException} If the linked jurisdiction cannot be found.
101107
*/
102-
async create(propertyDto: PropertyCreate) {
108+
async create(propertyDto: PropertyCreate, requestingUser: User) {
103109
if (!propertyDto.jurisdictions) {
104110
throw new BadRequestException('A jurisdiction must be provided');
105111
}
@@ -120,6 +126,15 @@ export class PropertyService {
120126
);
121127
}
122128

129+
await this.permissionService.canOrThrow(
130+
requestingUser,
131+
'properties',
132+
permissionActions.create,
133+
{
134+
jurisdictionId: rawJurisdiction.id,
135+
},
136+
);
137+
123138
const rawProperty = await this.prisma.properties.create({
124139
data: {
125140
...propertyDto,
@@ -147,7 +162,7 @@ export class PropertyService {
147162
* @throws {BadRequestException} If a jurisdiction is not provided.
148163
* @throws {NotFoundException} If the linked jurisdiction cannot be found.
149164
*/
150-
async update(propertyDto: PropertyUpdate) {
165+
async update(propertyDto: PropertyUpdate, requestingUser: User) {
151166
if (!propertyDto.jurisdictions) {
152167
throw new BadRequestException('A jurisdiction must be provided');
153168
}
@@ -169,6 +184,17 @@ export class PropertyService {
169184

170185
await this.findOrThrow(propertyDto.id);
171186

187+
await this.permissionService.canOrThrow(
188+
requestingUser,
189+
'properties',
190+
permissionActions.update,
191+
{
192+
jurisdictionId: rawJurisdiction.id,
193+
},
194+
);
195+
196+
await this.findOrThrow(propertyDto.id);
197+
172198
const rawProperty = await this.prisma.properties.update({
173199
data: {
174200
...propertyDto,
@@ -199,7 +225,7 @@ export class PropertyService {
199225
* @throws {BadRequestException} If no property ID is provided.
200226
* @throws {NotFoundException} If the property or its linked jurisdiction is not found.
201227
*/
202-
async deleteOne(propertyId: string) {
228+
async deleteOne(propertyId: string, requestingUser: User) {
203229
if (!propertyId) {
204230
throw new BadRequestException('a property ID must be provided');
205231
}
@@ -227,6 +253,15 @@ export class PropertyService {
227253
);
228254
}
229255

256+
await this.permissionService.canOrThrow(
257+
requestingUser,
258+
'properties',
259+
permissionActions.delete,
260+
{
261+
jurisdictionId: rawJurisdiction.id,
262+
},
263+
);
264+
230265
await this.prisma.properties.delete({
231266
where: {
232267
id: propertyId,
@@ -243,7 +278,7 @@ export class PropertyService {
243278
*
244279
* @param propertyId - The ID of the property to look up.
245280
* @returns The raw property entity including its jurisdictions.
246-
* @throws {BadRequestException} If no property is found for the given ID.
281+
* @throws {NotFoundException} If no property is found for the given ID.
247282
*/
248283
async findOrThrow(propertyId: string): Promise<Property> {
249284
const property = await this.prisma.properties.findFirst({
@@ -256,7 +291,7 @@ export class PropertyService {
256291
});
257292

258293
if (!property) {
259-
throw new BadRequestException(
294+
throw new NotFoundException(
260295
`Property with id ${propertyId} was not found`,
261296
);
262297
}

api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1479,7 +1479,7 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr
14791479
.expect(200);
14801480
});
14811481

1482-
it('should error as forbidden for create endpoint', async () => {
1482+
it('should succeed for create endpoint', async () => {
14831483
const propertyData = {
14841484
name: 'New Test Property',
14851485
jurisdictions: {
@@ -1492,7 +1492,7 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr
14921492
.send(propertyData)
14931493
.set({ passkey: process.env.API_PASS_KEY || '' })
14941494
.set('Cookie', cookies)
1495-
.expect(403);
1495+
.expect(201);
14961496
});
14971497

14981498
it('should succeed for filterable list endpoint', async () => {
@@ -1504,7 +1504,7 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr
15041504
.expect(201);
15051505
});
15061506

1507-
it('should error as forbidden for update endpoint', async () => {
1507+
it('should succeed for update endpoint', async () => {
15081508
if (!propertyId) {
15091509
throw new Error('Property ID not set up for test');
15101510
}
@@ -1522,10 +1522,10 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr
15221522
.send(propertyUpdateData)
15231523
.set({ passkey: process.env.API_PASS_KEY || '' })
15241524
.set('Cookie', cookies)
1525-
.expect(403);
1525+
.expect(200);
15261526
});
15271527

1528-
it('should error as forbidden for delete endpoint', async () => {
1528+
it('should succeed for delete endpoint', async () => {
15291529
const propertyData = {
15301530
name: 'Property to Delete',
15311531
jurisdictions: {
@@ -1553,7 +1553,7 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr
15531553
} as IdDTO)
15541554
.set({ passkey: process.env.API_PASS_KEY || '' })
15551555
.set('Cookie', cookies)
1556-
.expect(403);
1556+
.expect(200);
15571557
});
15581558
});
15591559

api/test/integration/permission-tests/permission-as-public.e2e-spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,15 +1380,15 @@ describe('Testing Permissioning of endpoints as public user', () => {
13801380
}
13811381
});
13821382

1383-
it('should succeed for list endpoint', async () => {
1383+
it('should error as forbidden for list endpoint', async () => {
13841384
await request(app.getHttpServer())
13851385
.get(`/properties?`)
13861386
.set({ passkey: process.env.API_PASS_KEY || '' })
13871387
.set('Cookie', cookies)
1388-
.expect(200);
1388+
.expect(403);
13891389
});
13901390

1391-
it('should succeed for retrieve endpoint', async () => {
1391+
it('should error as forbidden for retrieve endpoint', async () => {
13921392
if (!propertyId) {
13931393
throw new Error('Property ID not set up for test');
13941394
}
@@ -1397,7 +1397,7 @@ describe('Testing Permissioning of endpoints as public user', () => {
13971397
.get(`/properties/${propertyId}`)
13981398
.set({ passkey: process.env.API_PASS_KEY || '' })
13991399
.set('Cookie', cookies)
1400-
.expect(200);
1400+
.expect(403);
14011401
});
14021402

14031403
it('should error as forbidden for create endpoint', async () => {
@@ -1416,13 +1416,13 @@ describe('Testing Permissioning of endpoints as public user', () => {
14161416
.expect(403);
14171417
});
14181418

1419-
it('should succeed for filterable list endpoint', async () => {
1419+
it('should error as forbidden for filterable list endpoint', async () => {
14201420
await request(app.getHttpServer())
14211421
.post(`/properties/list`)
14221422
.send({})
14231423
.set({ passkey: process.env.API_PASS_KEY || '' })
14241424
.set('Cookie', cookies)
1425-
.expect(201);
1425+
.expect(403);
14261426
});
14271427

14281428
it('should error as forbidden for update endpoint', async () => {

api/test/integration/property.e2e-spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ describe('Properties Controller Tests', () => {
385385
expect(res.body.message[0]).toEqual('id should not be null or undefined');
386386
});
387387

388-
it('should throw error when an given ID does not exist', async () => {
388+
it('should throw error when a given ID does not exist', async () => {
389389
const randId = randomUUID();
390390
const res = await request(app.getHttpServer())
391391
.put('/properties')
@@ -396,7 +396,7 @@ describe('Properties Controller Tests', () => {
396396
})
397397
.set({ passkey: process.env.API_PASS_KEY || '' })
398398
.set('Cookie', cookies)
399-
.expect(400);
399+
.expect(404);
400400

401401
expect(res.body.message).toEqual(
402402
`Property with id ${randId} was not found`,
@@ -453,7 +453,7 @@ describe('Properties Controller Tests', () => {
453453
expect(res.body.message[0]).toBe('id should not be null or undefined');
454454
});
455455

456-
it('should throw error when an given ID does not exist', async () => {
456+
it('should throw error when a given ID does not exist', async () => {
457457
const randId = randomUUID();
458458
const res = await request(app.getHttpServer())
459459
.delete('/properties')
@@ -462,7 +462,7 @@ describe('Properties Controller Tests', () => {
462462
})
463463
.set({ passkey: process.env.API_PASS_KEY || '' })
464464
.set('Cookie', cookies)
465-
.expect(400);
465+
.expect(404);
466466

467467
expect(res.body.message).toBe(`Property with id ${randId} was not found`);
468468
});

api/test/unit/services/geocoding.service.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ describe('GeocodingService', () => {
2020
const date = new Date();
2121
const address: Address = {
2222
id: 'id',
23-
createdAt: date,
24-
updatedAt: date,
2523
city: 'Washington',
2624
county: null,
2725
state: 'DC',

0 commit comments

Comments
 (0)