race condition security patch

This commit is contained in:
Christian Beutel
2025-04-18 12:59:17 +02:00
parent 29b8a0352e
commit d8f7a862f6
53 changed files with 240 additions and 222 deletions

View File

@@ -1,21 +1,26 @@
<script lang="ts">
import { afterNavigate, goto } from "$app/navigation";
import { page } from "$app/state";
import LogoText from "$lib/components/logo/logo_text.svelte";
import { pb } from "$lib/pocketbase";
import { theme, toggleTheme } from "$lib/stores/theme_store";
import { currentUser, logout } from "$lib/stores/user_store";
import { getFileURL } from "$lib/util/file_util";
import type { AuthRecord } from "pocketbase";
import { _ } from "svelte-i18n";
import { backInOut, cubicOut } from "svelte/easing";
import { Tween } from "svelte/motion";
import Drawer from "./base/drawer.svelte";
import Dropdown from "./base/dropdown.svelte";
import LogoTextLight from "./logo/logo_text_light.svelte";
import NotificationDropdown from "./notification/notification_dropdown.svelte";
import { browser } from "$app/environment";
import { page } from "$app/state";
import { Tween } from "svelte/motion";
import UrlImportModal from "./settings/url_import_modal.svelte";
interface Props {
user: AuthRecord;
}
let { user }: Props = $props();
let navBarItems = [
{ text: "Home", value: "/" },
{ text: $_("trail", { values: { n: 2 } }), value: "/trails" },
@@ -107,8 +112,6 @@
urlImportModal.openModal();
}
}
let user = $derived(browser ? $currentUser : pb.authStore.record);
</script>
<Drawer bind:open={drawerOpen}>

View File

@@ -1,12 +1,11 @@
<script lang="ts">
import type { List } from "$lib/models/list";
import type { Trail } from "$lib/models/trail";
import type { User, UserAnonymous } from "$lib/models/user";
import { pb } from "$lib/pocketbase";
import { users_show } from "$lib/stores/user_store";
import type { UserAnonymous } from "$lib/models/user";
import { currentUser, users_show } from "$lib/stores/user_store";
import { getFileURL } from "$lib/util/file_util";
import { _ } from "svelte-i18n";
import { fly, slide } from "svelte/transition";
import { fly } from "svelte/transition";
interface Props {
subject: Trail | List;
@@ -16,12 +15,15 @@
let { subject, large = false, type }: Props = $props();
const shareData = type == "trail" ? (subject as Trail).expand?.trail_share_via_trail : (subject as List).expand?.list_share_via_list
const shareData =
type == "trail"
? (subject as Trail).expand?.trail_share_via_trail
: (subject as List).expand?.list_share_via_list;
let showInfo: boolean = $state(false);
let loading: boolean = $state(false);
let infoLoaded: boolean = $state(false);
let subjectIsOwned: boolean = subject.author == pb.authStore.record?.id;
let subjectIsOwned: boolean = subject.author == $currentUser?.id;
let author: UserAnonymous | undefined = $state();
async function fetchInfo() {
@@ -57,8 +59,9 @@
{#if showInfo}
<ul
class="menu absolute z-10 top-8 bg-menu-background border border-input-border rounded-xl shadow-md overflow-hidden p-3 space-y-3 text-content -translate-x-3/4"
in:fly={{y: -10, duration: 150}} out:fly={{y: -10, duration: 150}}
>
in:fly={{ y: -10, duration: 150 }}
out:fly={{ y: -10, duration: 150 }}
>
{#if loading}
<div class="spinner spinner-dark"></div>
{:else if infoLoaded && shareData}

View File

@@ -1,12 +1,10 @@
<script lang="ts">
import { createBubbler } from "svelte/legacy";
const bubble = createBubbler();
import emptyStateTrailDark from "$lib/assets/svgs/empty_states/empty_state_trail_dark.svg";
import emptyStateTrailLight from "$lib/assets/svgs/empty_states/empty_state_trail_light.svg";
import type { Trail } from "$lib/models/trail";
import { pb } from "$lib/pocketbase";
import { theme } from "$lib/stores/theme_store";
import { currentUser } from "$lib/stores/user_store";
import { getFileURL, isVideoURL } from "$lib/util/file_util";
import {
formatDistance,
@@ -14,7 +12,6 @@
formatTimeHHMM,
} from "$lib/util/format_util";
import { _ } from "svelte-i18n";
import ShareInfo from "../share_info.svelte";
import type { MouseEventHandler } from "svelte/elements";
import Chip from "../base/chip.svelte";
@@ -75,13 +72,13 @@
/>
{/if}
</div>
{#if (trail.public || trailIsShared) && pb.authStore.record}
{#if (trail.public || trailIsShared) && $currentUser}
<div
class="flex absolute top-4 right-4 {trail.public && trailIsShared
? 'w-14'
: 'w-8'} h-8 rounded-full items-center justify-center bg-background text-content"
>
{#if trail.public && pb.authStore.record}
{#if trail.public && $currentUser}
<span
class="tooltip"
class:mr-2={trail.public && trailIsShared}

View File

@@ -9,18 +9,17 @@
} from "$lib/stores/list_store";
import { show_toast } from "$lib/stores/toast_store.svelte";
import { trails_delete } from "$lib/stores/trail_store";
import { currentUser } from "$lib/stores/user_store";
import { getFileURL, saveAs } from "$lib/util/file_util";
import { trail2gpx } from "$lib/util/gpx_util";
import { gpx } from "$lib/vendor/toGeoJSON/toGeoJSON";
import JSZip from "jszip";
import { _ } from "svelte-i18n";
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
import ConfirmModal from "../confirm_modal.svelte";
import ListSelectModal from "../list/list_select_modal.svelte";
import TrailExportModal from "./trail_export_modal.svelte";
import JSZip from "jszip";
import { getFileURL, saveAs } from "$lib/util/file_util";
import TrailShareModal from "./trail_share_modal.svelte";
import { currentUser } from "$lib/stores/user_store";
import { pb } from "$lib/pocketbase";
interface Props {
trail: Trail;
@@ -62,10 +61,10 @@
]
: []),
{ text: $_("print"), value: "print", icon: "print" },
...(trail.author != pb.authStore.record?.id
...(trail.author != $currentUser?.id
? []
: [{ text: $_("add-to-list"), value: "list", icon: "bookmark" }]),
...(trail.author != pb.authStore.record?.id
...(trail.author != $currentUser?.id
? []
: [{ text: $_("share"), value: "share", icon: "share" }]),
...(allowEdit

View File

@@ -1,10 +1,13 @@
<script lang="ts">
import type { Category } from "$lib/models/category";
import type { TrailFilter } from "$lib/models/trail";
import { country_codes } from "$lib/util/country_code_util";
import { searchLocations } from "$lib/stores/search_store";
import { tags_index } from "$lib/stores/tag_store";
import { formatDistance, formatElevation } from "$lib/util/format_util";
import { getIconForLocation } from "$lib/util/icon_util";
import { _ } from "svelte-i18n";
import { slide } from "svelte/transition";
import Combobox, { type ComboboxItem } from "../base/combobox.svelte";
import Datepicker from "../base/datepicker.svelte";
import DoubleSlider from "../base/double_slider.svelte";
import MultiSelect from "../base/multi_select.svelte";
@@ -14,12 +17,7 @@
import type { SelectItem } from "../base/select.svelte";
import Slider from "../base/slider.svelte";
import UserSearch from "../user_search.svelte";
import { pb } from "$lib/pocketbase";
import { searchLocations } from "$lib/stores/search_store";
import { getIconForLocation } from "$lib/util/icon_util";
import { tags_index } from "$lib/stores/tag_store";
import Combobox, { type ComboboxItem } from "../base/combobox.svelte";
import { T } from "@threlte/core";
import { currentUser } from "$lib/stores/user_store";
interface Props {
categories: Category[];
@@ -199,7 +197,7 @@
></Combobox>
<hr class="my-4 border-separator" />
{#if pb.authStore.record}
{#if $currentUser}
<UserSearch
onclick={(item) => setAuthorFilter(item)}
onclear={() => {

View File

@@ -23,7 +23,6 @@
import { browser } from "$app/environment";
import emptyStateTrailDark from "$lib/assets/svgs/empty_states/empty_state_trail_dark.svg";
import emptyStateTrailLight from "$lib/assets/svgs/empty_states/empty_state_trail_light.svg";
import { pb } from "$lib/pocketbase";
import { theme } from "$lib/stores/theme_store";
import { show_toast } from "$lib/stores/toast_store.svelte";
import * as M from "maplibre-gl";
@@ -31,6 +30,7 @@
import { onMount } from "svelte";
import { _ } from "svelte-i18n";
import Button from "../base/button.svelte";
import Chip from "../base/chip.svelte";
import SkeletonNotificationCard from "../base/skeleton_notification_card.svelte";
import Textarea from "../base/textarea.svelte";
import CommentCard from "../comment/comment_card.svelte";
@@ -42,7 +42,6 @@
import SummitLogTable from "../summit_log/summit_log_table.svelte";
import MapWithElevationMaplibre from "./map_with_elevation_maplibre.svelte";
import TrailTimeline from "./trail_timeline.svelte";
import Chip from "../base/chip.svelte";
interface Props {
initTrail: Trail;
@@ -63,7 +62,7 @@
const tabs = [
$_("summit-book"),
$_("photos"),
...(pb.authStore.record ? [$_("comment", { values: { n: 2 } })] : []),
...($currentUser ? [$_("comment", { values: { n: 2 } })] : []),
];
const trailIsShared =
@@ -234,13 +233,13 @@
{/each}
</div>
{/if}
{#if (trail.public || trailIsShared) && pb.authStore.record}
{#if (trail.public || trailIsShared) && $currentUser}
<div
class="flex {trail.public && trailIsShared
? 'w-16'
: 'w-8'} h-8 rounded-full items-center"
>
{#if trail.public && pb.authStore.record}
{#if trail.public && $currentUser}
<span
class:tooltip={mode != "map"}
class:mr-3={trail.public && trailIsShared}

View File

@@ -2,8 +2,8 @@
import emptyStateTrailDark from "$lib/assets/svgs/empty_states/empty_state_trail_dark.svg";
import emptyStateTrailLight from "$lib/assets/svgs/empty_states/empty_state_trail_light.svg";
import type { Trail } from "$lib/models/trail";
import { pb } from "$lib/pocketbase";
import { theme } from "$lib/stores/theme_store";
import { currentUser } from "$lib/stores/user_store";
import { getFileURL, isVideoURL } from "$lib/util/file_util";
import {
formatDistance,
@@ -12,7 +12,6 @@
} from "$lib/util/format_util";
import { _ } from "svelte-i18n";
import ShareInfo from "../share_info.svelte";
import Chip from "../base/chip.svelte";
interface Props {
trail: Trail;
@@ -55,7 +54,7 @@
<h4 class="font-semibold text-lg">
{trail.name}
</h4>
{#if trail.public && pb.authStore.record}
{#if trail.public && $currentUser}
<span class="tooltip ml-3" data-title={$_("public")}>
<i class="fa fa-globe"></i>
</span>

View File

@@ -1,8 +1,6 @@
import { browser } from '$app/environment'
import { pb } from '$lib/pocketbase'
import { currentUser } from '$lib/stores/user_store'
import { init, register } from 'svelte-i18n'
import { get } from 'svelte/store'
import { browser } from '$app/environment';
import { getPb } from '$lib/pocketbase';
import { init, register } from 'svelte-i18n';
const defaultLocale = 'en'
@@ -18,5 +16,5 @@ register('zh', () => import('./locales/zh.json'))
init({
fallbackLocale: defaultLocale,
initialLocale: browser ? pb.authStore.record?.language ?? window.navigator.language : defaultLocale,
initialLocale: browser ? getPb().authStore.record?.language ?? window.navigator.language : defaultLocale,
})

View File

@@ -1,8 +1,15 @@
import { browser } from '$app/environment';
import { env } from '$env/dynamic/public'
import PocketBase from 'pocketbase'
export function createInstance() {
return new PocketBase(env.PUBLIC_POCKETBASE_URL)
}
let pb: PocketBase;
export const pb = createInstance()
export function getPb(): PocketBase {
if(!browser) {
throw Error("Only available client side!")
}
if (!pb) {
pb = new PocketBase(env.PUBLIC_POCKETBASE_URL);
}
return pb;
}

View File

@@ -1,9 +1,9 @@
import { Comment } from "$lib/models/comment";
import type { Trail } from "$lib/models/trail";
import { pb } from "$lib/pocketbase";
import { APIError } from "$lib/util/api_util";
import { type ListResult } from "pocketbase";
import { writable, type Writable } from "svelte/store";
import { get, writable, type Writable } from "svelte/store";
import { currentUser } from "./user_store";
export const comments: Writable<Comment[]> = writable([])
@@ -28,11 +28,12 @@ export async function comments_index(trail: Trail) {
}
export async function comments_create(comment: Comment) {
if (!pb.authStore.record) {
throw new Error("Unauthenticated");
const user = get(currentUser)
if (!user) {
throw Error("Unauthenticated")
}
comment.author = pb.authStore.record!.id;
comment.author = user.authStore.record!.id;
let r = await fetch('/api/v1/comment', {
method: 'PUT',

View File

@@ -1,8 +1,8 @@
import { Integration } from "$lib/models/integration";
import { pb } from "$lib/pocketbase";
import { APIError } from "$lib/util/api_util";
import { type ListResult } from "pocketbase";
import { writable, type Writable } from "svelte/store";
import { get, writable, type Writable } from "svelte/store";
import { currentUser } from "./user_store";
export const integrations: Writable<Integration[]> = writable([])
@@ -25,11 +25,12 @@ export async function integrations_index(f: (url: RequestInfo | URL, config?: Re
}
export async function integrations_create(integration: Integration) {
if (!pb.authStore.record) {
throw new Error("Unauthenticated");
const user = get(currentUser)
if (!user) {
throw Error("Unauthenticated")
}
integration.user = pb.authStore.record!.id;
integration.user = user.id;
let r = await fetch('/api/v1/integration', {
method: 'PUT',

View File

@@ -1,20 +1,22 @@
import { List, type ListFilter, ExpandType, ExpandTypeToString } from "$lib/models/list";
import { ExpandType, ExpandTypeToString, List, type ListFilter } from "$lib/models/list";
import type { Trail } from "$lib/models/trail";
import { pb } from "$lib/pocketbase";
import { type ListResult } from "pocketbase";
import { writable, type Writable } from "svelte/store";
import { fetchGPX } from "./trail_store";
import { APIError } from "$lib/util/api_util";
import type { Hits } from "meilisearch";
import { type AuthRecord, type ListResult } from "pocketbase";
import { get, writable, type Writable } from "svelte/store";
import { fetchGPX } from "./trail_store";
import { currentUser } from "./user_store";
let lists: List[] = []
export const list: Writable<List | null> = writable(null)
export const listTrail: Writable<Trail | null> = writable(null);
export async function lists_index(filter?: ListFilter, page: number = 1, perPage: number = 5,
export async function lists_index(filter?: ListFilter, page: number = 1, perPage: number = 5,
f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch,
e: ExpandType = ExpandType.All) {
const filterText = filter ? buildFilterText(filter) : ""
const user = get(currentUser)
const filterText = filter ? buildFilterText(user, filter) : ""
const r = await f('/api/v1/list?' + new URLSearchParams({
sort: `${filter?.sortOrder ?? "-"}${filter?.sort ?? "name"}`,
@@ -42,8 +44,9 @@ export async function lists_index(filter?: ListFilter, page: number = 1, perPage
export async function lists_search_filter(filter: ListFilter, page: number = 1, perPage: number = 5, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch): Promise<ListResult<List>> {
const user = get(currentUser)
const filterText = buildSearchFilterText(filter)
const filterText = buildSearchFilterText(user, filter)
let r = await f("/api/v1/search/lists", {
method: "POST",
@@ -96,7 +99,7 @@ export async function lists_search_filter(filter: ListFilter, page: number = 1,
export async function lists_show(id: string, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch
, e: ExpandType = ExpandType.All) {
const r = await f(`/api/v1/list/${id}?` + new URLSearchParams({
expand: ExpandTypeToString(e)
}), {
@@ -125,11 +128,11 @@ export async function lists_show(id: string, f: (url: RequestInfo | URL, config?
}
export async function lists_create(list: List, avatar?: File) {
if (!pb.authStore.record) {
throw new Error("Unauthenticated");
const user = get(currentUser)
if (!user) {
throw Error("Unauthenticated")
}
list.author = pb.authStore.record!.id;
list.author = user.id;
let r = await fetch('/api/v1/list', {
method: 'PUT',
@@ -240,24 +243,24 @@ export async function lists_delete(list: List) {
}
function buildFilterText(filter: ListFilter): string {
function buildFilterText(user: AuthRecord | undefined, filter: ListFilter): string {
let filterText = `(name~"${filter.q}"||description~"${filter.q}")`
if (filter.author?.length) {
filterText += `&&author="${filter.author}"`
}
if (pb.authStore.record) {
if (user) {
if (filter.public === false && filter.shared === false) {
filterText += `&&author="${pb.authStore.record.id}"`
filterText += `&&author="${user.id}"`
} else if (filter.public === true && filter.shared === false) {
filterText += `&&(public=true||list_share_via_list.user!="${pb.authStore.record.id}"||author="${pb.authStore.record.id}")`
filterText += `&&(public=true||list_share_via_list.user!="${user.id}"||author="${user.id}")`
} else if (filter.public === false && filter.shared === true) {
filterText += `&&(public=false||list_share_via_list.user="${pb.authStore.record.id}"||author="${pb.authStore.record.id}")`
filterText += `&&(public=false||list_share_via_list.user="${user.id}"||author="${user.id}")`
}
}
return filterText
}
function buildSearchFilterText(filter: ListFilter): string {
function buildSearchFilterText(user: AuthRecord, filter: ListFilter): string {
let filterText: string = "";
if (filter.author?.length) {
@@ -272,17 +275,17 @@ function buildSearchFilterText(filter: ListFilter): string {
if (filter.public !== undefined) {
filterText += `(public = ${filter.public}`
if (!filter.author?.length || filter.author == pb.authStore.record?.id) {
filterText += ` OR author = ${pb.authStore.record?.id}`
if (!filter.author?.length || filter.author == user?.id) {
filterText += ` OR author = ${user?.id}`
}
filterText += ")"
}
if (filter.shared !== undefined) {
if (filter.shared === true) {
filterText += ` OR shares = ${pb.authStore.record?.id}`
filterText += ` OR shares = ${user?.id}`
} else {
filterText += ` AND NOT shares = ${pb.authStore.record?.id}`
filterText += ` AND NOT shares = ${user?.id}`
}
}

View File

@@ -1,9 +1,8 @@
import { SummitLog, type SummitLogFilter } from "$lib/models/summit_log";
import { pb } from "$lib/pocketbase";
import { type ListResult } from "pocketbase";
import { writable, type Writable } from "svelte/store";
import { fetchGPX } from "./trail_store";
import { APIError } from "$lib/util/api_util";
import { type ListResult } from "pocketbase";
import { get, writable, type Writable } from "svelte/store";
import { currentUser } from "./user_store";
export const summitLog: Writable<SummitLog> = writable(new SummitLog(new Date().toISOString().substring(0, 10)));
export const summitLogs: Writable<SummitLog[]> = writable([]);
@@ -47,7 +46,12 @@ export async function summit_logs_index(author: string, filter?: SummitLogFilter
}
export async function summit_logs_create(summitLog: SummitLog, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
summitLog.author = pb.authStore.record!.id
const user = get(currentUser)
if (!user) {
throw Error("Unauthenticated")
}
summitLog.author = user.id
let r = await f('/api/v1/summit-log', {
method: 'PUT',
@@ -102,7 +106,12 @@ export async function summit_logs_create(summitLog: SummitLog, f: (url: RequestI
}
export async function summit_logs_update(oldSummitLog: SummitLog, newSummitLog: SummitLog) {
newSummitLog.author = pb.authStore.record!.id
const user = get(currentUser)
if (!user) {
throw Error("Unauthenticated")
}
newSummitLog.author = user.id
let r = await fetch('/api/v1/summit-log/' + newSummitLog.id, {
method: 'POST',

View File

@@ -1,18 +1,18 @@
import type { SummitLog } from "$lib/models/summit_log";
import type { Tag } from "$lib/models/tag";
import { defaultTrailSearchAttributes, Trail, type TrailFilter, type TrailFilterValues, type TrailSearchResult } from "$lib/models/trail";
import type { Waypoint } from "$lib/models/waypoint";
import { pb } from "$lib/pocketbase";
import { APIError } from "$lib/util/api_util";
import { deepEqual } from "$lib/util/deep_util";
import { getFileURL } from "$lib/util/file_util";
import * as M from "maplibre-gl";
import type { Hits } from "meilisearch";
import { type ListResult, type RecordModel } from "pocketbase";
import { writable, type Writable } from "svelte/store";
import { type AuthRecord, type ListResult, type RecordModel } from "pocketbase";
import { get, writable, type Writable } from "svelte/store";
import { summit_logs_create, summit_logs_delete, summit_logs_update } from "./summit_log_store";
import { waypoints_create, waypoints_delete, waypoints_update } from "./waypoint_store";
import { APIError } from "$lib/util/api_util";
import { tags_create } from "./tag_store";
import type { Tag } from "$lib/models/tag";
import { currentUser } from "./user_store";
import { waypoints_create, waypoints_delete, waypoints_update } from "./waypoint_store";
let trails: Trail[] = []
export const trail: Writable<Trail> = writable(new Trail(""));
@@ -57,7 +57,9 @@ export async function trails_recommend(size: number, f: (url: RequestInfo | URL,
}
export async function trails_search_filter(filter: TrailFilter, page: number = 1, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
let filterText: string = buildFilterText(filter, true);
const user = get(currentUser)
let filterText: string = buildFilterText(user, filter, true);
let r = await f("/api/v1/search/trails", {
method: "POST",
@@ -91,11 +93,12 @@ export async function trails_search_filter(filter: TrailFilter, page: number = 1
}
export async function trails_search_bounding_box(northEast: M.LngLat, southWest: M.LngLat, filter?: TrailFilter, page: number = 1, includePolyline: boolean = true) {
const user = get(currentUser)
let filterText: string = "";
if (filter) {
filterText = buildFilterText(filter, false);
filterText = buildFilterText(user, filter, false);
}
let r = await fetch("/api/v1/search/trails", {
@@ -173,6 +176,10 @@ export async function trails_show(id: string, loadGPX?: boolean, f: (url: Reques
}
export async function trails_create(trail: Trail, photos: File[], gpx: File | Blob | null, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
const user = get(currentUser)
if (!user) {
throw Error("Unauthenticated")
}
for (const waypoint of trail.expand?.waypoints ?? []) {
const model = await waypoints_create({
@@ -194,7 +201,7 @@ export async function trails_create(trail: Trail, photos: File[], gpx: File | Bl
}
}
trail.author = pb.authStore.record!.id
trail.author = user.id
let r = await f(`/api/v1/trail?` + new URLSearchParams({
expand: "category,waypoints,summit_logs,trail_share_via_trail,tags",
@@ -500,7 +507,7 @@ async function searchResultToTrailList(hits: Hits<TrailSearchResult>): Promise<T
return trails
}
function buildFilterText(filter: TrailFilter, includeGeo: boolean): string {
function buildFilterText(user: AuthRecord, filter: TrailFilter, includeGeo: boolean): string {
let filterText: string = "";
filterText += `distance >= ${Math.floor(filter.distanceMin)} AND elevation_gain >= ${Math.floor(filter.elevationGainMin)} AND elevation_loss >= ${Math.floor(filter.elevationLossMin)}`
@@ -530,17 +537,17 @@ function buildFilterText(filter: TrailFilter, includeGeo: boolean): string {
if (filter.public !== undefined) {
filterText += `(public = ${filter.public}`
if (!filter.author?.length || filter.author == pb.authStore.record?.id) {
filterText += ` OR author = ${pb.authStore.record?.id}`
if (!filter.author?.length || filter.author == user?.id) {
filterText += ` OR author = ${user?.id}`
}
filterText += ")"
}
if (filter.shared !== undefined) {
if (filter.shared === true) {
filterText += ` OR shares = ${pb.authStore.record?.id}`
filterText += ` OR shares = ${user?.id}`
} else {
filterText += ` AND NOT shares = ${pb.authStore.record?.id}`
filterText += ` AND NOT shares = ${user?.id}`
}
}

View File

@@ -1,8 +1,8 @@
import type { User, UserAnonymous } from "$lib/models/user";
import { pb } from "$lib/pocketbase";
import { getPb } from "$lib/pocketbase";
import { APIError } from "$lib/util/api_util";
import { type AuthMethodsList } from "pocketbase";
import { writable, type Writable } from "svelte/store";
import { get, writable, type Writable } from "svelte/store";
export const currentUser: Writable<User | null> = writable<User | null>()
@@ -24,8 +24,10 @@ export async function users_create(user: User) {
}
export async function users_search(q: string, includeSelf: boolean = true) {
const user = get(currentUser)
let r = await fetch('/api/v1/user/anonymous?' + new URLSearchParams({
"filter": `username~"${q}"${includeSelf ? '' : `&&id!="${pb.authStore.record?.id}"`}`,
"filter": `username~"${q}"${includeSelf ? '' : `&&id!="${user?.id}"`}`,
}), {
method: 'GET',
})
@@ -69,6 +71,8 @@ export async function users_auth_methods(f: (url: RequestInfo | URL, config?: Re
export async function login(user: User) {
const pb = getPb();
const r = await fetch('/api/v1/auth/login', {
method: 'POST',
body: JSON.stringify(user),
@@ -83,6 +87,8 @@ export async function login(user: User) {
}
export async function oauth_login(data: { name: string, code: string, codeVerifier: string }) {
const pb = getPb();
const r = await fetch('/api/v1/auth/oauth', {
method: 'POST',
body: JSON.stringify(data)
@@ -98,6 +104,8 @@ export async function oauth_login(data: { name: string, code: string, codeVerifi
export async function logout() {
const pb = getPb();
pb.authStore.clear();
}

View File

@@ -1,13 +1,17 @@
import { Waypoint } from "$lib/models/waypoint";
import { pb } from "$lib/pocketbase";
import { APIError } from "$lib/util/api_util";
import { writable, type Writable } from "svelte/store";
import { get, writable, type Writable } from "svelte/store";
import { currentUser } from "./user_store";
export const waypoint: Writable<Waypoint> = writable(new Waypoint(0, 0));
export async function waypoints_create(waypoint: Waypoint, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
const user = get(currentUser)
if (!user) {
throw Error("Unauthenticated")
}
waypoint.author = pb.authStore.record!.id
waypoint.author = user.id
let r = await f('/api/v1/waypoint', {
method: 'PUT',
@@ -45,7 +49,11 @@ export async function waypoints_create(waypoint: Waypoint, f: (url: RequestInfo
}
export async function waypoints_update(oldWaypoint: Waypoint, newWaypoint: Waypoint) {
newWaypoint.author = pb.authStore.record!.id
const user = get(currentUser)
if (!user) {
throw Error("Unauthenticated")
}
newWaypoint.author = user.id
let r = await fetch('/api/v1/waypoint/' + newWaypoint.id, {
method: 'POST',

View File

@@ -1,6 +1,5 @@
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";
@@ -46,7 +45,7 @@ export async function list<T>(event: RequestEvent, collection: Collection) {
let r: ListResult<T>;
if ((safeSearchParams.perPage ?? 0) < 0) {
const activities: T[] = await pb.collection(Collection[collection])
const activities: T[] = await event.locals.pb.collection(Collection[collection])
.getFullList<T>(safeSearchParams)
r = {
items: activities,
@@ -56,7 +55,7 @@ export async function list<T>(event: RequestEvent, collection: Collection) {
totalPages: 1
}
} else {
r = await pb.collection(Collection[collection])
r = await event.locals.pb.collection(Collection[collection])
.getList<T>(safeSearchParams.page, safeSearchParams.perPage, { ...safeSearchParams })
}
return r
@@ -69,7 +68,7 @@ export async function show<T>(event: RequestEvent, collection: Collection) {
const searchParams = Object.fromEntries(event.url.searchParams);
const safeSearchParams = RecordOptionsSchema.parse(searchParams);
const r = await pb.collection(collection.toString())
const r = await event.locals.pb.collection(collection.toString())
.getOne<T>(safeParams.id, safeSearchParams)
return r
@@ -82,7 +81,7 @@ export async function create<T>(event: RequestEvent, schema: ZodSchema, collecti
const data = await event.request.json();
const safeData = schema.parse(data);
const r = await pb.collection(Collection[collection]).create<T>(safeData, {...safeSearchParams, requestKey: null})
const r = await event.locals.pb.collection(Collection[collection]).create<T>(safeData, {...safeSearchParams, requestKey: null})
return r
}
@@ -97,7 +96,7 @@ export async function update<T>(event: RequestEvent, schema: ZodSchema, collecti
const data = await event.request.json();
const safeData = schema.parse(data);
const r = await pb.collection(Collection[collection]).update<T>(safeParams.id, safeData, safeSearchParams)
const r = await event.locals.pb.collection(Collection[collection]).update<T>(safeParams.id, safeData, safeSearchParams)
return r
}
@@ -108,7 +107,7 @@ export async function upload<T>(event: RequestEvent, collection: Collection) {
const data = await event.request.formData();
const r = await pb.collection(Collection[collection]).update<T>(safeParams.id, data)
const r = await event.locals.pb.collection(Collection[collection]).update<T>(safeParams.id, data)
return r
}
@@ -117,7 +116,7 @@ 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)
const r = await event.locals.pb.collection(Collection[collection]).delete(safeParams.id)
return { 'acknowledged': r }
}