"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const models_1 = require("../models"); const res_1 = require("../utils/res"); const cron_1 = require("cron"); const case_1 = require("../utils/case"); const cronUtils = require("../utils/cron"); const socket = require("../utils/socket"); const jsonUtils = require("../utils/json"); const helpers = require("../helpers"); const rsa = require("../crypto/rsa"); const moment = require("moment"); const constants = require(__dirname + '/../../config/constants.json'); // store all current running jobs in memory let jobs = {}; // init jobs from DB const initializeCronJobs = () => __awaiter(void 0, void 0, void 0, function* () { yield helpers.sleep(1000); const subs = yield getRawSubs({ where: { ended: false } }); subs.length && subs.forEach(sub => { console.log("=> starting subscription cron job", sub.id + ":", sub.cron); startCronJob(sub); }); }); exports.initializeCronJobs = initializeCronJobs; function startCronJob(sub) { return __awaiter(this, void 0, void 0, function* () { jobs[sub.id] = new cron_1.CronJob(sub.cron, function () { return __awaiter(this, void 0, void 0, function* () { const subscription = yield models_1.models.Subscription.findOne({ where: { id: sub.id } }); if (!subscription) { delete jobs[sub.id]; return this.stop(); } console.log('EXEC CRON =>', subscription.id); if (subscription.paused) { // skip, still in jobs{} tho return this.stop(); } let STOP = checkSubscriptionShouldAlreadyHaveEnded(subscription); if (STOP) { // end the job and return console.log("stop"); subscription.update({ ended: true }); delete jobs[subscription.id]; return this.stop(); } // SEND PAYMENT!!! sendSubscriptionPayment(subscription, false); }); }, null, true); }); } function checkSubscriptionShouldAlreadyHaveEnded(sub) { if (sub.endDate) { const now = new Date(); if (now.getTime() > sub.endDate.getTime()) { return true; } } if (sub.endNumber) { if (sub.count >= sub.endNumber) { return true; } } return false; } function checkSubscriptionShouldEndAfterThisPayment(sub) { if (sub.endDate) { const { ms } = cronUtils.parse(sub.cron); const now = new Date(); if ((now.getTime() + ms) > sub.endDate.getTime()) { return true; } } if (sub.endNumber) { if (sub.count + 1 >= sub.endNumber) { return true; } } return false; } function msgForSubPayment(owner, sub, isFirstMessage, forMe) { let text = ''; if (isFirstMessage) { const alias = forMe ? 'You' : owner.alias; text = `${alias} subscribed\n`; } else { text = 'Subscription\n'; } text += `Amount: ${sub.amount} sats\n`; text += `Interval: ${cronUtils.parse(sub.cron).interval}\n`; if (sub.endDate) { text += `End: ${moment(sub.endDate).format('MM/DD/YY')}\n`; text += `Status: ${sub.count + 1} sent`; } else if (sub.endNumber) { text += `Status: ${sub.count + 1} of ${sub.endNumber} sent`; } return text; } function sendSubscriptionPayment(sub, isFirstMessage) { return __awaiter(this, void 0, void 0, function* () { const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); var date = new Date(); date.setMilliseconds(0); const subscription = yield models_1.models.Subscription.findOne({ where: { id: sub.id } }); if (!subscription) { return; } const chat = yield models_1.models.Chat.findOne({ where: { id: subscription.chatId } }); if (!subscription) { console.log("=> no sub for this payment!!!"); return; } const forMe = false; const text = msgForSubPayment(owner, sub, isFirstMessage, forMe); const contact = yield models_1.models.Contact.findByPk(sub.contactId); const enc = rsa.encrypt(contact.contactKey, text); helpers.sendMessage({ chat: chat, sender: owner, type: constants.message_types.direct_payment, message: { amount: sub.amount, content: enc }, amount: sub.amount, success: (data) => __awaiter(this, void 0, void 0, function* () { const shouldEnd = checkSubscriptionShouldEndAfterThisPayment(subscription); const obj = { totalPaid: parseFloat(subscription.totalPaid || 0) + parseFloat(subscription.amount), count: parseInt(subscription.count || 0) + 1, ended: false, }; if (shouldEnd) { obj.ended = true; if (jobs[sub.id]) jobs[subscription.id].stop(); delete jobs[subscription.id]; } yield subscription.update(obj); const forMe = true; const text2 = msgForSubPayment(owner, sub, isFirstMessage, forMe); const encText = rsa.encrypt(owner.contactKey, text2); const message = yield models_1.models.Message.create({ chatId: chat.id, sender: owner.id, type: constants.message_types.direct_payment, status: constants.statuses.confirmed, messageContent: encText, amount: subscription.amount, amountMsat: parseFloat(subscription.amount) * 1000, date: date, createdAt: date, updatedAt: date, subscriptionId: subscription.id, }); socket.sendJson({ type: 'direct_payment', response: jsonUtils.messageToJson(message, chat) }); }), failure: (err) => __awaiter(this, void 0, void 0, function* () { console.log("SEND PAY ERROR"); let errMessage = constants.payment_errors[err] || 'Unknown'; errMessage = 'Payment Failed: ' + errMessage; const message = yield models_1.models.Message.create({ chatId: chat.id, sender: owner.id, type: constants.message_types.direct_payment, status: constants.statuses.failed, messageContent: errMessage, amount: sub.amount, amountMsat: parseFloat(sub.amount) * 1000, date: date, createdAt: date, updatedAt: date, subscriptionId: sub.id, }); socket.sendJson({ type: 'direct_payment', response: jsonUtils.messageToJson(message, chat) }); }) }); }); } // pause sub function pauseSubscription(req, res) { return __awaiter(this, void 0, void 0, function* () { const id = parseInt(req.params.id); try { const sub = yield models_1.models.Subscription.findOne({ where: { id } }); if (sub) { sub.update({ paused: true }); if (jobs[id]) jobs[id].stop(); res_1.success(res, jsonUtils.subscriptionToJson(sub, null)); } else { res_1.failure(res, 'not found'); } } catch (e) { console.log('ERROR pauseSubscription', e); res_1.failure(res, e); } }); } exports.pauseSubscription = pauseSubscription; ; // restart sub function restartSubscription(req, res) { return __awaiter(this, void 0, void 0, function* () { const id = parseInt(req.params.id); try { const sub = yield models_1.models.Subscription.findOne({ where: { id } }); if (sub) { sub.update({ paused: false }); if (jobs[id]) jobs[id].start(); res_1.success(res, jsonUtils.subscriptionToJson(sub, null)); } else { res_1.failure(res, 'not found'); } } catch (e) { console.log('ERROR restartSubscription', e); res_1.failure(res, e); } }); } exports.restartSubscription = restartSubscription; ; function getRawSubs(opts = {}) { return __awaiter(this, void 0, void 0, function* () { const options = Object.assign({ order: [['id', 'asc']] }, opts); try { const subs = yield models_1.models.Subscription.findAll(options); return subs; } catch (e) { throw e; } }); } // all subs const getAllSubscriptions = (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const subs = yield getRawSubs(); res_1.success(res, subs.map(sub => jsonUtils.subscriptionToJson(sub, null))); } catch (e) { console.log('ERROR getAllSubscriptions', e); res_1.failure(res, e); } }); exports.getAllSubscriptions = getAllSubscriptions; // one sub by id function getSubscription(req, res) { return __awaiter(this, void 0, void 0, function* () { try { const sub = yield models_1.models.Subscription.findOne({ where: { id: req.params.id } }); res_1.success(res, jsonUtils.subscriptionToJson(sub, null)); } catch (e) { console.log('ERROR getSubscription', e); res_1.failure(res, e); } }); } exports.getSubscription = getSubscription; ; // delete sub by id function deleteSubscription(req, res) { return __awaiter(this, void 0, void 0, function* () { const id = req.params.id; if (!id) return; try { if (jobs[id]) { jobs[id].stop(); delete jobs[id]; } models_1.models.Subscription.destroy({ where: { id } }); res_1.success(res, true); } catch (e) { console.log('ERROR deleteSubscription', e); res_1.failure(res, e); } }); } exports.deleteSubscription = deleteSubscription; ; // all subs for contact id const getSubscriptionsForContact = (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const subs = yield getRawSubs({ where: { contactId: req.params.contactId } }); res_1.success(res, subs.map(sub => jsonUtils.subscriptionToJson(sub, null))); } catch (e) { console.log('ERROR getSubscriptionsForContact', e); res_1.failure(res, e); } }); exports.getSubscriptionsForContact = getSubscriptionsForContact; // create new sub function createSubscription(req, res) { return __awaiter(this, void 0, void 0, function* () { const date = new Date(); date.setMilliseconds(0); const s = jsonToSubscription(Object.assign(Object.assign({}, req.body), { count: 0, total_paid: 0, createdAt: date, ended: false, paused: false })); if (!s.cron) { return res_1.failure(res, 'Invalid interval'); } try { const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); const chat = yield helpers.findOrCreateChat({ chat_id: req.body.chat_id, owner_id: owner.id, recipient_id: req.body.contact_id, }); s.chatId = chat.id; // add chat id if newly created if (!owner || !chat) { return res_1.failure(res, 'Invalid chat or contact'); } const sub = yield models_1.models.Subscription.create(s); startCronJob(sub); const isFirstMessage = true; sendSubscriptionPayment(sub, isFirstMessage); res_1.success(res, jsonUtils.subscriptionToJson(sub, chat)); } catch (e) { console.log('ERROR createSubscription', e); res_1.failure(res, e); } }); } exports.createSubscription = createSubscription; ; function editSubscription(req, res) { return __awaiter(this, void 0, void 0, function* () { console.log('======> editSubscription'); const date = new Date(); date.setMilliseconds(0); const id = parseInt(req.params.id); const s = jsonToSubscription(Object.assign(Object.assign({}, req.body), { count: 0, createdAt: date, ended: false, paused: false })); try { if (!id || !s.chatId || !s.cron) { return res_1.failure(res, 'Invalid data'); } const subRecord = yield models_1.models.Subscription.findOne({ where: { id } }); if (!subRecord) { return res_1.failure(res, 'No subscription found'); } // stop so it can be restarted if (jobs[id]) jobs[id].stop(); const obj = { cron: s.cron, updatedAt: date, }; if (s.amount) obj.amount = s.amount; if (s.endDate) obj.endDate = s.endDate; if (s.endNumber) obj.endNumber = s.endNumber; const sub = yield subRecord.update(obj); const end = checkSubscriptionShouldAlreadyHaveEnded(sub); if (end) { yield subRecord.update({ ended: true }); delete jobs[id]; } else { startCronJob(sub); // restart } const chat = yield models_1.models.Chat.findOne({ where: { id: s.chatId } }); res_1.success(res, jsonUtils.subscriptionToJson(sub, chat)); } catch (e) { console.log('ERROR createSubscription', e); res_1.failure(res, e); } }); } exports.editSubscription = editSubscription; ; function jsonToSubscription(j) { console.log("=>", j); const cron = cronUtils.make(j.interval); return case_1.toCamel(Object.assign(Object.assign({}, j), { cron })); } //# sourceMappingURL=subscriptions.js.map