diff --git a/src/components/UserHistory.svelte b/src/components/UserHistory.svelte index 6ff874d7fc7988dc73e9336d9322acec60e8ba99..9fe19e226746da623a27942ec78b07cb0b425e18 100644 --- a/src/components/UserHistory.svelte +++ b/src/components/UserHistory.svelte @@ -17,11 +17,14 @@ function fetchMoreTransactions(){ if(!hasMoreTransactions || isFetching) return; isFetching = true; - fetch("/api/transactions?" + new URLSearchParams({ + fetch("/api/transactions?" + new URLSearchParams(Object.fromEntries(Object.entries({ userId, - before: transactions[transactions.length-1].createdAt.getTime(), - balance: transactions[transactions.length-1].balance-transactions[transactions.length-1].difference - }), { + balance: transactions[transactions.length-1].balance-transactions[transactions.length-1].difference, + lastItemTransactionId: transactions.findLast(t=>t.type=="item")?.id, + lastMoneyTransactionId: transactions.findLast(t=>t.type=="transaction")?.id, + lastMoneyTransferId: transactions.findLast(t=>t.type=="transfer")?.id, + lastVoucherUseTime: transactions.findLast(t=>t.type=="voucher")?.id + }).filter(([,value])=>value!==undefined))), { method: "GET" }).then(r=>r.text()).then(devalue.parse).then(({transactions: prevTransactions, hasMore})=>{ if(prevTransactions.length > 0) transactions = [...transactions, ...prevTransactions]; diff --git a/src/routes/admin/user/[id]/history/+page.server.js b/src/routes/admin/user/[id]/history/+page.server.js index 0ddcab026a913ba346be967be15a7a2f68528957..52f6f6e050fcda60c5ac2ee5eac9cdf41f09ca14 100644 --- a/src/routes/admin/user/[id]/history/+page.server.js +++ b/src/routes/admin/user/[id]/history/+page.server.js @@ -1,9 +1,9 @@ -import { fetchTransactions } from '../../../../api/transactions.js'; -import { getUser } from '../../../api/users.js'; +import { fetchTransactions } from '../../../../api/transactions'; +import { getUser } from '../../../api/users'; export async function load(event) { const userId = parseInt(event.params.id); const user = await getUser(userId); - const { transactions, hasMore } = await fetchTransactions(userId, user.balance, Date.now(), 100); + const { transactions, hasMore } = await fetchTransactions(userId, user.balance, {}, 100); return { transactions, hasMore, user }; }; diff --git a/src/routes/api/[slug]/+server.js b/src/routes/api/[slug]/+server.js index d8362ed8896e6c0fffb0dd3c1ff4b492065ad02d..3f0c87b954891aa7c8f76cca09eac1b55fa74d67 100644 --- a/src/routes/api/[slug]/+server.js +++ b/src/routes/api/[slug]/+server.js @@ -117,15 +117,24 @@ export async function GET(req){ try { const userId = parseInt(req.url.searchParams.get("userId")); const balance = parseInt(req.url.searchParams.get("balance")); - const before = parseInt(req.url.searchParams.get("before")); - if (isNaN(userId) || isNaN(balance) || isNaN(before)) { + const lastItemTransactionId = req.url.searchParams.get("lastItemTransactionId"); + const lastMoneyTransactionId = req.url.searchParams.get("lastMoneyTransactionId"); + const lastMoneyTransferId = req.url.searchParams.get("lastMoneyTransferId"); + const lastVoucherUseId = req.url.searchParams.get("lastVoucherUseId"); + const beforeIds = { + itemTransactions: lastItemTransactionId ? parseInt(lastItemTransactionId) : undefined, + moneyTransactions: lastMoneyTransactionId ? parseInt(lastMoneyTransactionId) : undefined, + moneyTransfers: lastMoneyTransferId ? parseInt(lastMoneyTransferId) : undefined, + voucherUses: lastVoucherUseId ? parseInt(lastVoucherUseId) : undefined + } + if (isNaN(userId) || isNaN(balance) || Object.values(beforeIds).some(id => id!==undefined && isNaN(id))) { return new Response(JSON.stringify({ message: "Invalid fields" }), { status: 400 }); } if(userId !== req.locals.user.id && !(req.locals.user.perms & Flags.ADMIN)){ return new Response(JSON.stringify({ message: "You can't view other users' transactions" }), { status: 403 }); } try { - const transactions = await fetchTransactions(userId, balance, before, 100); + const transactions = await fetchTransactions(userId, balance, beforeIds, 100); // use devalue because JSON.stringify/JSON.parse doesn't really support Date return new Response(devalue.stringify(transactions), { status: 200 }); } catch (error) { diff --git a/src/routes/api/transactions.js b/src/routes/api/transactions.ts similarity index 74% rename from src/routes/api/transactions.js rename to src/routes/api/transactions.ts index 850ec9015f396a111a6ec621949e2a36dd3ced30..3e62f3116bdce33fc07c45e5efd2f96454c1851c 100644 --- a/src/routes/api/transactions.js +++ b/src/routes/api/transactions.ts @@ -94,45 +94,50 @@ export async function useVoucher(userId, voucherCode){ }); } -export async function getMoneyTransactions(userId, before=Date.now(), limit=100) { +export async function getMoneyTransactions(userId, beforeId?: number, limit=100) { return db.transaction.findMany({ take: limit, - where: { userId, createdAt: { lt: new Date(before) } }, - orderBy: { createdAt: "desc" } + where: { userId, id: { lt: beforeId } }, + orderBy: { id: "desc" } }); } -export async function getItemTransactions(userId, before=Date.now(), limit=100) { +export async function getItemTransactions(userId, beforeId?: number, limit=100) { return db.itemTransaction.findMany({ take: limit, - where: { userId, createdAt: { lt: new Date(before) } }, - orderBy: { createdAt: "desc" }, + where: { userId, id: { lt: beforeId } }, + orderBy: { id: "desc" }, include: { item: true } }); } -export async function getMoneyTransfers(userId, before=Date.now(), limit=100) { +export async function getMoneyTransfers(userId, beforeId?: number, limit=100) { return db.moneyTransfer.findMany({ take: limit, - where: { OR: [{ fromId: userId }, { toId: userId }], createdAt: { lt: new Date(before) } }, - orderBy: { createdAt: "desc" }, + where: { OR: [{ fromId: userId }, { toId: userId }], id: { lt: beforeId } }, + orderBy: { id: "desc" }, include: { from: true, to: true } }); } -export async function getUsedVouchers(usedById, before=Date.now(), limit=100) { +export async function getUsedVouchers(usedById, beforeId: number, limit=100) { + const args: {skip: 1, cursor: {id: number}}|{} = beforeId ? { skip: 1, cursor: { id: beforeId } } : {}; // skip cursor if present return db.voucher.findMany({ take: limit, - where: { usedById, createdAt: { lt: new Date(before) } }, - orderBy: { usedAt: "desc" } + ...args, + where: { usedById }, + orderBy: [{ id: "desc" }, { usedAt: "desc" }] // in case usedAt is the same for multiple vouchers, order by id to make sure the order is consistent }); } -// TODO somehow fix this -// this skips transactions if `before` is the same as the createdAt date of the previous transaction -// e.g. you could sort by id instead of createdAt and provide the last id of each transaction type as -// replacement for `before` -export async function fetchTransactions(userId, balanceAfterwards, before, limit) { +type BeforeIds = { + moneyTransactions?: number, + itemTransactions?: number, + moneyTransfers?: number, + voucherUses?: number +}; + +export async function fetchTransactions(userId: number, balanceAfterwards: number, before: BeforeIds, limit: number) { /* For every type of transaction that changes the user's balance the limit of transactions to return is used. This means at most we fetch 4*limit transactions from the database. @@ -149,32 +154,36 @@ export async function fetchTransactions(userId, balanceAfterwards, before, limit /* [limit]+1 items are fetched because then we can check if there are more items to fetch. */ - const moneyTransactions = (await getMoneyTransactions(userId, before, limit+1)).map(transaction => ({ + const moneyTransactions = (await getMoneyTransactions(userId, before.moneyTransactions, limit+1)).map(transaction => ({ type: "transaction", name: transaction.amount > 0 ? "Aufladen" : "Auszahlung", difference: transaction.amount, createdAt: transaction.createdAt, - verified: !!transaction.verifiedById + verified: !!transaction.verifiedById, + id: transaction.id })); - const itemTransactions = (await getItemTransactions(userId, before, limit+1)).map(transaction => ({ + const itemTransactions = (await getItemTransactions(userId, before.itemTransactions, limit+1)).map(transaction => ({ type: "item", name: transaction.item.name, difference: -(transaction.price + transaction.premium), premium: transaction.premium, createdAt: transaction.createdAt, image: transaction.item.image, + id: transaction.id })); - const moneyTransfers = (await getMoneyTransfers(userId, before, limit+1)).map(transfer => ({ + const moneyTransfers = (await getMoneyTransfers(userId, before.moneyTransactions, limit+1)).map(transfer => ({ type: "transfer", name: transfer.fromId === userId ? `Gesendet an ${transfer.to.name}` : `Empfangen von ${transfer.from.name}`, difference: transfer.fromId === userId ? -transfer.amount : transfer.amount, - createdAt: transfer.createdAt + createdAt: transfer.createdAt, + id: transfer.id })); - const voucherUses = (await getUsedVouchers(userId, before, limit+1)).map(voucher => ({ + const voucherUses = (await getUsedVouchers(userId, before.voucherUses, limit+1)).map(voucher => ({ type: "voucher", name: `Gutschein ${voucher.code} eingelöst`, difference: voucher.amount, - createdAt: voucher.usedAt // use usedAt because voucher creation time is not relevant + createdAt: voucher.usedAt, // use usedAt because voucher creation time is not relevant + id: voucher.id })); let transactions = [...moneyTransactions, ...itemTransactions, ...moneyTransfers, ...voucherUses] .sort((a, b) => b.createdAt - a.createdAt); diff --git a/src/routes/history/+page.server.js b/src/routes/history/+page.server.js index 6a1fe6a7e9ff92b2fb1fe46b5eca8cee275b3b24..dc7209424a7bdbdbde9750b1cebf82838bf27bcf 100644 --- a/src/routes/history/+page.server.js +++ b/src/routes/history/+page.server.js @@ -3,6 +3,6 @@ import { fetchTransactions } from "../api/transactions"; export async function load(event) { const userId = event.locals.user.id; const balance = event.locals.user.balance; - const {transactions, hasMore} = await fetchTransactions(userId, balance, Date.now(), 100); + const {transactions, hasMore} = await fetchTransactions(userId, balance, {}, 100); return { transactions, hasMore, userId }; };