diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc index b8a910a..ae8638e 100644 Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ diff --git a/__pycache__/forms.cpython-313.pyc b/__pycache__/forms.cpython-313.pyc index 9df81d2..45b67bf 100644 Binary files a/__pycache__/forms.cpython-313.pyc and b/__pycache__/forms.cpython-313.pyc differ diff --git a/__pycache__/models.cpython-313.pyc b/__pycache__/models.cpython-313.pyc index 9dae027..3d24018 100644 Binary files a/__pycache__/models.cpython-313.pyc and b/__pycache__/models.cpython-313.pyc differ diff --git a/app.py b/app.py index 536c857..e9d58fa 100644 --- a/app.py +++ b/app.py @@ -77,7 +77,7 @@ def create_app(): is_admin=True, is_active=True ) - admin.set_password('q]H488h[8?.A') + admin.set_password('changeme') db.session.add(admin) db.session.commit() click.echo("Default administrator user created successfully.") diff --git a/migrations/versions/__pycache__/c770e08966b4_add_email_templates_table.cpython-313.pyc b/migrations/versions/__pycache__/c770e08966b4_add_email_templates_table.cpython-313.pyc new file mode 100644 index 0000000..228118f Binary files /dev/null and b/migrations/versions/__pycache__/c770e08966b4_add_email_templates_table.cpython-313.pyc differ diff --git a/migrations/versions/c770e08966b4_add_email_templates_table.py b/migrations/versions/c770e08966b4_add_email_templates_table.py new file mode 100644 index 0000000..e95224a --- /dev/null +++ b/migrations/versions/c770e08966b4_add_email_templates_table.py @@ -0,0 +1,70 @@ +"""add email templates table + +Revision ID: c770e08966b4 +Revises: e7e4ff171f7a +Create Date: 2025-06-01 20:09:08.019884 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'c770e08966b4' +down_revision = 'e7e4ff171f7a' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('email_templates', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('subject', sa.String(length=200), nullable=False), + sa.Column('body', sa.Text(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.Column('created_by', sa.Integer(), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=True), + sa.ForeignKeyConstraint(['created_by'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.drop_table('notification') + with op.batch_alter_table('events', schema=None) as batch_op: + batch_op.alter_column('details', + existing_type=postgresql.JSONB(astext_type=sa.Text()), + type_=sa.JSON(), + existing_nullable=True) + batch_op.drop_index(batch_op.f('idx_events_event_type')) + batch_op.drop_index(batch_op.f('idx_events_timestamp')) + batch_op.drop_index(batch_op.f('idx_events_user_id')) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('events', schema=None) as batch_op: + batch_op.create_index(batch_op.f('idx_events_user_id'), ['user_id'], unique=False) + batch_op.create_index(batch_op.f('idx_events_timestamp'), ['timestamp'], unique=False) + batch_op.create_index(batch_op.f('idx_events_event_type'), ['event_type'], unique=False) + batch_op.alter_column('details', + existing_type=sa.JSON(), + type_=postgresql.JSONB(astext_type=sa.Text()), + existing_nullable=True) + + op.create_table('notification', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column('title', sa.VARCHAR(length=255), autoincrement=False, nullable=False), + sa.Column('message', sa.TEXT(), autoincrement=False, nullable=False), + sa.Column('type', sa.VARCHAR(length=50), autoincrement=False, nullable=False), + sa.Column('is_read', sa.BOOLEAN(), autoincrement=False, nullable=True), + sa.Column('created_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True), + sa.Column('link', sa.VARCHAR(length=512), autoincrement=False, nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], name=op.f('notification_user_id_fkey')), + sa.PrimaryKeyConstraint('id', name=op.f('notification_pkey')) + ) + op.drop_table('email_templates') + # ### end Alembic commands ### diff --git a/models.py b/models.py index 6c890a9..7dbd606 100644 --- a/models.py +++ b/models.py @@ -295,4 +295,21 @@ class Notif(db.Model): sender = db.relationship('User', foreign_keys=[sender_id], backref='sent_notifications') def __repr__(self): - return f'' \ No newline at end of file + return f'' + +class EmailTemplate(db.Model): + __tablename__ = 'email_templates' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + subject = db.Column(db.String(200), nullable=False) + body = db.Column(db.Text, nullable=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + created_by = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + is_active = db.Column(db.Boolean, default=True) + + # Relationships + creator = db.relationship('User', backref='created_email_templates', foreign_keys=[created_by]) + + def __repr__(self): + return f'' \ No newline at end of file diff --git a/routes/email_templates.py b/routes/email_templates.py new file mode 100644 index 0000000..11f3753 --- /dev/null +++ b/routes/email_templates.py @@ -0,0 +1,97 @@ +from flask import Blueprint, jsonify, request +from flask_login import login_required, current_user +from models import EmailTemplate, db +from utils import admin_required + +email_templates = Blueprint('email_templates', __name__) + +@email_templates.route('/api/email-templates', methods=['GET']) +@login_required +def get_templates(): + templates = EmailTemplate.query.filter_by(is_active=True).all() + return jsonify([{ + 'id': template.id, + 'name': template.name, + 'subject': template.subject, + 'body': template.body, + 'created_at': template.created_at.isoformat(), + 'updated_at': template.updated_at.isoformat(), + 'created_by': template.created_by, + 'is_active': template.is_active + } for template in templates]) + +@email_templates.route('/api/email-templates/', methods=['GET']) +@login_required +def get_template(template_id): + template = EmailTemplate.query.get_or_404(template_id) + return jsonify({ + 'id': template.id, + 'name': template.name, + 'subject': template.subject, + 'body': template.body, + 'created_at': template.created_at.isoformat(), + 'updated_at': template.updated_at.isoformat(), + 'created_by': template.created_by, + 'is_active': template.is_active + }) + +@email_templates.route('/api/email-templates', methods=['POST']) +@login_required +@admin_required +def create_template(): + data = request.get_json() + + template = EmailTemplate( + name=data['name'], + subject=data['subject'], + body=data['body'], + created_by=current_user.id + ) + + db.session.add(template) + db.session.commit() + + return jsonify({ + 'id': template.id, + 'name': template.name, + 'subject': template.subject, + 'body': template.body, + 'created_at': template.created_at.isoformat(), + 'updated_at': template.updated_at.isoformat(), + 'created_by': template.created_by, + 'is_active': template.is_active + }), 201 + +@email_templates.route('/api/email-templates/', methods=['PUT']) +@login_required +@admin_required +def update_template(template_id): + template = EmailTemplate.query.get_or_404(template_id) + data = request.get_json() + + template.name = data.get('name', template.name) + template.subject = data.get('subject', template.subject) + template.body = data.get('body', template.body) + template.is_active = data.get('is_active', template.is_active) + + db.session.commit() + + return jsonify({ + 'id': template.id, + 'name': template.name, + 'subject': template.subject, + 'body': template.body, + 'created_at': template.created_at.isoformat(), + 'updated_at': template.updated_at.isoformat(), + 'created_by': template.created_by, + 'is_active': template.is_active + }) + +@email_templates.route('/api/email-templates/', methods=['DELETE']) +@login_required +@admin_required +def delete_template(template_id): + template = EmailTemplate.query.get_or_404(template_id) + template.is_active = False + db.session.commit() + return '', 204 \ No newline at end of file