pull/6/head
Yax 6 years ago
parent ed2a284102
commit 0a2cdbbe8f

@ -38,6 +38,12 @@ Stacosys can be hosted on the same server or on a different server than the blog
- [Peewee ORM](http://docs.peewee-orm.com) - [Peewee ORM](http://docs.peewee-orm.com)
- [Markdown](http://daringfireball.net/projects/markdown) - [Markdown](http://daringfireball.net/projects/markdown)
### Installation
Python 3.7
pip libs: flask peewee pyrss2gen markdown clize flask-apscheduler profig
### Ways of improvement ### Ways of improvement
Current version of Stacosys fits my needs and it serves comments on [my blog](https://blogduyax.madyanne.fr). However Stacosys has been designed to serve several blogs and e-mail can be a constraint for some people. So an area of improvement would be to add an administration UI to configure sites, approve or reject comments, keep track of usage statistics and get rid of e-mails. I encourage you to fork the project and create such improvements if you need them. Current version of Stacosys fits my needs and it serves comments on [my blog](https://blogduyax.madyanne.fr). However Stacosys has been designed to serve several blogs and e-mail can be a constraint for some people. So an area of improvement would be to add an administration UI to configure sites, approve or reject comments, keep track of usage statistics and get rid of e-mails. I encourage you to fork the project and create such improvements if you need them.

@ -1,3 +0,0 @@
from flask import Flask
app = Flask(__name__)

@ -1,3 +1,48 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import profig
# constants
FLASK_APP = "flask.app"
DB_URL = "main.db_url"
HTTP_HOST = "http.host"
HTTP_PORT = "http.port"
SECURITY_SALT = "security.salt"
SECURITY_SECRET = "security.secret"
MAIL_POLLING = "polling.newmail"
COMMENT_POLLING = "polling.newcomment"
# variable
params = dict()
def initialize(config_pathname, flask_app):
cfg = profig.Config(config_pathname)
cfg.sync()
params.update(cfg)
params.update({FLASK_APP: flask_app})
def get(key):
return params[key]
def getInt(key):
return int(params[key])
def _str2bool(v):
return v.lower() in ("yes", "true", "t", "1")
def getBool(key):
return _str2bool(params[key])
def flaskapp():
return params[FLASK_APP]

@ -1,115 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Created with https://app.quicktype.io
# name: stacosys
json_schema = """
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$ref": "#/definitions/Welcome",
"definitions": {
"Welcome": {
"type": "object",
"additionalProperties": false,
"properties": {
"general": {
"$ref": "#/definitions/General"
},
"http": {
"$ref": "#/definitions/HTTP"
},
"security": {
"$ref": "#/definitions/Security"
},
"rss": {
"$ref": "#/definitions/RSS"
}
},
"required": [
"general",
"http",
"rss",
"security"
],
"title": "Welcome"
},
"General": {
"type": "object",
"additionalProperties": false,
"properties": {
"debug": {
"type": "boolean"
},
"lang": {
"type": "string"
},
"db_url": {
"type": "string"
}
},
"required": [
"db_url",
"debug",
"lang"
],
"title": "General"
},
"HTTP": {
"type": "object",
"additionalProperties": false,
"properties": {
"root_url": {
"type": "string"
},
"host": {
"type": "string"
},
"port": {
"type": "integer"
}
},
"required": [
"host",
"port",
"root_url"
],
"title": "HTTP"
},
"RSS": {
"type": "object",
"additionalProperties": false,
"properties": {
"proto": {
"type": "string"
},
"file": {
"type": "string"
}
},
"required": [
"file",
"proto"
],
"title": "RSS"
},
"Security": {
"type": "object",
"additionalProperties": false,
"properties": {
"salt": {
"type": "string"
},
"secret": {
"type": "string"
}
},
"required": [
"salt",
"secret"
],
"title": "Security"
}
}
}
"""

@ -1,84 +0,0 @@
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import os
import sys
import logging
from flask import Flask
from conf import config
from jsonschema import validate
from flask_apscheduler import APScheduler
app = Flask(__name__)
# add current path and parent path to syspath
current_path = os.path.dirname(__file__)
parent_path = os.path.abspath(os.path.join(current_path, os.path.pardir))
paths = [current_path, parent_path]
for path in paths:
if path not in sys.path:
sys.path.insert(0, path)
# more imports
import database
import processor
from interface import api
from interface import form
# configure logging
def configure_logging(level):
root_logger = logging.getLogger()
root_logger.setLevel(level)
ch = logging.StreamHandler()
ch.setLevel(level)
# create formatter
formatter = logging.Formatter(
'[%(asctime)s] %(name)s %(levelname)s %(message)s')
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
root_logger.addHandler(ch)
logging_level = (20, 10)[config.general['debug']]
configure_logging(logging_level)
logger = logging.getLogger(__name__)
class Config(object):
JOBS = [
{
'id': 'fetch_mail',
'func': 'core.cron:fetch_mail_answers',
'trigger': 'interval',
'seconds': 120
},
{
'id': 'submit_new_comment',
'func': 'core.cron:submit_new_comment',
'trigger': 'interval',
'seconds': 60
},
]
# initialize database
database.setup()
# start processor
template_path = os.path.abspath(os.path.join(current_path, '../templates'))
processor.start(template_path)
# cron
app.config.from_object(Config())
scheduler = APScheduler()
scheduler.init_app(app)
scheduler.start()
# tune logging level
if not config.general['debug']:
logging.getLogger('werkzeug').level = logging.WARNING
logger.info("Start Stacosys application")
app.run(host=config.http['host'],
port=config.http['port'],
debug=config.general['debug'], use_reloader=False)

@ -3,26 +3,29 @@
import logging import logging
import time import time
from core import app from core import mailer
from core import processor from core import templater
from models.comment import Comment from model.comment import Comment
from model.comment import Site
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def fetch_mail_answers(): def fetch_mail_answers():
logger.info('DEBUT POP MAIL') logger.info("DEBUT POP MAIL")
time.sleep(80) time.sleep(80)
logger.info('FIN POP MAIL') logger.info("FIN POP MAIL")
#data = request.get_json() # data = request.get_json()
#logger.debug(data) # logger.debug(data)
# processor.enqueue({'request': 'new_mail', 'data': data})
#processor.enqueue({'request': 'new_mail', 'data': data})
def submit_new_comment(): def submit_new_comment():
for comment in Comment.select().where(Comment.notified.is_null()): for comment in Comment.select().where(Comment.notified.is_null()):
# render email body template
comment_list = ( comment_list = (
"author: %s" % comment.author_name, "author: %s" % comment.author_name,
"site: %s" % comment.author_site, "site: %s" % comment.author_site,
@ -33,15 +36,10 @@ def submit_new_comment():
"", "",
) )
comment_text = "\n".join(comment_list) comment_text = "\n".join(comment_list)
email_body = get_template("new_comment").render(url=url, comment=comment_text) email_body = templater.get_template("new_comment").render(url=comment.url, comment=comment_text)
if clientip:
client_ips[comment.id] = clientip
# send email
subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, token)
mailer.send_mail(site.admin_email, subject, email_body)
logger.debug("new comment processed ")
def get_template(name): site = Site.select().where(Site.id == Comment.site).get()
return env.get_template(config.general["lang"] + "/" + name + ".tpl") # send email
subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, site.token)
mailer.send_mail(site.admin_email, subject, email_body)
logger.debug("new comment processed ")

@ -2,26 +2,15 @@
# -*- coding: UTF-8 -*- # -*- coding: UTF-8 -*-
from conf import config from conf import config
import functools
from playhouse.db_url import connect from playhouse.db_url import connect
def get_db(): def get_db():
return connect(config.general['db_url']) return connect(config.get(config.DB_URL))
def provide_db(func): def setup():
from model.site import Site
from model.comment import Comment
@functools.wraps(func) get_db().create_tables([Site, Comment], safe=True)
def new_function(*args, **kwargs):
return func(get_db(), *args, **kwargs)
return new_function
@provide_db
def setup(db):
from models.site import Site
from models.comment import Comment
db.create_tables([Site, Comment], safe=True)

@ -10,10 +10,8 @@ import json
from datetime import datetime from datetime import datetime
from threading import Thread from threading import Thread
from queue import Queue from queue import Queue
from jinja2 import Environment from model.site import Site
from jinja2 import FileSystemLoader from model.comment import Comment
from models.site import Site
from models.comment import Comment
from helpers.hashing import md5 from helpers.hashing import md5
from conf import config from conf import config
from core import mailer from core import mailer

@ -0,0 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
from jinja2 import Environment
from jinja2 import FileSystemLoader
from conf import config
current_path = os.path.dirname(__file__)
template_path = os.path.abspath(os.path.join(current_path, "../templates"))
env = Environment(loader=FileSystemLoader(template_path))
def get_template(name):
return env.get_template(config.general["lang"] + "/" + name + ".tpl")

@ -6,7 +6,7 @@ from conf import config
def salt(value): def salt(value):
string = '%s%s' % (value, config.security['salt']) string = "%s%s" % (value, config.get(config.SECURITY_SALT))
dk = hashlib.sha256(string.encode()) dk = hashlib.sha256(string.encode())
return dk.hexdigest() return dk.hexdigest()

@ -3,13 +3,13 @@
import logging import logging
from flask import request, jsonify, abort from flask import request, jsonify, abort
from core import app from model.site import Site
from models.site import Site from model.comment import Comment
from models.comment import Comment from conf import config
from core import processor from core import processor
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
app = config.flaskapp()
@app.route("/ping", methods=['GET']) @app.route("/ping", methods=['GET'])
def ping(): def ping():

@ -4,13 +4,13 @@
import logging import logging
from datetime import datetime from datetime import datetime
from flask import request, abort, redirect from flask import request, abort, redirect
from core import app from model.site import Site
from models.site import Site from model.comment import Comment
from models.comment import Comment from conf import config
from helpers.hashing import md5 from helpers.hashing import md5
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
app = config.flaskapp()
@app.route("/newcomment", methods=["POST"]) @app.route("/newcomment", methods=["POST"])
def new_form_comment(): def new_form_comment():

@ -6,7 +6,7 @@ from peewee import CharField
from peewee import TextField from peewee import TextField
from peewee import DateTimeField from peewee import DateTimeField
from peewee import ForeignKeyField from peewee import ForeignKeyField
from models.site import Site from model.site import Site
from core.database import get_db from core.database import get_db

@ -1,36 +1,90 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: UTF-8 -*- # -*- coding: UTF-8 -*-
import os
import logging import logging
import json
from clize import Clize, run from clize import Clize, run
from jsonschema import validate from flask import Flask
from conf import config, schema from flask_apscheduler import APScheduler
from conf import config
# configure logging
def configure_logging(level):
root_logger = logging.getLogger()
root_logger.setLevel(level)
ch = logging.StreamHandler()
ch.setLevel(level)
# create formatter
formatter = logging.Formatter("[%(asctime)s] %(name)s %(levelname)s %(message)s")
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
root_logger.addHandler(ch)
def load_json(filename):
jsondoc = None class JobConfig(object):
with open(filename, 'rt') as json_file:
jsondoc = json.loads(json_file.read()) JOBS = []
return jsondoc
def __init__(self, mail_polling_seconds, new_comment_polling_seconds):
self.JOBS = [
{
"id": "fetch_mail",
"func": "core.cron:fetch_mail_answers",
"trigger": "interval",
"seconds": mail_polling_seconds,
},
{
"id": "submit_new_comment",
"func": "core.cron:submit_new_comment",
"trigger": "interval",
"seconds": new_comment_polling_seconds,
},
]
@Clize @Clize
def stacosys_server(config_pathname): def stacosys_server(config_pathname):
# load and validate startup config app = Flask(__name__)
conf = load_json(config_pathname) config.initialize(config_pathname, app)
json_schema = json.loads(schema.json_schema)
validate(conf, json_schema) # configure logging
logger = logging.getLogger(__name__)
configure_logging(logging.INFO)
logging.getLogger("werkzeug").level = logging.WARNING
# initialize database
from core import database
database.setup()
# start processor
from core import processor
# cron email fetcher
app.config.from_object(
JobConfig(
config.getInt(config.MAIL_POLLING), config.getInt(config.COMMENT_POLLING)
)
)
scheduler = APScheduler()
scheduler.init_app(app)
scheduler.start()
logger.info("Start Stacosys application")
# start Flask
from interface import api
from interface import form
# set configuration app.run(
config.general = conf['general'] host=config.get(config.HTTP_HOST),
config.http = conf['http'] port=config.get(config.HTTP_PORT),
config.security = conf['security'] debug=False,
config.rss = conf['rss'] use_reloader=False,
)
# start application
from core import app
if __name__ == '__main__': if __name__ == "__main__":
run(stacosys_server) run(stacosys_server)

@ -0,0 +1,21 @@
; Default configuration
[main]
lang = fr
db_url = sqlite:///db.sqlite
[http]
root_url = http://localhost:8100
host = 0.0.0.0
port = 8100
[security]
salt = BRRJRqXgGpXWrgTidBPcixIThHpDuKc0
secret = Uqca5Kc8xuU6THz9
[rss]
proto = http
file = comments.xml
[polling]
newmail = 15
newcomment = 60
Loading…
Cancel
Save