feat: upgrade to SvelteKit 3 and adapter-bun
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:
2026-10-18 22:17:30 +00:00
parent b52e9188f4
commit 54924b39f0
240 changed files with 894 additions and 944 deletions

View File

@@ -1,6 +1,6 @@
import { isAdmin } from '$lib/server/authorization';
import { dev } from '$app/environment';
import { siteEnvironment } from '$lib/title';
import { isAdmin } from '#lib/server/authorization.js';
import { dev } from '$app/env';
import { siteEnvironment } from '#lib/title.js';
import type { LayoutServerLoad } from './$types';
const environment = siteEnvironment(dev, process.env.ORIGIN);
export const load: LayoutServerLoad = (event) => ({

View File

@@ -1,15 +1,15 @@
<script lang="ts">
import '../app.css';
import { onMount } from 'svelte';
import { assets, resolve } from '$app/paths';
import { dev } from '$app/environment';
import { resolve, asset } from '$app/paths';
import { dev } from '$app/env';
import { page } from '$app/state';
import { getSeo } from '$lib/seo';
import CopyButton from '$lib/components/CopyButton.svelte';
import ProjectIcon from '$lib/components/ProjectIcon.svelte';
import { projects } from '$lib/projects';
import { Button, buttonVariants } from '$lib/components/ui/button/index.js';
import * as Sheet from '$lib/components/ui/sheet/index.js';
import { getSeo } from '#lib/seo.js';
import CopyButton from '#lib/components/CopyButton.svelte';
import ProjectIcon from '#lib/components/ProjectIcon.svelte';
import { projects } from '#lib/projects.js';
import { Button, buttonVariants } from '#lib/components/ui/button/index.js';
import * as Sheet from '#lib/components/ui/sheet/index.js';
import ArrowRight from 'lucide-svelte/icons/arrow-right';
import ChevronDown from 'lucide-svelte/icons/chevron-down';
import ExternalLink from 'lucide-svelte/icons/external-link';
@@ -179,7 +179,7 @@
>
<span class="h-7 w-7 overflow-hidden rounded-full bg-black"
><img
src="{assets}/favicon-32x32.png"
src={asset('favicon-32x32.png')}
alt=""
width="32"
height="32"
@@ -199,7 +199,7 @@
onpointerleave={handleProjectsPointerLeave}
>
<Button
href={resolve('/projects')}
href={resolve('projects')}
variant={current(item.href) ? 'secondary' : 'ghost'}
size="sm"
class="cursor-pointer gap-1"
@@ -217,7 +217,7 @@
aria-label="Projects"
>
<a
href={resolve('/projects')}
href={resolve('projects')}
class="flex items-center rounded-sm px-2 py-1.5 text-sm font-medium outline-none hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent"
>all projects <ArrowRight size={14} class="ml-auto" /></a
>
@@ -266,13 +266,13 @@
title={`Theme: ${theme}`}>{@render themeIcon()}</Button
>
{#if data.user}
<Button href={resolve('/account')} variant="outline" size="sm" class="max-w-36 truncate"
<Button href={resolve('account')} variant="outline" size="sm" class="max-w-36 truncate"
>{data.user.name}</Button
>
{:else}
<Button
href={resolve(
`/login?returnTo=${encodeURIComponent(page.url.pathname + page.url.search)}`
`login?returnTo=${encodeURIComponent(page.url.pathname + page.url.search)}`
)}
variant="outline"
size="sm">sign in</Button
@@ -322,20 +322,18 @@
aria-label={`Theme: ${theme}. Switch mode`}>{@render themeIcon()}</Button
>
{#if data.user}
<Button
href={resolve('/account')}
variant="outline"
onclick={() => (menuOpen = false)}>account</Button
<Button href={resolve('account')} variant="outline" onclick={() => (menuOpen = false)}
>account</Button
>
<form method="POST" action="/logout">
<Button type="submit" variant="outline">sign out</Button>
</form>
{:else}
<Button href={resolve('/login')} variant="outline" onclick={() => (menuOpen = false)}
<Button href={resolve('login')} variant="outline" onclick={() => (menuOpen = false)}
>sign in</Button
>
<Button
href={resolve('/register')}
href={resolve('register')}
variant="outline"
onclick={() => (menuOpen = false)}>register</Button
>

View File

@@ -1,6 +1,6 @@
import { fail } from '@sveltejs/kit';
import { db } from '$lib/server/db';
import { listItemCompletions } from '$lib/server/db/schema';
import { db } from '#lib/server/db/index.js';
import { listItemCompletions } from '#lib/server/db/schema/index.js';
import { eq } from 'drizzle-orm';
import type { PageServerLoad } from './$types';

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import { resolve } from '$app/paths';
import portrait from '$lib/assets/stan.jpg?url';
import ProgressBar from '$lib/components/ProgressBar.svelte';
import ProjectIcon from '$lib/components/ProjectIcon.svelte';
import * as Card from '$lib/components/ui/card/index.js';
import { projects } from '$lib/projects';
import portrait from '#lib/assets/stan.jpg?url';
import ProgressBar from '#lib/components/ProgressBar.svelte';
import ProjectIcon from '#lib/components/ProjectIcon.svelte';
import * as Card from '#lib/components/ui/card/index.js';
import { projects } from '#lib/projects.js';
import ArrowRight from 'lucide-svelte/icons/arrow-right';
import ExternalLink from 'lucide-svelte/icons/external-link';
import Mail from 'lucide-svelte/icons/mail';
@@ -99,7 +99,7 @@
<section aria-labelledby="projects-heading" class="border-t border-[var(--site-border)] pt-8">
<h2 id="projects-heading" class="mb-4 text-2xl font-semibold tracking-tight">
<a
href={resolve('/projects')}
href={resolve('projects')}
class="inline-flex items-center gap-1 hover:text-[var(--site-muted)]"
>projects <ArrowRight size={20} aria-hidden="true" /></a
>
@@ -167,7 +167,7 @@
<section aria-labelledby="lists-heading" class="pt-14 sm:pt-20">
<h2 id="lists-heading" class="mb-4 text-2xl font-semibold tracking-tight">
<a
href={resolve('/lists')}
href={resolve('lists')}
class="inline-flex items-center gap-1 hover:text-[var(--site-muted)]"
>lists <ArrowRight size={20} aria-hidden="true" /></a
>

View File

@@ -1,4 +1,4 @@
import { auth, authProviders } from '$lib/server/auth';
import { auth, authProviders } from '#lib/server/auth.js';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ locals, request, url }) => {

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import { resolve } from '$app/paths';
import { authClient } from '$lib/auth-client';
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 { authClient } from '#lib/auth-client.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';
import type { PageProps } from './$types';
let { data }: PageProps = $props();
let busy = $state(false);
@@ -66,11 +66,11 @@
<Card.Root
><Card.Header><Card.Title>Administration</Card.Title></Card.Header><Card.Content>
<div class="flex flex-wrap gap-4">
<a class="underline" href={resolve('/blog')}>Blog articles</a><a
<a class="underline" href={resolve('blog')}>Blog articles</a><a
class="underline"
href={resolve('/lists')}>Lists</a
><a class="underline" href={resolve('/projects/animaldex/admin')}>Animaldex moderation</a
><a class="underline" href={resolve('/projects/progress')}>Progress</a>
href={resolve('lists')}>Lists</a
><a class="underline" href={resolve('projects/animaldex/admin')}>Animaldex moderation</a
><a class="underline" href={resolve('projects/progress')}>Progress</a>
</div>
</Card.Content></Card.Root
>

View File

@@ -1,8 +1,8 @@
import { db } from '$lib/server/db';
import { blogArticles } from '$lib/server/db/schema';
import { isAdmin, requireAdmin } from '$lib/server/auth';
import { uniqueSlug } from '$lib/server/slugify';
import { createArticleSchema } from '$lib/validation/blog';
import { db } from '#lib/server/db/index.js';
import { blogArticles } from '#lib/server/db/schema/index.js';
import { isAdmin, requireAdmin } from '#lib/server/auth.js';
import { uniqueSlug } from '#lib/server/slugify.js';
import { createArticleSchema } from '#lib/validation/blog.js';
import { desc, eq } from 'drizzle-orm';
import { fail, redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types.js';

View File

@@ -4,13 +4,13 @@
onMount(() => {
hydrated = true;
});
import * as Dialog from '$lib/components/ui/dialog/index.js';
import * as Dialog from '#lib/components/ui/dialog/index.js';
import { enhance } from '$app/forms';
import { resolve } from '$app/paths';
import { calendarDate, 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 { calendarDate, relativeTime } from '#lib/relative-time.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 type { PageProps } from './$types';
let { data, form }: PageProps = $props();
let searchQuery = $state('');

View File

@@ -1,7 +1,7 @@
import { db } from '$lib/server/db';
import { blogArticles } from '$lib/server/db/schema';
import { isAdmin } from '$lib/server/auth';
import { renderMarkdown } from '$lib/server/markdown';
import { db } from '#lib/server/db/index.js';
import { blogArticles } from '#lib/server/db/schema/index.js';
import { isAdmin } from '#lib/server/auth.js';
import { renderMarkdown } from '#lib/server/markdown.js';
import { eq } from 'drizzle-orm';
import type { PageServerLoad } from './$types.js';
@@ -14,14 +14,14 @@ export const load: PageServerLoad = async (event) => {
.where(eq(blogArticles.slug, event.params.slug));
if (!article) {
return error(404, { message: 'Article not found' });
return error(404, 'Article not found');
}
const admin = isAdmin(event);
if (article.status !== 'published') {
if (!admin) {
return error(404, { message: 'Article not found' });
return error(404, 'Article not found');
}
// Drafts are only ever viewed/edited through the editor.
return redirect(303, `/blog/${article.slug}/edit`);

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { resolve } from '$app/paths';
import { calendarDate } from '$lib/relative-time';
import { calendarDate } from '#lib/relative-time.js';
import 'highlight.js/styles/github-dark.css';
let { data } = $props();

View File

@@ -1,8 +1,8 @@
import { db } from '$lib/server/db';
import { blogArticles } from '$lib/server/db/schema';
import { isAdmin, requireAdmin } from '$lib/server/auth';
import { parseArticleTags, updateArticleSchema } from '$lib/validation/blog';
import { validSlug } from '$lib/server/slugify';
import { db } from '#lib/server/db/index.js';
import { blogArticles } from '#lib/server/db/schema/index.js';
import { isAdmin, requireAdmin } from '#lib/server/auth.js';
import { parseArticleTags, updateArticleSchema } from '#lib/validation/blog.js';
import { validSlug } from '#lib/server/slugify.js';
import { and, eq, ne } from 'drizzle-orm';
import { error, fail, redirect, type RequestEvent } from '@sveltejs/kit';
@@ -19,7 +19,7 @@ export const load: PageServerLoad = async (event) => {
.where(eq(blogArticles.slug, event.params.slug));
if (!article) {
return error(404, { message: 'Article not found' });
return error(404, 'Article not found');
}
return {
@@ -68,7 +68,7 @@ export const actions = {
.returning();
if (!updated) {
return error(404, { message: 'Article not found' });
return error(404, 'Article not found');
}
if (values.slug !== event.params.slug) redirect(303, `/blog/${values.slug}/edit`);
@@ -105,7 +105,7 @@ export const actions = {
.returning();
if (!updated) {
return error(404, { message: 'Article not found' });
return error(404, 'Article not found');
}
return redirect(303, `/blog/${updated.slug}`);

View File

@@ -1,12 +1,12 @@
<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';
import { enhance } from '$app/forms';
import { resolve } from '$app/paths';
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
import ArrowRight from 'lucide-svelte/icons/arrow-right';
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';
let { data, form } = $props();
@@ -29,7 +29,7 @@
<div class="mx-auto max-w-5xl px-4 py-10 sm:px-6 sm:py-16">
<a
href={resolve('/blog')}
href={resolve('blog')}
class="inline-flex items-center gap-1 text-base text-[var(--site-muted)] hover:text-[var(--site-text)]"
><ArrowLeft size={16} aria-hidden="true" /> blog</a
>

View File

@@ -1,8 +1,8 @@
import { db } from '$lib/server/db';
import { listItemCompletions, lists } from '$lib/server/db/schema';
import { requireAdmin } from '$lib/server/authorization';
import { uniqueListSlug } from '$lib/server/slugify';
import { listImageUrl } from '$lib/server/r2';
import { db } from '#lib/server/db/index.js';
import { listItemCompletions, lists } from '#lib/server/db/schema/index.js';
import { requireAdmin } from '#lib/server/authorization.js';
import { uniqueListSlug } from '#lib/server/slugify.js';
import { listImageUrl } from '#lib/server/r2.js';
import { eq } from 'drizzle-orm';
import { fail, redirect } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';

View File

@@ -4,15 +4,14 @@
onMount(() => {
hydrated = true;
});
import { NativeSelect } from '$lib/components/ui/native-select/index.js';
import * as Dialog from '$lib/components/ui/dialog/index.js';
import { enhance } from '$app/forms';
import { NativeSelect } from '#lib/components/ui/native-select/index.js';
import * as Dialog from '#lib/components/ui/dialog/index.js';
import { enhance, type SubmitFunction } from '$app/forms';
import { resolve } from '$app/paths';
import ProgressBar from '$lib/components/ProgressBar.svelte';
import ListPictureDropzone from '$lib/components/ListPictureDropzone.svelte';
import { Button } from '$lib/components/ui/button/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import type { SubmitFunction } from '@sveltejs/kit';
import ProgressBar from '#lib/components/ProgressBar.svelte';
import ListPictureDropzone from '#lib/components/ListPictureDropzone.svelte';
import { Button } from '#lib/components/ui/button/index.js';
import { Input } from '#lib/components/ui/input/index.js';
import type { PageProps } from './$types';
let { data, form }: PageProps = $props();
let uploadError = $state('');
@@ -36,7 +35,7 @@
try {
const body = new FormData();
body.set('picture', picture);
const response = await fetch(resolve('/lists/api/upload'), { method: 'POST', body });
const response = await fetch(resolve('lists/api/upload'), { method: 'POST', body });
if (!response.ok) throw new Error('Could not upload the image. Please try again.');
const { key } = await response.json();
formData.set('pictureKey', key);

View File

@@ -1,9 +1,9 @@
import { db } from '$lib/server/db';
import { env } from '$env/dynamic/private';
import { lists, listItems, listItemCompletions } from '$lib/server/db/schema';
import { requireAdmin, requireUser } from '$lib/server/authorization';
import { deleteObject, listImageKey, listImageUrl } from '$lib/server/r2';
import { validSlug } from '$lib/server/slugify';
import { db } from '#lib/server/db/index.js';
import { ORIGIN } from '$app/env/private';
import { lists, listItems, listItemCompletions } from '#lib/server/db/schema/index.js';
import { requireAdmin, requireUser } from '#lib/server/authorization.js';
import { deleteObject, listImageKey, listImageUrl } from '#lib/server/r2.js';
import { validSlug } from '#lib/server/slugify.js';
import { and, eq, ne } from 'drizzle-orm';
import { error, fail, redirect } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';
@@ -15,7 +15,7 @@ async function currentList(slug: string) {
}
async function removeStoredPicture(url: string | null) {
// Development and staging may reference pictures copied from production.
if (env.ORIGIN !== 'https://stanrunge.dev') return;
if (ORIGIN !== 'https://stanrunge.dev') return;
const key = url ? listImageKey(url) : null;
if (!key) return;
try {

View File

@@ -1,20 +1,19 @@
<script lang="ts">
import { NativeSelect } from '$lib/components/ui/native-select/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import { Textarea } from '$lib/components/ui/textarea/index.js';
import { Checkbox } from '$lib/components/ui/checkbox/index.js';
import * as Collapsible from '$lib/components/ui/collapsible/index.js';
import * as AlertDialog from '$lib/components/ui/alert-dialog/index.js';
import * as Dialog from '$lib/components/ui/dialog/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { NativeSelect } from '#lib/components/ui/native-select/index.js';
import { Input } from '#lib/components/ui/input/index.js';
import { Textarea } from '#lib/components/ui/textarea/index.js';
import { Checkbox } from '#lib/components/ui/checkbox/index.js';
import * as Collapsible from '#lib/components/ui/collapsible/index.js';
import * as AlertDialog from '#lib/components/ui/alert-dialog/index.js';
import * as Dialog from '#lib/components/ui/dialog/index.js';
import { Button } from '#lib/components/ui/button/index.js';
let addOpen = $state(false);
import { resolve } from '$app/paths';
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
import Check from 'lucide-svelte/icons/check';
import { enhance } from '$app/forms';
import ListPictureDropzone from '$lib/components/ListPictureDropzone.svelte';
import ProgressBar from '$lib/components/ProgressBar.svelte';
import type { SubmitFunction } from '@sveltejs/kit';
import { enhance, type SubmitFunction } from '$app/forms';
import ListPictureDropzone from '#lib/components/ListPictureDropzone.svelte';
import ProgressBar from '#lib/components/ProgressBar.svelte';
import type { PageProps } from './$types';
let { data, form }: PageProps = $props();
let completed = $derived(new Set(data.completedIds));
@@ -60,7 +59,7 @@
try {
const uploadForm = new FormData();
uploadForm.set('picture', picture);
const uploaded = await fetch(resolve('/lists/api/upload'), {
const uploaded = await fetch(resolve('lists/api/upload'), {
method: 'POST',
body: uploadForm
});
@@ -79,7 +78,7 @@
<section class="mx-auto max-w-4xl space-y-6 px-4 py-10 sm:px-6 sm:py-14">
<a
href={resolve('/lists')}
href={resolve('lists')}
class="inline-flex items-center gap-1 text-base text-[var(--site-muted)] hover:text-[var(--site-text)]"
><ArrowLeft size={16} aria-hidden="true" /> lists</a
>
@@ -123,8 +122,8 @@
</div>
{:else}
<p>
<a class="underline" href={resolve(`/login?returnTo=/lists/${data.list.slug}`)}>Log in</a> to
save your progress.
<a class="underline" href={resolve(`login?returnTo=/lists/${data.list.slug}`)}>Log in</a> to save
your progress.
</p>
{/if}
{/if}

View File

@@ -1,6 +1,6 @@
import { json, error } from '@sveltejs/kit';
import { requireAdmin } from '$lib/server/authorization';
import { uploadImage } from '$lib/server/r2';
import { error } from '@sveltejs/kit';
import { requireAdmin } from '#lib/server/authorization.js';
import { uploadImage } from '#lib/server/r2.js';
import type { RequestHandler } from './$types';
export const POST: RequestHandler = async (event) => {
@@ -16,7 +16,7 @@ export const POST: RequestHandler = async (event) => {
if (!picture.size || picture.size > 5 * 1024 * 1024)
error(400, 'Images must be 5 MB or smaller.');
try {
return json(await uploadImage(picture, 'lists'));
return Response.json(await uploadImage(picture, 'lists'));
} catch (cause) {
console.error('List image upload failed', cause);
error(503, 'Image storage is unavailable.');

View File

@@ -1,5 +1,5 @@
import { authProviders } from '$lib/server/auth';
import { safeReturnTo } from '$lib/auth-redirect';
import { authProviders } from '#lib/server/auth.js';
import { safeReturnTo } from '#lib/auth-redirect.js';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = ({ url, locals }) => {

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import AuthForm from '$lib/components/AuthForm.svelte';
import AuthForm from '#lib/components/AuthForm.svelte';
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>

View File

@@ -1,4 +1,4 @@
import { auth } from '$lib/server/auth';
import { auth } from '#lib/server/auth.js';
import { error, redirect } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ request, url, cookies }) => {

View File

@@ -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',

View File

@@ -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.

View File

@@ -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({

View File

@@ -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

View File

@@ -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) => {

View File

@@ -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
>

View File

@@ -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 });
};

View File

@@ -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 });
};

View File

@@ -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 });
};

View File

@@ -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 });
};

View File

@@ -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 });
};

View File

@@ -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 });
};

View File

@@ -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.');

View File

@@ -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
>

View File

@@ -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';

View File

@@ -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();

View File

@@ -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 });
};

View File

@@ -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 });
};

View File

@@ -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"

View File

@@ -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">

View File

@@ -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 }));

View File

@@ -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) {

View File

@@ -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" />

View File

@@ -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) {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 }) => ({

View File

@@ -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">

View File

@@ -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];

View File

@@ -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}

View File

@@ -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));
};

View File

@@ -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' } });
}

View File

@@ -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 () => ({

View File

@@ -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>

View File

@@ -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'];

View File

@@ -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>

View File

@@ -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) => {

View File

@@ -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} />

View File

@@ -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));
};

View File

@@ -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' } });
};

View File

@@ -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 });
};

View File

@@ -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));
};

View File

@@ -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();

View File

@@ -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.

View File

@@ -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();

View File

@@ -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 }) => {

View File

@@ -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
>

View File

@@ -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);

View File

@@ -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 }) => {

View File

@@ -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);

View File

@@ -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 =

View File

@@ -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);

View File

@@ -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';

View File

@@ -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();

View File

@@ -1,4 +1,4 @@
import { db } from '$lib/server/db';
import { db } from '#lib/server/db/index.js';
export const load = async () => {
return {

View File

@@ -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>

View File

@@ -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) => {

View File

@@ -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>

View File

@@ -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 = {

View File

@@ -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">

View File

@@ -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 () => {

View File

@@ -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

View File

@@ -1,4 +1,4 @@
import { db } from '$lib/server/db';
import { db } from '#lib/server/db/index.js';
export const load = async ({ params }) => {
return {

View File

@@ -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>

View File

@@ -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();

View File

@@ -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');

View File

@@ -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();

View File

@@ -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' },

View File

@@ -1,5 +1,5 @@
import { authProviders } from '$lib/server/auth';
import { safeReturnTo } from '$lib/auth-redirect';
import { authProviders } from '#lib/server/auth.js';
import { safeReturnTo } from '#lib/auth-redirect.js';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = ({ url, locals }) => {

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import AuthForm from '$lib/components/AuthForm.svelte';
import AuthForm from '#lib/components/AuthForm.svelte';
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>

View File

@@ -1,4 +1,4 @@
import { SITE_ORIGIN } from '$lib/seo';
import { SITE_ORIGIN } from '#lib/seo.js';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = ({ url }) => {

View File

@@ -1,8 +1,8 @@
import { db } from '$lib/server/db';
import { blogArticles } from '$lib/server/db/schema';
import { SITE_ORIGIN } from '$lib/seo';
import { races } from '$lib/derby/races';
import { COURSES } from '$lib/language-learner/courses';
import { db } from '#lib/server/db/index.js';
import { blogArticles } from '#lib/server/db/schema/index.js';
import { SITE_ORIGIN } from '#lib/seo.js';
import { races } from '#lib/derby/races.js';
import { COURSES } from '#lib/language-learner/courses/index.js';
import { eq } from 'drizzle-orm';
import type { RequestHandler } from './$types';