Configuration moved to JSON and validated by JSON Schema

pull/6/head
Yax 7 years ago
parent 2c5b63fcf5
commit 754c37a373

@ -0,0 +1,147 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Created with https://app.quicktype.io
# name: stacosys
json_schema = """
{
"$ref": "#/definitions/Stacosys",
"definitions": {
"Stacosys": {
"type": "object",
"additionalProperties": false,
"properties": {
"general": {
"$ref": "#/definitions/General"
},
"http": {
"$ref": "#/definitions/HTTP"
},
"security": {
"$ref": "#/definitions/Security"
},
"rss": {
"$ref": "#/definitions/RSS"
},
"zmq": {
"$ref": "#/definitions/Zmq"
}
},
"required": [
"general",
"http",
"rss",
"security",
"zmq"
],
"title": "stacosys"
},
"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"
},
"private": {
"type": "boolean"
}
},
"required": [
"private",
"salt",
"secret"
],
"title": "security"
},
"Zmq": {
"type": "object",
"additionalProperties": false,
"properties": {
"active": {
"type": "boolean"
},
"host": {
"type": "string"
},
"pub_port": {
"type": "integer"
},
"sub_port": {
"type": "integer"
}
},
"required": [
"active",
"host",
"pub_port",
"sub_port"
],
"title": "zmq"
}
}
}
"""

@ -4,7 +4,12 @@
import os import os
import sys import sys
import logging import logging
from flask import Flask
from flask.ext.cors import CORS from flask.ext.cors import CORS
from conf import config
from jsonschema import validate
app = Flask(__name__)
# add current path and parent path to syspath # add current path and parent path to syspath
current_path = os.path.dirname(__file__) current_path = os.path.dirname(__file__)
@ -15,16 +20,12 @@ for path in paths:
sys.path.insert(0, path) sys.path.insert(0, path)
# more imports # more imports
import config import database
from app.services import database import processor
from app.services import processor from interface import api
from app.interface import api from interface import form
from app.interface import form from interface import report
from app.interface import report from interface import zclient
#from app.controllers import mail
from app.interface import zclient
from app import app
# configure logging # configure logging
def configure_logging(level): def configure_logging(level):
@ -40,7 +41,7 @@ def configure_logging(level):
# add ch to logger # add ch to logger
root_logger.addHandler(ch) root_logger.addHandler(ch)
logging_level = (20, 10)[config.DEBUG] logging_level = (20, 10)[config.general['debug']]
configure_logging(logging_level) configure_logging(logging_level)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -52,25 +53,23 @@ database.setup()
zclient.start() zclient.start()
# start processor # start processor
template_path = os.path.abspath(os.path.join(current_path, 'templates')) template_path = os.path.abspath(os.path.join(current_path, '../templates'))
processor.start(template_path) processor.start(template_path)
# less feature in private mode # less feature in private mode
if not config.PRIVATE: if not config.security['private']:
# enable CORS # enable CORS
cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) cors = CORS(app, resources={r"/comments/*": {"origins": "*"}})
from app.controllers import reader from app.controllers import reader
logger.debug('imported: %s ' % reader.__name__) logger.debug('imported: %s ' % reader.__name__)
# tune logging level # tune logging level
if not config.DEBUG: if not config.general['debug']:
logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('app.cors').level = logging.WARNING
logging.getLogger('werkzeug').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING
logger.info("Start Stacosys application") logger.info("Start Stacosys application")
if __name__ == '__main__': app.run(host=config.http['host'],
port=config.http['port'],
app.run(host=config.HTTP_ADDRESS, debug=config.general['debug'], use_reloader=False)
port=config.HTTP_PORT,
debug=config.DEBUG, use_reloader=False)

@ -1,13 +1,13 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: UTF-8 -*- # -*- coding: UTF-8 -*-
import config from conf import config
import functools import functools
from playhouse.db_url import connect from playhouse.db_url import connect
def get_db(): def get_db():
return connect(config.DB_URL) return connect(config.general['db_url'])
def provide_db(func): def provide_db(func):
@ -21,9 +21,9 @@ def provide_db(func):
@provide_db @provide_db
def setup(db): def setup(db):
from app.models.site import Site from models.site import Site
from app.models.comment import Comment from models.comment import Comment
from app.models.reader import Reader from models.reader import Reader
from app.models.report import Report from models.report import Report
db.create_tables([Site, Comment, Reader, Report], safe=True) db.create_tables([Site, Comment, Reader, Report], safe=True)

@ -9,21 +9,27 @@ from threading import Thread
from queue import Queue from queue import Queue
from jinja2 import Environment from jinja2 import Environment
from jinja2 import FileSystemLoader from jinja2 import FileSystemLoader
from app.models.site import Site from models.site import Site
from app.models.reader import Reader from models.reader import Reader
from app.models.report import Report from models.report import Report
from app.models.comment import Comment from models.comment import Comment
from app.helpers.hashing import md5 from helpers.hashing import md5
import json import json
import config from conf import config
import PyRSS2Gen import PyRSS2Gen
import markdown import markdown
import zmq
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
queue = Queue() queue = Queue()
proc = None proc = None
env = None env = None
if config.zmq['active']:
context = zmq.Context()
zpub = context.socket(zmq.PUB)
zpub.connect('tcp://127.0.0.1:{}'.format(config.zmq['sub_port']))
class Processor(Thread): class Processor(Thread):
@ -69,7 +75,7 @@ def new_comment(data):
subscribe = data.get('subscribe', '') subscribe = data.get('subscribe', '')
# private mode: email contains gravar md5 hash # private mode: email contains gravar md5 hash
if config.PRIVATE: if config.security['private']:
author_gravatar = author_email author_gravatar = author_email
author_email = '' author_email = ''
else: else:
@ -112,7 +118,7 @@ def new_comment(data):
mail(site.admin_email, subject, email_body) mail(site.admin_email, subject, email_body)
# Reader subscribes to further comments # Reader subscribes to further comments
if not config.PRIVATE and subscribe and author_email: if not config.security['private'] and subscribe and author_email:
subscribe_reader(author_email, token, url) subscribe_reader(author_email, token, url)
logger.debug("new comment processed ") logger.debug("new comment processed ")
@ -171,7 +177,7 @@ def reply_comment_email(data):
mail(from_email, 'Re: ' + subject, email_body) mail(from_email, 'Re: ' + subject, email_body)
# notify reader once comment is published # notify reader once comment is published
if not config.PRIVATE: if not config.security['private']:
reader_email = get_email_metadata(message) reader_email = get_email_metadata(message)
if reader_email: if reader_email:
notify_reader(from_email, reader_email, comment.site.token, notify_reader(from_email, reader_email, comment.site.token,
@ -258,7 +264,7 @@ def notify_subscribed_readers(token, site_url, url):
to_email = reader.email to_email = reader.email
logger.info('notify reader %s' % to_email) logger.info('notify reader %s' % to_email)
unsubscribe_url = '%s/unsubscribe?email=%s&token=%s&url=%s' % ( unsubscribe_url = '%s/unsubscribe?email=%s&token=%s&url=%s' % (
config.ROOT_URL, to_email, token, reader.url) config.http['root_url'], to_email, token, reader.url)
email_body = get_template( email_body = get_template(
'notify_subscriber').render(article_url=article_url, 'notify_subscriber').render(article_url=article_url,
unsubscribe_url=unsubscribe_url) unsubscribe_url=unsubscribe_url)
@ -337,8 +343,8 @@ def report(token):
unsubscribed.append({'url': "http://" + site.url + row.url, unsubscribed.append({'url': "http://" + site.url + row.url,
'name': row.name, 'email': row.email}) 'name': row.name, 'email': row.email})
email_body = get_template('report').render(secret=config.SECRET, email_body = get_template('report').render(secret=config.security['secret'],
root_url=config.ROOT_URL, root_url=config.http['root_url'],
standbys=standbys, standbys=standbys,
published=published, published=published,
rejected=rejected, rejected=rejected,
@ -354,7 +360,7 @@ def report(token):
def rss(token, onstart=False): def rss(token, onstart=False):
if onstart and os.path.isfile(config.RSS_FILE): if onstart and os.path.isfile(config.rss['file']):
return return
site = Site.select().where(Site.token == token).get() site = Site.select().where(Site.token == token).get()
@ -365,9 +371,9 @@ def rss(token, onstart=False):
for row in Comment.select().join(Site).where( for row in Comment.select().join(Site).where(
Site.token == token, Comment.published).order_by( Site.token == token, Comment.published).order_by(
-Comment.published).limit(10): -Comment.published).limit(10):
item_link = "%s://%s%s" % (config.RSS_URL_PROTO, site.url, row.url) item_link = "%s://%s%s" % (config.rss['proto'], site.url, row.url)
items.append(PyRSS2Gen.RSSItem( items.append(PyRSS2Gen.RSSItem(
title='%s - %s://%s%s' % (config.RSS_URL_PROTO, row.author_name, site.url, row.url), title='%s - %s://%s%s' % (config.rss['proto'], row.author_name, site.url, row.url),
link=item_link, link=item_link,
description=md.convert(row.content), description=md.convert(row.content),
guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)), guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)),
@ -376,11 +382,11 @@ def rss(token, onstart=False):
rss = PyRSS2Gen.RSS2( rss = PyRSS2Gen.RSS2(
title=rss_title, title=rss_title,
link='%s://%s' % (config.RSS_URL_PROTO, site.url), link='%s://%s' % (config.rss['proto'], site.url),
description="Commentaires du site '%s'" % site.name, description="Commentaires du site '%s'" % site.name,
lastBuildDate=datetime.now(), lastBuildDate=datetime.now(),
items=items) items=items)
rss.write_xml(open(config.RSS_FILE, "w"), encoding="utf-8") rss.write_xml(open(config.rss['file'], 'w'), encoding='utf-8')
def mail(to_email, subject, message): def mail(to_email, subject, message):
@ -400,7 +406,7 @@ def mail(to_email, subject, message):
def get_template(name): def get_template(name):
return env.get_template(config.LANG + '/' + name + '.tpl') return env.get_template(config.general['lang'] + '/' + name + '.tpl')
def enqueue(something): def enqueue(something):

@ -2,11 +2,11 @@
# -*- coding: UTF-8 -*- # -*- coding: UTF-8 -*-
import hashlib import hashlib
import config from conf import config
def salt(value): def salt(value):
string = '%s%s' % (value, config.SALT) string = '%s%s' % (value, config.security['salt'])
dk = hashlib.sha256(string.encode()) dk = hashlib.sha256(string.encode())
return dk.hexdigest() return dk.hexdigest()

@ -2,12 +2,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging import logging
import config
from flask import request, jsonify, abort from flask import request, jsonify, abort
from app import app from core import app
from app.models.site import Site from models.site import Site
from app.models.comment import Comment from models.comment import Comment
from app.services import processor from core import processor
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

@ -2,13 +2,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging import logging
import config
from flask import request, jsonify, abort, redirect from flask import request, jsonify, abort, redirect
from app import app from core import app
from app.models.site import Site from models.site import Site
from app.models.comment import Comment from models.comment import Comment
from app.helpers.hashing import md5 from helpers.hashing import md5
from app.services import processor from core import processor
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

@ -3,8 +3,8 @@
import logging import logging
from flask import request, abort from flask import request, abort
from app import app from core import app
from app.services import processor from core import processor
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

@ -3,8 +3,8 @@
import logging import logging
from flask import request, abort from flask import request, abort
from app import app from core import app
from app.services import processor from core import processor
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

@ -2,13 +2,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging import logging
import config from conf import config
from flask import request, jsonify, abort from flask import request, jsonify, abort
from app import app from core import app
from app.models.site import Site from models.site import Site
from app.models.comment import Comment from models.comment import Comment
from app.helpers.hashing import md5 from helpers.hashing import md5
from app.services import processor from core import processor
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -19,7 +19,7 @@ def report():
token = request.args.get('token', '') token = request.args.get('token', '')
secret = request.args.get('secret', '') secret = request.args.get('secret', '')
if secret != config.SECRET: if secret != config.security['secret']:
logger.warn('Unauthorized request') logger.warn('Unauthorized request')
abort(401) abort(401)
@ -45,7 +45,7 @@ def accept_comment():
id = request.args.get('comment', '') id = request.args.get('comment', '')
secret = request.args.get('secret', '') secret = request.args.get('secret', '')
if secret != config.SECRET: if secret != config.security['secret']:
logger.warn('Unauthorized request') logger.warn('Unauthorized request')
abort(401) abort(401)
@ -65,7 +65,7 @@ def reject_comment():
id = request.args.get('comment', '') id = request.args.get('comment', '')
secret = request.args.get('secret', '') secret = request.args.get('secret', '')
if secret != config.SECRET: if secret != config.security['secret']:
logger.warn('Unauthorized request') logger.warn('Unauthorized request')
abort(401) abort(401)

@ -6,7 +6,7 @@ from conf import config
from threading import Thread from threading import Thread
import logging import logging
import json import json
from app.services import processor from core import processor
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

@ -6,8 +6,8 @@ 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 app.models.site import Site from models.site import Site
from app.services.database import get_db from core.database import get_db
class Comment(Model): class Comment(Model):

@ -4,8 +4,8 @@
from peewee import Model from peewee import Model
from peewee import CharField from peewee import CharField
from peewee import ForeignKeyField from peewee import ForeignKeyField
from app.services.database import get_db from core.database import get_db
from app.models.site import Site from models.site import Site
class Reader(Model): class Reader(Model):

@ -5,8 +5,8 @@ from peewee import Model
from peewee import CharField from peewee import CharField
from peewee import BooleanField from peewee import BooleanField
from peewee import ForeignKeyField from peewee import ForeignKeyField
from app.services.database import get_db from core.database import get_db
from app.models.site import Site from models.site import Site
class Report(Model): class Report(Model):
name = CharField() name = CharField()

@ -3,7 +3,7 @@
from peewee import Model from peewee import Model
from peewee import CharField from peewee import CharField
from app.services.database import get_db from core.database import get_db
class Site(Model): class Site(Model):

@ -0,0 +1,37 @@
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import logging
import json
from clize import clize, run
from jsonschema import validate
from conf import config, schema
def load_json(filename):
jsondoc = None
with open(filename, 'rt') as json_file:
jsondoc = json.loads(json_file.read())
return jsondoc
@clize
def stacosys_server(config_pathname):
# load and validate startup config
conf = load_json(config_pathname)
json_schema = json.loads(schema.json_schema)
v = validate(conf, json_schema)
print('validation: {}'.format(v))
# set configuration
config.general = conf['general']
config.http = conf['http']
config.security = conf['security']
config.rss = conf['rss']
config.zmq = conf['zmq']
# start application
from core import app
if __name__ == '__main__':
run(stacosys_server)

@ -0,0 +1,27 @@
{
"general" : {
"debug": true,
"lang": "fr",
"db_url": "sqlite:///db.sqlite"
},
"http": {
"root_url": "http://localhost:8100",
"host": "127.0.0.1",
"port": 8100
},
"security": {
"salt": "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0",
"secret": "Uqca5Kc8xuU6THz9",
"private": true
},
"rss": {
"proto": "http",
"file": "comments.xml"
},
"zmq": {
"active": true,
"host": "127.0.0.1",
"pub_port": 7701,
"sub_port": 7702
}
}

@ -1,25 +0,0 @@
# Configuration file
DEBUG = True
LANG = "fr"
# DB_URL = "mysql://stacosys_user:stacosys_password@localhost:3306/stacosys"
DB_URL = "sqlite:///db.sqlite"
MAIL_URL = "http://localhost:8025/mbox"
HTTP_ADDRESS = "127.0.0.1"
HTTP_PORT = 8100
HTTP_WORKERS = 1
SALT = "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0"
SECRET = "Uqca5Kc8xuU6THz9"
ROOT_URL = 'http://localhost:8100'
RSS_URL_PROTO = 'http'
RSS_FILE = 'comments.xml'
PRIVATE = True

@ -1,11 +1,19 @@
attrs==17.4.0
chardet==3.0.4 chardet==3.0.4
click==6.7 click==6.7
clize==4.0.3
docutils==0.14
Flask==0.12.2 Flask==0.12.2
Flask-Cors==3.0.3
itsdangerous==0.24 itsdangerous==0.24
Jinja2==2.10 Jinja2==2.10
jsonschema==2.6.0
Markdown==2.6.11 Markdown==2.6.11
MarkupSafe==1.0 MarkupSafe==1.0
od==1.0
peewee==2.10.2 peewee==2.10.2
PyRSS2Gen==1.1 PyRSS2Gen==1.1
pyzmq==16.0.3 pyzmq==16.0.3
sigtools==2.0.1
six==1.11.0
Werkzeug==0.14.1 Werkzeug==0.14.1

@ -1,3 +1,3 @@
#!/bin/sh #!/bin/sh
python app/run.py "$@" python app/stacosys.py "$@"

Loading…
Cancel
Save