From f9b99ce66f56995a29709e9bf24750dab9430767 Mon Sep 17 00:00:00 2001 From: Cara Salter Date: Wed, 21 Sep 2022 21:33:40 -0400 Subject: bunch of features registration, logging out, listing networks, user profiles --- .flaskenv | 2 +- app/__init__.py | 2 + app/auth/__init__.py | 41 ++++++++++++++---- app/auth/forms.py | 1 + app/config.py | 1 + app/database.py | 27 ++++++++++-- app/manage/__init__.py | 50 ++++++++++++++++++++++ app/manage/forms.py | 8 ++++ app/meta/__init__.py | 1 - app/static/gen/style.css | 46 +++++++++----------- app/static/scss/style.scss | 10 ++++- app/templates/base.html | 11 +++++ app/templates/index.html | 8 ++++ app/templates/network_list.html | 42 ++++++++++++++++++ app/templates/profile.html | 46 ++++++++++++++++++++ app/templates/register.html | 17 ++++++++ migrations/versions/44e4ecea108b_.py | 34 +++++++++++++++ .../49b3fa6a4173_add_manager_to_network.py | 30 +++++++++++++ migrations/versions/4acd1ace5eb6_.py | 44 +++++++++++++++++++ .../versions/81173c1d4f0a_initial_migration.py | 45 +------------------ .../900a50c566c4_add_description_to_peers.py | 28 ++++++++++++ .../versions/afd561b2a827_add_admin_field.py | 28 ++++++++++++ .../bc8f89f077d8_uniquify_network_subnet.py | 28 ++++++++++++ migrations/versions/e7963e59fb3c_.py | 30 +++++++++++++ 24 files changed, 496 insertions(+), 84 deletions(-) create mode 100644 app/manage/__init__.py create mode 100644 app/manage/forms.py create mode 100644 app/templates/network_list.html create mode 100644 app/templates/profile.html create mode 100644 migrations/versions/44e4ecea108b_.py create mode 100644 migrations/versions/49b3fa6a4173_add_manager_to_network.py create mode 100644 migrations/versions/4acd1ace5eb6_.py create mode 100644 migrations/versions/900a50c566c4_add_description_to_peers.py create mode 100644 migrations/versions/afd561b2a827_add_admin_field.py create mode 100644 migrations/versions/bc8f89f077d8_uniquify_network_subnet.py create mode 100644 migrations/versions/e7963e59fb3c_.py diff --git a/.flaskenv b/.flaskenv index 37195f2..f8ed3ab 100644 --- a/.flaskenv +++ b/.flaskenv @@ -1,2 +1,2 @@ -FLASK_APP=app.py:create_app +FLASK_APP=app:create_app FLASK_ENV=development diff --git a/app/__init__.py b/app/__init__.py index a2486ed..37c992e 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -30,11 +30,13 @@ def create_app(): from .database import User, Role from . import auth from . import meta + from . import manage # Blueprints app.register_blueprint(auth.bp) app.register_blueprint(meta.bp) + app.register_blueprint(manage.bp) print(app.url_map) diff --git a/app/auth/__init__.py b/app/auth/__init__.py index b419351..06faa6d 100644 --- a/app/auth/__init__.py +++ b/app/auth/__init__.py @@ -1,6 +1,8 @@ -from flask import Blueprint, request, redirect, url_for, flash, render_template -from flask_login import current_user, login_user +from flask import Blueprint, request, redirect, url_for, flash, render_template, current_app +from flask_login import current_user, login_user, login_required +import flask_login from werkzeug.security import check_password_hash, generate_password_hash +from datetime import datetime from app.auth.forms import LoginForm, RegisterForm from app.database import User @@ -22,15 +24,21 @@ def login(): email = request.form.get('username') password = request.form.get('password') - u = User.query.fetch_one().filter_by(email=email) + u = User.query.filter_by(email=email).first() if u is not None: if check_password_hash(u.password, password): - login_user(u) + if u.active: + login_user(u) - flash("Logged in successfully") + u.last_login = datetime.now() + db.session.commit() + + flash("Logged in successfully") - return redirect(url_for("meta.home")) + return redirect(url_for("meta.home")) + else: + flash("User is inactive. Contact an administrator") else: flash("Incorrect password") @@ -57,7 +65,7 @@ def register(): # Passwords match user = User( - id=str(ulid.new()), + id=str(ulid.ulid()), email=email, password=generate_password_hash(password), pref_name=pref_name, @@ -73,4 +81,21 @@ def register(): else: flash("Passwords do not match") - return render_template("register.html") + return render_template("register.html", form=form) + + +@bp.route("/logout") +@login_required +def logout(): + flask_login.logout_user() + + return redirect("/") + +@bp.route("/profile") +@login_required +def profile(): + debug = current_app.config['DEBUG'] + peers = current_user.peers + networks = current_user.networks + return render_template("profile.html", debug=debug, peers=peers, + nets=networks) diff --git a/app/auth/forms.py b/app/auth/forms.py index 778e4fb..ce8ed4c 100644 --- a/app/auth/forms.py +++ b/app/auth/forms.py @@ -9,6 +9,7 @@ class LoginForm(FlaskForm): class RegisterForm(FlaskForm): username = StringField("Email", validators=[DataRequired()]) + pref_name = StringField("Preferred Name", validators=[DataRequired()]) password = PasswordField("Password", validators=[DataRequired()]) password_confirm = PasswordField("Confirm Password", validators=[DataRequired()]) submit = SubmitField("Register") diff --git a/app/config.py b/app/config.py index 2a157fa..fc37f36 100644 --- a/app/config.py +++ b/app/config.py @@ -6,6 +6,7 @@ def load_config(path): result = { 'SQLALCHEMY_DATABASE_URI': contents['database']['postgres_url'], 'SECRET_KEY': contents['server']['secret_key'], + 'DEBUG': contents['server']['debug'], 'SECURITY_REGISTERABLE': True, 'SECURITY_PASSWORD_SALT': contents['server']['secret_key'] } diff --git a/app/database.py b/app/database.py index 532f971..c66f603 100644 --- a/app/database.py +++ b/app/database.py @@ -5,15 +5,24 @@ from . import db from . import login from flask_login import UserMixin +from flask import redirect, url_for, flash + class User(db.Model, UserMixin): id = Column(String, primary_key=True) email = Column(String, unique=True, nullable=False) password = Column(String, nullable=False) pref_name = Column(String, nullable=False) last_login = Column(DateTime, nullable=False) - fs_uniquifier = Column(String, unique=True, nullable=False) active = Column(Boolean, nullable=False) + is_admin = Column(Boolean, nullable=False, default=False) roles = relationship('Role', secondary='roles_users',backref=backref('users', lazy='dynamic')) + peers = relationship('Peer', backref='owner', lazy=True) + networks = relationship('Network', backref="manager", lazy=True) + + def __repr__(self): + return f"" + def __str__(self): + return self.pref_name class Role(db.Model): id = Column(String, primary_key=True) @@ -28,15 +37,27 @@ class RolesUsers(db.Model): @login.user_loader def load_user(user_id): - return User.query.filer_by(id=user_id) + return User.query.filter_by(id=user_id).first() +@login.unauthorized_handler +def unauth(): + flash("Please log in first") + return redirect(url_for("auth.login")) class Peer(db.Model): id = Column(String, primary_key=True) addr = Column(CIDR, nullable=False) public_key = Column(String, nullable=False) + description = Column(String, nullable=False) + owner_id = Column(String, ForeignKey('user.id'), nullable=False) class Network(db.Model): id = Column(String, primary_key=True) - subnet = Column(CIDR, nullable=False) + subnet = Column(CIDR, nullable=False, unique=True) description = Column(String, nullable=True) + manager_id = Column(String, ForeignKey('user.id'), nullable=False) + + def __repr__(self): + return f"{self.description} ({self.subnet})" + def __str__(self): + return f"{self.description} ({self.subnet})" diff --git a/app/manage/__init__.py b/app/manage/__init__.py new file mode 100644 index 0000000..c69376f --- /dev/null +++ b/app/manage/__init__.py @@ -0,0 +1,50 @@ +from flask import Blueprint, render_template, request, flash, redirect, url_for +from flask_login import login_required, current_user +import ulid + +from app import db +from app.database import Network + +from .forms import NewNetworkForm + +bp = Blueprint('manage', __name__, url_prefix="/manage") + +@bp.route("/networks", methods=["GET", "POST"]) +@login_required +def list_networks(): + nets = current_user.networks + + form = NewNetworkForm(request.form) + + if request.method == "POST" and form.validate_on_submit(): + subnet = request.form.get('subnet') + description = request.form.get('description') + + n = Network( + id=str(ulid.ulid()), + subnet=subnet, + description=description, + manager_id=str(current_user.id) + ) + db.session.add(n) + db.session.commit() + + flash("Network added") + + return render_template("network_list.html", nets=nets, form=form) + +@bp.route("/networks//delete") +@login_required +def del_net(id): + n = Network.query.filter_by(id=id).first() + + if n.manager_id != current_user.id: + flash("You aren't a manager of this network.") + return redirect(url_for("manage.list_networks")) + + db.session.delete(n) + db.session.commit() + + flash("Network deleted") + + return redirect(url_for("manage.list_networks")) diff --git a/app/manage/forms.py b/app/manage/forms.py new file mode 100644 index 0000000..0849367 --- /dev/null +++ b/app/manage/forms.py @@ -0,0 +1,8 @@ +from flask_wtf import FlaskForm +from wtforms.fields.simple import PasswordField, StringField, SubmitField +from wtforms.validators import DataRequired, Regexp + +class NewNetworkForm(FlaskForm): + subnet = StringField("Subnet CIDR", validators=[DataRequired(), Regexp(r"^([0-9]{1,3}\.){3}[0-9]{1,3}($|/(8|16|24|32))$")]) + description = StringField("Description", validators=[DataRequired()]) + submit = SubmitField("Create") diff --git a/app/meta/__init__.py b/app/meta/__init__.py index 4fbe23c..8b6f6fa 100644 --- a/app/meta/__init__.py +++ b/app/meta/__init__.py @@ -6,5 +6,4 @@ bp = Blueprint("meta", __name__) @bp.route("/") def home(): - flash("Test") return render_template("index.html") diff --git a/app/static/gen/style.css b/app/static/gen/style.css index 9bca965..5159ee1 100644 --- a/app/static/gen/style.css +++ b/app/static/gen/style.css @@ -1,17 +1,14 @@ body { background: #282828; color: #ebdbb2; - font-family: monospace; -} + font-family: monospace; } a a:active, a:visited { - color: #458588; -} + color: #458588; } .container { margin: auto; - width: 60%; -} + width: 60%; } button, input[type=submit] { @@ -19,13 +16,15 @@ input[type=submit] { background-color: #458588; border-color: #458588; border: none; - margin: 0.5rem; -} + margin: 0.5rem; } button.accent { background-color: #d79921; - border-color: #d79921; -} + border-color: #d79921; } + +h1, h2, h3, h4, h5, h6 { + border-bottom: 1px solid; + width: 50%; } .navbar { list-style-type: none; @@ -34,44 +33,39 @@ button.accent { border-bottom: 1px solid; margin-bottom: 2rem; padding-bottom: 0.4rem; - text-align: center; -} + text-align: center; } .navbar-item { display: inline; - margin-right: 1rem; -} + margin-right: 1rem; } .flashes { list-style-type: none; display: flex; -} + justify-content: center; } .message { - width: 30%; + width: 80%; justify-content: center; border: 1px solid #ebdbb2; background-color: #d79921; - color: black; -} + padding: 0.2rem; + font-size: large; + color: black; } form { - width: 40%; -} + width: 40%; } label, input { margin-bottom: 0.5rem; margin-top: 0.5rem; - display: inline-block; -} + display: inline-block; } label { width: 40%; - text-align: left; -} + text-align: left; } label + input { width: 40%; - margin: 0 30% 0 4%; -} + margin: 0 30% 0 4%; } diff --git a/app/static/scss/style.scss b/app/static/scss/style.scss index cc23959..7c8760c 100644 --- a/app/static/scss/style.scss +++ b/app/static/scss/style.scss @@ -36,6 +36,11 @@ button.accent { border-color: $color-accent-bg; } +h1,h2,h3,h4,h5,h6 { + border-bottom: 1px solid; + width:50%; +} + // Navbar .navbar { @@ -56,13 +61,16 @@ button.accent { .flashes { list-style-type: none; display: flex; + justify-content: center; } .message { - width: 30%; + width: 80%; justify-content: center; border: 1px solid $color-fg; background-color: $color-accent-bg; + padding: 0.2rem; + font-size: large; color: black; } diff --git a/app/templates/base.html b/app/templates/base.html index d905156..735d430 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -14,6 +14,17 @@ {% with messages = get_flashed_messages() %} diff --git a/app/templates/index.html b/app/templates/index.html index 3c69e80..efaf29e 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1,5 +1,13 @@ {% extends "base.html" %} {% block content %} +{% if current_user.is_authenticated %} +

Hi, {{current_user.pref_name}}!

+{% else %} +

Hello!

+

Much of this tool requires authentication. Head on over here +to login, or here to register. +{% endif %} {% endblock %} diff --git a/app/templates/network_list.html b/app/templates/network_list.html new file mode 100644 index 0000000..f56c6dc --- /dev/null +++ b/app/templates/network_list.html @@ -0,0 +1,42 @@ +{% extends 'base.html' %} + +{% block content %} +

Managed Network Subnets

+ + + + + + + + + + + + + {% for n in nets %} + + + + + + + + {% endfor %} + +
IDSubnetDescriptionManagerDelete
{{n.id}}{{n.subnet}}{{n.description}}{{n.manager_id}}Delete
+ +

Want to add another one?

+
+ {{form.csrf_token}} +
+ {{form.description.label}} {{form.description}} +
+
+ {{form.subnet.label}} {{form.subnet}} +
+
+ {{form.submit}} +
+
+{% endblock %} diff --git a/app/templates/profile.html b/app/templates/profile.html new file mode 100644 index 0000000..6d4d60a --- /dev/null +++ b/app/templates/profile.html @@ -0,0 +1,46 @@ +{% extends 'base.html' %} + +{% block content %} +

{{current_user.pref_name}} - {{current_user.email}} ({{current_user.id}})

+ +{% if debug %} + + + + + + + + + {% for attr, value in current_user.__dict__.items() %} + + + + + {% endfor %} + +
KeyValue
{{attr}}{{value}}
+{% endif %} + +

Owned Peers

+ + + + + + + + + + + {% for p in peers %} + + + + + + + {% endfor %} + +
IDDescriptionAddressPublic Key
{{p.id}}{{p.description}}{{p.addr}}{{p.public_key}}
+{% endblock %} diff --git a/app/templates/register.html b/app/templates/register.html index 56d9855..6e19559 100644 --- a/app/templates/register.html +++ b/app/templates/register.html @@ -1,5 +1,22 @@ {% extends 'base.html' %} {% block content %} +
+ {{ form.csrf_token }} +
+ {{form.username.label}} {{form.username}} +
+
+ {{form.pref_name.label}} {{form.pref_name}} +
+
+ + {{form.password.label}} {{form.password}} + {{form.password_confirm.label}} {{form.password_confirm}} + +
+ + {{form.submit}} +
{% endblock %} diff --git a/migrations/versions/44e4ecea108b_.py b/migrations/versions/44e4ecea108b_.py new file mode 100644 index 0000000..c225770 --- /dev/null +++ b/migrations/versions/44e4ecea108b_.py @@ -0,0 +1,34 @@ +"""empty message + +Revision ID: 44e4ecea108b +Revises: e7963e59fb3c +Create Date: 2022-09-21 12:33:11.723559 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '44e4ecea108b' +down_revision = 'e7963e59fb3c' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('peer', sa.Column('owner_id', sa.String(), nullable=False)) + op.drop_constraint('peer_owner_fkey', 'peer', type_='foreignkey') + op.create_foreign_key(None, 'peer', 'user', ['owner_id'], ['id']) + op.drop_column('peer', 'owner') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('peer', sa.Column('owner', sa.VARCHAR(), autoincrement=False, nullable=False)) + op.drop_constraint(None, 'peer', type_='foreignkey') + op.create_foreign_key('peer_owner_fkey', 'peer', 'user', ['owner'], ['id']) + op.drop_column('peer', 'owner_id') + # ### end Alembic commands ### diff --git a/migrations/versions/49b3fa6a4173_add_manager_to_network.py b/migrations/versions/49b3fa6a4173_add_manager_to_network.py new file mode 100644 index 0000000..028c286 --- /dev/null +++ b/migrations/versions/49b3fa6a4173_add_manager_to_network.py @@ -0,0 +1,30 @@ +"""add manager to network + +Revision ID: 49b3fa6a4173 +Revises: 44e4ecea108b +Create Date: 2022-09-21 12:37:26.465495 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '49b3fa6a4173' +down_revision = '44e4ecea108b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('network', sa.Column('manager_id', sa.String(), nullable=False)) + op.create_foreign_key(None, 'network', 'user', ['manager_id'], ['id']) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'network', type_='foreignkey') + op.drop_column('network', 'manager_id') + # ### end Alembic commands ### diff --git a/migrations/versions/4acd1ace5eb6_.py b/migrations/versions/4acd1ace5eb6_.py new file mode 100644 index 0000000..da39eb2 --- /dev/null +++ b/migrations/versions/4acd1ace5eb6_.py @@ -0,0 +1,44 @@ +"""empty message + +Revision ID: 4acd1ace5eb6 +Revises: 946e687d2d42 +Create Date: 2022-09-21 10:20:39.896514 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '4acd1ace5eb6' +down_revision = '946e687d2d42' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('network', + sa.Column('id', sa.String(), nullable=False), + sa.Column('subnet', postgresql.CIDR(), nullable=False), + sa.Column('description', sa.String(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('peer', + sa.Column('id', sa.String(), nullable=False), + sa.Column('addr', postgresql.CIDR(), nullable=False), + sa.Column('public_key', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.drop_constraint('user_fs_uniquifier_key', 'user', type_='unique') + op.drop_column('user', 'fs_uniquifier') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('user', sa.Column('fs_uniquifier', sa.VARCHAR(), autoincrement=False, nullable=False)) + op.create_unique_constraint('user_fs_uniquifier_key', 'user', ['fs_uniquifier']) + op.drop_table('peer') + op.drop_table('network') + # ### end Alembic commands ### diff --git a/migrations/versions/81173c1d4f0a_initial_migration.py b/migrations/versions/81173c1d4f0a_initial_migration.py index f96ff21..4345556 100644 --- a/migrations/versions/81173c1d4f0a_initial_migration.py +++ b/migrations/versions/81173c1d4f0a_initial_migration.py @@ -37,54 +37,11 @@ def upgrade(): sa.UniqueConstraint('email'), sa.UniqueConstraint('fs_uniquifier') ) - op.drop_table('enroll_requests') - op.drop_table('_sqlx_migrations') - op.drop_table('networks') - op.drop_table('peers') - op.drop_table('users') - # ### end Alembic commands ### + # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('users', - sa.Column('id', sa.TEXT(), autoincrement=False, nullable=False), - sa.Column('email', sa.TEXT(), autoincrement=False, nullable=False), - sa.Column('pref_name', sa.TEXT(), autoincrement=False, nullable=False), - sa.Column('pw_hash', sa.TEXT(), autoincrement=False, nullable=False), - sa.Column('last_login', postgresql.TIMESTAMP(), autoincrement=False, nullable=True), - sa.PrimaryKeyConstraint('id', name='users_pkey'), - postgresql_ignore_search_path=False - ) - op.create_table('peers', - sa.Column('id', sa.TEXT(), autoincrement=False, nullable=False), - sa.Column('addr', postgresql.CIDR(), autoincrement=False, nullable=False), - sa.Column('public_key', sa.TEXT(), autoincrement=False, nullable=False), - sa.Column('owner', sa.TEXT(), autoincrement=False, nullable=False), - sa.ForeignKeyConstraint(['owner'], ['users.id'], name='peers_owner_fkey'), - sa.PrimaryKeyConstraint('id', name='peers_pkey') - ) - op.create_table('networks', - sa.Column('id', sa.TEXT(), autoincrement=False, nullable=False), - sa.Column('subnet', postgresql.CIDR(), autoincrement=False, nullable=False), - sa.Column('description', sa.TEXT(), autoincrement=False, nullable=True), - sa.PrimaryKeyConstraint('id', name='networks_pkey') - ) - op.create_table('_sqlx_migrations', - sa.Column('version', sa.BIGINT(), autoincrement=False, nullable=False), - sa.Column('description', sa.TEXT(), autoincrement=False, nullable=False), - sa.Column('installed_on', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=False), - sa.Column('success', sa.BOOLEAN(), autoincrement=False, nullable=False), - sa.Column('checksum', postgresql.BYTEA(), autoincrement=False, nullable=False), - sa.Column('execution_time', sa.BIGINT(), autoincrement=False, nullable=False), - sa.PrimaryKeyConstraint('version', name='_sqlx_migrations_pkey') - ) - op.create_table('enroll_requests', - sa.Column('token', sa.TEXT(), autoincrement=False, nullable=False), - sa.Column('expires', postgresql.TIMESTAMP(), server_default=sa.text("(CURRENT_TIMESTAMP + ((30)::double precision * '00:01:00'::interval))"), autoincrement=False, nullable=False), - sa.Column('confirmed', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False), - sa.PrimaryKeyConstraint('token', name='enroll_requests_pkey') - ) op.drop_table('user') op.drop_table('role') # ### end Alembic commands ### diff --git a/migrations/versions/900a50c566c4_add_description_to_peers.py b/migrations/versions/900a50c566c4_add_description_to_peers.py new file mode 100644 index 0000000..de751bf --- /dev/null +++ b/migrations/versions/900a50c566c4_add_description_to_peers.py @@ -0,0 +1,28 @@ +"""add description to peers + +Revision ID: 900a50c566c4 +Revises: 49b3fa6a4173 +Create Date: 2022-09-21 12:43:37.943280 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '900a50c566c4' +down_revision = '49b3fa6a4173' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('peer', sa.Column('description', sa.String(), nullable=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('peer', 'description') + # ### end Alembic commands ### diff --git a/migrations/versions/afd561b2a827_add_admin_field.py b/migrations/versions/afd561b2a827_add_admin_field.py new file mode 100644 index 0000000..da3552a --- /dev/null +++ b/migrations/versions/afd561b2a827_add_admin_field.py @@ -0,0 +1,28 @@ +"""add admin field + +Revision ID: afd561b2a827 +Revises: bc8f89f077d8 +Create Date: 2022-09-21 16:10:37.422259 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'afd561b2a827' +down_revision = 'bc8f89f077d8' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('user', sa.Column('is_admin', sa.Boolean(), nullable=False, server_default=str(False))) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('user', 'is_admin') + # ### end Alembic commands ### diff --git a/migrations/versions/bc8f89f077d8_uniquify_network_subnet.py b/migrations/versions/bc8f89f077d8_uniquify_network_subnet.py new file mode 100644 index 0000000..e216a79 --- /dev/null +++ b/migrations/versions/bc8f89f077d8_uniquify_network_subnet.py @@ -0,0 +1,28 @@ +"""uniquify network subnet + +Revision ID: bc8f89f077d8 +Revises: 900a50c566c4 +Create Date: 2022-09-21 16:01:43.914719 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'bc8f89f077d8' +down_revision = '900a50c566c4' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_unique_constraint(None, 'network', ['subnet']) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'network', type_='unique') + # ### end Alembic commands ### diff --git a/migrations/versions/e7963e59fb3c_.py b/migrations/versions/e7963e59fb3c_.py new file mode 100644 index 0000000..dbed101 --- /dev/null +++ b/migrations/versions/e7963e59fb3c_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: e7963e59fb3c +Revises: 4acd1ace5eb6 +Create Date: 2022-09-21 12:31:24.175307 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'e7963e59fb3c' +down_revision = '4acd1ace5eb6' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('peer', sa.Column('owner', sa.String(), nullable=False)) + op.create_foreign_key(None, 'peer', 'user', ['owner'], ['id']) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'peer', type_='foreignkey') + op.drop_column('peer', 'owner') + # ### end Alembic commands ### -- cgit v1.2.3