All files / apps/web/src/handlers/debts get-by-peer-paged.ts

100% Statements 23/23
100% Branches 4/4
100% Functions 11/11
100% Lines 22/22

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117                                6x                   6x       14x           14x 2x         12x 1x         11x     11x                     11x 11x     11x           9x       9x             11x                 11x       11x     28x       6x 11x 14x     6x                
import { TRPCError } from "@trpc/server";
import { z } from "zod";
 
import {
	debtsByPeerFiltersSchema,
	limitSchema,
	offsetSchema,
} from "~app/utils/validation";
import type { DebtId } from "~db/ids";
import { queueCallFactory } from "~web/handlers/batch";
import type { AuthorizedContext } from "~web/handlers/context";
import { authProcedure } from "~web/handlers/trpc";
import { peerIdSchema } from "~web/handlers/validation";
import type { GeneralOutput } from "~web/utils/batch";
import { queueList } from "~web/utils/batch";
 
const inputSchema = z.strictObject({
	peerId: peerIdSchema,
	cursor: offsetSchema,
	limit: limitSchema,
	filters: debtsByPeerFiltersSchema.optional().default({}),
});
 
type Input = z.infer<typeof inputSchema>;
type Output = GeneralOutput<DebtId> & { count: number };
 
const fetchPage = async (
	{ database, auth }: AuthorizedContext,
	input: Input,
) => {
	const peer = await database
		.selectFrom("peers")
		.where("peers.id", "=", input.peerId)
		.select("peers.ownerUserId")
		.limit(1)
		.executeTakeFirst();
	if (!peer) {
		throw new TRPCError({
			code: "NOT_FOUND",
			message: `Peer "${input.peerId}" does not exist.`,
		});
	}
	if (peer.ownerUserId !== auth.userId) {
		throw new TRPCError({
			code: "FORBIDDEN",
			message: `Peer "${input.peerId}" is not owned by "${auth.email}".`,
		});
	}
	const currencySums = database
		.selectFrom("debts")
		.where((eb) =>
			eb.and({
				"debts.peerId": input.peerId,
				"debts.ownerUserId": auth.userId,
			}),
		)
		.select([
			"debts.currencyCode",
			database.fn.sum<string>("debts.amount").as("sum"),
		])
		.groupBy("debts.currencyCode");
 
	const debts = database
		.with("currencySums", () => currencySums)
		.selectFrom("debts")
		.where((eb) =>
			eb.and({
				"debts.peerId": input.peerId,
				"debts.ownerUserId": auth.userId,
			}),
		)
		.$if(!input.filters.showResolved, (qb) =>
			qb.where(
				"debts.currencyCode",
				"in",
				database
					.with("currencySums", () => currencySums)
					.selectFrom("currencySums")
					.select("currencyCode")
					.where("sum", "!=", "0"),
			),
		);
 
	const [paginatedDebts, totalCount] = await Promise.all([
		debts
			.select(["debts.id"])
			.orderBy("debts.timestamp", "desc")
			.orderBy("debts.id")
			.offset(input.cursor)
			.limit(input.limit)
			.execute(),
		debts
			.select((eb) => eb.fn.count<number>("debts.id").as("count"))
			.executeTakeFirstOrThrow(),
	]);
 
	return {
		count: totalCount.count,
		cursor: input.cursor,
		items: paginatedDebts.map((debt) => debt.id),
	};
};
 
const queueDebtList = queueCallFactory<AuthorizedContext, Input, Output>(
	(ctx) => async (inputs) =>
		queueList<Input, DebtId, Output>(inputs, (value) => fetchPage(ctx, value)),
);
 
export const procedure = authProcedure
	.meta({
		title: "Get debts by peer, paged",
		description:
			"Returns a page of debt ids owned by the current  user for a given peerId, optionally excluding resolved currencies.",
	})
	.input(inputSchema)
	.query(queueDebtList);