Merge pull request #828 from shocknet/notifications
store and use notifications token
This commit is contained in:
commit
505846fb8b
28 changed files with 548 additions and 27 deletions
|
|
@ -18,6 +18,7 @@ import { InviteToken } from "./build/src/services/storage/entity/InviteToken.js"
|
|||
import { DebitAccess } from "./build/src/services/storage/entity/DebitAccess.js"
|
||||
import { UserOffer } from "./build/src/services/storage/entity/UserOffer.js"
|
||||
import { ManagementGrant } from "./build/src/services/storage/entity/ManagementGrant.js"
|
||||
import { AppUserDevice } from "./build/src/services/storage/entity/AppUserDevice.js"
|
||||
|
||||
import { Initial1703170309875 } from './build/src/services/storage/migrations/1703170309875-initial.js'
|
||||
import { LspOrder1718387847693 } from './build/src/services/storage/migrations/1718387847693-lsp_order.js'
|
||||
|
|
@ -43,7 +44,8 @@ export default new DataSource({
|
|||
PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043,
|
||||
UserOffer1733502626042, ManagementGrant1751307732346, InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611],
|
||||
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, AppUserDevice],
|
||||
// synchronize: true,
|
||||
})
|
||||
//npx typeorm migration:generate ./src/services/storage/migrations/management_grant_banned -d ./datasource.js
|
||||
//npx typeorm migration:generate ./src/services/storage/migrations/app_user_device -d ./datasource.js
|
||||
|
|
@ -33,6 +33,8 @@
|
|||
#PORT=1776
|
||||
#JWT_SECRET=
|
||||
|
||||
#SHOCK_PUSH_URL=
|
||||
|
||||
#Lightning Address Bridge
|
||||
#BRIDGE_URL=https://shockwallet.app
|
||||
|
||||
|
|
|
|||
|
|
@ -93,6 +93,11 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- input: [EnrollAdminTokenRequest](#EnrollAdminTokenRequest)
|
||||
- This methods has an __empty__ __response__ body
|
||||
|
||||
- EnrollMessagingToken
|
||||
- auth type: __User__
|
||||
- input: [MessagingToken](#MessagingToken)
|
||||
- This methods has an __empty__ __response__ body
|
||||
|
||||
- GetAppsMetrics
|
||||
- auth type: __Metrics__
|
||||
- input: [AppsMetricsRequest](#AppsMetricsRequest)
|
||||
|
|
@ -515,6 +520,13 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- input: [EnrollAdminTokenRequest](#EnrollAdminTokenRequest)
|
||||
- This methods has an __empty__ __response__ body
|
||||
|
||||
- EnrollMessagingToken
|
||||
- auth type: __User__
|
||||
- http method: __post__
|
||||
- http route: __/api/user/messaging/enroll__
|
||||
- input: [MessagingToken](#MessagingToken)
|
||||
- This methods has an __empty__ __response__ body
|
||||
|
||||
- GetApp
|
||||
- auth type: __App__
|
||||
- http method: __post__
|
||||
|
|
@ -1360,6 +1372,10 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
### ManageOperation
|
||||
- __npub__: _string_
|
||||
|
||||
### MessagingToken
|
||||
- __device_id__: _string_
|
||||
- __firebase_messaging_token__: _string_
|
||||
|
||||
### MetricsFile
|
||||
|
||||
### MigrationUpdate
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ type Client struct {
|
|||
EditDebit func(req DebitAuthorizationRequest) error
|
||||
EncryptionExchange func(req EncryptionExchangeRequest) error
|
||||
EnrollAdminToken func(req EnrollAdminTokenRequest) error
|
||||
EnrollMessagingToken func(req MessagingToken) error
|
||||
GetApp func() (*Application, error)
|
||||
GetAppUser func(req GetAppUserRequest) (*AppUser, error)
|
||||
GetAppUserLNURLInfo func(req GetAppUserLNURLInfoRequest) (*LnurlPayInfoResponse, error)
|
||||
|
|
@ -667,6 +668,30 @@ func NewClient(params ClientParams) *Client {
|
|||
}
|
||||
return nil
|
||||
},
|
||||
EnrollMessagingToken: func(req MessagingToken) error {
|
||||
auth, err := params.RetrieveUserAuth()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
finalRoute := "/api/user/messaging/enroll"
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result := ResultError{}
|
||||
err = json.Unmarshal(resBody, &result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.Status == "ERROR" {
|
||||
return fmt.Errorf(result.Reason)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
GetApp: func() (*Application, error) {
|
||||
auth, err := params.RetrieveAppAuth()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -445,6 +445,10 @@ type ManageAuthorizations struct {
|
|||
type ManageOperation struct {
|
||||
Npub string `json:"npub"`
|
||||
}
|
||||
type MessagingToken struct {
|
||||
Device_id string `json:"device_id"`
|
||||
Firebase_messaging_token string `json:"firebase_messaging_token"`
|
||||
}
|
||||
type MetricsFile struct {
|
||||
}
|
||||
type MigrationUpdate struct {
|
||||
|
|
|
|||
|
|
@ -427,6 +427,18 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
|||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'EnrollMessagingToken':
|
||||
if (!methods.EnrollMessagingToken) {
|
||||
throw new Error('method EnrollMessagingToken not found' )
|
||||
} else {
|
||||
const error = Types.MessagingTokenValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
await methods.EnrollMessagingToken({...operation, ctx}); responses.push({ status: 'OK' })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetDebitAuthorizations':
|
||||
if (!methods.GetDebitAuthorizations) {
|
||||
throw new Error('method GetDebitAuthorizations not found' )
|
||||
|
|
@ -847,6 +859,28 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
|||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
})
|
||||
if (!opts.allowNotImplementedMethods && !methods.EnrollMessagingToken) throw new Error('method: EnrollMessagingToken is not implemented')
|
||||
app.post('/api/user/messaging/enroll', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'EnrollMessagingToken', batch: false, nostr: false, batchSize: 0}
|
||||
const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
|
||||
let authCtx: Types.AuthContext = {}
|
||||
try {
|
||||
if (!methods.EnrollMessagingToken) throw new Error('method: EnrollMessagingToken is not implemented')
|
||||
const authContext = await opts.UserAuthGuard(req.headers['authorization'])
|
||||
authCtx = authContext
|
||||
stats.guard = process.hrtime.bigint()
|
||||
const request = req.body
|
||||
const error = Types.MessagingTokenValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
|
||||
const query = req.query
|
||||
const params = req.params
|
||||
await methods.EnrollMessagingToken({rpcName:'EnrollMessagingToken', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res.json({status: 'OK'})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); 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) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'GetApp', batch: false, nostr: false, batchSize: 0}
|
||||
|
|
|
|||
|
|
@ -276,6 +276,17 @@ export default (params: ClientParams) => ({
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
EnrollMessagingToken: async (request: Types.MessagingToken): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveUserAuth()
|
||||
if (auth === null) throw new Error('retrieveUserAuth() returned null')
|
||||
let finalRoute = '/api/user/messaging/enroll'
|
||||
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' }
|
||||
},
|
||||
GetApp: async (): Promise<ResultError | ({ status: 'OK' }& Types.Application)> => {
|
||||
const auth = await params.retrieveAppAuth()
|
||||
if (auth === null) throw new Error('retrieveAppAuth() returned null')
|
||||
|
|
|
|||
|
|
@ -233,6 +233,18 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
EnrollMessagingToken: async (request: Types.MessagingToken): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
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:'EnrollMessagingToken',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetAppsMetrics: async (request: Types.AppsMetricsRequest): Promise<ResultError | ({ status: 'OK' }& Types.AppsMetrics)> => {
|
||||
const auth = await params.retrieveNostrMetricsAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
|
||||
|
|
|
|||
|
|
@ -303,6 +303,18 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
|||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'EnrollMessagingToken':
|
||||
if (!methods.EnrollMessagingToken) {
|
||||
throw new Error('method not defined: EnrollMessagingToken')
|
||||
} else {
|
||||
const error = Types.MessagingTokenValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
await methods.EnrollMessagingToken({...operation, ctx}); responses.push({ status: 'OK' })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetDebitAuthorizations':
|
||||
if (!methods.GetDebitAuthorizations) {
|
||||
throw new Error('method not defined: GetDebitAuthorizations')
|
||||
|
|
@ -665,6 +677,22 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
|||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'EnrollMessagingToken':
|
||||
try {
|
||||
if (!methods.EnrollMessagingToken) throw new Error('method: EnrollMessagingToken is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.MessagingTokenValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
await methods.EnrollMessagingToken({rpcName:'EnrollMessagingToken', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK'})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetAppsMetrics':
|
||||
try {
|
||||
if (!methods.GetAppsMetrics) throw new Error('method: GetAppsMetrics is not implemented')
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ export type UserContext = {
|
|||
app_user_id: string
|
||||
user_id: string
|
||||
}
|
||||
export type UserMethodInputs = AddProduct_Input | AddUserOffer_Input | AuthorizeDebit_Input | AuthorizeManage_Input | BanDebit_Input | DecodeInvoice_Input | DeleteUserOffer_Input | EditDebit_Input | EnrollAdminToken_Input | GetDebitAuthorizations_Input | GetHttpCreds_Input | GetLNURLChannelLink_Input | GetLnurlPayLink_Input | GetLnurlWithdrawLink_Input | GetManageAuthorizations_Input | GetPaymentState_Input | GetUserInfo_Input | GetUserOffer_Input | GetUserOfferInvoices_Input | GetUserOffers_Input | GetUserOperations_Input | NewAddress_Input | NewInvoice_Input | NewProductInvoice_Input | PayAddress_Input | PayInvoice_Input | ResetDebit_Input | ResetManage_Input | RespondToDebit_Input | UpdateCallbackUrl_Input | UpdateUserOffer_Input | UserHealth_Input
|
||||
export type UserMethodOutputs = AddProduct_Output | AddUserOffer_Output | AuthorizeDebit_Output | AuthorizeManage_Output | BanDebit_Output | DecodeInvoice_Output | DeleteUserOffer_Output | EditDebit_Output | EnrollAdminToken_Output | GetDebitAuthorizations_Output | GetHttpCreds_Output | GetLNURLChannelLink_Output | GetLnurlPayLink_Output | GetLnurlWithdrawLink_Output | GetManageAuthorizations_Output | GetPaymentState_Output | GetUserInfo_Output | GetUserOffer_Output | GetUserOfferInvoices_Output | GetUserOffers_Output | GetUserOperations_Output | NewAddress_Output | NewInvoice_Output | NewProductInvoice_Output | PayAddress_Output | PayInvoice_Output | ResetDebit_Output | ResetManage_Output | RespondToDebit_Output | UpdateCallbackUrl_Output | UpdateUserOffer_Output | UserHealth_Output
|
||||
export type UserMethodInputs = AddProduct_Input | AddUserOffer_Input | AuthorizeDebit_Input | AuthorizeManage_Input | BanDebit_Input | DecodeInvoice_Input | DeleteUserOffer_Input | EditDebit_Input | EnrollAdminToken_Input | EnrollMessagingToken_Input | GetDebitAuthorizations_Input | GetHttpCreds_Input | GetLNURLChannelLink_Input | GetLnurlPayLink_Input | GetLnurlWithdrawLink_Input | GetManageAuthorizations_Input | GetPaymentState_Input | GetUserInfo_Input | GetUserOffer_Input | GetUserOfferInvoices_Input | GetUserOffers_Input | GetUserOperations_Input | NewAddress_Input | NewInvoice_Input | NewProductInvoice_Input | PayAddress_Input | PayInvoice_Input | ResetDebit_Input | ResetManage_Input | RespondToDebit_Input | UpdateCallbackUrl_Input | UpdateUserOffer_Input | UserHealth_Input
|
||||
export type UserMethodOutputs = AddProduct_Output | AddUserOffer_Output | AuthorizeDebit_Output | AuthorizeManage_Output | BanDebit_Output | DecodeInvoice_Output | DeleteUserOffer_Output | EditDebit_Output | EnrollAdminToken_Output | EnrollMessagingToken_Output | GetDebitAuthorizations_Output | GetHttpCreds_Output | GetLNURLChannelLink_Output | GetLnurlPayLink_Output | GetLnurlWithdrawLink_Output | GetManageAuthorizations_Output | GetPaymentState_Output | GetUserInfo_Output | GetUserOffer_Output | GetUserOfferInvoices_Output | GetUserOffers_Output | GetUserOperations_Output | NewAddress_Output | NewInvoice_Output | NewProductInvoice_Output | PayAddress_Output | PayInvoice_Output | ResetDebit_Output | ResetManage_Output | RespondToDebit_Output | UpdateCallbackUrl_Output | UpdateUserOffer_Output | UserHealth_Output
|
||||
export type AuthContext = AdminContext | AppContext | GuestContext | GuestWithPubContext | MetricsContext | UserContext
|
||||
|
||||
export type AddApp_Input = {rpcName:'AddApp', req: AddAppRequest}
|
||||
|
|
@ -99,6 +99,9 @@ export type EncryptionExchange_Output = ResultError | { status: 'OK' }
|
|||
export type EnrollAdminToken_Input = {rpcName:'EnrollAdminToken', req: EnrollAdminTokenRequest}
|
||||
export type EnrollAdminToken_Output = ResultError | { status: 'OK' }
|
||||
|
||||
export type EnrollMessagingToken_Input = {rpcName:'EnrollMessagingToken', req: MessagingToken}
|
||||
export type EnrollMessagingToken_Output = ResultError | { status: 'OK' }
|
||||
|
||||
export type GetApp_Input = {rpcName:'GetApp'}
|
||||
export type GetApp_Output = ResultError | ({ status: 'OK' } & Application)
|
||||
|
||||
|
|
@ -342,6 +345,7 @@ export type ServerMethods = {
|
|||
EditDebit?: (req: EditDebit_Input & {ctx: UserContext }) => Promise<void>
|
||||
EncryptionExchange?: (req: EncryptionExchange_Input & {ctx: GuestContext }) => Promise<void>
|
||||
EnrollAdminToken?: (req: EnrollAdminToken_Input & {ctx: UserContext }) => Promise<void>
|
||||
EnrollMessagingToken?: (req: EnrollMessagingToken_Input & {ctx: UserContext }) => Promise<void>
|
||||
GetApp?: (req: GetApp_Input & {ctx: AppContext }) => Promise<Application>
|
||||
GetAppUser?: (req: GetAppUser_Input & {ctx: AppContext }) => Promise<AppUser>
|
||||
GetAppUserLNURLInfo?: (req: GetAppUserLNURLInfo_Input & {ctx: AppContext }) => Promise<LnurlPayInfoResponse>
|
||||
|
|
@ -2616,6 +2620,29 @@ export const ManageOperationValidate = (o?: ManageOperation, opts: ManageOperati
|
|||
return null
|
||||
}
|
||||
|
||||
export type MessagingToken = {
|
||||
device_id: string
|
||||
firebase_messaging_token: string
|
||||
}
|
||||
export const MessagingTokenOptionalFields: [] = []
|
||||
export type MessagingTokenOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
device_id_CustomCheck?: (v: string) => boolean
|
||||
firebase_messaging_token_CustomCheck?: (v: string) => boolean
|
||||
}
|
||||
export const MessagingTokenValidate = (o?: MessagingToken, opts: MessagingTokenOptions = {}, path: string = 'MessagingToken::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.device_id !== 'string') return new Error(`${path}.device_id: is not a string`)
|
||||
if (opts.device_id_CustomCheck && !opts.device_id_CustomCheck(o.device_id)) return new Error(`${path}.device_id: custom check failed`)
|
||||
|
||||
if (typeof o.firebase_messaging_token !== 'string') return new Error(`${path}.firebase_messaging_token: is not a string`)
|
||||
if (opts.firebase_messaging_token_CustomCheck && !opts.firebase_messaging_token_CustomCheck(o.firebase_messaging_token)) return new Error(`${path}.firebase_messaging_token: custom check failed`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export type MetricsFile = {
|
||||
}
|
||||
export const MetricsFileOptionalFields: [] = []
|
||||
|
|
|
|||
|
|
@ -672,6 +672,12 @@ service LightningPub {
|
|||
option (http_route) = "/api/user/http_creds";
|
||||
option (nostr) = true;
|
||||
}
|
||||
rpc EnrollMessagingToken(structs.MessagingToken) returns (structs.Empty){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/messaging/enroll";
|
||||
option (nostr) = true;
|
||||
}
|
||||
rpc BatchUser(structs.Empty) returns (structs.Empty){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
|
|
|
|||
|
|
@ -812,4 +812,7 @@ message ProvidersDisruption {
|
|||
repeated ProviderDisruption disruptions = 1;
|
||||
}
|
||||
|
||||
|
||||
message MessagingToken {
|
||||
string device_id = 1;
|
||||
string firebase_messaging_token = 2;
|
||||
}
|
||||
|
|
|
|||
51
src/services/ShockPush/autogenerated/http_client.ts
Normal file
51
src/services/ShockPush/autogenerated/http_client.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
// This file was autogenerated from a .proto file, DO NOT EDIT!
|
||||
import axios from 'axios'
|
||||
import * as Types from './types.js'
|
||||
export type ResultError = { status: 'ERROR', reason: string }
|
||||
|
||||
export type ClientParams = {
|
||||
baseUrl: string
|
||||
retrieveAdminAuth: () => Promise<string | null>
|
||||
retrieveGuestAuth: () => Promise<string | null>
|
||||
retrieveNostrAppAuth: (rawBody: string, reqUrl: string, httpMethod: string) => Promise<string | null>
|
||||
encryptCallback: (plain: any) => Promise<any>
|
||||
decryptCallback: (encrypted: any) => Promise<any>
|
||||
deviceId: string
|
||||
checkResult?: true
|
||||
}
|
||||
export default (params: ClientParams) => ({
|
||||
EnrollServicePub: async (request: Types.ServiceNpub): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
let finalRoute = '/api/admin/service/enroll'
|
||||
const auth = await params.retrieveAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||
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' }
|
||||
},
|
||||
Health: async (): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
let finalRoute = '/api/health'
|
||||
const auth = await params.retrieveGuestAuth()
|
||||
if (auth === null) throw new Error('retrieveGuestAuth() returned null')
|
||||
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') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
SendNotification: async (request: Types.Notification): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
let finalRoute = '/api/user/notification'
|
||||
const rawBody = JSON.stringify(request)
|
||||
const auth = await params.retrieveNostrAppAuth(rawBody, finalRoute, 'post')
|
||||
if (auth === null) throw new Error('retrieveNostrAppAuth() returned null')
|
||||
const { data } = await axios.post(params.baseUrl + finalRoute, rawBody, { 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' }
|
||||
},
|
||||
})
|
||||
104
src/services/ShockPush/autogenerated/types.ts
Normal file
104
src/services/ShockPush/autogenerated/types.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// This file was autogenerated from a .proto file, DO NOT EDIT!
|
||||
|
||||
export type ResultError = { status: 'ERROR', reason: string }
|
||||
export type RequestInfo = { rpcName: string, batch: boolean, nostr: boolean, batchSize: number }
|
||||
export type RequestStats = { startMs: number, start: bigint, parse: bigint, guard: bigint, validate: bigint, handle: bigint }
|
||||
export type RequestMetric = AuthContext & RequestInfo & RequestStats & { error?: string }
|
||||
export type ProtoSocketState = 'OPEN' | 'CLOSED'
|
||||
export type ProtoSocket<T> = {
|
||||
getState: () => ProtoSocketState
|
||||
send: (res: T, err: Error | null) => void
|
||||
}
|
||||
export type AdminContext = {
|
||||
admin_id: string
|
||||
}
|
||||
export type AdminMethodInputs = EnrollServicePub_Input
|
||||
export type AdminMethodOutputs = EnrollServicePub_Output
|
||||
export type GuestContext = {
|
||||
}
|
||||
export type GuestMethodInputs = Health_Input
|
||||
export type GuestMethodOutputs = Health_Output
|
||||
export type NostrAppContext = {
|
||||
nostr_app_npub: string
|
||||
}
|
||||
export type NostrAppMethodInputs = SendNotification_Input
|
||||
export type NostrAppMethodOutputs = SendNotification_Output
|
||||
export type AuthContext = AdminContext | GuestContext | NostrAppContext
|
||||
|
||||
export type EnrollServicePub_Input = { rpcName: 'EnrollServicePub', req: ServiceNpub }
|
||||
export type EnrollServicePub_Output = ResultError | { status: 'OK' }
|
||||
|
||||
export type Health_Input = { rpcName: 'Health' }
|
||||
export type Health_Output = ResultError | { status: 'OK' }
|
||||
|
||||
export type SendNotification_Input = { rpcName: 'SendNotification', req: Notification }
|
||||
export type SendNotification_Output = ResultError | { status: 'OK' }
|
||||
|
||||
export type ServerMethods = {
|
||||
EnrollServicePub?: (req: EnrollServicePub_Input & { ctx: AdminContext }) => Promise<void>
|
||||
Health?: (req: Health_Input & { ctx: GuestContext }) => Promise<void>
|
||||
SendNotification?: (req: SendNotification_Input & { ctx: NostrAppContext }) => Promise<void>
|
||||
}
|
||||
|
||||
|
||||
export type OptionsBaseMessage = {
|
||||
allOptionalsAreSet?: true
|
||||
}
|
||||
|
||||
export type Empty = {
|
||||
}
|
||||
export const EmptyOptionalFields: [] = []
|
||||
export type EmptyOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
}
|
||||
export const EmptyValidate = (o?: Empty, opts: EmptyOptions = {}, path: string = 'Empty::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')
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export type Notification = {
|
||||
data: string
|
||||
recipient_registration_tokens: string[]
|
||||
}
|
||||
export const NotificationOptionalFields: [] = []
|
||||
export type NotificationOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
data_CustomCheck?: (v: string) => boolean
|
||||
recipient_registration_tokens_CustomCheck?: (v: string[]) => boolean
|
||||
}
|
||||
export const NotificationValidate = (o?: Notification, opts: NotificationOptions = {}, path: string = 'Notification::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.data !== 'string') return new Error(`${path}.data: is not a string`)
|
||||
if (opts.data_CustomCheck && !opts.data_CustomCheck(o.data)) return new Error(`${path}.data: custom check failed`)
|
||||
|
||||
if (!Array.isArray(o.recipient_registration_tokens)) return new Error(`${path}.recipient_registration_tokens: is not an array`)
|
||||
for (let index = 0; index < o.recipient_registration_tokens.length; index++) {
|
||||
if (typeof o.recipient_registration_tokens[index] !== 'string') return new Error(`${path}.recipient_registration_tokens[${index}]: is not a string`)
|
||||
}
|
||||
if (opts.recipient_registration_tokens_CustomCheck && !opts.recipient_registration_tokens_CustomCheck(o.recipient_registration_tokens)) return new Error(`${path}.recipient_registration_tokens: custom check failed`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export type ServiceNpub = {
|
||||
npub: string
|
||||
}
|
||||
export const ServiceNpubOptionalFields: [] = []
|
||||
export type ServiceNpubOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
npub_CustomCheck?: (v: string) => boolean
|
||||
}
|
||||
export const ServiceNpubValidate = (o?: ServiceNpub, opts: ServiceNpubOptions = {}, path: string = 'ServiceNpub::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.npub !== 'string') return new Error(`${path}.npub: is not a string`)
|
||||
if (opts.npub_CustomCheck && !opts.npub_CustomCheck(o.npub)) return new Error(`${path}.npub: custom check failed`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
61
src/services/ShockPush/index.ts
Normal file
61
src/services/ShockPush/index.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { nip98, UnsignedEvent, finalizeEvent } from 'nostr-tools'
|
||||
import { bytesToHex } from '@noble/hashes/utils'
|
||||
import { sha256 } from '@noble/hashes/sha256'
|
||||
import { base64 } from '@scure/base';
|
||||
import NewClient, { ClientParams } from './autogenerated/http_client.js'
|
||||
import { ERROR, getLogger } from '../helpers/logger.js'
|
||||
const utf8Encoder = new TextEncoder()
|
||||
export type PushPair = { pubkey: string, privateKey: string }
|
||||
const nip98Kind = 27235
|
||||
export class ShockPush {
|
||||
private client: ReturnType<typeof NewClient>
|
||||
private logger: ReturnType<typeof getLogger>
|
||||
private serviceBaseUrl: string
|
||||
private pair: PushPair
|
||||
constructor(shockPushUrl: string, pair: PushPair) {
|
||||
this.logger = getLogger({ component: 'shockPush' })
|
||||
this.serviceBaseUrl = shockPushUrl
|
||||
this.pair = pair
|
||||
this.client = NewClient({
|
||||
baseUrl: this.serviceBaseUrl,
|
||||
retrieveAdminAuth: async () => { throw new Error('not implemented') },
|
||||
retrieveGuestAuth: async () => (''),
|
||||
retrieveNostrAppAuth: async (rawBody, reqUrl, httpMethod) => this.generateNip98Header(rawBody, reqUrl, httpMethod),
|
||||
encryptCallback: () => { throw new Error('not implemented') },
|
||||
decryptCallback: () => { throw new Error('not implemented') },
|
||||
deviceId: '',
|
||||
})
|
||||
}
|
||||
|
||||
private generateNip98Header = async (raw: string, url: string, method: string) => {
|
||||
const tags = [
|
||||
["u", `${this.serviceBaseUrl}${url}`],
|
||||
["method", method.toUpperCase()]
|
||||
];
|
||||
|
||||
if (raw !== "") {
|
||||
tags.push(["payload", bytesToHex(sha256(utf8Encoder.encode(raw)))]);
|
||||
}
|
||||
|
||||
const npub = this.pair.pubkey
|
||||
|
||||
const event: UnsignedEvent = {
|
||||
created_at: Math.round(Date.now() / 1000),
|
||||
pubkey: npub,
|
||||
content: "",
|
||||
kind: nip98Kind,
|
||||
tags
|
||||
}
|
||||
|
||||
const signed = finalizeEvent(event, Buffer.from(this.pair.privateKey, 'hex'))
|
||||
const nip98Header = "Nostr " + base64.encode(utf8Encoder.encode(JSON.stringify(signed)));
|
||||
return nip98Header
|
||||
}
|
||||
|
||||
SendNotification = async (message: string, messagingTokens: string[]) => {
|
||||
const res = await this.client.SendNotification({ recipient_registration_tokens: messagingTokens, data: message })
|
||||
if (res.status !== 'OK') {
|
||||
this.logger(ERROR, `failed to send notification: ${res.status}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -111,4 +111,10 @@ export default class {
|
|||
user_identifier: ctx.app_user_id
|
||||
})
|
||||
}
|
||||
|
||||
async EnrollMessagingToken(ctx: Types.UserContext, req: Types.MessagingToken): Promise<void> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id);
|
||||
const user = await this.storage.applicationStorage.GetApplicationUser(app, ctx.app_user_id);
|
||||
await this.storage.applicationStorage.UpdateAppUserMessagingToken(user.identifier, req.device_id, req.firebase_messaging_token);
|
||||
}
|
||||
}
|
||||
|
|
@ -316,4 +316,6 @@ export default class {
|
|||
await this.storage.applicationStorage.SetInviteTokenAsUsed(inviteToken);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { nip44 } from 'nostr-tools'
|
||||
import fetch from "node-fetch"
|
||||
import Storage, { LoadStorageSettingsFromEnv } from '../storage/index.js'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
|
|
@ -28,6 +29,8 @@ import { parse } from "uri-template"
|
|||
import webRTC from "../webRTC/index.js"
|
||||
import { ManagementManager } from "./managementManager.js"
|
||||
import { Agent } from "https"
|
||||
import { NotificationsManager } from "./notificationsManager.js"
|
||||
import { ApplicationUser } from '../storage/entity/ApplicationUser.js'
|
||||
|
||||
type UserOperationsSub = {
|
||||
id: string
|
||||
|
|
@ -58,6 +61,7 @@ export default class {
|
|||
utils: Utils
|
||||
rugPullTracker: RugPullTracker
|
||||
unlocker: Unlocker
|
||||
notificationsManager: NotificationsManager
|
||||
//webRTC: webRTC
|
||||
nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") }
|
||||
nostrProcessPing: (() => Promise<void>) | null = null
|
||||
|
|
@ -81,6 +85,7 @@ export default class {
|
|||
this.debitManager = new DebitManager(this.storage, this.lnd, this.applicationManager)
|
||||
this.offerManager = new OfferManager(this.storage, this.settings, this.lnd, this.applicationManager, this.productManager, this.liquidityManager)
|
||||
this.managementManager = new ManagementManager(this.storage, this.settings)
|
||||
this.notificationsManager = new NotificationsManager(this.settings.shockPushBaseUrl)
|
||||
//this.webRTC = new webRTC(this.storage, this.utils)
|
||||
}
|
||||
|
||||
|
|
@ -287,7 +292,8 @@ export default class {
|
|||
|
||||
async triggerPaidCallback(log: PubLogger, url: string,
|
||||
{ invoice, amount, payerData, token, rejectUnauthorized }:
|
||||
{ invoice: string,
|
||||
{
|
||||
invoice: string,
|
||||
amount: number,
|
||||
payerData?: Record<string, string>,
|
||||
token?: string,
|
||||
|
|
@ -357,8 +363,27 @@ export default class {
|
|||
getLogger({ appName: app.name })("cannot notify user, not a nostr user")
|
||||
return
|
||||
}
|
||||
|
||||
const message: Types.LiveUserOperation & { requestId: string, status: 'OK' } = { operation: op, requestId: "GetLiveUserOperations", status: 'OK' }
|
||||
this.nostrSend({ type: 'app', appId: app.app_id }, { type: 'content', content: JSON.stringify(message), pub: user.nostr_public_key })
|
||||
const j = JSON.stringify(message)
|
||||
this.nostrSend({ type: 'app', appId: app.app_id }, { type: 'content', content: j, pub: user.nostr_public_key })
|
||||
this.SendEncryptedNotification(app, user, op)
|
||||
}
|
||||
|
||||
async SendEncryptedNotification(app: Application, appUser: ApplicationUser, op: Types.UserOperation) {
|
||||
const devices = await this.storage.applicationStorage.GetAppUserDevices(appUser.identifier)
|
||||
if (devices.length === 0 || !app.nostr_public_key || !app.nostr_private_key || !appUser.nostr_public_key) {
|
||||
return
|
||||
}
|
||||
const tokens = devices.map(d => d.firebase_messaging_token)
|
||||
const ck = nip44.getConversationKey(Buffer.from(app.nostr_private_key, 'hex'), appUser.nostr_public_key)
|
||||
const j = JSON.stringify(op)
|
||||
const encrypted = nip44.encrypt(j, ck)
|
||||
const encryptedData: { encrypted: string, app_npub_hex: string } = { encrypted, app_npub_hex: app.nostr_public_key }
|
||||
this.notificationsManager.SendNotification(JSON.stringify(encryptedData), tokens, {
|
||||
pubkey: app.nostr_public_key!,
|
||||
privateKey: app.nostr_private_key!
|
||||
})
|
||||
}
|
||||
|
||||
async UpdateBeacon(app: Application, content: { type: 'service', name: string }) {
|
||||
|
|
|
|||
31
src/services/main/notificationsManager.ts
Normal file
31
src/services/main/notificationsManager.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { PushPair, ShockPush } from "../ShockPush/index.js"
|
||||
import { getLogger, PubLogger } from "../helpers/logger.js"
|
||||
|
||||
export class NotificationsManager {
|
||||
private shockPushBaseUrl: string
|
||||
private clients: Record<string, ShockPush> = {}
|
||||
private logger: PubLogger
|
||||
constructor(shockPushBaseUrl: string) {
|
||||
this.shockPushBaseUrl = shockPushBaseUrl
|
||||
this.logger = getLogger({ component: 'notificationsManager' })
|
||||
}
|
||||
|
||||
private getClient = (pair: PushPair) => {
|
||||
const client = this.clients[pair.pubkey]
|
||||
if (client) {
|
||||
return client
|
||||
}
|
||||
const newClient = new ShockPush(this.shockPushBaseUrl, pair)
|
||||
this.clients[pair.pubkey] = newClient
|
||||
return newClient
|
||||
}
|
||||
|
||||
SendNotification = async (message: string, messagingTokens: string[], pair: PushPair) => {
|
||||
if (!this.shockPushBaseUrl) {
|
||||
this.logger("ShockPush is not configured, skipping notification")
|
||||
return
|
||||
}
|
||||
const client = this.getClient(pair)
|
||||
await client.SendNotification(message, messagingTokens)
|
||||
}
|
||||
}
|
||||
|
|
@ -39,6 +39,7 @@ export type MainSettings = {
|
|||
bridgeUrl: string,
|
||||
allowResetMetricsStorages: boolean
|
||||
allowHttpUpgrade: boolean
|
||||
shockPushBaseUrl: string
|
||||
}
|
||||
|
||||
export type BitcoinCoreSettings = {
|
||||
|
|
@ -81,7 +82,8 @@ export const LoadMainSettingsFromEnv = (): MainSettings => {
|
|||
lnurlMetaText: process.env.LNURL_META_TEXT || "LNURL via Lightning.pub",
|
||||
bridgeUrl: process.env.BRIDGE_URL || "https://shockwallet.app",
|
||||
allowResetMetricsStorages: process.env.ALLOW_RESET_METRICS_STORAGES === 'true' || false,
|
||||
allowHttpUpgrade: process.env.ALLOW_HTTP_UPGRADE === 'true' || false
|
||||
allowHttpUpgrade: process.env.ALLOW_HTTP_UPGRADE === 'true' || false,
|
||||
shockPushBaseUrl: process.env.SHOCK_PUSH_URL || ""
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -426,5 +426,13 @@ export default (mainHandler: Main): Types.ServerMethods => {
|
|||
GetHttpCreds: async ({ ctx }) => {
|
||||
return mainHandler.appUserManager.GetHttpCreds(ctx)
|
||||
},
|
||||
EnrollMessagingToken: async ({ ctx, req }) => {
|
||||
const err = Types.MessagingTokenValidate(req, {
|
||||
device_id_CustomCheck: id => id !== '',
|
||||
firebase_messaging_token_CustomCheck: token => token !== ''
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.appUserManager.EnrollMessagingToken(ctx, req)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { getLogger } from '../helpers/logger.js';
|
|||
import { User } from './entity/User.js';
|
||||
import { InviteToken } from './entity/InviteToken.js';
|
||||
import { StorageInterface } from './db/storageInterface.js';
|
||||
import { AppUserDevice } from './entity/AppUserDevice.js';
|
||||
export default class {
|
||||
dbs: StorageInterface
|
||||
userStorage: UserStorage
|
||||
|
|
@ -178,4 +179,23 @@ export default class {
|
|||
return this.dbs.Update<InviteToken>('InviteToken', inviteToken, { used: true })
|
||||
|
||||
}
|
||||
|
||||
async UpdateAppUserMessagingToken(appUserId: string, deviceId: string, firebaseMessagingToken: string) {
|
||||
const existing = await this.dbs.FindOne<AppUserDevice>('AppUserDevice', { where: { app_user_id: appUserId, device_id: deviceId } })
|
||||
if (!existing) {
|
||||
return this.dbs.CreateAndSave<AppUserDevice>('AppUserDevice', {
|
||||
app_user_id: appUserId,
|
||||
device_id: deviceId,
|
||||
firebase_messaging_token: firebaseMessagingToken
|
||||
})
|
||||
}
|
||||
if (existing.firebase_messaging_token === firebaseMessagingToken) {
|
||||
return
|
||||
}
|
||||
return this.dbs.Update<AppUserDevice>('AppUserDevice', existing.serial_id, { firebase_messaging_token: firebaseMessagingToken })
|
||||
}
|
||||
|
||||
async GetAppUserDevices(appUserId: string, txId?: string) {
|
||||
return this.dbs.Find<AppUserDevice>('AppUserDevice', { where: { app_user_id: appUserId } }, txId)
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ import { RootOperation } from "../entity/RootOperation.js"
|
|||
import { UserOffer } from "../entity/UserOffer.js"
|
||||
import { ManagementGrant } from "../entity/ManagementGrant.js"
|
||||
import { ChannelEvent } from "../entity/ChannelEvent.js"
|
||||
import { AppUserDevice } from "../entity/AppUserDevice.js"
|
||||
|
||||
|
||||
export type DbSettings = {
|
||||
|
|
@ -68,7 +69,8 @@ export const MainDbEntities = {
|
|||
'DebitAccess': DebitAccess,
|
||||
'UserOffer': UserOffer,
|
||||
'Product': Product,
|
||||
'ManagementGrant': ManagementGrant
|
||||
'ManagementGrant': ManagementGrant,
|
||||
'AppUserDevice': AppUserDevice
|
||||
}
|
||||
export type MainDbNames = keyof typeof MainDbEntities
|
||||
export const MainDbEntitiesNames = Object.keys(MainDbEntities)
|
||||
|
|
|
|||
22
src/services/storage/entity/AppUserDevice.ts
Normal file
22
src/services/storage/entity/AppUserDevice.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from "typeorm";
|
||||
|
||||
@Entity()
|
||||
export class AppUserDevice {
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@Column()
|
||||
app_user_id: string
|
||||
|
||||
@Column()
|
||||
device_id: string
|
||||
|
||||
@Column()
|
||||
firebase_messaging_token: string
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AppUserDevice1753285173175 implements MigrationInterface {
|
||||
name = 'AppUserDevice1753285173175'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "app_user_device" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_user_id" varchar NOT NULL, "device_id" varchar NOT NULL, "firebase_messaging_token" varchar NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE "app_user_device"`);
|
||||
}
|
||||
}
|
||||
|
|
@ -20,11 +20,15 @@ import { ChannelEvents1750777346411 } from './1750777346411-channel_events.js'
|
|||
import { ManagementGrant1751307732346 } from './1751307732346-management_grant.js'
|
||||
import { ManagementGrantBanned1751989251513 } from './1751989251513-management_grant_banned.js'
|
||||
import { InvoiceCallbackUrls1752425992291 } from './1752425992291-invoice_callback_urls.js'
|
||||
import { AppUserDevice1753285173175 } from './1753285173175-app_user_device.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,
|
||||
TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264,
|
||||
DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, ManagementGrantBanned1751989251513, InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611]
|
||||
DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, ManagementGrantBanned1751989251513, InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611, AppUserDevice1753285173175]
|
||||
|
||||
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> => {
|
||||
await connectAndMigrate(log, storageManager, allMigrations, allMetricsMigrations)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue