adds search api
This commit is contained in:
@@ -1,17 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ms } from "$lib/meilisearch";
|
|
||||||
import type { Category } from "$lib/models/category";
|
import type { Category } from "$lib/models/category";
|
||||||
import type { TrailFilter } from "$lib/models/trail";
|
import type { TrailFilter } from "$lib/models/trail";
|
||||||
import { country_codes } from "$lib/util/country_code_util";
|
import { country_codes } from "$lib/util/country_code_util";
|
||||||
import { formatDistance, formatElevation } from "$lib/util/format_util";
|
import { formatDistance, formatElevation } from "$lib/util/format_util";
|
||||||
import { createEventDispatcher } from "svelte";
|
import { createEventDispatcher } from "svelte";
|
||||||
|
import { _ } from "svelte-i18n";
|
||||||
|
import { slide } from "svelte/transition";
|
||||||
import DoubleSlider from "../base/double_slider.svelte";
|
import DoubleSlider from "../base/double_slider.svelte";
|
||||||
import type { RadioItem } from "../base/radio_group.svelte";
|
import type { RadioItem } from "../base/radio_group.svelte";
|
||||||
import RadioGroup from "../base/radio_group.svelte";
|
import RadioGroup from "../base/radio_group.svelte";
|
||||||
import Search, { type SearchItem } from "../base/search.svelte";
|
import Search, { type SearchItem } from "../base/search.svelte";
|
||||||
import Slider from "../base/slider.svelte";
|
import Slider from "../base/slider.svelte";
|
||||||
import { slide } from "svelte/transition";
|
|
||||||
import { _ } from "svelte-i18n";
|
|
||||||
|
|
||||||
export let categories: Category[];
|
export let categories: Category[];
|
||||||
export let filterExpanded: boolean = true;
|
export let filterExpanded: boolean = true;
|
||||||
@@ -87,8 +86,13 @@
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const result = await ms.index("cities500").search(q, { limit: 5 });
|
const r = await fetch("/api/v1/search/cities500", {
|
||||||
searchDropdownItems = result.hits.map((h) => ({
|
method: "POST",
|
||||||
|
body: JSON.stringify({ q: q, options: { limit: 5 } }),
|
||||||
|
});
|
||||||
|
const result = await r.json();
|
||||||
|
|
||||||
|
searchDropdownItems = result.hits.map((h: Record<string, any>) => ({
|
||||||
text: h.name,
|
text: h.name,
|
||||||
description:
|
description:
|
||||||
country_codes[h["country code"] as keyof typeof country_codes],
|
country_codes[h["country code"] as keyof typeof country_codes],
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { util } from "$lib/vendor/svelte-form-lib/util";
|
|||||||
import { get, writable, type Writable } from "svelte/store";
|
import { get, writable, type Writable } from "svelte/store";
|
||||||
import { summit_logs_create, summit_logs_delete, summit_logs_update } from "./summit_log_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 { waypoints_create, waypoints_delete, waypoints_update } from "./waypoint_store";
|
||||||
import { ms } from "$lib/meilisearch";
|
|
||||||
import type { LatLng } from "leaflet";
|
import type { LatLng } from "leaflet";
|
||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
@@ -45,19 +44,22 @@ export async function trails_search_filter(filter: TrailFilter, f: (url: Request
|
|||||||
if (filter.near.lat && filter.near.lon) {
|
if (filter.near.lat && filter.near.lon) {
|
||||||
filterText += ` AND _geoRadius(${filter.near.lat}, ${filter.near.lon}, ${filter.near.radius})`
|
filterText += ` AND _geoRadius(${filter.near.lat}, ${filter.near.lon}, ${filter.near.radius})`
|
||||||
}
|
}
|
||||||
const indexResponse = await ms
|
let r = await f("/api/v1/search/trails", {
|
||||||
.index("trails")
|
method: "POST",
|
||||||
.search(filter.q, { filter: filterText });
|
body: JSON.stringify({ q: filter.q, options: { filter: filterText } }),
|
||||||
const trailIds = indexResponse.hits.map((h) => h.id);
|
});
|
||||||
|
const result = await r.json();
|
||||||
|
|
||||||
|
const trailIds = result.hits.map((h: Record<string, any>) => h.id);
|
||||||
|
|
||||||
if (trailIds.length == 0) {
|
if (trailIds.length == 0) {
|
||||||
trails.set([]);
|
trails.set([]);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const r = await f('/api/v1/trail?' + new URLSearchParams({
|
r = await f('/api/v1/trail?' + new URLSearchParams({
|
||||||
expand: "category,waypoints,summit_logs",
|
expand: "category,waypoints,summit_logs",
|
||||||
filter: trailIds.map((id) => `id="${id}"`).join('||'),
|
filter: trailIds.map((id: Record<string, any>) => `id="${id}"`).join('||'),
|
||||||
sort: `${filter.sortOrder}${filter.sort}`
|
sort: `${filter.sortOrder}${filter.sort}`
|
||||||
}), {
|
}), {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -87,13 +89,20 @@ export async function trails_search_bounding_box(northEast: LatLng, southWest: L
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexResponse = await ms.index("trails").search("", {
|
let r = await fetch("/api/v1/search/trails", {
|
||||||
filter: [
|
method: "POST",
|
||||||
`_geoBoundingBox([${northEast.lat}, ${northEast.lng}], [${southWest.lat}, ${southWest.lng}])`,
|
body: JSON.stringify({
|
||||||
filterText
|
q: "", options: {
|
||||||
],
|
filter: [
|
||||||
|
`_geoBoundingBox([${northEast.lat}, ${northEast.lng}], [${southWest.lat}, ${southWest.lng}])`,
|
||||||
|
filterText
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
const trailIds = indexResponse.hits.map((h) => h.id);
|
const result = await r.json();
|
||||||
|
|
||||||
|
const trailIds = result.hits.map((h: Record<string, any>) => h.id);
|
||||||
|
|
||||||
if (trailIds.length == 0) {
|
if (trailIds.length == 0) {
|
||||||
const currentTrails: Trail[] = get(trails);
|
const currentTrails: Trail[] = get(trails);
|
||||||
@@ -101,8 +110,8 @@ export async function trails_search_bounding_box(northEast: LatLng, southWest: L
|
|||||||
return compareObjectArrays<Trail>(currentTrails, []);
|
return compareObjectArrays<Trail>(currentTrails, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
const r = await fetch('/api/v1/trail?' + new URLSearchParams({
|
r = await fetch('/api/v1/trail?' + new URLSearchParams({
|
||||||
filter: trailIds.map((id) => `id="${id}"`).join("||"),
|
filter: trailIds.map((id: Record<string, any>) => `id="${id}"`).join("||"),
|
||||||
expand: "category,waypoints,summit_logs",
|
expand: "category,waypoints,summit_logs",
|
||||||
sort: `+name`,
|
sort: `+name`,
|
||||||
}), {
|
}), {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
import CategoryCard from "$lib/components/category_card.svelte";
|
import CategoryCard from "$lib/components/category_card.svelte";
|
||||||
import Scene from "$lib/components/scene.svelte";
|
import Scene from "$lib/components/scene.svelte";
|
||||||
import TrailCard from "$lib/components/trail/trail_card.svelte";
|
import TrailCard from "$lib/components/trail/trail_card.svelte";
|
||||||
import { ms } from "$lib/meilisearch";
|
|
||||||
import { categories } from "$lib/stores/category_store";
|
import { categories } from "$lib/stores/category_store";
|
||||||
import { trails } from "$lib/stores/trail_store";
|
import { trails } from "$lib/stores/trail_store";
|
||||||
import { currentUser } from "$lib/stores/user_store";
|
import { currentUser } from "$lib/stores/user_store";
|
||||||
@@ -17,33 +16,38 @@
|
|||||||
let searchDropdownItems: SearchItem[] = [];
|
let searchDropdownItems: SearchItem[] = [];
|
||||||
|
|
||||||
async function search(q: string) {
|
async function search(q: string) {
|
||||||
const response = await ms.multiSearch({
|
const r = await fetch("/api/v1/search/multi", {
|
||||||
queries: [
|
method: "POST",
|
||||||
{
|
body: JSON.stringify({
|
||||||
indexUid: "trails",
|
queries: [
|
||||||
q: q,
|
{
|
||||||
limit: 3,
|
indexUid: "trails",
|
||||||
},
|
q: q,
|
||||||
{
|
limit: 3,
|
||||||
indexUid: "cities500",
|
},
|
||||||
q: q,
|
{
|
||||||
limit: 3,
|
indexUid: "cities500",
|
||||||
},
|
q: q,
|
||||||
],
|
limit: 3,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const trailItems = response.results[0].hits.map((t) => ({
|
const response = await r.json();
|
||||||
|
|
||||||
|
const trailItems = response.results[0].hits.map((t: Record<string, any>) => ({
|
||||||
text: t.name,
|
text: t.name,
|
||||||
description: `Trail | ${t.location}`,
|
description: `Trail | ${t.location}`,
|
||||||
value: t.id,
|
value: t.id,
|
||||||
icon: "route",
|
icon: "route",
|
||||||
}));
|
}));
|
||||||
const cityItems = response.results[1].hits.map((t) => ({
|
const cityItems = response.results[1].hits.map((c: Record<string, any>) => ({
|
||||||
text: t.name,
|
text: c.name,
|
||||||
description: `City | ${
|
description: `City | ${
|
||||||
country_codes[t["country code"] as keyof typeof country_codes]
|
country_codes[c["country code"] as keyof typeof country_codes]
|
||||||
}`,
|
}`,
|
||||||
value: t,
|
value: c,
|
||||||
icon: "city",
|
icon: "city",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
13
web/src/routes/api/v1/search/[index]/+server.ts
Normal file
13
web/src/routes/api/v1/search/[index]/+server.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { ms } from "$lib/meilisearch";
|
||||||
|
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
export async function POST(event: RequestEvent) {
|
||||||
|
const data = await event.request.json()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = await ms.index(event.params.index as string).search(data.q, data.options);
|
||||||
|
return json(r);
|
||||||
|
} catch (e: any) {
|
||||||
|
throw error(e.status, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
15
web/src/routes/api/v1/search/multi/+server.ts
Normal file
15
web/src/routes/api/v1/search/multi/+server.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { ms } from "$lib/meilisearch";
|
||||||
|
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
export async function POST(event: RequestEvent) {
|
||||||
|
const data = await event.request.json()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = await ms.multiSearch({
|
||||||
|
queries: data.queries
|
||||||
|
});
|
||||||
|
return json(r);
|
||||||
|
} catch (e: any) {
|
||||||
|
throw error(e.status, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
import EmptyStateSearch from "$lib/components/empty_states/empty_state_search.svelte";
|
import EmptyStateSearch from "$lib/components/empty_states/empty_state_search.svelte";
|
||||||
import TrailCard from "$lib/components/trail/trail_card.svelte";
|
import TrailCard from "$lib/components/trail/trail_card.svelte";
|
||||||
import TrailFilterPanel from "$lib/components/trail/trail_filter_panel.svelte";
|
import TrailFilterPanel from "$lib/components/trail/trail_filter_panel.svelte";
|
||||||
import { ms } from "$lib/meilisearch";
|
|
||||||
import type { Trail, TrailFilter } from "$lib/models/trail";
|
import type { Trail, TrailFilter } from "$lib/models/trail";
|
||||||
import { categories } from "$lib/stores/category_store";
|
import { categories } from "$lib/stores/category_store";
|
||||||
import {
|
import {
|
||||||
@@ -111,28 +110,33 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function search(q: string) {
|
async function search(q: string) {
|
||||||
const response = await ms.multiSearch({
|
const r = await fetch("/api/v1/search/multi", {
|
||||||
queries: [
|
method: "POST",
|
||||||
{
|
body: JSON.stringify({
|
||||||
indexUid: "trails",
|
queries: [
|
||||||
q: q,
|
{
|
||||||
limit: 3,
|
indexUid: "trails",
|
||||||
},
|
q: q,
|
||||||
{
|
limit: 3,
|
||||||
indexUid: "cities500",
|
},
|
||||||
q: q,
|
{
|
||||||
limit: 3,
|
indexUid: "cities500",
|
||||||
},
|
q: q,
|
||||||
],
|
limit: 3,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const trailItems = response.results[0].hits.map((t) => ({
|
const response = await r.json();
|
||||||
|
|
||||||
|
const trailItems = response.results[0].hits.map((t: Record<string, any>) => ({
|
||||||
text: t.name,
|
text: t.name,
|
||||||
description: `Trail | ${t.location}`,
|
description: `Trail | ${t.location}`,
|
||||||
value: t,
|
value: t,
|
||||||
icon: "route",
|
icon: "route",
|
||||||
}));
|
}));
|
||||||
const cityItems = response.results[1].hits.map((c) => ({
|
const cityItems = response.results[1].hits.map((c: Record<string, any>) => ({
|
||||||
text: c.name,
|
text: c.name,
|
||||||
description: `City | ${
|
description: `City | ${
|
||||||
country_codes[c["country code"] as keyof typeof country_codes]
|
country_codes[c["country code"] as keyof typeof country_codes]
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
type SelectItem,
|
type SelectItem,
|
||||||
} from "$lib/components/base/select.svelte";
|
} from "$lib/components/base/select.svelte";
|
||||||
import ConfirmModal from "$lib/components/confirm_modal.svelte";
|
import ConfirmModal from "$lib/components/confirm_modal.svelte";
|
||||||
import { ms } from "$lib/meilisearch";
|
|
||||||
import {
|
import {
|
||||||
currentUser,
|
currentUser,
|
||||||
logout,
|
logout,
|
||||||
@@ -45,8 +44,12 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function searchCities(q: string) {
|
async function searchCities(q: string) {
|
||||||
const result = await ms.index("cities500").search(q, { limit: 5 });
|
const r = await fetch("/api/v1/search/cities500", {
|
||||||
searchDropdownItems = result.hits.map((h) => ({
|
method: "POST",
|
||||||
|
body: JSON.stringify({ q: q, options: { limit: 5 } }),
|
||||||
|
});
|
||||||
|
const result = await r.json();
|
||||||
|
searchDropdownItems = result.hits.map((h: Record<string, any>) => ({
|
||||||
text: h.name,
|
text: h.name,
|
||||||
description:
|
description:
|
||||||
country_codes[h["country code"] as keyof typeof country_codes],
|
country_codes[h["country code"] as keyof typeof country_codes],
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
import SummitLogModal from "$lib/components/summit_log/summit_log_modal.svelte";
|
import SummitLogModal from "$lib/components/summit_log/summit_log_modal.svelte";
|
||||||
import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
|
import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
|
||||||
import WaypointModal from "$lib/components/waypoint/waypoint_modal.svelte";
|
import WaypointModal from "$lib/components/waypoint/waypoint_modal.svelte";
|
||||||
import { ms } from "$lib/meilisearch";
|
|
||||||
import { SummitLog } from "$lib/models/summit_log";
|
import { SummitLog } from "$lib/models/summit_log";
|
||||||
import { Trail, trailSchema } from "$lib/models/trail";
|
import { Trail, trailSchema } from "$lib/models/trail";
|
||||||
import { Waypoint } from "$lib/models/waypoint";
|
import { Waypoint } from "$lib/models/waypoint";
|
||||||
@@ -106,7 +105,12 @@
|
|||||||
);
|
);
|
||||||
$form.id = createdTrail.id;
|
$form.id = createdTrail.id;
|
||||||
} else {
|
} else {
|
||||||
await trails_update($trail, submittedTrail, photoFiles, gpxFile);
|
await trails_update(
|
||||||
|
$trail,
|
||||||
|
submittedTrail,
|
||||||
|
photoFiles,
|
||||||
|
gpxFile,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
show_toast({
|
show_toast({
|
||||||
@@ -222,13 +226,20 @@
|
|||||||
|
|
||||||
reader.onload = async function (e) {
|
reader.onload = async function (e) {
|
||||||
await addGPXLayer(e.target?.result as string);
|
await addGPXLayer(e.target?.result as string);
|
||||||
const closestCity = (
|
const r = await fetch("/api/v1/search/cities500", {
|
||||||
await ms.index("cities500").search("", {
|
method: "POST",
|
||||||
filter: [`_geoRadius(${$form.lat}, ${$form.lon}, 10000)`],
|
body: JSON.stringify({
|
||||||
sort: [`_geoPoint(${$form.lat}, ${$form.lon}):asc`],
|
q: "",
|
||||||
limit: 1,
|
options: {
|
||||||
})
|
filter: [
|
||||||
).hits[0];
|
`_geoRadius(${$form.lat}, ${$form.lon}, 10000)`,
|
||||||
|
],
|
||||||
|
sort: [`_geoPoint(${$form.lat}, ${$form.lon}):asc`],
|
||||||
|
limit: 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const closestCity = (await r.json()).hits[0];
|
||||||
|
|
||||||
$form.location = closestCity.name;
|
$form.location = closestCity.name;
|
||||||
};
|
};
|
||||||
@@ -318,7 +329,6 @@
|
|||||||
if ($form.thumbnail == index) {
|
if ($form.thumbnail == index) {
|
||||||
$form.thumbnail = 0;
|
$form.thumbnail = 0;
|
||||||
console.log("huere");
|
console.log("huere");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index >= $form.photos.length) {
|
if (index >= $form.photos.length) {
|
||||||
@@ -491,7 +501,9 @@
|
|||||||
{#each ($form.photos ?? []).concat(photoPreviews) as photo, i}
|
{#each ($form.photos ?? []).concat(photoPreviews) as photo, i}
|
||||||
<div class="shrink-0 grow-0 basis-auto m-2">
|
<div class="shrink-0 grow-0 basis-auto m-2">
|
||||||
<PhotoCard
|
<PhotoCard
|
||||||
src={i >= $form.photos.length ? photo : getFileURL($form, photo)}
|
src={i >= $form.photos.length
|
||||||
|
? photo
|
||||||
|
: getFileURL($form, photo)}
|
||||||
on:delete={() => handlePhotoDelete(i)}
|
on:delete={() => handlePhotoDelete(i)}
|
||||||
isThumbnail={$form.thumbnail === i}
|
isThumbnail={$form.thumbnail === i}
|
||||||
on:thumbnail={() => makePhotoThumbnail(i)}
|
on:thumbnail={() => makePhotoThumbnail(i)}
|
||||||
|
|||||||
Reference in New Issue
Block a user