user_receiving_invoice multi column index, and timestamp + serial_id cursor

This commit is contained in:
Mothana 2025-07-22 17:58:31 +04:00
parent 5d0b5a1c7b
commit 44870aa667
11 changed files with 156 additions and 83 deletions

View file

@ -33,13 +33,15 @@ import { UserOffer1733502626042 } from './build/src/services/storage/migrations/
import { ManagementGrant1751307732346 } from './build/src/services/storage/migrations/1751307732346-management_grant.js' import { ManagementGrant1751307732346 } from './build/src/services/storage/migrations/1751307732346-management_grant.js'
import { InvoiceCallbackUrls1752425992291 } from './build/src/services/storage/migrations/1752425992291-invoice_callback_urls.js' import { InvoiceCallbackUrls1752425992291 } from './build/src/services/storage/migrations/1752425992291-invoice_callback_urls.js'
import { OldSomethingLeftover1753106599604 } from './build/src/services/storage/migrations/1753106599604-old_something_leftover.js' import { OldSomethingLeftover1753106599604 } from './build/src/services/storage/migrations/1753106599604-old_something_leftover.js'
import { UserReceivingInvoiceIdx1753109184611 } from './build/src/services/storage/migrations/1753109184611-user_receiving_invoice_idx.js'
export default new DataSource({ export default new DataSource({
type: "sqlite", type: "sqlite",
database: "db.sqlite", database: "db.sqlite",
// logging: true, // logging: true,
migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, CreateInviteTokenTable1721751414878, migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, CreateInviteTokenTable1721751414878,
PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043,
UserOffer1733502626042, ManagementGrant1751307732346, InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604], UserOffer1733502626042, ManagementGrant1751307732346, InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611],
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment, entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo, TrackedProvider, InviteToken, DebitAccess, UserOffer, ManagementGrant], UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo, TrackedProvider, InviteToken, DebitAccess, UserOffer, ManagementGrant],
// synchronize: true, // synchronize: true,

View file

@ -1224,12 +1224,12 @@ The nostr server will send back a message response, and inside the body there wi
- __offer_id__: _string_ - __offer_id__: _string_
### GetUserOperationsRequest ### GetUserOperationsRequest
- __latestIncomingInvoice__: _number_ - __latestIncomingInvoice__: _[OperationsCursor](#OperationsCursor)_
- __latestIncomingTx__: _number_ - __latestIncomingTx__: _[OperationsCursor](#OperationsCursor)_
- __latestIncomingUserToUserPayment__: _number_ - __latestIncomingUserToUserPayment__: _[OperationsCursor](#OperationsCursor)_
- __latestOutgoingInvoice__: _number_ - __latestOutgoingInvoice__: _[OperationsCursor](#OperationsCursor)_
- __latestOutgoingTx__: _number_ - __latestOutgoingTx__: _[OperationsCursor](#OperationsCursor)_
- __latestOutgoingUserToUserPayment__: _number_ - __latestOutgoingUserToUserPayment__: _[OperationsCursor](#OperationsCursor)_
- __max_size__: _number_ - __max_size__: _number_
### GetUserOperationsResponse ### GetUserOperationsResponse
@ -1431,6 +1431,10 @@ The nostr server will send back a message response, and inside the body there wi
### OpenChannelResponse ### OpenChannelResponse
- __channel_id__: _string_ - __channel_id__: _string_
### OperationsCursor
- __id__: _number_
- __ts__: _number_
### PayAddressRequest ### PayAddressRequest
- __address__: _string_ - __address__: _string_
- __amoutSats__: _number_ - __amoutSats__: _number_
@ -1601,9 +1605,9 @@ The nostr server will send back a message response, and inside the body there wi
- __type__: _[UserOperationType](#UserOperationType)_ - __type__: _[UserOperationType](#UserOperationType)_
### UserOperations ### UserOperations
- __fromIndex__: _number_ - __fromIndex__: _[OperationsCursor](#OperationsCursor)_
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_ - __operations__: ARRAY of: _[UserOperation](#UserOperation)_
- __toIndex__: _number_ - __toIndex__: _[OperationsCursor](#OperationsCursor)_
### UsersInfo ### UsersInfo
- __always_been_inactive__: _number_ - __always_been_inactive__: _number_

View file

@ -309,12 +309,12 @@ type GetUserOfferInvoicesReq struct {
Offer_id string `json:"offer_id"` Offer_id string `json:"offer_id"`
} }
type GetUserOperationsRequest struct { type GetUserOperationsRequest struct {
Latestincominginvoice int64 `json:"latestIncomingInvoice"` Latestincominginvoice *OperationsCursor `json:"latestIncomingInvoice"`
Latestincomingtx int64 `json:"latestIncomingTx"` Latestincomingtx *OperationsCursor `json:"latestIncomingTx"`
Latestincomingusertouserpayment int64 `json:"latestIncomingUserToUserPayment"` Latestincomingusertouserpayment *OperationsCursor `json:"latestIncomingUserToUserPayment"`
Latestoutgoinginvoice int64 `json:"latestOutgoingInvoice"` Latestoutgoinginvoice *OperationsCursor `json:"latestOutgoingInvoice"`
Latestoutgoingtx int64 `json:"latestOutgoingTx"` Latestoutgoingtx *OperationsCursor `json:"latestOutgoingTx"`
Latestoutgoingusertouserpayment int64 `json:"latestOutgoingUserToUserPayment"` Latestoutgoingusertouserpayment *OperationsCursor `json:"latestOutgoingUserToUserPayment"`
Max_size int64 `json:"max_size"` Max_size int64 `json:"max_size"`
} }
type GetUserOperationsResponse struct { type GetUserOperationsResponse struct {
@ -516,6 +516,10 @@ type OpenChannelRequest struct {
type OpenChannelResponse struct { type OpenChannelResponse struct {
Channel_id string `json:"channel_id"` Channel_id string `json:"channel_id"`
} }
type OperationsCursor struct {
Id int64 `json:"id"`
Ts int64 `json:"ts"`
}
type PayAddressRequest struct { type PayAddressRequest struct {
Address string `json:"address"` Address string `json:"address"`
Amoutsats int64 `json:"amoutSats"` Amoutsats int64 `json:"amoutSats"`
@ -686,9 +690,9 @@ type UserOperation struct {
Type UserOperationType `json:"type"` Type UserOperationType `json:"type"`
} }
type UserOperations struct { type UserOperations struct {
Fromindex int64 `json:"fromIndex"` Fromindex *OperationsCursor `json:"fromIndex"`
Operations []UserOperation `json:"operations"` Operations []UserOperation `json:"operations"`
Toindex int64 `json:"toIndex"` Toindex *OperationsCursor `json:"toIndex"`
} }
type UsersInfo struct { type UsersInfo struct {
Always_been_inactive int64 `json:"always_been_inactive"` Always_been_inactive int64 `json:"always_been_inactive"`

View file

@ -1775,46 +1775,52 @@ export const GetUserOfferInvoicesReqValidate = (o?: GetUserOfferInvoicesReq, opt
} }
export type GetUserOperationsRequest = { export type GetUserOperationsRequest = {
latestIncomingInvoice: number latestIncomingInvoice: OperationsCursor
latestIncomingTx: number latestIncomingTx: OperationsCursor
latestIncomingUserToUserPayment: number latestIncomingUserToUserPayment: OperationsCursor
latestOutgoingInvoice: number latestOutgoingInvoice: OperationsCursor
latestOutgoingTx: number latestOutgoingTx: OperationsCursor
latestOutgoingUserToUserPayment: number latestOutgoingUserToUserPayment: OperationsCursor
max_size: number max_size: number
} }
export const GetUserOperationsRequestOptionalFields: [] = [] export const GetUserOperationsRequestOptionalFields: [] = []
export type GetUserOperationsRequestOptions = OptionsBaseMessage & { export type GetUserOperationsRequestOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: [] checkOptionalsAreSet?: []
latestIncomingInvoice_CustomCheck?: (v: number) => boolean latestIncomingInvoice_Options?: OperationsCursorOptions
latestIncomingTx_CustomCheck?: (v: number) => boolean latestIncomingTx_Options?: OperationsCursorOptions
latestIncomingUserToUserPayment_CustomCheck?: (v: number) => boolean latestIncomingUserToUserPayment_Options?: OperationsCursorOptions
latestOutgoingInvoice_CustomCheck?: (v: number) => boolean latestOutgoingInvoice_Options?: OperationsCursorOptions
latestOutgoingTx_CustomCheck?: (v: number) => boolean latestOutgoingTx_Options?: OperationsCursorOptions
latestOutgoingUserToUserPayment_CustomCheck?: (v: number) => boolean latestOutgoingUserToUserPayment_Options?: OperationsCursorOptions
max_size_CustomCheck?: (v: number) => boolean max_size_CustomCheck?: (v: number) => boolean
} }
export const GetUserOperationsRequestValidate = (o?: GetUserOperationsRequest, opts: GetUserOperationsRequestOptions = {}, path: string = 'GetUserOperationsRequest::root.'): Error | null => { export const GetUserOperationsRequestValidate = (o?: GetUserOperationsRequest, opts: GetUserOperationsRequestOptions = {}, path: string = 'GetUserOperationsRequest::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.latestIncomingInvoice !== 'number') return new Error(`${path}.latestIncomingInvoice: is not a number`) const latestIncomingInvoiceErr = OperationsCursorValidate(o.latestIncomingInvoice, opts.latestIncomingInvoice_Options, `${path}.latestIncomingInvoice`)
if (opts.latestIncomingInvoice_CustomCheck && !opts.latestIncomingInvoice_CustomCheck(o.latestIncomingInvoice)) return new Error(`${path}.latestIncomingInvoice: custom check failed`) if (latestIncomingInvoiceErr !== null) return latestIncomingInvoiceErr
if (typeof o.latestIncomingTx !== 'number') return new Error(`${path}.latestIncomingTx: is not a number`)
if (opts.latestIncomingTx_CustomCheck && !opts.latestIncomingTx_CustomCheck(o.latestIncomingTx)) return new Error(`${path}.latestIncomingTx: custom check failed`)
if (typeof o.latestIncomingUserToUserPayment !== 'number') return new Error(`${path}.latestIncomingUserToUserPayment: is not a number`) const latestIncomingTxErr = OperationsCursorValidate(o.latestIncomingTx, opts.latestIncomingTx_Options, `${path}.latestIncomingTx`)
if (opts.latestIncomingUserToUserPayment_CustomCheck && !opts.latestIncomingUserToUserPayment_CustomCheck(o.latestIncomingUserToUserPayment)) return new Error(`${path}.latestIncomingUserToUserPayment: custom check failed`) if (latestIncomingTxErr !== null) return latestIncomingTxErr
if (typeof o.latestOutgoingInvoice !== 'number') return new Error(`${path}.latestOutgoingInvoice: is not a number`)
if (opts.latestOutgoingInvoice_CustomCheck && !opts.latestOutgoingInvoice_CustomCheck(o.latestOutgoingInvoice)) return new Error(`${path}.latestOutgoingInvoice: custom check failed`)
if (typeof o.latestOutgoingTx !== 'number') return new Error(`${path}.latestOutgoingTx: is not a number`) const latestIncomingUserToUserPaymentErr = OperationsCursorValidate(o.latestIncomingUserToUserPayment, opts.latestIncomingUserToUserPayment_Options, `${path}.latestIncomingUserToUserPayment`)
if (opts.latestOutgoingTx_CustomCheck && !opts.latestOutgoingTx_CustomCheck(o.latestOutgoingTx)) return new Error(`${path}.latestOutgoingTx: custom check failed`) if (latestIncomingUserToUserPaymentErr !== null) return latestIncomingUserToUserPaymentErr
const latestOutgoingInvoiceErr = OperationsCursorValidate(o.latestOutgoingInvoice, opts.latestOutgoingInvoice_Options, `${path}.latestOutgoingInvoice`)
if (latestOutgoingInvoiceErr !== null) return latestOutgoingInvoiceErr
const latestOutgoingTxErr = OperationsCursorValidate(o.latestOutgoingTx, opts.latestOutgoingTx_Options, `${path}.latestOutgoingTx`)
if (latestOutgoingTxErr !== null) return latestOutgoingTxErr
const latestOutgoingUserToUserPaymentErr = OperationsCursorValidate(o.latestOutgoingUserToUserPayment, opts.latestOutgoingUserToUserPayment_Options, `${path}.latestOutgoingUserToUserPayment`)
if (latestOutgoingUserToUserPaymentErr !== null) return latestOutgoingUserToUserPaymentErr
if (typeof o.latestOutgoingUserToUserPayment !== 'number') return new Error(`${path}.latestOutgoingUserToUserPayment: is not a number`)
if (opts.latestOutgoingUserToUserPayment_CustomCheck && !opts.latestOutgoingUserToUserPayment_CustomCheck(o.latestOutgoingUserToUserPayment)) return new Error(`${path}.latestOutgoingUserToUserPayment: custom check failed`)
if (typeof o.max_size !== 'number') return new Error(`${path}.max_size: is not a number`) if (typeof o.max_size !== 'number') return new Error(`${path}.max_size: is not a number`)
if (opts.max_size_CustomCheck && !opts.max_size_CustomCheck(o.max_size)) return new Error(`${path}.max_size: custom check failed`) if (opts.max_size_CustomCheck && !opts.max_size_CustomCheck(o.max_size)) return new Error(`${path}.max_size: custom check failed`)
@ -3031,6 +3037,29 @@ export const OpenChannelResponseValidate = (o?: OpenChannelResponse, opts: OpenC
return null return null
} }
export type OperationsCursor = {
id: number
ts: number
}
export const OperationsCursorOptionalFields: [] = []
export type OperationsCursorOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
id_CustomCheck?: (v: number) => boolean
ts_CustomCheck?: (v: number) => boolean
}
export const OperationsCursorValidate = (o?: OperationsCursor, opts: OperationsCursorOptions = {}, path: string = 'OperationsCursor::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.id !== 'number') return new Error(`${path}.id: is not a number`)
if (opts.id_CustomCheck && !opts.id_CustomCheck(o.id)) return new Error(`${path}.id: custom check failed`)
if (typeof o.ts !== 'number') return new Error(`${path}.ts: is not a number`)
if (opts.ts_CustomCheck && !opts.ts_CustomCheck(o.ts)) return new Error(`${path}.ts: custom check failed`)
return null
}
export type PayAddressRequest = { export type PayAddressRequest = {
address: string address: string
amoutSats: number amoutSats: number
@ -3998,24 +4027,25 @@ export const UserOperationValidate = (o?: UserOperation, opts: UserOperationOpti
} }
export type UserOperations = { export type UserOperations = {
fromIndex: number fromIndex: OperationsCursor
operations: UserOperation[] operations: UserOperation[]
toIndex: number toIndex: OperationsCursor
} }
export const UserOperationsOptionalFields: [] = [] export const UserOperationsOptionalFields: [] = []
export type UserOperationsOptions = OptionsBaseMessage & { export type UserOperationsOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: [] checkOptionalsAreSet?: []
fromIndex_CustomCheck?: (v: number) => boolean fromIndex_Options?: OperationsCursorOptions
operations_ItemOptions?: UserOperationOptions operations_ItemOptions?: UserOperationOptions
operations_CustomCheck?: (v: UserOperation[]) => boolean operations_CustomCheck?: (v: UserOperation[]) => boolean
toIndex_CustomCheck?: (v: number) => boolean toIndex_Options?: OperationsCursorOptions
} }
export const UserOperationsValidate = (o?: UserOperations, opts: UserOperationsOptions = {}, path: string = 'UserOperations::root.'): Error | null => { export const UserOperationsValidate = (o?: UserOperations, opts: UserOperationsOptions = {}, path: string = 'UserOperations::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.fromIndex !== 'number') return new Error(`${path}.fromIndex: is not a number`) const fromIndexErr = OperationsCursorValidate(o.fromIndex, opts.fromIndex_Options, `${path}.fromIndex`)
if (opts.fromIndex_CustomCheck && !opts.fromIndex_CustomCheck(o.fromIndex)) return new Error(`${path}.fromIndex: custom check failed`) if (fromIndexErr !== null) return fromIndexErr
if (!Array.isArray(o.operations)) return new Error(`${path}.operations: is not an array`) if (!Array.isArray(o.operations)) return new Error(`${path}.operations: is not an array`)
for (let index = 0; index < o.operations.length; index++) { for (let index = 0; index < o.operations.length; index++) {
@ -4024,8 +4054,9 @@ export const UserOperationsValidate = (o?: UserOperations, opts: UserOperationsO
} }
if (opts.operations_CustomCheck && !opts.operations_CustomCheck(o.operations)) return new Error(`${path}.operations: custom check failed`) if (opts.operations_CustomCheck && !opts.operations_CustomCheck(o.operations)) return new Error(`${path}.operations: custom check failed`)
if (typeof o.toIndex !== 'number') return new Error(`${path}.toIndex: is not a number`) const toIndexErr = OperationsCursorValidate(o.toIndex, opts.toIndex_Options, `${path}.toIndex`)
if (opts.toIndex_CustomCheck && !opts.toIndex_CustomCheck(o.toIndex)) return new Error(`${path}.toIndex: custom check failed`) if (toIndexErr !== null) return toIndexErr
return null return null
} }

View file

@ -534,13 +534,19 @@ message UserInfo{
string bridge_url = 11; string bridge_url = 11;
string nmanage = 12; string nmanage = 12;
} }
message OperationsCursor {
int64 ts = 1; // last timestamp
int64 id = 2; // last serial_id
}
message GetUserOperationsRequest{ message GetUserOperationsRequest{
int64 latestIncomingInvoice = 1; OperationsCursor latestIncomingInvoice = 1;
int64 latestOutgoingInvoice = 2; OperationsCursor latestOutgoingInvoice = 2;
int64 latestIncomingTx = 3; OperationsCursor latestIncomingTx = 3;
int64 latestOutgoingTx = 4; OperationsCursor latestOutgoingTx = 4;
int64 latestIncomingUserToUserPayment = 5; OperationsCursor latestIncomingUserToUserPayment = 5;
int64 latestOutgoingUserToUserPayment = 6; OperationsCursor latestOutgoingUserToUserPayment = 6;
int64 max_size = 7; int64 max_size = 7;
} }
enum UserOperationType { enum UserOperationType {
@ -566,8 +572,8 @@ message UserOperation {
bool internal = 11; bool internal = 11;
} }
message UserOperations { message UserOperations {
int64 fromIndex=1; OperationsCursor fromIndex=1;
int64 toIndex=2; OperationsCursor toIndex=2;
repeated UserOperation operations=3; repeated UserOperation operations=3;
} }
message GetUserOperationsResponse{ message GetUserOperationsResponse{

View file

@ -227,9 +227,9 @@ export class LiquidityProvider {
throw new Error("liquidity provider is not ready yet") throw new Error("liquidity provider is not ready yet")
} }
const res = await this.client.GetUserOperations({ const res = await this.client.GetUserOperations({
latestIncomingInvoice: 0, latestOutgoingInvoice: 0, latestIncomingInvoice: { ts: 0, id: 0 }, latestOutgoingInvoice: { ts: 0, id: 0 },
latestIncomingTx: 0, latestOutgoingTx: 0, latestIncomingUserToUserPayment: 0, latestIncomingTx: { ts: 0, id: 0 }, latestOutgoingTx: { ts: 0, id: 0 }, latestIncomingUserToUserPayment: { ts: 0, id: 0 },
latestOutgoingUserToUserPayment: 0, max_size: 200 latestOutgoingUserToUserPayment: { ts: 0, id: 0 }, max_size: 200
}) })
if (res.status === 'ERROR') { if (res.status === 'ERROR') {
this.log("error getting operations", res.reason) this.log("error getting operations", res.reason)

View file

@ -626,14 +626,14 @@ export default class {
mapOperations(operations: UserOperationInfo[], type: Types.UserOperationType, inbound: boolean): Types.UserOperations { mapOperations(operations: UserOperationInfo[], type: Types.UserOperationType, inbound: boolean): Types.UserOperations {
if (operations.length === 0) { if (operations.length === 0) {
return { return {
fromIndex: 0, fromIndex: { ts: 0, id: 0 },
toIndex: 0, toIndex: { ts: 0, id: 0 },
operations: [] operations: []
} }
} }
return { return {
toIndex: operations[0].serial_id, toIndex: { ts: operations[0].paid_at_unix, id: operations[0]!.serial_id },
fromIndex: operations[operations.length - 1].serial_id, fromIndex: { ts: operations.at(-1)!.paid_at_unix, id: operations.at(-1)!.serial_id },
operations: operations.map((o: UserOperationInfo): Types.UserOperation => { operations: operations.map((o: UserOperationInfo): Types.UserOperation => {
let identifier = ""; let identifier = "";
if (o.invoice) { if (o.invoice) {
@ -687,12 +687,12 @@ export default class {
throw new Error("user is banned, cannot retrieve operations") throw new Error("user is banned, cannot retrieve operations")
} }
const [outgoingInvoices, outgoingTransactions, incomingInvoices, incomingTransactions, incomingUserToUser, outgoingUserToUser] = await Promise.all([ const [outgoingInvoices, outgoingTransactions, incomingInvoices, incomingTransactions, incomingUserToUser, outgoingUserToUser] = await Promise.all([
this.storage.paymentStorage.GetUserInvoicePayments(userId, req.latestOutgoingInvoice, req.max_size), // this.storage.paymentStorage.GetUserInvoicePayments(userId, req.latestOutgoingInvoice.id, req.max_size), //
this.storage.paymentStorage.GetUserTransactionPayments(userId, req.latestOutgoingTx, req.max_size), this.storage.paymentStorage.GetUserTransactionPayments(userId, req.latestOutgoingTx.id, req.max_size),
this.storage.paymentStorage.GetUserInvoicesFlaggedAsPaid(userId, req.latestIncomingInvoice, req.max_size), this.storage.paymentStorage.GetUserInvoicesFlaggedAsPaid(userId, req.latestIncomingInvoice.id, req.latestIncomingInvoice.ts, req.max_size),
this.storage.paymentStorage.GetUserReceivingTransactions(userId, req.latestIncomingTx, req.max_size), this.storage.paymentStorage.GetUserReceivingTransactions(userId, req.latestIncomingTx.id, req.max_size),
this.storage.paymentStorage.GetUserToUserReceivedPayments(userId, req.latestIncomingUserToUserPayment, req.max_size), this.storage.paymentStorage.GetUserToUserReceivedPayments(userId, req.latestIncomingUserToUserPayment.id, req.max_size),
this.storage.paymentStorage.GetUserToUserSentPayments(userId, req.latestOutgoingUserToUserPayment, req.max_size) this.storage.paymentStorage.GetUserToUserSentPayments(userId, req.latestOutgoingUserToUserPayment.id, req.max_size)
]) ])
return { return {
latestIncomingInvoiceOperations: this.mapOperations(incomingInvoices, Types.UserOperationType.INCOMING_INVOICE, true), latestIncomingInvoiceOperations: this.mapOperations(incomingInvoices, Types.UserOperationType.INCOMING_INVOICE, true),

View file

@ -9,6 +9,7 @@ export type ZapInfo = {
description: string description: string
} }
@Entity() @Entity()
@Index("recv_invoice_paid_serial", ["user.serial_id", "paid_at_unix", "serial_id"], { where: "paid_at_unix > 0" })
export class UserReceivingInvoice { export class UserReceivingInvoice {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()

View file

@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class UserReceivingInvoiceIdx1753109184611 implements MigrationInterface {
name = 'UserReceivingInvoiceIdx1753109184611'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE INDEX "recv_invoice_paid_serial" ON "user_receiving_invoice" ("userSerialId", "paid_at_unix", "serial_id") WHERE paid_at_unix > 0`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "recv_invoice_paid_serial"`);
}
}

View file

@ -21,9 +21,10 @@ import { ManagementGrant1751307732346 } from './1751307732346-management_grant.j
import { ManagementGrantBanned1751989251513 } from './1751989251513-management_grant_banned.js' import { ManagementGrantBanned1751989251513 } from './1751989251513-management_grant_banned.js'
import { InvoiceCallbackUrls1752425992291 } from './1752425992291-invoice_callback_urls.js' import { InvoiceCallbackUrls1752425992291 } from './1752425992291-invoice_callback_urls.js'
import { OldSomethingLeftover1753106599604 } from './1753106599604-old_something_leftover.js' import { OldSomethingLeftover1753106599604 } from './1753106599604-old_something_leftover.js'
import { UserReceivingInvoiceIdx1753109184611 } from './1753109184611-user_receiving_invoice_idx.js'
export const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, export const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189,
TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264,
DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, ManagementGrantBanned1751989251513, InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604] DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, ManagementGrantBanned1751989251513, InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611]
export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447, RootOpsTime1745428134124, ChannelEvents1750777346411] export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447, RootOpsTime1745428134124, ChannelEvents1750777346411]
/* export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => { /* export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => {
await connectAndMigrate(log, storageManager, allMigrations, allMetricsMigrations) await connectAndMigrate(log, storageManager, allMigrations, allMetricsMigrations)

View file

@ -1,5 +1,5 @@
import crypto from 'crypto'; import crypto from 'crypto';
import { Between, FindOperator, IsNull, LessThanOrEqual, MoreThan, MoreThanOrEqual, Not } from "typeorm" import { Between, FindOperator, FindOptionsWhere, IsNull, LessThanOrEqual, MoreThan, MoreThanOrEqual, Not } from "typeorm"
import { User } from './entity/User.js'; import { User } from './entity/User.js';
import { UserTransactionPayment } from './entity/UserTransactionPayment.js'; import { UserTransactionPayment } from './entity/UserTransactionPayment.js';
import { EphemeralKeyType, UserEphemeralKey } from './entity/UserEphemeralKey.js'; import { EphemeralKeyType, UserEphemeralKey } from './entity/UserEphemeralKey.js';
@ -73,17 +73,27 @@ export default class {
return this.dbs.Update<UserReceivingInvoice>('UserReceivingInvoice', invoice.serial_id, i, txId) return this.dbs.Update<UserReceivingInvoice>('UserReceivingInvoice', invoice.serial_id, i, txId)
} }
GetUserInvoicesFlaggedAsPaid(userId: string, fromIndex: number, take = 50, txId?: string): Promise<UserReceivingInvoice[]> { async GetUserInvoicesFlaggedAsPaid(userId: string, fromIndex: number, fromPaidTimestamp: number, take = 50, txId?: string): Promise<UserReceivingInvoice[]> {
const whereArray: FindOptionsWhere<UserReceivingInvoice>[] = [
// Later paid_at_unix timestamp
{
user: { user_id: userId },
paid_at_unix: MoreThan(Math.max(fromPaidTimestamp, 0)), // also excludes 0
}
];
// Same timestamp, higher serial_id
if (fromPaidTimestamp > 0) {
whereArray.push({
user: { user_id: userId },
paid_at_unix: fromPaidTimestamp,
serial_id: MoreThan(fromIndex),
});
}
return this.dbs.Find<UserReceivingInvoice>('UserReceivingInvoice', { return this.dbs.Find<UserReceivingInvoice>('UserReceivingInvoice', {
where: { where: whereArray,
user: {
user_id: userId
},
serial_id: MoreThanOrEqual(fromIndex),
paid_at_unix: MoreThan(0),
},
order: { order: {
paid_at_unix: 'DESC' paid_at_unix: 'DESC',
serial_id: 'DESC'
}, },
take take
}, txId) }, txId)