Money: move /projects/money-tracker to /trackers/money, rename to Money, 308 redirects from old paths (bank-callback query kept)
All checks were successful
Deploy production / deploy (push) Successful in 2m49s
All checks were successful
Deploy production / deploy (push) Successful in 2m49s
This commit is contained in:
@@ -260,9 +260,9 @@ bind-mounts the host's `BOOKS_HOST_DIR` (default `/home/stan/books`) read-only a
|
||||
`BOOKS_ROOT=/books`. Without a `library` directory there the deploy still succeeds, but files can't
|
||||
be opened.
|
||||
|
||||
## Money tracker
|
||||
## Money
|
||||
|
||||
`/projects/money-tracker` is restricted to administrator accounts. Its tables
|
||||
`/trackers/money` (formerly `/projects/money-tracker`, which now redirects) is restricted to administrator accounts. Its tables
|
||||
are created by `npm run db:migrate:money-tracker`; apply that migration to each
|
||||
environment before deploying the new route. The deployment preflight checks for
|
||||
all nine tracker tables and the projection columns.
|
||||
|
||||
@@ -69,7 +69,7 @@ const aspsp = {
|
||||
const defaultAccount = value('--account') ?? preset?.account ?? aspsp.name;
|
||||
const redirectUrl =
|
||||
process.env.ENABLEBANKING_REDIRECT_URL ??
|
||||
'https://stanrunge.dev/projects/money-tracker/bank-callback';
|
||||
'https://stanrunge.dev/projects/money-tracker/bank-callback'; // registered with Enable Banking; 308s to /trackers/money/bank-callback with the query kept
|
||||
|
||||
const app = await client.application();
|
||||
console.log(
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
'derby lens': ChartNoAxesCombined,
|
||||
'world opinions': Globe2,
|
||||
'language learner': Languages,
|
||||
'money tracker': Wallet,
|
||||
money: Wallet,
|
||||
'cs prices': CandlestickChart,
|
||||
'house tracker': House,
|
||||
lists: ListChecks,
|
||||
|
||||
@@ -41,9 +41,9 @@ export const trackers = [
|
||||
external: false
|
||||
},
|
||||
{
|
||||
name: 'money tracker',
|
||||
name: 'money',
|
||||
subtitle: 'a manual ledger for accounts, bills and utilities',
|
||||
href: '/projects/money-tracker',
|
||||
href: '/trackers/money',
|
||||
external: false
|
||||
},
|
||||
{
|
||||
|
||||
@@ -34,10 +34,10 @@ export function getTitle(path: string, environment: SiteEnvironment = null): str
|
||||
'/projects/energy': 'Energy',
|
||||
'/projects/game-tracker': 'Game Tracker',
|
||||
'/projects/language-learner': 'Language Learner',
|
||||
'/projects/money-tracker': 'Money Tracker',
|
||||
'/projects/spotify-beatmap-finder': 'osu! map finder',
|
||||
'/projects/progress': 'Progress',
|
||||
'/projects/world-opinions': 'World Opinions'
|
||||
'/projects/world-opinions': 'World Opinions',
|
||||
'/trackers/money': 'Money'
|
||||
};
|
||||
const cleaned = path.replace(/\/$/, '') || '/';
|
||||
const title =
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
stored. Session cookies keep you signed in; there are no advertising or third-party tracking
|
||||
cookies.
|
||||
</p>
|
||||
<h2 class="pt-2 text-lg font-semibold">Bank data (money tracker)</h2>
|
||||
<h2 class="pt-2 text-lg font-semibold">Bank data (Money)</h2>
|
||||
<p>
|
||||
The money tracker is a private tool used only by the site owner. With the account holders'
|
||||
Money (/trackers/money) is a private tool used only by the site owner. With the account holders'
|
||||
explicit consent, it reads account details, balances and booked transactions from Revolut
|
||||
through Enable Banking Oy, a licensed account information service provider. This data is used
|
||||
only to show the account holders their own finances. It is stored in a database on a server
|
||||
|
||||
10
src/routes/projects/money-tracker/[...rest]/+server.ts
Normal file
10
src/routes/projects/money-tracker/[...rest]/+server.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
// Money moved from /projects/money-tracker to /trackers/money. Old links, and the Enable Banking
|
||||
// redirect URL registered as /projects/money-tracker/bank-callback, keep working: 308 keeps the
|
||||
// method, the subpath and the query string (?range=, ?code=&state=).
|
||||
export const fallback: RequestHandler = ({ params, url }) => {
|
||||
redirect(308, `/trackers/money${params.rest ? `/${params.rest}` : ''}${url.search}`);
|
||||
};
|
||||
export const GET = fallback;
|
||||
30
src/routes/projects/money-tracker/[...rest]/redirect.test.ts
Normal file
30
src/routes/projects/money-tracker/[...rest]/redirect.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { isRedirect } from '@sveltejs/kit';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { fallback } from './+server';
|
||||
|
||||
function target(path: string, rest: string) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
fallback({ params: { rest }, url: new URL(`https://stanrunge.dev${path}`) } as any);
|
||||
} catch (thrown) {
|
||||
if (isRedirect(thrown)) return [thrown.status, thrown.location];
|
||||
throw thrown;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
describe('old money tracker URLs', () => {
|
||||
it('redirects to /trackers/money, keeping subpath and query', () => {
|
||||
expect(target('/projects/money-tracker?range=3y', '')).toEqual([
|
||||
308,
|
||||
'/trackers/money?range=3y'
|
||||
]);
|
||||
expect(target('/projects/money-tracker/transactions?page=2', 'transactions')).toEqual([
|
||||
308,
|
||||
'/trackers/money/transactions?page=2'
|
||||
]);
|
||||
expect(
|
||||
target('/projects/money-tracker/bank-callback?state=s&code=abc-123', 'bank-callback')
|
||||
).toEqual([308, '/trackers/money/bank-callback?state=s&code=abc-123']);
|
||||
});
|
||||
});
|
||||
@@ -22,10 +22,11 @@
|
||||
You're responsible for activity on your account. Accounts that abuse the site may be removed.
|
||||
You can ask for your account to be deleted at any time.
|
||||
</p>
|
||||
<h2 class="pt-2 text-lg font-semibold">Money tracker</h2>
|
||||
<h2 class="pt-2 text-lg font-semibold">Money</h2>
|
||||
<p>
|
||||
The money tracker is a private tool for the site owner and household. Its bank connection is
|
||||
read-only (account information via Enable Banking) and nothing shown in it is financial advice.
|
||||
Money (/trackers/money) is a private tool for the site owner and household. Its bank connection
|
||||
is read-only (account information via Enable Banking) and nothing shown in it is financial
|
||||
advice.
|
||||
</p>
|
||||
<h2 class="pt-2 text-lg font-semibold">Liability</h2>
|
||||
<p>
|
||||
|
||||
@@ -31,7 +31,7 @@ import { error, redirect } from '@sveltejs/kit';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
if (!event.locals.user) redirect(303, '/login?returnTo=/projects/money-tracker');
|
||||
if (!event.locals.user) redirect(303, '/login?returnTo=/trackers/money');
|
||||
const user = requireAdmin(event);
|
||||
const userId = user.id;
|
||||
const [
|
||||
@@ -250,7 +250,7 @@
|
||||
);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = 'money-tracker-transactions.csv';
|
||||
link.download = 'money-transactions.csv';
|
||||
link.click();
|
||||
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
||||
}
|
||||
@@ -276,7 +276,7 @@
|
||||
<header class="app-header">
|
||||
<div>
|
||||
<p class="muted">Personal finance</p>
|
||||
<h1>Money tracker</h1>
|
||||
<h1>Money</h1>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<Button variant="outline" onclick={() => open('import')}
|
||||
@@ -440,7 +440,7 @@
|
||||
<p>{filtered.length} of {data.transactions.length} entries</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<Button variant="outline" href={resolve('/projects/money-tracker/transactions')}
|
||||
<Button variant="outline" href={resolve('/trackers/money/transactions')}
|
||||
>All transactions</Button
|
||||
><Button variant="outline" onclick={exportCsv} disabled={!data.transactions.length}
|
||||
><Download size={16} /> Export CSV</Button
|
||||
@@ -767,7 +767,7 @@
|
||||
|
||||
<Dialog.Root bind:open={dialogOpen}>
|
||||
<Dialog.Content class="money-dialog block max-h-[90dvh] overflow-y-auto sm:max-w-lg">
|
||||
<Dialog.Description class="sr-only">Manage your money tracker records.</Dialog.Description>
|
||||
<Dialog.Description class="sr-only">Manage your money records.</Dialog.Description>
|
||||
{#if showActionError && form?.message}<p class="form-error" role="alert">{form.message}</p>{/if}
|
||||
<Dialog.Header class="mb-6 pr-8"
|
||||
><Dialog.Title>
|
||||
@@ -14,10 +14,7 @@
|
||||
</script>
|
||||
|
||||
<svelte:head
|
||||
><title>Bank connection · Money tracker</title><meta
|
||||
name="robots"
|
||||
content="noindex"
|
||||
/></svelte:head
|
||||
><title>Bank connection · Money</title><meta name="robots" content="noindex" /></svelte:head
|
||||
>
|
||||
|
||||
<section class="mx-auto max-w-xl space-y-6 p-6">
|
||||
@@ -48,6 +45,6 @@
|
||||
</p>
|
||||
{/if}
|
||||
<p>
|
||||
<a class="underline" href={resolve('/projects/money-tracker')}>Back to the money tracker</a>
|
||||
<a class="underline" href={resolve('/trackers/money')}>Back to Money</a>
|
||||
</p>
|
||||
</section>
|
||||
@@ -36,7 +36,7 @@
|
||||
else params.set(key, String(value));
|
||||
}
|
||||
const query = params.toString();
|
||||
return resolve('/projects/money-tracker/transactions') + (query ? '?' + query : '');
|
||||
return resolve('/trackers/money/transactions') + (query ? '?' + query : '');
|
||||
}
|
||||
function submit(event: SubmitEvent) {
|
||||
event.preventDefault();
|
||||
@@ -45,7 +45,7 @@
|
||||
for (const [key, value] of form)
|
||||
if (typeof value === 'string' && value) params.append(key, value);
|
||||
const query = params.toString();
|
||||
goto(resolve('/projects/money-tracker/transactions') + (query ? '?' + query : ''), {
|
||||
goto(resolve('/trackers/money/transactions') + (query ? '?' + query : ''), {
|
||||
keepFocus: true,
|
||||
noScroll: true
|
||||
});
|
||||
@@ -55,14 +55,14 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head><title>Transactions · Money tracker</title></svelte:head>
|
||||
<svelte:head><title>Transactions · Money</title></svelte:head>
|
||||
|
||||
<div class="mx-auto max-w-screen-xl space-y-6 px-4 py-8 sm:px-6 sm:py-10">
|
||||
<header class="flex flex-wrap items-end justify-between gap-4">
|
||||
<div>
|
||||
<a
|
||||
class="inline-flex items-center gap-1 text-sm opacity-70 hover:opacity-100"
|
||||
href={resolve('/projects/money-tracker')}><ArrowLeft size={14} /> Money tracker</a
|
||||
href={resolve('/trackers/money')}><ArrowLeft size={14} /> Money</a
|
||||
>
|
||||
<h1 class="text-2xl font-bold">Transactions</h1>
|
||||
<p class="text-sm opacity-70">
|
||||
@@ -138,7 +138,7 @@
|
||||
<div class="flex items-end gap-2">
|
||||
<Button type="submit">Apply</Button><Button
|
||||
variant="ghost"
|
||||
href={resolve('/projects/money-tracker/transactions')}>Reset</Button
|
||||
href={resolve('/trackers/money/transactions')}>Reset</Button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
@@ -365,7 +365,7 @@ test('shared dialogs have focus, Escape dismissal and mobile layouts without ove
|
||||
await expect(dialog).toHaveCount(0);
|
||||
await expect(page.getByRole('button', { name: label, exact: true })).toBeFocused();
|
||||
}
|
||||
await page.goto('/projects/money-tracker');
|
||||
await page.goto('/trackers/money');
|
||||
await page.getByRole('heading', { name: 'Accounts', exact: true }).waitFor();
|
||||
await page.locator('#accounts').getByRole('button', { name: 'Add', exact: true }).click();
|
||||
const moneyDialog = page.getByRole('dialog');
|
||||
|
||||
Reference in New Issue
Block a user