Projects Update 2020-12-26

* The new comment path now sends `sendotp` in json, which reflects the project setting
* Removed unneccessary export_location in bp_dashboard/comments.py
* Stored Mail addresses in the block and allow list are considered global and not part of projects. Trust is universal.
* added missing cross_origin decorators
* added redirect to dbv2 update template
* now using a jinja2 template for the html mail
* location and project_id are now a composite unique in t_location
* corrected total comments graph
projects
Domeniko Gentner 3 years ago
parent 1071acfc4c
commit 01d20f4641
  1. 2
      __implementation_example/static/css/labertasche.css
  2. 11
      __implementation_example/static/js/mysite.js
  3. 31
      labertasche/blueprints/bp_comments/__init__.py
  4. 21
      labertasche/blueprints/bp_dashboard/comments.py
  5. 20
      labertasche/blueprints/bp_dashboard/mail.py
  6. 20
      labertasche/blueprints/bp_dashboard/projects.py
  7. 14
      labertasche/blueprints/bp_dashboard/spam.py
  8. 46
      labertasche/blueprints/bp_jsconnector/projects.py
  9. 4
      labertasche/blueprints/bp_upgrades/db_v2.py
  10. 5
      labertasche/database/__init__.py
  11. 21
      labertasche/mail/__init__.py
  12. 1
      labertasche/models/t_emails.py
  13. 7
      labertasche/models/t_location.py
  14. 18
      static/js/dashboard.js
  15. 40
      templates/comment_confirmation.html
  16. 12
      templates/manage-comments.html
  17. 12
      templates/project-stats.html

@ -9,4 +9,4 @@
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/@font-face{font-family:'Font Awesome 5 Brands';font-style:normal;font-weight:400;font-display:swap;src:url("/css/fa-brands-400.eot");src:url("/css/fa-brands-400.eot?#iefix") format("embedded-opentype"),url("/css/fa-brands-400.woff2") format("woff2"),url("/css/fa-brands-400.woff") format("woff"),url("/css/fa-brands-400.ttf") format("truetype"),url("/css/fa-brands-400.svg#fontawesome") format("svg")}.fab{font-family:'Font Awesome 5 Brands';font-weight:400}
/*# sourceMappingURL=tuxstash.css.map */@font-face{font-family:fira code;font-style:normal;font-weight:500;font-display:swap;font-variant:common-ligatures;src:local(''),url(fira-code-v9-latin-500.woff2)format('woff2')}@font-face{font-family:open sans;font-style:normal;font-weight:400;font-display:swap;src:local('Open Sans Regular'),local('OpenSans-Regular'),url(open-sans-v18-latin-regular.woff2)format('woff2')}figcaption{background-color:#feda6a;color:#000;border-top:2px #000 solid;padding-top:5px;padding-bottom:5px}pre,code{background:#2d2d2d;color:#fff;border:none;font-family:fira code,monospace!important;font-weight:500}.vert-middle{vertical-align:middle}.bg-yayellow{background-color:#feda6a}.bg-deepmatte{background-color:#393f4d}.bg-darkslate{background-color:#1d1e22}.brdr-yayellow{border:2px solid #feda6a}.bg-compliment{background-color:#384667}.title-image-container{content:"";height:0;overflow:hidden;padding-top:calc(((191/2)/781 )* 100%);position:relative;background-color:#feda6a}.title-image{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover;object-position:top;border:.1rem solid #feda6a}.table-center{width:60%;margin:0 auto}.fg-red{color:red}.fg-green{color:green}.fg-yellow{color:#feda6a}.my-shadow{-webkit-box-shadow:6px 6px 15px 2px rgba(0,0,0,.75);-moz-box-shadow:6px 6px 15px 2px rgba(0,0,0,.75);box-shadow:6px 6px 15px 2px rgba(0,0,0,.75)}.my-shadow-subtle{-webkit-box-shadow:2px 2px 7px 2px rgba(0,0,0,.75);-moz-box-shadow:2px 2px 7px 2px rgba(0,0,0,.75);box-shadow:2px 2px 7px 2px rgba(0,0,0,.75)}.ribbon{width:100%;height:auto;margin-left:-10px;margin-right:-10px;background:#feda6a}#cookie-bar{position:fixed;z-index:999;bottom:0;left:0;width:100%;height:auto;background-color:rgba(0,0,0,.85);color:#fff}.twitter-hr{height:0;opacity:.75;margin-top:1vh;margin-bottom:1vh;border:1px solid #feda6a}.border-top{border-top:2px solid #1d1e22}.chroma{color:#d0d0d0;background-color:#202020}.chroma .x{}.chroma .err{color:#a61717;background-color:#e3d2d2}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block}.chroma .hl{display:block;width:100%;background-color:#ffc}.chroma .lnt{margin-right:.4em;padding:0 .4em;color:#686868}.chroma .ln{margin-right:.4em;padding:0 .4em;color:#686868}.chroma .k{color:#6ab825;font-weight:700}.chroma .kc{color:#6ab825;font-weight:700}.chroma .kd{color:#6ab825;font-weight:700}.chroma .kn{color:#6ab825;font-weight:700}.chroma .kp{color:#6ab825}.chroma .kr{color:#6ab825;font-weight:700}.chroma .kt{color:#6ab825;font-weight:700}.chroma .n{}.chroma .na{color:#bbb}.chroma .nb{color:#24909d}.chroma .bp{}.chroma .nc{color:#447fcf;text-decoration:underline}.chroma .no{color:#40ffff}.chroma .nd{color:orange}.chroma .ni{}.chroma .ne{color:#bbb}.chroma .nf{color:#447fcf}.chroma .fm{}.chroma .nl{}.chroma .nn{color:#447fcf;text-decoration:underline}.chroma .nx{}.chroma .py{}.chroma .nt{color:#6ab825;font-weight:700}.chroma .nv{color:#40ffff}.chroma .vc{}.chroma .vg{}.chroma .vi{}.chroma .vm{}.chroma .l{}.chroma .ld{}.chroma .s{color:#ed9d13}.chroma .sa{color:#ed9d13}.chroma .sb{color:#ed9d13}.chroma .sc{color:#ed9d13}.chroma .dl{color:#ed9d13}.chroma .sd{color:#ed9d13}.chroma .s2{color:#ed9d13}.chroma .se{color:#ed9d13}.chroma .sh{color:#ed9d13}.chroma .si{color:#ed9d13}.chroma .sx{color:orange}.chroma .sr{color:#ed9d13}.chroma .s1{color:#ed9d13}.chroma .ss{color:#ed9d13}.chroma .m{color:#3677a9}.chroma .mb{color:#3677a9}.chroma .mf{color:#3677a9}.chroma .mh{color:#3677a9}.chroma .mi{color:#3677a9}.chroma .il{color:#3677a9}.chroma .mo{color:#3677a9}.chroma .o{}.chroma .ow{color:#6ab825;font-weight:700}.chroma .p{}.chroma .c{color:#999;font-style:italic}.chroma .ch{color:#999;font-style:italic}.chroma .cm{color:#999;font-style:italic}.chroma .c1{color:#999;font-style:italic}.chroma .cs{color:#e50808;background-color:#520000;font-weight:700}.chroma .cp{color:#cd2828;font-weight:700}.chroma .cpf{color:#cd2828;font-weight:700}.chroma .g{}.chroma .gd{color:#d22323}.chroma .ge{font-style:italic}.chroma .gr{color:#d22323}.chroma .gh{color:#fff;font-weight:700}.chroma .gi{color:#589819}.chroma .go{color:#ccc}.chroma .gp{color:#aaa}.chroma .gs{font-weight:700}.chroma .gu{color:#fff;text-decoration:underline}.chroma .gt{color:#d22323}.chroma .gl{text-decoration:underline}.chroma .w{color:#666}.margin-left-128{margin-left:128px;}
/*# sourceMappingURL=tuxstash.css.map */@font-face{font-family:open sans;font-style:normal;font-weight:400;font-display:swap;src:local('Open Sans Regular'),local('OpenSans-Regular'),url(open-sans-v18-latin-regular.woff2)format('woff2')}figcaption{background-color:#feda6a;color:#000;border-top:2px #000 solid;padding-top:5px;padding-bottom:5px}pre,code{background:#2d2d2d;color:#fff;border:none;font-family:fira code,monospace!important;font-weight:500}.vert-middle{vertical-align:middle}.bg-yayellow{background-color:#feda6a}.bg-deepmatte{background-color:#393f4d}.bg-darkslate{background-color:#1d1e22}.brdr-yayellow{border:2px solid #feda6a}.bg-compliment{background-color:#384667}.title-image-container{content:"";height:0;overflow:hidden;padding-top:calc(((191/2)/781 )* 100%);position:relative;background-color:#feda6a}.title-image{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover;object-position:top;border:.1rem solid #feda6a}.table-center{width:60%;margin:0 auto}.fg-red{color:red}.fg-green{color:green}.fg-yellow{color:#feda6a}.my-shadow{-webkit-box-shadow:6px 6px 15px 2px rgba(0,0,0,.75);-moz-box-shadow:6px 6px 15px 2px rgba(0,0,0,.75);box-shadow:6px 6px 15px 2px rgba(0,0,0,.75)}.my-shadow-subtle{-webkit-box-shadow:2px 2px 7px 2px rgba(0,0,0,.75);-moz-box-shadow:2px 2px 7px 2px rgba(0,0,0,.75);box-shadow:2px 2px 7px 2px rgba(0,0,0,.75)}.ribbon{width:100%;height:auto;margin-left:-10px;margin-right:-10px;background:#feda6a}#cookie-bar{position:fixed;z-index:999;bottom:0;left:0;width:100%;height:auto;background-color:rgba(0,0,0,.85);color:#fff}.twitter-hr{height:0;opacity:.75;margin-top:1vh;margin-bottom:1vh;border:1px solid #feda6a}.border-top{border-top:2px solid #1d1e22}.chroma{color:#d0d0d0;background-color:#202020}.chroma .x{}.chroma .err{color:#a61717;background-color:#e3d2d2}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block}.chroma .hl{display:block;width:100%;background-color:#ffc}.chroma .lnt{margin-right:.4em;padding:0 .4em;color:#686868}.chroma .ln{margin-right:.4em;padding:0 .4em;color:#686868}.chroma .k{color:#6ab825;font-weight:700}.chroma .kc{color:#6ab825;font-weight:700}.chroma .kd{color:#6ab825;font-weight:700}.chroma .kn{color:#6ab825;font-weight:700}.chroma .kp{color:#6ab825}.chroma .kr{color:#6ab825;font-weight:700}.chroma .kt{color:#6ab825;font-weight:700}.chroma .n{}.chroma .na{color:#bbb}.chroma .nb{color:#24909d}.chroma .bp{}.chroma .nc{color:#447fcf;text-decoration:underline}.chroma .no{color:#40ffff}.chroma .nd{color:orange}.chroma .ni{}.chroma .ne{color:#bbb}.chroma .nf{color:#447fcf}.chroma .fm{}.chroma .nl{}.chroma .nn{color:#447fcf;text-decoration:underline}.chroma .nx{}.chroma .py{}.chroma .nt{color:#6ab825;font-weight:700}.chroma .nv{color:#40ffff}.chroma .vc{}.chroma .vg{}.chroma .vi{}.chroma .vm{}.chroma .l{}.chroma .ld{}.chroma .s{color:#ed9d13}.chroma .sa{color:#ed9d13}.chroma .sb{color:#ed9d13}.chroma .sc{color:#ed9d13}.chroma .dl{color:#ed9d13}.chroma .sd{color:#ed9d13}.chroma .s2{color:#ed9d13}.chroma .se{color:#ed9d13}.chroma .sh{color:#ed9d13}.chroma .si{color:#ed9d13}.chroma .sx{color:orange}.chroma .sr{color:#ed9d13}.chroma .s1{color:#ed9d13}.chroma .ss{color:#ed9d13}.chroma .m{color:#3677a9}.chroma .mb{color:#3677a9}.chroma .mf{color:#3677a9}.chroma .mh{color:#3677a9}.chroma .mi{color:#3677a9}.chroma .il{color:#3677a9}.chroma .mo{color:#3677a9}.chroma .o{}.chroma .ow{color:#6ab825;font-weight:700}.chroma .p{}.chroma .c{color:#999;font-style:italic}.chroma .ch{color:#999;font-style:italic}.chroma .cm{color:#999;font-style:italic}.chroma .c1{color:#999;font-style:italic}.chroma .cs{color:#e50808;background-color:#520000;font-weight:700}.chroma .cp{color:#cd2828;font-weight:700}.chroma .cpf{color:#cd2828;font-weight:700}.chroma .g{}.chroma .gd{color:#d22323}.chroma .ge{font-style:italic}.chroma .gr{color:#d22323}.chroma .gh{color:#fff;font-weight:700}.chroma .gi{color:#589819}.chroma .go{color:#ccc}.chroma .gp{color:#aaa}.chroma .gs{font-weight:700}.chroma .gu{color:#fff;text-decoration:underline}.chroma .gt{color:#d22323}.chroma .gl{text-decoration:underline}.chroma .w{color:#666}.margin-left-128{margin-left:128px;}

@ -41,7 +41,7 @@ function labertasche_validate_mail()
}
}
function labertasche_modal_hide()
function labertasche_modal_hide(url=null)
{
let modal = document.getElementById('labertasche-modal');
if (modal != null){
@ -49,7 +49,12 @@ function labertasche_modal_hide()
modal.classList.remove('is-active');
}
}
window.location.reload(true);
if (!modal.dataset.url) {
window.location.reload(true);
}
else{
window.location = modal.dataset.url;
}
}
function labertasche_comment_not_found()
@ -57,6 +62,7 @@ function labertasche_comment_not_found()
let modal = document.getElementById('labertasche-modal');
let modal_text = document.getElementById('labertasche-modal-text');
modal_text.innerText = "The link you followed was not valid. It either doesn't exist or was already used.";
modal.setAttribute('data-url', window.location.protocol + "//" + window.location.host)
modal.classList.add('is-active');
}
@ -65,6 +71,7 @@ function labertasche_comment_deleted()
let modal = document.getElementById('labertasche-modal');
let modal_text = document.getElementById('labertasche-modal-text');
modal_text.innerText = "Your comment has been deleted. Thank you for being here.";
modal.setAttribute('data-url', window.location.protocol + "//" + window.location.host)
modal.classList.add('is-active');
}

@ -19,16 +19,25 @@ from labertasche.models import TComments, TLocation, TEmail, TProjects
from labertasche.settings import Smileys
from secrets import compare_digest
# Blueprint
bp_comments = Blueprint("bp_comments", __name__, url_prefix='/comments')
# Route for adding new comments
@bp_comments.route("/<name>/new", methods=['POST'])
@cross_origin()
def check_and_insert_new_comment(name):
if request.method == 'POST':
# Get project
project = db.session.query(TProjects).filter(TProjects.name == name).first()
# Check refferer, this is not bullet proof
if not compare_digest(request.origin, project.blogurl):
return make_response(jsonify(status="not-allowed"), 403)
if not project:
return make_response(jsonify(status="post-project-not-found"), 400)
if compare_digest(request.method, "POST"):
smileys = Smileys()
sender = mail()
@ -57,11 +66,6 @@ def check_and_insert_new_comment(name):
content = re.sub(tags, '', new_comment['content']).strip()
content = re.sub(special, '', content).strip()
# Get project
project = db.session.query(TProjects).filter(TProjects.name == name).first()
if not project:
return make_response(jsonify(status="post-project-not-found"), 400)
# Convert smileys if enabled
if project.addon_smileys:
for key, value in smileys.smileys.items():
@ -168,11 +172,13 @@ def check_and_insert_new_comment(name):
return make_response(jsonify(status="post-internal-server-error"), 400)
export_location(t_comment.location_id)
return make_response(jsonify(status="post-success", comment_id=t_comment.comments_id), 200)
return make_response(jsonify(status="post-success",
comment_id=t_comment.comments_id,
sendotp=project.sendotp), 200)
# Route for confirming comments
@bp_comments.route("/confirm/<name>/<email_hash>", methods=['GET'])
@bp_comments.route("/<name>/confirm/<email_hash>", methods=['GET'])
@cross_origin()
def check_confirmation_link(name, email_hash):
comment = db.session.query(TComments).filter(TComments.confirmation == email_hash).first()
@ -193,7 +199,7 @@ def check_confirmation_link(name, email_hash):
# Route for deleting comments
@bp_comments.route("/delete/<name>/<email_hash>", methods=['GET'])
@bp_comments.route("<name>/delete/<email_hash>", methods=['GET'])
@cross_origin()
def check_deletion_link(name, email_hash):
project = db.session.query(TProjects).filter(TProjects.name == name).first()
@ -202,7 +208,8 @@ def check_deletion_link(name, email_hash):
if comment:
location = db.session.query(TLocation).filter(TLocation.id_location == comment.location_id).first()
if compare_digest(comment.deletion, email_hash):
comment.delete()
print("True")
db.session.delete(comment)
db.session.commit()
url = f"{project.blogurl}?deleted=true"
export_location(location.id_location)

@ -16,12 +16,28 @@ from labertasche.helper import export_location, get_id_from_project_name
@cross_origin
@bp_dashboard.route('<project>/manage-comments/', methods=["GET"])
@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()
all_locations = db.session.query(TLocation)\
.filter(TLocation.project_id == proj_id)\
.all()
# Check if there is a comment, otherwise don't show on management page
# This can happen when the last comment was deleted, the location
# won't be removed.
tmp_list = list()
for each in all_locations:
comment_count = db.session.query(TComments.comments_id)\
.filter(TComments.location_id == each.id_location)\
.filter(TComments.is_spam == False) \
.count()
if comment_count > 0:
tmp_list.append(each)
all_locations = tmp_list
# Project does not exist, error code is used by Javascript, not Flask
if proj_id == -1:
@ -46,7 +62,6 @@ def dashboard_manage_regular_comments(project: str):
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")

@ -7,28 +7,24 @@
# * _license : This project is under MIT License
# *********************************************************************************/
from . import bp_dashboard
from flask import render_template, redirect, url_for
from flask import render_template
from flask_login import login_required
from flask_cors import cross_origin
from labertasche.database import labertasche_db as db
from labertasche.models import TEmail
from labertasche.helper import get_id_from_project_name
# noinspection PyUnusedLocal
@cross_origin()
@bp_dashboard.route('<project>/manage-mail/')
@bp_dashboard.route('/manage-mail/')
@bp_dashboard.route('/<project>/manage-mail/')
@login_required
def dashboard_manage_mail(project: str):
def dashboard_manage_mail(project: str = None):
"""
Shows the panel to manage email addresses
:param project: The project name to manage
:param project: Not used
: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)
addresses = db.session.query(TEmail).all()
return render_template("manage-mail.html", addresses=addresses)

@ -7,8 +7,9 @@
# * _license : This project is under MIT License
# *********************************************************************************/
from . import bp_dashboard
from flask import render_template, redirect, url_for
from flask import render_template, redirect, url_for, request
from flask_login import login_required
from flask_cors import cross_origin
from sqlalchemy import func
from sqlalchemy.exc import OperationalError
from labertasche.database import labertasche_db as db
@ -16,6 +17,7 @@ from labertasche.models import TComments, TProjects
from labertasche.helper import get_id_from_project_name, dates_of_the_week
@cross_origin
@bp_dashboard.route("/")
@login_required
def dashboard_project_list():
@ -50,6 +52,7 @@ def dashboard_project_list():
return render_template('project-list.html', projects=projects)
@cross_origin
@bp_dashboard.route('/<project>/')
@login_required
def dashboard_project_stats(project: str):
@ -65,6 +68,17 @@ def dashboard_project_stats(project: str):
if proj_id == -1:
return redirect(url_for("bp_dashboard.dashboard_project_list", error=404))
# Total graphs
total_spam = db.session.query(TComments).filter(TComments.is_spam == True).count()
total_comments = db.session.query(TComments) \
.filter(TComments.is_spam == False)\
.filter(TComments.is_published == True).count()
total_unpublished = db.session.query(TComments).filter(TComments.is_spam == False)\
.filter(TComments.is_published == False).count()
# 7 day graph
dates = dates_of_the_week()
spam = list()
published = list()
@ -89,5 +103,7 @@ def dashboard_project_stats(project: str):
unpublished.append(len(unpub_comments))
return render_template('project-stats.html', dates=dates, spam=spam, project=project,
published=published, unpublished=unpublished)
published=published, unpublished=unpublished,
total_spam=total_spam, total_comments=total_comments,
total_unpublished=total_unpublished)

@ -28,6 +28,20 @@ def dashboard_review_spam(project: str):
proj_id = get_id_from_project_name(project)
all_locations = db.session.query(TLocation).filter(TLocation.project_id == proj_id).all()
# Check if there is a comment, otherwise don't show on management page
# This can happen when the last comment was deleted, the location
# won't be removed.
tmp_list = list()
for each in all_locations:
comment_count = db.session.query(TComments.comments_id) \
.filter(TComments.location_id == each.id_location) \
.filter(TComments.is_spam == True) \
.count()
if comment_count > 0:
tmp_list.append(each)
all_locations = tmp_list
# 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))

@ -15,17 +15,18 @@ from labertasche.helper import get_id_from_project_name
from labertasche.models import TProjects, TComments, TEmail, TLocation
from validators import url as validate_url
from pathlib import Path
from secrets import compare_digest
import re
def validate_project(project):
def validate_project(project, is_edit=False):
"""
Validates important bits of a project database entry
:param project: The json from the request, containing the data for a project.
:param is_edit: If we are updating the database, we need to know, so we don't check for dupes on urls.
:return: A response with the error or None if the project is valid.
"""
# Validate length
if not len(project['name']) and \
not len(project['blogurl']) and \
@ -34,16 +35,18 @@ def validate_project(project):
# Validate project name
if not re.match('^\\w+$', project['name']):
print(project['name'])
return make_response(jsonify(status='invalid-project-name'), 400)
# Check if project name already exists
name_check = db.session.query(TProjects.name).filter(TProjects.name == project['name']).first()
if name_check:
if name_check and not is_edit:
return make_response(jsonify(status='project-exists'), 400)
# Validate url
url_exists = db.session.query(TProjects.blogurl).filter(TProjects.blogurl == project['blogurl']).first()
# Validate existing only if we are not editing
url_exists = False
if not is_edit:
url_exists = db.session.query(TProjects.blogurl).filter(TProjects.blogurl == project['blogurl']).first()
if not validate_url(project['blogurl']) or url_exists:
return make_response(jsonify(status='invalid-blog-url'), 400)
@ -78,7 +81,12 @@ def api_create_project():
return response
try:
db.session.add(TProjects(**request.json))
new_project = request.json
# Remove trailing slash
if compare_digest(new_project['blogurl'][-1], '/'):
new_project['blogurl'] = new_project['blogurl'][:-1]
db.session.add(TProjects(**new_project))
db.session.commit()
except Exception as e:
print(str(e))
@ -97,21 +105,26 @@ def api_edit_project_name(name: str):
:param name: The previous name of the project to edit, must exist
:return: A string with an error code and 'ok' as string on success.
"""
response = validate_project(request.json)
response = validate_project(request.json, is_edit=True)
if response is not None:
return response
try:
project = db.session.query(TProjects).filter(TProjects.name == name).first()
project_json = request.json
# Remove trailing slash to streamline it
if compare_digest(project_json['blogurl'][-1], '/'):
setattr(project, "blogurl", project_json['blogurl'][:-1].strip())
setattr(project, "id_project", project.id_project)
setattr(project, "name", request.json['name'])
setattr(project, "blogurl", request.json['blogurl'].strip())
setattr(project, "output", request.json['output'].strip())
setattr(project, "sendotp", request.json['sendotp'])
setattr(project, "gravatar_cache", request.json['gravatar_cache'])
setattr(project, "gravatar_cache_dir", request.json['gravatar_cache_dir'])
setattr(project, "gravatar_size", request.json['gravatar_size'])
setattr(project, "addon_smileys", request.json['addon_smileys'])
setattr(project, "name", project_json['name'])
setattr(project, "output", project_json['output'].strip())
setattr(project, "sendotp", project_json['sendotp'])
setattr(project, "gravatar_cache", project_json['gravatar_cache'])
setattr(project, "gravatar_cache_dir", project_json['gravatar_cache_dir'])
setattr(project, "gravatar_size", project_json['gravatar_size'])
setattr(project, "addon_smileys", project_json['addon_smileys'])
db.session.commit()
except Exception as e:
print(str(e))
@ -138,7 +151,6 @@ def api_delete_project(name: str):
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()

@ -9,7 +9,7 @@
from . import bp_dbupgrades
from flask_cors import cross_origin
from flask_login import login_required
from flask import render_template, jsonify, make_response
from flask import render_template, jsonify, make_response, redirect, url_for
from pathlib import Path
from labertasche.database import labertasche_db as db
from labertasche.models import TProjects, TComments, TLocation, TEmail, TVersion
@ -36,6 +36,8 @@ def upgrade_db_to_v2():
version = db.session.query(TVersion).first()
if version:
status = True
return redirect(url_for('bp_dashboard.dashboard_project_list'))
except Exception as e:
print(e.__class__)
pass

@ -8,6 +8,7 @@
# *********************************************************************************/
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import MetaData
from sqlalchemy.pool import NullPool
# naming conventions
convention = {
@ -20,4 +21,6 @@ convention = {
metadata = MetaData(naming_convention=convention)
# Create SQLAlchemy
labertasche_db = SQLAlchemy(metadata=metadata)
labertasche_db = SQLAlchemy(metadata=metadata, engine_options={
'poolclass': NullPool
})

@ -18,6 +18,8 @@ from secrets import token_urlsafe
from labertasche.models import TProjects
from labertasche.database import labertasche_db as db
from labertasche.settings import Settings
from flask import render_template
class mail:
@ -78,22 +80,19 @@ class mail:
confirm_digest = token_urlsafe(48)
delete_digest = token_urlsafe(48)
confirm_url = f"{settings.weburl}/comments/confirm/{confirm_digest}"
delete_url = f"{settings.weburl}/comments/delete/{delete_digest}"
confirm_url = f"{settings.weburl}/comments/{project.name}/confirm/{confirm_digest}"
delete_url = f"{settings.weburl}/comments/{project.name}/delete/{delete_digest}"
txt_what = f"Hey there. You have made a comment on {project.blogurl}. Please confirm it by " \
f"copying this link into your browser:\n{confirm_url}\nIf you want to delete your comment for,"\
f"whatever reason, please use this link:\n{delete_url}"
f"copying this link into your browser:\n{confirm_url}\n" \
f"If you want to delete your comment for whatever reason, please use this link:\n{delete_url}"
html_what = f"Hey there. You have made a comment on {project.blogurl}.<br>Please confirm it by " \
f"clicking on this <a href='{confirm_url}'>link</a>.<br>"\
f"In case you want to delete your comment later, please click <a href='{delete_url}'>here</a>."\
f"<br><br>If you think this is in error or someone made this comment in your name, please "\
f"write me a <a href='mailto:contact@tuxstash.de'>mail</a> to discuss options such as " \
f"blocking your mail from being used."
html_what = render_template("comment_confirmation.html",
blogurl=project.blogurl,
confirmation_url=confirm_url,
deletion_url=delete_url)
self.send(txt_what, html_what, email)
return confirm_digest, delete_digest
def validate(self, addr):

@ -22,4 +22,3 @@ class TEmail(db.Model):
email = db.Column(db.Integer, unique=True)
is_blocked = db.Column(db.Boolean)
is_allowed = db.Column(db.Boolean)
project_id = db.Column(db.Integer, ForeignKey('t_projects.id_project'), nullable=False)

@ -7,7 +7,7 @@
# * _license : This project is under MIT License
# *********************************************************************************/
from labertasche.database import labertasche_db as db
from sqlalchemy import ForeignKey
from sqlalchemy import ForeignKey, UniqueConstraint
class TLocation(db.Model):
@ -19,5 +19,8 @@ class TLocation(db.Model):
id_location = db.Column(db.Integer, primary_key=True, autoincrement=True)
# data
location = db.Column(db.Text, nullable=False, unique=True)
location = db.Column(db.Text, nullable=False)
project_id = db.Column(db.Integer, ForeignKey('t_projects.id_project'), nullable=False)
# Unique constraint
UniqueConstraint('location', 'project_id', name="unique_per_project")

@ -155,6 +155,18 @@ async function show_modal_with_project(id_name, proj_name)
document.getElementById('edit-project-gravatar-size').value = r['gravatar_size'];
document.getElementById('edit-project-send-otp').checked = r['sendotp'];
document.getElementById('edit-project-addons-smileys').checked = r['addon_smileys'];
let cache = document.getElementById('edit-project-gravatar-cache-dir');
let size = document.getElementById('edit-project-gravatar-size');
if(!r['gravatar_cache']){
cache.setAttribute('disabled', '');
size.setAttribute('disabled', '');
cache.placeholder = "disabled";
cache.value = "";
size.placeholder = "disabled";
size.value = "";
}
});
// Set project name
@ -253,8 +265,10 @@ function toggle_gravatar_settings(chkbx)
if(!chkbx.checked){
cache.setAttribute('disabled', '');
size.setAttribute('disabled', '');
cache.value = "disabled";
size.value = "disabled";
cache.placeholder = "disabled";
cache.value = "";
size.placeholder = "disabled";
size.value = "";
}
else{
cache.removeAttribute('disabled');

@ -0,0 +1,40 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="{{ i18n['html_language'] }}">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Your comment on {{ blogurl }}</title>
</head>
<body style="background-color: #393f4d;color:white;width:70%;margin:0 auto;">
<table border="0" cellpadding="0" cellspacing="0" width='100%'>
<tr>
<td style="background-color:#feda6a;font-size:1.3em;color:black;padding:2%;">
<h1>Your comment on {{ blogurl }}</h1>
</td>
</tr>
<tr>
<td>
<p style="font-size: 1.2em;line-height: 1.3em;padding-left:2%;">
Hey there, <br><br>
You are receiving this mail, because you have recently made a comment on {{ blogurl }}.
<br><br>
If you wish to publish your comment, please click this <a style="color:#feda6a;" href="{{ confirmation_url }}">link</a>.
<br>
If you wish to delete your comment, please click this <a style="color:#feda6a;" href="{{ deletion_url }}">link</a>.
<br><br>
If this was in error or if someone used your mail address without your knowledge, please contact the site
adminitrator at {{ blogurl }}.
</p>
</td>
</tr>
<tr>
<td style="border-top: 1px dashed #feda6a;padding-left:2%;">
<p style="font-size: 0.8em">
Powered by <a style="color:#feda6a;" href="https://labertasche.tuxstash.de">Labertasche</a>,
&nbsp;licensed via MIT license.
</p>
<td>
</tr>
</table>
</body>

@ -1,11 +1,13 @@
{% extends "base.html" %}
{% block main %}
<div style="min-height: 100vh;" class="container bg-deepmatte p-6 brdr-yayellow">
{% if action == "spam" %}
<h1 class="title has-text-white has-text-centered">{{ i18n['manage_spam'] }}</h1>
{% else %}
<h1 class="title has-text-white has-text-centered">{{ i18n['manage_comments'] }}</h1>
{% endif %}
<h1 class="title has-text-white has-text-centered is-capitalized">
{% if action == "spam" %}
{{ i18n['manage_spam'] }}
{% else %}
{{ i18n['manage_comments'] }}
{% endif %}
</h1>
<div class="field">
<form method="GET" action="/dashboard/{{ project }}/manage-{{action}}/">
<div class="control">

@ -28,22 +28,12 @@
let total_spam = parseInt(chart_total.dataset.spam);
let total_comments = parseInt(chart_total.dataset.comments);
let total_unpublished = parseInt(chart_total.dataset.unpublished);
let total = (total_spam + total_comments + total_unpublished);
let spam_perc = (total_spam/total) * 100;
let comm_perc = (total_comments/total) * 100;
let unpub_perc = (total_unpublished/total) * 100;
console.log(total);
console.log(spam_perc);
console.log(comm_perc);
console.log(unpub_perc);
new Chart(document.getElementById('chart-total'), {
type: "pie",
data: {
datasets: [{
data: [spam_perc, comm_perc, unpub_perc],
data: [total_spam, total_comments, total_unpublished],
backgroundColor: [
"rgba(182, 106, 254, 0.8)",
"rgba(254, 218, 106, 0.8)",

Loading…
Cancel
Save