Projects implementation

* Separated routes into single files for better management
* Added sql for streamlined example data
* Streamlined function names, streamlined template file names
- Removed 2 functions with unnecessary code
* Added javascript for project overview
* New library: tippy.js for tooltips
* Added new configuration options for projects
* Added cookie secure setting to flask
projects
Domeniko Gentner 4 years ago
parent edd9ce7585
commit fdec8f74c8
  1. 6
      .gitignore
  2. 1
      __implementation_example/data/blog/article-3.json
  3. 23
      __implementation_example/data/blog/stramine.json
  4. 108
      db/example-data.sql
  5. 17
      labertasche.yaml
  6. 2
      labertasche/blueprints/__init__.py
  7. 7
      labertasche/blueprints/bp_comments/__init__.py
  8. 240
      labertasche/blueprints/bp_dashboard.py
  9. 18
      labertasche/blueprints/bp_dashboard/__init__.py
  10. 51
      labertasche/blueprints/bp_dashboard/comments.py
  11. 32
      labertasche/blueprints/bp_dashboard/mail.py
  12. 87
      labertasche/blueprints/bp_dashboard/projects.py
  13. 56
      labertasche/blueprints/bp_dashboard/spam.py
  14. 17
      labertasche/blueprints/bp_jsconnector/__init__.py
  15. 109
      labertasche/blueprints/bp_jsconnector/comments.py
  16. 36
      labertasche/blueprints/bp_jsconnector/mail.py
  17. 91
      labertasche/blueprints/bp_jsconnector/projects.py
  18. 71
      labertasche/blueprints/bp_jsconnector/spam.py
  19. 4
      labertasche/blueprints/bp_login/__init__.py
  20. 4
      labertasche/helper/__init__.py
  21. 1
      labertasche/settings/__init__.py
  22. 7
      server.py
  23. 25
      static/js/dashboard.js
  24. 24
      templates/base.html
  25. 11
      templates/login.html
  26. 16
      templates/manage-comments.html
  27. 4
      templates/manage-mail.html
  28. 58
      templates/project-list.html
  29. 0
      templates/project-stats.html
  30. 78
      templates/review-spam.html

6
.gitignore vendored

@ -1,9 +1,9 @@
.idea .idea
__pycache__/ __pycache__/
venv venv
db/labertasche.db-shm db/*.db
db/labertasche.db-wal db/*.db-shm
db/*.db-wal
output output
/output/ /output/
*.sql
*.old *.old

@ -0,0 +1 @@
{"comments": [{"comment_id": 9, "email": "commenter9@", "content": "9 This is a test comment and has no actual value. Please test all methods on this.", "created_on": "2020-12-16 23:37:00", "replied_to": null, "gravatar": "d9eef4df0ae5bfc1a9a9b1e39a99c07f"}], "replies": [{"comment_id": 10, "email": "commenter10@", "content": "10 This is a reply to the previous comment and has no actual value. Please test all methods on this.", "created_on": "2020-12-16 23:37:00", "replied_to": 9, "gravatar": "d9eef4df0ae5bfc1a9a9b1e39a99c07f"}]}

@ -1,22 +1 @@
{ {"comments": [], "replies": [{"comment_id": 2, "email": "commenter2@", "content": "2 This is a reply to the previous comment and has no actual value. Please test all methods on this.", "created_on": "2020-12-16 23:37:00", "replied_to": 1, "gravatar": "d9eef4df0ae5bfc1a9a9b1e39a99c07f"}]}
"comments": [
{
"comment_id": 1,
"email": "commenter1@",
"content": "This is an example comment with over 40 characters.",
"created_on": "2020-12-04 12:23:14",
"replied_to": null,
"gravatar": "d9eef4df0ae5bfc1a9a9b1e39a99c07f"
}
],
"replies": [
{
"comment_id": 2,
"email": "commenter2@",
"content": "This is an example reply, to test if this works.",
"created_on": "2020-12-04 12:24:19",
"replied_to": 1,
"gravatar": "d9eef4df0ae5bfc1a9a9b1e39a99c07f"
}
]
}

@ -0,0 +1,108 @@
/**********************************************************************************
* _author : Domeniko Gentner
* _mail : code@tuxstash.de
* _repo : https://git.tuxstash.de/gothseidank/labertasche
* _license : This project is under MIT License
**********************************************************************************
*
* This script generates sample data for the example implementation.
* Feed it into the automatically created database with either DBBeaver
* or the sqlite command line tool.
*
* Please note: Labertasche must have run once to create the database!
*
**********************************************************************************
*/
/* delete old data */
DELETE FROM t_comments;
DELETE FROM t_projects;
DELETE FROM t_email;
DELETE FROM t_comments;
DELETE FROM t_location;
/* Create example projects */
INSERT INTO t_projects (id_project, name)
VALUES
(1, 'default'),
(2, 'example.com'),
(3, 'tuxstash.de'),
(4, 'beispiel.de'),
(5, 'labertasche.tuxstash.de')
;
/* Create existing locations for each project */
INSERT INTO t_location (id_location, location, project_id)
VALUES
(1, '/blog/stramine/', 1),
(2, '/blog/readme/', 1),
(3, '/blog/article-1/', 2),
(4, '/blog/article-2/', 2),
(5, '/blog/article-3/', 3),
(6, '/blog/article-4/', 3),
(7, '/blog/article-5/', 4),
(8, '/blog/article-6/', 4),
(9, '/blog/article-7/', 5),
(10, '/blog/article-8/', 5)
;
/* Create some emails that are blocked and allowed */
INSERT INTO t_email (id_email, email, is_allowed, is_blocked, project_id)
VALUES
(1, "commenter1@example.com", true, false, 1),
(2, "commenter2@example.com", false, true, 1),
(3, "commenter3@example.com", true, false, 2),
(4, "commenter4@example.com", false, true, 2),
(5, "commenter5@example.com", true, false, 3),
(6, "commenter6@example.com", false, true, 3),
(7, "commenter7@example.com", true, false, 4),
(8, "commenter8@example.com", false, true, 4),
(9, "commenter9@example.com", true, false, 5),
(10, "commenter10@example.com", false, true, 5)
;
/* Create some comments */
INSERT INTO t_comments (comments_id, location_id, email, content, created_on, is_published, is_spam, spam_score, replied_to, confirmation, deletion, gravatar, project_id)
VALUES
(1, 1, 'commenter1@example.com', '1 This is a test comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, NULL, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 1),
(2, 1, 'commenter2@example.com', '2 This is a reply to the previous comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, 1, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 1),
(3, 2, 'commenter3@example.com', '3 This is a test comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, NULL, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 1),
(4, 2, 'commenter4@example.com', '4 This is a reply to the previous comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, 3, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 1),
(5, 3, 'commenter5@example.com', '5 This is a test comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, NULL, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 2),
(6, 3, 'commenter6@example.com', '6 This is a reply to the previous comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, 5, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 2),
(7, 4, 'commenter7@example.com', '7 This is a test comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, NULL, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 2),
(8, 4, 'commenter8@example.com', '8 This is a reply to the previous comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, 7, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 2),
(9, 5, 'commenter9@example.com', '9 This is a test comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', false, true, 0.09, NULL, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 3),
(10, 5, 'commenter10@example.com', '10 This is a reply to the previous comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, 9, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 3),
(11, 6, 'commenter11@example.com', '11 This is a test comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.09, NULL, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 3),
(12, 6, 'commenter12@example.com', '12 This is a reply to the previous comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, 11, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 3),
(13, 7, 'commenter13@example.com', '13 This is a test comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, NULL, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 4),
(14, 7, 'commenter14@example.com', '14 This is a reply to the previous comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, 13, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 4),
(15, 8, 'commenter15@example.com', '15 This is a test comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, NULL, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 4),
(16, 8, 'commenter16@example.com', '16 This is a reply to the previous comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, 16, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 4),
(17, 9, 'commenter17@example.com', '17 This is a test comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, NULL, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 5),
(18, 9, 'commenter18@example.com', '18 This is a reply to the previous comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, 18, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 5),
(19, 10, 'commenter19@example.com', '19 This is a test comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, NULL, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 5),
(20, 10, 'commenter20@example.com', '20 This is a reply to the previous comment and has no actual value. Please test all methods on this.', '2020-12-16 23:37:00.000000', true, false, 0.99, 19, NULL, NULL, 'd9eef4df0ae5bfc1a9a9b1e39a99c07f', 5)
;

@ -8,19 +8,32 @@
system: system:
web_url: "http://dev.localhost:1314/" # Url where the comment system is served web_url: "http://dev.localhost:1314/" # Url where the comment system is served
blog_url: "http://dev.localhost:1313/" # Url of your website blog_url: "http://dev.localhost:1313/" # Url of your website
cookie-domain: "dev.localhost" # Url where the comment system is served cookie_domain: "dev.localhost" # hostname where the comment system is served
database_uri: "sqlite:///db/labertasche.db" # Database URI. See documentation. Default is sqlite. database_uri: "sqlite:///db/labertasche.db" # Database URI. See documentation. Default is sqlite.
secret: "6Gxvb52bIJCm2vfDsmWKzShKp1omrzVG" # CHANGE ME! THIS IS IMPORTANT! secret: "6Gxvb52bIJCm2vfDsmWKzShKp1omrzVG" # CHANGE ME! THIS IS IMPORTANT!
output: "./__implementation_example/data/" # Base path for the output json output: "./__implementation_example/data/" # Base path for the output json
debug: false # Leave this as is, this is for development. debug: false # Leave this as is, this is for development.
send_otp_to_publish: true # Disables confirmation w/ OTP via mail send_otp_to_publish: true # Disables confirmation w/ OTP via mail
cookie_secure: false
projects:
- default:
web_url: "http://dev.localhost:1314/"
blog_url: "http://dev.localhost:1313/"
output: "./__implementation_example/data/"
send_otp_to_publish: true
- example.com:
web_url: "http://comments.example.com/"
blog_url: "http://blog.example.com/"
output: "./example/data/"
send_otp_to_publish: true
gravatar: gravatar:
cache: true # Enable caching of gravatar images cache: true # Enable caching of gravatar images
static_dir: "./__implementation_example/static/images/gravatar/" # Where to store cached images, must exist! static_dir: "./__implementation_example/static/images/gravatar/" # Where to store cached images, must exist!
size: 256 # only applies if images are cached, size: 256 # only applies if images are cached,
# otherwise use ?s=size at the end of the gravatar url # otherwise use ?s=size at the end of the gravatar url
dashboard: dashboard:
username: "admin" # CHANGE ME! username: "admin" # CHANGE ME!
password: "admin" # CHANGE ME! password: "admin" # CHANGE ME!

@ -9,4 +9,4 @@
from .bp_comments import bp_comments from .bp_comments import bp_comments
from .bp_login import bp_login from .bp_login import bp_login
from .bp_dashboard import bp_dashboard from .bp_dashboard import bp_dashboard
from .bp_jsconnector import bp_jsconnector

@ -131,6 +131,7 @@ def check_and_insert_new_comment():
new_comment.pop("location") new_comment.pop("location")
# insert comment # insert comment
# noinspection PyBroadException
try: try:
new_comment.update({"is_published": False}) new_comment.update({"is_published": False})
new_comment.update({"created_on": default_timestamp()}) new_comment.update({"created_on": default_timestamp()})
@ -154,11 +155,7 @@ def check_and_insert_new_comment():
print(f"Duplicate from {request.environ['REMOTE_ADDR']}, error is:\n{e}", file=stderr) print(f"Duplicate from {request.environ['REMOTE_ADDR']}, error is:\n{e}", file=stderr)
return make_response(jsonify(status="post-duplicate"), 400) return make_response(jsonify(status="post-duplicate"), 400)
except Exception as e: # must be at bottom except Exception: # must be at bottom
# mail(f"check_and_insert_new_comment has thrown an error: {e}", )
print("---------------------------------------------")
print(e, file=stderr)
print("---------------------------------------------")
return make_response(jsonify(status="post-internal-server-error"), 400) return make_response(jsonify(status="post-internal-server-error"), 400)
export_location(t_comment.location_id) export_location(t_comment.location_id)

@ -1,240 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from flask import Blueprint, render_template, request, redirect, make_response, jsonify
from flask_login import login_required
from labertasche.database import labertasche_db as db
from labertasche.models import TLocation, TComments, TEmail, TProjects
from labertasche.helper import dates_of_the_week, export_location, get_id_from_project_name
from sqlalchemy import func
import re
# Blueprint
bp_dashboard = Blueprint("bp_dashboard", __name__, url_prefix='/dashboard')
@bp_dashboard.route("/")
@login_required
def dashboard_index():
t_projects = db.session.query(TProjects).all()
projects = list()
for each in t_projects:
comments = db.session.query(TComments).filter(TComments.project_id == each.id_project) \
.filter(TComments.is_published == True) \
.filter(TComments.is_spam == False).count()
unpub_comments = db.session.query(TComments).filter(TComments.project_id == each.id_project) \
.filter(TComments.is_spam == False) \
.filter(TComments.is_published == False).count()
spam = db.session.query(TComments).filter(TComments.project_id == each.id_project) \
.filter(TComments.is_spam == True).count()
projects.append(dict({
"id_project": each.id_project,
"name": each.name,
"total_comments": comments,
"total_spam": spam,
"total_unpublished": unpub_comments
}))
return render_template('project_overview.html', projects=projects)
@bp_dashboard.route("/project/new", methods=['POST'])
@login_required
def dashboard_new_project():
return make_response(jsonify(status='too-short'), 200)
@bp_dashboard.route('/<project>/')
@login_required
def dashboard_project_index(project: str):
proj_id = get_id_from_project_name(project)
dates = dates_of_the_week()
spam = list()
published = list()
unpublished = list()
for each in dates:
spam_comments = db.session.query(TComments).filter(TComments.project_id == proj_id)\
.filter(func.DATE(TComments.created_on) == each.date())\
.filter(TComments.is_spam == True).all()
pub_comments = db.session.query(TComments).filter(func.DATE(TComments.created_on) == each.date()) \
.filter(TComments.is_spam == False)\
.filter(TComments.is_published == True).all()
unpub_comments = db.session.query(TComments).filter(func.DATE(TComments.created_on) == each.date()) \
.filter(TComments.is_spam == False)\
.filter(TComments.is_published == False).all()
published.append(len(pub_comments))
spam.append(len(spam_comments))
unpublished.append(len(unpub_comments))
return render_template('dashboard.html', dates=dates, spam=spam, published=published, unpublished=unpublished)
@bp_dashboard.route('/review-spam/', methods=["POST", "GET"])
@bp_dashboard.route('/review-spam/<int:location_id>', methods=["POST", "GET"])
@login_required
def dashboard_review_spam(location_id=None):
all_locations = db.session.query(TLocation).all()
# Check post
if request.method == "POST":
location_id = request.form.get('selected_location')
# no parameters found
if location_id is None:
return render_template("review-spam.html", locations=all_locations, selected=location_id)
try:
if int(location_id) >= 1:
spam_comments = db.session.query(TComments).filter(TComments.location_id == location_id)\
.filter(TComments.is_spam == True)
return render_template("review-spam.html", locations=all_locations, selected=location_id,
spam_comments=spam_comments)
except ValueError:
pass
export_location(location_id)
return render_template("review-spam.html", locations=all_locations, selected=location_id)
@bp_dashboard.route('/manage-comments/', methods=["POST", "GET"])
@bp_dashboard.route('/manage-comments/<int:location_id>', methods=["POST", "GET"])
@login_required
def dashboard_manage_regular_comments(location_id=None):
all_locations = db.session.query(TLocation).all()
# Check post
if request.method == "POST":
location_id = request.form.get('selected_location')
# no parameters found
if location_id is None:
return render_template("manage-comments.html", locations=all_locations, selected=location_id)
try:
if int(location_id) >= 1:
spam_comments = db.session.query(TComments).filter(TComments.location_id == location_id) \
.filter(TComments.is_spam == False)
return render_template("manage-comments.html", locations=all_locations, selected=location_id,
spam_comments=spam_comments)
except ValueError:
pass
export_location(location_id)
return render_template("manage-comments.html", locations=all_locations, selected=location_id)
@bp_dashboard.route('/manage-mail/')
@login_required
def dashboard_allow_email():
addresses = db.session.query(TEmail).all()
return render_template("manage_mail_addresses.html", addresses=addresses)
@bp_dashboard.route('/toggle-mail-allowed/<int:id_email>')
@login_required
def dashboard_allow_email_toggle(id_email):
address = db.session.query(TEmail).filter(TEmail.id_email == id_email).first()
if address:
setattr(address, "is_allowed", (not address.is_allowed))
setattr(address, "is_blocked", (not address.is_blocked))
db.session.commit()
return redirect(request.referrer)
@bp_dashboard.route('/reset-mail-reputation/<int:id_email>')
@login_required
def dashboard_reset_mail_reputation(id_email):
db.session.query(TEmail).filter(TEmail.id_email == id_email).delete()
db.session.commit()
return redirect(request.referrer)
@bp_dashboard.route('/delete-comment/<int:location_id>/<int:comment_id>', methods=['GET'])
@login_required
def dashboard_review_spam_delete_comment(location_id, comment_id):
comment = db.session.query(TComments).filter(TComments.comments_id == comment_id).first()
db.session.delete(comment)
db.session.commit()
# Remove after last slash, to keep the location but get rid of the comment id
url = re.match("^(.*[/])", request.referrer)[0]
export_location(location_id)
return redirect(f"{url}/{location_id}")
@bp_dashboard.route('/allow-comment/<int:location_id>/<int:comment_id>', methods=['GET'])
@login_required
def dashboard_review_spam_allow_comment(comment_id, location_id):
comment = db.session.query(TComments).filter(TComments.comments_id == comment_id).first()
if comment:
setattr(comment, 'is_published', True)
setattr(comment, 'is_spam', False)
db.session.commit()
url = re.match("^(.*[/])", request.referrer)[0]
export_location(location_id)
return redirect(f"{url}/{location_id}")
@bp_dashboard.route('/block-mail/<int:location_id>/<int:comment_id>', methods=["GET"])
@login_required
def dashboard_review_spam_block_mail(location_id, comment_id):
comment = db.session.query(TComments).filter(TComments.comments_id == comment_id).first()
if comment:
mail = db.session.query(TEmail).filter(TEmail.email == comment.email).first()
if mail:
setattr(mail, 'is_allowed', False)
setattr(mail, 'is_blocked', True)
else:
new_mail = {
"email": comment.first().email,
"is_allowed": False,
"is_blocked": True
}
db.session.add(TEmail(**new_mail))
# Delete all comments made by this mail address
db.session.query(TComments).filter(TComments.email == comment.email).delete()
db.session.commit()
url = re.match("^(.*[/])", request.referrer)[0]
export_location(location_id)
return redirect(f"{url}/{location_id}")
@bp_dashboard.route('/allow-user/<int:location_id>/<int:comment_id>', methods=["GET"])
@login_required
def dashboard_review_spam_allow_user(location_id, comment_id):
comment = db.session.query(TComments).filter(TComments.comments_id == comment_id).first()
if comment:
mail = db.session.query(TEmail).filter(TEmail.email == comment.email).first()
if mail:
setattr(mail, 'is_allowed', True)
setattr(mail, 'is_blocked', False)
else:
new_mail = {
"email": comment.email,
"is_allowed": True,
"is_blocked": False
}
db.session.add(TEmail(**new_mail))
# Allow all comments made by this mail address
all_comments = db.session.query(TComments).filter(TComments.email == comment.email).all()
if all_comments:
for comment in all_comments:
setattr(comment, 'is_published', True)
setattr(comment, 'is_spam', False)
db.session.commit()
url = re.match("^(.*[/])", request.referrer)[0]
export_location(location_id)
return redirect(f"{url}/{location_id}")

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from flask import Blueprint
# Blueprint
bp_dashboard = Blueprint("bp_dashboard", __name__, url_prefix='/dashboard')
# Files with routes
from .projects import dashboard_project_list
from .mail import dashboard_manage_mail
from .spam import dashboard_review_spam
from .comments import dashboard_manage_regular_comments

@ -0,0 +1,51 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from . import bp_dashboard
from flask import render_template, request, redirect, url_for
from flask_login import login_required
from labertasche.database import labertasche_db as db
from labertasche.models import TLocation, TComments
from labertasche.helper import export_location, get_id_from_project_name
@bp_dashboard.route('<project>/manage-comments/', methods=["GET"])
@login_required
def dashboard_manage_regular_comments(project: str):
location_id = 0
proj_id = get_id_from_project_name(project)
all_locations = db.session.query(TLocation).filter(TLocation.project_id == proj_id).all()
# Project does not exist, error code is used by Javascript, not Flask
if proj_id == -1:
return redirect(url_for("bp_dashboard.dashboard_project_list", error=404))
if request.args.get('location'):
location_id = request.args.get('location')
# no parameters found
if location_id is None:
return render_template("manage-comments.html", locations=all_locations,
selected=location_id, title="Manage Comments",
action="comments")
try:
if int(location_id) >= 1:
spam_comments = db.session.query(TComments).filter(TComments.location_id == location_id) \
.filter(TComments.is_spam == False)
return render_template("manage-comments.html", locations=all_locations, selected=location_id,
spam_comments=spam_comments, project=project,
title="Manage Comments", action="comments")
except ValueError:
pass
export_location(location_id)
return render_template("manage-comments.html", locations=all_locations,
selected=location_id, project=project, title="Manage Comments",
action="comments")

@ -0,0 +1,32 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from . import bp_dashboard
from flask import render_template, redirect, url_for
from flask_login import login_required
from labertasche.database import labertasche_db as db
from labertasche.models import TEmail
from labertasche.helper import get_id_from_project_name
@bp_dashboard.route('<project>/manage-mail/')
@login_required
def dashboard_manage_mail(project: str):
"""
Shows the panel to manage email addresses
:param project: The project name to manage
:return: The template used to display the route
"""
proj_id = get_id_from_project_name(project)
# Project does not exist, error code is used by Javascript, not Flask
if proj_id == -1:
return redirect(url_for("bp_dashboard.dashboard_project_list", error=404))
addresses = db.session.query(TEmail).filter(TEmail.project_id == proj_id).all()
return render_template("manage-mail.html", addresses=addresses, project=project)

@ -0,0 +1,87 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from . import bp_dashboard
from flask import render_template, redirect, url_for
from flask_login import login_required
from sqlalchemy import func
from labertasche.database import labertasche_db as db
from labertasche.models import TComments, TProjects
from labertasche.helper import get_id_from_project_name, dates_of_the_week
@bp_dashboard.route("/")
@login_required
def dashboard_project_list():
"""
Displays an overview of all projects.
:return: The overview template.
"""
t_projects = db.session.query(TProjects).all()
projects = list()
for each in t_projects:
comments = db.session.query(TComments).filter(TComments.project_id == each.id_project) \
.filter(TComments.is_published == True) \
.filter(TComments.is_spam == False).count()
unpub_comments = db.session.query(TComments).filter(TComments.project_id == each.id_project) \
.filter(TComments.is_spam == False) \
.filter(TComments.is_published == False).count()
spam = db.session.query(TComments).filter(TComments.project_id == each.id_project) \
.filter(TComments.is_spam == True).count()
projects.append(dict({
"id_project": each.id_project,
"name": each.name,
"total_comments": comments,
"total_spam": spam,
"total_unpublished": unpub_comments
}))
return render_template('project-list.html', projects=projects)
@bp_dashboard.route('/<project>/')
@login_required
def dashboard_project_stats(project: str):
"""
Displays the project dashboard
:param project: The project to show
:return: The template for the route
"""
proj_id = get_id_from_project_name(project)
# Project does not exist, error code is used by Javascript, not Flask
if proj_id == -1:
return redirect(url_for("bp_dashboard.dashboard_project_list", error=404))
dates = dates_of_the_week()
spam = list()
published = list()
unpublished = list()
for each in dates:
spam_comments = db.session.query(TComments).filter(TComments.project_id == proj_id) \
.filter(func.DATE(TComments.created_on) == each.date()) \
.filter(TComments.is_spam == True).all()
pub_comments = db.session.query(TComments).filter(func.DATE(TComments.created_on) == each.date()) \
.filter(TComments.project_id == proj_id) \
.filter(TComments.is_spam == False) \
.filter(TComments.is_published == True).all()
unpub_comments = db.session.query(TComments).filter(func.DATE(TComments.created_on) == each.date()) \
.filter(TComments.project_id == proj_id) \
.filter(TComments.is_spam == False) \
.filter(TComments.is_published == False).all()
published.append(len(pub_comments))
spam.append(len(spam_comments))
unpublished.append(len(unpub_comments))
return render_template('project-stats.html', dates=dates, spam=spam, project=project,
published=published, unpublished=unpublished)

@ -0,0 +1,56 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from . import bp_dashboard
from flask import render_template, request, redirect, url_for
from flask_login import login_required
from labertasche.database import labertasche_db as db
from labertasche.models import TLocation, TComments
from labertasche.helper import export_location, get_id_from_project_name
@bp_dashboard.route('<project>/manage-spam/', methods=["GET"])
@login_required
def dashboard_review_spam(project: str):
"""
Shows the manage spam template
:param project: The project used for displaying data
:return: The template to display for this rouet
"""
location_id = 0
proj_id = get_id_from_project_name(project)
all_locations = db.session.query(TLocation).filter(TLocation.project_id == proj_id).all()
# Project does not exist, error code is used by Javascript, not Flask
if proj_id == -1:
return redirect(url_for("bp_dashboard.dashboard_project_list", error=404))
if request.args.get('location'):
location_id = request.args.get('location')
# no parameters found
if location_id is None:
return render_template("manage-comments.html", locations=all_locations,
selected=location_id, title="Review Spam", action="spam")
try:
if int(location_id) >= 1:
spam_comments = db.session.query(TComments) \
.filter(TComments.project_id == proj_id) \
.filter(TComments.location_id == location_id) \
.filter(TComments.is_spam == True)
return render_template("manage-comments.html", locations=all_locations, selected=location_id,
spam_comments=spam_comments, project=project, title="Review Spam", action="spam")
except ValueError:
pass
export_location(location_id)
return render_template("manage-comments.html", locations=all_locations,
selected=location_id, project=project, title="Review Spam", action="spam")

@ -0,0 +1,17 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from flask import Blueprint
# Blueprint
bp_jsconnector = Blueprint("bp_jsconnector", __name__, url_prefix='/api/')
from .projects import api_create_project, api_delete_project, api_edit_project_name
from .mail import api_toggle_email_reputation, api_reset_mail_reputation
from .comments import api_comment_allow_user, api_comment_allow_comment, \
api_comment_block_mail, api_comments_delete_comment

@ -0,0 +1,109 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from . import bp_jsconnector
from flask import request, redirect
from flask_login import login_required
from labertasche.database import labertasche_db as db
from labertasche.helper import export_location
from labertasche.models import TComments, TEmail
# This file contains the routes for the manage comments menu point.
# They are called via GET
@bp_jsconnector.route('/comment-delete/<int:comment_id>', methods=['GET'])
@login_required
def api_comments_delete_comment(comment_id):
db.session.query(TComments).filter(TComments.comments_id == comment_id).delete()
db.session.commit()
# Get location id from get params
location_id = request.args.get('location')
export_location(location_id)
return redirect(request.referrer)
@bp_jsconnector.route('/comment-allow/<int:comment_id>', methods=['GET'])
@login_required
def api_comment_allow_comment(comment_id):
comment = db.session.query(TComments).filter(TComments.comments_id == comment_id).first()
if comment:
setattr(comment, 'is_published', True)
setattr(comment, 'is_spam', False)
db.session.commit()
# Get location id from get params
location_id = request.args.get('location')
export_location(location_id)
return redirect(request.referrer)
@bp_jsconnector.route('/comment-allow-user/<int:comment_id>', methods=["GET"])
@login_required
def api_comment_allow_user(comment_id):
comment = db.session.query(TComments).filter(TComments.comments_id == comment_id).first()
if comment:
addr = db.session.query(TEmail).filter(TEmail.email == comment.email).first()
if addr:
setattr(addr, 'is_allowed', True)
setattr(addr, 'is_blocked', False)
else:
new_mail = {
"email": comment.email,
"is_allowed": True,
"is_blocked": False
}
db.session.add(TEmail(**new_mail))
# Allow all comments made by this mail address
all_comments = db.session.query(TComments).filter(TComments.email == comment.email).all()
if all_comments:
for comment in all_comments:
setattr(comment, 'is_published', True)
setattr(comment, 'is_spam', False)
db.session.commit()
# Get location id from get params
location_id = request.args.get('location')
export_location(location_id)
return redirect(request.referrer)
@bp_jsconnector.route('/comment-block-mail/<int:comment_id>', methods=["GET"])
@login_required
def api_comment_block_mail(comment_id):
comment = db.session.query(TComments).filter(TComments.comments_id == comment_id).first()
if comment:
addr = db.session.query(TEmail).filter(TEmail.email == comment.email).first()
if addr:
setattr(addr, 'is_allowed', False)
setattr(addr, 'is_blocked', True)
else:
new_mail = {
"email": comment.first().email,
"is_allowed": False,
"is_blocked": True
}
db.session.add(TEmail(**new_mail))
# Delete all comments made by this mail address
db.session.query(TComments).filter(TComments.email == comment.email).delete()
db.session.commit()
# Get location id from get params
location_id = request.args.get('location')
export_location(location_id)
return redirect(request.referrer)

@ -0,0 +1,36 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from . import bp_jsconnector
from flask import request, redirect
from flask_login import login_required
from labertasche.database import labertasche_db as db
from labertasche.helper import get_id_from_project_name, export_location
from labertasche.models import TEmail, TComments
from re import match
@bp_jsconnector.route('/mail-toggle-status/<int:id_email>')
@login_required
def api_toggle_email_reputation(id_email):
address = db.session.query(TEmail).filter(TEmail.id_email == id_email).first()
if address:
setattr(address, "is_allowed", (not address.is_allowed))
setattr(address, "is_blocked", (not address.is_blocked))
db.session.commit()
return redirect(request.referrer)
@bp_jsconnector.route('/mail-reset-reputation/<int:id_email>')
@login_required
def api_reset_mail_reputation(id_email):
db.session.query(TEmail).filter(TEmail.id_email == id_email).delete()
db.session.commit()
return redirect(request.referrer)

@ -0,0 +1,91 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from . import bp_jsconnector
from flask import request, make_response, jsonify, redirect, url_for
from flask_login import login_required
from labertasche.database import labertasche_db as db
from labertasche.helper import get_id_from_project_name
from labertasche.models import TProjects, TComments, TEmail, TLocation
import re
@bp_jsconnector.route("/project/new", methods=['POST'])
@login_required
def api_create_project():
"""
Called on dashboard project overview to create a new project.
:return: A string with an error code and 'ok' as string on success.
"""
# TODO: Project name exists?
name = request.json['name']
if not len(name):
return make_response(jsonify(status='too-short'), 400)
if not re.match('^\\w+$', name):
return make_response(jsonify(status='invalid-name'), 400)
proj = TProjects(name=name)
db.session.add(proj)
db.session.commit()
return make_response(jsonify(status='ok'), 200)
@bp_jsconnector.route('project/edit/<name>', methods=['POST'])
@login_required
def api_edit_project_name(name: str):
"""
Renames the project.
:param name:
:return: A string with an error code and 'ok' as string on success.
"""
# TODO: Project name exists?
new_name = request.json['name']
if not len(new_name):
return make_response(jsonify(status='too-short'), 400)
if not re.match('^\\w+$', new_name):
return make_response(jsonify(status='invalid-name'), 400)
proj_id = get_id_from_project_name(name)
project = db.session.query(TProjects).filter(TProjects.id_project == proj_id)
setattr(project, 'name', new_name)
db.session.upate(project)
db.session.commit()
return make_response(jsonify(status='ok'), 200)
@bp_jsconnector.route('project/delete/<project>', methods=['GET'])
@login_required
def api_delete_project(project: str):
"""
Deletes a project from the database and all associated data
:param project: The name of the project
:return: A string with an error code and 'ok' as string on success.
"""
# TODO: Javascript
proj_id = get_id_from_project_name(project)
if proj_id == -1:
return make_response(jsonify(status='not-found'), 400)
# noinspection PyBroadException
try:
db.session.query(TComments).filter(TComments.project_id == proj_id).delete()
db.session.query(TLocation).filter(TLocation.project_id == proj_id).delete()
db.session.query(TEmail).filter(TEmail.project_id == proj_id).delete()
db.session.query(TProjects).filter(TProjects.id_project == proj_id).delete()
db.session.commit()
db.session.flush()
except Exception:
return make_response(jsonify(status='exception'), 400)
return make_response(jsonify(status='ok'), 200)

@ -0,0 +1,71 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# /**********************************************************************************
# * _author : Domeniko Gentner
# * _mail : code@tuxstash.de
# * _repo : https://git.tuxstash.de/gothseidank/labertasche
# * _license : This project is under MIT License
# *********************************************************************************/
from . import bp_jsconnector
from flask import request, redirect
from flask_login import login_required
from labertasche.database import labertasche_db as db
from labertasche.models import TComments, TEmail
from labertasche.helper import export_location
import re
# @bp_jsconnector.route('/block-mail/<int:location_id>/<int:comment_id>', methods=["GET"])
# @login_required
# def dashboard_review_spam_block_mail(location_id, comment_id):
# comment = db.session.query(TComments).filter(TComments.comments_id == comment_id).first()
# if comment:
# addr = db.session.query(TEmail).filter(TEmail.email == comment.email).first()
# if addr:
# setattr(addr, 'is_allowed', False)
# setattr(addr, 'is_blocked', True)
# else:
# new_mail = {
# "email": comment.first().email,
# "is_allowed": False,
# "is_blocked": True
# }
# db.session.add(TEmail(**new_mail))
#
# # Delete all comments made by this mail address
# db.session.query(TComments).filter(TComments.email == comment.email).delete()
# db.session.commit()
#
# url = re.match("^(.*[/])", request.referrer)[0]
# export_location(location_id)
# return redirect(f"{url}/{location_id}")
#
# @bp_jsconnector.route('/allow-user/<int:location_id>/<int:comment_id>', methods=["GET"])
# @login_required
# def dashboard_review_spam_allow_user(location_id, comment_id):
# comment = db.session.query(TComments).filter(TComments.comments_id == comment_id).first()
# if comment:
# addr = db.session.query(TEmail).filter(TEmail.email == comment.email).first()
# if addr:
# setattr(addr, 'is_allowed', True)
# setattr(addr, 'is_blocked', False)
# else:
# new_mail = {
# "email": comment.email,
# "is_allowed": True,
# "is_blocked": False
# }
# db.session.add(TEmail(**new_mail))
#
# # Allow all comments made by this mail address
# all_comments = db.session.query(TComments).filter(TComments.email == comment.email).all()
# if all_comments:
# for comment in all_comments:
# setattr(comment, 'is_published', True)
# setattr(comment, 'is_spam', False)
#
# db.session.commit()
# url = re.match("^(.*[/])", request.referrer)[0]
# export_location(location_id)
# return redirect(f"{url}/{location_id}")

@ -19,7 +19,7 @@ bp_login = Blueprint("bp_login", __name__)
@bp_login.route('/', methods=['GET']) @bp_login.route('/', methods=['GET'])
def show_login(): def show_login():
if current_user.is_authenticated: if current_user.is_authenticated:
return redirect(url_for('bp_dashboard.dashboard_index')) return redirect(url_for('bp_dashboard.dashboard_project_list'))
return render_template('login.html') return render_template('login.html')
@ -32,7 +32,7 @@ def login():
if check_auth(username, password): if check_auth(username, password):
login_user(User(0), remember=True) login_user(User(0), remember=True)
return redirect(url_for('bp_dashboard.dashboard_index')) return redirect(url_for('bp_dashboard.dashboard_project_list'))
# Redirect get request to the login page # Redirect get request to the login page
return redirect(url_for('bp_login.show_login')) return redirect(url_for('bp_login.show_login'))

@ -201,4 +201,8 @@ def get_id_from_project_name(name: str) -> int:
:return: the ID of the project :return: the ID of the project
""" """
proj = db.session.query(TProjects).filter(TProjects.name == name).first() proj = db.session.query(TProjects).filter(TProjects.name == name).first()
if proj is None:
return -1
return proj.id_project return proj.id_project

@ -28,3 +28,4 @@ class Settings:
self.gravatar = conf['gravatar'] self.gravatar = conf['gravatar']
self.addons = conf['addons'] self.addons = conf['addons']
self.smileys = conf['smileys'] self.smileys = conf['smileys']
self.projects = conf['projects']

@ -14,7 +14,7 @@ from sqlalchemy import event
from sqlalchemy.engine import Engine from sqlalchemy.engine import Engine
from labertasche.settings import Settings from labertasche.settings import Settings
from labertasche.database import labertasche_db from labertasche.database import labertasche_db
from labertasche.blueprints import bp_comments, bp_login, bp_dashboard from labertasche.blueprints import bp_comments, bp_login, bp_dashboard, bp_jsconnector
from labertasche.models import TProjects from labertasche.models import TProjects
from labertasche.helper import User from labertasche.helper import User
from flask_login import LoginManager from flask_login import LoginManager
@ -27,7 +27,9 @@ settings = Settings()
# Flask App # Flask App
laberflask = Flask(__name__) laberflask = Flask(__name__)
laberflask.config.update(dict( laberflask.config.update(dict(
SESSION_COOKIE_DOMAIN=settings.system['cookie-domain'], SESSION_COOKIE_DOMAIN=settings.system['cookie_domain'],
SESSION_COOKIE_SECURE=settings.system['cookie_secure'],
REMEMBER_COOKIE_SECURE=settings.system['cookie_secure'],
DEBUG=settings.system['debug'], DEBUG=settings.system['debug'],
SECRET_KEY=settings.system['secret'], SECRET_KEY=settings.system['secret'],
TEMPLATES_AUTO_RELOAD=True, TEMPLATES_AUTO_RELOAD=True,
@ -58,6 +60,7 @@ CORS(laberflask, resources={r"/comments": {"origins": settings.system['blog_url'
laberflask.register_blueprint(bp_comments) laberflask.register_blueprint(bp_comments)
laberflask.register_blueprint(bp_dashboard) laberflask.register_blueprint(bp_dashboard)
laberflask.register_blueprint(bp_login) laberflask.register_blueprint(bp_login)
laberflask.register_blueprint(bp_jsconnector)
# Disable Werkzeug's verbosity during development # Disable Werkzeug's verbosity during development
log = logging.getLogger('werkzeug') log = logging.getLogger('werkzeug')

@ -5,6 +5,9 @@
// # * _license : This project is under MIT License // # * _license : This project is under MIT License
// # *********************************************************************************/ // # *********************************************************************************/
// ------------------------------------------------------
// Called when search for mail addresses in manage mail
// ------------------------------------------------------
function dashboard_mailsearch(search_txt) function dashboard_mailsearch(search_txt)
{ {
let el = document.getElementById('mail-table'); let el = document.getElementById('mail-table');
@ -20,19 +23,23 @@ function dashboard_mailsearch(search_txt)
} }
} }
// ------------------------------------------------------
// Called when a new project is created,
// posts it to the server
// ------------------------------------------------------
function new_project_save() { function new_project_save() {
let modal_ok = document.getElementById('modal-ok'); let modal_ok = document.getElementById('modal-ok');
let modal_cancel = document.getElementById('modal-cancel'); let modal_cancel = document.getElementById('modal-cancel');
let short_help = document.getElementById('new-project-too-short'); let short_help_short = document.getElementById('new-project-too-short');
let short_help_invalid = document.getElementById('new-project-invalid-name'); let short_help_invalid = document.getElementById('new-project-invalid-name');
let name = document.getElementById('project-name').value let name = document.getElementById('project-name').value
short_help.classList.add('is-hidden'); short_help_short.classList.add('is-hidden');
short_help_invalid.classList.add('is-hidden'); short_help_invalid.classList.add('is-hidden');
// Validate input // Validate input
if (name.length === 0) { if (name.length === 0) {
short_help.classList.remove('is-hidden'); short_help_short.classList.remove('is-hidden');
return false; return false;
} }
if (/^\w+$/.test(name) === false){ if (/^\w+$/.test(name) === false){
@ -42,7 +49,7 @@ function new_project_save() {
modal_ok.classList.add('is-loading'); modal_ok.classList.add('is-loading');
modal_cancel.classList.add('is-hidden'); modal_cancel.classList.add('is-hidden');
fetch(window.location.protocol + "//" + window.location.host + '/dashboard/project/new', fetch(window.location.protocol + "//" + window.location.host + '/api/project/new',
{ {
mode: "cors", mode: "cors",
headers: { headers: {
@ -63,9 +70,10 @@ function new_project_save() {
modal_cancel.classList.remove('is-hidden'); modal_cancel.classList.remove('is-hidden');
if (result === "ok"){ if (result === "ok"){
hide_modal('modal-new-project'); hide_modal('modal-new-project');
window.location.reload(true);
} }
if (result === "too-short"){ if (result === "too-short"){
short_help.classList.remove('is-hidden'); short_help_short.classList.remove('is-hidden');
} }
if (result === "invalid-name"){ if (result === "invalid-name"){
short_help_invalid.classList.remove('is-hidden'); short_help_invalid.classList.remove('is-hidden');
@ -76,12 +84,19 @@ function new_project_save() {
}) })
} }
// ------------------------------------------------------
// Hides any modal
// ------------------------------------------------------
function hide_modal(id_name) function hide_modal(id_name)
{ {
let el = document.getElementById(id_name); let el = document.getElementById(id_name);
el.classList.remove("is-active"); el.classList.remove("is-active");
} }
// ------------------------------------------------------
// Shows any modal
// ------------------------------------------------------
function show_modal(id_name) function show_modal(id_name)
{ {
let el = document.getElementById(id_name); let el = document.getElementById(id_name);

@ -25,18 +25,20 @@
</a> </a>
<div class="navbar-start"></div> <div class="navbar-start"></div>
<div class="navbar-end"> <div class="navbar-end">
<a class="navbar-item" href="/dashboard/review-spam/"> {% if project is defined %}
<a class="navbar-item" href="/dashboard/{{ project }}/manage-spam/">
<span class="icon"><i class="mdi mdi-24px mdi-space-invaders"></i></span> <span class="icon"><i class="mdi mdi-24px mdi-space-invaders"></i></span>
&nbsp;REVIEW SPAM &nbsp;MANAGE SPAM
</a> </a>
<a class="navbar-item" href="/dashboard/manage-comments/"> <a class="navbar-item" href="/dashboard/{{ project }}/manage-comments/">
<span class="icon"><i class="mdi mdi-24px mdi-comment"></i></span> <span class="icon"><i class="mdi mdi-24px mdi-comment"></i></span>
&nbsp;MANAGE COMMENTS &nbsp;MANAGE COMMENTS
</a> </a>
<a class="navbar-item" href="/dashboard/manage-mail"> <a class="navbar-item" href="/dashboard/{{ project }}/manage-mail">
<span class="icon"><i class="mdi mdi-24px mdi-email"></i></span> <span class="icon"><i class="mdi mdi-24px mdi-email"></i></span>
&nbsp;MANAGE EMAIL ADDRESSES &nbsp;MANAGE EMAIL ADDRESSES
</a> </a>
{% endif %}
<a class="navbar-item" href="/logout/"> <a class="navbar-item" href="/logout/">
<span class="icon"><i class="mdi mdi-24px mdi-account-cancel"></i></span> <span class="icon"><i class="mdi mdi-24px mdi-account-cancel"></i></span>
&nbsp;LOGOUT &nbsp;LOGOUT
@ -49,5 +51,19 @@
</section> </section>
<script defer src="/static/js/dashboard.js"></script> <script defer src="/static/js/dashboard.js"></script>
<script defer src="/static/js/Chart.bundle.min.js"></script> <script defer src="/static/js/Chart.bundle.min.js"></script>
<script defer src="https://unpkg.com/@popperjs/core@2"></script>
<script defer src="https://unpkg.com/tippy.js@6"></script>
<script defer>
document.addEventListener('DOMContentLoaded', () => {
// Comments
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get("error") === "404"){
show_modal('modal-project-not-found');
}
{% block javascript %}
{% endblock %}
});
</script>
</body> </body>
</html> </html>

@ -19,7 +19,7 @@
<body> <body>
<section class="hero bg-yayellow is-fullheight"> <section class="hero bg-yayellow is-fullheight">
<div class="hero-head"> <div class="hero-head">
<a target="_blank" rel="noopener nofollow noreferrer" <a target="_blank" href="https://github.com/domeniko-gentner/labertasche" rel="noopener nofollow noreferrer"
class="button is-info is-inverted is-medium"> class="button is-info is-inverted is-medium">
<span class="icon mr-2"> <span class="icon mr-2">
<i class="mdi mdi-36px mdi-github"></i> <i class="mdi mdi-36px mdi-github"></i>
@ -30,10 +30,13 @@
<div class="hero-body has-text-black"> <div class="hero-body has-text-black">
<div class="container has-text-centered"> <div class="container has-text-centered">
<p class="title">Labertasche Login</p> <p class="title">Labertasche Login</p>
<!--suppress HtmlUnknownTarget -->
<form method="POST" action="/login"> <form method="POST" action="/login">
<div class="field"> <div class="field">
<div class="control is-expanded has-icons-left"> <div class="control is-expanded has-icons-left">
<input class="input" name="username" type="text"> <label for="username" class="help has-text-left has-text-black">
<input class="input" name="username" type="text" placeholder="username">
</label>
<span class="icon is-small is-left"> <span class="icon is-small is-left">
<span class="mdi mdi-24px mdi-shield-account"></span> <span class="mdi mdi-24px mdi-shield-account"></span>
</span> </span>
@ -41,7 +44,9 @@
</div> </div>
<div class="field"> <div class="field">
<div class="control is-expanded has-icons-left"> <div class="control is-expanded has-icons-left">
<input class="input" name="password" type="password"> <label for="password">
<input class="input" name="password" type="password" placeholder="password">
</label>
<span class="icon is-small is-left"> <span class="icon is-small is-left">
<span class="mdi mdi-24px mdi-shield-key"></span> <span class="mdi mdi-24px mdi-shield-key"></span>
</span> </span>

@ -1,12 +1,13 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block main %} {% block main %}
<div style="min-height: 100vh;" class="container bg-deepmatte p-6 brdr-yayellow"> <div style="min-height: 100vh;" class="container bg-deepmatte p-6 brdr-yayellow">
<h1 class="title has-text-white has-text-centered">Manage Comments</h1> <h1 class="title has-text-white has-text-centered">{{ title }}</h1>
<div class="field"> <div class="field">
<form method="post" action="/dashboard/manage-comments/"> <form method="GET" action="/dashboard/{{ project }}/manage-{{action}}/">
<div class="control"> <div class="control">
<div class="select"> <div class="select">
<select name="selected_location" onchange="this.form.submit();"> <label for="location">
<select name="location" onchange="this.form.submit();">
<option value="-1">Select the article</option> <option value="-1">Select the article</option>
{% for each in locations %} {% for each in locations %}
{% if selected is defined %} {% if selected is defined %}
@ -20,6 +21,7 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</select> </select>
</label>
</div> </div>
</div> </div>
</form> </form>
@ -48,22 +50,22 @@
<nav class="level is-mobile"> <nav class="level is-mobile">
<a title="Delete this comment" <a title="Delete this comment"
class="level-item" class="level-item"
href="/dashboard/delete-comment/{{ selected }}/{{ comment.comments_id }}"> href="/api/comment-delete/{{ comment.comments_id }}?location={{ selected }}">
<span class="icon is-medium"><i class="mdi mdi-24px mdi-trash-can"></i></span> <span class="icon is-medium"><i class="mdi mdi-24px mdi-trash-can"></i></span>
</a> </a>
<a title="Delete comment and block mail address" <a title="Delete comment and block mail address"
class="level-item" class="level-item"
href="/dashboard/block-mail/{{ selected }}/{{ comment.comments_id }}"> href="/api/comment-block-mail/{{ comment.comments_id }}?location={{ selected }}">
<span class="icon is-medium"><i class="mdi mdi-24px mdi-shield-lock"></i></span> <span class="icon is-medium"><i class="mdi mdi-24px mdi-shield-lock"></i></span>
</a> </a>
<a title="Publish this comment, don't allow mail" <a title="Publish this comment, don't allow mail"
class="level-item" class="level-item"
href="/dashboard/allow-comment/{{ selected }}/{{ comment.comments_id }}"> href="/api/comment-allow/{{ comment.comments_id }}?location={{ selected }}">
<span class="icon is-medium"><i class="mdi mdi-24px mdi-check"></i></span> <span class="icon is-medium"><i class="mdi mdi-24px mdi-check"></i></span>
</a> </a>
<a title="Allow email to bypass spam detection and allow comment" <a title="Allow email to bypass spam detection and allow comment"
class="level-item" class="level-item"
href="/dashboard/allow-user/{{ selected }}/{{ comment.comments_id }}"> href="/api/comment-allow-user/{{ comment.comments_id }}?location={{ selected }}">
<span class="icon is-medium"><i class="mdi mdi-24px mdi-check-all"></i></span> <span class="icon is-medium"><i class="mdi mdi-24px mdi-check-all"></i></span>
</a> </a>
</nav> </nav>

@ -33,7 +33,7 @@
{% if each.is_blocked %} {% if each.is_blocked %}
<a title="Email is currently blocked. Click to unblock." <a title="Email is currently blocked. Click to unblock."
class="has-text-black" class="has-text-black"
href="/dashboard/toggle-mail-allowed/{{ each.id_email }}"> href="/api/mail-toggle-status/{{ each.id_email }}">
<i class="mdi mdi-24px mdi-check"></i> <i class="mdi mdi-24px mdi-check"></i>
</a> </a>
{% else %} {% else %}
@ -49,7 +49,7 @@
<p class="has-text-centered"> <p class="has-text-centered">
<a title="Delete entry, this resets the reputation" <a title="Delete entry, this resets the reputation"
class="has-text-danger-dark" class="has-text-danger-dark"
href="/dashboard/reset-mail-reputation/{{ each.id_email }}"> href="/api/mail-reset-reputation/{{ each.id_email }}">
<i class="mdi mdi-24px mdi-trash-can"></i> <i class="mdi mdi-24px mdi-trash-can"></i>
</a> </a>
</p> </p>

@ -38,18 +38,19 @@
<div class="card-footer-item"> <div class="card-footer-item">
<a class="has-text-weight-bold has-text-black is-uppercase" <a class="has-text-weight-bold has-text-black is-uppercase"
onclick="show_modal('modal-new-project');" onclick="show_modal('modal-new-project');"
data-tippy-content="Create a new project"
href="#">NEW</a> href="#">NEW</a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% for project in projects %} {% for each in projects %}
<div class="column is-4"> <div class="column is-4">
<div class="card my-shadow-subtle brdr-darkslate"> <div class="card my-shadow-subtle brdr-darkslate">
<div class="card-header"> <div class="card-header">
<div class="card-header-title"> <div class="card-header-title">
<p class="is-size-4 is-uppercase"> <p class="is-size-4 is-uppercase">
{{ project['name'] }} {{ each['name'] }}
</p> </p>
</div> </div>
</div> </div>
@ -58,33 +59,43 @@
<div class="level-item has-text-centered"> <div class="level-item has-text-centered">
<div> <div>
<p class="heading is-capitalized">Comments</p> <p class="heading is-capitalized">Comments</p>
<p class="is-size-4 has-text-weight-bold has-text-white">{{ project['total_comments'] }}</p> <p class="is-size-4 has-text-weight-bold has-text-white">{{ each['total_comments'] }}</p>
</div> </div>
</div> </div>
<div class="level-item has-text-centered"> <div class="level-item has-text-centered">
<div> <div>
<p class="heading is-capitalized">Unpublished</p> <p class="heading is-capitalized">Unpublished</p>
<p class="is-size-4 has-text-weight-bold has-text-white">{{ project['total_unpublished'] }}</p> <p class="is-size-4 has-text-weight-bold has-text-white">{{ each['total_unpublished'] }}</p>
</div> </div>
</div> </div>
<div class="level-item has-text-centered"> <div class="level-item has-text-centered">
<div class="ml-4"> <div class="ml-4">
<p class="heading is-capitalized">Spam</p> <p class="heading is-capitalized">Spam</p>
<p class="is-size-4 has-text-weight-bold has-text-white">{{ project['total_spam'] }}</p> <p class="is-size-4 has-text-weight-bold has-text-white">{{ each['total_spam'] }}</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="card-footer"> <div class="card-footer">
<div class="card-footer-item has-background-danger-dark">
<a class="has-text-weight-bold has-text-white is-uppercase"
data-tippy-content="Delete the project and all of its content"
href="{{ url_for('bp_jsconnector.api_delete_project', project=each['name']) }}">DELETE</a>
</div>
<div class="card-footer-item"> <div class="card-footer-item">
<a class="has-text-weight-bold has-text-black is-uppercase" href="#">EDIT</a> <a class="has-text-weight-bold has-text-black is-uppercase"
data-tippy-content="Edit the name of the project and it's properties"
href="#">EDIT</a>
</div> </div>
<div class="card-footer-item"> <div class="card-footer-item">
<a class="has-text-weight-bold has-text-black is-uppercase" href="#">DELETE</a> <a class="has-text-weight-bold has-text-black is-uppercase"
data-tippy-content="Export all comments to Hugo.<br>This is normally not needed."
href="#">EXPORT</a>
</div> </div>
<div class="card-footer-item"> <div class="card-footer-item">
<a class="has-text-weight-bold has-text-black is-uppercase" <a class="has-text-weight-bold has-text-black is-uppercase"
href="{{ url_for('bp_dashboard.dashboard_project_index', project=project['name'])}}">VIEW</a> data-tippy-content="Manage this project"
href="{{ url_for('bp_dashboard.dashboard_project_stats', project=each['name']) }}">VIEW</a>
</div> </div>
</div> </div>
</div> </div>
@ -92,7 +103,7 @@
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div class="modal is-active" id="modal-new-project"> <div class="modal" id="modal-new-project">
<div class="modal-background"></div> <div class="modal-background"></div>
<div class="modal-card"> <div class="modal-card">
<header class="modal-card-head"> <header class="modal-card-head">
@ -117,4 +128,33 @@
</footer> </footer>
</div> </div>
</div> </div>
<div class="modal" id="modal-project-not-found">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head has-background-danger">
<p class="modal-card-title has-text-white">ERROR</p>
<button class="delete" aria-label="close"></button>
</header>
<section class="modal-card-body has-text-black">
The specified project was not found! Did you delete it? Try refreshing the page.<br>
If you believe this to be a bug, please report it
<a class="has-text-info"
href="https://github.com/domeniko-gentner/labertasche/issues"
target="_blank" rel="nofollow noopener norefferer">
here
</a>.
</section>
<footer class="modal-card-foot">
<button id="modal-ok" onclick="hide_modal('modal-project-not-found')" class="button is-success">
OK
</button>
</footer>
</div>
</div>
{% endblock %}
{% block javascript %}
tippy('[data-tippy-content]', {
allowHTML: true,
delay: 500
});
{% endblock %} {% endblock %}

@ -1,78 +0,0 @@
{% extends "base.html" %}
{% block main %}
<div style="min-height: 100vh;" class="container bg-deepmatte p-6 brdr-yayellow">
<h1 class="title has-text-white has-text-centered">Review Spam</h1>
<div class="field">
<form method="post" action="/dashboard/review-spam/">
<div class="control">
<div class="select">
<select name="selected_location" onchange="this.form.submit();">
<option value="-1">Select the article</option>
{% for each in locations %}
{% if selected is defined %}
{% if selected | string() == each.id_location | string() %}
<option selected="selected" value="{{ each.id_location }}">{{ each.location }}</option>
{% else %}
<option value="{{ each.id_location }}">{{ each.location }}</option>
{% endif %}
{% else %}
<option value="{{ each.id_location }}">{{ each.location }}</option>
{% endif %}
{% endfor %}
</select>
</div>
</div>
</form>
</div>
<div>
{% if spam_comments is defined %}
{% for comment in spam_comments %}
<article>
<div class="media mb-5 brdr-yayellow my-shadow-subtle bg-compliment">
<figure class="media-left ml-0 mb-0">
<p class="image is-128x128">
<img alt="gravatar portrait" src="https://gravatar.com/avatar/{{comment.gravatar}}?size=128">
</p>
</figure>
<div class="media-content">
<div class="content mr-5 mt-2">
<a title="comment ID" id="comment_{{comment.comments_id}}" href="#comment_{{comment.comments_id}}">#{{comment.comments_id}}</a>
Posted by <span class="fg-yellow">{{comment.email}}</span>
on <small class="fg-yellow">{{comment.created_on}}</small>
spam score: <span title="The higher this is, the less likely it is spam" class="fg-yellow">{{ comment.spam_score }}</span>
<br><br>
<span class="mt-5">
{{comment.content}}
</span>
</div>
<nav class="level is-mobile">
<a title="Delete this comment"
class="level-item"
href="/dashboard/delete-comment/{{ selected }}/{{ comment.comments_id }}">
<span class="icon is-medium"><i class="mdi mdi-24px mdi-trash-can"></i></span>
</a>
<a title="Delete comment and block mail address"
class="level-item"
href="/dashboard/block-mail/{{ selected }}/{{ comment.comments_id }}">
<span class="icon is-medium"><i class="mdi mdi-24px mdi-shield-lock"></i></span>
</a>
<a title="Publish this comment, don't allow mail"
class="level-item"
href="/dashboard/allow-comment/{{ selected }}/{{ comment.comments_id }}">
<span class="icon is-medium"><i class="mdi mdi-24px mdi-check"></i></span>
</a>
<a title="Allow email to bypass spam detection and allow comment"
class="level-item"
href="/dashboard/allow-user/{{ selected }}/{{ comment.comments_id }}">
<span class="icon is-medium"><i class="mdi mdi-24px mdi-check-all"></i></span>
</a>
</nav>
</div>
</div>
</article>
{% endfor %}
{% endif %}
</div>
</div>
{% endblock %}
Loading…
Cancel
Save