add config system, shut up the linter
parent
970edc10c6
commit
ecef425e8b
|
@ -1 +1,2 @@
|
||||||
/*.db
|
/*.db
|
||||||
|
/config.json
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"port": 7099,
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"instance_name": "BBJ",
|
||||||
|
"allow_anon": True,
|
||||||
|
"debug": False
|
||||||
|
}
|
143
server.py
143
server.py
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue