adds zod API validation
This commit is contained in:
57
web/package-lock.json
generated
57
web/package-lock.json
generated
@@ -15,7 +15,6 @@
|
||||
"@turf/destination": "^7.1.0",
|
||||
"@turf/distance": "^7.1.0",
|
||||
"@types/chart.js": "^2.9.41",
|
||||
"@types/leaflet.awesome-markers": "^2.0.28",
|
||||
"@types/three": "^0.161.2",
|
||||
"@types/xmldom": "^0.1.34",
|
||||
"canvg": "^4.0.1",
|
||||
@@ -28,9 +27,6 @@
|
||||
"isomorphic-xml2js": "^0.1.3",
|
||||
"jspdf": "^2.5.1",
|
||||
"jszip": "^3.10.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet-gpx": "^1.7.0",
|
||||
"leaflet.awesome-markers": "^2.0.5",
|
||||
"maplibre-gl": "^4.7.1",
|
||||
"meilisearch": "^0.37.0",
|
||||
"nouislider": "^15.7.1",
|
||||
@@ -42,15 +38,14 @@
|
||||
"svelte-i18n": "^4.0.0",
|
||||
"three": "^0.161.0",
|
||||
"xmldom": "^0.6.0",
|
||||
"yup": "^1.3.3"
|
||||
"yup": "^1.3.3",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.42.1",
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@types/leaflet": "^1.9.8",
|
||||
"@types/leaflet-gpx": "^1.3.7",
|
||||
"@types/node": "^20.11.25",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.33",
|
||||
@@ -1250,31 +1245,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz",
|
||||
"integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw=="
|
||||
},
|
||||
"node_modules/@types/leaflet": {
|
||||
"version": "1.9.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.8.tgz",
|
||||
"integrity": "sha512-EXdsL4EhoUtGm2GC2ZYtXn+Fzc6pluVgagvo2VC1RHWToLGlTRwVYoDpqS/7QXa01rmDyBjJk3Catpf60VMkwg==",
|
||||
"dependencies": {
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/leaflet-gpx": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/leaflet-gpx/-/leaflet-gpx-1.3.7.tgz",
|
||||
"integrity": "sha512-IDshIOLZ7dUUjRiCE3DuJcAGavgUCw0xQ93dc/3YvsA6jrFc+nx8eXr0tqZIf2SaWMgqiDj/n7N24WWNh/898g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/leaflet": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/leaflet.awesome-markers": {
|
||||
"version": "2.0.28",
|
||||
"resolved": "https://registry.npmjs.org/@types/leaflet.awesome-markers/-/leaflet.awesome-markers-2.0.28.tgz",
|
||||
"integrity": "sha512-weYTpd102+C2elY2LgNYc9PLLKbvqo9762LTeYc9VRCZqcQLabFdWzgozlQp1u71sMoHP13r7gbvIZbPMzhv/Q==",
|
||||
"dependencies": {
|
||||
"@types/leaflet": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mapbox__point-geometry": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz",
|
||||
@@ -3842,21 +3812,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/leaflet": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
|
||||
},
|
||||
"node_modules/leaflet-gpx": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/leaflet-gpx/-/leaflet-gpx-1.7.0.tgz",
|
||||
"integrity": "sha512-5NS5WKfp5zaAHMB2KM6OUaIng+SZ3e/UHK2++ABl2wELojYlmLqIL8t1Mj6DaZGkIh3kld+mAUvMGdIzC1yGRA=="
|
||||
},
|
||||
"node_modules/leaflet.awesome-markers": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/leaflet.awesome-markers/-/leaflet.awesome-markers-2.0.5.tgz",
|
||||
"integrity": "sha512-Ne/xDjkGyaujwNVVkv2tyXQUV0ZW7gZ0Mo0FuQY4jp2qWrvXi0hwDBvmZyF/8YOvybyMabTMM/mFWCTd1jZIQA=="
|
||||
},
|
||||
"node_modules/lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
@@ -6784,6 +6739,14 @@
|
||||
"toposort": "^2.0.2",
|
||||
"type-fest": "^2.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.24.1",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
|
||||
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
"svelte-i18n": "^4.0.0",
|
||||
"three": "^0.161.0",
|
||||
"xmldom": "^0.6.0",
|
||||
"yup": "^1.3.3"
|
||||
"yup": "^1.3.3",
|
||||
"zod": "^3.24.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +203,9 @@
|
||||
<a class="btn-primary btn-large" href="/trail/edit/new"
|
||||
><i class="fa fa-plus mr-2"></i>{$_("new-trail")}</a
|
||||
>
|
||||
{#if $page.data.notifications}
|
||||
<NotificationDropdown></NotificationDropdown>
|
||||
{/if}
|
||||
<Dropdown
|
||||
items={dropdownItems}
|
||||
on:change={(e) => handleDropdownClick(e.detail)}
|
||||
|
||||
@@ -32,13 +32,13 @@
|
||||
onMount(async () => {});
|
||||
|
||||
async function openMap(log: SummitLog) {
|
||||
if (!log.expand.gpx_data) {
|
||||
if (!log.expand?.gpx_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
trail = (await gpx2trail(log.expand.gpx_data)).trail;
|
||||
trail.id = log.id;
|
||||
trail.expand.gpx_data = log.expand.gpx_data;
|
||||
trail.expand!.gpx_data = log.expand.gpx_data;
|
||||
|
||||
openMapModal();
|
||||
await tick();
|
||||
@@ -75,7 +75,7 @@
|
||||
{#if summitLogs.some((l) => l.text?.length)}
|
||||
<th>{$_("description")}</th>
|
||||
{/if}
|
||||
{#if showAuthor && summitLogs.some(l => l.expand.author)}
|
||||
{#if showAuthor && summitLogs.some(l => l.expand?.author)}
|
||||
<th>
|
||||
{$_("author", { values: { n: 1 } })}
|
||||
</th>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
export let fullWidth: boolean = false;
|
||||
|
||||
$: thumbnail = trail.photos.length
|
||||
? getFileURL(trail, trail.photos[trail.thumbnail])
|
||||
? getFileURL(trail, trail.photos[trail.thumbnail ?? 0])
|
||||
: "/imgs/default_thumbnail.webp";
|
||||
|
||||
$: trailIsShared = (trail.expand?.trail_share_via_trail?.length ?? 0) > 0;
|
||||
@@ -66,7 +66,7 @@
|
||||
})}
|
||||
</p>
|
||||
{/if}
|
||||
{#if trail.expand.author}
|
||||
{#if trail.expand?.author}
|
||||
<p class="text-xs text-gray-500 mb-3">
|
||||
{$_("by")}
|
||||
<img
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
const allowEdit =
|
||||
trail.author == $currentUser?.id ||
|
||||
trail.expand.trail_share_via_trail?.some((s) => s.permission == "edit");
|
||||
trail.expand?.trail_share_via_trail?.some((s) => s.permission == "edit");
|
||||
|
||||
const dropdownItems: DropdownItem[] = [
|
||||
mode == "overview"
|
||||
@@ -142,7 +142,7 @@
|
||||
}
|
||||
if (exportSettings.summitLog) {
|
||||
let summitLogString = "";
|
||||
for (const summitLog of trail.expand.summit_logs) {
|
||||
for (const summitLog of trail.expand?.summit_logs ?? []) {
|
||||
summitLogString += `${summitLog.date},${summitLog.text}\n`;
|
||||
}
|
||||
zip.file(
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
(trail.expand?.trail_share_via_trail?.length ?? 0) > 0;
|
||||
|
||||
let thumbnail = trail.photos.length
|
||||
? getFileURL(trail, trail.photos[trail.thumbnail])
|
||||
? getFileURL(trail, trail.photos[trail.thumbnail ?? 0])
|
||||
: "/imgs/default_thumbnail.webp";
|
||||
|
||||
let openGallery: (idx?: number) => void;
|
||||
@@ -99,7 +99,7 @@
|
||||
comments_create(newComment).then((c) => {
|
||||
newComment.text = "";
|
||||
c.expand = {
|
||||
author: $currentUser!,
|
||||
author: { ...$currentUser!, private: false },
|
||||
};
|
||||
|
||||
const newCommentList = [c, ...$comments];
|
||||
@@ -182,7 +182,7 @@
|
||||
)}
|
||||
</h5>
|
||||
{/if}
|
||||
{#if trail.expand.author}
|
||||
{#if trail.expand?.author}
|
||||
<p class="my-3">
|
||||
{$_("by")}
|
||||
<img
|
||||
@@ -218,7 +218,7 @@
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
{#if ($currentUser && $currentUser.id == trail.author) || trail.expand.trail_share_via_trail?.length || trail.public}
|
||||
{#if ($currentUser && $currentUser.id == trail.author) || trail.expand?.trail_share_via_trail?.length || trail.public}
|
||||
<TrailDropdown {trail} {mode}></TrailDropdown>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -251,7 +251,7 @@
|
||||
>{formatElevation(trail.elevation_loss)}</span
|
||||
>
|
||||
</div>
|
||||
{#if trail.expand.category}
|
||||
{#if trail.expand?.category}
|
||||
<div class="flex flex-col items-center">
|
||||
<span>{$_("category")}</span>
|
||||
<span class="font-semibold text-lg"
|
||||
@@ -286,7 +286,7 @@
|
||||
{/if}
|
||||
{#if activeTab == 1}
|
||||
<ul>
|
||||
{#each trail.expand.waypoints ?? [] as waypoint, i}
|
||||
{#each trail.expand?.waypoints ?? [] as waypoint, i}
|
||||
<li
|
||||
on:mouseenter={() => openMarkerPopup(i)}
|
||||
on:mouseleave={() => closeMarkerPopup(i)}
|
||||
@@ -322,7 +322,7 @@
|
||||
{#if activeTab == 3}
|
||||
<div class="overflow-x-auto">
|
||||
<SummitLogTable
|
||||
summitLogs={trail.expand.summit_logs}
|
||||
summitLogs={trail.expand?.summit_logs}
|
||||
showAuthor
|
||||
showRoute
|
||||
showPhotos
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
export let showDescription: boolean = true;
|
||||
|
||||
$: thumbnail = trail.photos.length
|
||||
? getFileURL(trail, trail.photos[trail.thumbnail])
|
||||
? getFileURL(trail, trail.photos[trail.thumbnail ?? 0])
|
||||
: "/imgs/default_thumbnail.webp";
|
||||
</script>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
})}
|
||||
</p>
|
||||
{/if}
|
||||
{#if trail.expand.author}
|
||||
{#if trail.expand?.author}
|
||||
<p class="text-xs text-gray-500 mb-3">
|
||||
{$_("by")}
|
||||
<img
|
||||
|
||||
23
web/src/lib/models/api/base_schema.ts
Normal file
23
web/src/lib/models/api/base_schema.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { z } from "zod";
|
||||
|
||||
|
||||
const RecordOptionsSchema = z.object({
|
||||
expand: z.string().optional(),
|
||||
requestKey: z.string().optional()
|
||||
})
|
||||
|
||||
const RecordListOptionsSchema = RecordOptionsSchema.extend({
|
||||
page: z.number({ coerce: true }).int().positive().optional(),
|
||||
perPage: z.number({ coerce: true }).int().optional(),
|
||||
sort: z.string().optional(),
|
||||
filter: z.string().optional(),
|
||||
expand: z.string().optional(),
|
||||
requestKey: z.string().optional()
|
||||
})
|
||||
|
||||
const RecordIdSchema = z.object({
|
||||
id: z.string().length(15)
|
||||
})
|
||||
|
||||
|
||||
export { RecordOptionsSchema, RecordListOptionsSchema, RecordIdSchema }
|
||||
17
web/src/lib/models/api/comment_schema.ts
Normal file
17
web/src/lib/models/api/comment_schema.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { z, ZodType } from "zod";
|
||||
import type { Comment } from "../comment";
|
||||
|
||||
const CommentCreateSchema = z.object({
|
||||
text: z.string(),
|
||||
rating: z.number(),
|
||||
author: z.string().length(15),
|
||||
trail: z.string().length(15),
|
||||
|
||||
}) satisfies ZodType<Comment>
|
||||
|
||||
const CommentUpdateSchema = z.object({
|
||||
text: z.string().optional(),
|
||||
rating: z.number().optional(),
|
||||
}) satisfies ZodType<Partial<Comment>>
|
||||
|
||||
export { CommentCreateSchema, CommentUpdateSchema }
|
||||
11
web/src/lib/models/api/follow_schema.ts
Normal file
11
web/src/lib/models/api/follow_schema.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { z, ZodType } from "zod";
|
||||
import type { Follow } from "../follow";
|
||||
|
||||
const FollowCreateSchema = z.object({
|
||||
follower: z.string().length(15),
|
||||
followee: z.string().length(15),
|
||||
|
||||
}) satisfies ZodType<Follow>
|
||||
|
||||
|
||||
export { FollowCreateSchema };
|
||||
23
web/src/lib/models/api/list_schema.ts
Normal file
23
web/src/lib/models/api/list_schema.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { z, ZodType } from "zod";
|
||||
import type { List } from "../list";
|
||||
|
||||
const ListCreateSchema = z.object({
|
||||
name: z.string(),
|
||||
public: z.boolean(),
|
||||
description: z.string().optional(),
|
||||
trails: z.array(z.string().length(15)),
|
||||
avatar: z.string().optional(),
|
||||
author: z.string()
|
||||
}) satisfies ZodType<List>
|
||||
|
||||
const ListUpdateSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
public: z.boolean().optional(),
|
||||
description: z.string().optional(),
|
||||
trails: z.array(z.string().length(15)).optional(),
|
||||
"trails-": z.string().optional(),
|
||||
"trails+": z.string().optional(),
|
||||
avatar: z.string().optional(),
|
||||
}) satisfies ZodType<Partial<List>>
|
||||
|
||||
export { ListCreateSchema, ListUpdateSchema };
|
||||
16
web/src/lib/models/api/list_share_schema.ts
Normal file
16
web/src/lib/models/api/list_share_schema.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { z, ZodType } from "zod";
|
||||
import type { ListShare } from "../list_share";
|
||||
|
||||
const ListShareCreateSchema = z.object({
|
||||
user: z.string().length(15),
|
||||
list: z.string().length(15),
|
||||
permission: z.enum(["view", "edit"])
|
||||
|
||||
}) satisfies ZodType<ListShare>
|
||||
|
||||
const ListShareUpdateSchema = z.object({
|
||||
permission: z.enum(["view", "edit"])
|
||||
}) satisfies ZodType<Partial<ListShare>>
|
||||
|
||||
|
||||
export { ListShareCreateSchema, ListShareUpdateSchema };
|
||||
8
web/src/lib/models/api/notification_schema.ts
Normal file
8
web/src/lib/models/api/notification_schema.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { z, type ZodType } from "zod"
|
||||
import { type Notification } from "../notification";
|
||||
|
||||
const NotificationUpdateSchema = z.object({
|
||||
seen: z.literal(true)
|
||||
}) satisfies ZodType<Partial<Notification>>
|
||||
|
||||
export { NotificationUpdateSchema }
|
||||
28
web/src/lib/models/api/settings_schema.ts
Normal file
28
web/src/lib/models/api/settings_schema.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { z, ZodType } from "zod";
|
||||
import { NotificationType } from "../notification";
|
||||
import type { Settings } from "../settings";
|
||||
|
||||
const SettingsCreateSchema = z.object({
|
||||
unit: z.enum(["metric", "imperial"]).optional(),
|
||||
language: z.enum(["en", "de", "fr", "hu", "it", "nl", "pl", "pt", "zh"]).optional(),
|
||||
mapFocus: z.enum(["trails", "location"]).optional(),
|
||||
location: z.object({
|
||||
name: z.string(),
|
||||
lat: z.number(),
|
||||
lon: z.number()
|
||||
}).optional(),
|
||||
category: z.string().optional(),
|
||||
tilesets: z.array(z.object({ name: z.string(), url: z.string().url() })).optional(),
|
||||
terrain: z.object({ terrain: z.string().url(), hillshading: z.string().url() }).optional(),
|
||||
user: z.string().optional(),
|
||||
privacy: z.object({
|
||||
account: z.enum(["public", "private"]),
|
||||
trails: z.enum(["public", "private"]),
|
||||
lists: z.enum(["public", "private"])
|
||||
}).optional(),
|
||||
notifications: z.record(z.enum(Object.values(NotificationType) as [string, ...string[]]), z.object({ web: z.boolean(), email: z.boolean() })).optional()
|
||||
|
||||
}) satisfies ZodType<Settings>
|
||||
ZodType<Partial<Comment>>
|
||||
|
||||
export { SettingsCreateSchema };
|
||||
31
web/src/lib/models/api/summit_log_schema.ts
Normal file
31
web/src/lib/models/api/summit_log_schema.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { z, ZodType } from "zod";
|
||||
import type { SummitLog } from "../summit_log";
|
||||
|
||||
|
||||
const SummitLogCreateSchema = z.object({
|
||||
id: z.string().length(15).optional(),
|
||||
date: z.string().date(),
|
||||
text: z.string().optional(),
|
||||
gpx: z.string().optional(),
|
||||
distance: z.number().nonnegative().optional(),
|
||||
elevation_gain: z.number().nonnegative().optional(),
|
||||
elevation_loss: z.number().nonnegative().optional(),
|
||||
duration: z.number().nonnegative().optional(),
|
||||
author: z.string().length(15),
|
||||
photos: z.array(z.string())
|
||||
}) satisfies ZodType<SummitLog>
|
||||
|
||||
const SummitLogUpdateSchema = z.object({
|
||||
date: z.string().date().optional(),
|
||||
text: z.string().optional(),
|
||||
gpx: z.string().optional(),
|
||||
distance: z.number().nonnegative().optional(),
|
||||
elevation_gain: z.number().nonnegative().optional(),
|
||||
elevation_loss: z.number().nonnegative().optional(),
|
||||
duration: z.number().nonnegative().optional(),
|
||||
photos: z.array(z.string()).optional(),
|
||||
"photos-": z.string().optional(),
|
||||
"photos+": z.string().optional(),
|
||||
}) satisfies ZodType<Partial<SummitLog>>
|
||||
|
||||
export { SummitLogCreateSchema, SummitLogUpdateSchema };
|
||||
49
web/src/lib/models/api/trail_schema.ts
Normal file
49
web/src/lib/models/api/trail_schema.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { z, ZodType } from "zod";
|
||||
import type { Trail } from "../trail";
|
||||
|
||||
|
||||
const TrailCreateSchema = z.object({
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
location: z.string().optional(),
|
||||
date: z.string().date(),
|
||||
public: z.boolean(),
|
||||
difficulty: z.enum(["easy", "moderate", "difficult"]).optional(),
|
||||
lat: z.number().optional(),
|
||||
lon: z.number().optional(),
|
||||
distance: z.number({coerce: true}).nonnegative().optional(),
|
||||
elevation_gain: z.number({coerce: true}).nonnegative().optional(),
|
||||
elevation_loss: z.number({coerce: true}).nonnegative().optional(),
|
||||
duration: z.number({coerce: true}).nonnegative().optional(),
|
||||
photos: z.array(z.string()),
|
||||
thumbnail: z.number().int().nonnegative().optional(),
|
||||
waypoints: z.array(z.string()),
|
||||
summit_logs: z.array(z.string()),
|
||||
category: z.string().optional(),
|
||||
gpx: z.string().optional(),
|
||||
author: z.string().length(15),
|
||||
|
||||
}) satisfies ZodType<Trail>
|
||||
|
||||
const TrailUpdateSchema = z.object({
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
location: z.string().optional(),
|
||||
date: z.string().date(),
|
||||
public: z.boolean().optional(),
|
||||
difficulty: z.enum(["easy", "moderate", "difficult"]).optional(),
|
||||
lat: z.number().optional(),
|
||||
lon: z.number().optional(),
|
||||
distance: z.number({coerce: true}).nonnegative().optional(),
|
||||
elevation_gain: z.number({coerce: true}).nonnegative().optional(),
|
||||
elevation_loss: z.number({coerce: true}).nonnegative().optional(),
|
||||
duration: z.number({coerce: true}).nonnegative().optional(),
|
||||
photos: z.array(z.string()).optional(),
|
||||
thumbnail: z.number().int().nonnegative().optional(),
|
||||
waypoints: z.array(z.string()).optional(),
|
||||
summit_logs: z.array(z.string()).optional(),
|
||||
category: z.string().optional(),
|
||||
gpx: z.string().optional(),
|
||||
}) satisfies ZodType<Partial<Trail>>
|
||||
|
||||
export { TrailCreateSchema, TrailUpdateSchema };
|
||||
16
web/src/lib/models/api/trail_share_schema.ts
Normal file
16
web/src/lib/models/api/trail_share_schema.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { z, ZodType } from "zod";
|
||||
import type { TrailShare } from "../trail_share";
|
||||
|
||||
const TrailShareCreateSchema = z.object({
|
||||
user: z.string().length(15),
|
||||
trail: z.string().length(15),
|
||||
permission: z.enum(["view", "edit"])
|
||||
|
||||
}) satisfies ZodType<TrailShare>
|
||||
|
||||
const TrailShareUpdateSchema = z.object({
|
||||
permission: z.enum(["view", "edit"])
|
||||
}) satisfies ZodType<Partial<TrailShare>>
|
||||
|
||||
|
||||
export { TrailShareCreateSchema, TrailShareUpdateSchema };
|
||||
29
web/src/lib/models/api/user_schema.ts
Normal file
29
web/src/lib/models/api/user_schema.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { z, ZodType } from "zod";
|
||||
import type { User } from "../user";
|
||||
|
||||
const UserCreateSchema = (z.object({
|
||||
username: z.string({ message: "required" }).min(3, "must-be-at-least-n-characters-long").regex(/^[\w][\w\.]*$/, "invalid-username"),
|
||||
email: z.string({ message: "required" }).email("not-a-valid-email-address"),
|
||||
password: z.string().min(8, "must-be-at-least-n-characters-long"),
|
||||
passwordConfirm: z.string().optional()
|
||||
|
||||
}) satisfies ZodType<User>).refine((data) => data.password === data.passwordConfirm, {
|
||||
message: "passwords-must-match",
|
||||
path: ["passwordConfirm"],
|
||||
})
|
||||
|
||||
const UserUpdateSchema = (z.object({
|
||||
username: z.string({ message: "required" }).min(3, "must-be-at-least-n-characters-long").regex(/^[\w][\w\.]*$/, "invalid-username").optional(),
|
||||
email: z.string({ message: "required" }).email("not-a-valid-email-address").optional(),
|
||||
password: z.string().min(8, "must-be-at-least-n-characters-long").optional(),
|
||||
oldPassword: z.string().min(8, "must-be-at-least-n-characters-long").optional(),
|
||||
passwordConfirm: z.string().min(8, "must-be-at-least-n-characters-long").optional(),
|
||||
bio: z.string().optional()
|
||||
}) satisfies ZodType<Partial<User>>).refine((data) => data.password === data.passwordConfirm, {
|
||||
message: "passwords-must-match",
|
||||
path: ["passwordConfirm"],
|
||||
});
|
||||
|
||||
|
||||
|
||||
export { UserCreateSchema, UserUpdateSchema };
|
||||
28
web/src/lib/models/api/waypoint_schema.ts
Normal file
28
web/src/lib/models/api/waypoint_schema.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { z, ZodType } from "zod";
|
||||
import type { Waypoint } from "../waypoint";
|
||||
|
||||
const WaypointCreateSchema = z.object({
|
||||
id: z.string().length(15).optional(),
|
||||
name: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
text: z.string().optional(),
|
||||
lat: z.number(),
|
||||
lon: z.number(),
|
||||
icon: z.string().optional(),
|
||||
author: z.string().length(15),
|
||||
photos: z.array(z.string()),
|
||||
}) satisfies ZodType<Waypoint>
|
||||
|
||||
const WaypointUpdateSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
text: z.string().optional(),
|
||||
lat: z.number().optional(),
|
||||
lon: z.number().optional(),
|
||||
icon: z.string().optional(),
|
||||
photos: z.array(z.string()),
|
||||
"photos-": z.string().optional(),
|
||||
"photos+": z.string().optional(),
|
||||
}) satisfies ZodType<Partial<Waypoint>>
|
||||
|
||||
export { WaypointCreateSchema, WaypointUpdateSchema };
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { User } from "$lib/stores/user_store";
|
||||
import type { UserAnonymous } from "./user";
|
||||
|
||||
export class Comment {
|
||||
id?: string;
|
||||
@@ -8,7 +8,7 @@ export class Comment {
|
||||
trail: string;
|
||||
created?: string;
|
||||
expand?: {
|
||||
author: User
|
||||
author: UserAnonymous
|
||||
}
|
||||
|
||||
constructor(text: string, rating: number, author: string, trail: string) {
|
||||
|
||||
@@ -6,16 +6,16 @@ class SummitLog {
|
||||
date: string;
|
||||
text?: string;
|
||||
gpx?: string;
|
||||
_gpx: File | null;
|
||||
_gpx?: File | null;
|
||||
photos: string[];
|
||||
_photos: File[];
|
||||
_photos?: File[];
|
||||
distance?: number
|
||||
elevation_gain?: number
|
||||
elevation_loss?: number
|
||||
duration?: number
|
||||
author?: string;
|
||||
|
||||
expand: {
|
||||
expand?: {
|
||||
gpx_data?: string;
|
||||
trails_via_summit_logs?: Trail[];
|
||||
author?: UserAnonymous
|
||||
|
||||
@@ -18,14 +18,14 @@ class Trail {
|
||||
difficulty?: "easy" | "moderate" | "difficult"
|
||||
lat?: number;
|
||||
lon?: number;
|
||||
thumbnail: number;
|
||||
thumbnail?: number;
|
||||
photos: string[];
|
||||
gpx?: string;
|
||||
created?: string;
|
||||
category?: string;
|
||||
waypoints: string[];
|
||||
summit_logs: string[];
|
||||
expand: {
|
||||
expand?: {
|
||||
category?: Category;
|
||||
waypoints: Waypoint[]
|
||||
summit_logs: SummitLog[]
|
||||
|
||||
@@ -7,7 +7,6 @@ export type User = {
|
||||
password: string,
|
||||
avatar?: string;
|
||||
bio?: string;
|
||||
language?: string;
|
||||
created?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ class Waypoint {
|
||||
icon?: string;
|
||||
marker?: M.Marker;
|
||||
photos: string[];
|
||||
_photos: File[];
|
||||
_photos?: File[];
|
||||
author?: string;
|
||||
|
||||
constructor(lat: number, lon: number, params?: {
|
||||
|
||||
@@ -5,7 +5,7 @@ let activities: Activity[] = [];
|
||||
|
||||
export async function activities_index(author: string, page: number = 1, perPage: number = 10, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||
const r = await f('/api/v1/activity?' + new URLSearchParams({
|
||||
"per-page": perPage.toString(),
|
||||
"perPage": perPage.toString(),
|
||||
page: page.toString(),
|
||||
filter: `author="${author}"`
|
||||
}), {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import type { Category } from "$lib/models/category";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { ClientResponseError, type ListResult } from "pocketbase";
|
||||
|
||||
export const categories: Writable<Category[]> = writable([])
|
||||
|
||||
@@ -14,9 +14,9 @@ export async function categories_index(f: (url: RequestInfo | URL, config?: Requ
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
const response = await r.json();
|
||||
const response: ListResult<Category> = await r.json();
|
||||
|
||||
categories.set(response);
|
||||
categories.set(response.items);
|
||||
|
||||
return response as Category[];
|
||||
return response.items as Category[];
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Comment } from "$lib/models/comment";
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { ClientResponseError, type ListResult } from "pocketbase";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
|
||||
export const comments: Writable<Comment[]> = writable([])
|
||||
@@ -9,6 +9,7 @@ export const comments: Writable<Comment[]> = writable([])
|
||||
export async function comments_index(trail: Trail) {
|
||||
let r = await fetch('/api/v1/comment?' + new URLSearchParams({
|
||||
filter: `trail="${trail.id}"`,
|
||||
sort: "-created"
|
||||
}), {
|
||||
method: 'GET',
|
||||
})
|
||||
@@ -17,11 +18,11 @@ export async function comments_index(trail: Trail) {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
const fetchedComments: Comment[] = await r.json();
|
||||
const fetchedComments: ListResult<Comment> = await r.json();
|
||||
|
||||
comments.set(fetchedComments);
|
||||
comments.set(fetchedComments.items);
|
||||
|
||||
return fetchedComments;
|
||||
return fetchedComments.items;
|
||||
}
|
||||
|
||||
export async function comments_create(comment: Comment) {
|
||||
|
||||
@@ -7,7 +7,8 @@ export async function follows_index(data: { follower?: string, followee?: string
|
||||
const r = await f('/api/v1/follow?' + new URLSearchParams({
|
||||
filter: data.follower ? `follower='${data.follower}'` : data.followee ? `followee='${data.followee}'` : '',
|
||||
page: page.toString(),
|
||||
"per-page": perPage.toString()
|
||||
perPage: perPage.toString(),
|
||||
requestKey: "page"
|
||||
}), {
|
||||
method: 'GET',
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ListShare } from "$lib/models/list_share";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { ClientResponseError, type ListResult } from "pocketbase";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
|
||||
export const shares: Writable<ListShare[]> = writable([])
|
||||
@@ -16,9 +16,9 @@ export async function list_share_index(list: string, f: (url: RequestInfo | URL,
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
const response: ListShare[] = await r.json();
|
||||
const response: ListResult<ListShare> = await r.json();
|
||||
|
||||
shares.set(response);
|
||||
shares.set(response.items);
|
||||
|
||||
return response;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { List, type ListFilter } from "$lib/models/list";
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { ClientResponseError, type ListResult } from "pocketbase";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
import { fetchGPX } from "./trail_store";
|
||||
|
||||
let lists: List[] = []
|
||||
@@ -14,9 +14,10 @@ export async function lists_index(filter?: ListFilter, page: number = 1, perPage
|
||||
|
||||
const r = await f('/api/v1/list?' + new URLSearchParams({
|
||||
sort: `${filter?.sortOrder ?? "-"}${filter?.sort ?? "name"}`,
|
||||
"per-page": perPage.toString(),
|
||||
perPage: perPage.toString(),
|
||||
page: page.toString(),
|
||||
filter: filterText
|
||||
filter: filterText,
|
||||
expand: "trails,trails.waypoints,trails.category,list_share_via_list"
|
||||
}), {
|
||||
method: 'GET',
|
||||
})
|
||||
@@ -36,7 +37,9 @@ export async function lists_index(filter?: ListFilter, page: number = 1, perPage
|
||||
}
|
||||
|
||||
export async function lists_show(id: string, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||
const r = await f(`/api/v1/list/${id}`, {
|
||||
const r = await f(`/api/v1/list/${id}?` + new URLSearchParams({
|
||||
expand: "trails,trails.waypoints,trails.category,list_share_via_list"
|
||||
}), {
|
||||
method: 'GET',
|
||||
})
|
||||
const response = await r.json()
|
||||
@@ -132,6 +135,10 @@ export async function lists_add_trail(list: List, trail: Trail) {
|
||||
if (!r.ok) {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
const model: List = await r.json();
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
export async function lists_remove_trail(list: List, trail: Trail) {
|
||||
@@ -145,6 +152,10 @@ export async function lists_remove_trail(list: List, trail: Trail) {
|
||||
if (!r.ok) {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
const model: List = await r.json();
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
export async function lists_delete(list: List) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SummitLog, type SummitLogFilter } from "$lib/models/summit_log";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { ClientResponseError, type ListResult } from "pocketbase";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
import { fetchGPX } from "./trail_store";
|
||||
|
||||
@@ -14,6 +14,8 @@ export async function summit_logs_index(author: string, filter?: SummitLogFilter
|
||||
|
||||
const r = await f('/api/v1/summit-log?' + new URLSearchParams({
|
||||
filter: filterText,
|
||||
expand: "trails_via_summit_logs.category",
|
||||
sort: "+date",
|
||||
}), {
|
||||
method: 'GET',
|
||||
})
|
||||
@@ -22,9 +24,9 @@ export async function summit_logs_index(author: string, filter?: SummitLogFilter
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
const fetchedSummitLogs: SummitLog[] = await r.json();
|
||||
const fetchedSummitLogs: ListResult<SummitLog> = await r.json();
|
||||
|
||||
for (const log of fetchedSummitLogs) {
|
||||
for (const log of fetchedSummitLogs.items) {
|
||||
if (!log.gpx) {
|
||||
continue
|
||||
}
|
||||
@@ -36,7 +38,7 @@ export async function summit_logs_index(author: string, filter?: SummitLogFilter
|
||||
log.expand.gpx_data = gpxData;
|
||||
}
|
||||
|
||||
summitLogs.set(fetchedSummitLogs);
|
||||
summitLogs.set(fetchedSummitLogs.items);
|
||||
|
||||
return fetchedSummitLogs;
|
||||
}
|
||||
@@ -82,7 +84,7 @@ export async function summit_logs_create(summitLog: SummitLog, f: (url: RequestI
|
||||
}
|
||||
|
||||
if (r.ok) {
|
||||
return await r.json();
|
||||
return model;
|
||||
} else {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TrailShare } from "$lib/models/trail_share";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { ClientResponseError, type ListResult } from "pocketbase";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
|
||||
export const shares: Writable<TrailShare[]> = writable([])
|
||||
@@ -16,11 +16,11 @@ export async function trail_share_index(data: {trail?: string, user?: string}, f
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
const response: TrailShare[] = await r.json();
|
||||
const response: ListResult<TrailShare> = await r.json();
|
||||
|
||||
shares.set(response);
|
||||
shares.set(response.items);
|
||||
|
||||
return response;
|
||||
return response.items;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -179,14 +179,14 @@ export async function trails_create(trail: Trail, photos: File[], gpx: File | Bl
|
||||
throw new ClientResponseError({ status: 401, response: { message: "Forbidden" } });
|
||||
}
|
||||
|
||||
for (const waypoint of trail.expand.waypoints) {
|
||||
for (const waypoint of trail.expand?.waypoints ?? []) {
|
||||
const model = await waypoints_create({
|
||||
...waypoint,
|
||||
marker: undefined,
|
||||
}, f);
|
||||
trail.waypoints.push(model.id!);
|
||||
}
|
||||
for (const summitLog of trail.expand.summit_logs) {
|
||||
for (const summitLog of trail.expand?.summit_logs ?? []) {
|
||||
const model = await summit_logs_create(summitLog, f);
|
||||
trail.summit_logs.push(model.id!);
|
||||
}
|
||||
@@ -227,7 +227,7 @@ export async function trails_create(trail: Trail, photos: File[], gpx: File | Bl
|
||||
|
||||
export async function trails_update(oldTrail: Trail, newTrail: Trail, photos?: File[], gpx?: File | Blob | null) {
|
||||
|
||||
const waypointUpdates = compareObjectArrays<Waypoint>(oldTrail.expand.waypoints ?? [], newTrail.expand.waypoints ?? []);
|
||||
const waypointUpdates = compareObjectArrays<Waypoint>(oldTrail.expand?.waypoints ?? [], newTrail.expand?.waypoints ?? []);
|
||||
|
||||
for (const addedWaypoint of waypointUpdates.added) {
|
||||
const model = await waypoints_create({
|
||||
@@ -238,7 +238,7 @@ export async function trails_update(oldTrail: Trail, newTrail: Trail, photos?: F
|
||||
}
|
||||
|
||||
for (const updatedWaypoint of waypointUpdates.updated) {
|
||||
const oldWaypoint = oldTrail.expand.waypoints.find(w => w.id == updatedWaypoint.id);
|
||||
const oldWaypoint = oldTrail.expand?.waypoints.find(w => w.id == updatedWaypoint.id);
|
||||
const model = await waypoints_update(oldWaypoint!, {
|
||||
...updatedWaypoint,
|
||||
marker: undefined,
|
||||
@@ -249,7 +249,7 @@ export async function trails_update(oldTrail: Trail, newTrail: Trail, photos?: F
|
||||
const success = await waypoints_delete(deletedWaypoint);
|
||||
}
|
||||
|
||||
const summitLogUpdates = compareObjectArrays<SummitLog>(oldTrail.expand.summit_logs ?? [], newTrail.expand.summit_logs ?? []);
|
||||
const summitLogUpdates = compareObjectArrays<SummitLog>(oldTrail.expand?.summit_logs ?? [], newTrail.expand?.summit_logs ?? []);
|
||||
|
||||
for (const summitLog of summitLogUpdates.added) {
|
||||
const model = await summit_logs_create(summitLog);
|
||||
@@ -257,7 +257,7 @@ export async function trails_update(oldTrail: Trail, newTrail: Trail, photos?: F
|
||||
}
|
||||
|
||||
for (const updatedSummitLog of summitLogUpdates.updated) {
|
||||
const oldSummitLog = oldTrail.expand.summit_logs.find(w => w.id == updatedSummitLog.id);
|
||||
const oldSummitLog = oldTrail.expand?.summit_logs.find(w => w.id == updatedSummitLog.id);
|
||||
|
||||
const model = await summit_logs_update(oldSummitLog!, updatedSummitLog);
|
||||
}
|
||||
@@ -266,7 +266,9 @@ export async function trails_update(oldTrail: Trail, newTrail: Trail, photos?: F
|
||||
const success = await summit_logs_delete(deletedSummitLog);
|
||||
}
|
||||
|
||||
let r = await fetch('/api/v1/trail/' + newTrail.id, {
|
||||
let r = await fetch(`/api/v1/trail/${newTrail.id}?` + new URLSearchParams({
|
||||
expand: "category,waypoints,summit_logs,trail_share_via_trail",
|
||||
}), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ...newTrail, expand: undefined }),
|
||||
})
|
||||
@@ -312,12 +314,12 @@ export async function trails_update(oldTrail: Trail, newTrail: Trail, photos?: F
|
||||
|
||||
|
||||
export async function trails_delete(trail: Trail) {
|
||||
if (trail.expand.waypoints) {
|
||||
if (trail.expand?.waypoints) {
|
||||
for (const waypoint of trail.expand.waypoints) {
|
||||
await waypoints_delete(waypoint);
|
||||
}
|
||||
}
|
||||
if (trail.expand.summit_logs) {
|
||||
if (trail.expand?.summit_logs) {
|
||||
for (const summit_log of trail.expand.summit_logs) {
|
||||
await summit_logs_delete(summit_log);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export async function users_create(user: User) {
|
||||
|
||||
export async function users_search(q: string, includeSelf: boolean = true) {
|
||||
let r = await fetch('/api/v1/user/anonymous?' + new URLSearchParams({
|
||||
"filter": `username~"${q}"${includeSelf ? '' : '&&id!="${pb.authStore.model?.id}"'}`,
|
||||
"filter": `username~"${q}"${includeSelf ? '' : `&&id!="${pb.authStore.model?.id}"`}`,
|
||||
}), {
|
||||
method: 'GET',
|
||||
})
|
||||
|
||||
115
web/src/lib/util/api_util.ts
Normal file
115
web/src/lib/util/api_util.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { error, type NumericRange, type RequestEvent } from "@sveltejs/kit";
|
||||
import { ClientResponseError, type ListResult } from "pocketbase";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { ZodError, type ZodSchema } from "zod";
|
||||
import { RecordListOptionsSchema, RecordIdSchema, RecordOptionsSchema } from "$lib/models/api/base_schema";
|
||||
|
||||
export enum Collection {
|
||||
users = "users",
|
||||
categories = "categories",
|
||||
comments = "comments",
|
||||
follows = "follows",
|
||||
list_share = "list_share",
|
||||
lists = "lists",
|
||||
notifications = "notifications",
|
||||
settings = "settings",
|
||||
summit_logs = "summit_logs",
|
||||
trail_share = "trail_share",
|
||||
trails = "trails",
|
||||
waypoints = "waypoints",
|
||||
activities = "activities",
|
||||
follow_counts = "follow_counts",
|
||||
trails_bounding_box = "trails_bounding_box",
|
||||
trails_filter = "trails_filter",
|
||||
users_anonymous = "users_anonymous"
|
||||
}
|
||||
|
||||
export async function list<T>(event: RequestEvent, collection: Collection) {
|
||||
const searchParams = Object.fromEntries(event.url.searchParams);
|
||||
const safeSearchParams = RecordListOptionsSchema.parse(searchParams);
|
||||
|
||||
let r: ListResult<T>;
|
||||
if ((safeSearchParams.perPage ?? 0) < 0) {
|
||||
const activities: T[] = await pb.collection(Collection[collection])
|
||||
.getFullList<T>(safeSearchParams)
|
||||
r = {
|
||||
items: activities,
|
||||
perPage: -1,
|
||||
page: 1,
|
||||
totalItems: activities.length,
|
||||
totalPages: 1
|
||||
}
|
||||
} else {
|
||||
r = await pb.collection(Collection[collection])
|
||||
.getList<T>(safeSearchParams.page, safeSearchParams.perPage, { ...safeSearchParams })
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
export async function show<T>(event: RequestEvent, collection: Collection) {
|
||||
const params = event.params
|
||||
const safeParams = RecordIdSchema.parse(params);
|
||||
|
||||
const searchParams = Object.fromEntries(event.url.searchParams);
|
||||
const safeSearchParams = RecordOptionsSchema.parse(searchParams);
|
||||
|
||||
const r = await pb.collection(collection.toString())
|
||||
.getOne<T>(safeParams.id, safeSearchParams)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
export async function create<T>(event: RequestEvent, schema: ZodSchema, collection: Collection) {
|
||||
const data = await event.request.json();
|
||||
|
||||
const safeData = schema.parse(data);
|
||||
|
||||
const r = await pb.collection(Collection[collection]).create<T>(safeData)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
export async function update<T>(event: RequestEvent, schema: ZodSchema, collection: Collection) {
|
||||
const params = event.params
|
||||
const safeParams = RecordIdSchema.parse(params);
|
||||
|
||||
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]).update<T>(safeParams.id, safeData, safeSearchParams)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
export async function upload<T>(event: RequestEvent, collection: Collection) {
|
||||
const params = event.params
|
||||
const safeParams = RecordIdSchema.parse(params);
|
||||
|
||||
const data = await event.request.formData();
|
||||
|
||||
const r = await pb.collection(Collection[collection]).update<T>(safeParams.id, data)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
export async function remove(event: RequestEvent, collection: Collection) {
|
||||
const params = event.params
|
||||
const safeParams = RecordIdSchema.parse(params);
|
||||
|
||||
const r = await pb.collection(Collection[collection]).delete(safeParams.id)
|
||||
|
||||
return { 'acknowledged': r }
|
||||
}
|
||||
|
||||
export function handleError(e: any) {
|
||||
if (e instanceof ZodError) {
|
||||
return error(400, e.message)
|
||||
} else if (e instanceof ClientResponseError && e.status > 0) {
|
||||
return error(e.status as NumericRange<400, 599>, e.message)
|
||||
} else {
|
||||
return e
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ export async function gpx2trail(gpxString: string, fallbackName?: string) {
|
||||
wp.id = cryptoRandomString({ length: 15 });
|
||||
wp.name = wpt.name ?? ""
|
||||
wp.description = wpt.desc;
|
||||
trail.expand.waypoints.push(wp);
|
||||
trail.expand!.waypoints.push(wp);
|
||||
}
|
||||
|
||||
const totals = gpx.getTotals()
|
||||
|
||||
@@ -8,7 +8,7 @@ import { notifications_index } from '$lib/stores/notification_store';
|
||||
export const load: LayoutServerLoad = async ({ locals, url, fetch }) => {
|
||||
|
||||
let notifications
|
||||
if (locals.user) {
|
||||
if (locals.user?.id) {
|
||||
notifications = await notifications_index({ recipient: locals.user.id }, 1, 10, fetch);
|
||||
}
|
||||
return { settings: locals.settings as Settings, notifications, origin: env.ORIGIN }
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import type { Activity } from "$lib/models/activity";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { Collection, handleError, list } from "$lib/util/api_util";
|
||||
import { json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
const page = event.url.searchParams.get("page") ?? "0";
|
||||
const perPage = event.url.searchParams.get("per-page") ?? "10";
|
||||
const filter = event.url.searchParams.get("filter") ?? "";
|
||||
|
||||
try {
|
||||
const r = await pb.collection('activities')
|
||||
.getList<Activity>(parseInt(page), parseInt(perPage), { filter: filter ?? "" })
|
||||
const r = await list<Activity>(event, Collection.activities);
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
} catch (e) {
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { z } from "zod";
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
try {
|
||||
let data;
|
||||
try {
|
||||
data = await event.request.json()
|
||||
} catch (e) {
|
||||
throw new ClientResponseError({ status: 400, response: { message: "Invalid json" } })
|
||||
}
|
||||
|
||||
const r = await pb.collection('users').confirmPasswordReset(data.token, data.password, data.passwordConfirm);
|
||||
const data = await event.request.json()
|
||||
const safeData = z.object({
|
||||
token: z.string(),
|
||||
password: z.string(),
|
||||
passwordConfirm: z.string()
|
||||
}).refine(d => d.password === d.passwordConfirm).parse(data)
|
||||
const r = await pb.collection('users').confirmPasswordReset(safeData.token, safeData.password, safeData.passwordConfirm);
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { z } from "zod";
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
try {
|
||||
let data;
|
||||
try {
|
||||
data = await event.request.json()
|
||||
} catch (e) {
|
||||
throw new ClientResponseError({ status: 400, response: { message: "Invalid json" } })
|
||||
}
|
||||
const data = await event.request.json();
|
||||
const safeData = z.object({
|
||||
email: z.string().email().optional(),
|
||||
username: z.string().optional(),
|
||||
password: z.string()
|
||||
}).refine(d => d.email !== undefined || d.username !== undefined).parse(data);
|
||||
|
||||
const r = await pb.collection('users').authWithPassword(data.email ?? data.username!, data.password);
|
||||
|
||||
const r = await pb.collection('users').authWithPassword(safeData.email ?? safeData.username!, data.password);
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
|
||||
@@ -3,6 +3,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";
|
||||
|
||||
const redirectURL = private_env.ORIGIN + "/login/redirect"
|
||||
|
||||
@@ -23,12 +24,18 @@ export async function GET(event: RequestEvent) {
|
||||
}
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json();
|
||||
try {
|
||||
const data = await event.request.json();
|
||||
const safeData = z.object({
|
||||
name: z.string(),
|
||||
code: z.string(),
|
||||
codeVerifier: z.string()
|
||||
}).parse(data)
|
||||
|
||||
const r = await pb.collection('users').authWithOAuth2Code(
|
||||
data.name,
|
||||
data.code,
|
||||
data.codeVerifier,
|
||||
safeData.name,
|
||||
safeData.code,
|
||||
safeData.codeVerifier,
|
||||
redirectURL,
|
||||
)
|
||||
return json(r)
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { handleError } from "$lib/util/api_util";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { z } from "zod";
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
try {
|
||||
let data;
|
||||
try {
|
||||
data = await event.request.json()
|
||||
} catch (e) {
|
||||
throw new ClientResponseError({ status: 400, response: { message: "Invalid json" } })
|
||||
}
|
||||
const data = await event.request.json()
|
||||
const safeData = z.object({
|
||||
email: z.string().email()
|
||||
}).parse(data)
|
||||
|
||||
const r = await pb.collection('users').requestPasswordReset(data.email);
|
||||
const r = await pb.collection('users').requestPasswordReset(safeData.email);
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
throw handleError(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { Category } from "$lib/models/category";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { Collection, handleError, list } from "$lib/util/api_util";
|
||||
import { json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r: Category[] = await pb.collection('categories').getFullList<Category>()
|
||||
const r = await list<Category>(event, Collection.categories);
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
} catch (e) {
|
||||
throw handleError(e)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,37 +1,30 @@
|
||||
import { CommentCreateSchema } from '$lib/models/api/comment_schema';
|
||||
import type { Comment } from '$lib/models/comment';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
import { Collection, create, handleError, list } from '$lib/util/api_util';
|
||||
import { json, type RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
const filter = event.url.searchParams.get('filter') ?? ""
|
||||
|
||||
try {
|
||||
const r: Comment[] = await pb.collection('comments').getFullList<Comment>({
|
||||
expand: "author",
|
||||
filter: filter,
|
||||
sort: "-created",
|
||||
})
|
||||
|
||||
for (const comment of r) {
|
||||
if(!comment.expand?.author) {
|
||||
const r = await list<Comment>(event, Collection.comments);
|
||||
for (const comment of r.items) {
|
||||
if (!comment.expand?.author) {
|
||||
comment.expand = {
|
||||
author: await pb.collection('users_anonymous').getOne(comment.author)
|
||||
}
|
||||
}
|
||||
}
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
} catch (e) {
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
const data = await event.request.json();
|
||||
|
||||
try {
|
||||
const r = await pb.collection('comments').create<Comment>(data)
|
||||
const r = await create<Comment>(event, CommentCreateSchema, Collection.comments)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
} catch (e) {
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,35 @@
|
||||
import { CommentUpdateSchema } from "$lib/models/api/comment_schema";
|
||||
import type { Comment } from "$lib/models/comment";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { Collection, handleError, remove, show, update } from "$lib/util/api_util";
|
||||
import { json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('comments')
|
||||
.getOne<Comment>(event.params.id as string, { expand: "author" })
|
||||
const r = await show<Comment>(event, Collection.comments)
|
||||
r.expand = {
|
||||
author: await pb.collection('users_anonymous').getOne(r.author)
|
||||
}
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
|
||||
try {
|
||||
const r = await pb.collection('comments').update<Comment>(event.params.id as string, data)
|
||||
const r = await update<Comment>(event, CommentUpdateSchema, Collection.comments)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('comments').delete(event.params.id as string)
|
||||
return json({ 'acknowledged': r });
|
||||
const r = await remove(event, Collection.comments)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, type RequestEvent } from "@sveltejs/kit";
|
||||
import { z } from "zod";
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
|
||||
const safeParams = z.object({
|
||||
collection: z.string().length(15),
|
||||
record: z.string().length(15),
|
||||
file: z.string()
|
||||
}).safeParse(event.params)
|
||||
|
||||
if (!safeParams.success) {
|
||||
throw error(400, 'Invalid params')
|
||||
}
|
||||
|
||||
const parts = [];
|
||||
parts.push("api");
|
||||
parts.push("files");
|
||||
parts.push(encodeURIComponent(event.params.collection as string));
|
||||
parts.push(encodeURIComponent(event.params.record as string));
|
||||
parts.push(encodeURIComponent(event.params.file as string));
|
||||
parts.push(encodeURIComponent(safeParams.data.collection));
|
||||
parts.push(encodeURIComponent(safeParams.data.record));
|
||||
parts.push(encodeURIComponent(safeParams.data.file));
|
||||
|
||||
let fileURL = pb.buildUrl(parts.join("/"));
|
||||
|
||||
try {
|
||||
const r = await event.fetch(fileURL)
|
||||
return new Response(r.body, {headers: r.headers});
|
||||
return new Response(r.body, { headers: r.headers });
|
||||
} catch (e: any) {
|
||||
console.log(e);
|
||||
|
||||
throw error(500, e);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,45 +1,31 @@
|
||||
import { FollowCreateSchema } from '$lib/models/api/follow_schema';
|
||||
import type { Follow } from '$lib/models/follow';
|
||||
import type { UserAnonymous } from '$lib/models/user';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
import { Collection, create, handleError, list } from '$lib/util/api_util';
|
||||
import { json, type RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
const page = event.url.searchParams.get("page") ?? "0";
|
||||
const perPage = event.url.searchParams.get("per-page") ?? "10";
|
||||
const sort = event.url.searchParams.get('sort') ?? ""
|
||||
const filter = event.url.searchParams.get("filter") ?? "";
|
||||
|
||||
try {
|
||||
let r;
|
||||
if (parseInt(perPage) < 0) {
|
||||
r = {
|
||||
items: await pb.collection('follows')
|
||||
.getFullList<Follow>({ sort: sort, filter: filter, requestKey: filter })
|
||||
}
|
||||
} else {
|
||||
r = await pb.collection('follows')
|
||||
.getList<Follow>(parseInt(page), parseInt(perPage), { sort: sort ?? "", filter: filter ?? "", requestKey: filter })
|
||||
}
|
||||
const r = await list<Follow>(event, Collection.follows);
|
||||
for (const follow of r.items) {
|
||||
const follower = await pb.collection('users_anonymous').getOne<UserAnonymous>(follow.follower, { requestKey: filter })
|
||||
const followee = await pb.collection('users_anonymous').getOne<UserAnonymous>(follow.followee, { requestKey: filter })
|
||||
const follower = await pb.collection('users_anonymous').getOne<UserAnonymous>(follow.follower, { requestKey: null })
|
||||
const followee = await pb.collection('users_anonymous').getOne<UserAnonymous>(follow.followee, { requestKey: null })
|
||||
follow.expand = {
|
||||
follower, followee
|
||||
}
|
||||
}
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
} catch (e) {
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
const data = await event.request.json();
|
||||
|
||||
try {
|
||||
const r = await pb.collection('follows').create<Follow>(data)
|
||||
const r = await create<Follow>(event, FollowCreateSchema, Collection.follows)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
} catch (e) {
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,13 @@
|
||||
import type { Follow } from "$lib/models/follow";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { Collection, handleError, remove } from "$lib/util/api_util";
|
||||
import { json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('follows')
|
||||
.getOne<Follow>(event.params.id as string)
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
|
||||
try {
|
||||
const r = await pb.collection('follows').update<Follow>(event.params.id as string, data)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('follows').delete(event.params.id as string)
|
||||
return json({ 'acknowledged': r });
|
||||
const r = await remove(event, Collection.follows)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { Collection, handleError, show } from "$lib/util/api_util";
|
||||
import { json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('follow_counts')
|
||||
.getOne(event.params.id as string)
|
||||
const r = await show(event, Collection.follow_counts)
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,15 @@
|
||||
import { ListShareCreateSchema } from '$lib/models/api/list_share_schema';
|
||||
import type { ListShare } from '$lib/models/list_share';
|
||||
import type { User } from '$lib/models/user';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import { Collection, create, handleError, list } from '$lib/util/api_util';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
const sort = event.url.searchParams.get('sort') ?? ""
|
||||
const filter = event.url.searchParams.get("filter") ?? "";
|
||||
|
||||
try {
|
||||
const r: ListShare[] = await pb.collection('list_share').getFullList<ListShare>({
|
||||
sort: sort,
|
||||
filter: filter
|
||||
})
|
||||
for (const share of r) {
|
||||
const r = await list<ListShare>(event, Collection.list_share);
|
||||
|
||||
for (const share of r.items) {
|
||||
const anonymous_user = await pb.collection('users_anonymous').getOne<User>(share.user)
|
||||
share.expand = {
|
||||
user: anonymous_user
|
||||
@@ -20,17 +17,15 @@ export async function GET(event: RequestEvent) {
|
||||
}
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
throw handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
const data = await event.request.json();
|
||||
|
||||
try {
|
||||
const r = await pb.collection('list_share').create<ListShare>(data)
|
||||
const r = await create<ListShare>(event, ListShareCreateSchema, Collection.list_share)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
} catch (e) {
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,31 @@
|
||||
import { ListShareUpdateSchema } from "$lib/models/api/list_share_schema";
|
||||
import type { ListShare } from "$lib/models/list_share";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { Collection, handleError, remove, show, update } from "$lib/util/api_util";
|
||||
import { json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('list_share')
|
||||
.getOne<ListShare>(event.params.id as string)
|
||||
const r = await show<ListShare>(event, Collection.list_share)
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
|
||||
try {
|
||||
const r = await pb.collection('list_share').update<ListShare>(event.params.id as string, data)
|
||||
const r = await update<ListShare>(event, ListShareUpdateSchema, Collection.list_share)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('list_share').delete(event.params.id as string)
|
||||
return json({ 'acknowledged': r });
|
||||
const r = await remove(event, Collection.list_share)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,12 @@
|
||||
import { ListCreateSchema } from '$lib/models/api/list_schema';
|
||||
import type { List } from '$lib/models/list';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
import { Collection, create, handleError, list } from '$lib/util/api_util';
|
||||
import { json, type RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
const page = event.url.searchParams.get("page") ?? "0";
|
||||
const perPage = event.url.searchParams.get("per-page") ?? "10";
|
||||
const sort = event.url.searchParams.get('sort') ?? ""
|
||||
const filter = event.url.searchParams.get("filter") ?? "";
|
||||
|
||||
const expand = "trails,trails.waypoints,trails.category,list_share_via_list";
|
||||
|
||||
try {
|
||||
let r;
|
||||
if (parseInt(perPage) < 0) {
|
||||
r = {
|
||||
items: await pb.collection('lists')
|
||||
.getFullList<List>({ expand: expand, sort: sort, filter: filter })
|
||||
}
|
||||
} else {
|
||||
r = await pb.collection('lists')
|
||||
.getList<List>(parseInt(page), parseInt(perPage), { expand: expand, sort: sort ?? "", filter: filter ?? "" })
|
||||
}
|
||||
|
||||
const r = await list<List>(event, Collection.lists);
|
||||
for (const t of r.items) {
|
||||
if (!t.author || !pb.authStore.model) {
|
||||
continue;
|
||||
@@ -31,20 +16,17 @@ export async function GET(event: RequestEvent) {
|
||||
}
|
||||
t.expand!.author = await pb.collection("users_anonymous").getOne(t.author);
|
||||
}
|
||||
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
} catch (e) {
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
const data = await event.request.json();
|
||||
|
||||
try {
|
||||
const r = await pb.collection('lists').create<List>(data)
|
||||
const r = await create<List>(event, ListCreateSchema, Collection.lists)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
} catch (e) {
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,36 @@
|
||||
import { ListUpdateSchema } from "$lib/models/api/list_schema";
|
||||
import type { List } from "$lib/models/list";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { Collection, handleError, remove, show, update } from "$lib/util/api_util";
|
||||
import { json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('lists')
|
||||
.getOne<List>(event.params.id as string, { expand: "trails,trails.waypoints,trails.category,list_share_via_list" })
|
||||
const r = await show<List>(event, Collection.lists)
|
||||
r.expand = {
|
||||
author: await pb.collection('users_anonymous').getOne(r.author!)
|
||||
}
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
|
||||
try {
|
||||
const r = await pb.collection('lists').update<List>(event.params.id as string, data)
|
||||
const r = await update<List>(event, ListUpdateSchema, Collection.lists)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('lists').delete(event.params.id as string)
|
||||
return json({ 'acknowledged': r });
|
||||
const r = await remove(event, Collection.lists)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { Collection, handleError, upload } from "$lib/util/api_util";
|
||||
import { json, type RequestEvent } from "@sveltejs/kit";
|
||||
import type { List } from "postcss/lib/list";
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.formData()
|
||||
try {
|
||||
const r = await pb.collection("lists").update<List>(event.params.id as string, data,);
|
||||
const r = await upload<List>(event, Collection.lists);
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,16 @@
|
||||
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 { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
const page = event.url.searchParams.get("page") ?? "0";
|
||||
const perPage = event.url.searchParams.get("per-page") ?? "10";
|
||||
const sort = event.url.searchParams.get('sort') ?? ""
|
||||
const filter = event.url.searchParams.get("filter") ?? "";
|
||||
|
||||
try {
|
||||
let r;
|
||||
if (parseInt(perPage) < 0) {
|
||||
r = {
|
||||
items: await pb.collection('notifications')
|
||||
.getFullList<Notification>({ sort: sort, filter: filter, requestKey: filter })
|
||||
}
|
||||
} else {
|
||||
r = await pb.collection('notifications')
|
||||
.getList<Notification>(parseInt(page), parseInt(perPage), { sort: sort ?? "", filter: filter ?? "", requestKey: filter })
|
||||
}
|
||||
const r = await list<Notification>(event, Collection.notifications);
|
||||
|
||||
for (const notification of r.items) {
|
||||
const recipient = await pb.collection('users_anonymous').getOne<UserAnonymous>(notification.recipient, { requestKey: filter })
|
||||
const author = await pb.collection('users_anonymous').getOne<UserAnonymous>(notification.author, { requestKey: filter })
|
||||
const recipient = await pb.collection('users_anonymous').getOne<UserAnonymous>(notification.recipient, { requestKey: null })
|
||||
const author = await pb.collection('users_anonymous').getOne<UserAnonymous>(notification.author, { requestKey: null })
|
||||
notification.expand = {
|
||||
recipient, author
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import type { Follow } from "$lib/models/follow";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { NotificationUpdateSchema } from "$lib/models/api/notification_schema";
|
||||
import { Collection, handleError, update } from "$lib/util/api_util";
|
||||
import { json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
|
||||
try {
|
||||
const r = await pb.collection('notifications').update<Notification>(event.params.id as string, { ...data, seen: true })
|
||||
const r = await update<Comment>(event, NotificationUpdateSchema, Collection.notifications)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
import type { Settings } from '$lib/models/settings';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import { SettingsCreateSchema } from '$lib/models/api/settings_schema';
|
||||
import { Collection, create } from '$lib/util/api_util';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
const data = await event.request.json();
|
||||
|
||||
try {
|
||||
const r = await pb.collection('settings').create<Settings>(data)
|
||||
const r = await create<Comment>(event, SettingsCreateSchema, Collection.settings)
|
||||
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
import { SettingsCreateSchema } from '$lib/models/api/settings_schema';
|
||||
import type { Settings } from "$lib/models/settings";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { Collection, handleError, remove, show, update } from "$lib/util/api_util";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('settings').getFirstListItem<Settings>(`user="${event.params.id}"`)
|
||||
const r = await show<Settings>(event, Collection.settings)
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
throw handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
try {
|
||||
const r = await await pb.collection("settings").update<Settings>(event.params.id as string, data);
|
||||
const r = await update<Settings>(event, SettingsCreateSchema, Collection.settings)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('settings').delete(event.params.id as string)
|
||||
return json({ 'acknowledged': r });
|
||||
const r = await remove(event, Collection.settings)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,32 @@
|
||||
import { SummitLogCreateSchema } from '$lib/models/api/summit_log_schema';
|
||||
import type { SummitLog } from '$lib/models/summit_log';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import { create, handleError } from '$lib/util/api_util';
|
||||
import { Collection, list } from '$lib/util/api_util';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
const filter = event.url.searchParams.get('filter') ?? ""
|
||||
|
||||
try {
|
||||
const r: SummitLog[] = await pb.collection('summit_logs').getFullList<SummitLog>({
|
||||
expand: "trails_via_summit_logs.category",
|
||||
sort: "+date",
|
||||
filter: filter
|
||||
})
|
||||
const r = await list<SummitLog>(event, Collection.summit_logs);
|
||||
|
||||
for (const t of r) {
|
||||
for (const t of r.items) {
|
||||
if (!t.author || !pb.authStore.model) {
|
||||
continue;
|
||||
}
|
||||
if (!t.expand) {
|
||||
t.expand = {} as any
|
||||
}
|
||||
t.expand.author = await pb.collection("users_anonymous").getOne(t.author);
|
||||
t.expand!.author = await pb.collection("users_anonymous").getOne(t.author);
|
||||
}
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
throw handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
const data = await event.request.json();
|
||||
|
||||
try {
|
||||
const r = await pb.collection('summit_logs').create<SummitLog>(data)
|
||||
const r = await create<SummitLog>(event, SummitLogCreateSchema, Collection.summit_logs)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
import { SummitLogUpdateSchema } from "$lib/models/api/summit_log_schema";
|
||||
import type { SummitLog } from "$lib/models/summit_log";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { Collection, handleError, remove, show, update } from "$lib/util/api_util";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('summit_logs')
|
||||
.getOne<SummitLog>(event.params.id as string)
|
||||
const r = await show<SummitLog>(event, Collection.summit_logs)
|
||||
|
||||
if (!r.expand) {
|
||||
r.expand = {} as any
|
||||
}
|
||||
r.expand.author = await pb.collection("users_anonymous").getOne(r.author!);
|
||||
r.expand!.author = await pb.collection("users_anonymous").getOne(r.author!);
|
||||
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
throw handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
try {
|
||||
const r = await await pb.collection("summit_logs").update<SummitLog>(event.params.id as string, data);
|
||||
const r = await update<SummitLog>(event, SummitLogUpdateSchema, Collection.summit_logs)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('summit_logs').delete(event.params.id as string)
|
||||
return json({ 'acknowledged': r });
|
||||
const r = await remove(event, Collection.summit_logs)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { SummitLog } from "$lib/models/summit_log";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { Collection, upload } from "$lib/util/api_util";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.formData()
|
||||
try {
|
||||
const r = await pb.collection("summit_logs").update<SummitLog>(event.params.id as string, data,);
|
||||
const r = await upload<SummitLog>(event, Collection.summit_logs);
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
|
||||
@@ -1,36 +1,30 @@
|
||||
import { TrailShareCreateSchema } from '$lib/models/api/trail_share_schema';
|
||||
import type { TrailShare } from '$lib/models/trail_share';
|
||||
import type { User } from '$lib/models/user';
|
||||
import type { UserAnonymous } from '$lib/models/user';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
import { Collection, handleError, list, create } from '$lib/util/api_util';
|
||||
import { json, type RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
const sort = event.url.searchParams.get('sort') ?? ""
|
||||
const filter = event.url.searchParams.get("filter") ?? "";
|
||||
|
||||
try {
|
||||
const r: TrailShare[] = await pb.collection('trail_share').getFullList<TrailShare>({
|
||||
sort: sort,
|
||||
filter: filter
|
||||
})
|
||||
for (const share of r) {
|
||||
const anonymous_user = await pb.collection('users_anonymous').getOne<User>(share.user)
|
||||
const r = await list<TrailShare>(event, Collection.trail_share);
|
||||
for (const share of r.items) {
|
||||
const anonymous_user = await pb.collection('users_anonymous').getOne<UserAnonymous>(share.user)
|
||||
share.expand = {
|
||||
user: anonymous_user
|
||||
}
|
||||
}
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
const data = await event.request.json();
|
||||
|
||||
try {
|
||||
const r = await pb.collection('trail_share').create<TrailShare>(data)
|
||||
const r = await create<TrailShare>(event, TrailShareCreateSchema, Collection.trail_share)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
} catch (e) {
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,31 @@
|
||||
import { TrailShareUpdateSchema } from "$lib/models/api/trail_share_schema";
|
||||
import type { TrailShare } from "$lib/models/trail_share";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { Collection, handleError, remove, show, update } from "$lib/util/api_util";
|
||||
import { json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('trail_share')
|
||||
.getOne<TrailShare>(event.params.id as string)
|
||||
const r = await show<TrailShare>(event, Collection.trail_share)
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
|
||||
try {
|
||||
const r = await pb.collection('trail_share').update<TrailShare>(event.params.id as string, data)
|
||||
const r = await update<TrailShare>(event, TrailShareUpdateSchema, Collection.trail_share)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('trail_share').delete(event.params.id as string)
|
||||
return json({ 'acknowledged': r });
|
||||
const r = await remove(event, Collection.trail_share)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,33 @@
|
||||
import { TrailCreateSchema } from '$lib/models/api/trail_schema';
|
||||
import type { Trail } from '$lib/models/trail';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import { Collection, create, handleError, list } from '$lib/util/api_util';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
const page = event.url.searchParams.get("page") ?? "0";
|
||||
const perPage = event.url.searchParams.get("per-page") ?? "21";
|
||||
const expand = event.url.searchParams.get("expand") ?? ""
|
||||
const sort = event.url.searchParams.get("sort") ?? ""
|
||||
const filter = event.url.searchParams.get("filter") ?? "";
|
||||
|
||||
try {
|
||||
let r;
|
||||
if (parseInt(perPage) < 0) {
|
||||
r = {
|
||||
items: await pb.collection('trails')
|
||||
.getFullList<Trail>({ expand: expand, sort: sort, filter: filter })
|
||||
}
|
||||
} else {
|
||||
r = await pb.collection('trails')
|
||||
.getList<Trail>(parseInt(page), parseInt(perPage), { expand: expand ?? "", sort: sort ?? "", filter: filter ?? "" })
|
||||
}
|
||||
const r = await list<Trail>(event, Collection.trails);
|
||||
|
||||
for (const t of r.items) {
|
||||
if (!t.author || !pb.authStore.model) {
|
||||
continue;
|
||||
}
|
||||
if(!t.expand) {
|
||||
if (!t.expand) {
|
||||
t.expand = {} as any
|
||||
}
|
||||
t.expand.author = await pb.collection("users_anonymous").getOne(t.author);
|
||||
t.expand!.author = await pb.collection("users_anonymous").getOne(t.author);
|
||||
}
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
console.error(e)
|
||||
throw error(e.status, e);
|
||||
throw handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
const data = await event.request.json();
|
||||
try {
|
||||
const r = await pb.collection('trails').create<Trail>(data)
|
||||
|
||||
const r = await create<Trail>(event, TrailCreateSchema, Collection.trails)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
} catch (e) {
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,18 @@
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { Collection, handleError, remove, show, update } from "$lib/util/api_util";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { TrailUpdateSchema } from '$lib/models/api/trail_schema';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
const expand = event.url.searchParams.get("expand") ?? ""
|
||||
|
||||
try {
|
||||
const r = await pb.collection('trails')
|
||||
.getOne<Trail>(event.params.id as string, { expand: expand ?? "" })
|
||||
|
||||
const r = await show<Trail>(event, Collection.trails)
|
||||
|
||||
if (pb.authStore.model) {
|
||||
if (!r.expand) {
|
||||
r.expand = {} as any
|
||||
}
|
||||
r.expand.author = await pb.collection("users_anonymous").getOne(r.author!);
|
||||
r.expand!.author = await pb.collection("users_anonymous").getOne(r.author!);
|
||||
}
|
||||
|
||||
// remove time from dates
|
||||
@@ -25,33 +23,31 @@ export async function GET(event: RequestEvent) {
|
||||
if (!log.expand) {
|
||||
log.expand = {} as any
|
||||
}
|
||||
if(log.author) {
|
||||
log.expand.author = await pb.collection("users_anonymous").getOne(log.author);
|
||||
if (log.author) {
|
||||
log.expand!.author = await pb.collection("users_anonymous").getOne(log.author);
|
||||
}
|
||||
}
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
console.error(e)
|
||||
throw error(e.status || 500, e);
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
try {
|
||||
const r = await pb.collection("trails").update<Trail>(event.params.id as string, data, { expand: "category,waypoints,summit_logs" });
|
||||
const r = await update<Trail>(event, TrailUpdateSchema, Collection.trails)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('trails').delete(event.params.id as string)
|
||||
return json({ 'acknowledged': r });
|
||||
const r = await remove(event, Collection.trails)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { Collection, upload } from "$lib/util/api_util";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.formData()
|
||||
try {
|
||||
const r = await pb.collection("trails").update<Trail>(event.params.id as string, data,);
|
||||
const r = await upload<Trail>(event, Collection.trails);
|
||||
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
|
||||
@@ -24,10 +24,10 @@ export async function PUT(event: RequestEvent) {
|
||||
elevation_loss: trail.elevation_loss,
|
||||
duration: trail.duration ? trail.duration * 60 : undefined,
|
||||
})
|
||||
log.expand.gpx_data = gpxData;
|
||||
log.expand!.gpx_data = gpxData;
|
||||
log._gpx = new File([gpxFile], (data.get("name") as string | null) ?? "file");
|
||||
|
||||
trail.expand.summit_logs.push(log);
|
||||
trail.expand!.summit_logs.push(log);
|
||||
} catch (e: any) {
|
||||
throw new ClientResponseError({ status: 400, response: { message: "Invalid file" } })
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { UserCreateSchema } from '$lib/models/api/user_schema';
|
||||
import type { User } from '$lib/models/user';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit'
|
||||
import { Collection, create, handleError } from '$lib/util/api_util';
|
||||
import { json, type RequestEvent } from '@sveltejs/kit';
|
||||
import { ClientResponseError } from 'pocketbase';
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
try {
|
||||
if (env.PUBLIC_DISABLE_SIGNUP === "true") {
|
||||
throw new ClientResponseError({ status: 401, response: { messgage: "Forbidden" } })
|
||||
}
|
||||
const r = await pb.collection('users').create<User>(data)
|
||||
const r = await create<User>(event, UserCreateSchema, Collection.users)
|
||||
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,41 +1,49 @@
|
||||
import { RecordIdSchema } from '$lib/models/api/base_schema';
|
||||
import { UserUpdateSchema } from '$lib/models/api/user_schema';
|
||||
import type { User } from '$lib/models/user';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import { Collection, handleError, remove, show } from '$lib/util/api_util';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit'
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('users')
|
||||
.getOne<User>(event.params.id as string)
|
||||
const r = await show<User>(event, Collection.users)
|
||||
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
throw handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
try {
|
||||
if (data.email != pb.authStore.model!.email) {
|
||||
const r = await pb.collection('users').requestEmailChange(data.email);
|
||||
pb.authStore.model!.email = data.email;
|
||||
const params = event.params
|
||||
const safeParams = RecordIdSchema.parse(params);
|
||||
|
||||
const safeData = UserUpdateSchema.parse(data);
|
||||
|
||||
if (safeData.email && safeData.email != pb.authStore.model!.email) {
|
||||
const r = await pb.collection('users').requestEmailChange(safeData.email);
|
||||
pb.authStore.model!.email = safeData.email;
|
||||
}
|
||||
const r = await pb.collection('users').update<User>(event.params.id as string, data)
|
||||
if (data.password) {
|
||||
const r = await pb.collection('users').authWithPassword(data.email ?? data.username!, data.password);
|
||||
const r = await pb.collection('users').update<User>(safeParams.id, safeData)
|
||||
if (safeData.password) {
|
||||
const r = await pb.collection('users').authWithPassword(safeData.email ?? safeData.username!, safeData.password);
|
||||
return json(r.record)
|
||||
} else {
|
||||
return json(r);
|
||||
}
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('users').delete(event.params.id as string)
|
||||
return json({ 'acknowledged': r });
|
||||
const r = await remove(event, Collection.users)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import type { User } from "$lib/stores/user_store";
|
||||
import type { User } from "$lib/models/user";
|
||||
import { Collection, upload } from "$lib/util/api_util";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.formData()
|
||||
try {
|
||||
const r = await pb.collection("users").update<User>(event.params.id as string, data,);
|
||||
const r = await upload<User>(event, Collection.users);
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
import type { User } from '$lib/models/user';
|
||||
import type { UserAnonymous } from '$lib/models/user';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
import { Collection, handleError, list } from '$lib/util/api_util';
|
||||
import { json, type RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
const page = event.url.searchParams.get("page") ?? "0";
|
||||
const perPage = event.url.searchParams.get("per-page") ?? "10";
|
||||
const sort = event.url.searchParams.get("sort") ?? ""
|
||||
let filter = event.url.searchParams.get("filter") ?? "";
|
||||
let filter = event.url.searchParams.get("filter");
|
||||
|
||||
if (!filter.length) {
|
||||
if (!filter) {
|
||||
filter = "private=false"
|
||||
} else {
|
||||
filter += "&&private=false"
|
||||
}
|
||||
event.url.searchParams.set("filter", filter);
|
||||
|
||||
try {
|
||||
const r = await pb.collection('users_anonymous')
|
||||
.getList<User>(parseInt(page), parseInt(perPage), { sort: sort ?? "", filter: filter ?? "" })
|
||||
const r = await list<UserAnonymous>(event, Collection.users_anonymous);
|
||||
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
throw handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { User } from '$lib/models/user';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit'
|
||||
import type { UserAnonymous } from "$lib/models/user";
|
||||
import { Collection, handleError, show } from '$lib/util/api_util';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('users_anonymous')
|
||||
.getOne<User>(event.params.id as string)
|
||||
const r = await show<UserAnonymous>(event, Collection.users_anonymous)
|
||||
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
throw handleError(e);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,22 @@
|
||||
import { WaypointCreateSchema } from '$lib/models/api/waypoint_schema';
|
||||
import type { Waypoint } from '$lib/models/waypoint';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
import { Collection, create, handleError, list } from '$lib/util/api_util';
|
||||
import { json, type RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r: Waypoint[] = await pb.collection('waypoints').getFullList<Waypoint>()
|
||||
const r = await list<Waypoint>(event, Collection.waypoints);
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
throw handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
const data = await event.request.json();
|
||||
|
||||
try {
|
||||
const r = await pb.collection('waypoints').create<Waypoint>(data)
|
||||
const r = await create<Waypoint>(event, WaypointCreateSchema, Collection.waypoints)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
} catch (e) {
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,31 @@
|
||||
import { WaypointUpdateSchema } from "$lib/models/api/waypoint_schema";
|
||||
import type { Waypoint } from "$lib/models/waypoint";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { Collection, handleError, remove, show, update } from "$lib/util/api_util";
|
||||
import { json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('waypoints')
|
||||
.getOne<Waypoint>(event.params.id as string)
|
||||
const r = await show<Waypoint>(event, Collection.waypoints)
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(event: RequestEvent){
|
||||
const data = await event.request.json()
|
||||
export async function POST(event: RequestEvent) {
|
||||
try {
|
||||
const r = await await pb.collection("waypoints").update<Waypoint>(event.params.id as string, data);
|
||||
const r = await update<Waypoint>(event, WaypointUpdateSchema, Collection.waypoints)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('waypoints').delete(event.params.id as string)
|
||||
return json({ 'acknowledged': r });
|
||||
const r = await remove(event, Collection.waypoints)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import type { Waypoint } from "$lib/models/waypoint";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { Collection, handleError, upload } from "$lib/util/api_util";
|
||||
import { json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.formData()
|
||||
try {
|
||||
const r = await pb.collection("waypoints").update<Waypoint>(event.params.id as string, data,);
|
||||
const r = await upload<Waypoint>(event, Collection.waypoints);
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
throw handleError(e);
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,10 @@
|
||||
) {
|
||||
for (const trail of item.expand.trails) {
|
||||
const gpxData: string = await fetchGPX(trail);
|
||||
trail.expand.gpx_data = gpxData;
|
||||
if(!trail.expand) {
|
||||
(trail as any).expand = {}
|
||||
}
|
||||
trail.expand!.gpx_data = gpxData;
|
||||
}
|
||||
}
|
||||
selectedList = item;
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import type { Follow } from "$lib/models/follow";
|
||||
import type { Settings } from "$lib/models/settings";
|
||||
import type { UserAnonymous } from "$lib/models/user";
|
||||
import { follows_a_b, follows_counts, follows_index } from "$lib/stores/follow_store";
|
||||
import { settings_show } from "$lib/stores/settings_store";
|
||||
import { follows_a_b, follows_counts } from "$lib/stores/follow_store";
|
||||
import { users_show } from "$lib/stores/user_store";
|
||||
import { error, type ServerLoad } from "@sveltejs/kit";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
if (!trail.expand) {
|
||||
(trail as any).expand = {};
|
||||
}
|
||||
trail.expand.gpx_data = gpxData;
|
||||
trail.expand!.gpx_data = gpxData;
|
||||
const trailFolder = zip.folder(`${trail.name}`);
|
||||
let fileData: string = await trail2gpx(trail);
|
||||
if (exportSettings.fileFormat == "json") {
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
import { categories } from "$lib/stores/category_store";
|
||||
import {
|
||||
lists_add_trail,
|
||||
lists_index,
|
||||
lists_remove_trail,
|
||||
} from "$lib/stores/list_store";
|
||||
import { summitLog } from "$lib/stores/summit_log_store";
|
||||
@@ -33,7 +32,6 @@
|
||||
trails_create,
|
||||
trails_update,
|
||||
} from "$lib/stores/trail_store";
|
||||
import { currentUser } from "$lib/stores/user_store.js";
|
||||
import {
|
||||
anchors,
|
||||
appendToRoute,
|
||||
@@ -71,7 +69,7 @@
|
||||
let map: M.Map;
|
||||
|
||||
let mapTrail: Trail[] = [];
|
||||
$: gpxData = $form.expand?.gpx_data;
|
||||
$: gpxData = $form.expand!?.gpx_data;
|
||||
$: if (gpxData) {
|
||||
updateTrailOnMap();
|
||||
}
|
||||
@@ -119,7 +117,9 @@
|
||||
const { form, errors, handleChange, handleSubmit } = createForm<Trail>({
|
||||
initialValues: {
|
||||
...data.trail,
|
||||
public: $page.data.settings?.privacy.trails === "public",
|
||||
public: data.trail.id
|
||||
? data.trail.public
|
||||
: $page.data.settings?.privacy.trails === "public",
|
||||
category:
|
||||
data.trail.category ||
|
||||
$page.data.settings?.category ||
|
||||
@@ -160,8 +160,8 @@
|
||||
(p) => !p.startsWith("data:image/svg+xml;base64"),
|
||||
);
|
||||
|
||||
if ($form.expand.gpx_data && overwriteGPX) {
|
||||
gpxFile = new Blob([$form.expand.gpx_data], {
|
||||
if ($form.expand!.gpx_data && overwriteGPX) {
|
||||
gpxFile = new Blob([$form.expand!.gpx_data], {
|
||||
type: "text/xml",
|
||||
});
|
||||
}
|
||||
@@ -219,8 +219,8 @@
|
||||
clearAnchorMarker();
|
||||
clearRoute();
|
||||
|
||||
if ($form.expand.gpx_data) {
|
||||
const gpx = await GPX.parse($form.expand.gpx_data);
|
||||
if ($form.expand!.gpx_data) {
|
||||
const gpx = await GPX.parse($form.expand!.gpx_data);
|
||||
if (!(gpx instanceof Error)) {
|
||||
setRoute(gpx);
|
||||
initRouteAnchors(gpx);
|
||||
@@ -255,7 +255,7 @@
|
||||
const parseResult = await gpx2trail(gpxData, selectedFile.name);
|
||||
$form = parseResult.trail;
|
||||
$form.id = prevId;
|
||||
$form.expand.gpx_data = gpxData;
|
||||
$form.expand!.gpx_data = gpxData;
|
||||
$form.category = $page.data.settings.category || $categories[0].id;
|
||||
$form.public = $page.data.settings?.privacy.trails === "public";
|
||||
|
||||
@@ -266,18 +266,18 @@
|
||||
duration: $form.duration ? $form.duration * 60 : undefined,
|
||||
});
|
||||
|
||||
log.expand.gpx_data = gpxData;
|
||||
log.expand!.gpx_data = gpxData;
|
||||
const blob = new Blob([gpxData], { type: selectedFile.type });
|
||||
log._gpx = new File([blob], selectedFile.name, {
|
||||
type: selectedFile.type,
|
||||
});
|
||||
|
||||
$form.expand.summit_logs.push(log);
|
||||
$form.expand!.summit_logs.push(log);
|
||||
|
||||
setRoute(parseResult.gpx);
|
||||
initRouteAnchors(parseResult.gpx);
|
||||
|
||||
for (const waypoint of $form.expand.waypoints) {
|
||||
for (const waypoint of $form.expand!.waypoints) {
|
||||
saveWaypoint(waypoint);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -307,10 +307,10 @@
|
||||
}
|
||||
|
||||
function clearWaypoints() {
|
||||
for (const waypoint of $form.expand.waypoints) {
|
||||
for (const waypoint of $form.expand!.waypoints) {
|
||||
waypoint.marker?.remove();
|
||||
}
|
||||
$form.expand.waypoints = [];
|
||||
$form.expand!.waypoints = [];
|
||||
$form.waypoints = [];
|
||||
}
|
||||
|
||||
@@ -365,22 +365,25 @@
|
||||
}
|
||||
|
||||
function deleteWaypoint(index: number) {
|
||||
$form.expand.waypoints.splice(index, 1);
|
||||
$form.expand!.waypoints.splice(index, 1);
|
||||
$form.waypoints.splice(index, 1);
|
||||
$form.expand.waypoints = $form.expand.waypoints;
|
||||
$form.expand!.waypoints = $form.expand!.waypoints;
|
||||
}
|
||||
|
||||
function saveWaypoint(savedWaypoint: Waypoint) {
|
||||
let editedWaypointIndex = $form.expand.waypoints.findIndex(
|
||||
let editedWaypointIndex = $form.expand!.waypoints.findIndex(
|
||||
(s) => s.id == savedWaypoint.id,
|
||||
);
|
||||
|
||||
if (editedWaypointIndex >= 0) {
|
||||
$form.expand.waypoints[editedWaypointIndex].marker?.remove();
|
||||
$form.expand.waypoints[editedWaypointIndex] = savedWaypoint;
|
||||
$form.expand!.waypoints[editedWaypointIndex].marker?.remove();
|
||||
$form.expand!.waypoints[editedWaypointIndex] = savedWaypoint;
|
||||
} else {
|
||||
savedWaypoint.id = cryptoRandomString({ length: 15 });
|
||||
$form.expand.waypoints = [...$form.expand.waypoints, savedWaypoint];
|
||||
$form.expand!.waypoints = [
|
||||
...$form.expand!.waypoints,
|
||||
savedWaypoint,
|
||||
];
|
||||
}
|
||||
const marker = createMarkerFromWaypoint(savedWaypoint, moveMarker);
|
||||
|
||||
@@ -390,16 +393,16 @@
|
||||
|
||||
function moveMarker(marker: M.Marker, wpId?: string) {
|
||||
const position = marker.getLngLat();
|
||||
const editableWaypointIndex = $form.expand.waypoints.findIndex(
|
||||
const editableWaypointIndex = $form.expand!.waypoints.findIndex(
|
||||
(w) => w.id == wpId,
|
||||
);
|
||||
const editableWaypoint = $form.expand.waypoints[editableWaypointIndex];
|
||||
const editableWaypoint = $form.expand!.waypoints[editableWaypointIndex];
|
||||
if (!editableWaypoint) {
|
||||
return;
|
||||
}
|
||||
editableWaypoint.lat = position.lat;
|
||||
editableWaypoint.lon = position.lng;
|
||||
$form.expand.waypoints = [...$form.expand.waypoints];
|
||||
$form.expand!.waypoints = [...$form.expand!.waypoints];
|
||||
}
|
||||
|
||||
function beforeSummitLogModalOpen() {
|
||||
@@ -410,16 +413,16 @@
|
||||
function saveSummitLog(e: CustomEvent<SummitLog>) {
|
||||
const savedSummitLog = e.detail;
|
||||
|
||||
let editedSummitLogIndex = $form.expand.summit_logs.findIndex(
|
||||
let editedSummitLogIndex = $form.expand!.summit_logs.findIndex(
|
||||
(s) => s.id == savedSummitLog.id,
|
||||
);
|
||||
|
||||
if (editedSummitLogIndex >= 0) {
|
||||
$form.expand.summit_logs[editedSummitLogIndex] = savedSummitLog;
|
||||
$form.expand!.summit_logs[editedSummitLogIndex] = savedSummitLog;
|
||||
} else {
|
||||
savedSummitLog.id = cryptoRandomString({ length: 15 });
|
||||
$form.expand.summit_logs = [
|
||||
...$form.expand.summit_logs,
|
||||
$form.expand!.summit_logs = [
|
||||
...$form.expand!.summit_logs,
|
||||
savedSummitLog,
|
||||
];
|
||||
}
|
||||
@@ -434,9 +437,9 @@
|
||||
summitLog.set(currentSummitLog);
|
||||
openSummitLogModal();
|
||||
} else if (e.detail.value === "delete") {
|
||||
$form.expand.summit_logs.splice(index, 1);
|
||||
$form.expand!.summit_logs.splice(index, 1);
|
||||
$form.summit_logs.splice(index, 1);
|
||||
$form.expand.summit_logs = $form.expand.summit_logs;
|
||||
$form.expand!.summit_logs = $form.expand!.summit_logs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,11 +449,15 @@
|
||||
}
|
||||
try {
|
||||
if (list.trails?.includes($form.id!)) {
|
||||
await lists_remove_trail(list, $form);
|
||||
list = await lists_remove_trail(list, $form);
|
||||
} else {
|
||||
await lists_add_trail(list, $form);
|
||||
list = await lists_add_trail(list, $form);
|
||||
}
|
||||
await lists_index({ q: "", author: $currentUser?.id ?? "" }, 1, -1);
|
||||
const index = data.lists.items.findIndex((l) => l.id == list.id);
|
||||
if (index >= 0) {
|
||||
data.lists.items[index] = list;
|
||||
}
|
||||
// await lists_index({ q: "", author: $currentUser?.id ?? "" }, 1, -1);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
show_toast({
|
||||
@@ -612,7 +619,7 @@
|
||||
$form.duration = totals.duration;
|
||||
$form.elevation_gain = totals.elevationGain;
|
||||
$form.elevation_loss = totals.elevationLoss;
|
||||
$form.expand.gpx_data = route.toString();
|
||||
$form.expand!.gpx_data = route.toString();
|
||||
}
|
||||
|
||||
function updateTrailOnMap() {
|
||||
@@ -794,7 +801,7 @@
|
||||
{$_("waypoints", { values: { n: 2 } })}
|
||||
</h3>
|
||||
<ul>
|
||||
{#each $form.expand.waypoints ?? [] as waypoint, i}
|
||||
{#each $form.expand?.waypoints ?? [] as waypoint, i}
|
||||
<li on:mouseenter={() => openMarkerPopup(waypoint)}>
|
||||
<WaypointCard
|
||||
{waypoint}
|
||||
@@ -823,7 +830,7 @@
|
||||
<hr class="border-separator" />
|
||||
<h3 class="text-xl font-semibold">{$_("summit-book")}</h3>
|
||||
<ul>
|
||||
{#each $form.expand.summit_logs ?? [] as log, i}
|
||||
{#each $form.expand?.summit_logs ?? [] as log, i}
|
||||
<li>
|
||||
<SummitLogCard
|
||||
{log}
|
||||
|
||||
Reference in New Issue
Block a user