diff --git a/Pipfile b/Pipfile
index 1ac692e..1210db9 100644
--- a/Pipfile
+++ b/Pipfile
@@ -15,6 +15,7 @@ flask-login = "*"
sqlalchemy = "*"
requests = "*"
py3-validate-email = "*"
+flask-migrate = "*"
[requires]
python_version = "3.8"
diff --git a/Pipfile.lock b/Pipfile.lock
index 8c7a9d0..7b9ea1d 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "57134ef6f8a30aa46c1ab6263e62e14edbb27d6df2911fc6b2140dde8c49d27c"
+ "sha256": "bda9276f38dcb49704cadb2f9097ecdfa1dafdd4e4b3d6666dfcb92d24f0ea57"
},
"pipfile-spec": 6,
"requires": {
@@ -16,6 +16,13 @@
]
},
"default": {
+ "alembic": {
+ "hashes": [
+ "sha256:4e02ed2aa796bd179965041afa092c55b51fb077de19d61835673cc80672c01c",
+ "sha256:5334f32314fb2a56d86b4c4dd1ae34b08c03cae4cb888bc699942104d66bc245"
+ ],
+ "version": "==1.4.3"
+ },
"antispam": {
"hashes": [
"sha256:e188b424ea9b76c408a592a5ff60eb1280f45f26b404db4d5e96123f485de39b"
@@ -82,6 +89,14 @@
"index": "pypi",
"version": "==0.5.0"
},
+ "flask-migrate": {
+ "hashes": [
+ "sha256:4dc4a5cce8cbbb06b8dc963fd86cf8136bd7d875aabe2d840302ea739b243732",
+ "sha256:a69d508c2e09d289f6e55a417b3b8c7bfe70e640f53d2d9deb0d056a384f37ee"
+ ],
+ "index": "pypi",
+ "version": "==2.5.3"
+ },
"flask-sqlalchemy": {
"hashes": [
"sha256:05b31d2034dd3f2a685cbbae4cfc4ed906b2a733cff7964ada450fd5e462b84e",
@@ -111,6 +126,13 @@
],
"version": "==2.11.2"
},
+ "mako": {
+ "hashes": [
+ "sha256:8195c8c1400ceb53496064314c6736719c6f25e7479cd24c77be3d9361cddc27",
+ "sha256:93729a258e4ff0747c876bd9e20df1b9758028946e976324ccd2d68245c7b6a9"
+ ],
+ "version": "==1.1.3"
+ },
"markupsafe": {
"hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
@@ -151,21 +173,38 @@
},
"py3-validate-email": {
"hashes": [
- "sha256:3bbb264b49c0ae09afdb2738956f00b0e8dd7e079e2d079b2e9b6688de474d28"
+ "sha256:e5815a929c064face7b6e775f290f157ab52c1c88d56d27d031a02a185b991e3"
],
"index": "pypi",
- "version": "==0.2.10"
+ "version": "==0.2.12"
+ },
+ "python-dateutil": {
+ "hashes": [
+ "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
+ "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
+ ],
+ "version": "==2.8.1"
+ },
+ "python-editor": {
+ "hashes": [
+ "sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d",
+ "sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b",
+ "sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8"
+ ],
+ "version": "==1.0.4"
},
"pyyaml": {
"hashes": [
"sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
"sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
"sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
+ "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e",
"sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
"sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
"sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
"sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
"sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
+ "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a",
"sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
"sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
diff --git a/README.md b/README.md
index 941d5f9..50ef115 100644
--- a/README.md
+++ b/README.md
@@ -83,7 +83,7 @@ Once you can see the administrative page, you can start integrating it into Hugo
In the project folder is a small javascript file. You will need to add this to Hugo. I suggest using Hugo's asset
pipeline to integrate it into your site and merge it with your current javascript.
One thing is important to know: this script only does the bare bones post request to the comment backend.
-Anything else must be done by yourself, such as messages about minimum length etc.
+Any frontend work must be done by yourself, such as messages about minimum length etc.
But don't worry: The function is making use of a callback, where you can receive various messages with error codes
and act on them. See the javascript file for an example callback.
@@ -101,8 +101,12 @@ Within that template the following structure is needed:
{{ if (fileExists $location ) }}
{{ $dataJ := getJSON $location }}
{{ range $dataJ.comments }}
-
+ {# HTML and template codes here #}
{{ end }}
+ {# This is to display replies to this comment, you can use them same variables #}
+ {{ range where $dataJ.replies "replied_to" .comment_id }}
+ {# HTML and template codes here for replies #}
+ {{end}}
{{ end }}
```
@@ -128,6 +132,17 @@ Here is a base skeleton to start out:
```
+This is the recommended element on each top post if you want to utilize replies:
+
+```
+
+```
+
Please take note of the `id` on each element, these are mandatory, as well as the function call for the `onclick` event.
Again, style as needed and add more Javascript to your gusto. Make sure to implement the callback, otherwise the
Javascript will crash. The `data-remote=` needs to have the URL where you host this program, as well as the path to
diff --git a/js/labertasche.js b/js/labertasche.js
index 6e6bcd1..74f5270 100644
--- a/js/labertasche.js
+++ b/js/labertasche.js
@@ -6,17 +6,15 @@
// *********************************************************************************/
/*
- Callback example.
- Possible messages:
-
- post-min-length
- post-max-length
- post-invalid-json
- post-duplicate
- post-internal-server-error
- post-success
- post-before-fetch
+//Callback example for post. Possible messages:
+// post-min-length
+// post-max-length
+// post-invalid-json
+// post-duplicate
+// post-internal-server-error
+// post-success
+// post-before-fetch
function labertasche_callback(state)
{
if (state === "post-before-fetch"){
@@ -35,20 +33,33 @@ function labertasche_callback(state)
}
}
+
+// Callback for initiating and cancelling replies.
+// Posstible message: 'on' and 'off'
+function labertasche_reply_callback()
+{
+ if (state === "on"){
+ }
+
+ if (state === "off"){
+ }
+}
+
*/
function labertasche_post_comment(btn, callback)
{
let remote = document.getElementById('labertasche-comment-section').dataset.remote;
- let comment = document.getElementById('labertasche-text').value;
- let mail = document.getElementById('labertasche-mail').value;
+ let comment = document.getElementById('labertasche-text').value.trim();
+ let mail = document.getElementById('labertasche-mail').value.trim();
+ let reply = document.getElementById('labertasche-replied-to');
if (mail.length <= 0 || comment.length < 40){
callback('post-min-length');
if(btn) {
- btn.preventDefault();
+ return false;
}
- return
+ return false;
}
callback('post-before-fetch');
@@ -65,7 +76,7 @@ function labertasche_post_comment(btn, callback)
body: JSON.stringify({ "email": mail,
"content": comment,
"location": window.location.pathname,
- "replied_to": null // TODO: future feature: replies?
+ "replied_to": reply.value
})
})
.then(async function(response){
diff --git a/labertasche/blueprints/bp_comments.py b/labertasche/blueprints/bp_comments.py
index 2583706..bd5a557 100644
--- a/labertasche/blueprints/bp_comments.py
+++ b/labertasche/blueprints/bp_comments.py
@@ -64,11 +64,21 @@ def check_and_insert_new_comment():
for key, value in smileys.items():
content = content.replace(key, value)
+ # Validate replied_to field is integer
+ replied_to = new_comment['replied_to']
+ try:
+ if replied_to:
+ replied_to = int(replied_to)
+
+ # not a valid id at all
+ except ValueError:
+ return make_response(jsonify(status="bad-reply"), 400)
+
# Update values
new_comment.update({"content": content})
new_comment.update({"email": new_comment['email'].strip()})
new_comment.update({"location": location})
- new_comment.update({"replied_to": None}) # Not (yet?) implemented
+ new_comment.update({"replied_to": replied_to})
# Check mail
if not sender.validate(new_comment['email']):
@@ -92,6 +102,9 @@ def check_and_insert_new_comment():
if not email.is_allowed:
is_spam = True
if email.is_allowed:
+ # This forces the comment to be not spam if the address is in the allowed list,
+ # but the commenter will still need to confirm it to avoid brute
+ # force attacks against this feature
is_spam = False
# Look for location
@@ -99,7 +112,7 @@ def check_and_insert_new_comment():
.filter(TLocation.location == new_comment['location'])
if loc_query.first():
- # Set existing location id
+ # Location exists, set existing location id
new_comment.update({'location_id': loc_query.first().id_location})
# TComments does not have this field
new_comment.pop("location")
@@ -143,7 +156,9 @@ def check_and_insert_new_comment():
except Exception as e: # 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)
export_location(t_comment.location_id)
diff --git a/labertasche/helper/__init__.py b/labertasche/helper/__init__.py
index 7a78e9f..2cd3199 100644
--- a/labertasche/helper/__init__.py
+++ b/labertasche/helper/__init__.py
@@ -145,16 +145,18 @@ def export_location(location_id: int) -> bool:
loc_query = db.session.query(TLocation).filter(TLocation.id_location == location_id).first()
if loc_query:
- print(f"has loc_query")
comments = db.session.query(TComments).filter(TComments.is_spam != True) \
- .filter(TComments.is_published == True) \
- .filter(TComments.location_id == loc_query.id_location) \
- .filter(TComments.replied_to == None)
+ .filter(TComments.is_published == True) \
+ .filter(TComments.location_id == loc_query.id_location)
bundle = {
- "comments": []
+ "comments": [],
+ "replies": []
}
for comment in comments:
+ if comment.replied_to is not None:
+ bundle["replies"].append(alchemy_query_to_dict(comment))
+ continue
bundle['comments'].append(alchemy_query_to_dict(comment))
path_loc = re_match(".*(?=/)", loc_query.location)[0]
@@ -162,12 +164,10 @@ def export_location(location_id: int) -> bool:
system = Settings().system
out = Path(f"{system['output']}/{path_loc}.json")
out = out.absolute()
- print(out)
folder = out.parents[0]
folder.mkdir(parents=True, exist_ok=True)
with out.open('w') as fp:
json.dump(bundle, fp)
- print(bundle)
return True
diff --git a/labertasche/mail/__init__.py b/labertasche/mail/__init__.py
index b49b29e..3ac951f 100644
--- a/labertasche/mail/__init__.py
+++ b/labertasche/mail/__init__.py
@@ -14,7 +14,7 @@ from platform import system
from smtplib import SMTP_SSL, SMTPHeloError, SMTPAuthenticationError, SMTPException
from ssl import create_default_context
from labertasche.settings import Settings
-from validate_email import validate_email
+from validate_email import validate_email_or_fail
from secrets import token_urlsafe
@@ -91,10 +91,6 @@ class mail:
def validate(self, addr):
# validate email
- is_valid = validate_email(email_address=addr,
- check_regex=True,
- check_mx=False,
- dns_timeout=10,
- use_blacklist=True,
- debug=False)
+ is_valid = validate_email_or_fail(email_address=addr, check_regex=True, check_mx=False,
+ dns_timeout=10, use_blacklist=True, debug=False)
return is_valid
diff --git a/labertasche/models/t_comments.py b/labertasche/models/t_comments.py
index 8c8e1f9..e2a23a1 100644
--- a/labertasche/models/t_comments.py
+++ b/labertasche/models/t_comments.py
@@ -28,7 +28,7 @@ class TComments(db.Model):
is_published = db.Column(db.Boolean, nullable=False)
is_spam = db.Column(db.Boolean, nullable=False)
spam_score = db.Column(db.Float, nullable=False)
- replied_to = db.Column(db.Boolean, nullable=True)
+ replied_to = db.Column(db.Integer, ForeignKey('t_comments.comments_id'), nullable=True)
confirmation = db.Column(db.Text, nullable=True)
deletion = db.Column(db.Text, nullable=True)
gravatar = db.Column(db.Text, nullable=True)
diff --git a/server.py b/server.py
index 1b93525..09c9d79 100644
--- a/server.py
+++ b/server.py
@@ -17,6 +17,7 @@ from labertasche.database import labertasche_db
from labertasche.blueprints import bp_comments, bp_login, bp_dashboard
from labertasche.helper import User
from flask_login import LoginManager
+from flask_migrate import Migrate
# Load settings
@@ -33,6 +34,9 @@ laberflask.config.update(dict(
SQLALCHEMY_TRACK_MODIFICATIONS=False
))
+# flask migrate
+migrate = Migrate(laberflask, labertasche_db, render_as_batch=True)
+
# CORS
CORS(laberflask, resources={r"/comments": {"origins": settings.system['blog_url']}})