From 5a57e3bc44ed7f73ac4d8a601a08d5168ba263dd Mon Sep 17 00:00:00 2001 From: Son NK Date: Wed, 8 Jan 2020 21:23:41 +0100 Subject: [PATCH 1/7] Add Directory model and add directory_id column to GenEmail --- app/models.py | 18 ++++++++ .../versions/2020_010821_ba6f13ccbabb_.py | 42 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 migrations/versions/2020_010821_ba6f13ccbabb_.py diff --git a/app/models.py b/app/models.py index 5557eee6..11c88897 100644 --- a/app/models.py +++ b/app/models.py @@ -439,6 +439,11 @@ class GenEmail(db.Model, ModelMixin): db.Boolean, nullable=False, default=False, server_default="0" ) + # to know whether an alias belongs to a directory + directory_id = db.Column( + db.ForeignKey("directory.id", ondelete="cascade"), nullable=True + ) + user = db.relationship(User) @classmethod @@ -725,3 +730,16 @@ class CustomDomain(db.Model, ModelMixin): class LifetimeCoupon(db.Model, ModelMixin): code = db.Column(db.String(128), nullable=False, unique=True) nb_used = db.Column(db.Integer, nullable=False) + + +class Directory(db.Model, ModelMixin): + user_id = db.Column(db.ForeignKey(User.id, ondelete="cascade"), nullable=False) + name = db.Column(db.String(128), unique=True, nullable=False) + + user = db.relationship(User) + + def nb_alias(self): + return GenEmail.filter_by(directory_id=self.id).count() + + def __repr__(self): + return f"" diff --git a/migrations/versions/2020_010821_ba6f13ccbabb_.py b/migrations/versions/2020_010821_ba6f13ccbabb_.py new file mode 100644 index 00000000..55b419a2 --- /dev/null +++ b/migrations/versions/2020_010821_ba6f13ccbabb_.py @@ -0,0 +1,42 @@ +"""empty message + +Revision ID: ba6f13ccbabb +Revises: d29cca963221 +Create Date: 2020-01-08 21:23:06.288453 + +""" +import sqlalchemy_utils +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ba6f13ccbabb' +down_revision = 'd29cca963221' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('directory', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('created_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=False), + sa.Column('updated_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=128), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='cascade'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.add_column('gen_email', sa.Column('directory_id', sa.Integer(), nullable=True)) + op.create_foreign_key(None, 'gen_email', 'directory', ['directory_id'], ['id'], ondelete='cascade') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'gen_email', type_='foreignkey') + op.drop_column('gen_email', 'directory_id') + op.drop_table('directory') + # ### end Alembic commands ### From cdae3c5309945a92de757951e4a6b5f723c59d0e Mon Sep 17 00:00:00 2001 From: Son NK Date: Wed, 8 Jan 2020 21:38:01 +0100 Subject: [PATCH 2/7] add Directory page to add/delete directory --- app/dashboard/__init__.py | 1 + .../templates/dashboard/directory.html | 94 +++++++++++++++++++ app/dashboard/views/directory.py | 76 +++++++++++++++ templates/menu.html | 8 ++ 4 files changed, 179 insertions(+) create mode 100644 app/dashboard/templates/dashboard/directory.html create mode 100644 app/dashboard/views/directory.py diff --git a/app/dashboard/__init__.py b/app/dashboard/__init__.py index 4fa49b9a..4cc25e80 100644 --- a/app/dashboard/__init__.py +++ b/app/dashboard/__init__.py @@ -13,4 +13,5 @@ from .views import ( mfa_cancel, domain_detail, lifetime_licence, + directory, ) diff --git a/app/dashboard/templates/dashboard/directory.html b/app/dashboard/templates/dashboard/directory.html new file mode 100644 index 00000000..71a44257 --- /dev/null +++ b/app/dashboard/templates/dashboard/directory.html @@ -0,0 +1,94 @@ +{% extends 'default.html' %} +{% set active_page = "directory" %} + +{% block title %} + Directory +{% endblock %} + +{% block default_content %} +
+
+

Directories

+ + + + {% for dir in dirs %} +
+
+
+ {{ dir.name }} +
+
+ Created {{ dir.created_at | dt }}
+ {{ dir.nb_alias() }} aliases. +
+
+ + + +
+ {% endfor %} + + {% if dirs|length > 0 %} +
+ {% endif %} + +
+ {{ new_dir_form.csrf_token }} + + +
Directory Name
+
+ Directory name must be at least 3 characters. + Only letter, number, dash (-), underscore (_) can be used. +
+ + {{ new_dir_form.name(class="form-control", placeholder="my-directory", pattern="[0-9A-Za-z-_]{3,}", + title="Only letter, number, dash (-), underscore (_) can be used. Directory name must be at least 3 characters.", + autofocus="1") }} + {{ render_field_errors(new_dir_form.name) }} + +
+ +
+ +
+{% endblock %} + +{% block script %} + +{% endblock %} \ No newline at end of file diff --git a/app/dashboard/views/directory.py b/app/dashboard/views/directory.py new file mode 100644 index 00000000..f0b8f912 --- /dev/null +++ b/app/dashboard/views/directory.py @@ -0,0 +1,76 @@ +from flask import render_template, request, redirect, url_for, flash +from flask_login import login_required, current_user +from flask_wtf import FlaskForm +from wtforms import StringField, validators + +from app.config import EMAIL_DOMAIN +from app.dashboard.base import dashboard_bp +from app.extensions import db +from app.models import Directory + + +class NewDirForm(FlaskForm): + name = StringField( + "name", validators=[validators.DataRequired(), validators.Length(min=3)] + ) + + +@dashboard_bp.route("/directory", methods=["GET", "POST"]) +@login_required +def directory(): + # only premium user can add directory + if not current_user.is_premium(): + flash("Only premium user can add directories", "warning") + return redirect(url_for("dashboard.index")) + + dirs = Directory.query.filter_by(user_id=current_user.id).all() + + new_dir_form = NewDirForm() + + if request.method == "POST": + if request.form.get("form-name") == "delete": + dir_id = request.form.get("dir-id") + dir = Directory.get(dir_id) + + if not dir: + flash("Unknown error. Refresh the page", "warning") + return redirect(url_for("dashboard.directory")) + elif dir.user_id != current_user.id: + flash("You cannot delete this directory", "warning") + return redirect(url_for("dashboard.directory")) + + name = dir.name + Directory.delete(dir_id) + db.session.commit() + flash(f"Directory {name} has been deleted", "success") + + return redirect(url_for("dashboard.directory")) + + elif request.form.get("form-name") == "create": + if new_dir_form.validate(): + new_dir_name = new_dir_form.name.data + + if Directory.get_by(name=new_dir_name): + flash(f"{new_dir_name} already added", "warning") + # ra+ and reply+ are reserved for reply-email and reverse-alias + elif new_dir_name in ("ra", "reply"): + flash( + f"{new_dir_name} cannot be *ra* and *reply*, please use another name!", + "warning", + ) + else: + new_dir = Directory.create( + name=new_dir_name, user_id=current_user.id + ) + db.session.commit() + + flash(f"Directory {new_dir.name} is created", "success") + + return redirect(url_for("dashboard.directory",)) + + return render_template( + "dashboard/directory.html", + dirs=dirs, + new_dir_form=new_dir_form, + EMAIL_DOMAIN=EMAIL_DOMAIN, + ) diff --git a/templates/menu.html b/templates/menu.html index d89abeca..e93e73e4 100644 --- a/templates/menu.html +++ b/templates/menu.html @@ -30,6 +30,14 @@ + +