capsul-flask/capsulflask/cli.py

178 lines
7.2 KiB
Python

import os
import re
from datetime import datetime
import click
from flask.cli import with_appcontext
from flask import Blueprint
from flask import current_app
from psycopg2 import ProgrammingError
from flask_mail import Message
from capsulflask.db import get_model, my_exec_info_message
from capsulflask.console import get_account_balance
bp = Blueprint('cli', __name__)
@bp.cli.command('sql')
@click.option('-f', help='script filename')
@click.option('-c', help='sql command')
@with_appcontext
def sql_script(f, c):
"""Run a sql script against the database. script is run 1 command at a time inside a single transaction."""
model = get_model()
script = ""
if f:
filepath = os.path.join(os.getcwd(), f)
if not os.path.isfile(filepath):
raise f"{filepath} is not a file"
with open(filepath, 'rb') as file:
script = file.read().decode("utf8")
elif c:
script = c
else:
click.echo(f"you must provide sql to run either inline with the -c argument or in a file with the -f argument")
return
commands = re.split(";\\s+", script)
for command in commands:
if command.strip() != "":
model.cursor.execute(command)
if re.match("^\\s*select", command, re.IGNORECASE) is not None:
for row in model.cursor.fetchall():
def format_value(x):
if isinstance(x, bool):
return "true" if x else "false"
if not x :
return "null"
if isinstance(x, datetime):
return x.isoformat()
return f"{x}"
click.echo(", ".join(list(map(format_value, row))))
else:
click.echo(f"{model.cursor.rowcount} rows affected.")
model.connection.commit()
@bp.cli.command('cron-task')
@with_appcontext
def cron_task():
# make sure btcpay payments get completed (in case we miss a webhook), otherwise invalidate the payment
unresolved_btcpay_invoices = get_model().get_unresolved_btcpay_invoices()
for invoice in unresolved_btcpay_invoices:
invoice_id = invoice.id
invoice = current_app.config['BTCPAY_CLIENT'].get_invoice(invoice_id)
days = float((datetime.now() - invoice.created).total_seconds())/float(60*60*24)
if invoice['status'] == "complete":
get_model().btcpay_invoice_resolved(invoice_id, True)
elif days >= 1:
get_model().btcpay_invoice_resolved(invoice_id, False)
# notify when funds run out
accounts = get_model().accounts_list()
for account in accounts:
vms = get_model().list_vms_for_account(account['email'])
payments = get_model().list_payments_for_account(account['email'])
balance_1w = get_account_balance(vms, payments, datetime.utcnow() + datetime.timedelta(days=7))
balance_1d = get_account_balance(vms, payments, datetime.utcnow() + datetime.timedelta(days=1))
balance_now = get_account_balance(vms, payments, datetime.utcnow())
current_warning = account['account_balance_warning']
delete_at_account_balance = -10
pluralize_capsul = "s" if len(vms) > 1 else ""
warnings = [
dict(
id='zero_1w',
active=balance_1w < 0,
subject="Capsul One Week Payment Reminder",
body=("According to our calculations, your Capsul account will run out of funds in one week.\n\n"
f"Log in now to re-fill your account! {current_app.config["BASE_URL"]}/console/account-balance\n\n"
"If you believe you have recieved this message in error, please let us know: support@cyberia.club")
),
dict(
id='zero_1d',
active=balance_1d < 0,
subject="Capsul One Day Payment Reminder",
body=("According to our calculations, your Capsul account will run out of funds tomorrow.\n\n"
f"Log in now to re-fill your account! {current_app.config["BASE_URL"]}/console/account-balance\n\n"
"If you believe you have recieved this message in error, please let us know: support@cyberia.club")
),
dict(
id='zero_now',
active=balance_now < 0,
subject="Your Capsul Account is No Longer Funded",
body=(f"You have run out of funds! You will no longer be able to create Capsul{pluralize_capsul}.\n\n"
f"As a courtesy, we'll let your existing Capsul{pluralize_capsul} keep running until your account "
"reaches a -$10 balance, at which point they will be deleted.\n\n"
f"Log in now to re-fill your account! {current_app.config["BASE_URL"]}/console/account-balance\n\n"
f"If you need help decomissioning your Capsul{pluralize_capsul}, "
"would like to request backups, or de-activate your account, please contact: support@cyberia.club")
),
dict(
id='delete_1w',
active=balance_1w < delete_at_account_balance,
subject=f"Your Capsul{pluralize_capsul} Will be Deleted In Less Than a Week",
body=("You have run out of funds and have not refilled your account.\n\n"
f"As a courtesy, we have let your existing Capsul{pluralize_capsul} keep running. "
f"However, your account will reach a -$10 balance some time next week and your Capsul{pluralize_capsul}""
"will be deleted.\n\n"
f"Log in now to re-fill your account! {current_app.config["BASE_URL"]}/console/account-balance\n\n"
f"If you need help decomissioning your Capsul{pluralize_capsul}, "
"would like to request backups, or de-activate your account, please contact: support@cyberia.club")
),
dict(
id='delete_1d',
active=balance_1d < delete_at_account_balance,
subject=f"Last Chance to Save your Capsul{pluralize_capsul}: Gone Tomorrow",
body=("You have run out of funds and have not refilled your account.\n\n"
f"As a courtesy, we have let your existing Capsul{pluralize_capsul} keep running. "
f"However, your account will reach a -$10 balance tomorrow and your Capsul{pluralize_capsul} will be deleted.\n\n"
f"Last chance to deposit funds now and keep your Capsul{pluralize_capsul} running! {current_app.config["BASE_URL"]}/console/account-balance")
),
dict(
id='delete_now',
active=balance_now < delete_at_account_balance,
subject=f"Capsul{pluralize_capsul} Deleted",
body=(f"Your account reached a -$10 balance and your Capsul{pluralize_capsul} were deleted.")
)
]
current_warning_index = -1
if current_warning:
for i in range(0, len(warnings)):
if warnings[i]['id'] == current_warning:
current_warning_index = i
index_to_send = -1
for i in range(0, len(warnings)):
if i > current_warning_index and warnings[i].active:
index_to_send = i
if index_to_send > -1:
current_app.config["FLASK_MAIL_INSTANCE"].send(
Message(
warnings[index_to_send]['subject'],
body=warnings[index_to_send]['body'],
recipients=[account['email']]
)
)
get_model().set_account_balance_warning(account['email'], warnings[index_to_send]['id'])
if index_to_send == len(warnings)-1:
print('TODO: delete capsuls')
# make sure vm system and DB are synced