Let's set up the service to return JSON responses. We'll need to define the data types for our posts, users, and followers, and then write functions to serialize and deserialize these data types to and from JSON.

Using the JSON library

You may want to define the following data types for your service:

type client.User =
type client.CreatePost =
type client.Post =
type client.Following =

These "client-facing" models might have more or less information than what you'll eventually need in the database, so while it's possible to directly save the client-facing models in Cloud storage, it's a good practice to decouple layers of the application which contain different data or evolve at different rates.

Once you have your target data types, you'll need to use the Json library to serialize and deserialize them. Here's an example of turning a Unison data type into a JSON string:

User.toJson : client.User -> Json
User.toJson = cases
  client.User.User userId userHandle name avatar ->
    use Json text
    use List ++
    use Optional fold
    maybeAvatar = fold '[] (uri -> [("avatar", text uri)]) avatar
    maybeUserId = fold '[] (uid -> [("userid", text uid)]) userId
    jsonFields =
      [("userHandle", text userHandle), ("name", text name)]
    Object (jsonFields ++ maybeAvatar ++ maybeUserId)

Turning a Unison datatype into a JSON object typically takes the form of a pattern match on the data type, where the fields of the data type are translated into JSON using constructors like Json.object or Json.text.

Here's an example of turning a JSON object into a Unison data type:

User.fromJson : '{Decoder} client.User
User.fromJson = do
  use Decoder text
  use object at at!
  userId = optional! (at "userId" text)
  userHandle = at! "userHandle" text
  name = at! "name" text
  avatar = optional! (at "avatar" text)
  client.User.User userId userHandle name avatar

We represent JSON deserialization with the Decoder ability. You can inspect keys with the object.at function and decode them into the appropriate type with functions like Decoder.text or Decoder.int.

📝 Instructions

Update your service to expect and produce the following JSON data blobs. You can supply stub values for the JSON responses for now. Take a look at the examples below to get a sense of the expected types for the required keys.

When you're ready, update your codebase and run your solution.

cloud-start/main> run submit.ex3_microblog.pt2

You can name your data types and add fields as you see fit, but they should conform to the JSON below at minimum:

User endpoints

POST /api/user

{
  "userHandle": "@bob",
  "name": "Bob Bob",
  "avatar": "https://example.com/avatar.jpg"
}

Where the "avatar" field is optional.

The response from the POST should be a 200 OK with a JSON body with the following form:

{
  "userId": "123uuidUserId123"
}

GET /api/user/:id

The response should be a 200 OK with a JSON body with the following form:

{
  "userId": "123uuidUserId123",
  "userHandle": "@bob",
  "name": "Bob Bob",
  "avatar": "https://example.com/avatar.jpg"
}

Post endpoints

POST /api/posts

Expect a JSON body with the following form:

{
  "body": "Text",
  "userId": "UserId"
}

The response should be a 200 OK with a JSON body with the following form:

{
  "postId": "123uuidPostId123"
}

GET /api/posts/:id

The response should be a 200 OK with a JSON body with the following form:

{
  "body": "Text",
  "userName": "Bob Bob",
  "userHandle": "@bob",
  "timestamp": "2021-01-01T00:00:00Z"
}

Follower endpoints

POST /api/follow

The endpoint should contain a JSON request body with the following:

{
  "follower": "123uuidUserId123",
  "target": "123uuidUserId123"
}

The response body should be a 200 OK with JSON which looks like:

{
  "followingId": "123uuidFollowingId123"
}

User feed endpoint

GET /api/feed/:handle

Returns all posts of the users you're following, ordered by time.

The response should be a JSON array:

[
  {
    "body": "Text",
    "userName": "Bob Bob",
    "userHandle": "@bob",
    "timestamp": "2021-01-01T00:00:00Z"
  }
]