All checks were successful
Deploy production / deploy (push) Successful in 2m49s
115 lines
3.4 KiB
TypeScript
115 lines
3.4 KiB
TypeScript
import { db } from '#lib/server/db/index.js';
|
|
import { moneyAccounts, moneyTransactions } from '#lib/server/db/schema/index.js';
|
|
import { requireAdmin } from '#lib/server/authorization.js';
|
|
import { positiveId, validDate } from '#lib/server/money-tracker.js';
|
|
import {
|
|
and,
|
|
asc,
|
|
count,
|
|
desc,
|
|
eq,
|
|
gt,
|
|
gte,
|
|
ilike,
|
|
inArray,
|
|
isNotNull,
|
|
lt,
|
|
lte,
|
|
or,
|
|
sql,
|
|
sum,
|
|
type SQL
|
|
} from 'drizzle-orm';
|
|
import { redirect } from '@sveltejs/kit';
|
|
import type { PageServerLoad } from './$types';
|
|
|
|
const PAGE_SIZE = 50;
|
|
const sources = ['manual', 'csv', 'enablebanking', 'linked'] as const;
|
|
const sorts = ['newest', 'oldest', 'largest', 'smallest'] as const;
|
|
|
|
export const load: PageServerLoad = async (event) => {
|
|
if (!event.locals.user)
|
|
redirect(303, '/login?returnTo=' + encodeURIComponent(event.url.pathname + event.url.search));
|
|
const user = requireAdmin(event);
|
|
const params = event.url.searchParams;
|
|
const q = (params.get('q') ?? '').trim().slice(0, 100);
|
|
const accountIds = params
|
|
.getAll('account')
|
|
.map(positiveId)
|
|
.filter((id): id is number => id !== null);
|
|
const source = sources.find((item) => item === params.get('source')) ?? '';
|
|
const type = params.get('type') === 'in' ? 'in' : params.get('type') === 'out' ? 'out' : '';
|
|
const from = validDate(params.get('from')) ?? '';
|
|
const to = validDate(params.get('to')) ?? '';
|
|
const sort = sorts.find((item) => item === params.get('sort')) ?? 'newest';
|
|
const requestedPage = positiveId(params.get('page')) ?? 1;
|
|
|
|
const t = moneyTransactions;
|
|
const filters: (SQL | undefined)[] = [eq(t.userId, user.id)];
|
|
if (q) {
|
|
const pattern = '%' + q.replace(/[\\%_]/g, (c) => '\\' + c) + '%';
|
|
filters.push(or(ilike(t.description, pattern), ilike(t.counterparty, pattern)));
|
|
}
|
|
if (accountIds.length) filters.push(inArray(t.accountId, accountIds));
|
|
if (source === 'linked') filters.push(and(eq(t.source, 'manual'), isNotNull(t.externalId)));
|
|
else if (source) filters.push(eq(t.source, source));
|
|
if (type === 'in') filters.push(gt(t.amountCents, 0));
|
|
if (type === 'out') filters.push(lt(t.amountCents, 0));
|
|
if (from) filters.push(gte(t.date, from));
|
|
if (to) filters.push(lte(t.date, to));
|
|
const where = and(...filters);
|
|
|
|
const [accounts, [totals]] = await Promise.all([
|
|
db
|
|
.select({
|
|
id: moneyAccounts.id,
|
|
name: moneyAccounts.name,
|
|
color: moneyAccounts.color,
|
|
scope: moneyAccounts.scope
|
|
})
|
|
.from(moneyAccounts)
|
|
.where(eq(moneyAccounts.userId, user.id))
|
|
.orderBy(asc(moneyAccounts.name)),
|
|
db
|
|
.select({ count: count(), sum: sum(t.amountCents) })
|
|
.from(t)
|
|
.where(where)
|
|
]);
|
|
const pages = Math.max(1, Math.ceil(totals.count / PAGE_SIZE));
|
|
const page = Math.min(requestedPage, pages);
|
|
const order =
|
|
sort === 'oldest'
|
|
? [asc(t.date), asc(t.id)]
|
|
: sort === 'largest'
|
|
? [desc(t.amountCents), desc(t.date)]
|
|
: sort === 'smallest'
|
|
? [asc(t.amountCents), desc(t.date)]
|
|
: [desc(t.date), desc(t.id)];
|
|
const rows = await db
|
|
.select({
|
|
id: t.id,
|
|
date: t.date,
|
|
description: t.description,
|
|
counterparty: t.counterparty,
|
|
amountCents: t.amountCents,
|
|
accountId: t.accountId,
|
|
source: t.source,
|
|
linked: sql<boolean>`${t.externalId} is not null`
|
|
})
|
|
.from(t)
|
|
.where(where)
|
|
.orderBy(...order)
|
|
.limit(PAGE_SIZE)
|
|
.offset((page - 1) * PAGE_SIZE);
|
|
|
|
return {
|
|
accounts,
|
|
rows,
|
|
total: totals.count,
|
|
sumCents: Number(totals.sum ?? 0),
|
|
page,
|
|
pages,
|
|
filters: { q, accountIds, source, type, from, to, sort }
|
|
};
|
|
};
|