add sql cli and explain it in the readme
This commit is contained in:
parent
8de802aff5
commit
0dc58ed6a8
49
README.md
49
README.md
@ -42,7 +42,7 @@ nano capsulflask/__init__.py
|
|||||||
Run the app
|
Run the app
|
||||||
|
|
||||||
```
|
```
|
||||||
FLASK_APP=capsulflask flask run
|
flask run
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the app in gunicorn
|
Run the app in gunicorn
|
||||||
@ -50,8 +50,49 @@ Run the app in gunicorn
|
|||||||
.venv/bin/gunicorn --bind 127.0.0.1:5000 capsulflask:app
|
.venv/bin/gunicorn --bind 127.0.0.1:5000 capsulflask:app
|
||||||
```
|
```
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
# postgres database schema management
|
## cli
|
||||||
|
|
||||||
|
You can manually mess around with the database like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
flask cli sql -f test.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
flask cli sql -c 'SELECT * FROM vms'
|
||||||
|
```
|
||||||
|
|
||||||
|
This one selects the vms table with the column name header:
|
||||||
|
|
||||||
|
```
|
||||||
|
flask cli sql -c "SELECT string_agg(column_name::text, ', ') from information_schema.columns WHERE table_name='vms'; SELECT * from vms"
|
||||||
|
```
|
||||||
|
|
||||||
|
How to modify a payment manually, like if you get a chargeback or to fix customer payment issues:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ flask cli sql -c "SELECT id, created, email, dollars, invalidated from payments"
|
||||||
|
1, 2020-05-05T00:00:00, forest.n.johnson@gmail.com, 20.00, FALSE
|
||||||
|
|
||||||
|
$ flask cli sql -c "UPDATE payments SET invalidated = True WHERE id = 1"
|
||||||
|
1 rows affected.
|
||||||
|
|
||||||
|
$ flask cli sql -c "SELECT id, created, email, dollars, invalidated from payments"
|
||||||
|
1, 2020-05-05T00:00:00, forest.n.johnson@gmail.com, 20.00, TRUE
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
How you would kick off the scheduled task:
|
||||||
|
|
||||||
|
```
|
||||||
|
flask cli cron-task
|
||||||
|
```
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## postgres database schema management
|
||||||
|
|
||||||
capsulflask has a concept of a schema version. When the application starts, it will query the database for a table named
|
capsulflask has a concept of a schema version. When the application starts, it will query the database for a table named
|
||||||
`schemaversion` that has one row and one column (`version`). If the `version` it finds is not equal to the `desiredSchemaVersion` variable set in `db.py`, it will run migration scripts from the `schema_migrations` folder one by one until the `schemaversion` table shows the correct version.
|
`schemaversion` that has one row and one column (`version`). If the `version` it finds is not equal to the `desiredSchemaVersion` variable set in `db.py`, it will run migration scripts from the `schema_migrations` folder one by one until the `schemaversion` table shows the correct version.
|
||||||
@ -62,9 +103,9 @@ For example, the script named `02_up_xyz.sql` should contain code that migrates
|
|||||||
|
|
||||||
In general, for safety, schema version upgrades should not delete data. Schema version downgrades will simply throw an error and exit for now.
|
In general, for safety, schema version upgrades should not delete data. Schema version downgrades will simply throw an error and exit for now.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## how to setup btcpay server
|
||||||
# how to setup btcpay server
|
|
||||||
|
|
||||||
Generate a private key and the accompanying bitpay SIN for the bitpay API client.
|
Generate a private key and the accompanying bitpay SIN for the bitpay API client.
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ from flask import Flask
|
|||||||
from flask_mail import Mail
|
from flask_mail import Mail
|
||||||
from flask import render_template
|
from flask import render_template
|
||||||
|
|
||||||
from capsulflask import virt_model
|
from capsulflask import virt_model, cli
|
||||||
|
|
||||||
load_dotenv(find_dotenv())
|
load_dotenv(find_dotenv())
|
||||||
|
|
||||||
@ -48,13 +48,14 @@ from capsulflask import db
|
|||||||
|
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
|
|
||||||
from capsulflask import auth, landing, console, payment, metrics
|
from capsulflask import auth, landing, console, payment, metrics, cli
|
||||||
|
|
||||||
app.register_blueprint(landing.bp)
|
app.register_blueprint(landing.bp)
|
||||||
app.register_blueprint(auth.bp)
|
app.register_blueprint(auth.bp)
|
||||||
app.register_blueprint(console.bp)
|
app.register_blueprint(console.bp)
|
||||||
app.register_blueprint(payment.bp)
|
app.register_blueprint(payment.bp)
|
||||||
app.register_blueprint(metrics.bp)
|
app.register_blueprint(metrics.bp)
|
||||||
|
app.register_blueprint(cli.bp)
|
||||||
|
|
||||||
app.add_url_rule("/", endpoint="index")
|
app.add_url_rule("/", endpoint="index")
|
||||||
|
|
||||||
|
63
capsulflask/cli.py
Normal file
63
capsulflask/cli.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import click
|
||||||
|
from flask.cli import with_appcontext
|
||||||
|
from flask import Blueprint
|
||||||
|
from psycopg2 import ProgrammingError
|
||||||
|
|
||||||
|
from capsulflask.db import get_model, my_exec_info_message
|
||||||
|
|
||||||
|
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():
|
||||||
|
print('a')
|
@ -146,12 +146,12 @@ class DBModel:
|
|||||||
|
|
||||||
def list_payments_for_account(self, email):
|
def list_payments_for_account(self, email):
|
||||||
self.cursor.execute("""
|
self.cursor.execute("""
|
||||||
SELECT dollars, invalidated, created
|
SELECT id, dollars, invalidated, created
|
||||||
FROM payments WHERE payments.email = %s""",
|
FROM payments WHERE payments.email = %s""",
|
||||||
(email, )
|
(email, )
|
||||||
)
|
)
|
||||||
return list(map(
|
return list(map(
|
||||||
lambda x: dict(dollars=x[0], invalidated=x[1], created=x[2]),
|
lambda x: dict(id=x[0], dollars=x[1], invalidated=x[2], created=x[3]),
|
||||||
self.cursor.fetchall()
|
self.cursor.fetchall()
|
||||||
))
|
))
|
||||||
|
|
||||||
|
@ -49,11 +49,12 @@ CREATE TABLE vm_ssh_public_key (
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE payments (
|
CREATE TABLE payments (
|
||||||
|
id SERIAL,
|
||||||
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
|
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
|
||||||
created TIMESTAMP NOT NULL DEFAULT NOW(),
|
created TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
dollars NUMERIC(8, 2) NOT NULL,
|
dollars NUMERIC(8, 2) NOT NULL,
|
||||||
invalidated BOOLEAN NOT NULL DEFAULT FALSE,
|
invalidated BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
PRIMARY KEY (email, created)
|
PRIMARY KEY (email, id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE login_tokens (
|
CREATE TABLE login_tokens (
|
||||||
@ -74,8 +75,8 @@ CREATE TABLE payment_sessions (
|
|||||||
CREATE TABLE unconfirmed_btcpay_invoices (
|
CREATE TABLE unconfirmed_btcpay_invoices (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
|
email TEXT REFERENCES accounts(email) ON DELETE RESTRICT,
|
||||||
created TIMESTAMP NOT NULL,
|
payment_id INTEGER NOT NULL,
|
||||||
FOREIGN KEY (email, created) REFERENCES payments(email, created) ON DELETE CASCADE
|
FOREIGN KEY (email, payment_id) REFERENCES payments(email, id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT INTO os_images (id, template_image_file_name, description)
|
INSERT INTO os_images (id, template_image_file_name, description)
|
||||||
@ -100,7 +101,7 @@ INSERT INTO accounts (email)
|
|||||||
VALUES ('forest.n.johnson@gmail.com');
|
VALUES ('forest.n.johnson@gmail.com');
|
||||||
|
|
||||||
INSERT INTO payments (email, dollars, created)
|
INSERT INTO payments (email, dollars, created)
|
||||||
VALUES ('forest.n.johnson@gmail.com', 20.00, TO_TIMESTAMP('2020-05-05','YYYY-MM-DD'));
|
VALUES ('forest.n.johnson@gmail.com', 20.00, TO_TIMESTAMP('2020-05-05','YYYY-MM-DDTHH24-MI-SS'));
|
||||||
|
|
||||||
INSERT INTO vms (id, email, os, size)
|
INSERT INTO vms (id, email, os, size)
|
||||||
VALUES ('capsul-yi9ffqbjly', 'forest.n.johnson@gmail.com', 'alpine311', 'f1-xx');
|
VALUES ('capsul-yi9ffqbjly', 'forest.n.johnson@gmail.com', 'alpine311', 'f1-xx');
|
||||||
|
Loading…
Reference in New Issue
Block a user