feat: upgrade to SvelteKit 3 and adapter-bun
Some checks failed
Test and deploy staging / staging (push) Failing after 43s
Some checks failed
Test and deploy staging / staging (push) Failing after 43s
Run the sveltekit-3 migration and finish its manual tasks:
- Move kit config into the sveltekit() Vite plugin; drop svelte.config.js
- Replace $lib with #lib subpath imports
- Declare env vars in src/env.ts and import them from $app/env/{private,public}
- Consolidate param matchers into src/params.ts
- Use error(status, message), Response.json, refreshAll and goto reset/shallow
- Fall back to a full page load when goto rejects a login return path
Switch from adapter-node to adapter-bun: the build script runs Vite under
Bun (the adapter requires it) and the image starts with `bun ./build`.
Runtime env names (PORT, BODY_SIZE_LIMIT, ADDRESS_HEADER, ...) are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,10 +2,10 @@
|
||||
import { resolve } from '$app/paths';
|
||||
import ArrowRight from 'lucide-svelte/icons/arrow-right';
|
||||
import ExternalLink from 'lucide-svelte/icons/external-link';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import { Badge } from '$lib/components/ui/badge/index.js';
|
||||
import ProjectIcon from '$lib/components/ProjectIcon.svelte';
|
||||
import { projects } from '$lib/projects';
|
||||
import * as Card from '#lib/components/ui/card/index.js';
|
||||
import { Badge } from '#lib/components/ui/badge/index.js';
|
||||
import ProjectIcon from '#lib/components/ProjectIcon.svelte';
|
||||
import { projects } from '#lib/projects.js';
|
||||
|
||||
const earlier = [
|
||||
'distributor',
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { resolve } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import { goto, preloadData, pushState } from '$app/navigation';
|
||||
import AnimalDetails from '$lib/animaldex/AnimalDetails.svelte';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import { goto, preloadData } from '$app/navigation';
|
||||
import AnimalDetails from '#lib/animaldex/AnimalDetails.svelte';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import { Input } from '#lib/components/ui/input/index.js';
|
||||
import PawPrint from 'lucide-svelte/icons/paw-print';
|
||||
import * as Dialog from '$lib/components/ui/dialog/index.js';
|
||||
import * as Dialog from '#lib/components/ui/dialog/index.js';
|
||||
import type { Map as MaptilerMap, Marker as MaptilerMarker } from '@maptiler/sdk';
|
||||
|
||||
interface TrailPoint {
|
||||
@@ -64,8 +64,9 @@
|
||||
const id = String(animalId);
|
||||
const result = await preloadData(resolve('/projects/animaldex/[id=integer]', { id }));
|
||||
if (result.type === 'loaded' && result.status === 200) {
|
||||
pushState(resolve('/projects/animaldex/[id=integer]', { id }), {
|
||||
animal: result.data.animal
|
||||
goto(resolve('/projects/animaldex/[id=integer]', { id }), {
|
||||
shallow: true,
|
||||
state: { animal: result.data.animal }
|
||||
});
|
||||
} else {
|
||||
goto(resolve('/projects/animaldex/[id=integer]', { id }));
|
||||
@@ -82,22 +83,21 @@
|
||||
(async () => {
|
||||
try {
|
||||
const { Map, Marker, config } = await import('@maptiler/sdk');
|
||||
const { env } = await import('$env/dynamic/public');
|
||||
const { PUBLIC_MAPTILER_API_KEY } = await import('$app/env/public');
|
||||
if (destroyed) return;
|
||||
if (!env.PUBLIC_MAPTILER_API_KEY) {
|
||||
if (!PUBLIC_MAPTILER_API_KEY) {
|
||||
mapError = 'The map is not configured yet.';
|
||||
return;
|
||||
}
|
||||
|
||||
config.apiKey = env.PUBLIC_MAPTILER_API_KEY;
|
||||
config.apiKey = PUBLIC_MAPTILER_API_KEY;
|
||||
MapMarker = Marker;
|
||||
|
||||
const initMap = (center: [number, number], zoom: number) => {
|
||||
map = new Map({
|
||||
container: mapContainer,
|
||||
style:
|
||||
'https://api.maptiler.com/maps/satellite/style.json?key=' +
|
||||
env.PUBLIC_MAPTILER_API_KEY,
|
||||
'https://api.maptiler.com/maps/satellite/style.json?key=' + PUBLIC_MAPTILER_API_KEY,
|
||||
center,
|
||||
zoom,
|
||||
scrollZoom: false,
|
||||
@@ -176,7 +176,7 @@
|
||||
request = new AbortController();
|
||||
let geojson: { features: GeoJSONFeature[] };
|
||||
try {
|
||||
const res = await fetch(`${resolve('/projects/animaldex/api/map')}?${params}`, {
|
||||
const res = await fetch(`${resolve('projects/animaldex/api/map')}?${params}`, {
|
||||
signal: request.signal
|
||||
});
|
||||
if (!res.ok) throw new Error('Map request failed');
|
||||
@@ -298,7 +298,7 @@
|
||||
<h1 class="text-4xl font-semibold tracking-tight sm:text-5xl">animaldex</h1>
|
||||
<p class="mt-2 text-base text-[var(--site-muted)]">neighborhood animals on the map</p>
|
||||
</div>
|
||||
<Button href={resolve('/projects/animaldex/register')}>add an animal</Button>
|
||||
<Button href={resolve('projects/animaldex/register')}>add an animal</Button>
|
||||
</div>
|
||||
{#if submitted}<p role="status" class="mb-5 border-y border-[var(--site-border)] py-3">
|
||||
sighting submitted. it will appear after review.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { animals, photos, sightings } from '$lib/server/db/schema';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { animals, photos, sightings } from '#lib/server/db/schema/index.js';
|
||||
import { and, asc, desc, eq, inArray, isNotNull, isNull } from 'drizzle-orm';
|
||||
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
@@ -23,7 +23,7 @@ export const load: PageServerLoad = async ({ params }) => {
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (!animal) throw error(404, { message: 'Animal not found' });
|
||||
if (!animal) throw error(404, 'Animal not found');
|
||||
|
||||
const sightingRows = await db
|
||||
.select({
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
|
||||
import AnimalDetails from '$lib/animaldex/AnimalDetails.svelte';
|
||||
import AnimalDetails from '#lib/animaldex/AnimalDetails.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<section class="mx-auto max-w-6xl px-4 py-10 sm:px-6 sm:py-14">
|
||||
<a
|
||||
href={resolve('/projects/animaldex')}
|
||||
href={resolve('projects/animaldex')}
|
||||
class="mb-6 inline-flex items-center gap-1 text-base underline underline-offset-4"
|
||||
>
|
||||
<ArrowLeft size={16} aria-hidden="true" /> back to map
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { animals, sightings } from '$lib/server/db/schema';
|
||||
import { isAdmin } from '$lib/server/auth';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { animals, sightings } from '#lib/server/db/schema/index.js';
|
||||
import { isAdmin } from '#lib/server/auth.js';
|
||||
import { and, eq, sql } from 'drizzle-orm';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { refreshAll } from '$app/navigation';
|
||||
import { resolve } from '$app/paths';
|
||||
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
|
||||
import Bot from 'lucide-svelte/icons/bot';
|
||||
@@ -8,9 +8,9 @@
|
||||
import MapPin from 'lucide-svelte/icons/map-pin';
|
||||
import PawPrint from 'lucide-svelte/icons/paw-print';
|
||||
import X from 'lucide-svelte/icons/x';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import { Badge } from '$lib/components/ui/badge/index.js';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import * as Card from '#lib/components/ui/card/index.js';
|
||||
import { Badge } from '#lib/components/ui/badge/index.js';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
@@ -21,13 +21,13 @@
|
||||
actionLoading = `animals-${id}-${action}`;
|
||||
actionError = '';
|
||||
try {
|
||||
const response = await fetch(resolve(`/projects/animaldex/api/animals/${id}/moderate`), {
|
||||
const response = await fetch(resolve(`projects/animaldex/api/animals/${id}/moderate`), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action })
|
||||
});
|
||||
if (!response.ok) throw new Error();
|
||||
await invalidateAll();
|
||||
await refreshAll();
|
||||
} catch {
|
||||
actionError = 'Could not update this animal. Please try again.';
|
||||
} finally {
|
||||
@@ -43,14 +43,14 @@
|
||||
<h1 class="text-xl font-bold">Administrator access required</h1>
|
||||
{#if !data.user}<a
|
||||
class="mt-4 inline-block underline"
|
||||
href={resolve('/login?returnTo=/projects/animaldex/admin')}>Log in with your account</a
|
||||
href={resolve('login?returnTo=/projects/animaldex/admin')}>Log in with your account</a
|
||||
>
|
||||
{:else}<p class="mt-4">Your account does not have administrator access.</p>{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<section class="mx-auto max-w-4xl px-4 py-10 sm:px-6 sm:py-14">
|
||||
<a
|
||||
href={resolve('/projects/animaldex')}
|
||||
href={resolve('projects/animaldex')}
|
||||
class="inline-flex items-center gap-1 text-base text-[var(--site-muted)]"
|
||||
><ArrowLeft size={16} aria-hidden="true" /> animaldex</a
|
||||
>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { animals, sightings, photos } from '$lib/server/db/schema';
|
||||
import { registerAnimalSchema } from '$lib/validation/sighting';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { distanceMeters } from '$lib/geo';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { animals, sightings, photos } from '#lib/server/db/schema/index.js';
|
||||
import { registerAnimalSchema } from '#lib/validation/sighting.js';
|
||||
import { R2_PUBLIC_URL } from '$app/env/private';
|
||||
import { distanceMeters } from '#lib/geo.js';
|
||||
|
||||
const MAX_DISTANCE_METERS = 30_000;
|
||||
|
||||
@@ -13,16 +13,14 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
const parsed = registerAnimalSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
throw error(400, { message: parsed.error.issues[0]?.message ?? 'Invalid request' });
|
||||
throw error(400, parsed.error.issues[0]?.message ?? 'Invalid request');
|
||||
}
|
||||
|
||||
const data = parsed.data;
|
||||
|
||||
const dist = distanceMeters(data.deviceLat, data.deviceLng, data.lat, data.lng);
|
||||
if (dist > MAX_DISTANCE_METERS) {
|
||||
throw error(422, {
|
||||
message: `Location is ${Math.round(dist / 1000)}km from your device. Maximum is 30km.`
|
||||
});
|
||||
throw error(422, `Location is ${Math.round(dist / 1000)}km from your device. Maximum is 30km.`);
|
||||
}
|
||||
|
||||
const result = await db.transaction(async (tx) => {
|
||||
@@ -53,7 +51,7 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
data.photoKeys.map((key, i) => ({
|
||||
sightingId: sighting.id,
|
||||
r2Key: key,
|
||||
url: `${env.R2_PUBLIC_URL}/${key}`,
|
||||
url: `${R2_PUBLIC_URL}/${key}`,
|
||||
sortOrder: i
|
||||
}))
|
||||
);
|
||||
@@ -62,5 +60,5 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
});
|
||||
|
||||
console.log(`[animals] created animal ${result.animalId}, sighting ${result.sightingId}`);
|
||||
return json(result, { status: 201 });
|
||||
return Response.json(result, { status: 201 });
|
||||
};
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { animals, sightings } from '$lib/server/db/schema';
|
||||
import { moderationActionSchema } from '$lib/validation/sighting';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { animals, sightings } from '#lib/server/db/schema/index.js';
|
||||
import { moderationActionSchema } from '#lib/validation/sighting.js';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { requireAdmin } from '$lib/server/auth';
|
||||
import { requireAdmin } from '#lib/server/auth.js';
|
||||
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
requireAdmin(event);
|
||||
|
||||
const id = Number(event.params.id);
|
||||
if (!Number.isInteger(id) || id <= 0) throw error(404, { message: 'Not found' });
|
||||
if (!Number.isInteger(id) || id <= 0) throw error(404, 'Not found');
|
||||
|
||||
const body = await event.request.json().catch(() => null);
|
||||
const parsed = moderationActionSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
throw error(400, { message: 'action must be "accept" or "deny"' });
|
||||
throw error(400, 'action must be "accept" or "deny"');
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
@@ -43,12 +43,12 @@ export const POST: RequestHandler = async (event) => {
|
||||
});
|
||||
|
||||
if (!updated) {
|
||||
throw error(404, { message: 'Animal not found' });
|
||||
throw error(404, 'Animal not found');
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[moderate] animal ${event.params.id} → ${parsed.data.action}, updated ${updated?.id}`
|
||||
);
|
||||
|
||||
return json({ ok: true, action: parsed.data.action });
|
||||
return Response.json({ ok: true, action: parsed.data.action });
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { animals, sightings, photos } from '$lib/server/db/schema';
|
||||
import { addSightingSchema } from '$lib/validation/sighting';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { animals, sightings, photos } from '#lib/server/db/schema/index.js';
|
||||
import { addSightingSchema } from '#lib/validation/sighting.js';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { distanceMeters } from '$lib/geo';
|
||||
import { R2_PUBLIC_URL } from '$app/env/private';
|
||||
import { distanceMeters } from '#lib/geo.js';
|
||||
|
||||
const MAX_DISTANCE_METERS = 30_000;
|
||||
|
||||
@@ -14,7 +14,7 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
const parsed = addSightingSchema.safeParse({ ...body, animalId: params.id });
|
||||
|
||||
if (!parsed.success) {
|
||||
throw error(400, { message: parsed.error.issues[0]?.message ?? 'Invalid request' });
|
||||
throw error(400, parsed.error.issues[0]?.message ?? 'Invalid request');
|
||||
}
|
||||
|
||||
const data = parsed.data;
|
||||
@@ -26,14 +26,12 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
.limit(1);
|
||||
|
||||
if (!animal) {
|
||||
throw error(404, { message: 'Animal not found' });
|
||||
throw error(404, 'Animal not found');
|
||||
}
|
||||
|
||||
const dist = distanceMeters(data.deviceLat, data.deviceLng, data.lat, data.lng);
|
||||
if (dist > MAX_DISTANCE_METERS) {
|
||||
throw error(422, {
|
||||
message: `Location is ${Math.round(dist / 1000)}km from your device. Maximum is 30km.`
|
||||
});
|
||||
throw error(422, `Location is ${Math.round(dist / 1000)}km from your device. Maximum is 30km.`);
|
||||
}
|
||||
|
||||
const animalAlreadyAccepted = animal.acceptedAt !== null;
|
||||
@@ -56,7 +54,7 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
data.photoKeys.map((key, i) => ({
|
||||
sightingId: sighting.id,
|
||||
r2Key: key,
|
||||
url: `${env.R2_PUBLIC_URL}/${key}`,
|
||||
url: `${R2_PUBLIC_URL}/${key}`,
|
||||
sortOrder: i
|
||||
}))
|
||||
);
|
||||
@@ -68,5 +66,5 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
console.log(
|
||||
`[sightings] created sighting ${result.sightingId} for animal ${params.id} (auto-accepted: ${animalAlreadyAccepted})`
|
||||
);
|
||||
return json(result, { status: 201 });
|
||||
return Response.json(result, { status: 201 });
|
||||
};
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { sightings } from '$lib/server/db/schema';
|
||||
import { moderationActionSchema } from '$lib/validation/sighting';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { sightings } from '#lib/server/db/schema/index.js';
|
||||
import { moderationActionSchema } from '#lib/validation/sighting.js';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { requireAdmin } from '$lib/server/auth';
|
||||
import { requireAdmin } from '#lib/server/auth.js';
|
||||
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
requireAdmin(event);
|
||||
|
||||
const id = Number(event.params.id);
|
||||
if (!Number.isInteger(id) || id <= 0) throw error(404, { message: 'Not found' });
|
||||
if (!Number.isInteger(id) || id <= 0) throw error(404, 'Not found');
|
||||
|
||||
const body = await event.request.json().catch(() => null);
|
||||
const parsed = moderationActionSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
throw error(400, { message: 'action must be "accept" or "deny"' });
|
||||
throw error(400, 'action must be "accept" or "deny"');
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
@@ -32,8 +32,8 @@ export const POST: RequestHandler = async (event) => {
|
||||
.returning({ id: sightings.id });
|
||||
|
||||
if (!updated) {
|
||||
throw error(404, { message: 'Sighting not found' });
|
||||
throw error(404, 'Sighting not found');
|
||||
}
|
||||
|
||||
return json({ ok: true, action: parsed.data.action });
|
||||
return Response.json({ ok: true, action: parsed.data.action });
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import * as z from 'zod';
|
||||
|
||||
@@ -9,9 +9,9 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
const parsed = schema.safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
throw error(400, { message: 'imageUrl is required' });
|
||||
throw error(400, 'imageUrl is required');
|
||||
}
|
||||
|
||||
// Breed detection is not configured for this site yet.
|
||||
return json({ breed: null, confidence: 0 });
|
||||
return Response.json({ breed: null, confidence: 0 });
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { animals, sightings } from '$lib/server/db/schema';
|
||||
import { mapQuerySchema } from '$lib/validation/sighting';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { animals, sightings } from '#lib/server/db/schema/index.js';
|
||||
import { mapQuerySchema } from '#lib/validation/sighting.js';
|
||||
import { and, eq, gte, ilike, isNotNull, isNull, lte, sql } from 'drizzle-orm';
|
||||
|
||||
export const GET: RequestHandler = async ({ url }) => {
|
||||
@@ -10,7 +10,7 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
const parsed = mapQuerySchema.safeParse(params);
|
||||
|
||||
if (!parsed.success) {
|
||||
throw error(400, { message: 'Invalid query parameters' });
|
||||
throw error(400, 'Invalid query parameters');
|
||||
}
|
||||
|
||||
const q = parsed.data;
|
||||
@@ -118,5 +118,5 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
};
|
||||
});
|
||||
|
||||
return json({ type: 'FeatureCollection', features });
|
||||
return Response.json({ type: 'FeatureCollection', features });
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { uploadImage, isAllowedMimeType } from '$lib/server/r2';
|
||||
import { uploadImage, isAllowedMimeType } from '#lib/server/r2.js';
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
const form = await request.formData().catch(() => null);
|
||||
@@ -10,7 +10,7 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
if (!photo.size || photo.size > 5 * 1024 * 1024)
|
||||
throw error(400, 'Photos must be 5 MB or smaller.');
|
||||
try {
|
||||
return json(await uploadImage(photo));
|
||||
return Response.json(await uploadImage(photo));
|
||||
} catch (cause) {
|
||||
console.error('Animaldex image upload failed', cause);
|
||||
throw error(503, 'Image storage is unavailable.');
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import { Textarea } from '$lib/components/ui/textarea/index.js';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import { Input } from '#lib/components/ui/input/index.js';
|
||||
import { Textarea } from '#lib/components/ui/textarea/index.js';
|
||||
import { resolve } from '$app/paths';
|
||||
import type { Map as MaptilerMap, Marker as MaptilerMarker } from '@maptiler/sdk';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { page } from '$app/state';
|
||||
import { goto } from '$app/navigation';
|
||||
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
|
||||
import X from 'lucide-svelte/icons/x';
|
||||
|
||||
const animalId = $derived($page.url.searchParams.get('animalId'));
|
||||
const animalId = $derived(page.url.searchParams.get('animalId'));
|
||||
const isNewSighting = $derived(!!animalId);
|
||||
|
||||
let species = $state('');
|
||||
@@ -95,16 +95,16 @@
|
||||
if (!mapContainer || lat === null || lng === null) return;
|
||||
try {
|
||||
const { Map, Marker, config } = await import('@maptiler/sdk');
|
||||
const { env } = await import('$env/dynamic/public');
|
||||
const { PUBLIC_MAPTILER_API_KEY } = await import('$app/env/public');
|
||||
if (destroyed) return;
|
||||
if (!env.PUBLIC_MAPTILER_API_KEY) {
|
||||
if (!PUBLIC_MAPTILER_API_KEY) {
|
||||
mapError = 'The map is not configured yet. Your GPS location is still available.';
|
||||
return;
|
||||
}
|
||||
config.apiKey = env.PUBLIC_MAPTILER_API_KEY;
|
||||
config.apiKey = PUBLIC_MAPTILER_API_KEY;
|
||||
map = new Map({
|
||||
container: mapContainer,
|
||||
style: `https://api.maptiler.com/maps/satellite/style.json?key=${env.PUBLIC_MAPTILER_API_KEY}`,
|
||||
style: `https://api.maptiler.com/maps/satellite/style.json?key=${PUBLIC_MAPTILER_API_KEY}`,
|
||||
center: [lng!, lat!],
|
||||
zoom: 15
|
||||
});
|
||||
@@ -181,7 +181,7 @@
|
||||
for (const file of photoFiles) {
|
||||
const uploadForm = new FormData();
|
||||
uploadForm.set('photo', file);
|
||||
const uploadRes = await fetch(resolve('/projects/animaldex/api/upload'), {
|
||||
const uploadRes = await fetch(resolve('projects/animaldex/api/upload'), {
|
||||
method: 'POST',
|
||||
body: uploadForm
|
||||
});
|
||||
@@ -193,8 +193,8 @@
|
||||
if (reporterName) localStorage.setItem('reporterName', reporterName);
|
||||
|
||||
const endpoint = isNewSighting
|
||||
? resolve(`/projects/animaldex/api/animals/${animalId}/sightings`)
|
||||
: resolve('/projects/animaldex/api/animals');
|
||||
? resolve(`projects/animaldex/api/animals/${animalId}/sightings`)
|
||||
: resolve('projects/animaldex/api/animals');
|
||||
const body = isNewSighting
|
||||
? {
|
||||
animalId,
|
||||
@@ -229,7 +229,7 @@
|
||||
const err = await res.json();
|
||||
throw new Error(err.message ?? 'Submission failed');
|
||||
}
|
||||
goto(resolve('/projects/animaldex?submitted=1'));
|
||||
goto(resolve('projects/animaldex?submitted=1'));
|
||||
} catch (e: unknown) {
|
||||
submitError = e instanceof Error ? e.message : 'Something went wrong.';
|
||||
} finally {
|
||||
@@ -244,7 +244,7 @@
|
||||
|
||||
<section class="mx-auto max-w-3xl px-4 py-10 sm:px-6 sm:py-14">
|
||||
<a
|
||||
href={resolve('/projects/animaldex')}
|
||||
href={resolve('projects/animaldex')}
|
||||
class="inline-flex items-center gap-1 text-base text-[var(--site-muted)] hover:text-[var(--site-text)]"
|
||||
><ArrowLeft size={16} aria-hidden="true" /> animaldex</a
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { db } from '$lib/server/db';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { NativeSelect } from '$lib/components/ui/native-select/index.js';
|
||||
import * as Tabs from '$lib/components/ui/tabs/index.js';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import { Badge } from '$lib/components/ui/badge/index.js';
|
||||
import { NativeSelect } from '#lib/components/ui/native-select/index.js';
|
||||
import * as Tabs from '#lib/components/ui/tabs/index.js';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import { Input } from '#lib/components/ui/input/index.js';
|
||||
import { Badge } from '#lib/components/ui/badge/index.js';
|
||||
import { SvelteDate, SvelteMap } from 'svelte/reactivity';
|
||||
import PriceChart, { type PriceSeries } from '$lib/cs-prices/PriceChart.svelte';
|
||||
import PriceChart, { type PriceSeries } from '#lib/cs-prices/PriceChart.svelte';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { db } from '$lib/server/db';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const GET: RequestHandler = async ({ url }) => {
|
||||
@@ -13,12 +12,12 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
const rows = await db.execute(sql`SELECT id, name, market_hash_name, icon_url
|
||||
FROM cs_market_items WHERE name ILIKE ${pattern} ESCAPE '\'
|
||||
ORDER BY name, id LIMIT ${limit} OFFSET ${offset}`);
|
||||
return json({ results: rows });
|
||||
return Response.json({ results: rows });
|
||||
}
|
||||
if (!/^[a-z][a-z0-9_]*$/.test(dimension))
|
||||
return json({ error: 'Invalid dimension' }, { status: 400 });
|
||||
return Response.json({ error: 'Invalid dimension' }, { status: 400 });
|
||||
const rows = await db.execute(sql`SELECT value, count(*)::int AS items
|
||||
FROM cs_item_dimensions WHERE key = ${dimension} AND value ILIKE ${pattern} ESCAPE '\'
|
||||
GROUP BY value ORDER BY value LIMIT ${limit} OFFSET ${offset}`);
|
||||
return json({ results: rows });
|
||||
return Response.json({ results: rows });
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { db } from '$lib/server/db';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
const maxSeries = 1000;
|
||||
@@ -24,13 +23,13 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
ids.some((id) => !Number.isSafeInteger(id) || id <= 0) ||
|
||||
values.some((value) => typeof value !== 'string' || value.length > 200)
|
||||
)
|
||||
return json({ error: 'Invalid selection' }, { status: 400 });
|
||||
return Response.json({ error: 'Invalid selection' }, { status: 400 });
|
||||
if (dimension === 'item' ? !ids.length : !values.length)
|
||||
return json({ series: [], bucketSeconds: 0 });
|
||||
return Response.json({ series: [], bucketSeconds: 0 });
|
||||
const to = new Date(typeof body.to === 'string' ? body.to : Date.now());
|
||||
const from = new Date(typeof body.from === 'string' ? body.from : to.getTime() - 365 * 86400_000);
|
||||
if (Number.isNaN(+from) || Number.isNaN(+to) || from >= to || +to - +from > maxDays * 86400_000)
|
||||
return json({ error: 'Invalid date range' }, { status: 400 });
|
||||
return Response.json({ error: 'Invalid date range' }, { status: 400 });
|
||||
// Aim for at most 240 buckets per series. Raw observations remain untouched.
|
||||
const seconds = Math.max(60, Math.ceil((+to - +from) / 1000 / 240 / 60) * 60);
|
||||
const selection =
|
||||
@@ -64,5 +63,5 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
count(*)::int AS items, sum(volume)::bigint AS volume,
|
||||
min(min_cents)::int AS min_cents, max(max_cents)::int AS max_cents
|
||||
FROM per_item GROUP BY 1, 2 ORDER BY 1, 2`);
|
||||
return json({ bucketSeconds: seconds, currency, source, kind, points: rows });
|
||||
return Response.json({ bucketSeconds: seconds, currency, source, kind, points: rows });
|
||||
};
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { resolve } from '$app/paths';
|
||||
import { SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import Check from 'lucide-svelte/icons/check';
|
||||
import ExternalLink from 'lucide-svelte/icons/external-link';
|
||||
import { horses, race, type Horse } from '$lib/derby/data';
|
||||
import { scenarios } from '$lib/derby/practice';
|
||||
import { races } from '$lib/derby/races';
|
||||
import { horses, race, type Horse } from '#lib/derby/data.js';
|
||||
import { scenarios } from '#lib/derby/practice.js';
|
||||
import { races } from '#lib/derby/races.js';
|
||||
import {
|
||||
betValue,
|
||||
defaultWeights,
|
||||
@@ -20,7 +20,7 @@
|
||||
type FeatureKey,
|
||||
type Prediction,
|
||||
type Weights
|
||||
} from '$lib/derby/model';
|
||||
} from '#lib/derby/model.js';
|
||||
|
||||
type Saved = {
|
||||
w: Weights;
|
||||
@@ -451,7 +451,7 @@
|
||||
><b>{r.number}</b> {r.short} <span>{r.time}</span></a
|
||||
>
|
||||
{/each}
|
||||
<a href={resolve('/projects/derby')} class="active"><b>6</b> Derby R <span>17:40</span></a>
|
||||
<a href={resolve('projects/derby')} class="active"><b>6</b> Derby R <span>17:40</span></a>
|
||||
</nav>
|
||||
|
||||
{#if scenario}
|
||||
@@ -510,7 +510,7 @@
|
||||
<div>
|
||||
<!-- The hash is appended to the resolved Derby route. -->
|
||||
<!-- eslint-disable svelte/no-navigation-without-resolve -->
|
||||
<a href={`${resolve('/projects/derby')}${st.href}`}>{st.title}</a>
|
||||
<a href={`${resolve('projects/derby')}${st.href}`}>{st.title}</a>
|
||||
<p>{st.detail}</p>
|
||||
{#if i === 0}
|
||||
<label class="step-check"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { untrack } from 'svelte';
|
||||
import { resolve } from '$app/paths';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import ExternalLink from 'lucide-svelte/icons/external-link';
|
||||
import { races, type Runner } from '$lib/derby/races';
|
||||
import { betValue, fairOdds, formatTime, placings } from '$lib/derby/model';
|
||||
import { races, type Runner } from '#lib/derby/races.js';
|
||||
import { betValue, fairOdds, formatTime, placings } from '#lib/derby/model.js';
|
||||
import {
|
||||
defaultsFor,
|
||||
gFeatureKeys,
|
||||
@@ -13,7 +13,7 @@
|
||||
type GFeatureKey,
|
||||
type GPrediction,
|
||||
type GWeights
|
||||
} from '$lib/derby/generic';
|
||||
} from '#lib/derby/generic.js';
|
||||
|
||||
let { data } = $props();
|
||||
const race = $derived(data.race);
|
||||
@@ -282,7 +282,7 @@
|
||||
<div class="shell">
|
||||
<div class="inner">
|
||||
<nav class="topbar">
|
||||
<a class="brand" href={resolve('/projects/derby')}><span class="mark"></span>DERBY LENS</a>
|
||||
<a class="brand" href={resolve('projects/derby')}><span class="mark"></span>DERBY LENS</a>
|
||||
<div class="topbar-right">
|
||||
<Button variant={help ? 'secondary' : 'outline'} size="sm" onclick={() => (help = !help)}
|
||||
>Uitleg {help ? 'aan' : 'uit'}</Button
|
||||
@@ -298,7 +298,7 @@
|
||||
class:active={r.slug === race.slug}><b>{r.number}</b> {r.short} <span>{r.time}</span></a
|
||||
>
|
||||
{/each}
|
||||
<a href={resolve('/projects/derby')}><b>6</b> Derby R <span>17:40</span></a>
|
||||
<a href={resolve('projects/derby')}><b>6</b> Derby R <span>17:40</span></a>
|
||||
</nav>
|
||||
|
||||
<section class="hero">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { raceBySlug, races } from '$lib/derby/races';
|
||||
import { raceBySlug, races } from '#lib/derby/races.js';
|
||||
import type { EntryGenerator, PageLoad } from './$types';
|
||||
|
||||
export const entries: EntryGenerator = () => races.map((r) => ({ race: r.slug }));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { asc, eq } from 'drizzle-orm';
|
||||
import { db } from '$lib/server/db';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import {
|
||||
trackerGames,
|
||||
trackerItems,
|
||||
@@ -9,12 +9,12 @@ import {
|
||||
trackerChallenges,
|
||||
trackerChallengeProgress,
|
||||
osuEntries
|
||||
} from '$lib/server/db/schema';
|
||||
import { loadPokemonProgress } from '$lib/server/tracker-progress';
|
||||
import { challengeProgress, combinedProgress, sumProgress } from '$lib/game-tracker/progress';
|
||||
import { gameRanksProgress, hasReachedMaxLevel } from '$lib/game-tracker/rank-progress';
|
||||
import { summarizeGame } from '$lib/game-tracker/mastery';
|
||||
import { chronologicalPokemon, pokemonRelease } from '$lib/game-tracker/game-art';
|
||||
} from '#lib/server/db/schema/index.js';
|
||||
import { loadPokemonProgress } from '#lib/server/tracker-progress.js';
|
||||
import { challengeProgress, combinedProgress, sumProgress } from '#lib/game-tracker/progress.js';
|
||||
import { gameRanksProgress, hasReachedMaxLevel } from '#lib/game-tracker/rank-progress.js';
|
||||
import { summarizeGame } from '#lib/game-tracker/mastery.js';
|
||||
import { chronologicalPokemon, pokemonRelease } from '#lib/game-tracker/game-art.js';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
async function pokemonSummary(userId: string | undefined) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import GameLogo from '$lib/game-tracker/GameLogo.svelte';
|
||||
import ProgressMeter from '$lib/game-tracker/ProgressMeter.svelte';
|
||||
import GameLogo from '#lib/game-tracker/GameLogo.svelte';
|
||||
import ProgressMeter from '#lib/game-tracker/ProgressMeter.svelte';
|
||||
import type { PageProps } from './$types';
|
||||
let { data }: PageProps = $props();
|
||||
</script>
|
||||
@@ -14,10 +14,10 @@
|
||||
</div>
|
||||
{#if !data.user}<a
|
||||
class="text-sm text-muted-foreground underline"
|
||||
href={resolve('/login?returnTo=/projects/game-tracker')}>Sign in to save progress</a
|
||||
href={resolve('login?returnTo=/projects/game-tracker')}>Sign in to save progress</a
|
||||
>{:else}<a
|
||||
class="text-xs text-muted-foreground underline"
|
||||
href={resolve('/projects/game-tracker/credits')}>Artwork credits</a
|
||||
href={resolve('projects/game-tracker/credits')}>Artwork credits</a
|
||||
>{/if}
|
||||
</div>
|
||||
<div
|
||||
@@ -55,7 +55,7 @@
|
||||
aria-labelledby="pokemon-series"
|
||||
>
|
||||
<a
|
||||
href={resolve('/projects/game-tracker/pokemon')}
|
||||
href={resolve('projects/game-tracker/pokemon')}
|
||||
class="series-heading mb-3 flex items-center gap-3 rounded-xl bg-amber-500/10 p-3 transition-colors hover:bg-amber-500/20"
|
||||
>
|
||||
<GameLogo slug="pokemon" name="Pokémon" class="h-12 w-24" />
|
||||
@@ -87,7 +87,7 @@
|
||||
{#each data.pokemon.games as game (game.id)}
|
||||
<a
|
||||
class="game-row flex min-h-0 min-w-0 items-center gap-2 rounded-lg border px-2 py-1.5 transition-colors hover:border-amber-500/50 hover:bg-amber-500/5"
|
||||
href={resolve(`/projects/game-tracker/pokemon?game=${game.slug ?? game.id}`)}
|
||||
href={resolve(`projects/game-tracker/pokemon?game=${game.slug ?? game.id}`)}
|
||||
title={`${game.name} · ${game.releaseYear}`}
|
||||
>
|
||||
<GameLogo
|
||||
@@ -112,7 +112,7 @@
|
||||
</div>
|
||||
{:else}<a
|
||||
class="rounded-xl border border-dashed p-5 text-sm text-muted-foreground hover:text-foreground"
|
||||
href={resolve('/projects/game-tracker/pokemon')}
|
||||
href={resolve('projects/game-tracker/pokemon')}
|
||||
>Choose your games in “set rules” to build your collection here.</a
|
||||
>{/if}
|
||||
</section>
|
||||
@@ -135,7 +135,7 @@
|
||||
<div class="cod-rows grid min-h-0 gap-1.5">
|
||||
{#each data.callOfDuty as game (game.slug)}
|
||||
<a
|
||||
href={resolve(`/projects/game-tracker/call-of-duty/${game.slug}`)}
|
||||
href={resolve(`projects/game-tracker/call-of-duty/${game.slug}`)}
|
||||
class="game-row flex min-h-0 items-center gap-3 rounded-lg border p-2 transition-colors hover:border-primary/40 hover:bg-muted/50"
|
||||
>
|
||||
<GameLogo slug={`cod/${game.slug}`} name={game.name} class="h-11 w-24" />
|
||||
@@ -169,7 +169,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href={resolve('/projects/game-tracker/osu')}
|
||||
href={resolve('projects/game-tracker/osu')}
|
||||
class="rounded-xl border p-4 transition-colors hover:border-pink-500/50 hover:bg-pink-500/5"
|
||||
><div class="flex items-center gap-3">
|
||||
<GameLogo slug="osu" name="osu!" class="h-14 w-14" />
|
||||
|
||||
@@ -3,7 +3,7 @@ import achievementArt from '../../../../../../data/cod-achievement-art.json';
|
||||
const achievementImages: Record<string, string> = achievementArt;
|
||||
import { and, asc, eq, sql } from 'drizzle-orm';
|
||||
import type { AnyPgColumn } from 'drizzle-orm/pg-core';
|
||||
import { db } from '$lib/server/db';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import {
|
||||
trackerGames,
|
||||
trackerItems,
|
||||
@@ -12,11 +12,15 @@ import {
|
||||
trackerWeaponRanks,
|
||||
trackerChallenges,
|
||||
trackerChallengeProgress
|
||||
} from '$lib/server/db/schema';
|
||||
import { boundedInteger } from '$lib/game-tracker/journal';
|
||||
import { appliesTo } from '$lib/game-tracker/mastery';
|
||||
import { playerRankLimits, weaponRankLimits, type RankLimits } from '$lib/game-tracker/rank-limits';
|
||||
import { maxUncappedLevel, stepRank } from '$lib/game-tracker/rank-progress';
|
||||
} from '#lib/server/db/schema/index.js';
|
||||
import { boundedInteger } from '#lib/game-tracker/journal.js';
|
||||
import { appliesTo } from '#lib/game-tracker/mastery.js';
|
||||
import {
|
||||
playerRankLimits,
|
||||
weaponRankLimits,
|
||||
type RankLimits
|
||||
} from '#lib/game-tracker/rank-limits.js';
|
||||
import { maxUncappedLevel, stepRank } from '#lib/game-tracker/rank-progress.js';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
async function findGame(slug: string) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import RankStepper from '$lib/game-tracker/RankStepper.svelte';
|
||||
import WeaponArtwork from '$lib/game-tracker/WeaponArtwork.svelte';
|
||||
import * as Dialog from '$lib/components/ui/dialog/index.js';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox/index.js';
|
||||
import { NativeSelect } from '$lib/components/ui/native-select/index.js';
|
||||
import RankStepper from '#lib/game-tracker/RankStepper.svelte';
|
||||
import WeaponArtwork from '#lib/game-tracker/WeaponArtwork.svelte';
|
||||
import * as Dialog from '#lib/components/ui/dialog/index.js';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import { Input } from '#lib/components/ui/input/index.js';
|
||||
import { Checkbox } from '#lib/components/ui/checkbox/index.js';
|
||||
import { NativeSelect } from '#lib/components/ui/native-select/index.js';
|
||||
import Crosshair from 'lucide-svelte/icons/crosshair';
|
||||
import Trophy from 'lucide-svelte/icons/trophy';
|
||||
import Target from 'lucide-svelte/icons/target';
|
||||
@@ -13,19 +13,19 @@
|
||||
import ChevronDown from 'lucide-svelte/icons/chevron-down';
|
||||
import EyeOff from 'lucide-svelte/icons/eye-off';
|
||||
import ChartNoAxesCombined from 'lucide-svelte/icons/chart-no-axes-combined';
|
||||
import PlayerRankBadge from '$lib/game-tracker/PlayerRankBadge.svelte';
|
||||
import PlayerRankBadge from '#lib/game-tracker/PlayerRankBadge.svelte';
|
||||
import {
|
||||
gameRanksProgress,
|
||||
rankProgress as countRank,
|
||||
weaponLevelsProgress,
|
||||
weaponFinished,
|
||||
hasReachedMaxLevel
|
||||
} from '$lib/game-tracker/rank-progress';
|
||||
import { fraction } from '$lib/game-tracker/progress';
|
||||
import GameLogo from '$lib/game-tracker/GameLogo.svelte';
|
||||
import ProgressMeter from '$lib/game-tracker/ProgressMeter.svelte';
|
||||
import { combinedProgress, sumProgress, challengeProgress } from '$lib/game-tracker/progress';
|
||||
import { playerRankLimits, weaponRankLimits } from '$lib/game-tracker/rank-limits';
|
||||
} from '#lib/game-tracker/rank-progress.js';
|
||||
import { fraction } from '#lib/game-tracker/progress.js';
|
||||
import GameLogo from '#lib/game-tracker/GameLogo.svelte';
|
||||
import ProgressMeter from '#lib/game-tracker/ProgressMeter.svelte';
|
||||
import { combinedProgress, sumProgress, challengeProgress } from '#lib/game-tracker/progress.js';
|
||||
import { playerRankLimits, weaponRankLimits } from '#lib/game-tracker/rank-limits.js';
|
||||
let rankOpen = $state(false);
|
||||
let rankWeapon = $state<number | null>(null);
|
||||
let challengeSearch = $state('');
|
||||
@@ -40,8 +40,8 @@
|
||||
import { resolve } from '$app/paths';
|
||||
import { deserialize, enhance } from '$app/forms';
|
||||
import { SvelteSet, SvelteMap } from 'svelte/reactivity';
|
||||
import * as Tabs from '$lib/components/ui/tabs/index.js';
|
||||
import { appliesTo, summarizeGame } from '$lib/game-tracker/mastery';
|
||||
import * as Tabs from '#lib/components/ui/tabs/index.js';
|
||||
import { appliesTo, summarizeGame } from '#lib/game-tracker/mastery.js';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data, form }: PageProps = $props();
|
||||
@@ -239,7 +239,7 @@
|
||||
|
||||
<div class="cod-companion">
|
||||
<div class="companion-header">
|
||||
<a href={resolve('/projects/game-tracker')} class="back-link">← game tracker</a>
|
||||
<a href={resolve('projects/game-tracker')} class="back-link">← game tracker</a>
|
||||
<GameLogo
|
||||
slug={`cod/${data.game.slug}`}
|
||||
name={data.game.shortName}
|
||||
@@ -268,7 +268,7 @@
|
||||
onupdate={(r) => (playerRank = { level: r.level, prestige: r.prestige })}
|
||||
/>{:else}<Button
|
||||
variant="outline"
|
||||
href={resolve(`/login?returnTo=/projects/game-tracker/call-of-duty/${data.game.slug}`)}
|
||||
href={resolve(`login?returnTo=/projects/game-tracker/call-of-duty/${data.game.slug}`)}
|
||||
>Sign in to save</Button
|
||||
>{/if}
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<!-- Download manifest links point to reviewed external image sources. -->
|
||||
<!-- eslint-disable svelte/no-navigation-without-resolve -->
|
||||
<div class="mx-auto max-w-4xl px-4 py-10 sm:px-6">
|
||||
<a href={resolve('/projects/game-tracker')} class="text-sm text-muted-foreground hover:underline"
|
||||
<a href={resolve('projects/game-tracker')} class="text-sm text-muted-foreground hover:underline"
|
||||
>← Game tracker</a
|
||||
>
|
||||
<h1 class="mt-6 text-3xl font-semibold">Artwork credits</h1>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { and, desc, eq } from 'drizzle-orm';
|
||||
import { fail } from '@sveltejs/kit';
|
||||
import { db } from '$lib/server/db';
|
||||
import { osuEntries } from '$lib/server/db/schema';
|
||||
import { allowedOsuUrl, boundedInteger, validDate } from '$lib/game-tracker/journal';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { osuEntries } from '#lib/server/db/schema/index.js';
|
||||
import { allowedOsuUrl, boundedInteger, validDate } from '#lib/game-tracker/journal.js';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ locals }) => ({
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
});
|
||||
import { resolve } from '$app/paths';
|
||||
import { enhance } from '$app/forms';
|
||||
import * as Dialog from '$lib/components/ui/dialog/index.js';
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog/index.js';
|
||||
import * as Tabs from '$lib/components/ui/tabs/index.js';
|
||||
import * as Table from '$lib/components/ui/table/index.js';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import { Textarea } from '$lib/components/ui/textarea/index.js';
|
||||
import { NativeSelect } from '$lib/components/ui/native-select/index.js';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox/index.js';
|
||||
import { Badge } from '$lib/components/ui/badge/index.js';
|
||||
import GameLogo from '$lib/game-tracker/GameLogo.svelte';
|
||||
import ProgressMeter from '$lib/game-tracker/ProgressMeter.svelte';
|
||||
import * as Dialog from '#lib/components/ui/dialog/index.js';
|
||||
import * as AlertDialog from '#lib/components/ui/alert-dialog/index.js';
|
||||
import * as Tabs from '#lib/components/ui/tabs/index.js';
|
||||
import * as Table from '#lib/components/ui/table/index.js';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import { Input } from '#lib/components/ui/input/index.js';
|
||||
import { Textarea } from '#lib/components/ui/textarea/index.js';
|
||||
import { NativeSelect } from '#lib/components/ui/native-select/index.js';
|
||||
import { Checkbox } from '#lib/components/ui/checkbox/index.js';
|
||||
import { Badge } from '#lib/components/ui/badge/index.js';
|
||||
import GameLogo from '#lib/game-tracker/GameLogo.svelte';
|
||||
import ProgressMeter from '#lib/game-tracker/ProgressMeter.svelte';
|
||||
import type { PageProps } from './$types';
|
||||
let { data, form }: PageProps = $props();
|
||||
type Entry = (typeof data.entries)[number];
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
<div class="mx-auto max-w-6xl px-3 py-4 sm:px-5">
|
||||
<a
|
||||
href={resolve('/projects/game-tracker')}
|
||||
href={resolve('projects/game-tracker')}
|
||||
class="text-sm text-muted-foreground hover:text-foreground">← game tracker</a
|
||||
>
|
||||
<div class="mt-3 flex flex-wrap items-center gap-3">
|
||||
@@ -95,7 +95,7 @@
|
||||
>
|
||||
</div>
|
||||
{#if !data.user}<p class="mt-6">
|
||||
<a class="underline" href={resolve('/login?returnTo=/projects/game-tracker/osu')}>Sign in</a> to
|
||||
<a class="underline" href={resolve('login?returnTo=/projects/game-tracker/osu')}>Sign in</a> to
|
||||
start your training journal.
|
||||
</p>{/if}
|
||||
{#if form?.message && !open && !deleteOpen}<p class="mt-4 text-destructive" role="alert">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { pokemonJournalActions, catchesFor } from '$lib/server/pokemon-journal';
|
||||
import { loadPokemonProgress } from '$lib/server/tracker-progress';
|
||||
import { pokemonJournalActions, catchesFor } from '#lib/server/pokemon-journal.js';
|
||||
import { loadPokemonProgress } from '#lib/server/tracker-progress.js';
|
||||
import {
|
||||
pokemonCatches,
|
||||
pokemonDexNumbers,
|
||||
@@ -9,9 +9,9 @@ import {
|
||||
pokemonLocations,
|
||||
pokemonEncounters,
|
||||
pokemonSeen
|
||||
} from '$lib/server/db/schema';
|
||||
} from '#lib/server/db/schema/index.js';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { db } from '$lib/server/db';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import {
|
||||
games,
|
||||
pokemon as pokemonTable,
|
||||
@@ -19,11 +19,15 @@ import {
|
||||
userEntries,
|
||||
userGames,
|
||||
catchEmAllSettings
|
||||
} from '$lib/server/db/schema';
|
||||
} from '#lib/server/db/schema/index.js';
|
||||
import { and, asc, eq, inArray } from 'drizzle-orm';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
import { availabilityValues, obtainableAvailability, type Availability } from '$lib/availability';
|
||||
import { requireAdmin } from '$lib/server/authorization';
|
||||
import {
|
||||
availabilityValues,
|
||||
obtainableAvailability,
|
||||
type Availability
|
||||
} from '#lib/availability.js';
|
||||
import { requireAdmin } from '#lib/server/authorization.js';
|
||||
import { fail } from '@sveltejs/kit';
|
||||
export type WireEntry = [id: number, gameId: number, availability: number, note?: string];
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { enhance } from '$app/forms';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import * as Dialog from '$lib/components/ui/dialog/index.js';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox/index.js';
|
||||
import { chronologicalPokemon } from '$lib/game-tracker/game-art';
|
||||
import GameLogo from '$lib/game-tracker/GameLogo.svelte';
|
||||
import PokemonJournal from '$lib/game-tracker/PokemonJournal.svelte';
|
||||
import * as Dialog from '#lib/components/ui/dialog/index.js';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import { Checkbox } from '#lib/components/ui/checkbox/index.js';
|
||||
import { chronologicalPokemon } from '#lib/game-tracker/game-art.js';
|
||||
import GameLogo from '#lib/game-tracker/GameLogo.svelte';
|
||||
import PokemonJournal from '#lib/game-tracker/PokemonJournal.svelte';
|
||||
import type { PageProps } from './$types';
|
||||
let { data, form }: PageProps = $props();
|
||||
let rulesOpen = $state(false);
|
||||
@@ -49,7 +49,7 @@
|
||||
/>{/if}
|
||||
<div>
|
||||
<a
|
||||
href={resolve('/projects/game-tracker')}
|
||||
href={resolve('projects/game-tracker')}
|
||||
class="text-xs text-muted-foreground hover:underline">← Game tracker</a
|
||||
>
|
||||
<h1 class="text-xl font-semibold">
|
||||
@@ -79,9 +79,8 @@
|
||||
title={game.name}
|
||||
class={`game-cover-button h-12 min-w-0 rounded-md border p-1 ${data.activeGame?.id === game.id ? 'border-amber-500 bg-amber-500/15 ring-1 ring-amber-500' : 'border-transparent opacity-65 hover:opacity-100'}`}
|
||||
onclick={() =>
|
||||
goto(resolve(`/projects/game-tracker/pokemon?game=${game.slug ?? game.id}`), {
|
||||
noScroll: true,
|
||||
keepFocus: true
|
||||
goto(resolve(`projects/game-tracker/pokemon?game=${game.slug ?? game.id}`), {
|
||||
reset: false
|
||||
})}
|
||||
>
|
||||
<GameLogo slug={`pokemon/${game.slug}`} name={game.name} class="h-10 w-full" />
|
||||
@@ -99,7 +98,7 @@
|
||||
</div>
|
||||
|
||||
{#if !data.user}<p class="mt-4 text-sm">
|
||||
<a href={resolve('/login?returnTo=/projects/game-tracker/pokemon')} class="underline"
|
||||
<a href={resolve('login?returnTo=/projects/game-tracker/pokemon')} class="underline"
|
||||
>Sign in</a
|
||||
> to save catches, teams and adventures.
|
||||
</p>{/if}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { error, json } from '@sveltejs/kit';
|
||||
import { pokemonDetail } from '$lib/server/pokemon-detail';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { pokemonDetail } from '#lib/server/pokemon-detail.js';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const GET: RequestHandler = async ({ params, url }) => {
|
||||
@@ -7,5 +7,5 @@ export const GET: RequestHandler = async ({ params, url }) => {
|
||||
const gameId = Number.isSafeInteger(game) && game > 0 && game <= 2147483647 ? game : null;
|
||||
const number = Number(params.number);
|
||||
if (number > 100000) error(404, 'Unknown Pokémon');
|
||||
return json(await pokemonDetail(number, gameId));
|
||||
return Response.json(await pokemonDetail(number, gameId));
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { and, asc, eq } from 'drizzle-orm';
|
||||
import { db } from '$lib/server/db';
|
||||
import { pokemon, pokemonEncounters } from '$lib/server/db/schema';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { pokemon, pokemonEncounters } from '#lib/server/db/schema/index.js';
|
||||
|
||||
export async function GET({ url }) {
|
||||
const gameId = Number(url.searchParams.get('game'));
|
||||
@@ -28,5 +28,5 @@ export async function GET({ url }) {
|
||||
.innerJoin(pokemon, eq(pokemon.id, pokemonEncounters.pokemonId))
|
||||
.where(and(eq(pokemonEncounters.gameId, gameId), eq(pokemonEncounters.locationId, locationId)))
|
||||
.orderBy(asc(pokemon.number), asc(pokemonEncounters.method), asc(pokemonEncounters.minLevel));
|
||||
return json(rows, { headers: { 'Cache-Control': 'public, max-age=3600' } });
|
||||
return Response.json(rows, { headers: { 'Cache-Control': 'public, max-age=3600' } });
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { asc, countDistinct, eq, sql } from 'drizzle-orm';
|
||||
import { db } from '$lib/server/db';
|
||||
import { pokemonEncounters, pokemonLocations } from '$lib/server/db/schema';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { pokemonEncounters, pokemonLocations } from '#lib/server/db/schema/index.js';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async () => ({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { NativeSelect } from '$lib/components/ui/native-select/index.js';
|
||||
import { NativeSelect } from '#lib/components/ui/native-select/index.js';
|
||||
import { resolve } from '$app/paths';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import { Input } from '#lib/components/ui/input/index.js';
|
||||
import * as Card from '#lib/components/ui/card/index.js';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<div class="mx-auto max-w-6xl px-4 py-10 sm:px-6 sm:py-14">
|
||||
<a
|
||||
href={resolve('/projects/game-tracker/pokemon')}
|
||||
href={resolve('projects/game-tracker/pokemon')}
|
||||
class="text-[var(--site-muted)] hover:text-[var(--site-text)]">← catchemall</a
|
||||
>
|
||||
<h1 class="mt-3 text-4xl font-semibold tracking-tight sm:text-5xl">locations</h1>
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { error, fail } from '@sveltejs/kit';
|
||||
import { asc, eq } from 'drizzle-orm';
|
||||
import { db } from '$lib/server/db';
|
||||
import { games, pokemon, pokemonEncounters, pokemonLocations } from '$lib/server/db/schema';
|
||||
import { requireAdmin } from '$lib/server/authorization';
|
||||
import { deleteObject, locationImageUrl, uploadImage } from '$lib/server/r2';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import {
|
||||
games,
|
||||
pokemon,
|
||||
pokemonEncounters,
|
||||
pokemonLocations
|
||||
} from '#lib/server/db/schema/index.js';
|
||||
import { requireAdmin } from '#lib/server/authorization.js';
|
||||
import { deleteObject, locationImageUrl, uploadImage } from '#lib/server/r2.js';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
const PICTURE_TYPES = ['image/jpeg', 'image/png', 'image/webp'];
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { pokemonImage } from '$lib/game-tracker/game-art';
|
||||
import { NativeSelect } from '$lib/components/ui/native-select/index.js';
|
||||
import { pokemonImage } from '#lib/game-tracker/game-art.js';
|
||||
import { NativeSelect } from '#lib/components/ui/native-select/index.js';
|
||||
import { resolve } from '$app/paths';
|
||||
import { enhance } from '$app/forms';
|
||||
import { page } from '$app/state';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import { Textarea } from '$lib/components/ui/textarea/index.js';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import { Input } from '#lib/components/ui/input/index.js';
|
||||
import { Textarea } from '#lib/components/ui/textarea/index.js';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data, form }: PageProps = $props();
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
<div class="mx-auto max-w-6xl px-4 py-10 sm:px-6 sm:py-14">
|
||||
<a
|
||||
href={resolve('/projects/game-tracker/pokemon/locations')}
|
||||
href={resolve('projects/game-tracker/pokemon/locations')}
|
||||
class="text-[var(--site-muted)] hover:text-[var(--site-text)]">← locations</a
|
||||
>
|
||||
<h1 class="mt-3 text-4xl font-semibold tracking-tight sm:text-5xl">{data.location.name}</h1>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import { and, asc, desc, eq, gte, ilike, isNull, lte, or, sql, type SQL } from 'drizzle-orm';
|
||||
import { db } from '$lib/server/db';
|
||||
import { houseAlertRules, houseListings, houseWorkerRuns } from '$lib/server/db/schema';
|
||||
import { requireAdmin } from '$lib/server/authorization';
|
||||
import { sendAlert } from '$lib/server/house-tracker';
|
||||
import { normalizeCity } from '$lib/house-tracker/alerts';
|
||||
import { listingStatuses, propertyTypes } from '$lib/house-tracker/types';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { houseAlertRules, houseListings, houseWorkerRuns } from '#lib/server/db/schema/index.js';
|
||||
import { requireAdmin } from '#lib/server/authorization.js';
|
||||
import { sendAlert } from '#lib/server/house-tracker.js';
|
||||
import { normalizeCity } from '#lib/house-tracker/alerts.js';
|
||||
import { listingStatuses, propertyTypes } from '#lib/house-tracker/types.js';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
const int = (value: string | null) => {
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
import { onMount } from 'svelte';
|
||||
import ExternalLink from 'lucide-svelte/icons/external-link';
|
||||
import BellRing from 'lucide-svelte/icons/bell-ring';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import { Badge } from '$lib/components/ui/badge/index.js';
|
||||
import { NativeSelect } from '$lib/components/ui/native-select/index.js';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import ListingMap from '$lib/house-tracker/ListingMap.svelte';
|
||||
import { relativeTime } from '$lib/relative-time';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import { Input } from '#lib/components/ui/input/index.js';
|
||||
import { Badge } from '#lib/components/ui/badge/index.js';
|
||||
import { NativeSelect } from '#lib/components/ui/native-select/index.js';
|
||||
import * as Card from '#lib/components/ui/card/index.js';
|
||||
import ListingMap from '#lib/house-tracker/ListingMap.svelte';
|
||||
import { relativeTime } from '#lib/relative-time.js';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data, form }: PageProps = $props();
|
||||
@@ -222,7 +222,7 @@
|
||||
action="?/status"
|
||||
use:enhance={() =>
|
||||
async ({ update }) =>
|
||||
update({ reset: false, invalidateAll: false })}
|
||||
update({ reset: false, refreshAll: false })}
|
||||
class="shrink-0"
|
||||
>
|
||||
<input type="hidden" name="id" value={listing.id} />
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { detailsRequestSchema } from '$lib/house-tracker/types';
|
||||
import { requireWorker, saveDetails } from '$lib/server/house-tracker';
|
||||
import { detailsRequestSchema } from '#lib/house-tracker/types.js';
|
||||
import { requireWorker, saveDetails } from '#lib/server/house-tracker.js';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
requireWorker(request);
|
||||
const parsed = detailsRequestSchema.safeParse(await request.json().catch(() => null));
|
||||
if (!parsed.success) return json({ error: parsed.error.issues.slice(0, 5) }, { status: 400 });
|
||||
return json(await saveDetails(parsed.data));
|
||||
if (!parsed.success)
|
||||
return Response.json({ error: parsed.error.issues.slice(0, 5) }, { status: 400 });
|
||||
return Response.json(await saveDetails(parsed.data));
|
||||
};
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { requireWorker } from '$lib/server/house-tracker';
|
||||
import { requireWorker } from '#lib/server/house-tracker.js';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
// Authenticated connectivity check; no database writes or listing-site requests.
|
||||
export const GET: RequestHandler = async ({ request }) => {
|
||||
requireWorker(request);
|
||||
return json({ ok: true }, { headers: { 'cache-control': 'no-store' } });
|
||||
return Response.json({ ok: true }, { headers: { 'cache-control': 'no-store' } });
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { reportRequestSchema } from '$lib/house-tracker/types';
|
||||
import { recordReport, requireWorker } from '$lib/server/house-tracker';
|
||||
import { reportRequestSchema } from '#lib/house-tracker/types.js';
|
||||
import { recordReport, requireWorker } from '#lib/server/house-tracker.js';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
requireWorker(request);
|
||||
const parsed = reportRequestSchema.safeParse(await request.json().catch(() => null));
|
||||
if (!parsed.success) return json({ error: parsed.error.issues.slice(0, 5) }, { status: 400 });
|
||||
if (!parsed.success)
|
||||
return Response.json({ error: parsed.error.issues.slice(0, 5) }, { status: 400 });
|
||||
await recordReport(parsed.data);
|
||||
return json({ ok: true });
|
||||
return Response.json({ ok: true });
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { syncRequestSchema } from '$lib/house-tracker/types';
|
||||
import { requireWorker, syncCards } from '$lib/server/house-tracker';
|
||||
import { syncRequestSchema } from '#lib/house-tracker/types.js';
|
||||
import { requireWorker, syncCards } from '#lib/server/house-tracker.js';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
requireWorker(request);
|
||||
const parsed = syncRequestSchema.safeParse(await request.json().catch(() => null));
|
||||
if (!parsed.success) return json({ error: parsed.error.issues.slice(0, 5) }, { status: 400 });
|
||||
return json(await syncCards(parsed.data));
|
||||
if (!parsed.success)
|
||||
return Response.json({ error: parsed.error.issues.slice(0, 5) }, { status: 400 });
|
||||
return Response.json(await syncCards(parsed.data));
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import './language-learner.css';
|
||||
import { onMount } from 'svelte';
|
||||
import { progress } from '$lib/language-learner/progress.svelte';
|
||||
import { progress } from '#lib/language-learner/progress.svelte.js';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { allEntries, allLessons } from '$lib/language-learner/course';
|
||||
import { COURSES, loadCourse } from '$lib/language-learner/courses';
|
||||
import { allEntries, allLessons } from '#lib/language-learner/course.js';
|
||||
import { COURSES, loadCourse } from '#lib/language-learner/courses/index.js';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
// Counted on the server so the overview doesn't download three full courses.
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script lang="ts">
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog/index.js';
|
||||
import * as AlertDialog from '#lib/components/ui/alert-dialog/index.js';
|
||||
let resetTarget = $state<{ code: LangCode; name: string } | null>(null);
|
||||
import { resolve } from '$app/paths';
|
||||
import ArrowRight from 'lucide-svelte/icons/arrow-right';
|
||||
import Flame from 'lucide-svelte/icons/flame';
|
||||
import ProgressBar from '$lib/components/ProgressBar.svelte';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import { Badge } from '$lib/components/ui/badge/index.js';
|
||||
import { COURSES } from '$lib/language-learner/courses';
|
||||
import { progress } from '$lib/language-learner/progress.svelte';
|
||||
import type { LangCode } from '$lib/language-learner/types';
|
||||
import ProgressBar from '#lib/components/ProgressBar.svelte';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import * as Card from '#lib/components/ui/card/index.js';
|
||||
import { Badge } from '#lib/components/ui/badge/index.js';
|
||||
import { COURSES } from '#lib/language-learner/courses/index.js';
|
||||
import { progress } from '#lib/language-learner/progress.svelte.js';
|
||||
import type { LangCode } from '#lib/language-learner/types.js';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { isLang, loadCourse } from '$lib/language-learner/courses';
|
||||
import { loadDrills } from '$lib/language-learner/drills';
|
||||
import { isLang, loadCourse } from '#lib/language-learner/courses/index.js';
|
||||
import { loadDrills } from '#lib/language-learner/drills/index.js';
|
||||
import type { LayoutLoad } from './$types';
|
||||
|
||||
export const load: LayoutLoad = async ({ params }) => {
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
|
||||
import Check from 'lucide-svelte/icons/check';
|
||||
import Flame from 'lucide-svelte/icons/flame';
|
||||
import ProgressBar from '$lib/components/ProgressBar.svelte';
|
||||
import { allLessons } from '$lib/language-learner/course';
|
||||
import { progress } from '$lib/language-learner/progress.svelte';
|
||||
import { dayKey } from '$lib/language-learner/srs';
|
||||
import { MODES } from '$lib/language-learner/modes';
|
||||
import ProgressBar from '#lib/components/ProgressBar.svelte';
|
||||
import { allLessons } from '#lib/language-learner/course.js';
|
||||
import { progress } from '#lib/language-learner/progress.svelte.js';
|
||||
import { dayKey } from '#lib/language-learner/srs.js';
|
||||
import { MODES } from '#lib/language-learner/modes.js';
|
||||
|
||||
let { data } = $props();
|
||||
const course = $derived(data.course);
|
||||
@@ -22,7 +22,7 @@
|
||||
</script>
|
||||
|
||||
<a
|
||||
href={resolve('/projects/language-learner')}
|
||||
href={resolve('projects/language-learner')}
|
||||
class="mb-6 inline-flex items-center gap-1 text-sm ll-muted hover:text-[var(--site-text)]"
|
||||
><ArrowLeft size={14} aria-hidden="true" /> language learner</a
|
||||
>
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
import { onMount } from 'svelte';
|
||||
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
|
||||
import ArrowRight from 'lucide-svelte/icons/arrow-right';
|
||||
import { lessonSession } from '$lib/language-learner/exercises';
|
||||
import { romanize } from '$lib/language-learner/hangul';
|
||||
import { progress } from '$lib/language-learner/progress.svelte';
|
||||
import { seeded } from '$lib/language-learner/random';
|
||||
import { hasVoice } from '$lib/language-learner/speech';
|
||||
import type { Exercise, SessionResult } from '$lib/language-learner/types';
|
||||
import Results from '$lib/language-learner/ui/Results.svelte';
|
||||
import Session from '$lib/language-learner/ui/Session.svelte';
|
||||
import Speak from '$lib/language-learner/ui/Speak.svelte';
|
||||
import { lessonSession } from '#lib/language-learner/exercises.js';
|
||||
import { romanize } from '#lib/language-learner/hangul.js';
|
||||
import { progress } from '#lib/language-learner/progress.svelte.js';
|
||||
import { seeded } from '#lib/language-learner/random.js';
|
||||
import { hasVoice } from '#lib/language-learner/speech.js';
|
||||
import type { Exercise, SessionResult } from '#lib/language-learner/types.js';
|
||||
import Results from '#lib/language-learner/ui/Results.svelte';
|
||||
import Session from '#lib/language-learner/ui/Session.svelte';
|
||||
import Speak from '#lib/language-learner/ui/Speak.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
const course = $derived(data.course);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { marked } from 'marked';
|
||||
import { allLessons } from '$lib/language-learner/course';
|
||||
import { allLessons } from '#lib/language-learner/course.js';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async ({ parent, params }) => {
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
import { resolve } from '$app/paths';
|
||||
import { onMount } from 'svelte';
|
||||
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
|
||||
import { mixed, reviews, scopeOf } from '$lib/language-learner/challenges';
|
||||
import { progress } from '$lib/language-learner/progress.svelte';
|
||||
import { seeded, type Rng } from '$lib/language-learner/random';
|
||||
import { hasVoice } from '$lib/language-learner/speech';
|
||||
import { dayKey } from '$lib/language-learner/srs';
|
||||
import type { Exercise, SessionResult } from '$lib/language-learner/types';
|
||||
import Results from '$lib/language-learner/ui/Results.svelte';
|
||||
import Session from '$lib/language-learner/ui/Session.svelte';
|
||||
import { mixed, reviews, scopeOf } from '#lib/language-learner/challenges.js';
|
||||
import { progress } from '#lib/language-learner/progress.svelte.js';
|
||||
import { seeded, type Rng } from '#lib/language-learner/random.js';
|
||||
import { hasVoice } from '#lib/language-learner/speech.js';
|
||||
import { dayKey } from '#lib/language-learner/srs.js';
|
||||
import type { Exercise, SessionResult } from '#lib/language-learner/types.js';
|
||||
import Results from '#lib/language-learner/ui/Results.svelte';
|
||||
import Session from '#lib/language-learner/ui/Session.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
const course = $derived(data.course);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { MODES } from '$lib/language-learner/modes';
|
||||
import { MODES } from '#lib/language-learner/modes.js';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export type Mode =
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
import { resolve } from '$app/paths';
|
||||
import { onMount } from 'svelte';
|
||||
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
|
||||
import { allLessons } from '$lib/language-learner/course';
|
||||
import { romanize } from '$lib/language-learner/hangul';
|
||||
import { progress } from '$lib/language-learner/progress.svelte';
|
||||
import { hasVoice } from '$lib/language-learner/speech';
|
||||
import { mastery, type Mastery } from '$lib/language-learner/srs';
|
||||
import { normalize } from '$lib/language-learner/text';
|
||||
import Speak from '$lib/language-learner/ui/Speak.svelte';
|
||||
import { allLessons } from '#lib/language-learner/course.js';
|
||||
import { romanize } from '#lib/language-learner/hangul.js';
|
||||
import { progress } from '#lib/language-learner/progress.svelte.js';
|
||||
import { hasVoice } from '#lib/language-learner/speech.js';
|
||||
import { mastery, type Mastery } from '#lib/language-learner/srs.js';
|
||||
import { normalize } from '#lib/language-learner/text.js';
|
||||
import Speak from '#lib/language-learner/ui/Speak.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
const course = $derived(data.course);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { db } from '$lib/server/db';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import {
|
||||
moneyAccounts,
|
||||
moneyTransactions,
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
moneyWishlist,
|
||||
moneyInsurances,
|
||||
moneyProjectionSettings
|
||||
} from '$lib/server/db/schema';
|
||||
import { requireAdmin } from '$lib/server/authorization';
|
||||
} from '#lib/server/db/schema/index.js';
|
||||
import { requireAdmin } from '#lib/server/authorization.js';
|
||||
import {
|
||||
type ImportedTransaction,
|
||||
cadence,
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
textField,
|
||||
utilityKind,
|
||||
validDate
|
||||
} from '$lib/server/money-tracker';
|
||||
} from '#lib/server/money-tracker.js';
|
||||
import { and, asc, desc, eq } from 'drizzle-orm';
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
onMount(() => {
|
||||
hydrated = true;
|
||||
});
|
||||
import { NativeSelect } from '$lib/components/ui/native-select/index.js';
|
||||
import { Textarea } from '$lib/components/ui/textarea/index.js';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox/index.js';
|
||||
import * as Dialog from '$lib/components/ui/dialog/index.js';
|
||||
import { NativeSelect } from '#lib/components/ui/native-select/index.js';
|
||||
import { Textarea } from '#lib/components/ui/textarea/index.js';
|
||||
import { Checkbox } from '#lib/components/ui/checkbox/index.js';
|
||||
import * as Dialog from '#lib/components/ui/dialog/index.js';
|
||||
import './money-tracker.css';
|
||||
import { enhance } from '$app/forms';
|
||||
import Plus from 'lucide-svelte/icons/plus';
|
||||
@@ -15,10 +15,10 @@
|
||||
import Trash2 from 'lucide-svelte/icons/trash-2';
|
||||
import Upload from 'lucide-svelte/icons/upload';
|
||||
import Download from 'lucide-svelte/icons/download';
|
||||
import MoneyChart from '$lib/money-tracker/MoneyChart.svelte';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import * as Tabs from '$lib/components/ui/tabs/index.js';
|
||||
import MoneyChart from '#lib/money-tracker/MoneyChart.svelte';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import { Input } from '#lib/components/ui/input/index.js';
|
||||
import * as Tabs from '#lib/components/ui/tabs/index.js';
|
||||
import {
|
||||
accountBalances,
|
||||
accountColor,
|
||||
@@ -27,7 +27,7 @@
|
||||
utilityEstimate,
|
||||
wealthProjection,
|
||||
wealthTimeline
|
||||
} from '$lib/money-tracker/charts';
|
||||
} from '#lib/money-tracker/charts.js';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data, form }: PageProps = $props();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { db } from '$lib/server/db';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
|
||||
export const load = async () => {
|
||||
return {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Badge } from '$lib/components/ui/badge/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import ProgressBar from '$lib/components/ProgressBar.svelte';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import { Badge } from '#lib/components/ui/badge/index.js';
|
||||
import { Input } from '#lib/components/ui/input/index.js';
|
||||
import * as Card from '#lib/components/ui/card/index.js';
|
||||
import ProgressBar from '#lib/components/ProgressBar.svelte';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
@@ -31,8 +31,8 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<Button href={resolve('/projects/progress/topics')} variant="outline">Topics</Button
|
||||
>{#if data.editable}<Button href={resolve('/projects/progress/create')}>Create task</Button
|
||||
<Button href={resolve('projects/progress/topics')} variant="outline">Topics</Button
|
||||
>{#if data.editable}<Button href={resolve('projects/progress/create')}>Create task</Button
|
||||
>{/if}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { isAdmin, requireAdmin } from '$lib/server/authorization';
|
||||
import { isAdmin, requireAdmin } from '#lib/server/authorization.js';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { tasks, tasksToTopics, topics } from '$lib/server/db/schema';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { tasks, tasksToTopics, topics } from '#lib/server/db/schema/index.js';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { NativeSelect } from '$lib/components/ui/native-select/index.js';
|
||||
import { NativeSelect } from '#lib/components/ui/native-select/index.js';
|
||||
import { resolve } from '$app/paths';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Badge } from '$lib/components/ui/badge/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import { Textarea } from '$lib/components/ui/textarea/index.js';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import ProgressBar from '$lib/components/ProgressBar.svelte';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import { Badge } from '#lib/components/ui/badge/index.js';
|
||||
import { Input } from '#lib/components/ui/input/index.js';
|
||||
import { Label } from '#lib/components/ui/label/index.js';
|
||||
import { Textarea } from '#lib/components/ui/textarea/index.js';
|
||||
import * as Card from '#lib/components/ui/card/index.js';
|
||||
import ProgressBar from '#lib/components/ProgressBar.svelte';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-4xl space-y-6 px-4 py-12 sm:px-6">
|
||||
<Button href={resolve('/projects/progress')} variant="ghost" size="sm">← All tasks</Button>
|
||||
<Button href={resolve('projects/progress')} variant="ghost" size="sm">← All tasks</Button>
|
||||
{#if data.task}
|
||||
<header>
|
||||
<h1 class="text-4xl font-semibold tracking-tight">{data.task.name}</h1>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { requireAdmin } from '$lib/server/authorization';
|
||||
import { requireAdmin } from '#lib/server/authorization.js';
|
||||
import type { Actions } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { tasks } from '$lib/server/db/schema';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { tasks } from '#lib/server/db/schema/index.js';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const actions: Actions = {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import { Input } from '#lib/components/ui/input/index.js';
|
||||
import { Label } from '#lib/components/ui/label/index.js';
|
||||
import * as Card from '#lib/components/ui/card/index.js';
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-xl space-y-6 px-4 py-12 sm:px-6">
|
||||
<Button href={resolve('/projects/progress')} variant="ghost" size="sm">← All tasks</Button>
|
||||
<Button href={resolve('projects/progress')} variant="ghost" size="sm">← All tasks</Button>
|
||||
<Card.Root
|
||||
><Card.Header><Card.Title>Create task</Card.Title></Card.Header><Card.Content
|
||||
><form method="POST" class="space-y-4">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { db } from '$lib/server/db';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import * as Table from '$lib/components/ui/table/index.js';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import * as Table from '#lib/components/ui/table/index.js';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import type { PageProps } from './$types';
|
||||
let { data }: PageProps = $props();
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-4xl space-y-6 px-4 py-12 sm:px-6">
|
||||
<Button href={resolve('/projects/progress')} variant="ghost" size="sm">← All tasks</Button>
|
||||
<Button href={resolve('projects/progress')} variant="ghost" size="sm">← All tasks</Button>
|
||||
<h1 class="text-4xl font-semibold tracking-tight">topics</h1>
|
||||
<div class="overflow-hidden rounded-md border border-border">
|
||||
<Table.Root
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { db } from '$lib/server/db';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
|
||||
export const load = async ({ params }) => {
|
||||
return {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import * as Card from '#lib/components/ui/card/index.js';
|
||||
import type { PageProps } from './$types';
|
||||
let { data }: PageProps = $props();
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-4xl space-y-6 px-4 py-12 sm:px-6">
|
||||
<Button href={resolve('/projects/progress/topics')} variant="ghost" size="sm">← All topics</Button
|
||||
>
|
||||
<Button href={resolve('projects/progress/topics')} variant="ghost" size="sm">← All topics</Button>
|
||||
{#if data.topic}
|
||||
<h1 class="text-4xl font-semibold tracking-tight">{data.topic.name}</h1>
|
||||
<p class="text-muted-foreground">{data.topic.topicsToTasks.length} tasks</p>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { Actions } from './$types';
|
||||
import { fail } from '@sveltejs/kit';
|
||||
import { parseSpotifyInput } from '$lib/map-finder/input';
|
||||
import type { TrackMatchResult } from '$lib/map-finder/types';
|
||||
import { getSpotifyMetadata } from '$lib/server/spotify';
|
||||
import { getOsuMaps, MATCH_CONCURRENCY } from '$lib/server/osu';
|
||||
import { ApiError, publicError, logApiError, mapConcurrent } from '$lib/server/finder-http';
|
||||
import { acquireSearch } from '$lib/server/finder-limit';
|
||||
import { parseSpotifyInput } from '#lib/map-finder/input.js';
|
||||
import type { TrackMatchResult } from '#lib/map-finder/types.js';
|
||||
import { getSpotifyMetadata } from '#lib/server/spotify.js';
|
||||
import { getOsuMaps, MATCH_CONCURRENCY } from '#lib/server/osu.js';
|
||||
import { ApiError, publicError, logApiError, mapConcurrent } from '#lib/server/finder-http.js';
|
||||
import { acquireSearch } from '#lib/server/finder-limit.js';
|
||||
export const actions: Actions = {
|
||||
default: async ({ request, fetch, getClientAddress }) => {
|
||||
const data = await request.formData();
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { NativeSelect } from '$lib/components/ui/native-select/index.js';
|
||||
import { NativeSelect } from '#lib/components/ui/native-select/index.js';
|
||||
import { enhance } from '$app/forms';
|
||||
import type { PageProps } from './$types';
|
||||
import Artwork from '$lib/map-finder/Artwork.svelte';
|
||||
import MapCard from '$lib/map-finder/MapCard.svelte';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import Artwork from '#lib/map-finder/Artwork.svelte';
|
||||
import MapCard from '#lib/map-finder/MapCard.svelte';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
import { Input } from '#lib/components/ui/input/index.js';
|
||||
import * as Card from '#lib/components/ui/card/index.js';
|
||||
let { form }: PageProps = $props();
|
||||
let pending = $state(false);
|
||||
let filter = $state('all');
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { expect, it, vi } from 'vitest';
|
||||
vi.mock('$lib/server/spotify', () => ({ getSpotifyMetadata: vi.fn() }));
|
||||
vi.mock('$lib/server/osu', () => ({ getOsuMaps: vi.fn(), MATCH_CONCURRENCY: 3 }));
|
||||
vi.mock('#lib/server/spotify.js', () => ({ getSpotifyMetadata: vi.fn() }));
|
||||
vi.mock('#lib/server/osu.js', () => ({ getOsuMaps: vi.fn(), MATCH_CONCURRENCY: 3 }));
|
||||
import { actions } from './+page.server';
|
||||
import { getSpotifyMetadata } from '$lib/server/spotify';
|
||||
import { getOsuMaps } from '$lib/server/osu';
|
||||
import { getSpotifyMetadata } from '#lib/server/spotify.js';
|
||||
import { getOsuMaps } from '#lib/server/osu.js';
|
||||
import type { RequestEvent } from './$types';
|
||||
const event = (query: string, address = 'validation-test') => {
|
||||
const data = new FormData();
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { NativeSelect } from '$lib/components/ui/native-select/index.js';
|
||||
import { NativeSelect } from '#lib/components/ui/native-select/index.js';
|
||||
import './world-opinions.css';
|
||||
import CountryPanel from '$lib/world-opinions/CountryPanel.svelte';
|
||||
import CultureMap from '$lib/world-opinions/CultureMap.svelte';
|
||||
import PartyCompass from '$lib/world-opinions/PartyCompass.svelte';
|
||||
import WorldMap, { type MapMode } from '$lib/world-opinions/WorldMap.svelte';
|
||||
import { countries, countryById, narratives, sources } from '$lib/world-opinions/data';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import CountryPanel from '#lib/world-opinions/CountryPanel.svelte';
|
||||
import CultureMap from '#lib/world-opinions/CultureMap.svelte';
|
||||
import PartyCompass from '#lib/world-opinions/PartyCompass.svelte';
|
||||
import WorldMap, { type MapMode } from '#lib/world-opinions/WorldMap.svelte';
|
||||
import { countries, countryById, narratives, sources } from '#lib/world-opinions/data.js';
|
||||
import { Button } from '#lib/components/ui/button/index.js';
|
||||
|
||||
const modes: { id: MapMode; label: string }[] = [
|
||||
{ id: 'liberal', label: 'what "liberal" means' },
|
||||
|
||||
Reference in New Issue
Block a user