touchups after refactor

This commit is contained in:
Christian Beutel
2025-01-03 00:13:59 +01:00
parent 871481a84e
commit 0d2e71c2f6
32 changed files with 1157 additions and 45 deletions

View File

@@ -20,7 +20,7 @@ function csrf(allowedPaths: string[]): Handle {
request.method === "PATCH" ||
request.method === "DELETE") &&
request.headers.get("origin") !== url.origin &&
!allowedPaths.includes(url.pathname);
!allowedPaths.some(p => url.pathname.startsWith(p));
if (forbidden) {
const message = `Cross-site ${request.method} form submissions are forbidden`;
@@ -111,4 +111,4 @@ const auth: Handle = async ({ event, resolve }) => {
return response
}
export const handle = sequence(csrf(['/api/v1/trail/upload']), auth)
export const handle = sequence(csrf(['/api/v1']), auth)

View File

@@ -1,17 +1,17 @@
<script lang="ts">
import type { Notification } from "$lib/models/notification";
import { fly } from "svelte/transition";
import NotificationCard from "./notification_card.svelte";
import { goto } from "$app/navigation";
import { page } from "$app/stores";
import type { Notification } from "$lib/models/notification";
import {
notifications_index,
notifications_mark_as_seen,
} from "$lib/stores/notification_store";
import { currentUser } from "$lib/stores/user_store";
import { goto } from "$app/navigation";
import { onMount } from "svelte";
import SkeletonListItem from "../base/skeleton_list_item.svelte";
import { fly } from "svelte/transition";
import SkeletonNotificationCard from "../base/skeleton_notification_card.svelte";
import NotificationCard from "./notification_card.svelte";
import { _ } from "svelte-i18n";
let notifications: Notification[] = [];
@@ -134,7 +134,7 @@
{/each}
{:else}
<p class="text-gray-500 text-sm text-center py-6">
No notifications
{$_("no-notifications")}
</p>
{/if}
</ul>

View File

@@ -34,7 +34,7 @@
? $_("planned-a-trail")
: $_("completed-a-trail")}
<p class="text-xs text-gray-500 mb-3">
{new Date(activity.date).toLocaleDateString(undefined, {
{new Date(activity.created).toLocaleDateString(undefined, {
month: "long",
day: "2-digit",
year: "numeric",

View File

@@ -174,6 +174,7 @@
"no-account": "Du hast noch kein Konto?",
"no-data": "Keine Daten",
"no-grid": "Kein Gitter",
"no-notifications": "",
"no-preference": "Keine Präferenz",
"no-results": "Keine Ergebnisse gefunden",
"no-routes-added": "",

View File

@@ -174,6 +174,7 @@
"no-account": "Don't have an account?",
"no-data": "No data",
"no-grid": "No Grid",
"no-notifications": "No notifications",
"no-preference": "No preference",
"no-results": "No results found",
"no-routes-added": "No routes added",

View File

@@ -174,6 +174,7 @@
"no-account": "Pas encore de compte ?",
"no-data": "",
"no-grid": "",
"no-notifications": "",
"no-preference": "Pas de préférence",
"no-results": "Pas de résultat",
"no-routes-added": "",

View File

@@ -174,6 +174,7 @@
"no-account": "Nincs még fiókja?",
"no-data": "",
"no-grid": "",
"no-notifications": "",
"no-preference": "Nincs preferált",
"no-results": "Nincs eredmény",
"no-routes-added": "",

View File

@@ -174,6 +174,7 @@
"no-account": "Non hai ancora un account?",
"no-data": "",
"no-grid": "",
"no-notifications": "",
"no-preference": "Nessuna preferenza",
"no-results": "Nessun risultato trovato",
"no-routes-added": "",

View File

@@ -174,6 +174,7 @@
"no-account": "Heb je nog geen account?",
"no-data": "",
"no-grid": "",
"no-notifications": "",
"no-preference": "Geen voorkeur",
"no-results": "Er zijn geen zoekresultaten",
"no-routes-added": "",

View File

@@ -174,6 +174,7 @@
"no-account": "Nie masz konta?",
"no-data": "",
"no-grid": "",
"no-notifications": "",
"no-preference": "Brak preferencji",
"no-results": "Brak wyników",
"no-routes-added": "",

View File

@@ -174,6 +174,7 @@
"no-account": "Não tem uma conta?",
"no-data": "",
"no-grid": "",
"no-notifications": "",
"no-preference": "Nenhuma preferência",
"no-results": "Nenhum resultado encontrado",
"no-routes-added": "",

View File

@@ -174,6 +174,7 @@
"no-account": "还未注册?",
"no-data": "",
"no-grid": "",
"no-notifications": "",
"no-preference": "尚未规划",
"no-results": "没有找到结果",
"no-routes-added": "",

View File

@@ -12,6 +12,7 @@ interface Activity {
elevation_loss: number;
type: "trail" | "summit_log"
author: string;
created: string;
}
export { type Activity }

View File

@@ -3,7 +3,7 @@ import type { Comment } from "../comment";
const CommentCreateSchema = z.object({
text: z.string(),
rating: z.number(),
rating: z.number().optional(),
author: z.string().length(15),
trail: z.string().length(15),

View File

@@ -3,7 +3,7 @@ import type { UserAnonymous } from "./user";
export class Comment {
id?: string;
text: string;
rating: number;
rating?: number;
author: string;
trail: string;
created?: string;

View File

@@ -1,4 +1,3 @@
import { object, string } from "yup";
import type { Trail } from "./trail";
import type { ListShare } from "./list_share";
import type { UserAnonymous } from "./user";
@@ -36,11 +35,4 @@ export interface ListFilter {
public?: boolean;
shared?: boolean;
sortOrder?: "+" | "-"
}
export const listSchema = object<List>({
id: string().optional(),
name: string().required(),
description: string().optional(),
avatar: string().optional()
});
}

View File

@@ -1,5 +1,4 @@
import * as M from "maplibre-gl";
import { number, object, string } from "yup";
class Waypoint {
id?: string;

View File

@@ -7,7 +7,8 @@ export async function activities_index(author: string, page: number = 1, perPage
const r = await f('/api/v1/activity?' + new URLSearchParams({
"perPage": perPage.toString(),
page: page.toString(),
filter: `author="${author}"`
filter: `author="${author}"`,
sort: "-created"
}), {
method: 'GET',
})

View File

@@ -8,7 +8,7 @@ export async function notifications_index(data: { recipient: string, seen?: bool
filter: `created>=@month&&recipient='${data.recipient}'` + (data.seen !== undefined ? `&&seen=${data.seen}` : ''),
sort: '+seen,-created',
page: page.toString(),
"per-page": perPage.toString()
"perPage": perPage.toString()
}), {
method: 'GET',
})

View File

@@ -18,7 +18,7 @@ export const editTrail: Writable<Trail> = writable(new Trail(""));
export async function trails_index(perPage: number = 21, random: boolean = false, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
const r = await f('/api/v1/trail?' + new URLSearchParams({
"per-page": perPage.toString(),
"perPage": perPage.toString(),
expand: "category,waypoints,summit_logs",
sort: random ? "@random" : "",
}), {
@@ -102,7 +102,7 @@ export async function trails_search_bounding_box(northEast: M.LngLat, southWest:
}
r = await fetch('/api/v1/trail?' + new URLSearchParams({
"per-page": "-1",
"perPage": "-1",
filter: `'${trailIds.join(',')}'~id`,
expand: "category,waypoints,summit_logs",
sort: `+name`,

View File

@@ -60,11 +60,13 @@ export async function show<T>(event: RequestEvent, collection: Collection) {
}
export async function create<T>(event: RequestEvent, schema: ZodSchema, collection: Collection) {
const data = await event.request.json();
const searchParams = Object.fromEntries(event.url.searchParams);
const safeSearchParams = RecordOptionsSchema.parse(searchParams);
const data = await event.request.json();
const safeData = schema.parse(data);
const r = await pb.collection(Collection[collection]).create<T>(safeData)
const r = await pb.collection(Collection[collection]).create<T>(safeData, safeSearchParams)
return r
}
@@ -106,10 +108,12 @@ export async function remove(event: RequestEvent, collection: Collection) {
export function handleError(e: any) {
if (e instanceof ZodError) {
return error(400, e.message)
return error(400, { message: "invalid_params", details: e.issues } as any)
} else if (e instanceof ClientResponseError && e.status > 0) {
return error(e.status as NumericRange<400, 599>, e.message)
return error(e.status as NumericRange<400, 599>, { message: e.message, detail: e.originalError.data } as any)
} else if (e instanceof SyntaxError) {
return error(400, "invalid_json")
} else {
return e
return error(500, e.toString())
}
}

View File

@@ -1,4 +1,5 @@
import { pb } from "$lib/pocketbase";
import { handleError } from "$lib/util/api_util";
import { error, json, type RequestEvent } from "@sveltejs/kit";
import { z } from "zod";
@@ -13,7 +14,7 @@ export async function POST(event: RequestEvent) {
const r = await pb.collection('users').confirmPasswordReset(safeData.token, safeData.password, safeData.passwordConfirm);
return json(r);
} catch (e: any) {
throw error(e.status, e);
throw handleError(e);
}
}

View File

@@ -1,4 +1,5 @@
import { pb } from "$lib/pocketbase";
import { handleError } from "$lib/util/api_util";
import { error, json, type RequestEvent } from "@sveltejs/kit";
import { z } from "zod";
@@ -15,7 +16,7 @@ export async function POST(event: RequestEvent) {
const r = await pb.collection('users').authWithPassword(safeData.email ?? safeData.username!, data.password);
return json(r);
} catch (e: any) {
throw error(e.status, e);
throw handleError(e);
}
}

View File

@@ -4,6 +4,7 @@ import { env as private_env } from "$env/dynamic/private";
import { pb } from "$lib/pocketbase";
import { error, json, type RequestEvent } from "@sveltejs/kit";
import { z } from "zod";
import { handleError } from "$lib/util/api_util";
const redirectURL = private_env.ORIGIN + "/login/redirect"
@@ -18,7 +19,7 @@ export async function GET(event: RequestEvent) {
}
return json(r)
} catch (e: any) {
throw error(e.status, e);
throw handleError(e);
}
}
@@ -40,7 +41,7 @@ export async function POST(event: RequestEvent) {
)
return json(r)
} catch (e: any) {
throw error(e.status, e);
throw handleError(e);
}
}

View File

@@ -5,13 +5,13 @@ import { z } from "zod";
export async function GET(event: RequestEvent) {
const safeParams = z.object({
collection: z.string().length(15),
collection: z.string(),
record: z.string().length(15),
file: z.string()
}).safeParse(event.params)
if (!safeParams.success) {
throw error(400, 'Invalid params')
throw error(400, safeParams.error)
}
const parts = [];

View File

@@ -1,7 +1,7 @@
import type { Notification } from '$lib/models/notification';
import type { UserAnonymous } from '$lib/models/user';
import { pb } from '$lib/pocketbase';
import { Collection, list } from '$lib/util/api_util';
import { Collection, handleError, list } from '$lib/util/api_util';
import { error, json, type RequestEvent } from '@sveltejs/kit';
export async function GET(event: RequestEvent) {
@@ -17,6 +17,6 @@ export async function GET(event: RequestEvent) {
}
return json(r)
} catch (e: any) {
throw error(e.status, e);
throw handleError(e);
}
}

View File

@@ -152,10 +152,9 @@
$page.url.searchParams.has("lat") &&
$page.url.searchParams.has("lon")
) {
map.setCenter([
parseFloat($page.url.searchParams.get("lon")!),
parseFloat($page.url.searchParams.get("lat")!),
]);
const lat = $page.url.searchParams.get("lat");
const lon = $page.url.searchParams.get("lon");
map.setCenter([parseFloat(lon!), parseFloat(lat!)]);
map.setZoom(14);
} else if (settings && settings.mapFocus == "trails") {
const boundingBox: M.LngLatBoundsLike = [

View File

@@ -46,7 +46,7 @@
<div
class="grid grid-cols-1 lg:grid-cols-[356px_minmax(0,_1fr)] gap-4 max-w-6xl mx-auto items-start"
>
<div class="border border-input-border rounded-xl">
<div class="border border-input-border rounded-xl sticky top-8">
{#if data.user}
<div class="flex items-center gap-x-6 px-6 my-6">
<img

View File

@@ -217,7 +217,7 @@
async function loadSummitLogs() {
const logs = await summit_logs_index($page.params.id, filter);
summitLogs.set(logs);
summitLogs.set(logs.items);
}
</script>