adds svelte API
This commit is contained in:
@@ -13,9 +13,12 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
// validate the user existence and if the path is acceesible
|
||||
if (!pb.authStore.model && isRouteProtected(url.pathname)) {
|
||||
throw redirect(302, '/login');
|
||||
} else if (pb.authStore.model && url.pathname === "/login") {
|
||||
throw redirect(302, '/');
|
||||
}
|
||||
|
||||
regenerateInstance();
|
||||
|
||||
try {
|
||||
// get an up-to-date auth store state by verifying and refreshing the loaded auth model (if any)
|
||||
if (pb.authStore.isValid) {
|
||||
@@ -30,10 +33,10 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
event.locals.user = pb.authStore.model
|
||||
|
||||
const lang = pb.authStore.model?.language ?? event.request.headers.get('accept-language')?.split(',')[0]
|
||||
|
||||
if (lang) {
|
||||
locale.set(lang)
|
||||
}
|
||||
|
||||
if (lang) {
|
||||
locale.set(lang)
|
||||
}
|
||||
|
||||
const response = await resolve(event)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { List } from "$lib/models/list";
|
||||
import { getFileURL } from "$lib/util/file_util";
|
||||
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
export let list: List;
|
||||
@@ -17,8 +18,8 @@
|
||||
>
|
||||
{#if list.avatar}
|
||||
<img
|
||||
class="w-16 md:w-24 aspect-square rounded-full"
|
||||
src={list.avatar}
|
||||
class="w-16 md:w-24 aspect-square rounded-full object-cover"
|
||||
src={getFileURL(list, list.avatar)}
|
||||
alt="avatar"
|
||||
/>
|
||||
{:else}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import Modal from "../base/modal.svelte";
|
||||
import TextField from "../base/text_field.svelte";
|
||||
import Textarea from "../base/textarea.svelte";
|
||||
import { getFileURL } from "$lib/util/file_util";
|
||||
export let openModal: (() => void) | undefined = undefined;
|
||||
export let closeModal: (() => void) | undefined = undefined;
|
||||
|
||||
@@ -21,12 +22,7 @@
|
||||
initialValues: $list,
|
||||
validationSchema: listSchema,
|
||||
onSubmit: async (submittedList) => {
|
||||
const htmlForm = document.getElementById(
|
||||
"list-form",
|
||||
) as HTMLFormElement;
|
||||
const formData = new FormData(htmlForm);
|
||||
|
||||
dispatch("save", { list: submittedList, formData: formData });
|
||||
dispatch("save", { list: submittedList, avatar: (document.getElementById("avatar") as HTMLInputElement).files![0] });
|
||||
(document.getElementById("avatar") as HTMLInputElement).value = "";
|
||||
closeModal!();
|
||||
},
|
||||
@@ -48,7 +44,7 @@
|
||||
}
|
||||
$: if (browser) {
|
||||
form.set(util.cloneDeep($list));
|
||||
previewURL = $list.avatar ?? "";
|
||||
previewURL = getFileURL($list, $list.avatar) ?? "";
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -78,7 +74,7 @@
|
||||
<div class="flex items-center gap-4">
|
||||
{#if previewURL.length > 0}
|
||||
<img
|
||||
class="w-32 aspect-square rounded-full"
|
||||
class="w-32 aspect-square rounded-full object-cover"
|
||||
alt="avatar"
|
||||
src={previewURL}
|
||||
/>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { trail } from "$lib/stores/trail_store";
|
||||
import { _ } from "svelte-i18n";
|
||||
import Modal from "../base/modal.svelte";
|
||||
import { getFileURL } from "$lib/util/file_util";
|
||||
|
||||
export let openModal: (() => void) | undefined = undefined;
|
||||
export let closeModal: (() => void) | undefined = undefined;
|
||||
@@ -42,7 +43,7 @@
|
||||
{#if list.avatar}
|
||||
<img
|
||||
class="w-12 aspect-square rounded-full"
|
||||
src={list.avatar}
|
||||
src={getFileURL(list, list.avatar)}
|
||||
alt="avatar"
|
||||
/>
|
||||
{:else}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
></i>
|
||||
{/if}
|
||||
<div
|
||||
class="flex opacity-0 group-hover:opacity-100 absolute top-0 w-full h-full bg-white bg-opacity-75 items-center justify-center gap-6 transition-all"
|
||||
class="flex opacity-0 group-hover:opacity-100 absolute top-0 w-full h-full bg-white/75 rounded-xl items-center justify-center gap-6 transition-all"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<script lang="ts">
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
import { getFileURL } from "$lib/util/file_util";
|
||||
import { formatDistance, formatElevation, formatTimeHHMM } from "$lib/util/format_util";
|
||||
import {
|
||||
formatDistance,
|
||||
formatElevation,
|
||||
formatTimeHHMM,
|
||||
} from "$lib/util/format_util";
|
||||
|
||||
export let trail: Trail;
|
||||
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -14,7 +17,7 @@
|
||||
role="listitem"
|
||||
>
|
||||
<div class="w-full min-h-40 max-h-48 overflow-hidden rounded-t-2xl">
|
||||
<img src={getFileURL(trail, trail.thumbnail)} alt="" />
|
||||
<img src={getFileURL(trail, trail.photos[trail.thumbnail])} alt="" />
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<div class="shrink-0">
|
||||
<img
|
||||
class="h-28 w-28 object-cover rounded-xl"
|
||||
src={getFileURL(trail, trail.thumbnail)}
|
||||
src={getFileURL(trail, trail.photos[trail.thumbnail])}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -13,10 +13,13 @@ class Trail {
|
||||
duration?: number;
|
||||
lat?: number;
|
||||
lon?: number;
|
||||
thumbnail?: string;
|
||||
thumbnail: number;
|
||||
photos: string[];
|
||||
gpx?: string;
|
||||
created?: string;
|
||||
category?: string;
|
||||
waypoints: string[];
|
||||
summit_logs: string[];
|
||||
expand: {
|
||||
category?: Category;
|
||||
waypoints: Waypoint[]
|
||||
@@ -27,8 +30,6 @@ class Trail {
|
||||
description?: string;
|
||||
author?: string;
|
||||
|
||||
_photoFiles: File[]
|
||||
|
||||
constructor(name: string,
|
||||
params?: {
|
||||
id?: string,
|
||||
@@ -39,7 +40,7 @@ class Trail {
|
||||
duration?: number,
|
||||
lat?:number,
|
||||
lon?: number,
|
||||
thumbnail?: string,
|
||||
thumbnail?: number,
|
||||
photos?: string[],
|
||||
gpx?: string,
|
||||
category?: Category,
|
||||
@@ -60,8 +61,10 @@ class Trail {
|
||||
this.duration = params?.duration;
|
||||
this.lat = params?.lat;
|
||||
this.lon = params?.lon;
|
||||
this.thumbnail = params?.thumbnail;
|
||||
this.thumbnail = params?.thumbnail ?? 0;
|
||||
this.photos = params?.photos ?? [];
|
||||
this.waypoints = [];
|
||||
this.summit_logs = [];
|
||||
this.gpx = params?.gpx;
|
||||
this.expand = {
|
||||
category: params?.category,
|
||||
@@ -71,7 +74,6 @@ class Trail {
|
||||
this.tags = params?.tags ?? []
|
||||
this.description = params?.description ?? "";
|
||||
this.created = params?.created;
|
||||
this._photoFiles = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,66 +2,130 @@ import { List, type ListFilter } from "$lib/models/list";
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { getFileURL } from "$lib/util/file_util";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
|
||||
export const lists: Writable<List[]> = writable([])
|
||||
export const list: Writable<List> = writable(new List("", []))
|
||||
|
||||
|
||||
export async function lists_index(filter?: ListFilter) {
|
||||
const dbResponse: List[] = (await pb.collection('lists').getFullList<List>({
|
||||
expand: "trails",
|
||||
sort: `${filter?.sortOrder ?? "+"}${filter?.sort ?? "name"}`
|
||||
}))
|
||||
export async function lists_index(filter?: ListFilter, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||
const r = await f('/api/v1/list?' + new URLSearchParams({
|
||||
sort: `${filter?.sortOrder ?? "-"}${filter?.sort ?? "name"}`,
|
||||
}), {
|
||||
method: 'GET',
|
||||
})
|
||||
|
||||
for (const list of dbResponse) {
|
||||
list.avatar = getFileURL(list, list.avatar);
|
||||
if (!r.ok) {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
lists.set(dbResponse);
|
||||
const fetchedLists: List[] = await r.json();
|
||||
|
||||
if (dbResponse.length > 0) {
|
||||
list.set(dbResponse[0])
|
||||
lists.set(fetchedLists);
|
||||
|
||||
if (fetchedLists.length > 0) {
|
||||
list.set(fetchedLists[0])
|
||||
}
|
||||
|
||||
return dbResponse;
|
||||
return fetchedLists;
|
||||
|
||||
}
|
||||
|
||||
export async function lists_create(formData: { [key: string]: any; } | FormData) {
|
||||
export async function lists_create(list: List, avatar?: File) {
|
||||
if (!pb.authStore.model) {
|
||||
throw new Error("Unauthenticated");
|
||||
}
|
||||
|
||||
formData.append("author", pb.authStore.model!.id);
|
||||
list.author = pb.authStore.model!.id;
|
||||
|
||||
let model = await pb
|
||||
.collection("lists")
|
||||
.create<List>(formData);
|
||||
}
|
||||
let r = await fetch('/api/v1/list', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(list),
|
||||
})
|
||||
|
||||
export async function lists_update(list: List, formData: { [key: string]: any; } | FormData) {
|
||||
if ((formData.get("avatar") as File).size == 0) {
|
||||
formData.delete("avatar");
|
||||
if (!r.ok) {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
const model: List = await r.json();
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
if (avatar) {
|
||||
formData.append("avatar", avatar);
|
||||
}
|
||||
|
||||
r = await fetch(`/api/v1/list/${model.id!}/file`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
|
||||
if (!r.ok) {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
let model = await pb
|
||||
.collection("lists")
|
||||
.update<List>(list.id!, formData);
|
||||
}
|
||||
|
||||
export async function lists_delete(list: List) {
|
||||
let success = await pb
|
||||
.collection("lists")
|
||||
.delete(list.id!);
|
||||
export async function lists_update(list: List, avatar?: File) {
|
||||
let r = await fetch('/api/v1/list/' + list.id, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(list),
|
||||
})
|
||||
|
||||
if (!r.ok) {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
const model: List = await r.json();
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
if (avatar) {
|
||||
formData.append("avatar", avatar);
|
||||
}
|
||||
|
||||
r = await fetch(`/api/v1/list/${model.id!}/file`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
|
||||
if (!r.ok) {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
}
|
||||
|
||||
export async function lists_add_trail(list: List, trail: Trail) {
|
||||
let model = await pb
|
||||
.collection("lists")
|
||||
.update(list.id!, { "trails+": trail.id });
|
||||
const r = await fetch('/api/v1/list/' + list.id, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
"trails+": trail.id
|
||||
}),
|
||||
})
|
||||
|
||||
if (!r.ok) {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
}
|
||||
|
||||
export async function lists_remove_trail(list: List, trail: Trail) {
|
||||
let model = await pb
|
||||
.collection("lists")
|
||||
.update(list.id!, { "trails-": trail.id });
|
||||
const r = await fetch('/api/v1/list/' + list.id, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
"trails-": trail.id
|
||||
}),
|
||||
})
|
||||
|
||||
if (!r.ok) {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
}
|
||||
|
||||
export async function lists_delete(list: List) {
|
||||
const r = await fetch('/api/v1/list/' + list.id, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
if (!r.ok) {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,43 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { SummitLog } from "$lib/models/summit_log";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
|
||||
export const summitLog: Writable<SummitLog> = writable(new SummitLog(new Date().toISOString()));
|
||||
|
||||
export async function summit_logs_create(bodyParams?: { [key: string]: any; } | FormData) {
|
||||
const model = await pb
|
||||
.collection("summit_logs")
|
||||
.create<SummitLog>(bodyParams);
|
||||
export async function summit_logs_create(summitLog: SummitLog) {
|
||||
const r = await fetch('/api/v1/summit-log', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(summitLog),
|
||||
})
|
||||
|
||||
return model;
|
||||
if (r.ok) {
|
||||
return await r.json();
|
||||
} else {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
}
|
||||
|
||||
export async function summit_logs_update(updatedSummitLog: SummitLog) {
|
||||
const model = await pb
|
||||
.collection("summit_logs")
|
||||
.update(updatedSummitLog.id!, updatedSummitLog);
|
||||
export async function summit_logs_update(summitLog: SummitLog) {
|
||||
const r = await fetch('/api/v1/summit-log/' + summitLog.id, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(summitLog),
|
||||
})
|
||||
|
||||
return model;
|
||||
if (r.ok) {
|
||||
return await r.json();
|
||||
} else {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
}
|
||||
|
||||
export async function summit_logs_delete(id: string) {
|
||||
const success = await pb
|
||||
.collection("summit_logs")
|
||||
.delete(id);
|
||||
export async function summit_logs_delete(summitLog: SummitLog) {
|
||||
const r = await fetch('/api/v1/summit-log/' + summitLog.id, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
return success;
|
||||
if (r.ok) {
|
||||
return await r.json();
|
||||
} else {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
}
|
||||
@@ -9,25 +9,31 @@ import { summit_logs_create, summit_logs_delete, summit_logs_update } from "./su
|
||||
import { waypoints_create, waypoints_delete, waypoints_update } from "./waypoint_store";
|
||||
import { ms } from "$lib/meilisearch";
|
||||
import type { LatLng } from "leaflet";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
export const trails: Writable<Trail[]> = writable([])
|
||||
export const trail: Writable<Trail> = writable(new Trail(""));
|
||||
|
||||
export const editTrail: Writable<Trail> = writable(new Trail(""));
|
||||
|
||||
export async function trails_index(data: { perPage: number, random?: boolean } = { perPage: 5, random: false }) {
|
||||
const response: Trail[] = (await pb.collection('trails').getList<Trail>(1, data.perPage, { expand: "category,waypoints,summit_logs", sort: data.random ? "@random" : "" })).items
|
||||
export async function trails_index(data: { perPage: number, random?: boolean, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> } = { perPage: 5, random: false, f: fetch }) {
|
||||
const r = await data.f('/api/v1/trail?' + new URLSearchParams({
|
||||
expand: "category,waypoints,summit_logs",
|
||||
sort: data.random ? "@random" : ""
|
||||
}), {
|
||||
method: 'GET',
|
||||
})
|
||||
const response = await r.json()
|
||||
|
||||
for (const trail of response) {
|
||||
setFileURLs(trail);
|
||||
if (r.ok) {
|
||||
trails.set(response.items);
|
||||
return response.items;
|
||||
} else {
|
||||
throw new ClientResponseError(response)
|
||||
}
|
||||
|
||||
trails.set(response);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function trails_search_filter(filter: TrailFilter) {
|
||||
export async function trails_search_filter(filter: TrailFilter, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||
let filterText: string = `distance >= ${filter.distanceMin} AND distance <= ${filter.distanceMax} AND elevation_gain >= ${filter.elevationGainMin} AND elevation_gain <= ${filter.elevationGainMax}`;
|
||||
|
||||
if (filter.category.length > 0) {
|
||||
@@ -49,18 +55,21 @@ export async function trails_search_filter(filter: TrailFilter) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const dbResponse: Trail[] = (await pb.collection('trails').getList<Trail>(1, 5, {
|
||||
filter: trailIds.map((id) => `id="${id}"`).join('||'), expand: "category,waypoints,summit_logs",
|
||||
const r = await f('/api/v1/trail?' + new URLSearchParams({
|
||||
expand: "category,waypoints,summit_logs",
|
||||
filter: trailIds.map((id) => `id="${id}"`).join('||'),
|
||||
sort: `${filter.sortOrder}${filter.sort}`
|
||||
})).items
|
||||
}), {
|
||||
method: 'GET',
|
||||
})
|
||||
const response = await r.json()
|
||||
|
||||
for (const trail of dbResponse) {
|
||||
setFileURLs(trail);
|
||||
if (r.ok) {
|
||||
trails.set(response.items);
|
||||
return response.items;
|
||||
} else {
|
||||
throw new ClientResponseError(response)
|
||||
}
|
||||
|
||||
trails.set(dbResponse);
|
||||
|
||||
return dbResponse;
|
||||
}
|
||||
|
||||
export async function trails_search_bounding_box(northEast: LatLng, southWest: LatLng, filter?: TrailFilter) {
|
||||
@@ -78,13 +87,13 @@ export async function trails_search_bounding_box(northEast: LatLng, southWest: L
|
||||
}
|
||||
}
|
||||
|
||||
const response = await ms.index("trails").search("", {
|
||||
const indexResponse = await ms.index("trails").search("", {
|
||||
filter: [
|
||||
`_geoBoundingBox([${northEast.lat}, ${northEast.lng}], [${southWest.lat}, ${southWest.lng}])`,
|
||||
filterText
|
||||
],
|
||||
});
|
||||
const trailIds = response.hits.map((h) => h.id);
|
||||
const trailIds = indexResponse.hits.map((h) => h.id);
|
||||
|
||||
if (trailIds.length == 0) {
|
||||
const currentTrails: Trail[] = get(trails);
|
||||
@@ -92,96 +101,111 @@ export async function trails_search_bounding_box(northEast: LatLng, southWest: L
|
||||
return compareObjectArrays<Trail>(currentTrails, []);
|
||||
}
|
||||
|
||||
const dbResponse: Trail[] = (
|
||||
await pb.collection("trails").getList<Trail>(1, 5, {
|
||||
filter: trailIds.map((id) => `id="${id}"`).join("||"),
|
||||
expand: "category,waypoints,summit_logs",
|
||||
sort: `+name`,
|
||||
})
|
||||
).items;
|
||||
const r = await fetch('/api/v1/trail?' + new URLSearchParams({
|
||||
filter: trailIds.map((id) => `id="${id}"`).join("||"),
|
||||
expand: "category,waypoints,summit_logs",
|
||||
sort: `+name`,
|
||||
}), {
|
||||
method: 'GET',
|
||||
})
|
||||
const response = await r.json()
|
||||
|
||||
for (const trail of dbResponse) {
|
||||
setFileURLs(trail);
|
||||
const gpxData: string = await fetchGPX(trail);
|
||||
trail.expand.gpx_data = gpxData;
|
||||
if (r.ok) {
|
||||
|
||||
for (const trail of response.items) {
|
||||
const gpxData: string = await fetchGPX(trail);
|
||||
trail.expand.gpx_data = gpxData;
|
||||
}
|
||||
|
||||
const comparison = compareObjectArrays<Trail>(get(trails), response.items)
|
||||
|
||||
trails.set(response.items);
|
||||
|
||||
return comparison;
|
||||
} else {
|
||||
throw new ClientResponseError(response)
|
||||
}
|
||||
|
||||
const comparison = compareObjectArrays<Trail>(get(trails), dbResponse)
|
||||
|
||||
trails.set(dbResponse);
|
||||
|
||||
return comparison;
|
||||
}
|
||||
|
||||
export async function trails_show(id: string, loadGPX?: boolean) {
|
||||
const response: Trail = await pb.collection('trails').getOne<Trail>(id, { expand: "category,waypoints,summit_logs" })
|
||||
export async function trails_show(id: string, loadGPX?: boolean, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||
const r = await f(`/api/v1/trail/${id}?` + new URLSearchParams({
|
||||
expand: "category,waypoints,summit_logs",
|
||||
}), {
|
||||
method: 'GET',
|
||||
})
|
||||
const response = await r.json()
|
||||
|
||||
if (!r.ok) {
|
||||
throw new ClientResponseError(response)
|
||||
}
|
||||
|
||||
if (loadGPX) {
|
||||
const gpxData: string = await fetchGPX(response);
|
||||
response.expand.gpx_data = gpxData;
|
||||
}
|
||||
|
||||
setFileURLs(response);
|
||||
|
||||
response.expand.waypoints = response.expand.waypoints || [];
|
||||
response.expand.summit_logs = response.expand.summit_logs || [];
|
||||
|
||||
response._photoFiles = [];
|
||||
|
||||
trail.set(response);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function trails_create(trail: Trail, formData: { [key: string]: any; } | FormData) {
|
||||
export async function trails_create(trail: Trail, photos: File[], gpx: File | null) {
|
||||
|
||||
if (!pb.authStore.model) {
|
||||
throw new Error("Unauthenticated");
|
||||
}
|
||||
formData.set("category", trail.expand.category!.id);
|
||||
|
||||
for (const file of trail._photoFiles) {
|
||||
formData.append("photos", file);
|
||||
}
|
||||
|
||||
for (const waypoint of trail.expand.waypoints) {
|
||||
const model = await waypoints_create({
|
||||
...waypoint,
|
||||
marker: undefined,
|
||||
});
|
||||
formData.append("waypoints", model.id!);
|
||||
trail.waypoints.push(model.id!);
|
||||
}
|
||||
for (const summitLog of trail.expand.summit_logs) {
|
||||
const model = await summit_logs_create(summitLog);
|
||||
formData.append("summit_logs", model.id!);
|
||||
trail.summit_logs.push(model.id!);
|
||||
}
|
||||
|
||||
if (!formData.get("public")) {
|
||||
formData.set("public", 0);
|
||||
trail.author = pb.authStore.model!.id
|
||||
|
||||
let r = await fetch('/api/v1/trail', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ ...trail, expand: undefined }),
|
||||
})
|
||||
|
||||
if (!r.ok) {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
formData.append("author", pb.authStore.model!.id);
|
||||
let model: Trail = await r.json();
|
||||
|
||||
let model = await pb
|
||||
.collection("trails")
|
||||
.create<Trail>(formData);
|
||||
|
||||
const thumbnailIndex = trail.photos.findIndex(
|
||||
(p) => p == trail.thumbnail,
|
||||
);
|
||||
let thumbnail: string | undefined = "/imgs/thumbnail.jpg";
|
||||
if (thumbnailIndex >= 0) {
|
||||
thumbnail = model.photos.at(thumbnailIndex);
|
||||
const formData = new FormData()
|
||||
if (gpx) {
|
||||
formData.append("gpx", gpx);
|
||||
}
|
||||
|
||||
model = await pb
|
||||
.collection("trails")
|
||||
.update<Trail>(model.id!, { thumbnail: thumbnail }, { expand: "category" });
|
||||
for (const photo of photos) {
|
||||
formData.append("photos", photo)
|
||||
}
|
||||
|
||||
return model;
|
||||
r = await fetch(`/api/v1/trail/${model.id!}/file`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
|
||||
if (r.ok) {
|
||||
return await r.json();
|
||||
} else {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
}
|
||||
|
||||
export async function trails_update(oldTrail: Trail, newTrail: Trail, formData: { [key: string]: any; } | FormData) {
|
||||
export async function trails_update(oldTrail: Trail, newTrail: Trail, photos: File[], gpx: File | null) {
|
||||
|
||||
const waypointUpdates = compareObjectArrays<Waypoint>(oldTrail.expand.waypoints ?? [], newTrail.expand.waypoints ?? []);
|
||||
|
||||
@@ -190,7 +214,7 @@ export async function trails_update(oldTrail: Trail, newTrail: Trail, formData:
|
||||
...addedWaypoint,
|
||||
marker: undefined,
|
||||
});
|
||||
formData.append("waypoints", model.id!);
|
||||
newTrail.waypoints.push(model.id!);
|
||||
}
|
||||
|
||||
for (const updatedWaypoint of waypointUpdates.updated) {
|
||||
@@ -198,39 +222,45 @@ export async function trails_update(oldTrail: Trail, newTrail: Trail, formData:
|
||||
...updatedWaypoint,
|
||||
marker: undefined,
|
||||
});
|
||||
formData.append("waypoints", model.id!);
|
||||
}
|
||||
|
||||
for (const deletedWaypoint of waypointUpdates.deleted) {
|
||||
const success = await waypoints_delete(deletedWaypoint.id!);
|
||||
}
|
||||
|
||||
for (const unchangedWaypoint of waypointUpdates.unchanged) {
|
||||
formData.append("waypoints", unchangedWaypoint.id!);
|
||||
const success = await waypoints_delete(deletedWaypoint);
|
||||
}
|
||||
|
||||
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);
|
||||
formData.append("summit_logs", model.id!);
|
||||
newTrail.summit_logs.push(model.id!);
|
||||
}
|
||||
|
||||
for (const updatedSummitLog of summitLogUpdates.updated) {
|
||||
const model = await summit_logs_update(updatedSummitLog);
|
||||
formData.append("summit_logs", model.id!);
|
||||
}
|
||||
|
||||
for (const deletedSummitLog of summitLogUpdates.deleted) {
|
||||
const success = await summit_logs_delete(deletedSummitLog.id!);
|
||||
const success = await summit_logs_delete(deletedSummitLog);
|
||||
}
|
||||
|
||||
for (const unchangedSummitLog of summitLogUpdates.unchanged) {
|
||||
formData.append("summit_logs", unchangedSummitLog.id!);
|
||||
let r = await fetch('/api/v1/trail/' + newTrail.id, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ...newTrail, expand: undefined }),
|
||||
})
|
||||
|
||||
if (!r.ok) {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
for (const file of newTrail._photoFiles) {
|
||||
formData.append("photos", file);
|
||||
let model: Trail = await r.json();
|
||||
|
||||
const formData = new FormData()
|
||||
if (gpx) {
|
||||
formData.append("gpx", gpx);
|
||||
}
|
||||
|
||||
for (const photo of photos) {
|
||||
formData.append("photos", photo)
|
||||
}
|
||||
|
||||
const deletedPhotos = oldTrail.photos.filter(oldPhoto => !newTrail.photos.find(newPhoto => newPhoto === oldPhoto));
|
||||
@@ -239,30 +269,15 @@ export async function trails_update(oldTrail: Trail, newTrail: Trail, formData:
|
||||
formData.append("photos-", deletedPhoto.replace(/^.*[\\/]/, ''));
|
||||
}
|
||||
|
||||
if (formData.get("gpx").size == 0) {
|
||||
formData.delete("gpx");
|
||||
r = await fetch(`/api/v1/trail/${newTrail.id!}/file`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
|
||||
if (!r.ok) {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
if (!formData.get("public")) {
|
||||
formData.set("public", 0);
|
||||
}
|
||||
|
||||
const thumbnailIndex = newTrail.photos.findIndex(
|
||||
(p) => p == newTrail.thumbnail,
|
||||
);
|
||||
|
||||
let model = await pb
|
||||
.collection("trails")
|
||||
.update<Trail>(newTrail.id!, formData);
|
||||
|
||||
let thumbnail: string | undefined = oldTrail.thumbnail;
|
||||
if (thumbnailIndex >= 0) {
|
||||
thumbnail = model.photos.at(thumbnailIndex);
|
||||
}
|
||||
|
||||
model = await pb
|
||||
.collection("trails")
|
||||
.update<Trail>(model.id!, { thumbnail: thumbnail }, { expand: "category,waypoints,summit_logs" });
|
||||
|
||||
trail.set(model);
|
||||
|
||||
@@ -273,20 +288,24 @@ export async function trails_update(oldTrail: Trail, newTrail: Trail, formData:
|
||||
export async function trails_delete(trail: Trail) {
|
||||
if (trail.expand.waypoints) {
|
||||
for (const waypoint of trail.expand.waypoints) {
|
||||
waypoints_delete(waypoint.id!);
|
||||
waypoints_delete(waypoint);
|
||||
}
|
||||
}
|
||||
if (trail.expand.summit_logs) {
|
||||
for (const summit_log of trail.expand.summit_logs) {
|
||||
summit_logs_delete(summit_log.id!);
|
||||
summit_logs_delete(summit_log);
|
||||
}
|
||||
}
|
||||
|
||||
const success = await pb
|
||||
.collection("trails")
|
||||
.delete(trail.id!);
|
||||
const r = await fetch('/api/v1/trail/' + trail.id, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
return success;
|
||||
if (r.ok) {
|
||||
return await r.json();
|
||||
} else {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchGPX(trail: Trail) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { regenerateInstance } from "$lib/meilisearch";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
|
||||
export type User = {
|
||||
@@ -10,20 +11,38 @@ export type User = {
|
||||
avatar?: string;
|
||||
unit?: "metric" | "imperial";
|
||||
language?: "en" | "de";
|
||||
location?: {name: string, lat: number, lon: number}
|
||||
location?: { name: string, lat: number, lon: number }
|
||||
}
|
||||
|
||||
export const currentUser: Writable<User | null> = writable<User | null>()
|
||||
|
||||
export async function users_create(user: User) {
|
||||
const model = await pb.collection('users').create({ ...user, passwordConfirm: user.password });
|
||||
const r = await fetch('/api/v1/user', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ ...user, passwordConfirm: user.password })
|
||||
})
|
||||
|
||||
if (r.ok) {
|
||||
return await r.json();
|
||||
} else {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
export async function login(user: User) {
|
||||
const authData = await pb.collection('users').authWithPassword(user.email ?? user.username!, user.password);
|
||||
regenerateInstance()
|
||||
const r = await fetch('/api/v1/auth/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(user),
|
||||
})
|
||||
|
||||
if (r.ok) {
|
||||
pb.authStore.loadFromCookie(document.cookie)
|
||||
regenerateInstance()
|
||||
} else {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export async function logout() {
|
||||
@@ -32,15 +51,27 @@ export async function logout() {
|
||||
}
|
||||
|
||||
export async function users_update(id: string, user: User | { [K in keyof User]?: User[K] } | FormData) {
|
||||
let model = await pb
|
||||
.collection("users")
|
||||
.update<User>(id, user);
|
||||
const r = await fetch('/api/v1/user', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ id: id, user: user })
|
||||
})
|
||||
|
||||
currentUser.set(model);
|
||||
if (r.ok) {
|
||||
const model = await r.json();
|
||||
currentUser.set(model);
|
||||
} else {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
}
|
||||
|
||||
export async function users_delete(user: User) {
|
||||
let success = await pb
|
||||
.collection("users")
|
||||
.delete(user.id);
|
||||
const r = await fetch('/api/v1/user', {
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify({ id: user.id })
|
||||
})
|
||||
|
||||
if (!r.ok) {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +1,43 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { Waypoint } from "$lib/models/waypoint";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
|
||||
export const waypoint: Writable<Waypoint> = writable(new Waypoint(0, 0));
|
||||
|
||||
export async function waypoints_create(bodyParams?: { [key: string]: any; } | FormData) {
|
||||
export async function waypoints_create(waypoint: Waypoint) {
|
||||
const r = await fetch('/api/v1/waypoint', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(waypoint),
|
||||
})
|
||||
|
||||
const model = await pb
|
||||
.collection("waypoints")
|
||||
.create<Waypoint>(bodyParams);
|
||||
|
||||
return model;
|
||||
if (r.ok) {
|
||||
return await r.json();
|
||||
} else {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
}
|
||||
|
||||
export async function waypoints_update(updatedWaypoint: Waypoint) {
|
||||
const model = await pb
|
||||
.collection("waypoints")
|
||||
.update(updatedWaypoint.id!, updatedWaypoint);
|
||||
export async function waypoints_update(waypoint: Waypoint) {
|
||||
const r = await fetch('/api/v1/waypoint/' + waypoint.id, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(waypoint),
|
||||
})
|
||||
|
||||
return model;
|
||||
if (r.ok) {
|
||||
return await r.json();
|
||||
} else {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
}
|
||||
|
||||
export async function waypoints_delete(id: string) {
|
||||
const success = await pb
|
||||
.collection("waypoints")
|
||||
.delete(id);
|
||||
export async function waypoints_delete(waypoint: Waypoint) {
|
||||
const r = await fetch('/api/v1/waypoint/' + waypoint.id, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
return success;
|
||||
if (r.ok) {
|
||||
return await r.json();
|
||||
} else {
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { categories_index } from "$lib/stores/category_store";
|
||||
import { trails_index } from "$lib/stores/trail_store";
|
||||
import type { ServerLoad } from "@sveltejs/kit";
|
||||
|
||||
export const load: ServerLoad = async ({ params, locals }) => {
|
||||
await trails_index({perPage: 20, random: true})
|
||||
export const load: ServerLoad = async ({ params, locals, fetch }) => {
|
||||
await trails_index({perPage: 20, random: true, f: fetch})
|
||||
await categories_index()
|
||||
};
|
||||
13
web/src/routes/api/v1/auth/login/+server.ts
Normal file
13
web/src/routes/api/v1/auth/login/+server.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
try {
|
||||
const r = await pb.collection('users').authWithPassword(data.email ?? data.username!, data.password);
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
}
|
||||
|
||||
}
|
||||
28
web/src/routes/api/v1/list/+server.ts
Normal file
28
web/src/routes/api/v1/list/+server.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { List } from '$lib/models/list';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
const sort = event.url.searchParams.get('sort') ?? ""
|
||||
try {
|
||||
const r: List[] = await pb.collection('lists').getFullList<List>({
|
||||
expand: "trails",
|
||||
sort: sort,
|
||||
})
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
const data = await event.request.json();
|
||||
|
||||
try {
|
||||
const r = await pb.collection('lists').create<List>(data)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
23
web/src/routes/api/v1/list/[id]/+server.ts
Normal file
23
web/src/routes/api/v1/list/[id]/+server.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import type { List } from "postcss/lib/list";
|
||||
|
||||
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)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('lists').delete(event.params.id as string)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
13
web/src/routes/api/v1/list/[id]/file/+server.ts
Normal file
13
web/src/routes/api/v1/list/[id]/file/+server.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, 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,);
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
14
web/src/routes/api/v1/summit-log/+server.ts
Normal file
14
web/src/routes/api/v1/summit-log/+server.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { SummitLog } from '$lib/models/summit_log';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
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('summit_logs').create<SummitLog>(data)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
23
web/src/routes/api/v1/summit-log/[id]/+server.ts
Normal file
23
web/src/routes/api/v1/summit-log/[id]/+server.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { SummitLog } from "$lib/models/summit_log";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
|
||||
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);
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('summit_logs').delete(event.params.id as string)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
30
web/src/routes/api/v1/trail/+server.ts
Normal file
30
web/src/routes/api/v1/trail/+server.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { Trail } from '$lib/models/trail';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
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") ?? "5";
|
||||
const expand = event.url.searchParams.get("expand") ?? ""
|
||||
const sort = event.url.searchParams.get("sort") ?? ""
|
||||
const filter = event.url.searchParams.get("filter") ?? "";
|
||||
|
||||
try {
|
||||
const r = await pb.collection('trails')
|
||||
.getList<Trail>(parseInt(page), parseInt(perPage), { expand: expand ?? "", sort: sort ?? "", filter: filter ?? "" })
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
const data = await event.request.json();
|
||||
try {
|
||||
const r = await pb.collection('trails').create<Trail>(data)
|
||||
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
35
web/src/routes/api/v1/trail/[id]/+server.ts
Normal file
35
web/src/routes/api/v1/trail/[id]/+server.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
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 ?? "" })
|
||||
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("trails").update<Trail>(event.params.id as string, data, { expand: "category,waypoints,summit_logs" });
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('trails').delete(event.params.id as string)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
13
web/src/routes/api/v1/trail/[id]/file/+server.ts
Normal file
13
web/src/routes/api/v1/trail/[id]/file/+server.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
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,);
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
33
web/src/routes/api/v1/user/+server.ts
Normal file
33
web/src/routes/api/v1/user/+server.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import type { User } from '$lib/stores/user_store';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit'
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
try {
|
||||
const r = await pb.collection('lists').update<User>(data.id, data.user)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
try {
|
||||
const r = await pb.collection('users').create<User>(data)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
try {
|
||||
const r = await pb.collection('users').delete(data.id)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
14
web/src/routes/api/v1/waypoint/+server.ts
Normal file
14
web/src/routes/api/v1/waypoint/+server.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { Waypoint } from '$lib/models/waypoint';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
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('waypoints').create<Waypoint>(data)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
23
web/src/routes/api/v1/waypoint/[id]/+server.ts
Normal file
23
web/src/routes/api/v1/waypoint/[id]/+server.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { Waypoint } from "$lib/models/waypoint";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
|
||||
export async function POST(event: RequestEvent){
|
||||
const data = await event.request.json()
|
||||
try {
|
||||
const r = await await pb.collection("waypoints").update<Waypoint>(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('waypoints').delete(event.params.id as string)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
@@ -30,16 +30,16 @@
|
||||
}
|
||||
|
||||
async function saveList(
|
||||
e: CustomEvent<{ list: List; formData: FormData }>,
|
||||
e: CustomEvent<{ list: List; avatar?: File }>,
|
||||
) {
|
||||
const result = e.detail;
|
||||
if (result.list.id) {
|
||||
await lists_update(result.list, result.formData);
|
||||
await lists_index();
|
||||
await lists_update(result.list, result.avatar);
|
||||
} else {
|
||||
await lists_create(result.formData);
|
||||
await lists_index();
|
||||
await lists_create(result.list, result.avatar);
|
||||
}
|
||||
await lists_index();
|
||||
|
||||
}
|
||||
|
||||
async function handleDropdownClick(
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { TrailFilter } from "$lib/models/trail";
|
||||
import { lists_index } from "$lib/stores/list_store";
|
||||
import type { Load } from "@sveltejs/kit";
|
||||
|
||||
export const load: Load = async ({ params }) => {
|
||||
export const load: Load = async ({ params, fetch }) => {
|
||||
const filter: TrailFilter = {
|
||||
q: "",
|
||||
category: [],
|
||||
@@ -16,7 +16,7 @@ export const load: Load = async ({ params }) => {
|
||||
sort: "created",
|
||||
sortOrder: "+",
|
||||
};
|
||||
await lists_index();
|
||||
await lists_index(undefined, fetch);
|
||||
|
||||
return {filter};
|
||||
};
|
||||
@@ -205,7 +205,7 @@
|
||||
<li class="flex items-center gap-4 cursor-pointer text-black">
|
||||
<div class="shrink-0"><img class="h-14 w-14 object-cover rounded-xl" src="${getFileURL(
|
||||
trail,
|
||||
trail.thumbnail,
|
||||
trail.photos[trail.thumbnail],
|
||||
)}" alt="">
|
||||
</div>
|
||||
<div>
|
||||
@@ -263,7 +263,7 @@
|
||||
extraClasses="w-full"
|
||||
on:update={(e) => search(e.detail)}
|
||||
on:click={(e) => handleSearchClick(e.detail)}
|
||||
placeholder="{$_("search-for-trails-places")}..."
|
||||
placeholder="{$_('search-for-trails-places')}..."
|
||||
items={searchDropdownItems}
|
||||
></Search>
|
||||
<button
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
onMount(() => {
|
||||
lightboxDataSource = $trail.photos.map((p) => ({
|
||||
src: p,
|
||||
src: getFileURL($trail, p),
|
||||
}));
|
||||
lightbox = new PhotoSwipeLightbox({
|
||||
dataSource: lightboxDataSource,
|
||||
@@ -83,7 +83,7 @@
|
||||
<section class="relative h-80">
|
||||
<img
|
||||
class="w-full h-80 object-cover"
|
||||
src={getFileURL($trail, $trail.thumbnail)}
|
||||
src={getFileURL($trail, $trail.photos[$trail.thumbnail])}
|
||||
alt=""
|
||||
/>
|
||||
<div
|
||||
@@ -152,14 +152,14 @@
|
||||
</ul>
|
||||
{/if}
|
||||
{#if activeTab == 2}
|
||||
<div id="photo-gallery" class="space-y-4">
|
||||
<div id="photo-gallery" class="space-y-4 mx-4">
|
||||
{#each $trail.photos ?? [] as photo, i}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<img
|
||||
class="rounded-xl cursor-pointer hover:scale-105 transition-transform"
|
||||
on:click={() => openGallery(i)}
|
||||
src={photo}
|
||||
src={getFileURL($trail, photo)}
|
||||
alt=""
|
||||
/>
|
||||
{/each}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { trails, trails_show } from "$lib/stores/trail_store";
|
||||
import { lists_index } from "$lib/stores/list_store";
|
||||
import { trails_show } from "$lib/stores/trail_store";
|
||||
import { error, type ServerLoad } from "@sveltejs/kit";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
export const load: ServerLoad = async ({ params, locals }) => {
|
||||
export const load: ServerLoad = async ({ params, locals, fetch }) => {
|
||||
try {
|
||||
await trails_show(params.id!, true)
|
||||
await trails_show(params.id!, true, fetch)
|
||||
} catch (e) {
|
||||
if (e instanceof ClientResponseError && e.status == 404) {
|
||||
error(404, {
|
||||
@@ -13,4 +14,5 @@ export const load: ServerLoad = async ({ params, locals }) => {
|
||||
}
|
||||
|
||||
}
|
||||
await lists_index(undefined, fetch);
|
||||
};
|
||||
@@ -22,6 +22,7 @@
|
||||
trails_update,
|
||||
} from "$lib/stores/trail_store";
|
||||
import { waypoint } from "$lib/stores/waypoint_store";
|
||||
import { getFileURL } from "$lib/util/file_util";
|
||||
import {
|
||||
formatDistance,
|
||||
formatElevation,
|
||||
@@ -50,6 +51,11 @@
|
||||
|
||||
let loading = false;
|
||||
|
||||
const photoFiles: File[] = [];
|
||||
let photoPreviews: string[] = [];
|
||||
|
||||
let gpxFile: File | null = null;
|
||||
|
||||
onMount(async () => {
|
||||
L = (await import("leaflet")).default;
|
||||
await import("leaflet-gpx");
|
||||
@@ -86,14 +92,21 @@
|
||||
"trail-form",
|
||||
) as HTMLFormElement;
|
||||
const formData = new FormData(htmlForm);
|
||||
if (!formData.get("public")) {
|
||||
submittedTrail.public = false;
|
||||
}
|
||||
submittedTrail.photos = submittedTrail.photos.filter(
|
||||
(p) => !p.startsWith("data:image/svg+xml;base64"),
|
||||
);
|
||||
if (!submittedTrail.id) {
|
||||
const createdTrail = await trails_create(
|
||||
submittedTrail,
|
||||
formData,
|
||||
photoFiles,
|
||||
gpxFile,
|
||||
);
|
||||
$form.id = createdTrail.id;
|
||||
} else {
|
||||
await trails_update($trail, submittedTrail, formData);
|
||||
await trails_update($trail, submittedTrail, photoFiles, gpxFile);
|
||||
}
|
||||
|
||||
show_toast({
|
||||
@@ -199,8 +212,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
$form.gpx = selectedFile?.name;
|
||||
gpxFile = selectedFile;
|
||||
$form.expand.waypoints = [];
|
||||
$form.waypoints = [];
|
||||
|
||||
var reader = new FileReader();
|
||||
|
||||
@@ -235,6 +249,7 @@
|
||||
} else if (e.detail.value === "delete") {
|
||||
currentWaypoint.marker?.remove();
|
||||
$form.expand.waypoints.splice(index, 1);
|
||||
$form.waypoints.splice(index, 1);
|
||||
$form.expand.waypoints = $form.expand.waypoints;
|
||||
}
|
||||
}
|
||||
@@ -278,17 +293,14 @@
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
$form._photoFiles.push(file);
|
||||
photoFiles.push(file);
|
||||
|
||||
(function (file) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
if (e.target?.result) {
|
||||
if ($form.photos.length == 0) {
|
||||
$form.thumbnail = e.target.result as string;
|
||||
}
|
||||
$form.photos = [
|
||||
...$form.photos,
|
||||
photoPreviews = [
|
||||
...photoPreviews,
|
||||
e.target.result as string,
|
||||
];
|
||||
}
|
||||
@@ -299,20 +311,23 @@
|
||||
}
|
||||
|
||||
function makePhotoThumbnail(index: number) {
|
||||
$form.thumbnail = $form.photos[index];
|
||||
$form.thumbnail = index;
|
||||
}
|
||||
|
||||
function handlePhotoDelete(index: number) {
|
||||
const photoToDelete = $form.photos[index];
|
||||
let reassignThumbnail: boolean = false;
|
||||
if ($form.thumbnail == photoToDelete) {
|
||||
reassignThumbnail = true;
|
||||
if ($form.thumbnail == index) {
|
||||
$form.thumbnail = 0;
|
||||
console.log("huere");
|
||||
|
||||
}
|
||||
$form.photos.splice(index, 1);
|
||||
$form._photoFiles.splice(index, 1);
|
||||
$form.photos = $form.photos;
|
||||
if (reassignThumbnail) {
|
||||
$form.thumbnail = $form.photos[0];
|
||||
|
||||
if (index >= $form.photos.length) {
|
||||
const adjustedIndex = index - $form.photos.length;
|
||||
photoFiles.splice(adjustedIndex, 1);
|
||||
photoPreviews.splice(adjustedIndex, 1);
|
||||
} else {
|
||||
$form.photos.splice(index, 1);
|
||||
$form.photos = $form.photos;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,6 +365,7 @@
|
||||
openSummitLogModal();
|
||||
} else if (e.detail.value === "delete") {
|
||||
$form.expand.summit_logs.splice(index, 1);
|
||||
$form.summit_logs.splice(index, 1);
|
||||
$form.expand.summit_logs = $form.expand.summit_logs;
|
||||
}
|
||||
}
|
||||
@@ -383,7 +399,7 @@
|
||||
<h3 class="text-xl font-semibold">{$_("basic-info")}</h3>
|
||||
<div class="flex gap-4 justify-around">
|
||||
<div class="flex flex-col items-center">
|
||||
<span>{$_('distance')}</span>
|
||||
<span>{$_("distance")}</span>
|
||||
<span class="font-medium">{formatDistance($form.distance)}</span
|
||||
>
|
||||
<input type="hidden" name="distance" value={$form.distance} />
|
||||
@@ -430,7 +446,7 @@
|
||||
<Select
|
||||
name="category"
|
||||
label={$_("category")}
|
||||
bind:value={$form.expand.category.id}
|
||||
bind:value={$form.category}
|
||||
items={$categories.map((c) => ({ text: c.name, value: c.id }))}
|
||||
></Select>
|
||||
{/if}
|
||||
@@ -472,19 +488,19 @@
|
||||
style="display: none;"
|
||||
on:change={handlePhotoSelection}
|
||||
/>
|
||||
{#each $form.photos ?? [] as photo, i}
|
||||
{#each ($form.photos ?? []).concat(photoPreviews) as photo, i}
|
||||
<div class="shrink-0 grow-0 basis-auto m-2">
|
||||
<PhotoCard
|
||||
src={photo}
|
||||
src={i >= $form.photos.length ? photo : getFileURL($form, photo)}
|
||||
on:delete={() => handlePhotoDelete(i)}
|
||||
isThumbnail={$form.thumbnail === photo}
|
||||
isThumbnail={$form.thumbnail === i}
|
||||
on:thumbnail={() => makePhotoThumbnail(i)}
|
||||
></PhotoCard>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<hr class="border-separator" />
|
||||
<h3 class="text-xl font-semibold">{$_('summit-book')}</h3>
|
||||
<h3 class="text-xl font-semibold">{$_("summit-book")}</h3>
|
||||
<ul>
|
||||
{#each $form.expand.summit_logs ?? [] as log, i}
|
||||
<li>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { categories_index } from "$lib/stores/category_store";
|
||||
import { trails_show } from "$lib/stores/trail_store";
|
||||
import { error, type Load } from "@sveltejs/kit";
|
||||
|
||||
export const load: Load = async ({ params }) => {
|
||||
export const load: Load = async ({ params, fetch }) => {
|
||||
if (!params.id) {
|
||||
return error(400, "Bad Request")
|
||||
}
|
||||
@@ -11,9 +11,9 @@ export const load: Load = async ({ params }) => {
|
||||
|
||||
let trail: Trail;
|
||||
if (params.id === "new") {
|
||||
trail = new Trail("", {category: categories[0]});
|
||||
trail = new Trail("", { category: categories[0] });
|
||||
} else {
|
||||
trail = await trails_show(params.id, true);
|
||||
trail = await trails_show(params.id, true, fetch);
|
||||
}
|
||||
|
||||
return { trail: trail }
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
}
|
||||
|
||||
lightboxDataSource = $trail.photos.map((p) => ({
|
||||
src: p,
|
||||
src: getFileURL($trail, p),
|
||||
}));
|
||||
lightbox = new PhotoSwipeLightbox({
|
||||
dataSource: lightboxDataSource,
|
||||
@@ -134,7 +134,7 @@
|
||||
<section class="relative h-80">
|
||||
<img
|
||||
class="w-full h-80"
|
||||
src={getFileURL($trail, $trail.thumbnail)}
|
||||
src={getFileURL($trail, $trail.photos[$trail.thumbnail])}
|
||||
alt=""
|
||||
/>
|
||||
<div
|
||||
@@ -225,7 +225,7 @@
|
||||
<img
|
||||
class="rounded-xl cursor-pointer hover:scale-105 transition-transform"
|
||||
on:click={() => openGallery(i)}
|
||||
src={photo}
|
||||
src={getFileURL($trail, photo)}
|
||||
alt=""
|
||||
/>
|
||||
{/each}
|
||||
|
||||
@@ -3,9 +3,9 @@ import { trails_show } from "$lib/stores/trail_store";
|
||||
import { error, type Load } from "@sveltejs/kit";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
export const load: Load = async ({ params }) => {
|
||||
export const load: Load = async ({ params, fetch }) => {
|
||||
try {
|
||||
await trails_show(params.id!, true)
|
||||
await trails_show(params.id!, true, fetch)
|
||||
} catch (e) {
|
||||
if (e instanceof ClientResponseError && e.status == 404) {
|
||||
error(404, {
|
||||
@@ -13,5 +13,6 @@ export const load: Load = async ({ params }) => {
|
||||
});
|
||||
}
|
||||
|
||||
} await lists_index();
|
||||
}
|
||||
await lists_index(undefined, fetch);
|
||||
};
|
||||
@@ -3,7 +3,7 @@ import { categories_index } from "$lib/stores/category_store";
|
||||
import { trails_search_filter } from "$lib/stores/trail_store";
|
||||
import type { ServerLoad } from "@sveltejs/kit";
|
||||
|
||||
export const load: ServerLoad = async ({ params, locals, url }) => {
|
||||
export const load: ServerLoad = async ({ params, locals, url, fetch }) => {
|
||||
const filter: TrailFilter = {
|
||||
q: "",
|
||||
category: [],
|
||||
@@ -21,7 +21,7 @@ export const load: ServerLoad = async ({ params, locals, url }) => {
|
||||
if (paramCategory) {
|
||||
filter.category.push(paramCategory);
|
||||
}
|
||||
await trails_search_filter(filter);
|
||||
await trails_search_filter(filter, fetch);
|
||||
await categories_index()
|
||||
|
||||
return {filter};
|
||||
|
||||
Reference in New Issue
Block a user