Skip to content

Commit 844a503

Browse files
committed
refactor: replace classic fetch/API with tRPC
1 parent d910aec commit 844a503

26 files changed

+1000
-1017
lines changed

app/api/[trpc]/route.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
2+
import { journeys } from "../journeys/journeys";
3+
import { parseUrl } from "../parseUrl";
4+
import { splitJourney } from "../splitJourney/splitJourney";
5+
import { t } from "@/utils/trpc-init";
6+
7+
const appRouter = t.router({
8+
journeys,
9+
splitJourney,
10+
parseUrl,
11+
});
12+
13+
export type AppRouter = typeof appRouter;
14+
15+
const handler = (req: Request) =>
16+
fetchRequestHandler({
17+
endpoint: "/api",
18+
req,
19+
router: appRouter,
20+
onError(opts) {
21+
console.error("TRPC Error", opts.error.message);
22+
},
23+
});
24+
25+
export { handler as GET, handler as POST };

app/api/_lib/error-handler.ts

Lines changed: 0 additions & 27 deletions
This file was deleted.

app/api/journeys/_lib/configure-search-options.ts

Lines changed: 0 additions & 51 deletions
This file was deleted.

app/api/journeys/_lib/extract-url-params.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { incrementApiCount } from "@/utils/apiCounter";
2+
import { vendoJourneySchema, type VendoJourney } from "@/utils/schemas";
3+
import type { SplitPoint } from "@/utils/types";
4+
import { createClient } from "db-vendo-client";
5+
import { profile as dbProfile } from "db-vendo-client/p/db/index";
6+
import { z } from "zod/v4";
7+
import type { QueryOptions } from "../splitJourney/QueryOptions";
8+
import { createSplitResult } from "../splitJourney/createSplitResult";
9+
import { findMatchingJourney } from "../splitJourney/findMatchingJourney";
10+
11+
const client = createClient(dbProfile, "mail@lukasweihrauch.de");
12+
13+
// Split Analysis Functions
14+
export async function analyzeSingleSplit(
15+
originalJourney: VendoJourney,
16+
splitPoint: SplitPoint,
17+
queryOptions: QueryOptions,
18+
originalPrice: number
19+
) {
20+
const origin = originalJourney.legs[0].origin;
21+
const destination =
22+
originalJourney.legs[originalJourney.legs.length - 1].destination;
23+
const originalDeparture = new Date(originalJourney.legs[0].departure);
24+
const splitDeparture = new Date(splitPoint.departure);
25+
26+
try {
27+
// Increment API counters for both segments
28+
incrementApiCount(
29+
"SPLIT_SEARCH_SEGMENT_1",
30+
`${origin?.name}${splitPoint.station?.name}`
31+
);
32+
incrementApiCount(
33+
"SPLIT_SEARCH_SEGMENT_2",
34+
`${splitPoint.station?.name}${destination?.name}`
35+
);
36+
37+
// Schema validation at entry point ensures origin/destination IDs exist
38+
39+
// Make both API calls in parallel using Promise.all
40+
const [firstSegmentUntyped, secondSegmentUntyped] = await Promise.all([
41+
client.journeys(origin!.id, splitPoint.station.id, {
42+
...queryOptions,
43+
departure: originalDeparture,
44+
}),
45+
46+
client.journeys(splitPoint.station.id, destination!.id, {
47+
...queryOptions,
48+
departure: splitDeparture,
49+
}),
50+
]);
51+
52+
const clientJourneySchema = z.object({
53+
journeys: z.array(vendoJourneySchema),
54+
});
55+
56+
const firstSegment = clientJourneySchema.parse(firstSegmentUntyped);
57+
const secondSegment = clientJourneySchema.parse(secondSegmentUntyped);
58+
59+
if (
60+
firstSegment.journeys === undefined ||
61+
secondSegment.journeys === undefined
62+
) {
63+
return null;
64+
}
65+
66+
const firstJourney = findMatchingJourney(
67+
firstSegment.journeys,
68+
originalDeparture
69+
);
70+
71+
if (!firstJourney) {
72+
return null;
73+
}
74+
75+
const secondJourney = findMatchingJourney(
76+
secondSegment.journeys,
77+
splitDeparture
78+
);
79+
80+
if (!secondJourney) {
81+
return null;
82+
}
83+
84+
// Calculate pricing
85+
const firstPrice = firstJourney.price?.amount || 0;
86+
const secondPrice = secondJourney.price?.amount || 0;
87+
const totalPrice = firstPrice + secondPrice;
88+
89+
if (totalPrice > 0 && totalPrice < originalPrice) {
90+
return createSplitResult(
91+
"single",
92+
[splitPoint.station],
93+
[firstJourney, secondJourney],
94+
totalPrice,
95+
originalPrice,
96+
splitPoint.trainLine
97+
);
98+
}
99+
100+
return null;
101+
} catch (error) {
102+
const typedError = error as { message: string };
103+
console.log(
104+
`Single split analysis error at ${splitPoint.station.name}:`,
105+
typedError.message
106+
);
107+
throw error; // Re-throw to be handled by Promise.allSettled
108+
}
109+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { inferRouterInputs } from "@trpc/server/unstable-core-do-not-import";
2+
import type { SearchJourneysOptions } from "db-vendo-client";
3+
import { data as loyaltyCards } from "db-vendo-client/format/loyalty-cards";
4+
import type { AppRouter } from "../[trpc]/route";
5+
6+
export const configureSearchOptions = (
7+
input: inferRouterInputs<AppRouter>["journeys"]
8+
) => {
9+
const options: SearchJourneysOptions = {
10+
results: input.type === "accurate-time" ? 5 : input.results, // Weniger Ergebnisse bei genauer Zeit um Rauschen zu reduzieren
11+
stopovers: true,
12+
// Bei genauer Abfahrtszeit wollen wir exakte Treffer, nicht verschiedene Alternativen
13+
notOnlyFastRoutes: input.type === "accurate-time", // Nur schnelle Routen bei genauer Zeit
14+
remarks: true, // Verbindungshinweise einschließen
15+
transfers: -1, // System entscheidet über optimale Anzahl Umstiege
16+
// Reiseklasse-Präferenz setzen - verwende firstClass boolean Parameter
17+
firstClass: input.travelClass === 1, // true für erste Klasse, false für zweite Klasse
18+
age: input.passengerAge, // Passagieralter für angemessene Preisgestaltung hinzufügen
19+
departure: input.type === "accurate-time" ? input.departure : undefined, // Abfahrtszeit hinzufügen falls angegeben
20+
};
21+
22+
// BahnCard-Rabattkarte hinzufügen falls angegeben
23+
if (input.bahnCard !== undefined && [25, 50, 100].includes(input.bahnCard)) {
24+
options.loyaltyCard = {
25+
type: loyaltyCards.BAHNCARD,
26+
discount: input.bahnCard,
27+
class: input.travelClass, // 1 für erste Klasse, 2 für zweite Klasse
28+
};
29+
}
30+
31+
// Deutschland-Ticket Optionen für genauere Preisgestaltung
32+
if (input.hasDeutschlandTicket) {
33+
options.deutschlandTicketDiscount = true;
34+
// Diese Option kann helfen, genauere Preise zurückzugeben wenn Deutschland-Ticket verfügbar ist
35+
options.deutschlandTicketConnectionsOnly = false; // Wir wollen alle Verbindungen, aber mit genauen Preisen
36+
}
37+
38+
console.log("API options being passed to db-vendo-client:", options);
39+
console.log("Travel class requested:", input.travelClass);
40+
console.log("BahnCard with class:", options.loyaltyCard);
41+
42+
return options;
43+
};

0 commit comments

Comments
 (0)