Skip to content

Commit

Permalink
Merge pull request #43 from krystxf/fix/be-swagger-mark-endpoints-as-…
Browse files Browse the repository at this point in the history
…deprecated

refactor(be): mark old endpoints as deprecated in swagger
  • Loading branch information
krystxf authored Nov 14, 2024
2 parents b0ad93e + 86f3d0d commit 18432d8
Show file tree
Hide file tree
Showing 7 changed files with 367 additions and 20 deletions.
1 change: 1 addition & 0 deletions apps/backend/src/enums/endpoint-version.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export enum EndpointVersion {
v1 = "1",
v2 = "2",
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { PrismaService } from "src/modules/prisma/prisma.service";
import { getDelayInSeconds } from "src/utils/delay";

@Injectable()
export class DepartureService {
export class DepartureServiceV1 {
constructor(
private prisma: PrismaService,
private golemioService: GolemioService,
Expand Down Expand Up @@ -53,15 +53,18 @@ export class DepartureService {
const searchParams = new URLSearchParams(
allPlatformIds
.map((id) => ["ids", id])
.concat([
["skip", "canceled"],
["mode", "departures"],
["order", "real"],
]),
.concat(
Object.entries({
skip: "canceled",
mode: "departures",
order: "real",
minutesAfter: String(24 * 60),
}),
),
);

const res = await this.golemioService.getGolemioData(
`/v2/pid/departureboards?minutesAfter=600&${searchParams.toString()}`,
`/v2/pid/departureboards?${searchParams}`,
);

if (!res.ok) {
Expand Down
97 changes: 97 additions & 0 deletions apps/backend/src/modules/departure/departure-v2.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Injectable } from "@nestjs/common";
import { unique } from "radash";

import { departureBoardsSchema } from "src/modules/departure/schema/departure-boards.schema";
import type { DepartureSchema } from "src/modules/departure/schema/departure.schema";
import { GolemioService } from "src/modules/golemio/golemio.service";
import { PrismaService } from "src/modules/prisma/prisma.service";
import { getDelayInSeconds } from "src/utils/delay";

@Injectable()
export class DepartureServiceV2 {
constructor(
private prisma: PrismaService,
private golemioService: GolemioService,
) {}

async getDepartures(args: {
stopIds: string[];
platformIds: string[];
metroOnly: boolean;
}): Promise<DepartureSchema[]> {
const dbPlatforms = (
await this.prisma.platform.findMany({
select: { id: true },
where: {
id: { in: args.platformIds },
...(args.metroOnly ? { isMetro: true } : {}),
},
})
).map((platform) => platform.id);

const stopPlatforms = (
await this.prisma.stop.findMany({
select: {
platforms: {
select: { id: true },
where: { ...(args.metroOnly ? { isMetro: true } : {}) },
},
},
where: { id: { in: args.stopIds } },
})
).flatMap((stop) => stop.platforms.map((platform) => platform.id));

const allPlatformIds = unique([...dbPlatforms, ...stopPlatforms]).slice(
0,
100,
);

if (allPlatformIds.length === 0) {
return [];
}

const searchParams = new URLSearchParams(
allPlatformIds
.map((id) => ["ids", id])
.concat(
Object.entries({
skip: "canceled",
mode: "departures",
order: "real",
minutesBefore: String(5),
minutesAfter: String(10 * 60),
}),
),
);

const res = await this.golemioService.getGolemioData(
`/v2/pid/departureboards&${searchParams.toString()}`,
);

if (!res.ok) {
throw new Error(
`Failed to fetch departure data: ${res.status} ${res.statusText}`,
);
}

const json = await res.json();
const parsed = departureBoardsSchema.safeParse(json);

if (!parsed.success) {
throw new Error(parsed.error.message);
}

const parsedDepartures = parsed.data.departures.map((departure) => {
return {
departure: departure.departure_timestamp,
delay: getDelayInSeconds(departure.delay),
headsign: departure.trip.headsign,
route: departure.route.short_name,
platformId: departure.stop.id,
platformCode: departure.stop.platform_code,
};
});

return parsedDepartures;
}
}
131 changes: 124 additions & 7 deletions apps/backend/src/modules/departure/departure.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import {
Version,
VERSION_NEUTRAL,
} from "@nestjs/common";
import { ApiTags } from "@nestjs/swagger";
import { ApiOperation, ApiTags } from "@nestjs/swagger";
import { z } from "zod";

import { QUERY_IDS_COUNT_MAX } from "src/constants/constants";
import { ApiQueries } from "src/decorators/swagger.decorator";
import { ApiDescription, ApiQueries } from "src/decorators/swagger.decorator";
import { EndpointVersion } from "src/enums/endpoint-version";
import { DepartureService } from "src/modules/departure/departure.service";
import { DepartureServiceV1 } from "src/modules/departure/departure-v1.service";
import { DepartureServiceV2 } from "src/modules/departure/departure-v2.service";
import {
departureSchema,
type DepartureSchema,
Expand All @@ -30,10 +31,13 @@ import { toArray } from "src/utils/array.utils";
@UseInterceptors(CacheInterceptor, LogInterceptor)
@CacheTTL(4 * 1000)
export class DepartureController {
constructor(private readonly departureService: DepartureService) {}
constructor(
private readonly departureServiceV1: DepartureServiceV1,
private readonly departureServiceV2: DepartureServiceV2,
) {}

@Get()
@Version([VERSION_NEUTRAL, EndpointVersion.v1])
@Version([VERSION_NEUTRAL])
@ApiQueries([
metroOnlyQuery,
{
Expand All @@ -53,6 +57,12 @@ export class DepartureController {
required: false,
},
])
@ApiOperation({
deprecated: true,
})
@ApiDescription({
deprecated: true,
})
async getDepartures(@Query() query): Promise<DepartureSchema[]> {
const schema = z.object({
metroOnly: metroOnlySchema,
Expand All @@ -75,7 +85,59 @@ export class DepartureController {
);
}

const departures = await this.departureService.getDepartures({
const departures = await this.departureServiceV1.getDepartures({
stopIds: parsedQuery.stop,
platformIds: parsedQuery.platform,
metroOnly: parsedQuery.metroOnly,
});

return departureSchema.array().parse(departures);
}

@Get()
@Version([EndpointVersion.v1])
@ApiQueries([
metroOnlyQuery,
{
name: "platform[]",
description: "Platform IDs",
type: String,
isArray: true,
allowEmptyValue: true,
required: false,
},
{
name: "stop[]",
description: "Stop IDs",
type: String,
isArray: true,
allowEmptyValue: true,
required: false,
},
])
async getDeparturesV1(@Query() query): Promise<DepartureSchema[]> {
const schema = z.object({
metroOnly: metroOnlySchema,
platform: z.string().array().optional().default([]),
stop: z.string().array().optional().default([]),
});
const parsed = schema.safeParse(query);
if (!parsed.success) {
throw new HttpException(
"Invalid query params",
HttpStatus.BAD_REQUEST,
);
}
const parsedQuery = parsed.data;

if (parsedQuery.platform.length + parsedQuery.stop.length === 0) {
throw new HttpException(
"At least one platform or stop ID must be provided",
HttpStatus.BAD_REQUEST,
);
}

const departures = await this.departureServiceV1.getDepartures({
stopIds: parsedQuery.stop,
platformIds: parsedQuery.platform,
metroOnly: parsedQuery.metroOnly,
Expand All @@ -86,6 +148,9 @@ export class DepartureController {

@Get("/platform")
@Version([VERSION_NEUTRAL, EndpointVersion.v1])
@ApiDescription({
deprecated: true,
})
async getDeparturesByPlatform(@Query("id") id): Promise<DepartureSchema[]> {
const platformSchema = z
.string()
Expand All @@ -101,12 +166,64 @@ export class DepartureController {
);
}

const departures = await this.departureService.getDepartures({
const departures = await this.departureServiceV1.getDepartures({
stopIds: [],
platformIds: parsed.data,
metroOnly: false,
});

return departureSchema.array().parse(departures);
}

@Get()
@Version([EndpointVersion.v2])
@ApiQueries([
metroOnlyQuery,
{
name: "platform[]",
description: "Platform IDs",
type: String,
isArray: true,
allowEmptyValue: true,
required: false,
},
{
name: "stop[]",
description: "Stop IDs",
type: String,
isArray: true,
allowEmptyValue: true,
required: false,
},
])
async getDeparturesV2(@Query() query): Promise<DepartureSchema[]> {
const schema = z.object({
metroOnly: metroOnlySchema,
platform: z.string().array().optional().default([]),
stop: z.string().array().optional().default([]),
});
const parsed = schema.safeParse(query);
if (!parsed.success) {
throw new HttpException(
"Invalid query params",
HttpStatus.BAD_REQUEST,
);
}
const parsedQuery = parsed.data;

if (parsedQuery.platform.length + parsedQuery.stop.length === 0) {
throw new HttpException(
"At least one platform or stop ID must be provided",
HttpStatus.BAD_REQUEST,
);
}

const departures = await this.departureServiceV2.getDepartures({
stopIds: parsedQuery.stop,
platformIds: parsedQuery.platform,
metroOnly: parsedQuery.metroOnly,
});

return departureSchema.array().parse(departures);
}
}
5 changes: 3 additions & 2 deletions apps/backend/src/modules/departure/departure.module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Module } from "@nestjs/common";

import { DepartureServiceV1 } from "src/modules/departure/departure-v1.service";
import { DepartureServiceV2 } from "src/modules/departure/departure-v2.service";
import { DepartureController } from "src/modules/departure/departure.controller";
import { DepartureService } from "src/modules/departure/departure.service";
import { GolemioService } from "src/modules/golemio/golemio.service";

@Module({
controllers: [DepartureController],
providers: [DepartureService, GolemioService],
providers: [DepartureServiceV1, DepartureServiceV2, GolemioService],
imports: [],
})
export class DepartureModule {}
Loading

1 comment on commit 18432d8

@vercel
Copy link

@vercel vercel bot commented on 18432d8 Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please # to comment.