product qr

This commit is contained in:
hatim boufnichel 2023-03-24 00:23:25 +01:00
parent 38241a3266
commit 8b7ab71078
18 changed files with 1752 additions and 1263 deletions

View file

@ -18,6 +18,18 @@ The nostr server will send back a message response, and inside the body there wi
- This methods has an __empty__ __request__ body - This methods has an __empty__ __request__ body
- output: [UserInfo](#UserInfo) - output: [UserInfo](#UserInfo)
- AddProduct
- auth type: __User__
- input: [AddProductRequest](#AddProductRequest)
- output: [Product](#Product)
- NewProductInvoice
- auth type: __User__
- the request url __query__ can take the following string items:
- id
- This methods has an __empty__ __request__ body
- output: [NewInvoiceResponse](#NewInvoiceResponse)
- GetUserOperations - GetUserOperations
- auth type: __User__ - auth type: __User__
- input: [GetUserOperationsRequest](#GetUserOperationsRequest) - input: [GetUserOperationsRequest](#GetUserOperationsRequest)
@ -126,6 +138,22 @@ The nostr server will send back a message response, and inside the body there wi
- This methods has an __empty__ __request__ body - This methods has an __empty__ __request__ body
- output: [UserInfo](#UserInfo) - output: [UserInfo](#UserInfo)
- AddProduct
- auth type: __User__
- http method: __post__
- http route: __/api/user/product/add__
- input: [AddProductRequest](#AddProductRequest)
- output: [Product](#Product)
- NewProductInvoice
- auth type: __User__
- http method: __get__
- http route: __/api/user/product/get/invoice__
- the request url __query__ can take the following string items:
- id
- This methods has an __empty__ __request__ body
- output: [NewInvoiceResponse](#NewInvoiceResponse)
- GetUserOperations - GetUserOperations
- auth type: __User__ - auth type: __User__
- http method: __post__ - http method: __post__
@ -232,15 +260,78 @@ The nostr server will send back a message response, and inside the body there wi
## Messages ## Messages
### The content of requests and response from the methods ### The content of requests and response from the methods
### OpenChannelResponse
- __channelId__: _string_
### UserInfo
- __userId__: _string_
- __balance__: _number_
### GetUserOperationsRequest
- __latestIncomingInvoice__: _number_
- __latestOutgoingInvoice__: _number_
- __latestIncomingTx__: _number_
- __latestOutgoingTx__: _number_
### AddProductRequest
- __name__: _string_
- __price_sats__: _number_
### Product
- __id__: _string_
- __name__: _string_
- __price_sats__: _number_
### EncryptionExchangeRequest
- __publicKey__: _string_
- __deviceId__: _string_
### NewInvoiceRequest
- __amountSats__: _number_
- __memo__: _string_
### PayInvoiceResponse
- __preimage__: _string_
### HandleLnurlPayResponse
- __pr__: _string_
- __routes__: ARRAY of: _[Empty](#Empty)_
### AuthUserRequest
- __name__: _string_
- __secret__: _string_
### UserOperations
- __fromIndex__: _number_
- __toIndex__: _number_
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
### PayAddressRequest
- __address__: _string_
- __amoutSats__: _number_
- __targetConf__: _number_
### OpenChannelRequest
- __destination__: _string_
- __fundingAmount__: _number_
- __pushAmount__: _number_
- __closeAddress__: _string_
### LnurlLinkResponse
- __lnurl__: _string_
- __k1__: _string_
### Empty ### Empty
### NewInvoiceResponse
- __invoice__: _string_
### DecodeInvoiceRequest ### DecodeInvoiceRequest
- __invoice__: _string_ - __invoice__: _string_
### AddUserRequest ### AuthUserResponse
- __callbackUrl__: _string_ - __userId__: _string_
- __name__: _string_ - __authToken__: _string_
- __secret__: _string_
### GetUserOperationsResponse ### GetUserOperationsResponse
- __latestOutgoingInvoiceOperations__: _[UserOperations](#UserOperations)_ - __latestOutgoingInvoiceOperations__: _[UserOperations](#UserOperations)_
@ -248,8 +339,15 @@ The nostr server will send back a message response, and inside the body there wi
- __latestOutgoingTxOperations__: _[UserOperations](#UserOperations)_ - __latestOutgoingTxOperations__: _[UserOperations](#UserOperations)_
- __latestIncomingTxOperations__: _[UserOperations](#UserOperations)_ - __latestIncomingTxOperations__: _[UserOperations](#UserOperations)_
### PayAddressResponse ### LndGetInfoRequest
- __txId__: _string_ - __nodeId__: _number_
### LndGetInfoResponse
- __alias__: _string_
### AddUserResponse
- __userId__: _string_
- __authToken__: _string_
### LnurlWithdrawInfoResponse ### LnurlWithdrawInfoResponse
- __tag__: _string_ - __tag__: _string_
@ -261,21 +359,13 @@ The nostr server will send back a message response, and inside the body there wi
- __balanceCheck__: _string_ - __balanceCheck__: _string_
- __payLink__: _string_ - __payLink__: _string_
### UserInfo ### GetProductBuyLinkResponse
- __userId__: _string_ - __link__: _string_
- __balance__: _number_
### NewAddressResponse ### AddUserRequest
- __address__: _string_ - __callbackUrl__: _string_
- __name__: _string_
### PayAddressRequest - __secret__: _string_
- __address__: _string_
- __amoutSats__: _number_
- __targetConf__: _number_
### HandleLnurlPayResponse
- __pr__: _string_
- __routes__: ARRAY of: _[Empty](#Empty)_
### UserOperation ### UserOperation
- __paidAtUnix__: _number_ - __paidAtUnix__: _number_
@ -283,25 +373,21 @@ The nostr server will send back a message response, and inside the body there wi
- __inbound__: _boolean_ - __inbound__: _boolean_
- __amount__: _number_ - __amount__: _number_
### DecodeInvoiceResponse
- __amount__: _number_
### AddUserResponse
- __userId__: _string_
- __authToken__: _string_
### NewInvoiceResponse
- __invoice__: _string_
### PayInvoiceResponse
- __preimage__: _string_
### NewAddressRequest ### NewAddressRequest
- __addressType__: _[AddressType](#AddressType)_ - __addressType__: _[AddressType](#AddressType)_
### LnurlLinkResponse ### NewAddressResponse
- __lnurl__: _string_ - __address__: _string_
- __k1__: _string_
### PayInvoiceRequest
- __invoice__: _string_
- __amount__: _number_
### PayAddressResponse
- __txId__: _string_
### DecodeInvoiceResponse
- __amount__: _number_
### LnurlPayInfoResponse ### LnurlPayInfoResponse
- __tag__: _string_ - __tag__: _string_
@ -309,52 +395,6 @@ The nostr server will send back a message response, and inside the body there wi
- __maxSendable__: _number_ - __maxSendable__: _number_
- __minSendable__: _number_ - __minSendable__: _number_
- __metadata__: _string_ - __metadata__: _string_
### GetUserOperationsRequest
- __latestIncomingInvoice__: _number_
- __latestOutgoingInvoice__: _number_
- __latestIncomingTx__: _number_
- __latestOutgoingTx__: _number_
### AuthUserResponse
- __userId__: _string_
- __authToken__: _string_
### EncryptionExchangeRequest
- __publicKey__: _string_
- __deviceId__: _string_
### LndGetInfoRequest
- __nodeId__: _number_
### LndGetInfoResponse
- __alias__: _string_
### PayInvoiceRequest
- __invoice__: _string_
- __amount__: _number_
### OpenChannelRequest
- __destination__: _string_
- __fundingAmount__: _number_
- __pushAmount__: _number_
- __closeAddress__: _string_
### OpenChannelResponse
- __channelId__: _string_
### AuthUserRequest
- __name__: _string_
- __secret__: _string_
### UserOperations
- __fromIndex__: _number_
- __toIndex__: _number_
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
### NewInvoiceRequest
- __amountSats__: _number_
- __memo__: _string_
## Enums ## Enums
### The enumerators used in the messages ### The enumerators used in the messages

File diff suppressed because it is too large Load diff

View file

@ -105,6 +105,31 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
res.json({status: 'OK', ...response}) res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e } } catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
}) })
if (!opts.allowNotImplementedMethods && !methods.AddProduct) throw new Error('method: AddProduct is not implemented')
app.post('/api/user/product/add', async (req, res) => {
try {
if (!methods.AddProduct) throw new Error('method: AddProduct is not implemented')
const authContext = await opts.UserAuthGuard(req.headers['authorization'])
const request = req.body
const error = Types.AddProductRequestValidate(request)
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
const query = req.query
const params = req.params
const response = await methods.AddProduct({ ...authContext, ...query, ...params }, request)
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.NewProductInvoice) throw new Error('method: NewProductInvoice is not implemented')
app.get('/api/user/product/get/invoice', async (req, res) => {
try {
if (!methods.NewProductInvoice) throw new Error('method: NewProductInvoice is not implemented')
const authContext = await opts.UserAuthGuard(req.headers['authorization'])
const query = req.query
const params = req.params
const response = await methods.NewProductInvoice({ ...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.GetUserOperations) throw new Error('method: GetUserOperations is not implemented') if (!opts.allowNotImplementedMethods && !methods.GetUserOperations) throw new Error('method: GetUserOperations is not implemented')
app.post('/api/user/operations', async (req, res) => { app.post('/api/user/operations', async (req, res) => {
try { try {

View file

@ -92,6 +92,36 @@ export default (params: ClientParams) => ({
} }
return { status: 'ERROR', reason: 'invalid response' } return { status: 'ERROR', reason: 'invalid response' }
}, },
AddProduct: async (request: Types.AddProductRequest): Promise<ResultError | ({ status: 'OK' }& Types.Product)> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/user/product/add'
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') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.ProductValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
NewProductInvoice: async (query: Types.NewProductInvoice_Query): Promise<ResultError | ({ status: 'OK' }& Types.NewInvoiceResponse)> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/user/product/get/invoice'
const q = (new URLSearchParams(query)).toString()
finalRoute = finalRoute + (q === '' ? '' : '?' + q)
const { data } = await axios.get(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.NewInvoiceResponseValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetUserOperations: async (request: Types.GetUserOperationsRequest): Promise<ResultError | ({ status: 'OK' }& Types.GetUserOperationsResponse)> => { GetUserOperations: async (request: Types.GetUserOperationsRequest): Promise<ResultError | ({ status: 'OK' }& Types.GetUserOperationsResponse)> => {
const auth = await params.retrieveUserAuth() const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null') if (auth === null) throw new Error('retrieveUserAuth() returned null')

View file

@ -23,6 +23,36 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
} }
return { status: 'ERROR', reason: 'invalid response' } return { status: 'ERROR', reason: 'invalid response' }
}, },
AddProduct: async (request: Types.AddProductRequest): Promise<ResultError | ({ status: 'OK' }& Types.Product)> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'AddProduct',authIdentifier:auth, ...nostrRequest })
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.ProductValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
NewProductInvoice: async (query: Types.NewProductInvoice_Query): Promise<ResultError | ({ status: 'OK' }& Types.NewInvoiceResponse)> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.query = query
const data = await send(params.pubDestination, {rpcName:'NewProductInvoice',authIdentifier:auth, ...nostrRequest })
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.NewInvoiceResponseValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetUserOperations: async (request: Types.GetUserOperationsRequest): Promise<ResultError | ({ status: 'OK' }& Types.GetUserOperationsResponse)> => { GetUserOperations: async (request: Types.GetUserOperationsRequest): Promise<ResultError | ({ status: 'OK' }& Types.GetUserOperationsResponse)> => {
const auth = await params.retrieveNostrUserAuth() const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null') if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')

View file

@ -31,6 +31,29 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
res({status: 'OK', ...response}) res({status: 'OK', ...response})
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e } }catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
break break
case 'AddProduct':
try {
if (!methods.AddProduct) throw new Error('method: AddProduct is not implemented')
const authContext = await opts.NostrUserAuthGuard(req.authIdentifier)
const request = req.body
const error = Types.AddProductRequestValidate(request)
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
const query = req.query
const params = req.params
const response = await methods.AddProduct({ ...authContext, ...query, ...params }, request)
res({status: 'OK', ...response})
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
break
case 'NewProductInvoice':
try {
if (!methods.NewProductInvoice) throw new Error('method: NewProductInvoice is not implemented')
const authContext = await opts.NostrUserAuthGuard(req.authIdentifier)
const query = req.query
const params = req.params
const response = await methods.NewProductInvoice({ ...authContext, ...query, ...params })
res({status: 'OK', ...response})
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
break
case 'GetUserOperations': case 'GetUserOperations':
try { try {
if (!methods.GetUserOperations) throw new Error('method: GetUserOperations is not implemented') if (!methods.GetUserOperations) throw new Error('method: GetUserOperations is not implemented')

File diff suppressed because it is too large Load diff

View file

@ -78,7 +78,6 @@ service LightningPub {
option (http_method) = "post"; option (http_method) = "post";
option (http_route) = "/api/lnd/getinfo"; option (http_route) = "/api/lnd/getinfo";
}; };
rpc AddUser(structs.AddUserRequest)returns (structs.AddUserResponse){ rpc AddUser(structs.AddUserRequest)returns (structs.AddUserResponse){
option (auth_type) = "Guest"; option (auth_type) = "Guest";
option (http_method) = "post"; option (http_method) = "post";
@ -96,6 +95,19 @@ service LightningPub {
option (nostr) = true; option (nostr) = true;
} }
// USER // USER
rpc AddProduct(structs.AddProductRequest) returns (structs.Product){
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/user/product/add";
option (nostr) = true;
};
rpc NewProductInvoice(structs.Empty) returns (structs.NewInvoiceResponse){
option (auth_type) = "User";
option (http_method) = "get";
option (http_route) = "/api/user/product/get/invoice";
option (query) = {items: ["id"]};
option (nostr) = true;
};
rpc GetUserOperations(structs.GetUserOperationsRequest) returns (structs.GetUserOperationsResponse) { rpc GetUserOperations(structs.GetUserOperationsRequest) returns (structs.GetUserOperationsResponse) {
option (auth_type) = "User"; option (auth_type) = "User";
option (http_method) = "post"; option (http_method) = "post";

View file

@ -153,3 +153,18 @@ message GetUserOperationsResponse{
UserOperations latestOutgoingTxOperations=3; UserOperations latestOutgoingTxOperations=3;
UserOperations latestIncomingTxOperations=4; UserOperations latestIncomingTxOperations=4;
} }
message AddProductRequest {
string name = 1;
int64 price_sats = 2;
}
message Product {
string id = 1;
string name = 2;
int64 price_sats = 3;
}
message GetProductBuyLinkResponse {
string link = 1;
}

View file

@ -5,17 +5,8 @@ import * as Types from '../../../proto/autogenerated/ts/types.js'
import LND, { AddressPaidCb, InvoicePaidCb, LndSettings, LoadLndSettingsFromEnv } from '../lnd/index.js' import LND, { AddressPaidCb, InvoicePaidCb, LndSettings, LoadLndSettingsFromEnv } from '../lnd/index.js'
import { EnvMustBeInteger, EnvMustBeNonEmptyString } from '../helpers/envParser.js' import { EnvMustBeInteger, EnvMustBeNonEmptyString } from '../helpers/envParser.js'
import { UserReceivingInvoice } from '../storage/entity/UserReceivingInvoice.js' import { UserReceivingInvoice } from '../storage/entity/UserReceivingInvoice.js'
import { Type } from 'typescript' import ProductManager from './product.js'
export type MainSettings = { import { MainSettings } from './settings.js'
storageSettings: StorageSettings,
lndSettings: LndSettings,
jwtSecret: string
incomingTxFee: number
outgoingTxFee: number
incomingInvoiceFee: number
outgoingInvoiceFee: number
serviceUrl: string
}
export const LoadMainSettingsFromEnv = (test = false): MainSettings => { export const LoadMainSettingsFromEnv = (test = false): MainSettings => {
return { return {
lndSettings: LoadLndSettingsFromEnv(test), lndSettings: LoadLndSettingsFromEnv(test),
@ -47,10 +38,12 @@ export default class {
lnd: LND lnd: LND
settings: MainSettings settings: MainSettings
userOperationsSub: UserOperationsSub | null = null userOperationsSub: UserOperationsSub | null = null
productManager: ProductManager
constructor(settings: MainSettings) { constructor(settings: MainSettings) {
this.settings = settings this.settings = settings
this.storage = new Storage(settings.storageSettings) this.storage = new Storage(settings.storageSettings)
this.lnd = new LND(settings.lndSettings, this.addressPaidCb, this.invoicePaidCb) this.lnd = new LND(settings.lndSettings, this.addressPaidCb, this.invoicePaidCb)
this.productManager = new ProductManager(this.storage, this.lnd, this.settings)
} }
getServiceFee(action: Types.UserOperationType, amount: number): number { getServiceFee(action: Types.UserOperationType, amount: number): number {
switch (action) { switch (action) {
@ -119,8 +112,9 @@ export default class {
} }
async NewInvoice(userId: string, req: Types.NewInvoiceRequest): Promise<Types.NewInvoiceResponse> { async NewInvoice(userId: string, req: Types.NewInvoiceRequest): Promise<Types.NewInvoiceResponse> {
const user = await this.storage.GetUser(userId)
const res = await this.lnd.NewInvoice(req.amountSats, req.memo) const res = await this.lnd.NewInvoice(req.amountSats, req.memo)
const userInvoice = await this.storage.AddUserInvoice(userId, res.paymentRequest) const userInvoice = await this.storage.AddUserInvoice(user, res.paymentRequest)
return { return {
invoice: userInvoice.invoice invoice: userInvoice.invoice
} }

View file

@ -0,0 +1,36 @@
import Storage from '../storage/index.js'
import * as Types from '../../../proto/autogenerated/ts/types.js'
import LND from '../lnd/index.js'
import { MainSettings } from './settings.js'
export default class {
storage: Storage
settings: MainSettings
lnd: LND
constructor(storage: Storage, lnd: LND, settings: MainSettings) {
this.storage = storage
this.settings = settings
this.lnd = lnd
}
async AddProduct(userId: string, req: Types.AddProductRequest): Promise<Types.Product> {
const user = await this.storage.GetUser(userId)
const newProduct = await this.storage.productStorage.AddProduct(req.name, req.price_sats, user)
return {
id: newProduct.product_id,
name: newProduct.name,
price_sats: newProduct.price_sats,
}
}
async NewProductInvoice(id: string): Promise<Types.NewInvoiceResponse> {
const product = await this.storage.productStorage.GetProduct(id)
const newInvoice = await this.lnd.NewInvoice(product.price_sats, product.name)
await this.storage.AddUserInvoice(product.owner, newInvoice.paymentRequest, product)
return {
invoice: newInvoice.paymentRequest
}
}
}

View file

@ -0,0 +1,12 @@
import { StorageSettings } from '../storage/index.js'
import { LndSettings } from '../lnd/index.js'
export type MainSettings = {
storageSettings: StorageSettings,
lndSettings: LndSettings,
jwtSecret: string
incomingTxFee: number
outgoingTxFee: number
incomingInvoiceFee: number
outgoingInvoiceFee: number
serviceUrl: string
}

View file

@ -90,8 +90,17 @@ export default (mainHandler: Main): Types.ServerMethods => {
} }
return mainHandler.HandleLnurlPay(ctx.k1, +ctx.amount) return mainHandler.HandleLnurlPay(ctx.k1, +ctx.amount)
}, },
AddProduct: async (ctx, req) => {
return mainHandler.productManager.AddProduct(ctx.user_id, req)
},
NewProductInvoice: async (ctx) => {
if (!ctx.id) {
throw new Error("product id must be non empty")
}
return mainHandler.productManager.NewProductInvoice(ctx.id)
},
GetLNURLChannelLink: async (ctx) => { GetLNURLChannelLink: async (ctx) => {
throw new Error("unimplemented") throw new Error("unimplemented")
} },
} }
} }

View file

@ -10,6 +10,7 @@ import { UserTransactionPayment } from "./entity/UserTransactionPayment.js"
import { UserNostrAuth } from "./entity/UserNostrAuth.js" import { UserNostrAuth } from "./entity/UserNostrAuth.js"
import { UserBasicAuth } from "./entity/UserBasicAuth.js" import { UserBasicAuth } from "./entity/UserBasicAuth.js"
import { UserEphemeralKey } from "./entity/UserEphemeralKey.js" import { UserEphemeralKey } from "./entity/UserEphemeralKey.js"
import { Product } from "./entity/Product.js"
export type DbSettings = { export type DbSettings = {
databaseFile: string databaseFile: string
} }
@ -21,7 +22,7 @@ export default async (settings: DbSettings) => {
type: "sqlite", type: "sqlite",
database: settings.databaseFile, database: settings.databaseFile,
//logging: true, //logging: true,
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment, UserNostrAuth, UserBasicAuth, UserEphemeralKey], entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment, UserNostrAuth, UserBasicAuth, UserEphemeralKey, Product],
synchronize: true synchronize: true
}).initialize() }).initialize()
} }

View file

@ -0,0 +1,18 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm"
import { User } from "./User.js"
@Entity()
export class Product {
@PrimaryGeneratedColumn('uuid')
product_id: string
@ManyToOne(type => User, { eager: true })
owner: User
@Column()
name: string
@Column()
price_sats: number
}

View file

@ -1,4 +1,5 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn } from "typeorm" import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn } from "typeorm"
import { Product } from "./Product.js"
import { User } from "./User.js" import { User } from "./User.js"
@Entity() @Entity()
@ -26,4 +27,7 @@ export class UserReceivingInvoice {
@Column({ default: 0 }) @Column({ default: 0 })
service_fee: number service_fee: number
@ManyToOne(type => Product, { eager: true })
product: Product | null
} }

View file

@ -10,6 +10,8 @@ import { UserTransactionPayment } from "./entity/UserTransactionPayment.js";
import { UserNostrAuth } from "./entity/UserNostrAuth.js"; import { UserNostrAuth } from "./entity/UserNostrAuth.js";
import { UserBasicAuth } from "./entity/UserBasicAuth.js"; import { UserBasicAuth } from "./entity/UserBasicAuth.js";
import { EphemeralKeyType, UserEphemeralKey } from "./entity/UserEphemeralKey.js"; import { EphemeralKeyType, UserEphemeralKey } from "./entity/UserEphemeralKey.js";
import ProductStorage from './productStorage.js'
import { Product } from "./entity/Product.js";
export type StorageSettings = { export type StorageSettings = {
dbSettings: DbSettings dbSettings: DbSettings
} }
@ -20,11 +22,13 @@ export const LoadStorageSettingsFromEnv = (test = false): StorageSettings => {
export default class { export default class {
DB: DataSource | EntityManager DB: DataSource | EntityManager
settings: StorageSettings settings: StorageSettings
productStorage: ProductStorage
constructor(settings: StorageSettings) { constructor(settings: StorageSettings) {
this.settings = settings this.settings = settings
} }
async Connect() { async Connect() {
this.DB = await NewDB(this.settings.dbSettings) this.DB = await NewDB(this.settings.dbSettings)
this.productStorage = new ProductStorage(this.DB)
} }
StartTransaction(exec: (entityManager: EntityManager) => Promise<void>) { StartTransaction(exec: (entityManager: EntityManager) => Promise<void>) {
return this.DB.transaction(exec) return this.DB.transaction(exec)
@ -134,11 +138,12 @@ export default class {
}) })
} }
async AddUserInvoice(userId: string, invoice: string, callbackUrl = "", entityManager = this.DB): Promise<UserReceivingInvoice> { async AddUserInvoice(user: User, invoice: string, product?: Product, callbackUrl = "", entityManager = this.DB): Promise<UserReceivingInvoice> {
const newUserInvoice = entityManager.getRepository(UserReceivingInvoice).create({ const newUserInvoice = entityManager.getRepository(UserReceivingInvoice).create({
invoice: invoice, invoice: invoice,
callbackUrl, callbackUrl,
user: await this.GetUser(userId, entityManager) user: user,
product: product
}) })
return entityManager.getRepository(UserReceivingInvoice).save(newUserInvoice) return entityManager.getRepository(UserReceivingInvoice).save(newUserInvoice)
} }

View file

@ -0,0 +1,23 @@
import { DataSource, EntityManager } from "typeorm"
import { Product } from "./entity/Product.js"
import { User } from "./entity/User.js"
export default class {
DB: DataSource | EntityManager
constructor(DB: DataSource | EntityManager) {
this.DB = DB
}
async AddProduct(name: string, priceSats: number, user: User, entityManager = this.DB): Promise<Product> {
const newProduct = entityManager.getRepository(Product).create({
name: name, price_sats: priceSats, owner: user
})
return entityManager.getRepository(Product).save(newProduct)
}
async GetProduct(id: string, entityManager = this.DB): Promise<Product> {
const product = await entityManager.getRepository(Product).findOne({ where: { product_id: id } })
if (!product) {
throw new Error("product not found")
}
return product
}
}