diff --git a/__implementation_example/static/css/labertasche.css b/__implementation_example/static/css/labertasche.css index 9d0872f..ce3328a 100644 --- a/__implementation_example/static/css/labertasche.css +++ b/__implementation_example/static/css/labertasche.css @@ -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;} \ No newline at end of file +/*# 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;} diff --git a/__implementation_example/static/js/mysite.js b/__implementation_example/static/js/mysite.js index 9ce5c19..8b81733 100644 --- a/__implementation_example/static/js/mysite.js +++ b/__implementation_example/static/js/mysite.js @@ -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'); } diff --git a/labertasche/blueprints/bp_comments/__init__.py b/labertasche/blueprints/bp_comments/__init__.py index 9dda7c4..2e217b0 100644 --- a/labertasche/blueprints/bp_comments/__init__.py +++ b/labertasche/blueprints/bp_comments/__init__.py @@ -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("//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//", methods=['GET']) +@bp_comments.route("//confirm/", 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//", methods=['GET']) +@bp_comments.route("/delete/", 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) diff --git a/labertasche/blueprints/bp_dashboard/comments.py b/labertasche/blueprints/bp_dashboard/comments.py index 6b2170f..ac8cb31 100644 --- a/labertasche/blueprints/bp_dashboard/comments.py +++ b/labertasche/blueprints/bp_dashboard/comments.py @@ -16,12 +16,28 @@ from labertasche.helper import export_location, get_id_from_project_name @cross_origin -@bp_dashboard.route('/manage-comments/', methods=["GET"]) +@bp_dashboard.route('//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") diff --git a/labertasche/blueprints/bp_dashboard/mail.py b/labertasche/blueprints/bp_dashboard/mail.py index fccc153..e790b79 100644 --- a/labertasche/blueprints/bp_dashboard/mail.py +++ b/labertasche/blueprints/bp_dashboard/mail.py @@ -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('/manage-mail/') +@bp_dashboard.route('/manage-mail/') +@bp_dashboard.route('//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) diff --git a/labertasche/blueprints/bp_dashboard/projects.py b/labertasche/blueprints/bp_dashboard/projects.py index 0045986..6b1c42b 100644 --- a/labertasche/blueprints/bp_dashboard/projects.py +++ b/labertasche/blueprints/bp_dashboard/projects.py @@ -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('//') @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) diff --git a/labertasche/blueprints/bp_dashboard/spam.py b/labertasche/blueprints/bp_dashboard/spam.py index 36b8a14..ed84bbb 100644 --- a/labertasche/blueprints/bp_dashboard/spam.py +++ b/labertasche/blueprints/bp_dashboard/spam.py @@ -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)) diff --git a/labertasche/blueprints/bp_jsconnector/projects.py b/labertasche/blueprints/bp_jsconnector/projects.py index 582bccd..9b31ceb 100644 --- a/labertasche/blueprints/bp_jsconnector/projects.py +++ b/labertasche/blueprints/bp_jsconnector/projects.py @@ -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() diff --git a/labertasche/blueprints/bp_upgrades/db_v2.py b/labertasche/blueprints/bp_upgrades/db_v2.py index 1c1ae7b..db052c2 100644 --- a/labertasche/blueprints/bp_upgrades/db_v2.py +++ b/labertasche/blueprints/bp_upgrades/db_v2.py @@ -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 diff --git a/labertasche/database/__init__.py b/labertasche/database/__init__.py index 3f447f5..049e3aa 100644 --- a/labertasche/database/__init__.py +++ b/labertasche/database/__init__.py @@ -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 +}) diff --git a/labertasche/mail/__init__.py b/labertasche/mail/__init__.py index a49f9fd..fdb4967 100644 --- a/labertasche/mail/__init__.py +++ b/labertasche/mail/__init__.py @@ -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}.
Please confirm it by " \ - f"clicking on this link.
"\ - f"In case you want to delete your comment later, please click here."\ - f"

If you think this is in error or someone made this comment in your name, please "\ - f"write me a mail 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): diff --git a/labertasche/models/t_emails.py b/labertasche/models/t_emails.py index 49ed6af..e589bf1 100644 --- a/labertasche/models/t_emails.py +++ b/labertasche/models/t_emails.py @@ -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) diff --git a/labertasche/models/t_location.py b/labertasche/models/t_location.py index 6b17f18..264f6a1 100644 --- a/labertasche/models/t_location.py +++ b/labertasche/models/t_location.py @@ -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") diff --git a/static/js/dashboard.js b/static/js/dashboard.js index 396e950..e5b8962 100644 --- a/static/js/dashboard.js +++ b/static/js/dashboard.js @@ -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'); diff --git a/templates/comment_confirmation.html b/templates/comment_confirmation.html new file mode 100644 index 0000000..aac523e --- /dev/null +++ b/templates/comment_confirmation.html @@ -0,0 +1,40 @@ + + + + + + Your comment on {{ blogurl }} + + + + + + + + + + + +
+

Your comment on {{ blogurl }}

+
+

+ Hey there,

+ You are receiving this mail, because you have recently made a comment on {{ blogurl }}. +

+ If you wish to publish your comment, please click this link. +
+ If you wish to delete your comment, please click this link. +

+ If this was in error or if someone used your mail address without your knowledge, please contact the site + adminitrator at {{ blogurl }}. +

+
+

+ Powered by Labertasche, +  licensed via MIT license. +

+
+
+ diff --git a/templates/manage-comments.html b/templates/manage-comments.html index 0cf713b..0a4f000 100644 --- a/templates/manage-comments.html +++ b/templates/manage-comments.html @@ -1,11 +1,13 @@ {% extends "base.html" %} {% block main %}
- {% if action == "spam" %} -

{{ i18n['manage_spam'] }}

- {% else %} -

{{ i18n['manage_comments'] }}

- {% endif %} +

+ {% if action == "spam" %} + {{ i18n['manage_spam'] }} + {% else %} + {{ i18n['manage_comments'] }} + {% endif %} +

diff --git a/templates/project-stats.html b/templates/project-stats.html index 6a9a52b..b684eee 100644 --- a/templates/project-stats.html +++ b/templates/project-stats.html @@ -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)",