move stuff
This commit is contained in:
parent
d7cb1c38f3
commit
6a1cedd718
6 changed files with 222 additions and 173 deletions
|
|
@ -5,7 +5,7 @@ import * as Types from '../proto/autogenerated/ts/types.js'
|
|||
import NewNostrTransport, { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js';
|
||||
import { ERROR, getLogger } from "./services/helpers/logger.js";
|
||||
import { NdebitData, NofferData } from "@shocknet/clink-sdk";
|
||||
import { ManagementManager } from "./services/managementManager.js";
|
||||
import { ManagementManager } from "./services/main/managementManager.js";
|
||||
|
||||
export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings, onClientEvent: (e: { requestId: string }, fromPub: string) => void): { Stop: () => void, Send: NostrSend, Ping: () => Promise<void> } => {
|
||||
const log = getLogger({})
|
||||
|
|
@ -40,7 +40,8 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
|||
})
|
||||
|
||||
let nostr: Nostr;
|
||||
const managementManager = new ManagementManager((...args) => nostr.Send(...args), nostrSettings);
|
||||
const managementManager = new ManagementManager((...args: Parameters<NostrSend>) => nostr.Send(...args), nostrSettings, mainHandler.storage.managementStorage);
|
||||
mainHandler.managementManager = managementManager;
|
||||
|
||||
nostr = new Nostr(nostrSettings, mainHandler.utils, event => {
|
||||
let j: NostrRequest
|
||||
|
|
@ -76,7 +77,7 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
|||
}, event.startAtNano, event.startAtMs)
|
||||
})
|
||||
|
||||
return { Stop: () => nostr.Stop, Send: (...args) => nostr.Send(...args), Ping: () => nostr.Ping() }
|
||||
return { Stop: () => nostr.Stop, Send: (...args: Parameters<NostrSend>) => nostr.Send(...args), Ping: () => nostr.Ping() }
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { defaultInvoiceExpiry } from "../storage/paymentStorage.js"
|
|||
import { DebitManager } from "./debitManager.js"
|
||||
import { OfferManager } from "./offerManager.js"
|
||||
import webRTC from "../webRTC/index.js"
|
||||
import { ManagementManager } from "./managementManager.js"
|
||||
|
||||
type UserOperationsSub = {
|
||||
id: string
|
||||
|
|
@ -51,17 +52,16 @@ export default class {
|
|||
liquidityProvider: LiquidityProvider
|
||||
debitManager: DebitManager
|
||||
offerManager: OfferManager
|
||||
managementManager?: ManagementManager
|
||||
utils: Utils
|
||||
rugPullTracker: RugPullTracker
|
||||
unlocker: Unlocker
|
||||
//webRTC: webRTC
|
||||
nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") }
|
||||
nostrProcessPing: (() => Promise<void>) | null = null
|
||||
constructor(settings: MainSettings, storage: Storage, adminManager: AdminManager, utils: Utils, unlocker: Unlocker) {
|
||||
constructor(settings: MainSettings, utils: Utils, unlocker: Unlocker) {
|
||||
this.settings = settings
|
||||
this.storage = storage
|
||||
this.utils = utils
|
||||
this.adminManager = adminManager
|
||||
this.unlocker = unlocker
|
||||
const updateProviderBalance = (b: number) => this.storage.liquidityStorage.IncrementTrackedProviderBalance('lnPub', settings.liquiditySettings.liquidityProviderPub, b)
|
||||
this.liquidityProvider = new LiquidityProvider(settings.liquiditySettings.liquidityProviderPub, this.utils, this.invoicePaidCb, updateProviderBalance)
|
||||
|
|
@ -340,6 +340,10 @@ export default class {
|
|||
log({ unsigned: event })
|
||||
this.nostrSend({ type: 'app', appId: invoice.linkedApplication.app_id }, { type: 'event', event }, zapInfo.relays || undefined)
|
||||
}
|
||||
|
||||
async Start() {
|
||||
// ... existing code ...
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
195
src/services/main/managementManager.ts
Normal file
195
src/services/main/managementManager.ts
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
import { getRepository } from "typeorm";
|
||||
import { User } from "../storage/entity/User.js";
|
||||
import { UserOffer } from "../storage/entity/UserOffer.js";
|
||||
import { ManagementGrant } from "../storage/entity/ManagementGrant.js";
|
||||
import { NostrEvent, NostrSend, NostrSettings } from "../nostr/handler.js";
|
||||
import { ManagementStorage } from "../storage/managementStorage.js";
|
||||
|
||||
export class ManagementManager {
|
||||
private nostrSend: NostrSend;
|
||||
private settings: NostrSettings;
|
||||
private storage: ManagementStorage;
|
||||
|
||||
constructor(nostrSend: NostrSend, settings: NostrSettings, storage: ManagementStorage) {
|
||||
this.nostrSend = nostrSend;
|
||||
this.settings = settings;
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an incoming CLINK Manage request
|
||||
* @param event The raw Nostr event
|
||||
*/
|
||||
public async handleRequest(event: NostrEvent) {
|
||||
const app = this.settings.apps.find((a: any) => a.appId === event.appId);
|
||||
if (!app) {
|
||||
console.error(`App with id ${event.appId} not found in settings`);
|
||||
return; // Cannot proceed
|
||||
}
|
||||
|
||||
if (!this._validateRequest(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const grant = await this._checkGrant(event, app.publicKey);
|
||||
if (!grant) {
|
||||
this.sendErrorResponse(event.pubkey, "Permission denied.", app);
|
||||
return;
|
||||
}
|
||||
|
||||
await this._performAction(event, app);
|
||||
}
|
||||
|
||||
private _validateRequest(event: NostrEvent): boolean {
|
||||
// TODO: NIP-44 validation or similar
|
||||
return true;
|
||||
}
|
||||
|
||||
private async _checkGrant(event: NostrEvent, appPubkey: string): Promise<ManagementGrant | null> {
|
||||
const userIdTag = event.tags.find((t: string[]) => t[0] === 'p');
|
||||
if (!userIdTag) {
|
||||
return null;
|
||||
}
|
||||
const userId = userIdTag[1];
|
||||
|
||||
const grant = await this.storage.getGrant(userId, appPubkey);
|
||||
|
||||
if (!grant || (grant.expires_at && grant.expires_at.getTime() < Date.now())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return grant;
|
||||
}
|
||||
|
||||
private async _performAction(event: NostrEvent, app: {publicKey: string, appId: string}) {
|
||||
const actionTag = event.tags.find((t: string[]) => t[0] === 'a');
|
||||
if (!actionTag) {
|
||||
console.error("No action specified in event");
|
||||
return;
|
||||
}
|
||||
|
||||
const action = actionTag[1];
|
||||
|
||||
switch (action) {
|
||||
case "create":
|
||||
await this._createOffer(event, app);
|
||||
break;
|
||||
case "update":
|
||||
await this._updateOffer(event, app);
|
||||
break;
|
||||
case "delete":
|
||||
await this._deleteOffer(event, app);
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown action: ${action}`);
|
||||
this.sendErrorResponse(event.pubkey, `Unknown action: ${action}`, app);
|
||||
}
|
||||
}
|
||||
|
||||
private async _createOffer(event: NostrEvent, app: {publicKey: string, appId: string}) {
|
||||
const createDetailsTag = event.tags.find((t: string[]) => t[0] === 'd');
|
||||
if (!createDetailsTag || !createDetailsTag[1]) {
|
||||
console.error("No details provided for create action");
|
||||
return;
|
||||
}
|
||||
|
||||
const userId = event.tags.find((t: string[]) => t[0] === 'p')![1];
|
||||
|
||||
try {
|
||||
const offerData = JSON.parse(createDetailsTag[1]);
|
||||
const offerRepo = getRepository(UserOffer);
|
||||
const newOffer = offerRepo.create({
|
||||
...offerData,
|
||||
user_id: userId,
|
||||
managing_app_pubkey: app.publicKey
|
||||
});
|
||||
await offerRepo.save(newOffer);
|
||||
this.sendSuccessResponse(event.pubkey, "Offer created successfully", app);
|
||||
} catch (e) {
|
||||
console.error("Failed to parse or save offer data", e);
|
||||
this.sendErrorResponse(event.pubkey, "Failed to create offer", app);
|
||||
}
|
||||
}
|
||||
|
||||
private async _updateOffer(event: NostrEvent, app: {publicKey: string, appId: string}) {
|
||||
const updateTags = event.tags.filter((t: string[]) => t[0] === 'd');
|
||||
if (updateTags.length < 2) {
|
||||
console.error("Insufficient details for update action");
|
||||
return;
|
||||
}
|
||||
const offerIdToUpdate = updateTags[0][1];
|
||||
const updateData = JSON.parse(updateTags[1][1]);
|
||||
const offerRepo = getRepository(UserOffer);
|
||||
|
||||
try {
|
||||
const existingOffer = await offerRepo.findOne({where: { offer_id: offerIdToUpdate }});
|
||||
if (!existingOffer) {
|
||||
console.error(`Offer ${offerIdToUpdate} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingOffer.managing_app_pubkey !== app.publicKey) {
|
||||
console.error(`App ${app.publicKey} not authorized to update offer ${offerIdToUpdate}`);
|
||||
return;
|
||||
}
|
||||
|
||||
offerRepo.merge(existingOffer, updateData);
|
||||
await offerRepo.save(existingOffer);
|
||||
this.sendSuccessResponse(event.pubkey, "Offer updated successfully", app);
|
||||
} catch (e) {
|
||||
console.error("Failed to update offer data", e);
|
||||
this.sendErrorResponse(event.pubkey, "Failed to update offer", app);
|
||||
}
|
||||
}
|
||||
|
||||
private async _deleteOffer(event: NostrEvent, app: {publicKey: string, appId: string}) {
|
||||
const deleteDetailsTag = event.tags.find((t: string[]) => t[0] === 'd');
|
||||
if (!deleteDetailsTag || !deleteDetailsTag[1]) {
|
||||
console.error("No details provided for delete action");
|
||||
return;
|
||||
}
|
||||
const offerIdToDelete = deleteDetailsTag[1];
|
||||
const offerRepo = getRepository(UserOffer);
|
||||
|
||||
try {
|
||||
const offerToDelete = await offerRepo.findOne({where: { offer_id: offerIdToDelete }});
|
||||
if (!offerToDelete) {
|
||||
console.error(`Offer ${offerIdToDelete} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (offerToDelete.managing_app_pubkey !== app.publicKey) {
|
||||
console.error(`App ${app.publicKey} not authorized to delete offer ${offerIdToDelete}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await offerRepo.remove(offerToDelete);
|
||||
this.sendSuccessResponse(event.pubkey, "Offer deleted successfully", app);
|
||||
} catch (e) {
|
||||
console.error("Failed to delete offer", e);
|
||||
this.sendErrorResponse(event.pubkey, "Failed to delete offer", app);
|
||||
}
|
||||
}
|
||||
|
||||
private sendSuccessResponse(recipient: string, message: string, app: {publicKey: string, appId: string}) {
|
||||
const responseEvent = {
|
||||
kind: 21003,
|
||||
pubkey: app.publicKey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
content: JSON.stringify({ status: "success", message }),
|
||||
tags: [['p', recipient]],
|
||||
};
|
||||
this.nostrSend({ type: 'app', appId: app.appId }, { type: 'event', event: responseEvent, encrypt: { toPub: recipient } });
|
||||
}
|
||||
|
||||
private sendErrorResponse(recipient: string, message: string, app: {publicKey: string, appId: string}) {
|
||||
const responseEvent = {
|
||||
kind: 21003,
|
||||
pubkey: app.publicKey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
content: JSON.stringify({ status: "error", message }),
|
||||
tags: [['p', recipient]],
|
||||
};
|
||||
this.nostrSend({ type: 'app', appId: app.appId }, { type: 'event', event: responseEvent, encrypt: { toPub: recipient } });
|
||||
}
|
||||
}
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
import { getRepository } from "typeorm";
|
||||
import { User } from "./storage/entity/User";
|
||||
import { UserOffer } from "./storage/entity/UserOffer";
|
||||
import { ManagementGrant } from "./storage/entity/ManagementGrant";
|
||||
import { NostrEvent, NostrSend, NostrSettings } from "./nostr/handler.js";
|
||||
|
||||
export class ManagementManager {
|
||||
private nostrSend: NostrSend;
|
||||
private settings: NostrSettings;
|
||||
|
||||
constructor(nostrSend: NostrSend, settings: NostrSettings) {
|
||||
this.nostrSend = nostrSend;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an incoming CLINK Manage request
|
||||
* @param event The raw Nostr event
|
||||
*/
|
||||
public async handleRequest(event: NostrEvent) {
|
||||
const app = this.settings.apps.find(a => a.appId === event.appId);
|
||||
if (!app) {
|
||||
console.error(`App with id ${event.appId} not found in settings`);
|
||||
return; // Cannot proceed
|
||||
}
|
||||
const appPubkey = app.publicKey;
|
||||
|
||||
// Check grant
|
||||
const userIdTag = event.tags.find((t: string[]) => t[0] === 'p');
|
||||
if (!userIdTag) {
|
||||
console.error("No user specified in event");
|
||||
return;
|
||||
}
|
||||
const userId = userIdTag[1];
|
||||
const requestingPubkey = event.pubkey;
|
||||
|
||||
const grantRepo = getRepository(ManagementGrant);
|
||||
const grant = await grantRepo.findOne({ where: { user_id: userId, app_pubkey: appPubkey } });
|
||||
|
||||
if (!grant) {
|
||||
console.error(`No management grant found for app ${appPubkey} and user ${userId}`);
|
||||
this.sendErrorResponse(requestingPubkey, `No management grant found for app`, app);
|
||||
return;
|
||||
}
|
||||
|
||||
if (grant.expires_at && grant.expires_at.getTime() < Date.now()) {
|
||||
console.error(`Management grant for app ${appPubkey} and user ${userId} has expired`);
|
||||
this.sendErrorResponse(requestingPubkey, `Management grant has expired`, app);
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform action
|
||||
const actionTag = event.tags.find((t: string[]) => t[0] === 'a');
|
||||
if (!actionTag) {
|
||||
console.error("No action specified in event");
|
||||
return;
|
||||
}
|
||||
|
||||
const action = actionTag[1];
|
||||
const offerRepo = getRepository(UserOffer);
|
||||
|
||||
switch (action) {
|
||||
case "create":
|
||||
const createDetailsTag = event.tags.find((t: string[]) => t[0] === 'd');
|
||||
if (!createDetailsTag || !createDetailsTag[1]) {
|
||||
console.error("No details provided for create action");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const offerData = JSON.parse(createDetailsTag[1]);
|
||||
const newOffer = offerRepo.create({
|
||||
...offerData,
|
||||
user_id: userId,
|
||||
managing_app_pubkey: appPubkey
|
||||
});
|
||||
await offerRepo.save(newOffer);
|
||||
this.sendSuccessResponse(requestingPubkey, "Offer created successfully", app);
|
||||
} catch (e) {
|
||||
console.error("Failed to parse or save offer data", e);
|
||||
this.sendErrorResponse(requestingPubkey, "Failed to create offer", app);
|
||||
}
|
||||
break;
|
||||
case "update":
|
||||
const updateTags = event.tags.filter((t: string[]) => t[0] === 'd');
|
||||
if (updateTags.length < 2) {
|
||||
console.error("Insufficient details for update action");
|
||||
return;
|
||||
}
|
||||
const offerIdToUpdate = updateTags[0][1];
|
||||
const updateData = JSON.parse(updateTags[1][1]);
|
||||
|
||||
try {
|
||||
const existingOffer = await offerRepo.findOne({where: { offer_id: offerIdToUpdate }});
|
||||
if (!existingOffer) {
|
||||
console.error(`Offer ${offerIdToUpdate} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingOffer.managing_app_pubkey !== appPubkey) {
|
||||
console.error(`App ${appPubkey} not authorized to update offer ${offerIdToUpdate}`);
|
||||
return;
|
||||
}
|
||||
|
||||
offerRepo.merge(existingOffer, updateData);
|
||||
await offerRepo.save(existingOffer);
|
||||
this.sendSuccessResponse(requestingPubkey, "Offer updated successfully", app);
|
||||
} catch (e) {
|
||||
console.error("Failed to update offer data", e);
|
||||
this.sendErrorResponse(requestingPubkey, "Failed to update offer", app);
|
||||
}
|
||||
break;
|
||||
case "delete":
|
||||
const deleteDetailsTag = event.tags.find((t: string[]) => t[0] === 'd');
|
||||
if (!deleteDetailsTag || !deleteDetailsTag[1]) {
|
||||
console.error("No details provided for delete action");
|
||||
return;
|
||||
}
|
||||
const offerIdToDelete = deleteDetailsTag[1];
|
||||
|
||||
try {
|
||||
const offerToDelete = await offerRepo.findOne({where: { offer_id: offerIdToDelete }});
|
||||
if (!offerToDelete) {
|
||||
console.error(`Offer ${offerIdToDelete} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (offerToDelete.managing_app_pubkey !== appPubkey) {
|
||||
console.error(`App ${appPubkey} not authorized to delete offer ${offerIdToDelete}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await offerRepo.remove(offerToDelete);
|
||||
this.sendSuccessResponse(requestingPubkey, "Offer deleted successfully", app);
|
||||
} catch (e) {
|
||||
console.error("Failed to delete offer", e);
|
||||
this.sendErrorResponse(requestingPubkey, "Failed to delete offer", app);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown action: ${action}`);
|
||||
this.sendErrorResponse(requestingPubkey, `Unknown action: ${action}`, app);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private sendSuccessResponse(recipient: string, message: string, app: {publicKey: string, appId: string}) {
|
||||
const responseEvent = {
|
||||
kind: 21003,
|
||||
pubkey: app.publicKey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
content: JSON.stringify({ status: "success", message }),
|
||||
tags: [['p', recipient]],
|
||||
};
|
||||
this.nostrSend({ type: 'app', appId: app.appId }, { type: 'event', event: responseEvent, encrypt: { toPub: recipient } });
|
||||
}
|
||||
|
||||
private sendErrorResponse(recipient: string, message: string, app: {publicKey: string, appId: string}) {
|
||||
const responseEvent = {
|
||||
kind: 21003,
|
||||
pubkey: app.publicKey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
content: JSON.stringify({ status: "error", message }),
|
||||
tags: [['p', recipient]],
|
||||
};
|
||||
this.nostrSend({ type: 'app', appId: app.appId }, { type: 'event', event: responseEvent, encrypt: { toPub: recipient } });
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import EventsLogManager from "./eventsLog.js";
|
|||
import { LiquidityStorage } from "./liquidityStorage.js";
|
||||
import DebitStorage from "./debitStorage.js"
|
||||
import OfferStorage from "./offerStorage.js"
|
||||
import { ManagementStorage } from "./managementStorage.js";
|
||||
import { StorageInterface, TX } from "./db/storageInterface.js";
|
||||
import { PubLogger } from "../helpers/logger.js"
|
||||
import { TlvStorageFactory } from './tlv/tlvFilesStorageFactory.js';
|
||||
|
|
@ -36,6 +37,7 @@ export default class {
|
|||
liquidityStorage: LiquidityStorage
|
||||
debitStorage: DebitStorage
|
||||
offerStorage: OfferStorage
|
||||
managementStorage: ManagementStorage
|
||||
eventsLog: EventsLogManager
|
||||
utils: Utils
|
||||
constructor(settings: StorageSettings, utils: Utils) {
|
||||
|
|
@ -58,6 +60,7 @@ export default class {
|
|||
this.liquidityStorage = new LiquidityStorage(this.dbs)
|
||||
this.debitStorage = new DebitStorage(this.dbs)
|
||||
this.offerStorage = new OfferStorage(this.dbs)
|
||||
this.managementStorage = new ManagementStorage(this.dbs);
|
||||
try { if (this.settings.dataDir) fs.mkdirSync(this.settings.dataDir) } catch (e) { }
|
||||
/* const executedMetricsMigrations = */ await this.metricsStorage.Connect()
|
||||
/* if (executedMigrations.length > 0) {
|
||||
|
|
|
|||
13
src/services/storage/managementStorage.ts
Normal file
13
src/services/storage/managementStorage.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { StorageInterface } from "./db/storageInterface.js";
|
||||
import { ManagementGrant } from "./entity/ManagementGrant.js";
|
||||
|
||||
export class ManagementStorage {
|
||||
private dbs: StorageInterface;
|
||||
constructor(dbs: StorageInterface) {
|
||||
this.dbs = dbs;
|
||||
}
|
||||
|
||||
getGrant(user_id: string, app_pubkey: string) {
|
||||
return this.dbs.FindOne<ManagementGrant>('ManagementGrant' as any, { where: { user_id, app_pubkey } });
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue