Waypoints snapped to OSM POIs in the Planner now carry their metadata all the way through to the Journal: - Extend Waypoint type with osmId and poiTags fields - Extract osmId/poiTags from Yjs Y.Map in ExportButton and SaveToJournalButton - Encode POI metadata as <trails:poi> extensions in GPX <wpt> elements - Parse <trails:poi> extensions back in the GPX parser - Display phone, website, opening hours, address on Journal route detail - E2E test for the full roundtrip; seed endpoint now defaults to public visibility Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
77 lines
2.3 KiB
TypeScript
77 lines
2.3 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import { data } from "react-router";
|
|
import { eq } from "drizzle-orm";
|
|
import { getDb } from "~/lib/db";
|
|
import { users, routes } from "@trails-cool/db/schema/journal";
|
|
import { createRouteToken } from "~/lib/jwt.server";
|
|
import { TERMS_VERSION } from "~/lib/legal";
|
|
|
|
// Only available when the server is started with E2E=true.
|
|
// Never enabled in production.
|
|
function assertE2EEnabled() {
|
|
if (process.env.E2E !== "true") {
|
|
throw new Response("Not found", { status: 404 });
|
|
}
|
|
}
|
|
|
|
const E2E_USER_USERNAME = "e2e-test-user";
|
|
const E2E_USER_EMAIL = "e2e@localhost";
|
|
|
|
/**
|
|
* POST /api/e2e/seed
|
|
*
|
|
* Idempotently creates the e2e test user and a bare route (no GPX, no geom),
|
|
* then returns a callback-scoped JWT for that route. Used by e2e tests that
|
|
* need to hit the Planner callback endpoint without going through the browser
|
|
* auth flow.
|
|
*
|
|
* Body: none required. Optionally pass { routeName } to label the route.
|
|
*/
|
|
export async function action({ request }: { request: Request }) {
|
|
assertE2EEnabled();
|
|
if (request.method !== "POST") {
|
|
return data({ error: "Method not allowed" }, { status: 405 });
|
|
}
|
|
|
|
const db = getDb();
|
|
|
|
// Upsert the e2e test user
|
|
await db
|
|
.insert(users)
|
|
.values({
|
|
id: randomUUID(),
|
|
username: E2E_USER_USERNAME,
|
|
email: E2E_USER_EMAIL,
|
|
displayName: "E2E Test User",
|
|
domain: "localhost",
|
|
termsAcceptedAt: new Date(),
|
|
termsVersion: TERMS_VERSION,
|
|
})
|
|
.onConflictDoNothing({ target: users.username });
|
|
|
|
const [user] = await db
|
|
.select({ id: users.id })
|
|
.from(users)
|
|
.where(eq(users.username, E2E_USER_USERNAME));
|
|
|
|
if (!user) throw new Error("e2e seed: failed to upsert test user");
|
|
|
|
const body = request.headers.get("content-type")?.includes("application/json")
|
|
? await request.json().catch(() => ({}))
|
|
: {};
|
|
const routeName = (body as { routeName?: string }).routeName ?? "E2E callback test route";
|
|
const visibility = (body as { visibility?: string }).visibility ?? "public";
|
|
|
|
const routeId = randomUUID();
|
|
await db.insert(routes).values({
|
|
id: routeId,
|
|
ownerId: user.id,
|
|
name: routeName,
|
|
description: "",
|
|
visibility: visibility as "public" | "unlisted" | "private",
|
|
});
|
|
|
|
const token = await createRouteToken(routeId);
|
|
|
|
return data({ routeId, token, ownerId: user.id });
|
|
}
|