refactor: enforce frontend view model boundaries
Frontend verify / commits (pull_request) Successful in 28s
Frontend verify / security (pull_request) Successful in 10s
Frontend verify / verify (pull_request) Successful in 8m19s

This commit is contained in:
2026-09-28 18:48:23 +03:00
parent 5b69003516
commit ca0ad4f824
287 changed files with 8558 additions and 4538 deletions
+25 -4
View File
@@ -9,24 +9,34 @@ import { moneyCents } from "../model/form-validation";
type BankCard
= paths["/api/v1/bank-cards/{id}"]["get"]["responses"][200]["content"]["application/json"];
type BankOperation
= paths["/api/v1/bank-cards/operations/{id}"]["get"]["responses"][200]["content"]["application/json"];
type BankOperations
= paths["/api/v1/bank-cards/{id}/operations"]["get"]["responses"][200]["content"]["application/json"];
type BankSummary
= paths["/api/v1/bank-cards/summary"]["get"]["responses"][200]["content"]["application/json"];
type CreateBankCard
= paths["/api/v1/bank-cards"]["post"]["requestBody"]["content"]["application/json"];
type CreateBankOperation
= paths["/api/v1/bank-cards/operations"]["post"]["requestBody"]["content"]["application/json"];
type CreateExpenseCategory
= paths["/api/v1/bank-cards/categories"]["post"]["requestBody"]["content"]["application/json"];
type ExpenseCategory
= paths["/api/v1/bank-cards/categories"]["get"]["responses"][200]["content"]["application/json"][number];
type UpdateBankCard
= paths["/api/v1/bank-cards/{id}"]["patch"]["requestBody"]["content"]["application/json"];
type UpdateBankOperation
= paths["/api/v1/bank-cards/operations/{id}"]["patch"]["requestBody"]["content"]["application/json"];
type UpdateExpenseCategory
= paths["/api/v1/bank-cards/categories/{id}"]["patch"]["requestBody"]["content"]["application/json"];
@@ -41,12 +51,13 @@ function sortBankSummary(data: BankSummary): BankSummary {
if (left === null || right === null)
throw new Error("Invalid bank summary amount");
if (left > right) return -1;
return left < right ? 1 : 0;
}),
};
}
function useBankCard(id: string | undefined) {
function useBankCard(id?: string) {
return useAuthenticatedQuery({
...apiQueryClient.queryOptions("get", "/api/v1/bank-cards/{id}", {
params: { path: { id: id ?? "" } },
@@ -83,7 +94,7 @@ function useBankExpenses(
});
}
function useBankOperation(id: string | undefined) {
function useBankOperation(id?: string) {
return useAuthenticatedQuery({
...apiQueryClient.queryOptions(
"get",
@@ -105,6 +116,7 @@ function useBankOperations(
},
) {
const isAll = query.limit === "all";
const parameters = {
path: { id: id ?? "" },
query: {
@@ -113,11 +125,13 @@ function useBankOperations(
page: isAll ? 1 : query.page,
},
};
const options = apiQueryClient.queryOptions(
"get",
"/api/v1/bank-cards/{id}/operations",
{ params: parameters },
);
// eslint-disable-next-line @tanstack/query/exhaustive-deps -- Generated options.queryKey already contains params; retain the shared cache identity.
return useAuthenticatedQuery({
enabled: Boolean(id && (query.month || (query.from && query.to))),
@@ -126,6 +140,7 @@ function useBankOperations(
previousQuery,
) => {
const previous = previousQuery?.queryKey[2]?.params;
return !isAll
&& previousQuery?.queryKey.length === options.queryKey.length
&& previous?.path?.id === id
@@ -138,6 +153,7 @@ function useBankOperations(
},
queryFn: async ({ signal }) => {
const items: BankOperation[] = [];
for (let page = parameters.query.page; ; page++) {
const { data, error } = await apiClient.GET(
"/api/v1/bank-cards/{id}/operations",
@@ -146,6 +162,7 @@ function useBankOperations(
signal,
},
);
// eslint-disable-next-line @typescript-eslint/only-throw-error -- Preserve generated ProblemDetails for apiErrorCode and field-error consumers.
if (error) throw error;
if (!data) throw new Error("Missing bank operations response");
@@ -154,18 +171,22 @@ function useBankOperations(
if (!data.hasNext) return { ...data, hasNext: false, items, page: 1 };
}
},
queryKey: isAll ? ([...options.queryKey, "all"] as const) : options.queryKey,
queryKey: isAll
? ([...options.queryKey, "all"] as const)
: options.queryKey,
});
}
function useBankSummary(
period: string | { from: string; to: string },
period: string
| { from: string; to: string },
cardId?: string,
) {
const query
= typeof period === "string"
? { cardId, month: period }
: { ...period, cardId };
return useAuthenticatedQuery({
...apiQueryClient.queryOptions("get", "/api/v1/bank-cards/summary", {
params: { query },