|
| 1 | + |
| 2 | +# Endpoint documentation |
| 3 | + |
| 4 | +This directory contains documentation for our API endpoints, organized into files based on the |
| 5 | +first component of the URI path, e.g. `users.md`, `courses.md`, etc. There are a few extra files too: |
| 6 | +- `models.md`: defines all of the data schemas for request and response bodies, and the data types that comprise them. |
| 7 | +- `pagination.md`: describes how to traverse paginated responses. |
| 8 | +- `request.md`: describes common request properties that would be tedious and error-prone to repeat for every endpoint. |
| 9 | +- `responses.md`: describes common responses and their meanings, that would be tedious and error-prone to |
| 10 | + repeat for every endpoint. |
| 11 | + |
| 12 | +## Documenting an endpoint |
| 13 | + |
| 14 | +Throughout this section, we'll be using the [sessions.md](sessions.md) file for examples of each of |
| 15 | +the components of well-written endpoint documentation. Please refer to that file when you need more detail. Each |
| 16 | +subsection that follows will be about one piece of the documentation, in order from top to bottom. |
| 17 | + |
| 18 | +### File heading |
| 19 | + |
| 20 | +The file should start with a level-one heading based on the name of the file: |
| 21 | + |
| 22 | +```markdown |
| 23 | +# Session endpoints |
| 24 | +``` |
| 25 | + |
| 26 | +### Endpoint heading |
| 27 | + |
| 28 | +Each endpoint should have a level-two heading with a human-readable, English-only name. This allows us to |
| 29 | +easily link to specific endpoints with fragment IDs, like the [create session](sessions.md#create-session) endpoint. |
| 30 | +Deprecated endpoints should be rendered with a ~~strikethrough~~, as in the second example. |
| 31 | + |
| 32 | +```markdown |
| 33 | +## Create session |
| 34 | + |
| 35 | +## ~~Start course~~ |
| 36 | +``` |
| 37 | + |
| 38 | +### Endpoint summary |
| 39 | + |
| 40 | +The heading should be immediately followed by a one-line summary of the endpoint. It needs to be short so |
| 41 | +that it can be read while skimming the page. Deprecation should also be indicated here, with **[DEPRECATED]** |
| 42 | +before the summary, as in the second example. |
| 43 | + |
| 44 | +```markdown |
| 45 | +Create a session for a specified user. |
| 46 | + |
| 47 | +**[DEPRECATED]** Join a course as a learner. |
| 48 | +``` |
| 49 | + |
| 50 | +### Request properties |
| 51 | + |
| 52 | +The summary should be followed by the _request properties_ table, which provides key facts about making a request to |
| 53 | +the endpoint. Formatting it as a table and keeping it close to the endpoint heading means that it will be |
| 54 | +able to be read at a glance. |
| 55 | + |
| 56 | +```markdown |
| 57 | +Request property | Spec |
| 58 | +-----------------|----- |
| 59 | +Action | POST /sessions |
| 60 | +[`SID` header](request.md#sid-header) | |
| 61 | +Body model | [Request session](models.md#request-session); `expiresIn` optional; either `userId` or `email` must be specified |
| 62 | +Scala | `object CreateSession` |
| 63 | +``` |
| 64 | + |
| 65 | +Here's a meta-table describing each property: |
| 66 | + |
| 67 | +Request property | Explanation |
| 68 | +-----------------|------------ |
| 69 | +Action | The HTTP method, URI path, and query parameters, if any. The value should be in normal text, with URI path parameters rendered as `code` and linking to the appropriate type in `models.md`. The format of query parameter values hasn't been decided yet, but this table entry will be updated once it is. |
| 70 | +`SID` header | Requirements that the `SID` header value must satisfy to avoid a 401 response. In most cases this should just be a link to the default requirements, as shown above. If the requirements are different, describe them in the table. |
| 71 | +Pagination parameters | If present, specifies that the endpoint is paginated and the behavior can be controlled via request parameters. This should always just be a link to the pagination description; see [list started courses](courses.md#list-started-courses) for an example. |
| 72 | +Body model | A link to the [models.md](models.md) fragment that describes the request body model, followed by endpoint-specific constraints imposed on that model. Notice that the link to the request body model uses a simple filename and fragment ID. We'll cover model documentation a bit later. |
| 73 | +Scala | The name of the Scala class or object that implements the endpoint. |
| 74 | + |
| 75 | +It's possible that other rows in the table may be needed in some cases, to document other headers, or to provide |
| 76 | +details on query parameters. If a row is used often enough it should be added to this guide, in the table above. |
| 77 | + |
| 78 | +### Endpoint description |
| 79 | + |
| 80 | +This section is free-form, and should be used to describe the behavior of the endpoint in detail. Especially important |
| 81 | +to include are requirements, constraints, or behaviors that are surprising, and also _reasons_ why the endpoint |
| 82 | +works as it does. Here's an example from [create session](sessions.md#create-session): |
| 83 | + |
| 84 | +```markdown |
| 85 | +The most direct way to specify a user is with the `userId` field. Alternatively, an `email` field can be |
| 86 | +provided, which must contain an email address belonging to a user. If the `userId` does not exist, |
| 87 | +or no user can be found with the provided `email`, then the request will fail with a 400 status. |
| 88 | + |
| 89 | +The deprecated `expiresIn` field is no longer used by the API, but will still be validated if provided in |
| 90 | +the request body. It is therefore recommended to avoid including it. |
| 91 | +``` |
| 92 | + |
| 93 | +### Response table |
| 94 | + |
| 95 | +This section describes the responses sent by the endpoint. Again, a table is used to |
| 96 | +keep the information compact on the page. Here's the section for our example endpoint: |
| 97 | + |
| 98 | +```markdown |
| 99 | +Status | Response body spec |
| 100 | +-------|------------------- |
| 101 | +201 | [Response session](models.md#response-session) |
| 102 | +400 | `{"error": 400, "message": "Missing field: userId or email"}` <br> At least one of `userId` and `email` must be provided. |
| 103 | +404 | `{"error": 404, "message": "User '$userId' not found"}` <br> The `userId` field was specified in the request body with an invalid ID. |
| 104 | +403 | `{"error": 403, "message": "Insufficient permissions (must be a partner)"}` <br> The provided `SID` is not a partner key, or does not belong to an authenticator or an admin of an org that the specified user is a member of. |
| 105 | +401 | [Invalid credentials](responses.md#invalid-credentials) |
| 106 | +400 | [Bad request](responses.md#bad-request) |
| 107 | +``` |
| 108 | + |
| 109 | +Notice that the successful response is in the first row, since it is likely to be searched for frequently. It should |
| 110 | +always link to a response body model definition, and can optionally also specify constraints, similar to the request |
| 111 | +body model. The other rows should describe error responses, and always in the same format: body schema, `<br>`, explanation. |
| 112 | +The final rows in the table are for error responses that many endpoints have in common. To avoid error-prone repetition, |
| 113 | +simply link to each response description as shown. Status codes should not be specified, in case they are changed in |
| 114 | +the future. |
| 115 | + |
| 116 | +## Documenting types and models |
| 117 | + |
| 118 | +The [`models.md`](models.md) file describes all of the data that is sent in request and response bodies. It's |
| 119 | +divided into several sections, which are described below, and link to the corresponding sections in `models.md`. |
| 120 | + |
| 121 | +### [Primitive types](models.md#primitive-types) |
| 122 | + |
| 123 | +Assigns names to the basic (non-composite) data types used in requests and responses. It starts with the "fundamental" |
| 124 | +JSON types like [string](models.md#string), [number](models.md#number), and [boolean](models.md#boolean). Any type |
| 125 | +that can be described as constraints on one of the JSON types should also be included here, such as [slug](models.md#slug) |
| 126 | +or [uuid](models.md#uuid). |
| 127 | + |
| 128 | +### [ID types](models.md#id-types) |
| 129 | + |
| 130 | +Since all of the ID types (e.g., user ID, course ID, asset ID, ...) are very similar to each other, it makes more sense |
| 131 | +to document them together in a single table. When linking to an ID type from another place, just use the `#id-types` |
| 132 | +fragment ID. The table format should be pretty self-explanatory. |
| 133 | + |
| 134 | +### [Models](models.md#models) |
| 135 | + |
| 136 | +Every JSON object that appears in a request or response body should be documented in this section. Groups of related |
| 137 | +models get their own sub-sections here, and if there is more than one model in the group, each one gets its own |
| 138 | +sub-sub-section. See [Session](models.md#session) for an example. |
| 139 | + |
| 140 | +Each model is described by listing all of its fields out in a table. Nothing about whether fields are optional or |
| 141 | +required should be documented here; that's up to the individual endpoints to specify. Three columns are defined for |
| 142 | +each row: |
| 143 | + |
| 144 | +Column | Explanation |
| 145 | +-------|------------ |
| 146 | +**Field** | The name of the field in the JSON object. |
| 147 | +**Type** | The type of the field value. This can link to anything defined in this file, including primitive types and models. |
| 148 | +**Description** | A few remarks on what the field is, any global constraints on it, and hopefully why it is useful. |
| 149 | + |
| 150 | +Here's an example, for the [org](models.md#org) model: |
| 151 | + |
| 152 | +```markdown |
| 153 | + |
| 154 | +### Org |
| 155 | + |
| 156 | +A JSON object with the following fields: |
| 157 | + |
| 158 | +Field | Type | Description |
| 159 | +------|------|------------ |
| 160 | +`id` | [org ID](#id-types) | Unique, API-generated identifier. |
| 161 | +`title` | [string](#string) | Word or short phrase naming or describing the org. |
| 162 | +`orgName` | [slug](#slug) | A unique, human-readable user identifier. It is used in the type strings of gadgets created by the org in place of the org ID, if defined. |
| 163 | +`users` | array of [org member](#org-member) | Contains one element for each user belonging to the org. |
| 164 | +``` |
| 165 | + |
| 166 | +## Cryptography (Hashing) library |
| 167 | + |
| 168 | +To Hash passwords, the bouncy castle library's password based key derivation function is used. Extend the trait |
| 169 | +CryptoOps inside the crypto library. See examples of PartnerIDHandler etc. To get a new supersecret seed |
| 170 | +(SODIUM_CHLORIDE) do the following in a console |
| 171 | + |
| 172 | +``` |
| 173 | +import com.versal.restapi.crypto |
| 174 | +crypto.newSalt |
| 175 | +``` |
| 176 | + |
| 177 | +If somebody gives you an ID (say a partner id) and you want to check if its in the database, (assuming no clear |
| 178 | +IDs are stored which will be the final state of the hashing / migration process), do the following in a console |
| 179 | + |
| 180 | +``` |
| 181 | +import com.versal.restapi.crypto.PartnerIDHandler |
| 182 | +PartnerIDHandler.hashPartnerKey("MY_GIVEN_KEY") |
| 183 | +``` |
| 184 | + |
| 185 | +You can look for this hashed key in the hashed_partner table, for example. |
| 186 | + |
| 187 | +## Add a new local partner key |
| 188 | + |
| 189 | +If a developer is running a rest api locally off a prod database dump, and wants to insert a partner key, |
| 190 | +open a db session to the underlying database and a sbt scala console |
| 191 | + |
| 192 | +``` |
| 193 | +import com.versal.restapi.crypto |
| 194 | +crypto.newLocalPartnerKeyInserts(ownerId) |
| 195 | +``` |
| 196 | + |
| 197 | +`ownerId` is a existing user id. |
| 198 | + |
| 199 | +This will give you the new partner key and sql to be run to create it |
| 200 | + |
| 201 | +## Add a new user and partner key for that user |
| 202 | + |
| 203 | +This can be used to seed a new database with no users in it at all - |
| 204 | +open a db session to the underlying database and a sbt scala console |
| 205 | + |
| 206 | +``` |
| 207 | +import com.versal.restapi.support |
| 208 | +support.virginUserAndPartnerKey(3, "Donald", "Trump", "trump@maga.org") |
| 209 | +``` |
0 commit comments