add config system, shut up the linter

pull/4/head
Blake DeMarcy 2017-05-01 03:13:36 -05:00
parent 970edc10c6
commit ecef425e8b
3 changed files with 83 additions and 68 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/*.db /*.db
/config.json

View File

@ -0,0 +1,7 @@
{
"port": 7099,
"host": "127.0.0.1",
"instance_name": "BBJ",
"allow_anon": True,
"debug": False
}

143
server.py
View File

@ -9,7 +9,24 @@ import sqlite3
import json import json
dbname = "data.sqlite" dbname = "data.sqlite"
debug = False
# any values here may be overrided in the config.json. Any values not listed
# here will have no effect on the server.
app_config = {
"port": 7099,
"host": "127.0.0.1",
"instance_name": "BBJ",
"allow_anon": True,
"debug": False
}
try:
with open("config.json") as _conf:
app_config.update(json.load(_conf))
except FileNotFoundError:
with open("config.json", "w") as _conf:
json.dump(app_config, _conf)
def api_method(function): def api_method(function):
""" """
@ -25,9 +42,11 @@ def api_method(function):
caught as well and returned to the client as code 0. caught as well and returned to the client as code 0.
""" """
function.exposed = True function.exposed = True
@wraps(function) @wraps(function)
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
response = None response = None
debug = app_config["debug"]
try: try:
connection = sqlite3.connect(dbname) connection = sqlite3.connect(dbname)
# read in the body from the request to a string... # read in the body from the request to a string...
@ -49,7 +68,8 @@ def api_method(function):
print("\n\n\nBody: {}\n\ne----------".format(body)) print("\n\n\nBody: {}\n\ne----------".format(body))
if (username and not auth) or (auth and not username): if (username and not auth) or (auth and not username):
raise BBJParameterError("User or Auth was given without the other.") raise BBJParameterError(
"User or Auth was given without the other.")
elif not username and not auth: elif not username and not auth:
user = db.anon user = db.anon
@ -60,7 +80,8 @@ def api_method(function):
raise BBJUserError("User %s is not registered" % username) raise BBJUserError("User %s is not registered" % username)
elif auth.lower() != user["auth_hash"].lower(): elif auth.lower() != user["auth_hash"].lower():
raise BBJException(5, "Invalid authorization key for user.") raise BBJException(
5, "Invalid authorization key for user.")
# api_methods may choose to bind a usermap into the thread_data # api_methods may choose to bind a usermap into the thread_data
# which will send it off with the response # which will send it off with the response
@ -76,9 +97,9 @@ def api_method(function):
except Exception as e: except Exception as e:
error_id = uuid1().hex error_id = uuid1().hex
response = schema.error(1, response = schema.error(
"Internal server error: code {} {}" 1, "Internal server error: code {} {}".format(
.format(error_id, repr(e))) error_id, repr(e)))
with open("logs/exceptions/" + error_id, "a") as log: with open("logs/exceptions/" + error_id, "a") as log:
traceback.print_tb(e.__traceback__, file=log) traceback.print_tb(e.__traceback__, file=log)
log.write(repr(e)) log.write(repr(e))
@ -142,6 +163,16 @@ def validate(json, args):
.format(arg, ", ".join(args))) .format(arg, ", ".join(args)))
def no_anon_hook(user, message=None, user_error=True):
if user is db.anon:
exception = BBJUserError if user_error else BBJParameterError
if message:
raise exception(message)
elif not app_config["allow_anon"]:
raise exception(
"Anonymous participation has been disabled on this instance.")
class API(object): class API(object):
""" """
This object contains all the API endpoints for bbj. This object contains all the API endpoints for bbj.
@ -149,6 +180,7 @@ class API(object):
yet, so this is currently the only module being yet, so this is currently the only module being
served. served.
""" """
@api_method @api_method
def user_register(self, args, database, user, **kwargs): def user_register(self, args, database, user, **kwargs):
""" """
@ -160,7 +192,6 @@ class API(object):
return db.user_register( return db.user_register(
database, args["user_name"], args["auth_hash"]) database, args["user_name"], args["auth_hash"])
@api_method @api_method
def user_update(self, args, database, user, **kwargs): def user_update(self, args, database, user, **kwargs):
""" """
@ -171,12 +202,10 @@ class API(object):
The newly updated user object is returned on success. The newly updated user object is returned on success.
""" """
if user == db.anon: no_anon_hook(user, "Anons cannot modify their account.")
raise BBJParameterError("Anons cannot modify their account.") validate(args, []) # just make sure its not empty
validate(args, []) # just make sure its not empty
return db.user_update(database, user, args) return db.user_update(database, user, args)
@api_method @api_method
def get_me(self, args, database, user, **kwargs): def get_me(self, args, database, user, **kwargs):
""" """
@ -185,14 +214,15 @@ class API(object):
""" """
return user return user
@api_method @api_method
def user_map(self, args, database, user, **kwargs): def user_map(self, args, database, user, **kwargs):
""" """
Returns an array with all registered user_ids, with the usermap Returns an array with all registered user_ids, with the usermap
object populated by their full objects. object populated by their full objects.
""" """
users = {user[0] for user in database.execute("SELECT user_id FROM users")} users = {
user[0] for user in database.execute("SELECT user_id FROM users")
}
cherrypy.thread_data.usermap = { cherrypy.thread_data.usermap = {
user: db.user_resolve( user: db.user_resolve(
database, database,
@ -203,7 +233,6 @@ class API(object):
} }
return list(users) return list(users)
@api_method @api_method
def user_get(self, args, database, user, **kwargs): def user_get(self, args, database, user, **kwargs):
""" """
@ -214,7 +243,6 @@ class API(object):
return db.user_resolve( return db.user_resolve(
database, args["user"], return_false=False, externalize=True) database, args["user"], return_false=False, externalize=True)
@api_method @api_method
def user_is_registered(self, args, database, user, **kwargs): def user_is_registered(self, args, database, user, **kwargs):
""" """
@ -224,7 +252,6 @@ class API(object):
validate(args, ["target_user"]) validate(args, ["target_user"])
return bool(db.user_resolve(database, args["target_user"])) return bool(db.user_resolve(database, args["target_user"]))
@api_method @api_method
def check_auth(self, args, database, user, **kwargs): def check_auth(self, args, database, user, **kwargs):
""" """
@ -232,26 +259,25 @@ class API(object):
returns boolean true or false whether the hash is valid. returns boolean true or false whether the hash is valid.
""" """
validate(args, ["target_user", "target_hash"]) validate(args, ["target_user", "target_hash"])
user = db.user_resolve(database, args["target_user"], return_false=False) user = db.user_resolve(
database, args["target_user"], return_false=False)
return args["target_hash"].lower() == user["auth_hash"].lower() return args["target_hash"].lower() == user["auth_hash"].lower()
@api_method @api_method
def thread_index(self, args, database, user, **kwargs): def thread_index(self, args, database, user, **kwargs):
""" """
Return an array with all the threads, ordered by most recent activity. Return an array with all the threads, ordered by most recent activity.
Requires no arguments. Requires no arguments.
Optionally, you may supply the argument `include_op`, which, when non-nil, Optionally, you may supply the argument `include_op`, which, when
will include a "messages" key with the object, whose sole content is the non-nil, will include a "messages" key with the object, whose sole
original message (post_id 0). content is the original message (post_id 0).
""" """
op = isinstance(args, dict) and args.get("include_op") op = isinstance(args, dict) and args.get("include_op")
threads = db.thread_index(database, include_op=op) threads = db.thread_index(database, include_op=op)
cherrypy.thread_data.usermap = create_usermap(database, threads, True) cherrypy.thread_data.usermap = create_usermap(database, threads, True)
return threads return threads
@api_method @api_method
def message_feed(self, args, database, user, **kwargs): def message_feed(self, args, database, user, **kwargs):
""" """
@ -292,7 +318,6 @@ class API(object):
do_formatting(args.get("format"), feed["messages"]) do_formatting(args.get("format"), feed["messages"])
return feed return feed
@api_method @api_method
def thread_create(self, args, database, user, **kwargs): def thread_create(self, args, database, user, **kwargs):
""" """
@ -302,6 +327,7 @@ class API(object):
If the argument `send_raw` is specified and has a non-nil If the argument `send_raw` is specified and has a non-nil
value, the OP message will never recieve special formatting. value, the OP message will never recieve special formatting.
""" """
no_anon_hook(user)
validate(args, ["body", "title"]) validate(args, ["body", "title"])
thread = db.thread_create( thread = db.thread_create(
database, user["user_id"], args["body"], database, user["user_id"], args["body"],
@ -310,7 +336,6 @@ class API(object):
create_usermap(database, thread["messages"]) create_usermap(database, thread["messages"])
return thread return thread
@api_method @api_method
def thread_reply(self, args, database, user, **kwargs): def thread_reply(self, args, database, user, **kwargs):
""" """
@ -320,12 +345,12 @@ class API(object):
If the argument `send_raw` is specified and has a non-nil If the argument `send_raw` is specified and has a non-nil
value, the message will never recieve special formatting. value, the message will never recieve special formatting.
""" """
no_anon_hook(user)
validate(args, ["thread_id", "body"]) validate(args, ["thread_id", "body"])
return db.thread_reply( return db.thread_reply(
database, user["user_id"], args["thread_id"], database, user["user_id"], args["thread_id"],
args["body"], args.get("send_raw")) args["body"], args.get("send_raw"))
@api_method @api_method
def thread_load(self, args, database, user, **kwargs): def thread_load(self, args, database, user, **kwargs):
""" """
@ -345,7 +370,6 @@ class API(object):
do_formatting(args.get("format"), thread["messages"]) do_formatting(args.get("format"), thread["messages"])
return thread return thread
@api_method @api_method
def edit_post(self, args, database, user, **kwargs): def edit_post(self, args, database, user, **kwargs):
""" """
@ -366,14 +390,12 @@ class API(object):
Returns the new message object. Returns the new message object.
""" """
if user == db.anon: no_anon_hook(user, "Anons cannot edit messages.")
raise BBJUserError("Anons cannot edit messages.")
validate(args, ["body", "thread_id", "post_id"]) validate(args, ["body", "thread_id", "post_id"])
return db.message_edit_commit( return db.message_edit_commit(
database, user["user_id"], args["thread_id"], database, user["user_id"], args["thread_id"],
args["post_id"], args["body"], args.get("send_raw")) args["post_id"], args["body"], args.get("send_raw"))
@api_method @api_method
def delete_post(self, args, database, user, **kwargs): def delete_post(self, args, database, user, **kwargs):
""" """
@ -388,13 +410,11 @@ class API(object):
If the post_id is 0, the whole thread is deleted. If the post_id is 0, the whole thread is deleted.
""" """
if user == db.anon: no_anon_hook(user, "Anons cannot delete messages.")
raise BBJUserError("Anons cannot delete messages.")
validate(args, ["thread_id", "post_id"]) validate(args, ["thread_id", "post_id"])
return db.message_delete( return db.message_delete(
database, user["user_id"], args["thread_id"], args["post_id"]) database, user["user_id"], args["thread_id"], args["post_id"])
@api_method @api_method
def set_post_raw(self, args, database, user, **kwargs): def set_post_raw(self, args, database, user, **kwargs):
""" """
@ -412,15 +432,13 @@ class API(object):
but if this is the only change you want to make to the message, but if this is the only change you want to make to the message,
using this endpoint instead is preferable. using this endpoint instead is preferable.
""" """
if user == db.anon: no_anon_hook(user, "Anons cannot edit messages.")
raise BBJUserError("Anons cannot edit messages.")
validate(args, ["value", "thread_id", "post_id"]) validate(args, ["value", "thread_id", "post_id"])
return db.message_edit_commit( return db.message_edit_commit(
database, user["user_id"], database, user["user_id"],
args["thread_id"], args["post_id"], args["thread_id"], args["post_id"],
None, args["value"], None) None, args["value"], None)
@api_method @api_method
def is_admin(self, args, database, user, **kwargs): def is_admin(self, args, database, user, **kwargs):
""" """
@ -428,10 +446,10 @@ class API(object):
of whether that user is an admin. of whether that user is an admin.
""" """
validate(args, ["target_user"]) validate(args, ["target_user"])
user = db.user_resolve(database, args["target_user"], return_false=False) user = db.user_resolve(
database, args["target_user"], return_false=False)
return user["is_admin"] return user["is_admin"]
@api_method @api_method
def edit_query(self, args, database, user, **kwargs): def edit_query(self, args, database, user, **kwargs):
""" """
@ -442,13 +460,11 @@ class API(object):
Returns the original message object without any formatting Returns the original message object without any formatting
on success. Returns a descriptive code 4 otherwise. on success. Returns a descriptive code 4 otherwise.
""" """
if user == db.anon: no_anon_hook(user, "Anons cannot edit messages.")
raise BBJUserError("Anons cannot edit messages.")
validate(args, ["thread_id", "post_id"]) validate(args, ["thread_id", "post_id"])
return db.message_edit_query( return db.message_edit_query(
database, user["user_id"], args["thread_id"], args["post_id"]) database, user["user_id"], args["thread_id"], args["post_id"])
@api_method @api_method
def format_message(self, args, database, user, **kwargs): def format_message(self, args, database, user, **kwargs):
""" """
@ -461,7 +477,6 @@ class API(object):
do_formatting(args["format"], message) do_formatting(args["format"], message)
return message[0]["body"] return message[0]["body"]
@api_method @api_method
def set_thread_pin(self, args, database, user, **kwargs): def set_thread_pin(self, args, database, user, **kwargs):
""" """
@ -477,7 +492,6 @@ class API(object):
raise BBJUserError("Only admins can set thread pins") raise BBJUserError("Only admins can set thread pins")
return db.set_thread_pin(database, args["thread_id"], args["value"]) return db.set_thread_pin(database, args["thread_id"], args["value"])
@api_method @api_method
def db_validate(self, args, database, user, **kwargs): def db_validate(self, args, database, user, **kwargs):
""" """
@ -516,10 +530,11 @@ class API(object):
def api_http_error(status, message, traceback, version): def api_http_error(status, message, traceback, version):
return json.dumps(schema.error(2, "HTTP error {}: {}".format(status, message))) return json.dumps(schema.error(
2, "HTTP error {}: {}".format(status, message)))
CONFIG = { API_CONFIG = {
"/": { "/": {
"error_page.default": api_http_error "error_page.default": api_http_error
} }
@ -534,37 +549,29 @@ def run():
db.anon = db.user_resolve(_c, "anonymous") db.anon = db.user_resolve(_c, "anonymous")
if not db.anon: if not db.anon:
db.anon = db.user_register( db.anon = db.user_register(
_c, "anonymous", # this is the hash for "anon" _c, "anonymous", # this is the hash for "anon"
"5430eeed859cad61d925097ec4f53246" "5430eeed859cad61d925097ec4f53246"
"1ccf1ab6b9802b09a313be1478a4d614") "1ccf1ab6b9802b09a313be1478a4d614")
finally: finally:
_c.close() _c.close()
cherrypy.quickstart(API(), "/api", CONFIG) cherrypy.quickstart(API(), "/api", API_CONFIG)
def get_arg(key, default, get_value=True):
try:
spec = argv.index("--" + key)
value = argv[spec + 1] if get_value else True
except ValueError: # --key not specified
value = default
except IndexError: # flag given but no value
exit("invalid format for --" + key)
return value
if __name__ == "__main__": if __name__ == "__main__":
try: port = get_arg("port", app_config["port"])
port_spec = argv.index("--port") host = get_arg("host", app_config["host"])
port = argv[port_spec+1] debug = get_arg("debug", app_config["debug"], False)
except ValueError: # --port not specified
port = 7099
except IndexError: # flag given but no value
exit("thats not how this works, silly! --port 7099")
try:
host_spec = argv.index("--host")
host = argv[host_spec+1]
except ValueError: # --host not specified
host = "127.0.0.1"
except IndexError: # flag given but no value
exit("thats not how this works, silly! --host 127.0.0.1")
try:
host_spec = argv.index("--debug")
debug = True
except ValueError:
pass
cherrypy.config.update({ cherrypy.config.update({
"server.socket_port": int(port), "server.socket_port": int(port),
"server.socket_host": host "server.socket_host": host