more fixies

This commit is contained in:
hatim 2023-05-11 16:35:59 +02:00
parent c5ea8c899d
commit 6ae03e520c
19 changed files with 2330 additions and 1880 deletions

File diff suppressed because it is too large Load diff

View file

@ -66,10 +66,10 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.SetMockInvoiceAsPaid) throw new Error('method: SetMockInvoiceAsPaid is not implemented')
app.post('/api/admin/lnd/mock/invoice/paid', async (req, res) => {
app.post('/api/lnd/mock/invoice/paid', async (req, res) => {
try {
if (!methods.SetMockInvoiceAsPaid) throw new Error('method: SetMockInvoiceAsPaid is not implemented')
const authContext = await opts.AdminAuthGuard(req.headers['authorization'])
const authContext = await opts.GuestAuthGuard(req.headers['authorization'])
const request = req.body
const error = Types.SetMockInvoiceAsPaidRequestValidate(request)
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
@ -93,6 +93,17 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.GetApp) throw new Error('method: GetApp is not implemented')
app.post('/api/app/get', async (req, res) => {
try {
if (!methods.GetApp) throw new Error('method: GetApp is not implemented')
const authContext = await opts.AppAuthGuard(req.headers['authorization'])
const query = req.query
const params = req.params
const response = await methods.GetApp({ ...authContext, ...query, ...params })
res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.AddAppUser) throw new Error('method: AddAppUser is not implemented')
app.post('/api/app/user/add', async (req, res) => {
try {
@ -205,6 +216,34 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.SetMockAppUserBalance) throw new Error('method: SetMockAppUserBalance is not implemented')
app.post('/api/app/mock/user/blance/set', async (req, res) => {
try {
if (!methods.SetMockAppUserBalance) throw new Error('method: SetMockAppUserBalance is not implemented')
const authContext = await opts.AppAuthGuard(req.headers['authorization'])
const request = req.body
const error = Types.SetMockAppUserBalanceRequestValidate(request)
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
const query = req.query
const params = req.params
await methods.SetMockAppUserBalance({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK'})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.SetMockAppBalance) throw new Error('method: SetMockAppBalance is not implemented')
app.post('/api/app/mock/blance/set', async (req, res) => {
try {
if (!methods.SetMockAppBalance) throw new Error('method: SetMockAppBalance is not implemented')
const authContext = await opts.AppAuthGuard(req.headers['authorization'])
const request = req.body
const error = Types.SetMockAppBalanceRequestValidate(request)
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
const query = req.query
const params = req.params
await methods.SetMockAppBalance({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK'})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.AddUser) throw new Error('method: AddUser is not implemented')
app.post('/api/user/add', async (req, res) => {
try {

View file

@ -52,9 +52,9 @@ export default (params: ClientParams) => ({
return { status: 'ERROR', reason: 'invalid response' }
},
SetMockInvoiceAsPaid: async (request: Types.SetMockInvoiceAsPaidRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveAdminAuth()
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
let finalRoute = '/api/admin/lnd/mock/invoice/paid'
const auth = await params.retrieveGuestAuth()
if (auth === null) throw new Error('retrieveGuestAuth() returned null')
let finalRoute = '/api/lnd/mock/invoice/paid'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
@ -76,6 +76,20 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetApp: async (): Promise<ResultError | ({ status: 'OK' }& Types.Application)> => {
const auth = await params.retrieveAppAuth()
if (auth === null) throw new Error('retrieveAppAuth() returned null')
let finalRoute = '/api/app/get'
const { data } = await axios.post(params.baseUrl + finalRoute, {}, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.ApplicationValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
AddAppUser: async (request: Types.AddAppUserRequest): Promise<ResultError | ({ status: 'OK' }& Types.AppUser)> => {
const auth = await params.retrieveAppAuth()
if (auth === null) throw new Error('retrieveAppAuth() returned null')
@ -182,6 +196,28 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
SetMockAppUserBalance: async (request: Types.SetMockAppUserBalanceRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveAppAuth()
if (auth === null) throw new Error('retrieveAppAuth() returned null')
let finalRoute = '/api/app/mock/user/blance/set'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
}
return { status: 'ERROR', reason: 'invalid response' }
},
SetMockAppBalance: async (request: Types.SetMockAppBalanceRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveAppAuth()
if (auth === null) throw new Error('retrieveAppAuth() returned null')
let finalRoute = '/api/app/mock/blance/set'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
}
return { status: 'ERROR', reason: 'invalid response' }
},
AddUser: async (request: Types.AddUserRequest): Promise<ResultError | ({ status: 'OK' }& Types.AddUserResponse)> => {
const auth = await params.retrieveGuestAuth()
if (auth === null) throw new Error('retrieveGuestAuth() returned null')

File diff suppressed because it is too large Load diff

View file

@ -89,9 +89,9 @@ service LightningPub {
};
rpc SetMockInvoiceAsPaid(structs.SetMockInvoiceAsPaidRequest) returns (structs.Empty) {
option (auth_type) = "Admin";
option (auth_type) = "Guest";
option (http_method) = "post";
option (http_route) = "/api/admin/lnd/mock/invoice/paid";
option (http_route) = "/api/lnd/mock/invoice/paid";
}
// <App>
@ -102,6 +102,12 @@ service LightningPub {
option (http_route) = "/api/admin/app/add";
};
rpc GetApp(structs.Empty) returns (structs.Application) {
option (auth_type) = "App";
option (http_method) = "post";
option (http_route) = "/api/app/get";
}
rpc AddAppUser(structs.AddAppUserRequest)returns (structs.AppUser) {
option (auth_type) = "App";
option (http_method) = "post";
@ -149,6 +155,16 @@ service LightningPub {
option (http_method) = "post";
option (http_route) = "/api/app/user/lnurl/pay/info";
}
rpc SetMockAppUserBalance(structs.SetMockAppUserBalanceRequest) returns (structs.Empty) {
option (auth_type) = "App";
option (http_method) = "post";
option (http_route) = "/api/app/mock/user/blance/set";
}
rpc SetMockAppBalance(structs.SetMockAppBalanceRequest) returns (structs.Empty) {
option (auth_type) = "App";
option (http_method) = "post";
option (http_route) = "/api/app/mock/blance/set";
}
// </App>

View file

@ -29,11 +29,16 @@ message AddAppRequest {
string name = 1;
}
message AddAppResponse {
message Application {
string name = 1;
string id = 2;
string auth_token = 3;
int64 balance = 3;
}
message AddAppResponse {
Application app = 1;
string auth_token = 2;
}
message AddAppUserRequest {
string identifier = 1;
@ -91,6 +96,15 @@ message GetAppUserLNURLInfoRequest {
string user_identifier = 1;
}
message SetMockAppUserBalanceRequest {
string user_identifier = 1;
int64 amount = 2;
}
message SetMockAppBalanceRequest {
int64 amount = 1;
}
enum AddressType {
WITNESS_PUBKEY_HASH = 0;
NESTED_PUBKEY_HASH = 1;

View file

@ -32,8 +32,10 @@ export interface LightningHandler {
export default (settings: LndSettings, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb): LightningHandler => {
if (settings.mockLnd) {
console.log("registering mock lnd handler")
return new MockLnd(settings, addressPaidCb, invoicePaidCb)
} else {
console.log("registering prod lnd handler")
return new LND(settings, addressPaidCb, invoicePaidCb)
}
}

View file

@ -157,7 +157,6 @@ export default class {
this.checkReady()
const abortController = new AbortController()
const req = PayInvoiceReq(invoice, amount, feeLimit)
console.log(req)
const stream = this.router.sendPaymentV2(req, { abort: abortController.signal })
return new Promise((res, rej) => {
stream.responses.onError(error => {

View file

@ -15,7 +15,7 @@ import { SendCoinsReq } from './sendCoinsReq.js';
import { LndSettings, AddressPaidCb, InvoicePaidCb, NodeInfo, Invoice, DecodedInvoice, PaidInvoice } from './settings.js';
export default class {
invoicesAwaiting: Record<string /* invoice */, { value: number, memo: string, expiryUnix: number }>
invoicesAwaiting: Record<string /* invoice */, { value: number, memo: string, expiryUnix: number }> = {}
settings: LndSettings
abortController = new AbortController()
addressPaidCb: AddressPaidCb
@ -49,12 +49,16 @@ export default class {
}
async NewInvoice(value: number, memo: string, expiry: number): Promise<Invoice> {
const mockInvoice = "lnbcrtmock" + crypto.randomBytes(32).toString('hex')
const mockInvoice = "lnbcrtmockin" + crypto.randomBytes(32).toString('hex')
this.invoicesAwaiting[mockInvoice] = { value, memo, expiryUnix: expiry + Date.now() / 1000 }
return { payRequest: mockInvoice }
}
async DecodeInvoice(paymentRequest: string): Promise<DecodedInvoice> {
if (paymentRequest.startsWith('lnbcrtmockout')) {
const amt = this.decodeOutboundInvoice(paymentRequest)
return { numSatoshis: amt }
}
const i = this.invoicesAwaiting[paymentRequest]
if (!i) {
throw new Error("invoice not found")
@ -70,15 +74,23 @@ export default class {
return Math.max(0, Math.floor(amount * (1 - this.settings.feeRateLimit) - this.settings.feeFixedLimit))
}
async PayInvoice(invoice: string, amount: number, feeLimit: number): Promise<PaidInvoice> {
if (!invoice.startsWith('lnbcrtmock')) {
decodeOutboundInvoice(invoice: string): number {
if (!invoice.startsWith('lnbcrtmockout')) {
throw new Error("invalid mock invoice provided for payment")
}
const amt = invoice.substring('lnbcrtmock'.length)
const amt = invoice.substring('lnbcrtmockout'.length).split("__")[0]
if (isNaN(+amt)) {
throw new Error("invalid mock invoice provided for payment")
}
return { feeSat: 0, paymentPreimage: "all_good", valueSat: +amt || amount }
return +amt
}
async PayInvoice(invoice: string, amount: number, feeLimit: number): Promise<PaidInvoice> {
console.log('payng', invoice)
await new Promise(res => setTimeout(res, 200))
const amt = this.decodeOutboundInvoice(invoice)
console.log('paid', invoice)
return { feeSat: 1, paymentPreimage: "all_good", valueSat: amt || amount }
}
async EstimateChainFees(address: string, amount: number, targetConf: number): Promise<EstimateFeeResponse> {

View file

@ -27,19 +27,45 @@ export default class {
t = token.substring("Bearer ".length)
}
if (!t) throw new Error("no app token provided")
return (jwt.verify(token, this.settings.jwtSecret) as { appId: string }).appId
const decoded = jwt.verify(token, this.settings.jwtSecret) as { appId?: string }
if (!decoded.appId) {
throw new Error("the provided token is not an app token")
}
return decoded.appId
}
async SetMockAppUserBalance(appId: string, req: Types.SetMockAppUserBalanceRequest) {
const user = await this.storage.applicationStorage.GetOrCreateApplicationUser(appId, req.user_identifier, 0)
await this.paymentManager.SetMockUserBalance(user.user.user_id, req.amount)
}
async SetMockAppBalance(appId: string, req: Types.SetMockAppBalanceRequest) {
const app = await this.storage.applicationStorage.GetApplication(appId)
await this.paymentManager.SetMockUserBalance(app.owner.user_id, req.amount)
}
async AddApp(req: Types.AddAppRequest): Promise<Types.AddAppResponse> {
const app = await this.storage.applicationStorage.AddApplication(req.name)
return {
id: app.app_id,
name: app.name,
app: {
id: app.app_id,
name: app.name,
balance: 0
},
auth_token: this.SignAppToken(app.app_id)
}
}
async GetApp(appId: string): Promise<Types.Application> {
const app = await this.storage.applicationStorage.GetApplication(appId)
return {
name: app.name,
id: app.app_id,
balance: app.owner.balance_sats
}
}
async AddAppUser(appId: string, req: Types.AddAppUserRequest): Promise<Types.AppUser> {
let u: ApplicationUser
if (req.fail_if_exists) {
@ -60,7 +86,7 @@ export default class {
async AddAppInvoice(appId: string, req: Types.AddAppInvoiceRequest): Promise<Types.NewInvoiceResponse> {
const app = await this.storage.applicationStorage.GetApplication(appId)
const payer = await this.storage.applicationStorage.GetOrCreateApplicationUser(appId, req.payer_identifier, 0)
const opts: InboundOptionals = { callbackUrl: req.http_callback_url, expiry: defaultInvoiceExpiry, expectedPayer: payer.user }
const opts: InboundOptionals = { callbackUrl: req.http_callback_url, expiry: defaultInvoiceExpiry, expectedPayer: payer.user, linkedApplication: app }
return this.paymentManager.NewInvoice(app.owner.user_id, req.invoice_req, opts)
}
@ -77,6 +103,7 @@ export default class {
async GetAppUser(appId: string, req: Types.GetAppUserRequest): Promise<Types.AppUser> {
const user = await this.storage.applicationStorage.GetApplicationUser(appId, req.user_identifier)
const max = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats)
console.log(max, user.user.balance_sats)
return {
max_withdrawable: max, identifier: req.user_identifier, info: {
userId: user.user.user_id, balance: user.user.balance_sats
@ -85,14 +112,16 @@ export default class {
}
async PayAppUserInvoice(appId: string, req: Types.PayAppUserInvoiceRequest): Promise<Types.PayAppUserInvoiceResponse> {
const app = await this.storage.applicationStorage.GetApplication(appId)
const appUser = await this.storage.applicationStorage.GetApplicationUser(appId, req.user_identifier)
return this.paymentManager.PayInvoice(appUser.user.user_id, req)
return this.paymentManager.PayInvoice(appUser.user.user_id, req, app)
}
async SendAppUserToAppUserPayment(appId: string, req: Types.SendAppUserToAppUserPaymentRequest): Promise<void> {
const fromUser = await this.storage.applicationStorage.GetApplicationUser(appId, req.from_user_identifier)
const toUser = await this.storage.applicationStorage.GetApplicationUser(appId, req.to_user_identifier)
await this.paymentManager.SendUserToUserPayment(fromUser.user.user_id, toUser.user.user_id, req.amount)
const app = await this.storage.applicationStorage.GetApplication(appId)
await this.paymentManager.SendUserToUserPayment(fromUser.user.user_id, toUser.user.user_id, req.amount, app)
}
async SendAppUserToAppPayment(appId: string, req: Types.SendAppUserToAppPaymentRequest): Promise<void> {

View file

@ -73,11 +73,13 @@ export default class {
const userInvoice = await this.storage.paymentStorage.GetInvoiceOwner(paymentRequest, tx)
if (!userInvoice || userInvoice.paid_at_unix > 0) { return }
const fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_INVOICE, amount)
const maybeApp = await this.storage.applicationStorage.IsApplicationUser(userInvoice.user.user_id)
try {
// This call will fail if the invoice is already registered
await this.storage.paymentStorage.FlagInvoiceAsPaid(userInvoice, amount, fee, tx)
await this.storage.userStorage.IncrementUserBalance(userInvoice.user.user_id, amount - fee, tx)
if (userInvoice.linkedApplication) {
await this.storage.userStorage.IncrementUserBalance(userInvoice.linkedApplication.owner.user_id, fee, tx)
}
await this.triggerPaidCallback(userInvoice.callbackUrl)
} catch {
//TODO

View file

@ -4,6 +4,7 @@ import * as Types from '../../../proto/autogenerated/ts/types.js'
import { MainSettings } from './settings.js'
import { InboundOptionals, defaultInvoiceExpiry } from '../storage/paymentStorage.js'
import { LightningHandler } from '../lnd/index.js'
import { Application } from '../storage/entity/Application.js'
interface UserOperationInfo {
serial_id: number
paid_amount: number
@ -39,9 +40,20 @@ export default class {
}
async SetMockInvoiceAsPaid(req: Types.SetMockInvoiceAsPaidRequest) {
if (!this.settings.lndSettings.mockLnd) {
throw new Error("mock disabled, cannot set invoice as paid")
}
await this.lnd.SetMockInvoiceAsPaid(req.invoice, req.amount)
}
async SetMockUserBalance(userId: string, balance: number) {
if (!this.settings.lndSettings.mockLnd) {
throw new Error("mock disabled, cannot set invoice as paid")
}
console.log("setting mock balance...")
await this.storage.userStorage.UpdateUser(userId, { balance_sats: balance })
}
async NewAddress(userId: string, req: Types.NewAddressRequest): Promise<Types.NewAddressResponse> {
const res = await this.lnd.NewAddress(req.addressType)
const userAddress = await this.storage.paymentStorage.AddUserAddress(userId, res.address)
@ -60,8 +72,12 @@ export default class {
}
async lockUserWithMinBalance(userId: string, minBalance: number) {
console.log("locking", userId)
return this.storage.StartTransaction(async tx => {
const user = await this.storage.userStorage.GetUser(userId, tx)
if (user.locked) {
throw new Error("user is already withdrawing")
}
if (user.balance_sats < minBalance) {
throw new Error("insufficient balance")
}
@ -80,7 +96,7 @@ export default class {
amount: Number(decoded.numSatoshis)
}
}
async PayInvoice(userId: string, req: Types.PayInvoiceRequest): Promise<Types.PayInvoiceResponse> {
async PayInvoice(userId: string, req: Types.PayInvoiceRequest, linkedApplication?: Application): Promise<Types.PayInvoiceResponse> {
const decoded = await this.lnd.DecodeInvoice(req.invoice)
if (decoded.numSatoshis !== 0 && req.amount !== 0) {
throw new Error("invoice has value, do not provide amount the the request")
@ -92,11 +108,15 @@ export default class {
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_INVOICE, payAmount)
const totalAmountToDecrement = payAmount + serviceFee
const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount)
await this.lockUserWithMinBalance(userId, totalAmountToDecrement + routingFeeLimit)
const payment = await this.lnd.PayInvoice(req.invoice, req.amount, routingFeeLimit)
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + Number(payment.feeSat))
if (linkedApplication) {
await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, serviceFee)
} else {
//const appOwner = await this.storage.applicationStorage.IsApplicationOwner(userId)
}
await this.storage.userStorage.UnlockUser(userId)
await this.storage.paymentStorage.AddUserInvoicePayment(userId, req.invoice, payAmount, Number(payment.feeSat), serviceFee)
return {
@ -241,7 +261,7 @@ export default class {
}
}
async SendUserToUserPayment(fromUserId: string, toUserId: string, amount: number) {
async SendUserToUserPayment(fromUserId: string, toUserId: string, amount: number, linkedApplication?: Application) {
await this.storage.StartTransaction(async tx => {
const fromUser = await this.storage.userStorage.GetUser(fromUserId, tx)
const toUser = await this.storage.userStorage.GetUser(toUserId, tx)
@ -253,6 +273,9 @@ export default class {
await this.storage.userStorage.DecrementUserBalance(fromUser.user_id, amount, tx)
await this.storage.userStorage.IncrementUserBalance(toUser.user_id, toIncrement, tx)
await this.storage.paymentStorage.AddUserToUserPayment(fromUserId, toUserId, amount, fee)
if (linkedApplication) {
await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, fee)
}
})
}

View file

@ -15,13 +15,18 @@ export default class {
}
DecodeUserToken(token?: string): string {
if (!token) throw new Error("empty user token provided")
throw new Error("users methods temporarely disabled")
/*if (!token) throw new Error("empty user token provided")
let t = token
if (token.startsWith("Bearer ")) {
t = token.substring("Bearer ".length)
}
if (!t) throw new Error("no user token provided")
return (jwt.verify(token, this.settings.jwtSecret) as { userId: string }).userId
const decoded = jwt.verify(token, this.settings.jwtSecret) as { userId?: string }
if (!decoded.userId) {
throw new Error("the provided token is not an app token")
}
return decoded.userId*/
}
async AddBasicUser(req: Types.AddUserRequest): Promise<Types.AddUserResponse> {

View file

@ -116,6 +116,9 @@ export default (mainHandler: Main): Types.ServerMethods => {
if (err != null) throw new Error(err.message)
return mainHandler.applicationManager.AddApp(req)
},
GetApp: async (ctx) => {
return mainHandler.applicationManager.GetApp(ctx.app_id)
},
AddAppUser: async (ctx, req) => {
const err = Types.AddAppUserRequestValidate(req, {
identifier_CustomCheck: id => id !== ''
@ -176,6 +179,16 @@ export default (mainHandler: Main): Types.ServerMethods => {
})
if (err != null) throw new Error(err.message)
return mainHandler.applicationManager.GetAppUserLNURLInfo(ctx.app_id, req)
},
SetMockAppUserBalance: async (ctx, req) => {
const err = Types.SetMockAppUserBalanceRequestValidate(req, {
user_identifier_CustomCheck: id => id !== ''
})
if (err != null) throw new Error(err.message)
await mainHandler.applicationManager.SetMockAppUserBalance(ctx.app_id, req)
},
SetMockAppBalance: async (ctx, req) => {
await mainHandler.applicationManager.SetMockAppBalance(ctx.app_id, req)
}
}
}

View file

@ -66,7 +66,6 @@ export default class {
}
if (found.application.app_id !== appId) {
console.log(found, appId)
throw new Error("requested user does not belong to requestor application")
}
return found
@ -75,4 +74,8 @@ export default class {
async IsApplicationUser(userId: string, entityManager = this.DB): Promise<ApplicationUser | null> {
return await entityManager.getRepository(ApplicationUser).findOne({ where: { user: { user_id: userId } } })
}
async IsApplicationOwner(userId: string, entityManager = this.DB) {
return entityManager.getRepository(Application).findOne({ where: { owner: { user_id: userId } } })
}
}

View file

@ -1,6 +1,7 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
import { Product } from "./Product.js"
import { User } from "./User.js"
import { Application } from "./Application.js"
@Entity()
export class UserReceivingInvoice {
@ -37,6 +38,9 @@ export class UserReceivingInvoice {
@ManyToOne(type => User, { eager: true })
payer: User | null
@ManyToOne(type => Application, { eager: true })
linkedApplication: Application | null
@CreateDateColumn()
created_at: Date

View file

@ -1,4 +1,4 @@
import { DataSource, EntityManager, MoreThan, MoreThanOrEqual } from "typeorm"
import { DataSource, EntityManager, MoreThan, MoreThanOrEqual, TransactionAlreadyStartedError } from "typeorm"
import crypto from 'crypto';
import NewDB, { DbSettings, LoadDbSettingsFromEnv } from "./db.js"
import { User } from "./entity/User.js"
@ -29,6 +29,7 @@ export default class {
applicationStorage: ApplicationStorage
userStorage: UserStorage
paymentStorage: PaymentStorage
pendingTx: boolean
constructor(settings: StorageSettings) {
this.settings = settings
}
@ -40,6 +41,24 @@ export default class {
this.paymentStorage = new PaymentStorage(this.DB, this.userStorage)
}
StartTransaction(exec: (entityManager: EntityManager) => Promise<void>) {
return this.DB.transaction(exec)
if (this.pendingTx) {
throw new Error("cannot start transaction")
}
this.pendingTx = true
console.log("starting tx")
return this.DB.transaction(async tx => {
try {
await exec(tx)
console.log("tx done")
this.pendingTx = false
} catch (err) {
console.log("tx err")
this.pendingTx = false
throw err
}
})
}
}

View file

@ -12,7 +12,8 @@ import UserStorage from './userStorage.js';
import { AddressReceivingTransaction } from './entity/AddressReceivingTransaction.js';
import { UserInvoicePayment } from './entity/UserInvoicePayment.js';
import { UserToUserPayment } from './entity/UserToUserPayment.js';
export type InboundOptionals = { product?: Product, callbackUrl?: string, expiry: number, expectedPayer?: User }
import { Application } from './entity/Application.js';
export type InboundOptionals = { product?: Product, callbackUrl?: string, expiry: number, expectedPayer?: User, linkedApplication?: Application }
export const defaultInvoiceExpiry = 60 * 60
export default class {
DB: DataSource | EntityManager
@ -83,7 +84,8 @@ export default class {
user: user,
product: options.product,
expires_at_unix: Math.floor(Date.now() / 1000) + options.expiry,
payer: options.expectedPayer
payer: options.expectedPayer,
linkedApplication: options.linkedApplication
})
return entityManager.getRepository(UserReceivingInvoice).save(newUserInvoice)
}

View file

@ -75,6 +75,7 @@ export default class {
}
}
async UnlockUser(userId: string, entityManager = this.DB) {
console.log("unlocking", userId)
const res = await entityManager.getRepository(User).update({
user_id: userId
}, { locked: false })
@ -102,4 +103,9 @@ export default class {
throw new Error("unaffected balance decrement for " + userId) // TODO: fix logs doxing
}
}
async UpdateUser(userId: string, update: Partial<User>, entityManager = this.DB) {
const user = await this.GetUser(userId, entityManager)
await entityManager.getRepository(User).update(user.serial_id, update)
}
}