initial commit
by this point we have: - flask boilerplate - sqlite - user creation/deletion cli
This commit is contained in:
commit
00314f155a
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
venv
|
||||||
|
__pycache__/
|
||||||
|
.env
|
||||||
|
config.yml
|
||||||
|
instance/
|
8
config.yml.example
Normal file
8
config.yml.example
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# vim: filetype=yaml
|
||||||
|
|
||||||
|
secret_key: ""
|
||||||
|
|
||||||
|
sqlalchemy_basedir: "sqlite:///"
|
||||||
|
sqlalchemy_basename: "sachet"
|
||||||
|
|
||||||
|
# bcrypt_log_rounds: 13
|
16
requirements.txt
Normal file
16
requirements.txt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
bcrypt==4.0.1
|
||||||
|
click==8.1.3
|
||||||
|
Flask==2.2.3
|
||||||
|
Flask-Bcrypt==1.0.1
|
||||||
|
Flask-Cors==3.0.10
|
||||||
|
Flask-Script==2.0.6
|
||||||
|
Flask-SQLAlchemy==3.0.3
|
||||||
|
greenlet==2.0.2
|
||||||
|
itsdangerous==2.1.2
|
||||||
|
Jinja2==3.1.2
|
||||||
|
MarkupSafe==2.1.2
|
||||||
|
PyYAML==6.0
|
||||||
|
six==1.16.0
|
||||||
|
SQLAlchemy==2.0.5.post1
|
||||||
|
typing_extensions==4.5.0
|
||||||
|
Werkzeug==2.2.3
|
0
sachet/__init__.py
Normal file
0
sachet/__init__.py
Normal file
22
sachet/server/__init__.py
Normal file
22
sachet/server/__init__.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import os
|
||||||
|
from flask import Flask
|
||||||
|
from flask_cors import CORS
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_bcrypt import Bcrypt
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
CORS(app)
|
||||||
|
|
||||||
|
app_settings = os.getenv(
|
||||||
|
'APP_SETTINGS',
|
||||||
|
'sachet.server.config.DevelopmentConfig'
|
||||||
|
)
|
||||||
|
app.config.from_object(app_settings)
|
||||||
|
|
||||||
|
bcrypt = Bcrypt(app)
|
||||||
|
db = SQLAlchemy(app)
|
||||||
|
|
||||||
|
import sachet.server.commands
|
||||||
|
|
||||||
|
from sachet.server.auth.views import auth_blueprint
|
||||||
|
app.register_blueprint(auth_blueprint)
|
0
sachet/server/auth/__init__.py
Normal file
0
sachet/server/auth/__init__.py
Normal file
7
sachet/server/auth/views.py
Normal file
7
sachet/server/auth/views.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
auth_blueprint = Blueprint("auth_blueprint", __name__)
|
||||||
|
|
||||||
|
@auth_blueprint.route('/')
|
||||||
|
def index():
|
||||||
|
return "Hello world"
|
65
sachet/server/commands.py
Normal file
65
sachet/server/commands.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import click
|
||||||
|
from sachet.server import app, db
|
||||||
|
from sachet.server.models import User
|
||||||
|
from flask.cli import AppGroup
|
||||||
|
|
||||||
|
|
||||||
|
db_cli = AppGroup("db")
|
||||||
|
|
||||||
|
@db_cli.command("create")
|
||||||
|
def create_db():
|
||||||
|
"""Create all db tables."""
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
|
||||||
|
@db_cli.command("drop")
|
||||||
|
@click.option('--yes', is_flag=True, expose_value=False, prompt="Are you sure you want to drop all tables?")
|
||||||
|
def drop_db():
|
||||||
|
"""Drop all db tables."""
|
||||||
|
db.drop_all()
|
||||||
|
|
||||||
|
app.cli.add_command(db_cli)
|
||||||
|
|
||||||
|
|
||||||
|
user_cli = AppGroup("user")
|
||||||
|
|
||||||
|
@user_cli.command("create")
|
||||||
|
@click.option("--admin", default=False, prompt="Set this user as administrator?", help="Set this user an administrator.")
|
||||||
|
@click.option("--username", prompt="Username", help="Sets the username.")
|
||||||
|
@click.option("--password",
|
||||||
|
prompt="Password",
|
||||||
|
hide_input=True,
|
||||||
|
help="Sets the user's password (for security, avoid setting this from the command line).")
|
||||||
|
def create_user(admin, username, password):
|
||||||
|
"""Create a user directly in the database."""
|
||||||
|
user = User.query.filter_by(username=username).first()
|
||||||
|
if not user:
|
||||||
|
user = User(
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
|
admin=admin
|
||||||
|
)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
else:
|
||||||
|
raise Exception(f"User '{username}' already exists (ID: {user.id})")
|
||||||
|
|
||||||
|
@user_cli.command("delete")
|
||||||
|
@click.option("--username", "selector", help="Delete by username.", flag_value="username", default=True)
|
||||||
|
@click.option("--id", "selector", help="Delete by ID.", flag_value="ID", default=True)
|
||||||
|
@click.argument("usersel")
|
||||||
|
@click.option('--yes', is_flag=True, expose_value=False, prompt=f"Are you sure you want to delete this user?")
|
||||||
|
def delete_user(selector, usersel):
|
||||||
|
if selector == "username":
|
||||||
|
user = User.query.filter_by(username=usersel).first()
|
||||||
|
elif selector == "ID":
|
||||||
|
user = User.query.filter_by(id=usersel).first()
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
raise KeyError(f"User ({selector} {usersel}) does not exist.")
|
||||||
|
else:
|
||||||
|
db.session.delete(user)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
app.cli.add_command(user_cli)
|
39
sachet/server/config.py
Normal file
39
sachet/server/config.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
from os import environ, path
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
config_locations = ["/etc/sachet", "."]
|
||||||
|
config_path = ""
|
||||||
|
|
||||||
|
for dir in config_locations:
|
||||||
|
file_path = f"{dir}/config.yml"
|
||||||
|
if path.exists(file_path):
|
||||||
|
config_path = file_path
|
||||||
|
break
|
||||||
|
|
||||||
|
if config_path == "":
|
||||||
|
raise FileNotFoundError("Please create a configuration: copy config.yml.example to config.yml.")
|
||||||
|
|
||||||
|
config = yaml.safe_load(open(config_path))
|
||||||
|
|
||||||
|
if config["secret_key"] == "":
|
||||||
|
raise ValueError("Please set secret_key within the configuration.")
|
||||||
|
|
||||||
|
class BaseConfig:
|
||||||
|
DEBUG = False
|
||||||
|
SQLALCHEMY_DATABASE_URI = config["sqlalchemy_basedir"] + config["sqlalchemy_basename"] + ".db"
|
||||||
|
SECRET_KEY = config["secret_key"]
|
||||||
|
BCRYPT_LOG_ROUNDS = config.get("bcrypt_log_rounds", 13)
|
||||||
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
|
|
||||||
|
class TestingConfig(BaseConfig):
|
||||||
|
DEBUG = True
|
||||||
|
SQLALCHEMY_DATABASE_URI = config["sqlalchemy_basedir"] + config["sqlalchemy_basename"] + "_test" + ".db"
|
||||||
|
BCRYPT_LOG_ROUNDS = config.get("bcrypt_log_rounds", 4)
|
||||||
|
|
||||||
|
class DevelopmentConfig(BaseConfig):
|
||||||
|
DEBUG = True
|
||||||
|
SQLALCHEMY_DATABASE_URI = config["sqlalchemy_basedir"] + config["sqlalchemy_basename"] + "_dev" + ".db"
|
||||||
|
BCRYPT_LOG_ROUNDS = config.get("bcrypt_log_rounds", 4)
|
||||||
|
|
||||||
|
class ProductionConfig(BaseConfig):
|
||||||
|
DEBUG = False
|
19
sachet/server/models.py
Normal file
19
sachet/server/models.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from sachet.server import app, db, bcrypt
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
class User(db.Model):
|
||||||
|
__tablename__ = "users"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
|
username = db.Column(db.String(255), unique=True, nullable=False)
|
||||||
|
password = db.Column(db.String(255), nullable=False)
|
||||||
|
register_date = db.Column(db.DateTime, nullable=False)
|
||||||
|
admin = db.Column(db.Boolean, nullable=False, default=False)
|
||||||
|
|
||||||
|
def __init__(self, username, password, admin=False):
|
||||||
|
self.password = bcrypt.generate_password_hash(
|
||||||
|
password, app.config.get("BCRYPT_LOG_ROUNDS")
|
||||||
|
).decode()
|
||||||
|
self.username = username
|
||||||
|
self.register_date = datetime.datetime.now()
|
||||||
|
self.admin = admin
|
Loading…
Reference in New Issue
Block a user