2017-04-02 14:53:55 +00:00
|
|
|
from src.exceptions import BBJException, BBJParameterError, BBJUserError
|
|
|
|
from src import db, schema
|
2017-04-02 07:35:58 +00:00
|
|
|
from functools import wraps
|
2017-04-02 14:53:55 +00:00
|
|
|
from uuid import uuid1
|
|
|
|
import traceback
|
2017-04-02 07:35:58 +00:00
|
|
|
import cherrypy
|
|
|
|
import sqlite3
|
|
|
|
import json
|
|
|
|
|
|
|
|
|
2017-04-02 08:34:52 +00:00
|
|
|
dbname = "data.sqlite"
|
2017-04-02 07:35:58 +00:00
|
|
|
with sqlite3.connect(dbname) as _c:
|
2017-04-02 08:34:52 +00:00
|
|
|
db.anon_object = db.user_resolve(_c, "anonymous")
|
|
|
|
if not db.anon_object:
|
|
|
|
db.anon_object = db.user_register(_c, *db.anon_credentials)
|
2017-04-02 07:35:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
# creates a database connection for each thread
|
2017-04-02 08:34:52 +00:00
|
|
|
def db_connect(_):
|
2017-04-02 07:35:58 +00:00
|
|
|
cherrypy.thread_data.db = sqlite3.connect(dbname)
|
2017-04-02 08:34:52 +00:00
|
|
|
cherrypy.engine.subscribe('start_thread', db_connect)
|
2017-04-02 07:35:58 +00:00
|
|
|
|
|
|
|
|
2017-04-02 08:34:52 +00:00
|
|
|
def api_method(function):
|
2017-04-02 07:35:58 +00:00
|
|
|
"""
|
|
|
|
A wrapper that handles encoding of objects and errors to a
|
|
|
|
standard format for the API, resolves and authorizes users
|
2017-04-02 08:34:52 +00:00
|
|
|
from header data, and prepares cherrypy.thread_data so other
|
|
|
|
funtions can handle the request.
|
|
|
|
|
|
|
|
In the body of each api method and all the functions
|
|
|
|
they utilize, BBJExceptions are caught and their attached
|
|
|
|
schema is dispatched to the client. All other unhandled
|
|
|
|
exceptions will throw a code 1 back at the client and log
|
2017-04-02 14:53:55 +00:00
|
|
|
it for inspection. Errors related to JSON decoding are
|
|
|
|
caught as well and returned to the client as code 0.
|
2017-04-02 07:35:58 +00:00
|
|
|
"""
|
|
|
|
@wraps(function)
|
|
|
|
def wrapper(*args, **kwargs):
|
2017-04-02 08:34:52 +00:00
|
|
|
response = None
|
|
|
|
try:
|
2017-04-02 14:53:55 +00:00
|
|
|
# read in the body from the request to a string...
|
|
|
|
body = str(cherrypy.request.body.read(), "utf8")
|
|
|
|
# is it empty? not all methods require an input
|
|
|
|
if body:
|
|
|
|
# if this fucks up, we throw code 0 instead of code 1
|
|
|
|
body = json.loads(body)
|
|
|
|
if isinstance(body, dict):
|
|
|
|
# lowercase all of its keys
|
|
|
|
body = {str(key).lower(): value for key, value
|
|
|
|
in body.items()}
|
|
|
|
cherrypy.request.json = body
|
|
|
|
|
|
|
|
else:
|
|
|
|
cherrypy.request.json = None
|
|
|
|
|
2017-04-02 08:34:52 +00:00
|
|
|
username = cherrypy.request.headers.get("User")
|
|
|
|
auth = cherrypy.request.headers.get("Auth")
|
|
|
|
anon = False
|
|
|
|
|
|
|
|
if not username and not auth:
|
|
|
|
user = db.anon_object
|
|
|
|
anon = True
|
2017-04-02 14:53:55 +00:00
|
|
|
|
2017-04-02 08:34:52 +00:00
|
|
|
elif not username or not auth:
|
2017-04-02 07:35:58 +00:00
|
|
|
return json.dumps(schema.error(5,
|
2017-04-02 08:34:52 +00:00
|
|
|
"User or Auth was given without the other."))
|
2017-04-02 07:35:58 +00:00
|
|
|
|
2017-04-02 08:34:52 +00:00
|
|
|
if not anon:
|
|
|
|
user = db.user_resolve(cherrypy.thread_data.db, username)
|
2017-04-02 14:53:55 +00:00
|
|
|
if not user:
|
|
|
|
raise BBJUserError("User %s is not registered" % username)
|
|
|
|
|
2017-04-02 08:34:52 +00:00
|
|
|
if auth != user["auth_hash"]:
|
|
|
|
return json.dumps(schema.error(5,
|
|
|
|
"Invalid authorization key for user."))
|
2017-04-02 07:35:58 +00:00
|
|
|
|
2017-04-02 08:34:52 +00:00
|
|
|
cherrypy.thread_data.user = user
|
|
|
|
cherrypy.thread_data.anon = anon
|
|
|
|
response = function(*args, **kwargs)
|
2017-04-02 07:35:58 +00:00
|
|
|
|
2017-04-02 14:53:55 +00:00
|
|
|
except json.JSONDecodeError as e:
|
|
|
|
response = schema.error(0, str(e))
|
|
|
|
|
2017-04-02 07:35:58 +00:00
|
|
|
except BBJException as e:
|
2017-04-02 08:34:52 +00:00
|
|
|
response = e.schema
|
2017-04-02 07:35:58 +00:00
|
|
|
|
|
|
|
except Exception as e:
|
2017-04-02 14:53:55 +00:00
|
|
|
error_id = uuid1().hex
|
|
|
|
response = schema.error(1,
|
|
|
|
"Internal server error: code {}. Tell ~desvox (the code too)"
|
|
|
|
.format(error_id))
|
|
|
|
with open("logs/exceptions/" + error_id, "a") as log:
|
|
|
|
traceback.print_tb(e.__traceback__, file=log)
|
|
|
|
log.write(repr(e))
|
2017-04-02 08:34:52 +00:00
|
|
|
|
|
|
|
finally:
|
|
|
|
return json.dumps(response)
|
2017-04-02 07:35:58 +00:00
|
|
|
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
|
|
|
def create_usermap(connection, obj):
|
|
|
|
"""
|
|
|
|
Creates a mapping of all the user_ids that occur in OBJ to
|
|
|
|
their full user objects (names, profile info, etc). Can
|
|
|
|
be a thread_index or a messages object from one.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if isinstance(obj, dict):
|
2017-04-02 08:34:52 +00:00
|
|
|
# this is a message object for a thread, ditch the keys
|
|
|
|
obj = obj.values()
|
2017-04-02 07:35:58 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
user_id: db.user_resolve(
|
|
|
|
connection,
|
|
|
|
user_id,
|
|
|
|
externalize=True,
|
|
|
|
return_false=False)
|
|
|
|
for user_id in {
|
|
|
|
item["author"] for item in obj
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def validate(json, args):
|
|
|
|
"""
|
|
|
|
Ensure the json object contains all the keys needed to satisfy
|
2017-04-02 14:53:55 +00:00
|
|
|
its endpoint (and isnt empty)
|
2017-04-02 07:35:58 +00:00
|
|
|
"""
|
2017-04-02 14:53:55 +00:00
|
|
|
if not json:
|
|
|
|
raise BBJParameterError(
|
|
|
|
"JSON input is empty. This method requires the following "
|
|
|
|
"arguments: {}".format(", ".join(args)))
|
|
|
|
|
2017-04-02 07:35:58 +00:00
|
|
|
for arg in args:
|
|
|
|
if arg not in json.keys():
|
|
|
|
raise BBJParameterError(
|
2017-04-02 14:53:55 +00:00
|
|
|
"Required parameter {} is absent from the request. "
|
|
|
|
"This method requires the following arguments: {}"
|
|
|
|
.format(arg, ", ".join(args)))
|
2017-04-02 07:35:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
APICONFIG = {
|
|
|
|
"/": {
|
|
|
|
"tools.response_headers.on": True,
|
|
|
|
"tools.response_headers.headers": [
|
|
|
|
("Content-Type", "application/json")
|
|
|
|
],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class API(object):
|
2017-04-02 08:34:52 +00:00
|
|
|
@api_method
|
|
|
|
@cherrypy.expose
|
2017-04-02 14:53:55 +00:00
|
|
|
def get_me(self, *args, **kwargs):
|
2017-04-02 08:34:52 +00:00
|
|
|
"""
|
|
|
|
Requires no arguments. Returns your internal user object,
|
|
|
|
including your authorization hash.
|
|
|
|
"""
|
|
|
|
return schema.response(cherrypy.thread_data.user)
|
|
|
|
|
|
|
|
@api_method
|
|
|
|
@cherrypy.expose
|
2017-04-02 14:53:55 +00:00
|
|
|
def user_get(self, *args, **kwargs):
|
2017-04-02 08:34:52 +00:00
|
|
|
"""
|
|
|
|
Retreive an external user object for the given `user`.
|
|
|
|
Can be a user_id or user_name.
|
|
|
|
"""
|
|
|
|
args = cherrypy.request.json
|
2017-04-02 14:53:55 +00:00
|
|
|
validate(args, ["user"])
|
2017-04-02 08:34:52 +00:00
|
|
|
return schema.response(db.user_resolve(
|
|
|
|
cherrypy.thread_data.db,
|
|
|
|
args["user"],
|
|
|
|
return_false=False,
|
|
|
|
externalize=True))
|
|
|
|
|
|
|
|
|
|
|
|
@api_method
|
2017-04-02 07:35:58 +00:00
|
|
|
@cherrypy.expose
|
2017-04-02 14:53:55 +00:00
|
|
|
def thread_index(self, *args, **kwargs):
|
2017-04-02 07:35:58 +00:00
|
|
|
threads = db.thread_index(cherrypy.thread_data.db)
|
|
|
|
usermap = create_usermap(cherrypy.thread_data.db, threads)
|
2017-04-02 08:34:52 +00:00
|
|
|
return schema.response(threads, usermap)
|
2017-04-02 07:35:58 +00:00
|
|
|
|
|
|
|
|
2017-04-02 08:34:52 +00:00
|
|
|
@api_method
|
2017-04-02 07:35:58 +00:00
|
|
|
@cherrypy.expose
|
2017-04-02 14:53:55 +00:00
|
|
|
def thread_create(self, *args, **kwargs):
|
2017-04-02 07:35:58 +00:00
|
|
|
args = cherrypy.request.json
|
|
|
|
validate(args, ["body", "title"])
|
|
|
|
|
|
|
|
thread = db.thread_create(
|
|
|
|
cherrypy.thread_data.db,
|
|
|
|
cherrypy.thread_data.user["user_id"],
|
|
|
|
args["body"], args["title"])
|
|
|
|
|
|
|
|
usermap = {
|
|
|
|
cherrypy.thread_data.user["user_id"]:
|
|
|
|
cherrypy.thread_data.user
|
|
|
|
}
|
|
|
|
|
2017-04-02 08:34:52 +00:00
|
|
|
return schema.response(thread, usermap)
|
2017-04-02 07:35:58 +00:00
|
|
|
|
|
|
|
|
2017-04-02 08:34:52 +00:00
|
|
|
@api_method
|
2017-04-02 07:35:58 +00:00
|
|
|
@cherrypy.expose
|
2017-04-02 14:53:55 +00:00
|
|
|
def thread_reply(self, *args, **kwargs):
|
2017-04-02 07:35:58 +00:00
|
|
|
args = cherrypy.request.json
|
|
|
|
validate(args, ["thread_id", "body"])
|
2017-04-02 08:34:52 +00:00
|
|
|
return schema.response(db.thread_reply(
|
|
|
|
cherrypy.thread_data.db,
|
|
|
|
cherrypy.thread_data.user["user_id"],
|
|
|
|
args["thread_id"], args["body"]))
|
2017-04-02 07:35:58 +00:00
|
|
|
|
|
|
|
|
2017-04-02 08:34:52 +00:00
|
|
|
@api_method
|
2017-04-02 07:35:58 +00:00
|
|
|
@cherrypy.expose
|
2017-04-02 14:53:55 +00:00
|
|
|
def thread_load(self, *args, **kwargs):
|
2017-04-02 07:35:58 +00:00
|
|
|
args = cherrypy.request.json
|
|
|
|
validate(args, ["thread_id"])
|
|
|
|
|
|
|
|
thread = db.thread_get(
|
|
|
|
cherrypy.thread_data.db,
|
|
|
|
args["thread_id"])
|
|
|
|
|
|
|
|
usermap = create_usermap(
|
|
|
|
cherrypy.thread_data.db,
|
|
|
|
thread["messages"])
|
|
|
|
|
2017-04-02 08:34:52 +00:00
|
|
|
return schema.response(thread, usermap)
|
2017-04-02 07:35:58 +00:00
|
|
|
|
|
|
|
|
2017-04-02 08:34:52 +00:00
|
|
|
@api_method
|
2017-04-02 07:35:58 +00:00
|
|
|
@cherrypy.expose
|
2017-04-02 14:53:55 +00:00
|
|
|
def user_register(self, *args, **kwargs):
|
2017-04-02 07:35:58 +00:00
|
|
|
args = cherrypy.request.json
|
|
|
|
validate(args, ["user_name", "auth_hash"])
|
2017-04-02 08:34:52 +00:00
|
|
|
return schema.response(db.user_register(
|
|
|
|
cherrypy.thread_data.db,
|
|
|
|
args["user_name"],
|
|
|
|
args["auth_hash"]))
|
2017-04-02 07:35:58 +00:00
|
|
|
|
|
|
|
|
2017-04-02 08:34:52 +00:00
|
|
|
@api_method
|
2017-04-02 07:35:58 +00:00
|
|
|
@cherrypy.expose
|
2017-04-02 14:53:55 +00:00
|
|
|
def edit_query(self, *args, **kwargs):
|
2017-04-02 07:35:58 +00:00
|
|
|
args = cherrypy.request.json
|
|
|
|
validate(args, ["thread_id", "post_id"])
|
2017-04-02 08:34:52 +00:00
|
|
|
return schema.response(message_edit_query(
|
|
|
|
cherrypy.thread_data.db,
|
|
|
|
cherrypy.thread_data.user["user_id"],
|
|
|
|
args["thread_id"],
|
|
|
|
args["post_id"]))
|
2017-04-02 07:35:58 +00:00
|
|
|
|
|
|
|
|
2017-04-02 14:53:55 +00:00
|
|
|
@cherrypy.expose
|
|
|
|
def test(self, *args, **kwargs):
|
|
|
|
print(cherrypy.request.body.read())
|
|
|
|
return "{\"wow\": \"good job!\"}"
|
|
|
|
|
|
|
|
|
2017-04-02 07:35:58 +00:00
|
|
|
|
|
|
|
def run():
|
|
|
|
cherrypy.quickstart(API(), "/api")
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
print("wew")
|