diff --git a/src/anonchat/models.py b/src/anonchat/models.py index 77aa6f7..cf2659a 100644 --- a/src/anonchat/models.py +++ b/src/anonchat/models.py @@ -6,15 +6,29 @@ from argon2 import PasswordHasher from . import password_hasher class Message(db.Model): - id = db.Column(db.Integer, primary_key=True) + inquiry_id = db.Column(db.String(16), db.ForeignKey('inquiry.id'), nullable=False, primary_key=True) + message_number = db.Column(db.Integer, primary_key=True) content = db.Column(db.Text, nullable=False) timestamp = db.Column(db.DateTime, default=db.func.current_timestamp()) - inquiry_id = db.Column(db.String(16), db.ForeignKey('inquiry.id'), nullable=False) is_admin = db.Column(db.Boolean, default=False) + + __table_args__ = (db.UniqueConstraint('inquiry_id', 'message_number'),) + + def __init__(self, **kwargs): + super(Message, self).__init__(**kwargs) + if self.message_number is None: + # Find the highest message number for this inquiry and increment + last_message = Message.query.filter_by( + inquiry_id=self.inquiry_id + ).order_by(Message.message_number.desc()).first() + + self.message_number = 1 if last_message is None else last_message.message_number + 1 class Inquiry(db.Model): - id = db.Column(db.String(16), primary_key=True, default=lambda: secrets.token_hex(8)) - messages = db.relationship('Message', backref='inquiry', lazy=True, cascade='all, delete-orphan') + id = db.Column(db.String(16), primary_key=True, unique=True, default=lambda: secrets.token_hex(8)) + messages = db.relationship('Message', backref='inquiry', lazy=True, + cascade='all, delete-orphan', + order_by='Message.message_number') class Settings(db.Model): id = db.Column(db.Integer, primary_key=True) diff --git a/src/anonchat/routes.py b/src/anonchat/routes.py index 1bd85a7..4b6f606 100644 --- a/src/anonchat/routes.py +++ b/src/anonchat/routes.py @@ -126,17 +126,14 @@ def inquiry(inquiry_id): 'is_admin': is_admin_message }) - if is_admin: - flash('Response sent successfully') return redirect(url_for('inquiry', inquiry_id=inquiry_id)) - messages = Message.query.filter_by(inquiry_id=inquiry_id).order_by(Message.timestamp).all() + messages = Message.query.filter_by(inquiry_id=inquiry_id).order_by(Message.message_number).all() return render_template('inquiry.html', inquiry=inquiry, messages=messages, is_admin=is_admin) @app.route('/admin', methods=['GET']) def admin_login(): - error = None - return render_template('admin_login.html', error=error) + return render_template('admin_login.html') @app.route('/admin', methods=['POST']) @limiter.limit("1 per minute") @@ -160,9 +157,9 @@ def admin_login_post(): return redirect(next_page) return redirect(url_for('admin_dashboard')) else: - error = 'Invalid credentials' + flash('Invalid username or password', 'error') - return render_template('admin_login.html', error=error) + return redirect(url_for('admin_login')) @app.route('/admin/logout') @@ -180,7 +177,7 @@ def admin_dashboard(): # For each inquiry, get the latest message timestamp inquiries_with_data = [] for inquiry in inquiries: - latest_message = Message.query.filter_by(inquiry_id=inquiry.id).order_by(Message.timestamp.desc()).first() + latest_message = Message.query.filter_by(inquiry_id=inquiry.id).order_by(Message.message_number.desc()).first() message_count = Message.query.filter_by(inquiry_id=inquiry.id).count() inquiries_with_data.append({ 'inquiry': inquiry, @@ -293,4 +290,36 @@ def handle_csrf_error(e): if request.is_json or request.headers.get('X-Requested-With') == 'XMLHttpRequest': return jsonify(error="Invalid request. CSRF token missing or incorrect."), 400 else: - return render_template('error.html', error="Invalid request. Please try again."), 400 \ No newline at end of file + return render_template('error.html', error="Invalid request. Please try again."), 400 + +@app.route('/api/inquiry//messages', methods=['GET']) +@limiter.limit("10 per hour", deduct_when=lambda response: response.status_code != 200) +@limiter.limit("30 per minute") +def get_inquiry_messages(inquiry_id): + inquiry = Inquiry.query.get_or_404(inquiry_id) + + # Get after_message_number from query parameters if provided + after_message_number = request.args.get('after_message_number', type=int) + + # Base query + query = Message.query.filter_by(inquiry_id=inquiry_id) + + # Add after_message_number filter if provided + if after_message_number is not None: + query = query.filter(Message.message_number > after_message_number) + + # Get messages ordered by message_number + messages = query.order_by(Message.message_number).all() + + # Convert messages to JSON format + messages_json = [{ + 'message_number': message.message_number, + 'content': message.content, + 'is_admin': message.is_admin, + 'timestamp': message.timestamp.isoformat() + } for message in messages] + + return jsonify({ + 'inquiry_id': inquiry_id, + 'messages': messages_json + }) \ No newline at end of file diff --git a/src/anonchat/templates/admin_dashboard.html b/src/anonchat/templates/admin_dashboard.html index 6301507..035ea2e 100644 --- a/src/anonchat/templates/admin_dashboard.html +++ b/src/anonchat/templates/admin_dashboard.html @@ -1,5 +1,5 @@ {% extends 'base.html' %} -{% from 'admin_header.html' import admin_header %} +{% from 'headers.html' import admin_header, flash_messages %} {% block title %}Admin Dashboard - {{ config.SITE_TITLE }}{% endblock %} @@ -7,7 +7,8 @@

Admin Dashboard

{{ admin_header() }} - + {{ flash_messages() }} +

All Inquiries

{% if inquiries_with_data %} diff --git a/src/anonchat/templates/admin_header.html b/src/anonchat/templates/admin_header.html deleted file mode 100644 index 04c7a83..0000000 --- a/src/anonchat/templates/admin_header.html +++ /dev/null @@ -1,22 +0,0 @@ -{% macro admin_header() %} -
- Admin Dashboard - Admin Settings - Webhook Settings - Logout -
- -{% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
- {{ message }} -
- {% endfor %} - {% endif %} -{% endwith %} -{% endmacro %} \ No newline at end of file diff --git a/src/anonchat/templates/admin_login.html b/src/anonchat/templates/admin_login.html index 4045e8f..dcdcdad 100644 --- a/src/anonchat/templates/admin_login.html +++ b/src/anonchat/templates/admin_login.html @@ -1,13 +1,11 @@ {% extends 'base.html' %} - +{% from 'headers.html' import flash_messages %} {% block title %}Admin Login - {{ config.SITE_TITLE }}{% endblock %} {% block content %}

Admin Login

- - {% if error %} -
{{ error }}
- {% endif %} + + {{ flash_messages() }}
diff --git a/src/anonchat/templates/admin_settings.html b/src/anonchat/templates/admin_settings.html index ed5c498..3e2bf4e 100644 --- a/src/anonchat/templates/admin_settings.html +++ b/src/anonchat/templates/admin_settings.html @@ -1,6 +1,6 @@ {% extends 'base.html' %} -{% from 'admin_header.html' import admin_header %} +{% from 'headers.html' import admin_header, flash_messages %} {% block title %}Admin Settings - {{ config.SITE_TITLE }}{% endblock %} @@ -8,6 +8,7 @@

Admin Settings

{{ admin_header() }} + {{ flash_messages() }}

Change Admin Password

diff --git a/src/anonchat/templates/admin_webhook.html b/src/anonchat/templates/admin_webhook.html index 44f4332..2f3d9b4 100644 --- a/src/anonchat/templates/admin_webhook.html +++ b/src/anonchat/templates/admin_webhook.html @@ -1,5 +1,5 @@ {% extends 'base.html' %} -{% from 'admin_header.html' import admin_header %} +{% from 'headers.html' import admin_header, flash_messages %} {% block title %}Webhook Settings - {{ config.SITE_TITLE }}{% endblock %} @@ -7,6 +7,7 @@

Webhook Settings

{{ admin_header() }} + {{ flash_messages() }}

Configure webhook settings to receive notifications for new inquiries and responses.

diff --git a/src/anonchat/templates/create_inquiry.html b/src/anonchat/templates/create_inquiry.html index e10d3e9..8f5a2b0 100644 --- a/src/anonchat/templates/create_inquiry.html +++ b/src/anonchat/templates/create_inquiry.html @@ -1,5 +1,7 @@ {% extends "base.html" %} +{% from 'headers.html' import flash_messages %} + {% block title %}Create New Inquiry - {{ config.SITE_TITLE }}{% endblock %} {% block content %} @@ -10,21 +12,7 @@ Note: This is early software still under development. Please do not abuse this system.
- {% with messages = get_flashed_messages() %} - {% if messages %} - {% for message in messages %} -
- {{ message }} -
- {% endfor %} - {% endif %} - {% endwith %} - - {% if error %} -
- {{ error }} -
- {% endif %} + {{ flash_messages() }} diff --git a/src/anonchat/templates/headers.html b/src/anonchat/templates/headers.html new file mode 100644 index 0000000..7ff3bcf --- /dev/null +++ b/src/anonchat/templates/headers.html @@ -0,0 +1,24 @@ +{% macro admin_header() %} +
+ Admin Dashboard + Admin Settings + Webhook Settings + Logout +
+{% endmacro %} + +{% macro flash_messages() %} + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endif %} + {% endwith %} +{% endmacro %} \ No newline at end of file diff --git a/src/anonchat/templates/inquiry.html b/src/anonchat/templates/inquiry.html index 9b341b5..23f7514 100644 --- a/src/anonchat/templates/inquiry.html +++ b/src/anonchat/templates/inquiry.html @@ -1,16 +1,18 @@ {% extends "base.html" %} -{% from 'admin_header.html' import admin_header %} +{% from 'headers.html' import admin_header, flash_messages %} {% block title %}{% if is_admin %}Admin View - {% endif %}Inquiry #{{ inquiry.id[:6] }} - {{ config.SITE_TITLE }}{% endblock %} {% block content %}
+

Inquiry #{{ inquiry.id[:6] }}

+ {% if is_admin %} {{ admin_header() }} {% endif %} -

Inquiry #{{ inquiry.id[:6] }}

+ {{ flash_messages() }} {% if not is_admin %}

This is your conversation link: {{ request.url }}

@@ -24,35 +26,23 @@
- {% with messages = get_flashed_messages() %} - {% if messages %} - {% for message in messages %} -
- {{ message }} -
- {% endfor %} - {% endif %} - {% endwith %} - -
+

Messages

- {% if messages %} - {% for message in messages %} -
-
- {% if message.is_admin %}ADMIN: {% endif %} - {{ message.content }} +
+ {% if messages %} + {% for message in messages %} +
+
+ {% if message.is_admin %}ADMIN: {% endif %} + {{ message.content }} +
+
{{ message.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}
-
{{ message.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}
-
- {% endfor %} - {% else %} -

No messages yet. Start the conversation!

- {% endif %} -
- -
-

Please refresh the page to see new messages.

+ {% endfor %} + {% else %} +

No messages yet. Start the conversation!

+ {% endif %} +
@@ -66,4 +56,68 @@
+ + {% endblock %} \ No newline at end of file