pull/2/head
Yax 6 years ago
parent 637b00261a
commit 3c4a25e5ad

@ -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)
- [Markdown](http://daringfireball.net/projects/markdown)
### Installation
Python 3.7
pip libs: flask peewee pyrss2gen markdown clize flask-apscheduler profig
### 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.

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

@ -1,3 +1,48 @@
#!/usr/bin/env python
# -*- 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 time
from core import app
from core import processor
from models.comment import Comment
from core import mailer
from core import templater
from model.comment import Comment
from model.comment import Site
logger = logging.getLogger(__name__)
def fetch_mail_answers():
logger.info('DEBUT POP MAIL')
logger.info("DEBUT POP MAIL")
time.sleep(80)
logger.info('FIN POP MAIL')
#data = request.get_json()
#logger.debug(data)
logger.info("FIN POP MAIL")
# data = request.get_json()
# logger.debug(data)
# processor.enqueue({'request': 'new_mail', 'data': data})
#processor.enqueue({'request': 'new_mail', 'data': data})
def submit_new_comment():
for comment in Comment.select().where(Comment.notified.is_null()):
# render email body template
comment_list = (
"author: %s" % comment.author_name,
"site: %s" % comment.author_site,
@ -33,15 +36,10 @@ def submit_new_comment():
"",
)
comment_text = "\n".join(comment_list)
email_body = get_template("new_comment").render(url=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 ")
email_body = templater.get_template("new_comment").render(url=comment.url, comment=comment_text)
def get_template(name):
return env.get_template(config.general["lang"] + "/" + name + ".tpl")
site = Site.select().where(Site.id == Comment.site).get()
# 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 -*-
from conf import config
import functools
from playhouse.db_url import connect
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)
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)
get_db().create_tables([Site, Comment], safe=True)

@ -10,10 +10,8 @@ import json
from datetime import datetime
from threading import Thread
from queue import Queue
from jinja2 import Environment
from jinja2 import FileSystemLoader
from models.site import Site
from models.comment import Comment
from model.site import Site
from model.comment import Comment
from helpers.hashing import md5
from conf import config
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):
string = '%s%s' % (value, config.security['salt'])
string = "%s%s" % (value, config.get(config.SECURITY_SALT))
dk = hashlib.sha256(string.encode())
return dk.hexdigest()

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

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

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

@ -1,36 +1,90 @@
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import os
import logging
import json
from clize import Clize, run
from jsonschema import validate
from conf import config, schema
from flask import Flask
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
with open(filename, 'rt') as json_file:
jsondoc = json.loads(json_file.read())
return jsondoc
class JobConfig(object):
JOBS = []
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
def stacosys_server(config_pathname):
# load and validate startup config
conf = load_json(config_pathname)
json_schema = json.loads(schema.json_schema)
validate(conf, json_schema)
app = Flask(__name__)
config.initialize(config_pathname, app)
# 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
config.general = conf['general']
config.http = conf['http']
config.security = conf['security']
config.rss = conf['rss']
app.run(
host=config.get(config.HTTP_HOST),
port=config.get(config.HTTP_PORT),
debug=False,
use_reloader=False,
)
# start application
from core import app
if __name__ == '__main__':
if __name__ == "__main__":
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