aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBlake DeMarcy <ofunknowndescent@gmail.com>2017-05-02 17:06:02 -0500
committerBlake DeMarcy <ofunknowndescent@gmail.com>2017-05-02 17:06:02 -0500
commit52c47e75297f456f7765f34fffcbd75113ad2d67 (patch)
treec58dcdec4f3ca8615fda73cf26c88e74af13ce30
parentecef425e8b4f669193ac70ff9f1931959e7f6381 (diff)
downloadbbj-52c47e75297f456f7765f34fffcbd75113ad2d67.tar.gz
prelimiary changes for automated documentation
-rw-r--r--document_endpoints.py125
-rw-r--r--documentation/docs/api_overview.md315
-rw-r--r--documentation/docs/index.md26
-rw-r--r--documentation/mkdocs.yml2
-rw-r--r--server.py45
5 files changed, 497 insertions, 16 deletions
diff --git a/document_endpoints.py b/document_endpoints.py
new file mode 100644
index 0000000..71d2e31
--- /dev/null
+++ b/document_endpoints.py
@@ -0,0 +1,125 @@
+"""
+This is a small script that creates the endpoint doc page. It should be
+evoked from the command line each time changes are made. It writes
+to ./documentation/docs/api_overview.md
+
+The code used in this script is the absolute minimum required to
+get the job done; it can be considered a crude hack at best. I am
+more interested in writing good documentation than making sure that
+the script that shits it out is politcally correct ;)
+"""
+
+from server import API
+import pydoc
+
+body = """
+# How to BBJ?
+
+## Input
+
+BBJ is interacted with entirely through POST requests, whose bodies are
+json objects.
+
+The endpoints, all listed below, can be contacted at the path /api/ relative
+to the root of where BBJ is hosted. If bbj is hosted on a server on port 80
+at the root:
+
+`http://server.com/api/endpoint_here`
+
+The body of your request contains all of it's argument fields, instead of
+using URL parameters. As a demonstration, to call `thread_create`,
+it requires two arguments: `title`, and `body`. We put those argument
+names at the root of the json object, and their values are the info
+passed into the API for that spot. Your input will look like this:
+
+```json
+{
+ "title": "Hello world!!",
+ "body": "Hi! I am exploring this cool board thing!!"
+}
+```
+
+And you will POST this body to `http://server.com:PORT/api/thread_create`.
+
+A few endpoints do not require any arguments. These can still be POSTed to,
+but the body may be completely empty or an empty json object. You can even
+GET these if you so choose.
+
+For all endpoints, argument keys that are not consumed by the endpoint are
+ignored. Posting an object with a key/value pair of `"sandwich": True` will
+not clog up any pipes :) In the same vein, endpoints who dont take arguments
+don't care if you supply them anyway.
+
+## Output
+
+BBJ returns data in a consistently formatted json object. The base object
+has three keys: `data`, `usermap`, and `error`. Visualizied:
+
+```javascript
+{
+ "error": false, // boolean false or error object
+ "data": null, // null or the requested data from endpoint.
+ "usermap": {} // potentially empty object, maps user_ids to user objects
+}
+
+// If "error" is true, it looks like this:
+
+{
+ "error": {
+ "code": // an integer from 0 to 5,
+ "description": // a string describing the error in detail.
+ }
+ "data": null // ALWAYS null if error is not false
+ "usermap": {} // ALWAYS empty if error is not false
+}
+```
+
+### data
+
+`data` is what the endpoint actually returns. The type of contents vary
+by endpoint and are documented below. If an endpoint says it returns a
+boolean, it will look like `"data": True`. If it says it returns an array,
+it will look like `"data": ["stuff", "goes", "here"]`
+
+### usermap
+
+The usermap is a json object mapping user_ids within `data` to full user
+objects. BBJ handles users entirely by an ID system, meaning any references
+to them inside of response data will not include vital information like their
+username, or their profile information. Instead, we fetch those values from
+this usermap object. All of it's root keys are user_id's and their values
+are user objects. It should be noted that the anonymous user has it's own
+ID and profile object as well.
+
+### error
+
+`error` is typically `false`. If it is __not__ false, then the request failed
+and the json object that `error` contains should be inspected. (see the above
+visualation) Errors follow a strict code system, making it easy for your client
+to map these responses to native exception types or signals in your language of
+choice. See [the full error page](errors.md) for details.
+
+
+"""
+
+endpoints = [
+ ref for name, ref in API.__dict__.items()
+ if hasattr(ref, "exposed")
+]
+
+types = {
+ function.doctype: list() for function in endpoints
+}
+
+for function in endpoints:
+ types[function.doctype].append(function)
+
+for doctype in sorted(types.keys()):
+ body += "# %s\n\n" % doctype
+ funcs = sorted(types[doctype], key=lambda _: _.__name__)
+ for f in funcs:
+ body += "## %s\n\n%s\n\n" % (f.__name__, pydoc.getdoc(f))
+ body += "\n\n--------\n\n"
+
+with open("documentation/docs/api_overview.md", "w") as output:
+ output.write(body)
diff --git a/documentation/docs/api_overview.md b/documentation/docs/api_overview.md
new file mode 100644
index 0000000..3f31e9d
--- /dev/null
+++ b/documentation/docs/api_overview.md
@@ -0,0 +1,315 @@
+
+# How to BBJ?
+
+## Input
+
+BBJ is interacted with entirely through POST requests, whose bodies are
+json objects.
+
+The endpoints, all listed below, can be contacted at the path /api/ relative
+to the root of where BBJ is hosted. If bbj is hosted on a server on port 80
+at the root:
+
+`http://server.com/api/endpoint_here`
+
+The body of your request contains all of it's argument fields, instead of
+using URL parameters. As a demonstration, lets call `thread_create`.
+It requires two arguments: `title`, and `body`. We put those argument
+names at the root of the json object, and their values are the info
+passed into the API for that spot. Your input will look like this:
+
+```json
+{
+ "title": "Hello world!!",
+ "body": "Hi! I am exploring this cool board thing!!"
+}
+```
+
+And you will POST this body to `http://server.com:PORT/api/thread_create`.
+
+A few endpoints do not require any arguments. These can still be POSTed to,
+but the body may be completely empty or an empty json object. You can even
+GET these if you so choose.
+
+For all endpoints, argument keys that are not consumed by the endpoint are
+ignored. Posting an object with a key/value pair of `"sandwich": True` will
+not clog up any pipes :) In the same vein, endpoints who dont take arguments
+don't care if you supply them anyway.
+
+## Output
+
+BBJ returns data in a consistently formatted json object. The base object
+has three keys: `data`, `usermap`, and `error`. Visualizied:
+
+```javascript
+{
+ "error": false, // boolean false or error object
+ "data": null, // null or the requested data from endpoint.
+ "usermap": {} // a potentially empty object mapping user_ids to their objects
+}
+
+// If "error" is true, it looks like this:
+
+{
+ "error": {
+ "code": // an integer from 0 to 5,
+ "description": // a string describing the error in detail.
+ }
+ "data": null // ALWAYS null if error is not false
+ "usermap": {} // ALWAYS empty if error is not false
+}
+```
+
+### data
+
+`data` is what the endpoint actually returns. The type of contents vary
+by endpoint and are documented below. If an endpoint says it returns a
+boolean, it will look like `"data": True`. If it says it returns an array,
+it will look like `"data": ["stuff", "goes", "here"]`
+
+### usermap
+
+The usermap is a json object mapping user_ids within `data` to full user
+objects. BBJ handles users entirely by an ID system, meaning any references
+to them inside of response data will not include vital information like their
+username, or their profile information. Instead, we fetch those values from
+this usermap object. All of it's root keys are user_id's and their values
+are user objects. It should be noted that the anonymous user has it's own
+ID and profile object as well.
+
+### error
+
+`error` is typically `null`. If it is __not__ null, then the request failed
+and the json object that `error` contains should be inspected. (see the above
+visualation) Errors follow a strict code system, making it easy for your client
+to map these responses to native exception types or signals in your language of
+choice. See [the full error page](errors.md) for details.
+
+
+# Authorization
+
+## check_auth
+
+Takes the arguments `target_user` and `target_hash`, and
+returns boolean true or false whether the hash is valid.
+
+
+
+--------
+
+# Threads & Messages
+
+## delete_post
+
+Requires the arguments `thread_id` and `post_id`.
+
+Delete a message from a thread. The same rules apply
+here as `edit_post` and `edit_query`: the logged in user
+must either be the one who posted the message within 24hrs,
+or have admin rights. The same error descriptions and code
+are returned on falilure. Boolean true is returned on
+success.
+
+If the post_id is 0, the whole thread is deleted.
+
+## edit_post
+
+Replace a post with a new body. Requires the arguments
+`thread_id`, `post_id`, and `body`. This method verifies
+that the user can edit a post before commiting the change,
+otherwise an error object is returned whose description
+should be shown to the user.
+
+To perform sanity checks and retrieve the unformatted body
+of a post without actually attempting to replace it, use
+`edit_query` first.
+
+Optionally you may also include the argument `send_raw` to
+set the message's formatting flag. However, if this is the
+only change you would like to make, you should use the
+endpoint `set_post_raw` instead.
+
+Returns the new message object.
+
+## edit_query
+
+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 the original message object without any formatting
+on success. Returns a descriptive code 4 otherwise.
+
+## message_feed
+
+Returns a special object representing all activity on the board since
+the argument `time`, a unix/epoch timestamp.
+
+{
+ "threads": {
+ "thread_id": {
+ ...thread object
+ },
+ ...more thread_id/object pairs
+ },
+ "messages": [...standard message object array sorted by date]
+}
+
+The message objects in "messages" are the same objects returned
+in threads normally. They each have a thread_id parameter, and
+you can access metadata for these threads by the "threads" object
+which is also provided.
+
+The "messages" array is already sorted by submission time, newest
+first. The order in the threads object is undefined and you should
+instead use their `last_mod` attribute if you intend to list them
+out visually.
+
+You may optionally provide a `format` argument: this is treated
+the same way as the `thread_load` endpoint and you should refer
+to its documentation for more info.
+
+## set_post_raw
+
+Requires the boolean argument of `value`, string argument
+`thread_id`, and integer argument `post_id`. `value`, when false,
+means that the message will be passed through message formatters
+before being sent to clients. When `value` is true, this means
+it will never go through formatters, all of its whitespace is
+sent to clients verbatim and expressions are not processed.
+
+The same rules for editing messages (see `edit_query`) apply here
+and the same error objects are returned for violations.
+
+You may optionally set this value as well when using `edit_post`,
+but if this is the only change you want to make to the message,
+using this endpoint instead is preferable.
+
+## set_thread_pin
+
+Requires the arguments `thread_id` and `value`. `value`
+must be a boolean of what the pinned status should be.
+This method requires that the caller is logged in and
+has admin status on their account.
+
+Returns the same boolean you supply as `value`
+
+## thread_create
+
+Creates a new thread and returns it. Requires the non-empty
+string arguments `body` and `title`.
+
+If the argument `send_raw` is specified and has a non-nil
+value, the OP message will never recieve special formatting.
+
+## thread_index
+
+Return an array with all the threads, ordered by most recent activity.
+Requires no arguments.
+
+Optionally, you may supply the argument `include_op`, which, when
+non-nil, will include a "messages" key with the object, whose sole
+content is the original message (post_id 0).
+
+## thread_load
+
+Returns the thread object with all of its messages loaded.
+Requires the argument `thread_id`. `format` may also be
+specified as a formatter to run the messages through.
+Currently only "sequential" is supported.
+
+You may also supply the parameter `op_only`. When it's value
+is non-nil, the messages array will only include post_id 0 (the first)
+
+## thread_reply
+
+Creates a new reply for the given thread and returns it.
+Requires the string arguments `thread_id` and `body`
+
+If the argument `send_raw` is specified and has a non-nil
+value, the message will never recieve special formatting.
+
+
+
+--------
+
+# Tools
+
+## db_validate
+
+Requires the arguments `key` and `value`. Returns an object
+with information about the database sanity criteria for
+key. This can be used to validate user input in the client
+before trying to send it to the server.
+
+If the argument `error` is supplied with a non-nil value,
+the server will return a standard error object on failure
+instead of the special object described below.
+
+The returned object has two keys:
+
+{
+ "bool": true/false,
+ "description": null/"why this value is bad"
+}
+
+If bool == false, description is a string describing the
+problem. If bool == true, description is null and the
+provided value is safe to use.
+
+## format_message
+
+Requires the arguments `body` and `format`. Applies
+`format` to `body` and returns the new object. See
+`thread_load` for supported specifications for `format`.
+
+## user_map
+
+Returns an array with all registered user_ids, with the usermap
+object populated by their full objects.
+
+
+
+--------
+
+# Users
+
+## get_me
+
+Requires no arguments. Returns your internal user object,
+including your authorization hash.
+
+## is_admin
+
+Requires the argument `target_user`. Returns a boolean
+of whether that user is an admin.
+
+## user_get
+
+Retreive an external user object for the given `user`.
+Can be a user_id or user_name.
+
+## user_is_registered
+
+Takes the argument `target_user` and returns true or false
+whether they are in the system or not.
+
+## user_register
+
+Register a new user into the system and return the new object.
+Requires the string arguments `user_name` and `auth_hash`.
+Do not send User/Auth headers with this method.
+
+## user_update
+
+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.
+
+
+
+--------
+
diff --git a/documentation/docs/index.md b/documentation/docs/index.md
index da37213..4776965 100644
--- a/documentation/docs/index.md
+++ b/documentation/docs/index.md
@@ -1,17 +1,19 @@
-# Welcome to MkDocs
+# Bulletin Butter & Jelly
+## A simple community textboard
+### BBJ is trivial collection of python scripts and database queries that miraculously shit out a fully functional client-server textboard.
-For full documentation visit [mkdocs.org](http://mkdocs.org).
+See also: the [GitHub repository](https://github.com/desvox/bbj).
-## Commands
+BBJ is heavily inspired by image boards like 4chan, but it offers a simple
+account system to allow users to identify themselves and set profile
+attributes like a more traditional forum. Registration is optional and there
+are only minimal restrictions on anonymous participation.
-* `mkdocs new [dir-name]` - Create a new project.
-* `mkdocs serve` - Start the live-reloading docs server.
-* `mkdocs build` - Build the documentation site.
-* `mkdocs help` - Print this help message.
+![screenshot](https://tilde.town/~desvox/bbj/screenshot.png)
-## Project layout
+Being a command-line-oriented text board, BBJ has no avatars or file sharing
+capabilties, so its easier to administrate and can't be used to distribute illegal
+content like imageboards. It has very few dependancies and is easy to set up.
- mkdocs.yml # The configuration file.
- docs/
- index.md # The documentation homepage.
- ... # Other markdown pages, images and other files.
+The API is simple and doesn't use require complex authorization schemes or session management.
+It is fully documented on this site.
diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml
index c97182f..2feacb8 100644
--- a/documentation/mkdocs.yml
+++ b/documentation/mkdocs.yml
@@ -1 +1 @@
-site_name: My Docs
+site_name: BBJ API Documentation
diff --git a/server.py b/server.py
index 45a07cc..232e842 100644
--- a/server.py
+++ b/server.py
@@ -20,6 +20,7 @@ app_config = {
"debug": False
}
+
try:
with open("config.json") as _conf:
app_config.update(json.load(_conf))
@@ -191,6 +192,11 @@ class API(object):
validate(args, ["user_name", "auth_hash"])
return db.user_register(
database, args["user_name"], args["auth_hash"])
+ user_register.doctype = "Users"
+ user_register.args = [
+ ("user_name", "string: the desired display name"),
+ ("auth_hash", "string: a sha256 hash of a password")
+ ]
@api_method
def user_update(self, args, database, user, **kwargs):
@@ -205,6 +211,15 @@ class API(object):
no_anon_hook(user, "Anons cannot modify their account.")
validate(args, []) # just make sure its not empty
return db.user_update(database, user, args)
+ user_update.doctype = "Users"
+ user_update.args = [
+ ("Any of the following may be submitted:", ""),
+ ("user_name", "string: a desired display name"),
+ ("auth_hash", "string: sha256 hash for a new password"),
+ ("quip", "string: a short string that can be used as a signature"),
+ ("bio", "string: a user biography for their profile"),
+ ("color", "integer: 0-6, a display color for the user")
+ ]
@api_method
def get_me(self, args, database, user, **kwargs):
@@ -213,6 +228,8 @@ class API(object):
including your authorization hash.
"""
return user
+ get_me.doctype = "Users"
+ get_me.args = [("", "")]
@api_method
def user_map(self, args, database, user, **kwargs):
@@ -232,16 +249,22 @@ class API(object):
for user in users
}
return list(users)
+ user_map.doctype = "Tools"
+ user_map.args = [("", "")]
@api_method
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 `target_user`.
Can be a user_id or user_name.
"""
- validate(args, ["user"])
+ validate(args, ["target_user"])
return db.user_resolve(
- database, args["user"], return_false=False, externalize=True)
+ database, args["target_user"], return_false=False, externalize=True)
+ user_get.doctype = "Users"
+ user_get.args = [
+ ("user", "string: either a user_name or a user_id")
+ ]
@api_method
def user_is_registered(self, args, database, user, **kwargs):
@@ -251,6 +274,8 @@ class API(object):
"""
validate(args, ["target_user"])
return bool(db.user_resolve(database, args["target_user"]))
+ user_is_registered.doctype = "Users"
+ # user_is_registered.args =
@api_method
def check_auth(self, args, database, user, **kwargs):
@@ -262,6 +287,7 @@ class API(object):
user = db.user_resolve(
database, args["target_user"], return_false=False)
return args["target_hash"].lower() == user["auth_hash"].lower()
+ check_auth.doctype = "Authorization"
@api_method
def thread_index(self, args, database, user, **kwargs):
@@ -277,6 +303,7 @@ class API(object):
threads = db.thread_index(database, include_op=op)
cherrypy.thread_data.usermap = create_usermap(database, threads, True)
return threads
+ thread_index.doctype = "Threads & Messages"
@api_method
def message_feed(self, args, database, user, **kwargs):
@@ -317,6 +344,7 @@ class API(object):
do_formatting(args.get("format"), feed["messages"])
return feed
+ message_feed.doctype = "Threads & Messages"
@api_method
def thread_create(self, args, database, user, **kwargs):
@@ -335,6 +363,7 @@ class API(object):
cherrypy.thread_data.usermap = \
create_usermap(database, thread["messages"])
return thread
+ thread_create.doctype = "Threads & Messages"
@api_method
def thread_reply(self, args, database, user, **kwargs):
@@ -350,6 +379,7 @@ class API(object):
return db.thread_reply(
database, user["user_id"], args["thread_id"],
args["body"], args.get("send_raw"))
+ thread_reply.doctype = "Threads & Messages"
@api_method
def thread_load(self, args, database, user, **kwargs):
@@ -369,6 +399,7 @@ class API(object):
create_usermap(database, thread["messages"])
do_formatting(args.get("format"), thread["messages"])
return thread
+ thread_load.doctype = "Threads & Messages"
@api_method
def edit_post(self, args, database, user, **kwargs):
@@ -395,6 +426,7 @@ class API(object):
return db.message_edit_commit(
database, user["user_id"], args["thread_id"],
args["post_id"], args["body"], args.get("send_raw"))
+ edit_post.doctype = "Threads & Messages"
@api_method
def delete_post(self, args, database, user, **kwargs):
@@ -414,6 +446,7 @@ class API(object):
validate(args, ["thread_id", "post_id"])
return db.message_delete(
database, user["user_id"], args["thread_id"], args["post_id"])
+ delete_post.doctype = "Threads & Messages"
@api_method
def set_post_raw(self, args, database, user, **kwargs):
@@ -438,6 +471,7 @@ class API(object):
database, user["user_id"],
args["thread_id"], args["post_id"],
None, args["value"], None)
+ set_post_raw.doctype = "Threads & Messages"
@api_method
def is_admin(self, args, database, user, **kwargs):
@@ -449,6 +483,7 @@ class API(object):
user = db.user_resolve(
database, args["target_user"], return_false=False)
return user["is_admin"]
+ is_admin.doctype = "Users"
@api_method
def edit_query(self, args, database, user, **kwargs):
@@ -464,6 +499,7 @@ class API(object):
validate(args, ["thread_id", "post_id"])
return db.message_edit_query(
database, user["user_id"], args["thread_id"], args["post_id"])
+ edit_query.doctype = "Threads & Messages"
@api_method
def format_message(self, args, database, user, **kwargs):
@@ -476,6 +512,7 @@ class API(object):
message = [{"body": args["body"]}]
do_formatting(args["format"], message)
return message[0]["body"]
+ format_message.doctype = "Tools"
@api_method
def set_thread_pin(self, args, database, user, **kwargs):
@@ -491,6 +528,7 @@ class API(object):
if not user["is_admin"]:
raise BBJUserError("Only admins can set thread pins")
return db.set_thread_pin(database, args["thread_id"], args["value"])
+ set_thread_pin.doctype = "Threads & Messages"
@api_method
def db_validate(self, args, database, user, **kwargs):
@@ -527,6 +565,7 @@ class API(object):
response["bool"] = False
response["description"] = e.description
return response
+ db_validate.doctype = "Tools"
def api_http_error(status, message, traceback, version):
Un proyecto texto-plano.xyz