All files / apps/web/src/handlers/debts get.ts

100% Statements 23/23
100% Branches 10/10
100% Functions 10/10
100% Lines 21/21

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                    6x 4x     4x                             6x                     6x                 4x 4x   4x   4x 4x 4x 3x   4x 4x   4x 2x 1x         1x                         2x               4x                 6x                
import { TRPCError } from "@trpc/server";
import { pick } from "remeda";
import { z } from "zod";
 
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 { debtIdSchema } from "~web/handlers/validation";
 
const fetchDebts = async ({ database }: AuthorizedContext, ids: DebtId[]) =>
	database
		.selectFrom("debts")
		.where("debts.id", "in", ids)
		.leftJoin("peers", (qb) => qb.onRef("debts.peerId", "=", "peers.id"))
		.select([
			"debts.id",
			"debts.ownerUserId",
			"debts.amount",
			"debts.currencyCode",
			"debts.note",
			"debts.timestamp",
			"debts.peerId",
			"debts.updatedAt",
			"debts.receiptId",
			"peers.connectedUserId",
		])
		.execute();
 
const mapDebt = (debt: Awaited<ReturnType<typeof fetchDebts>>[number]) => ({
	id: debt.id,
	peerId: debt.peerId,
	receiptId: debt.receiptId || undefined,
	note: debt.note,
	amount: Number(debt.amount),
	currencyCode: debt.currencyCode,
	timestamp: debt.timestamp,
	updatedAt: debt.updatedAt,
});
 
const queueDebt = queueCallFactory<
	AuthorizedContext,
	{ id: DebtId },
	ReturnType<typeof mapDebt> & {
		their?: Pick<
			ReturnType<typeof mapDebt>,
			"amount" | "timestamp" | "currencyCode" | "updatedAt"
		>;
	}
>((ctx) => async (inputs) => {
	const debts = await fetchDebts(
		ctx,
		inputs.map(({ id }) => id),
	);
	return inputs.map((input) => {
		const localDebts = debts.filter(({ id }) => id === input.id);
		const ourDebt = localDebts.find(
			({ ownerUserId }) => ownerUserId === ctx.auth.userId,
		);
		const theirDebt = localDebts.find(
			({ ownerUserId }) => ownerUserId !== ctx.auth.userId,
		);
		if (!ourDebt) {
			if (!theirDebt) {
				return new TRPCError({
					code: "NOT_FOUND",
					message: `Debt "${input.id}" does not exist.`,
				});
			}
			return new TRPCError({
				code: "FORBIDDEN",
				message: `You don't have access to debt "${input.id}".`,
			});
		}
		/* c8 ignore start */
		if (theirDebt && theirDebt.connectedUserId !== ctx.auth.userId) {
			return new TRPCError({
				code: "INTERNAL_SERVER_ERROR",
				message: `Foreign debt "${input.id}" is not connected to ours.`,
			});
		}
		/* c8 ignore stop */
		const mappedTheirDebt = theirDebt
			? pick(mapDebt(theirDebt), [
					"amount",
					"timestamp",
					"currencyCode",
					"updatedAt",
				])
			: undefined;
		return {
			...mapDebt(ourDebt),
			their: mappedTheirDebt
				? { ...mappedTheirDebt, amount: -mappedTheirDebt.amount }
				: undefined,
		};
	});
});
 
export const procedure = authProcedure
	.meta({
		title: "Get debt",
		description:
			"Returns a debt by id owned by the current  user, including the counterparty's mirrored debt if one exists.",
	})
	.input(z.strictObject({ id: debtIdSchema }))
	.query(queueDebt);