Skip to content

Commit dfc5620

Browse files
committed
new repo for external api doc consumption
0 parents  commit dfc5620

28 files changed

+6316
-0
lines changed

README.md

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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+
```

assets.md

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
2+
# Asset endpoints
3+
4+
## Get asset metadata
5+
6+
Retrieves information about the specified asset.
7+
8+
Request property | Spec
9+
---|---
10+
Action | GET /assets/[`assetId`](models.md#id-types)
11+
[`SID` header](request.md#sid-header) | None.
12+
Body model | _no body_
13+
Scala | `class GetAsset`
14+
15+
If `assetId` does not identify an asset, this endpoint does not fail with a 404. See the
16+
[get asset representation](#get-asset-representation) endpoint for details.
17+
18+
Status | Response body spec
19+
---|---
20+
200 | [Asset](models.md#asset)
21+
22+
## Get asset representation
23+
24+
Retrieves the byte content of the specified asset representation.
25+
26+
Request property | Spec
27+
---|---
28+
Action | GET /assets/[`reprId`](models.md#id-types) **OR** ~~GET /ars/[`reprId`](models.md#id-types)~~
29+
[`SID` header](request.md#sid-header) | None.
30+
[`Range` header](request.md#range-header) |
31+
Body model | _no body_
32+
Scala | `class GetAsset` **OR** ~~`class GetAssetRepresentation`~~
33+
34+
This endpoint is in need of some rethinking. The /assets/`reprId` path is overloading the path
35+
for the [get asset metadata](#get-asset-metadata) endpoint: the code must first assume the path
36+
parameter is an asset ID, and if that lookup fails, then check for a representation. A distinct
37+
path for this endpoint is needed.
38+
39+
That distinct path shouldn't be provided by /ars/`reprId` though, because that was intended to be
40+
a temporary path only. We switched some assets from being served by the API to being served by
41+
CloudFront, but in the process we needed a way to test the mechanism by which the CloudFront URLs
42+
are provided. The `assetUrlTemplate` field of the [course details](models.md#course-details) model
43+
provides clients with a URL template that, when filled in with an asset representation ID,
44+
provides a URL to retrieve that representation. It's now using CloudFront, but during testing,
45+
it used the /ars/`reprId` path.
46+
47+
Status | Response body spec
48+
---|---
49+
200 | [File content](responses.md#file-content)
50+
500 | `{"error": 500, "message": "Unexpected exception: Not Found (Service: Amazon S3; Status Code: 404; ...)}` <br> If `reprId` did not identify an asset representation. This really should have a 404 and not a 500 status code.
51+
52+
## Patch asset repr
53+
54+
Request property | Spec
55+
---|---
56+
Action | PATCH /assets/[`assetId`](models.md#id-types)/representations/[`reprId`](models.md#id-types)
57+
Scala | `class PatchAssetRepr`
58+
59+
## Get gadget asset
60+
61+
Request property | Spec
62+
---|---
63+
Action | GET /assets/[`assetId`](models.md#id-types)/[`reprId`](models.md#id-types)
64+
Scala | `class GetGadgetAsset`
65+
66+
## Put asset
67+
68+
Request property | Spec
69+
---|---
70+
Action | PUT /assets/[`assetId`](models.md#id-types)
71+
Scala | `class ReplaceAsset`
72+
73+
Status | Response body spec
74+
---|---
75+
200 | `{}` <br> If asset is successfully replaced.
76+
403 | `{"error": 403, "message": "User [$userId] is not an owner of asset [$assetId]"}` <br> If the `SID` user is not an owner of the asset.
77+
401 | [Invalid credentials](responses.md#invalid-credentials), if `SID` is not for a user.
78+
400 | [Bad request](responses.md#bad-request)
79+
80+
## List assets
81+
82+
Request property | Spec
83+
---|---
84+
Action | GET /assets
85+
Scala | `object GetAssets`
86+
87+
## Create asset
88+
89+
Request property | Spec
90+
---|---
91+
Action | POST /assets
92+
Scala | `object AddAsset`

0 commit comments

Comments
 (0)