adds api gpx upload route
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { env } from '$env/dynamic/private'
|
||||
import { env as envPub } from '$env/dynamic/public'
|
||||
|
||||
import { pb } from '$lib/pocketbase'
|
||||
import { isRouteProtected } from '$lib/util/authorization_util'
|
||||
import { redirect, type Handle } from '@sveltejs/kit'
|
||||
@@ -11,11 +13,14 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
|
||||
const url = new URL(event.request.url);
|
||||
|
||||
|
||||
// validate the user existence and if the path is acceesible
|
||||
if (!pb.authStore.model && isRouteProtected(url.pathname)) {
|
||||
throw redirect(302, '/login?r=' + url.pathname);
|
||||
} else if (pb.authStore.model && url.pathname === "/login") {
|
||||
throw redirect(302, '/');
|
||||
} else if (envPub.PUBLIC_DISABLE_SIGNUP === "true" && url.pathname === "/register") {
|
||||
throw redirect(302, '/');
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -34,7 +39,7 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
} else {
|
||||
const r = await event.fetch(pb.buildUrl("/public/search/token"));
|
||||
const response = await r.json();
|
||||
meiliApiKey = response.token;
|
||||
meiliApiKey = response.token;
|
||||
}
|
||||
const ms = new MeiliSearch({ host: env.MEILI_URL, apiKey: meiliApiKey });
|
||||
|
||||
|
||||
@@ -193,10 +193,10 @@ export async function trails_show(id: string, loadGPX?: boolean, f: (url: Reques
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function trails_create(trail: Trail, photos: File[], gpx: File | null) {
|
||||
export async function trails_create(trail: Trail, photos: File[], gpx: File | Blob | null, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||
|
||||
if (!pb.authStore.model) {
|
||||
throw new Error("Unauthenticated");
|
||||
throw new ClientResponseError({ status: 401, response: { message: "Forbidden" } });
|
||||
}
|
||||
|
||||
for (const waypoint of trail.expand.waypoints) {
|
||||
@@ -213,7 +213,7 @@ export async function trails_create(trail: Trail, photos: File[], gpx: File | nu
|
||||
|
||||
trail.author = pb.authStore.model!.id
|
||||
|
||||
let r = await fetch('/api/v1/trail', {
|
||||
let r = await f('/api/v1/trail', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ ...trail, expand: undefined }),
|
||||
})
|
||||
@@ -233,7 +233,7 @@ export async function trails_create(trail: Trail, photos: File[], gpx: File | nu
|
||||
formData.append("photos", photo)
|
||||
}
|
||||
|
||||
r = await fetch(`/api/v1/trail/${model.id!}/file`, {
|
||||
r = await f(`/api/v1/trail/${model.id!}/file`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
const privateRoutes = [
|
||||
"/profile",
|
||||
"/lists",
|
||||
]
|
||||
|
||||
export function isRouteProtected(path: string) {
|
||||
return privateRoutes.some(allowedPath =>
|
||||
path === allowedPath || path.startsWith(allowedPath + '/')
|
||||
);
|
||||
}
|
||||
"/profile",
|
||||
"/lists",
|
||||
]
|
||||
|
||||
export function isRouteProtected(path: string) {
|
||||
return privateRoutes.some(allowedPath =>
|
||||
path === allowedPath || path.startsWith(allowedPath + '/')
|
||||
);
|
||||
}
|
||||
38
web/src/lib/util/gpx_util.ts
Normal file
38
web/src/lib/util/gpx_util.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Trail } from "$lib/models/trail";
|
||||
import { Waypoint } from "$lib/models/waypoint";
|
||||
import { JSDOM } from "jsdom"
|
||||
|
||||
export function gpx2trail(gpx: string) {
|
||||
const xml = new JSDOM(gpx).window.document
|
||||
const trail = new Trail("");
|
||||
|
||||
let name = xml.getElementsByTagName('name');
|
||||
if (name.length > 0) {
|
||||
trail.name = name[0].textContent ?? "";
|
||||
}
|
||||
let desc = xml.getElementsByTagName('desc');
|
||||
if (desc.length > 0) {
|
||||
trail.description = desc[0].textContent ?? "";
|
||||
}
|
||||
|
||||
const el = xml.getElementsByTagName('wpt');
|
||||
for (let i = 0; i < el.length; i++) {
|
||||
const wp = new Waypoint(parseFloat(el[i].getAttribute('lat')!), parseFloat(el[i].getAttribute('lon')!));
|
||||
|
||||
let nameEl = el[i].getElementsByTagName('name');
|
||||
wp.name = nameEl.length > 0 ? nameEl[0].textContent ?? "" : '';
|
||||
|
||||
let descEl = el[i].getElementsByTagName('desc');
|
||||
wp.description = descEl.length > 0 ? descEl[0].textContent ?? "" : '';
|
||||
|
||||
trail.expand.waypoints.push(wp);
|
||||
}
|
||||
|
||||
const startPoint = xml.getElementsByTagName("trk")[0].getElementsByTagName("trkseg")[0].getElementsByTagName("trkpt")[0];
|
||||
if(startPoint) {
|
||||
trail.lat = parseFloat(startPoint.getAttribute('lat')!)
|
||||
trail.lon = parseFloat(startPoint.getAttribute('lon')!)
|
||||
}
|
||||
|
||||
return trail
|
||||
}
|
||||
@@ -1,9 +1,16 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
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').authWithPassword(data.email ?? data.username!, data.password);
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -13,7 +13,6 @@ export async function GET(event: RequestEvent) {
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
|
||||
@@ -2,6 +2,16 @@ import { pb } from "$lib/pocketbase";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import type { List } from "postcss/lib/list";
|
||||
|
||||
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" })
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
|
||||
@@ -16,7 +26,7 @@ export async function POST(event: RequestEvent) {
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('lists').delete(event.params.id as string)
|
||||
return json(r);
|
||||
return json({ 'acknowledged': r });
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,15 @@ import type { SummitLog } from '$lib/models/summit_log';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r: SummitLog[] = await pb.collection('summit_logs').getFullList<SummitLog>()
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
const data = await event.request.json();
|
||||
|
||||
|
||||
@@ -3,6 +3,16 @@ import { pb } from "$lib/pocketbase";
|
||||
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)
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
try {
|
||||
@@ -16,7 +26,7 @@ export async function POST(event: RequestEvent) {
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('summit_logs').delete(event.params.id as string)
|
||||
return json(r);
|
||||
return json({ 'acknowledged': r });
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function POST(event: RequestEvent) {
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('trails').delete(event.params.id as string)
|
||||
return json(r);
|
||||
return json({ 'acknowledged': r });
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
|
||||
33
web/src/routes/api/v1/trail/upload/+server.ts
Normal file
33
web/src/routes/api/v1/trail/upload/+server.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
import { trails_create } from "$lib/stores/trail_store";
|
||||
import { gpx2trail } from "$lib/util/gpx_util";
|
||||
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
try {
|
||||
const data = await event.request.blob();
|
||||
const gpx = await data.text();
|
||||
if (!gpx.length) {
|
||||
throw new ClientResponseError({ status: 400, response: { message: "Empty file" } })
|
||||
}
|
||||
let trail: Trail;
|
||||
try {
|
||||
trail = gpx2trail(gpx);
|
||||
} catch (e: any) {
|
||||
throw new ClientResponseError({ status: 400, response: { message: "Invalid GPX file" } })
|
||||
}
|
||||
|
||||
try {
|
||||
await trails_create(trail, [], data, event.fetch);
|
||||
} catch(e: any) {
|
||||
console.log(e);
|
||||
|
||||
throw e
|
||||
}
|
||||
return json(trail);
|
||||
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import type { User } from '$lib/stores/user_store';
|
||||
import { error, 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)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -2,6 +2,16 @@ import { pb } from '$lib/pocketbase';
|
||||
import type { User } from '$lib/stores/user_store';
|
||||
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)
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
const data = await event.request.json()
|
||||
try {
|
||||
@@ -15,7 +25,7 @@ export async function POST(event: RequestEvent) {
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('users').delete(event.params.id as string)
|
||||
return json(r);
|
||||
return json({ 'acknowledged': r });
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,15 @@ import type { Waypoint } from '$lib/models/waypoint';
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r: Waypoint[] = await pb.collection('waypoints').getFullList<Waypoint>()
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
const data = await event.request.json();
|
||||
|
||||
|
||||
@@ -2,6 +2,15 @@ import type { Waypoint } from "$lib/models/waypoint";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
import { error, 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)
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(event: RequestEvent){
|
||||
const data = await event.request.json()
|
||||
@@ -16,7 +25,7 @@ export async function POST(event: RequestEvent){
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await pb.collection('waypoints').delete(event.params.id as string)
|
||||
return json(r);
|
||||
return json({ 'acknowledged': r });
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
|
||||
@@ -205,6 +205,8 @@
|
||||
[minLat, maxLon],
|
||||
];
|
||||
map.fitBounds(boundingBox);
|
||||
} else {
|
||||
map.setView([0, 0], 4);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -213,7 +215,7 @@
|
||||
<title>{$_("list", { values: { n: 2 } })} | wanderer</title>
|
||||
</svelte:head>
|
||||
<main
|
||||
class="grid grid-cols-1 md:grid-cols-[400px_1fr] gap-8 max-w-7xl mx-4 md:mx-auto"
|
||||
class="grid grid-cols-1 md:grid-cols-[400px_1fr] gap-4 lg:gap-8 max-w-7xl mx-4 md:mx-auto"
|
||||
style="min-height: calc(100vh - 124px)"
|
||||
>
|
||||
<ul
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
import { env } from "$env/dynamic/public";
|
||||
import Button from "$lib/components/base/button.svelte";
|
||||
import TextField from "$lib/components/base/text_field.svelte";
|
||||
import LogoTextTwoLineDark from "$lib/components/logo/logo_text_two_line_dark.svelte";
|
||||
import LogoTextTwoLineLight from "$lib/components/logo/logo_text_two_line_light.svelte";
|
||||
import { theme } from "$lib/stores/theme_store";
|
||||
import { show_toast } from "$lib/stores/toast_store";
|
||||
import { login, type User } from "$lib/stores/user_store";
|
||||
import { createForm } from "$lib/vendor/svelte-form-lib";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { object, string } from "yup";
|
||||
import { theme } from "$lib/stores/theme_store";
|
||||
import { _ } from "svelte-i18n";
|
||||
import { page } from "$app/stores";
|
||||
import { object, string } from "yup";
|
||||
|
||||
let loading: boolean = false;
|
||||
const { form, errors, handleChange, handleSubmit } = createForm<User>({
|
||||
@@ -37,13 +38,13 @@
|
||||
show_toast({
|
||||
icon: "close",
|
||||
type: "error",
|
||||
text: $_('wrong-username-or-password'),
|
||||
text: $_("wrong-username-or-password"),
|
||||
});
|
||||
} else {
|
||||
show_toast({
|
||||
icon: "close",
|
||||
type: "error",
|
||||
text: $_('error-during-login'),
|
||||
text: $_("error-during-login"),
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
@@ -90,11 +91,13 @@
|
||||
{loading}>Login</Button
|
||||
>
|
||||
</div>
|
||||
<span
|
||||
>{$_("no-account")}
|
||||
<a class="text-blue-500 underline" href="/register"
|
||||
>{$_("make-one")}</a
|
||||
></span
|
||||
>
|
||||
{#if env.PUBLIC_DISABLE_SIGNUP === "false"}
|
||||
<span
|
||||
>{$_("no-account")}
|
||||
<a class="text-blue-500 underline" href="/register"
|
||||
>{$_("make-one")}</a
|
||||
></span
|
||||
>
|
||||
{/if}
|
||||
</form>
|
||||
</main>
|
||||
|
||||
@@ -242,6 +242,8 @@
|
||||
})
|
||||
.on("loaded", function (e: LeafletEvent) {
|
||||
map.fitBounds(e.target.getBounds());
|
||||
$form.name = e.target._info.name;
|
||||
$form.description = e.target._info.desc;
|
||||
$form.distance = Math.round(e.target.get_distance());
|
||||
$form.elevation_gain = Math.round(
|
||||
e.target.get_elevation_gain(),
|
||||
|
||||
Reference in New Issue
Block a user