diff --git a/app/auth/forms.py b/app/auth/forms.py index d8ca5770..a6ce0017 100644 --- a/app/auth/forms.py +++ b/app/auth/forms.py @@ -43,6 +43,10 @@ class RegistrationForm(FlaskForm): EqualTo('password', message='Passwords must match') ] ) + terms_of_use_accepted = BooleanField( + 'I have read and accept the terms of use', + validators=[InputRequired()] + ) submit = SubmitField() def __init__(self, *args, **kwargs): diff --git a/app/auth/routes.py b/app/auth/routes.py index dc6140f2..54337d69 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -40,7 +40,8 @@ def register(): user = User.create( email=form.email.data.lower(), password=form.password.data, - username=form.username.data + username=form.username.data, + terms_of_use_accepted=form.terms_of_use_accepted.data ) except OSError: flash('Internal Server Error', category='error') diff --git a/app/models.py b/app/models.py index 07d648ad..34173d0f 100644 --- a/app/models.py +++ b/app/models.py @@ -522,6 +522,7 @@ class User(HashidMixin, UserMixin, db.Model): username_pattern = re.compile(r'^[A-Za-zÄÖÜäöüß0-9_.]*$') password_hash = db.Column(db.String(128)) confirmed = db.Column(db.Boolean, default=False) + terms_of_use_accepted = db.Column(db.Boolean, default=False) member_since = db.Column(db.DateTime(), default=datetime.utcnow) setting_job_status_mail_notification_level = db.Column( IntEnumColumn(UserSettingJobStatusMailNotificationLevel), diff --git a/app/static/js/Requests/users/settings.js b/app/static/js/Requests/users/settings.js index 0a92a09c..7584e9bb 100644 --- a/app/static/js/Requests/users/settings.js +++ b/app/static/js/Requests/users/settings.js @@ -13,4 +13,12 @@ Requests.users.entity.settings.profilePrivacy.update = (userId, profilePrivacySe body: JSON.stringify(enabled) }; return Requests.JSONfetch(input, init); -} +}; + +Requests.users.entity.settings.acceptTermsOfUse = () => { + let input = `/users/accept-terms-of-use`; + let init = { + method: 'POST' + }; + return Requests.JSONfetch(input, init); +}; diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2 index 9dd81933..5749d86b 100644 --- a/app/templates/_scripts.html.j2 +++ b/app/templates/_scripts.html.j2 @@ -107,4 +107,22 @@ for (let [category, message] of {{ get_flashed_messages(with_categories=True)|tojson }}) { app.flash(message, message); } + + // Initialize terms of use modal + const termsOfUseModal = document.getElementById('terms-of-use-modal'); + M.Modal.init( + termsOfUseModal, + { + dismissible: false, + onCloseEnd: () => { + Requests.users.entity.settings.acceptTermsOfUse(); + } + } + ); + {% if current_user.is_authenticated %} + {% if not current_user.terms_of_use_accepted %} + termsOfUseModal.M_Modal.open(); + {% endif %} + {% endif %} + diff --git a/app/templates/_terms_of_use_modal.html.j2 b/app/templates/_terms_of_use_modal.html.j2 new file mode 100644 index 00000000..ddd8985b --- /dev/null +++ b/app/templates/_terms_of_use_modal.html.j2 @@ -0,0 +1,115 @@ + diff --git a/app/templates/auth/register.html.j2 b/app/templates/auth/register.html.j2 index d8e023a7..adfbc0af 100644 --- a/app/templates/auth/register.html.j2 +++ b/app/templates/auth/register.html.j2 @@ -23,6 +23,10 @@ {{ wtf.render_field(form.password, material_icon='vpn_key') }} {{ wtf.render_field(form.password_2, material_icon='vpn_key') }} {{ wtf.render_field(form.email, class_='validate', material_icon='email', type='email') }} +
+ {{ wtf.render_field(form.terms_of_use_accepted, type='checkbox')}} +

+
{{ wtf.render_field(form.submit, class_='width-100', material_icon='send') }} diff --git a/app/templates/base.html.j2 b/app/templates/base.html.j2 index 82132828..22ba16e1 100644 --- a/app/templates/base.html.j2 +++ b/app/templates/base.html.j2 @@ -38,6 +38,7 @@ {# {% if current_user.is_authenticated %} {% include "_roadmap.html.j2" %} {% endif %} #} + {% include "_terms_of_use_modal.html.j2" %} {% endblock modals %} {% endblock main %} diff --git a/app/users/json_routes.py b/app/users/json_routes.py index b9cbb3e3..0e1503d6 100644 --- a/app/users/json_routes.py +++ b/app/users/json_routes.py @@ -55,3 +55,15 @@ def delete_user_avatar(user_id): 'message': f'Avatar marked for deletion' } return response_data, 202 + +@bp.route('/accept-terms-of-use', methods=['POST']) +@content_negotiation(produces='application/json') +def accept_terms_of_use(): + if not (current_user.is_authenticated or current_user.confirmed): + abort(403) + current_user.terms_of_use_accepted = True + db.session.commit() + response_data = { + 'message': 'You accepted the terms of use', + } + return response_data, 202 diff --git a/migrations/versions/b15b639288d4_.py b/migrations/versions/b15b639288d4_.py new file mode 100644 index 00000000..32e4d611 --- /dev/null +++ b/migrations/versions/b15b639288d4_.py @@ -0,0 +1,32 @@ +"""Add terms_of_use_accepted column to users table + +Revision ID: b15b639288d4 +Revises: 1f77ce4346c6 +Create Date: 2023-04-13 10:19:38.662996 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b15b639288d4' +down_revision = '1f77ce4346c6' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('users', schema=None) as batch_op: + batch_op.add_column(sa.Column('terms_of_use_accepted', sa.Boolean(), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('users', schema=None) as batch_op: + batch_op.drop_column('terms_of_use_accepted') + + # ### end Alembic commands ###