Skip to content

Commit

Permalink
feat: map marker endpoint (bloom-housing#4453) (#799)
Browse files Browse the repository at this point in the history
* feat: map marker endpoint

* feat: unit test

* feat: integration tests

* feat: correct unit test

* feat: style corrections
  • Loading branch information
mcgarrye authored Nov 6, 2024
1 parent bb8b1b5 commit 6ee2728
Show file tree
Hide file tree
Showing 13 changed files with 241 additions and 58 deletions.
46 changes: 29 additions & 17 deletions api/src/controllers/listing.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { ListingCreate } from '../dtos/listings/listing-create.dto';
import { ListingDuplicate } from '../dtos/listings/listing-duplicate.dto';
import { ListingCsvQueryParams } from '../dtos/listings/listing-csv-query-params.dto';
import { ListingFilterParams } from '../dtos/listings/listings-filter-params.dto';
import { ListingMapMarker } from '../dtos/listings/listing-map-marker.dto';
import { ListingsQueryParams } from '../dtos/listings/listings-query-params.dto';
import { ListingsRetrieveParams } from '../dtos/listings/listings-retrieve-params.dto';
import { ListingUpdate } from '../dtos/listings/listing-update.dto';
Expand Down Expand Up @@ -104,6 +105,16 @@ export class ListingController {
return await this.listingCsvExportService.exportFile(req, res, queryParams);
}

@Get('mapMarkers')
@ApiOperation({
summary: 'Get listing map markers',
operationId: 'mapMarkers',
})
@ApiOkResponse({ type: ListingMapMarker, isArray: true })
async mapMarkers() {
return await this.listingService.mapMarkers();
}

@Get(`external/:id`)
@ApiOperation({
summary: 'Get listing for external consumption by id',
Expand All @@ -124,23 +135,6 @@ export class ListingController {
);
}

@Get(`:id`)
@ApiOperation({ summary: 'Get listing by id', operationId: 'retrieve' })
@UseInterceptors(ClassSerializerInterceptor)
@UsePipes(new ValidationPipe(defaultValidationPipeOptions))
@ApiOkResponse({ type: Listing })
async retrieve(
@Headers('language') language: LanguagesEnum,
@Param('id', new ParseUUIDPipe({ version: '4' })) listingId: string,
@Query() queryParams: ListingsRetrieveParams,
) {
return await this.listingService.findOne(
listingId,
language,
queryParams.view,
);
}

@Post()
@ApiOperation({ summary: 'Create listing', operationId: 'create' })
@UseInterceptors(ClassSerializerInterceptor)
Expand Down Expand Up @@ -220,4 +214,22 @@ export class ListingController {
multiselectQuestionId,
);
}

// NestJS best practice to have get(':id') at the bottom of the file
@Get(`:id`)
@ApiOperation({ summary: 'Get listing by id', operationId: 'retrieve' })
@UseInterceptors(ClassSerializerInterceptor)
@UsePipes(new ValidationPipe(defaultValidationPipeOptions))
@ApiOkResponse({ type: Listing })
async retrieve(
@Headers('language') language: LanguagesEnum,
@Param('id', new ParseUUIDPipe({ version: '4' })) listingId: string,
@Query() queryParams: ListingsRetrieveParams,
) {
return await this.listingService.findOne(
listingId,
language,
queryParams.view,
);
}
}
22 changes: 22 additions & 0 deletions api/src/dtos/listings/listing-map-marker.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Expose } from 'class-transformer';
import { IsNumber, IsString, IsUUID } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { ValidationsGroupsEnum } from '../../enums/shared/validation-groups-enum';

export class ListingMapMarker {
@Expose()
@IsString({ groups: [ValidationsGroupsEnum.default] })
@IsUUID(4, { groups: [ValidationsGroupsEnum.default] })
@ApiProperty()
id: string;

@Expose()
@IsNumber({}, { groups: [ValidationsGroupsEnum.default] })
@ApiProperty()
lat: number;

@Expose()
@IsNumber({}, { groups: [ValidationsGroupsEnum.default] })
@ApiProperty()
lng: number;
}
23 changes: 23 additions & 0 deletions api/src/services/listing.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { AmiChart } from '../dtos/ami-charts/ami-chart.dto';
import { Listing } from '../dtos/listings/listing.dto';
import { ListingCreate } from '../dtos/listings/listing-create.dto';
import { ListingDuplicate } from '../dtos/listings/listing-duplicate.dto';
import { ListingMapMarker } from '../dtos/listings/listing-map-marker.dto';
import { ListingFilterParams } from '../dtos/listings/listings-filter-params.dto';
import { ListingsQueryParams } from '../dtos/listings/listings-query-params.dto';
import { ListingUpdate } from '../dtos/listings/listing-update.dto';
Expand Down Expand Up @@ -1809,4 +1810,26 @@ export class ListingService implements OnModuleInit {

return listing.jurisdictionId;
}

async mapMarkers(): Promise<ListingMapMarker[]> {
const listingsRaw = await this.prisma.listings.findMany({
select: {
id: true,
listingsBuildingAddress: true,
},
where: {
status: ListingsStatusEnum.active,
},
});

const listings = mapTo(Listing, listingsRaw);

return listings.map((listing) => {
return {
id: listing.id,
lat: listing.listingsBuildingAddress.latitude,
lng: listing.listingsBuildingAddress.longitude,
} as ListingMapMarker;
});
}
}
32 changes: 27 additions & 5 deletions api/test/integration/listing.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,11 +665,7 @@ describe('Listing Controller Tests', () => {

describe('duplicate endpoint', () => {
it('should duplicate listing, include units', async () => {
const jurisdictionA = await prisma.jurisdictions.create({
data: jurisdictionFactory(),
});
await reservedCommunityTypeFactoryAll(jurisdictionA.id, prisma);
const listingData = await listingFactory(jurisdictionA.id, prisma, {
const listingData = await listingFactory(jurisdictionAId, prisma, {
numberOfUnits: 2,
});
const listing = await prisma.listings.create({
Expand Down Expand Up @@ -1002,4 +998,30 @@ describe('Listing Controller Tests', () => {
);
});
});

describe('mapMarkers endpoint', () => {
it('should find all active listings', async () => {
const listingData = await listingFactory(jurisdictionAId, prisma);
const listing = await prisma.listings.create({
data: listingData,
});

const closedListingData = await listingFactory(jurisdictionAId, prisma, {
status: ListingsStatusEnum.closed,
});
const closedListing = await prisma.listings.create({
data: closedListingData,
});

const res = await request(app.getHttpServer())
.get('/listings/mapMarkers')
.expect(200);

expect(res.body.length).toBeGreaterThanOrEqual(1);

const ids = res.body.map((marker) => marker.id);
expect(ids).toContain(listing.id);
expect(ids).not.toContain(closedListing.id);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -1249,6 +1249,14 @@ describe('Testing Permissioning of endpoints as Admin User', () => {

expect(activityLogResult).not.toBeNull();
});

it('should succeed for mapMarkers endpoint', async () => {
await request(app.getHttpServer())
.get(`/listings/mapMarkers`)
.set({ passkey: process.env.API_PASS_KEY || '' })
.set('Cookie', cookies)
.expect(200);
});
});

describe('Testing application flagged set endpoints', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,14 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr

expect(activityLogResult).not.toBeNull();
});

it('should succeed for mapMarkers endpoint', async () => {
await request(app.getHttpServer())
.get(`/listings/mapMarkers`)
.set({ passkey: process.env.API_PASS_KEY || '' })
.set('Cookie', cookies)
.expect(200);
});
});

describe('Testing application flagged set endpoints', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,14 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the wron

expect(activityLogResult).not.toBeNull();
});

it('should succeed for mapMarkers endpoint', async () => {
await request(app.getHttpServer())
.get(`/listings/mapMarkers`)
.set({ passkey: process.env.API_PASS_KEY || '' })
.set('Cookie', cookies)
.expect(200);
});
});

describe('Testing application flagged set endpoints', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,14 @@ describe('Testing Permissioning of endpoints as logged out user', () => {
.set('Cookie', cookies)
.expect(403);
});

it('should succeed for mapMarkers endpoint', async () => {
await request(app.getHttpServer())
.get(`/listings/mapMarkers`)
.set({ passkey: process.env.API_PASS_KEY || '' })
.set('Cookie', cookies)
.expect(200);
});
});

describe('Testing application flagged set endpoints', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1139,6 +1139,14 @@ describe('Testing Permissioning of endpoints as partner with correct listing', (
.set('Cookie', cookies)
.expect(200);
});

it('should succeed for mapMarkers endpoint', async () => {
await request(app.getHttpServer())
.get(`/listings/mapMarkers`)
.set({ passkey: process.env.API_PASS_KEY || '' })
.set('Cookie', cookies)
.expect(200);
});
});

describe('Testing application flagged set endpoints', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,14 @@ describe('Testing Permissioning of endpoints as partner with wrong listing', ()
.set('Cookie', cookies)
.expect(200);
});

it('should succeed for mapMarkers endpoint', async () => {
await request(app.getHttpServer())
.get(`/listings/mapMarkers`)
.set({ passkey: process.env.API_PASS_KEY || '' })
.set('Cookie', cookies)
.expect(200);
});
});

describe('Testing application flagged set endpoints', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,14 @@ describe('Testing Permissioning of endpoints as public user', () => {
.set('Cookie', cookies)
.expect(403);
});

it('should succeed for mapMarkers endpoint', async () => {
await request(app.getHttpServer())
.get(`/listings/mapMarkers`)
.set({ passkey: process.env.API_PASS_KEY || '' })
.set('Cookie', cookies)
.expect(200);
});
});

describe('Testing application flagged set endpoints', () => {
Expand Down
23 changes: 23 additions & 0 deletions api/test/unit/services/listing.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3508,4 +3508,27 @@ describe('Testing listing service', () => {
expect(prisma.assets.deleteMany).not.toHaveBeenCalled();
});
});

describe('Test mapMarkers endpoint', () => {
it('should find all active listings', async () => {
prisma.listings.findMany = jest.fn().mockResolvedValue([
{
id: 'random id',
listingsBuildingAddress: exampleAddress,
},
]);

await service.mapMarkers();

expect(prisma.listings.findMany).toHaveBeenCalledWith({
select: {
id: true,
listingsBuildingAddress: true,
},
where: {
status: ListingsStatusEnum.active,
},
});
});
});
});
Loading

0 comments on commit 6ee2728

Please sign in to comment.