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
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,
|
|
})
|
|
}
|
|
|
|
|