things shalt be broken and thou shalt fix them later

pull/4/head
Blake DeMarcy 2017-04-02 14:26:49 -05:00
parent 474f13842b
commit 73a9063f93
2 changed files with 129 additions and 246 deletions

217
server.py
View File

@ -7,12 +7,17 @@ import cherrypy
import sqlite3 import sqlite3
import json import json
dbname = "data.sqlite" dbname = "data.sqlite"
# user anonymity is achieved in the laziest possible way: a literal user
# named anonymous. may god have mercy on my soul.
with sqlite3.connect(dbname) as _c: with sqlite3.connect(dbname) as _c:
db.anon_object = db.user_resolve(_c, "anonymous") db.anon = db.user_resolve(_c, "anonymous")
if not db.anon_object: if not db.anon:
db.anon_object = db.user_register(_c, *db.anon_credentials) db.anon = db.user_register(
_c, "anonymous", # this is the hash for "anon"
"5430eeed859cad61d925097ec4f53246"
"1ccf1ab6b9802b09a313be1478a4d614")
# creates a database connection for each thread # creates a database connection for each thread
@ -41,32 +46,27 @@ def api_method(function):
try: try:
# read in the body from the request to a string... # read in the body from the request to a string...
body = str(cherrypy.request.body.read(), "utf8") body = str(cherrypy.request.body.read(), "utf8")
# is it empty? not all methods require an input # is it just empty bytes? not all methods require an input
if body: if body:
# if this fucks up, we throw code 0 instead of code 1
body = json.loads(body) body = json.loads(body)
if isinstance(body, dict): if isinstance(body, dict):
# lowercase all of its keys # lowercase all of its keys
body = {str(key).lower(): value for key, value body = {str(key).lower(): value for key, value
in body.items()} in body.items()}
cherrypy.request.json = body else: # would rather a NoneType than b""
body = None
else:
cherrypy.request.json = None
username = cherrypy.request.headers.get("User") username = cherrypy.request.headers.get("User")
auth = cherrypy.request.headers.get("Auth") auth = cherrypy.request.headers.get("Auth")
anon = False
if not username and not auth: if (username and not auth) or (auth and not username):
user = db.anon_object
anon = True
elif not username or not auth:
return json.dumps(schema.error(5, return json.dumps(schema.error(5,
"User or Auth was given without the other.")) "User or Auth was given without the other."))
if not anon: elif not username and not auth:
user = db.anon
else:
user = db.user_resolve(cherrypy.thread_data.db, username) user = db.user_resolve(cherrypy.thread_data.db, username)
if not user: if not user:
raise BBJUserError("User %s is not registered" % username) raise BBJUserError("User %s is not registered" % username)
@ -75,24 +75,28 @@ def api_method(function):
return json.dumps(schema.error(5, return json.dumps(schema.error(5,
"Invalid authorization key for user.")) "Invalid authorization key for user."))
cherrypy.thread_data.user = user # api_methods may choose to bind a usermap into the thread_data
cherrypy.thread_data.anon = anon # which will send it off with the response
response = function(*args, **kwargs) cherrypy.thread_data.usermap = {}
# TODO: Why in kek's name is self needing to be supplied a value positionally?
except json.JSONDecodeError as e: value = function(None, body, cherrypy.thread_data.db, user)
response = schema.error(0, str(e)) response = schema.response(value, cherrypy.thread_data.usermap)
except BBJException as e: except BBJException as e:
response = e.schema response = e.schema
except json.JSONDecodeError as e:
response = schema.error(0, str(e))
except Exception as e: except Exception as e:
error_id = uuid1().hex error_id = uuid1().hex
response = schema.error(1, response = schema.error(1,
"Internal server error: code {}. Tell ~desvox (the code too)" "Internal server error: code {}. {}"
.format(error_id)) .format(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))
print("logged code 1 exception " + error_id)
finally: finally:
return json.dumps(response) return json.dumps(response)
@ -117,9 +121,7 @@ def create_usermap(connection, obj):
user_id, user_id,
externalize=True, externalize=True,
return_false=False) return_false=False)
for user_id in { for user_id in {item["author"] for item in obj}
item["author"] for item in obj
}
} }
@ -154,111 +156,148 @@ APICONFIG = {
class API(object): class API(object):
@api_method @api_method
@cherrypy.expose @cherrypy.expose
def get_me(self, *args, **kwargs): def user_register(self, args, database, user, **kwargs):
"""
Register a new user into the system and return the new object.
Requires the string arguments `user_name` and `auth_hash`
"""
validate(args, ["user_name", "auth_hash"])
return db.user_register(
database, args["user_name"], args["auth_hash"])
@api_method
@cherrypy.expose
def user_update(self, args, database, user, **kwargs):
"""
Receives new parameters and assigns them to the user_object
in the database. The following new parameters can be supplied:
`user_name`, `auth_hash`, `quip`, `bio`, and `color`. Any number
of them may be supplied.
The newly updated user object is returned on success.
"""
validate(args, []) # just make sure its not empty
return db.user_update(database, user, args)
@api_method
@cherrypy.expose
def get_me(self, args, database, user, **kwargs):
""" """
Requires no arguments. Returns your internal user object, Requires no arguments. Returns your internal user object,
including your authorization hash. including your authorization hash.
""" """
return schema.response(cherrypy.thread_data.user) return user
@api_method @api_method
@cherrypy.expose @cherrypy.expose
def user_get(self, *args, **kwargs): def user_get(self, args, database, user, **kwargs):
""" """
Retreive an external user object for the given `user`. Retreive an external user object for the given `user`.
Can be a user_id or user_name. Can be a user_id or user_name.
""" """
args = cherrypy.request.json
validate(args, ["user"]) validate(args, ["user"])
return schema.response(db.user_resolve( return db.user_resolve(
cherrypy.thread_data.db, database, args["user"], return_false=False, externalize=True)
args["user"],
return_false=False,
externalize=True))
@api_method @api_method
@cherrypy.expose @cherrypy.expose
def thread_index(self, *args, **kwargs): def thread_index(self, args, database, user, **kwargs):
threads = db.thread_index(cherrypy.thread_data.db) """
usermap = create_usermap(cherrypy.thread_data.db, threads) Return an array with all the threads, ordered by most recent activity.
return schema.response(threads, usermap) Requires no arguments.
"""
threads = db.thread_index(database)
cherrypy.thread_data.usermap = create_usermap(database, threads)
return threads
@api_method @api_method
@cherrypy.expose @cherrypy.expose
def thread_create(self, *args, **kwargs): def thread_create(self, args, database, user, **kwargs):
args = cherrypy.request.json """
Creates a new thread and returns it. Requires the non-empty
string arguments `body` and `title`
"""
validate(args, ["body", "title"]) validate(args, ["body", "title"])
thread = db.thread_create( thread = db.thread_create(
cherrypy.thread_data.db, database, user["user_id"], args["body"], args["title"])
cherrypy.thread_data.user["user_id"], cherrypy.thread_data.usermap = {user["user_id"]: user}
args["body"], args["title"]) return thread
usermap = {
cherrypy.thread_data.user["user_id"]:
cherrypy.thread_data.user
}
return schema.response(thread, usermap)
@api_method @api_method
@cherrypy.expose @cherrypy.expose
def thread_reply(self, *args, **kwargs): def thread_reply(self, args, database, user, **kwargs):
args = cherrypy.request.json """
Creates a new reply for the given thread and returns it.
Requires the string arguments `thread_id` and `body`
"""
validate(args, ["thread_id", "body"]) validate(args, ["thread_id", "body"])
return schema.response(db.thread_reply( return db.thread_reply(
cherrypy.thread_data.db, database, user["user_id"], args["thread_id"], args["body"])
cherrypy.thread_data.user["user_id"],
args["thread_id"], args["body"]))
@api_method @api_method
@cherrypy.expose @cherrypy.expose
def thread_load(self, *args, **kwargs): def thread_load(self, args, database, user, **kwargs):
args = cherrypy.request.json """
Returns the thread object with all of its messages loaded.
Requires the argument `thread_id`
"""
validate(args, ["thread_id"]) validate(args, ["thread_id"])
thread = db.thread_get(database, args["thread_id"])
thread = db.thread_get( cherrypy.thread_data.usermap = \
cherrypy.thread_data.db, create_usermap(database, thread["messages"])
args["thread_id"]) return thread
usermap = create_usermap(
cherrypy.thread_data.db,
thread["messages"])
return schema.response(thread, usermap)
@api_method @api_method
@cherrypy.expose @cherrypy.expose
def user_register(self, *args, **kwargs): def edit_post(self, args, database, user, **kwargs):
args = cherrypy.request.json """
validate(args, ["user_name", "auth_hash"]) Replace a post with a new body. Requires the arguments
return schema.response(db.user_register( `thread_id`, `post_id`, and `body`. This method verifies
cherrypy.thread_data.db, that the user can edit a post before commiting the change,
args["user_name"], otherwise an error object is returned whose description
args["auth_hash"])) should be shown to the user.
To perform sanity checks without actually attempting to
edit a post, use `edit_query`
Returns the new message object.
"""
if user == db.anon:
raise BBJUserError("Anons cannot edit messages.")
validate(args, ["body", "thread_id", "post_id"])
return message_edit_commit(
database, user["user_id"], args["thread_id"], args["post_id"], args["body"])
@api_method @api_method
@cherrypy.expose @cherrypy.expose
def edit_query(self, *args, **kwargs): def edit_query(self, args, database, user, **kwargs):
args = cherrypy.request.json """
Queries the database to ensure the user can edit a given
message. Requires the arguments `thread_id` and `post_id`
(does not require a new body)
Returns either boolean true or the current message object
"""
if user == db.anon:
raise BBJUserError("Anons cannot edit messages.")
validate(args, ["thread_id", "post_id"]) validate(args, ["thread_id", "post_id"])
return schema.response(message_edit_query( return message_edit_query(
cherrypy.thread_data.db, database, user["user_id"], args["thread_id"], args["post_id"])
cherrypy.thread_data.user["user_id"],
args["thread_id"],
args["post_id"]))
@cherrypy.expose @cherrypy.expose
def test(self, *args, **kwargs): def test(self, **kwargs):
print(cherrypy.request.body.read()) print(cherrypy.request.body.read())
return "{\"wow\": \"good job!\"}" return "{\"wow\": \"jolly good show!\"}"
@ -267,4 +306,4 @@ def run():
if __name__ == "__main__": if __name__ == "__main__":
print("wew") print("yo lets do that -i shit mang")

158
src/db.py
View File

@ -25,16 +25,7 @@ import pickle
import json import json
import os import os
anon_object = None anon = None
anon_credentials = \
("anonymous",
"5430eeed859cad61d925097ec4f53246"
"1ccf1ab6b9802b09a313be1478a4d614")
# this is the hash for "anon"
# if os.path.exists("cache"):
# os.rmdir("cache")
# os.mkdir("cache")
### THREADS ### ### THREADS ###
@ -267,11 +258,6 @@ def user_externalize(user_object):
return user_object return user_object
def user_auth(auth_hash, user_object):
# nominating this for most useless function in the program
return auth_hash == user_object["auth_hash"]
### SANITY CHECKS ### ### SANITY CHECKS ###
def contains_nonspaces(string): def contains_nonspaces(string):
@ -358,145 +344,3 @@ def validate(keys_and_values):
"Color specification out of range (int 0-8)") "Color specification out of range (int 0-8)")
return True return True
### OLD SHIT ###
# def thread_index(key="lastmod", markup=True):
# result = list()
# for ID in path.os.listdir(path.join(PATH, "threads")):
# thread = thread_load(ID, markup)
# thread.pop("replies")
# result.append(thread)
# return sorted(result, key=lambda i: i[key], reverse=True)
#
#
#
#
# def thread_load(ID, markup=True):
# try:
# with open(path.join(PATH, "threads", ID), "r") as f:
# return json.loads(f.read())
# except FileNotFoundError:
# return False
#
#
# def thread_dump(ID, obj):
# with open(path.join(PATH, "threads", ID), "w") as f:
# f.write(json.dumps(obj))
#
#
# def thread_reply(ID, author, body):
# thread = thread_load(ID)
# if not thread:
# return schema.error(7, "Requested thread does not exist.")
#
# thread["reply_count"] += 1
# thread["lastmod"] = time()
#
# if thread["replies"]:
# lastpost = thread["replies"][-1]["post_id"]
# else:
# lastpost = 1
#
# reply = schema.reply(lastpost + 1, author, body)
# thread["replies"].append(reply)
# thread_dump(ID, thread)
# return reply
#
#
# def index_reply(reply_list, post_id):
# for index, reply in enumerate(reply_list):
# if reply["post_id"] == post_id:
# return index
# else:
# raise IndexError
#
#
# def edit_handler(json, thread=None):
# try:
# target_id = json["post_id"]
# if not thread:
# thread = thread_load(json["thread_id"])
# if not thread:
# return False, schema.error(7, "Requested thread does not exist.")
#
#
# if target_id == 1:
# target = thread
# else:
# target = thread["replies"][
# index_reply(thread["replies"], target_id)]
#
# if not user_is_admin(json["user"]):
# if json["user"] != target["author"]:
# return False, schema.error(10,
# "non-admin attempt to edit another user's message")
#
# elif (time() - target["created"]) > 86400:
# return False, schema.error(9,
# "message is too old to edit (24hr limit)")
#
# return True, target
#
# except IndexError:
# return False, schema.error(3, "post_id out of bounds for requested thread")
#
#
# ### USER MANAGEMENT ###
#
# def user_dbdump(dictionary):
# with open(path.join(PATH, "userdb"), "w") as f:
# f.write(json.dumps(dictionary))
#
#
# def user_resolve(name_or_id):
# check = USERDB.get(name_or_id)
# try:
# if check:
# return name_or_id
# else:
# return USERDB["namemap"][name_or_id]
# except KeyError:
# return False
#
#
# def user_register(auth_hash, name, quip, bio):
# if USERDB["namemap"].get(name):
# return schema.error(4, "Username taken.")
#
# for ok, error in [
# user_namecheck(name),
# user_authcheck(auth_hash),
# user_quipcheck(quip),
# user_biocheck(bio)]:
#
# if not ok:
# return error
#
# ID = uuid1().hex
# scheme = schema.user_internal(ID, auth_hash, name, quip, bio, False)
# USERDB.update({ID: scheme})
# USERDB["namemap"].update({name: ID})
# user_dbdump(USERDB)
# return scheme
#
#
# def user_get(ID):
# user = USERDB[ID]
# return schema.user_external(
# ID, user["name"], user["quip"],
# user["bio"], user["admin"])
#
#
# def user_auth(ID, auth_hash):
# return auth_hash == USERDB[ID]["auth_hash"]
#
#
#
#
# def user_update(ID, **params):
# USERDB[ID].update(params)
# return USERDB[ID]
#
#