You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

378 lines
10 KiB

import {models} from '../models'
import {success, failure} from '../utils/res'
import {CronJob} from 'cron'
import {toCamel} from '../utils/case'
import * as cronUtils from '../utils/cron'
import * as socket from '../utils/socket'
import * as jsonUtils from '../utils/json'
import * as helpers from '../helpers'
import * as rsa from '../crypto/rsa'
import * as moment from 'moment'
import * as path from 'path'
import * as network from '../network'
const constants = require(path.join(__dirname,'../../config/constants.json'))
// store all current running jobs in memory
let jobs = {}
// init jobs from DB
export const initializeCronJobs = async () => {
await helpers.sleep(1000)
const subs = await getRawSubs({ where: { ended: false } })
subs.length && subs.forEach(sub => {
console.log("=> starting subscription cron job",sub.id+":",sub.cron)
startCronJob(sub)
})
}
async function startCronJob(sub) {
jobs[sub.id] = new CronJob(sub.cron, async function () {
const subscription = await 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
}
async function sendSubscriptionPayment(sub, isFirstMessage) {
const owner = await models.Contact.findOne({ where: { isOwner: true } })
var date = new Date();
date.setMilliseconds(0)
const subscription = await models.Subscription.findOne({ where: { id: sub.id } })
if (!subscription) {
return
}
const chat = await 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 = await models.Contact.findByPk(sub.contactId)
const enc = rsa.encrypt(contact.contactKey, text)
network.sendMessage({
chat: chat,
sender: owner,
type: constants.message_types.direct_payment,
message: { amount: sub.amount, content: enc },
amount: sub.amount,
success: async (data) => {
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]
}
await subscription.update(obj)
const forMe = true
const text2 = msgForSubPayment(owner, sub, isFirstMessage, forMe)
const encText = rsa.encrypt(owner.contactKey, text2)
const message = await 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: async (err) => {
console.log("SEND PAY ERROR")
let errMessage = constants.payment_errors[err] || 'Unknown'
errMessage = 'Payment Failed: ' + errMessage
const message = await 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
export async function pauseSubscription(req, res) {
const id = parseInt(req.params.id)
try {
const sub = await models.Subscription.findOne({ where: { id } })
if (sub) {
sub.update({ paused: true })
if (jobs[id]) jobs[id].stop()
success(res, jsonUtils.subscriptionToJson(sub,null))
} else {
failure(res, 'not found')
}
} catch (e) {
console.log('ERROR pauseSubscription', e)
failure(res, e)
}
};
// restart sub
export async function restartSubscription(req, res) {
const id = parseInt(req.params.id)
try {
const sub = await models.Subscription.findOne({ where: { id } })
if (sub) {
sub.update({ paused: false })
if (jobs[id]) jobs[id].start()
success(res, jsonUtils.subscriptionToJson(sub,null))
} else {
failure(res, 'not found')
}
} catch (e) {
console.log('ERROR restartSubscription', e)
failure(res, e)
}
};
async function getRawSubs(opts = {}) {
const options: {[k: string]: any} = { order: [['id', 'asc']], ...opts }
try {
const subs = await models.Subscription.findAll(options)
return subs
} catch (e) {
throw e
}
}
// all subs
export const getAllSubscriptions = async (req, res) => {
try {
const subs = await getRawSubs()
success(res, subs.map(sub => jsonUtils.subscriptionToJson(sub,null)))
} catch (e) {
console.log('ERROR getAllSubscriptions', e)
failure(res, e)
}
};
// one sub by id
export async function getSubscription(req, res) {
try {
const sub = await models.Subscription.findOne({ where: { id: req.params.id } })
success(res, jsonUtils.subscriptionToJson(sub,null))
} catch (e) {
console.log('ERROR getSubscription', e)
failure(res, e)
}
};
// delete sub by id
export async function deleteSubscription(req, res) {
const id = req.params.id
if (!id) return
try {
if (jobs[id]) {
jobs[id].stop()
delete jobs[id]
}
models.Subscription.destroy({ where: { id } })
success(res, true)
} catch (e) {
console.log('ERROR deleteSubscription', e)
failure(res, e)
}
};
// all subs for contact id
export const getSubscriptionsForContact = async (req, res) => {
try {
const subs = await getRawSubs({ where: { contactId: req.params.contactId } })
success(res, subs.map(sub => jsonUtils.subscriptionToJson(sub,null)))
} catch (e) {
console.log('ERROR getSubscriptionsForContact', e)
failure(res, e)
}
};
// create new sub
export async function createSubscription(req, res) {
const date = new Date()
date.setMilliseconds(0)
const s = jsonToSubscription({
...req.body,
count: 0,
total_paid: 0,
createdAt: date,
ended: false,
paused: false
})
if(!s.cron){
return failure(res, 'Invalid interval')
}
try {
const owner = await models.Contact.findOne({ where: { isOwner: true } })
const chat = await 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 failure(res, 'Invalid chat or contact')
}
const sub = await models.Subscription.create(s)
startCronJob(sub)
const isFirstMessage = true
sendSubscriptionPayment(sub, isFirstMessage)
success(res, jsonUtils.subscriptionToJson(sub, chat))
} catch (e) {
console.log('ERROR createSubscription', e)
failure(res, e)
}
};
export async function editSubscription(req, res) {
console.log('=> editSubscription')
const date = new Date()
date.setMilliseconds(0)
const id = parseInt(req.params.id)
const s = jsonToSubscription({
...req.body,
count: 0,
createdAt: date,
ended: false,
paused: false
})
try {
if(!id || !s.chatId || !s.cron){
return failure(res, 'Invalid data')
}
const subRecord = await models.Subscription.findOne({ where: { id }})
if(!subRecord) {
return failure(res, 'No subscription found')
}
// stop so it can be restarted
if (jobs[id]) jobs[id].stop()
const obj: {[k: string]: any} = {
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 = await subRecord.update(obj)
const end = checkSubscriptionShouldAlreadyHaveEnded(sub)
if(end) {
await subRecord.update({ended:true})
delete jobs[id]
} else {
startCronJob(sub) // restart
}
const chat = await models.Chat.findOne({ where: { id: s.chatId }})
success(res, jsonUtils.subscriptionToJson(sub, chat))
} catch (e) {
console.log('ERROR createSubscription', e)
failure(res, e)
}
};
function jsonToSubscription(j) {
console.log("=>",j)
const cron = cronUtils.make(j.interval)
return toCamel({
...j,
cron,
})
}