/* This file is part of GNU Taler (C) 2022 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ import { HttpResponse, HttpResponseOk, HttpResponsePaginated, RequestError, useApiContext, } from "@gnu-taler/web-util/lib/index.browser"; import { useEffect, useMemo, useState } from "preact/hooks"; import useSWR from "swr"; import { useBackendContext } from "../context/backend.js"; import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js"; import { useAuthenticatedBackend, useMatchMutate } from "./backend.js"; export function useAdminAccountAPI(): AdminAccountAPI { const { request } = useAuthenticatedBackend(); const mutateAll = useMatchMutate(); const { state } = useBackendContext(); if (state.status === "loggedOut") { throw Error("access-api can't be used when the user is not logged In"); } const createAccount = async ( data: SandboxBackend.Circuit.CircuitAccountRequest, ): Promise> => { const res = await request(`circuit-api/accounts`, { method: "POST", data, contentType: "json", }); await mutateAll(/.*circuit-api\/accounts.*/); return res; }; const updateAccount = async ( account: string, data: SandboxBackend.Circuit.CircuitAccountReconfiguration, ): Promise> => { const res = await request(`circuit-api/accounts/${account}`, { method: "PATCH", data, contentType: "json", }); await mutateAll(/.*circuit-api\/accounts.*/); return res; }; const deleteAccount = async ( account: string, ): Promise> => { const res = await request(`circuit-api/accounts/${account}`, { method: "DELETE", contentType: "json", }); await mutateAll(/.*circuit-api\/accounts.*/); return res; }; const changePassword = async ( account: string, data: SandboxBackend.Circuit.AccountPasswordChange, ): Promise> => { const res = await request(`circuit-api/accounts/${account}/auth`, { method: "PATCH", data, contentType: "json", }); return res; }; return { createAccount, deleteAccount, updateAccount, changePassword }; } export function useCircuitAccountAPI(): CircuitAccountAPI { const { request } = useAuthenticatedBackend(); const mutateAll = useMatchMutate(); const { state } = useBackendContext(); if (state.status === "loggedOut") { throw Error("access-api can't be used when the user is not logged In"); } const account = state.username; const updateAccount = async ( data: SandboxBackend.Circuit.CircuitAccountReconfiguration, ): Promise> => { const res = await request(`circuit-api/accounts/${account}`, { method: "PATCH", data, contentType: "json", }); await mutateAll(/.*circuit-api\/accounts.*/); return res; }; const changePassword = async ( data: SandboxBackend.Circuit.AccountPasswordChange, ): Promise> => { const res = await request(`circuit-api/accounts/${account}/auth`, { method: "PATCH", data, contentType: "json", }); return res; }; return { updateAccount, changePassword }; } export interface AdminAccountAPI { createAccount: ( data: SandboxBackend.Circuit.CircuitAccountRequest, ) => Promise>; deleteAccount: (account: string) => Promise>; updateAccount: ( account: string, data: SandboxBackend.Circuit.CircuitAccountReconfiguration, ) => Promise>; changePassword: ( account: string, data: SandboxBackend.Circuit.AccountPasswordChange, ) => Promise>; } export interface CircuitAccountAPI { updateAccount: ( data: SandboxBackend.Circuit.CircuitAccountReconfiguration, ) => Promise>; changePassword: ( data: SandboxBackend.Circuit.AccountPasswordChange, ) => Promise>; } export interface InstanceTemplateFilter { //FIXME: add filter to the template list position?: string; } async function getBusinessStatus( request: ReturnType["request"], url: string, basicAuth: { username: string; password: string }, ): Promise { try { const result = await request< HttpResponseOk >(url, `circuit-api/accounts/${basicAuth.username}`, { basicAuth }); return result.ok; } catch (error) { return false; } } export function useBusinessAccountFlag(): boolean | undefined { const [isBusiness, setIsBusiness] = useState(); const { state } = useBackendContext(); const { request } = useApiContext(); const creds = state.status === "loggedOut" ? undefined : { username: state.username, password: state.password }; useEffect(() => { if (!creds) return; getBusinessStatus(request, state.url, creds) .then((result) => { setIsBusiness(result); }) .catch((error) => { setIsBusiness(false); }); }); return isBusiness; } export function useBusinessAccountDetails( account: string, ): HttpResponse< SandboxBackend.Circuit.CircuitAccountData, SandboxBackend.SandboxError > { const { fetcher } = useAuthenticatedBackend(); const { data, error } = useSWR.default< HttpResponseOk, RequestError >([`circuit-api/accounts/${account}`], fetcher, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, refreshWhenOffline: false, errorRetryCount: 0, errorRetryInterval: 1, shouldRetryOnError: false, keepPreviousData: true, }); if (data) return data; if (error) return error.info; return { loading: true }; } interface PaginationFilter { account?: string; page?: number; } export function useBusinessAccounts( args?: PaginationFilter, ): HttpResponsePaginated< SandboxBackend.Circuit.CircuitAccounts, SandboxBackend.SandboxError > { const { sandboxAccountsFetcher } = useAuthenticatedBackend(); const [page, setPage] = useState(0); const { data: afterData, error: afterError, // isValidating: loadingAfter, } = useSWR.default< HttpResponseOk, RequestError >( [`circuit-api/accounts`, args?.page, PAGE_SIZE, args?.account], sandboxAccountsFetcher, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, refreshWhenOffline: false, errorRetryCount: 0, errorRetryInterval: 1, shouldRetryOnError: false, keepPreviousData: true, }, ); // const [lastAfter, setLastAfter] = useState< // HttpResponse // >({ loading: true }); // useEffect(() => { // if (afterData) setLastAfter(afterData); // }, [afterData]); // if the query returns less that we ask, then we have reach the end or beginning const isReachingEnd = afterData && afterData.data?.customers?.length < PAGE_SIZE; const isReachingStart = false; const pagination = { isReachingEnd, isReachingStart, loadMore: () => { if (!afterData || isReachingEnd) return; if (afterData.data?.customers?.length < MAX_RESULT_SIZE) { setPage(page + 1); } }, loadMorePrev: () => { null; }, }; const result = useMemo(() => { const customers = !afterData ? [] : afterData?.data?.customers ?? []; return { ok: true as const, data: { customers }, ...pagination }; }, [afterData?.data]); if (afterError) return afterError.info; if (afterData) { return result; } // if (loadingAfter) // return { loading: true, data: { customers } }; // if (afterData) { // return { ok: true, data: { customers }, ...pagination }; // } return { loading: true }; } export function useCashouts(): HttpResponse< (SandboxBackend.Circuit.CashoutStatusResponse & WithId)[], SandboxBackend.SandboxError > { const { fetcher, multiFetcher } = useAuthenticatedBackend(); const { data: list, error: listError } = useSWR.default< HttpResponseOk, RequestError >([`circuit-api/cashouts`], fetcher, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, refreshWhenOffline: false, }); const paths = (list?.data.cashouts || []).map( (cashoutId) => `circuit-api/cashouts/${cashoutId}`, ); const { data: cashouts, error: productError } = useSWR.default< HttpResponseOk[], RequestError >([paths], multiFetcher, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, refreshWhenOffline: false, }); if (listError) return listError.info; if (productError) return productError.info; if (cashouts) { const dataWithId = cashouts.map((d) => { //take the id from the queried url return { ...d.data, id: d.info?.url.replace(/.*\/cashouts\//, "") || "", }; }); return { ok: true, data: dataWithId }; } return { loading: true }; }