/key_maps

A simple data-structure API

Primary LanguageElixir

Key Maps

A simple data-structure API.

You can find old code in the legacy branches.

How it works

  1. You make a map (resembles a database table), e.g. "Quotes"
  2. You add data to the map, e.g. a quote and its author
  3. You can fetch this data through a public JSON API

The API

Authentication

Uses Auth0's passwordless authentication.

POST /auth/start

__Request body:__

{
  "email": "..."
}

__Response body:__

Status 200 with empty body if successful
Status 422 with { errors: ... } if unsuccessful


POST /auth/exchange

__Request body:__

{
  auth0_id_token: "..."
}

__Response body:__

{
  "data": {
    "token": "..."
  }
}


GET /auth/validate?token=...

__Response body:__

Status 200 with empty body if valid
Status 403 with empty body if invalid

Use the token to authenticate requests. User info is located inside the token's claims.
For example:

GET /api?query=query
Authorization: PLACE_TOKEN_HERE

Notes

The ability to sign-up is disabled by default, you have to define the ENV variable 'ENABLE_SIGN_UP=1' to enable it.

Private API

Endpoint

GET /api?query=PLACE_QUERY_HERE

GraphQL queries

# 1. Create a `map`
mutation M { createMap(
  name: "Quotes",
  attributes: [ "quote", "author" ]
) {
  id,
  attributes,
  types
}}

# 2. Create an item for a `map`
mutation M { createMapItem(
  map: "Quotes",

  quote: "Specialization tends to shut off the wide-band tuning searches and thus to preclude further discovery.",
  author: "Buckminster Fuller"
) {
  id,
  map_id,
  attributes
}}

# 3. Get all map items for a specific map
query Q { mapItems(map: "Quotes") {
  id,
  map_id,
  attributes
}}

# 4. Get all maps
query Q { maps {
  id,
  name,
  attributes,
  types
}}

# 5. Get a specific item and a specific map
query Q { mapItem(id: ITEM_ID) { ... }}
query Q { map(name: "Quotes") { ... }}
# -- uses the name argument to select the map,
#    but you can also use the map id.

# 6. Update a map item and a map
mutation M { updateMapItem(id: ITEM_ID, quote: "Updated quote") { ... }}
mutation M { updateMap(id: MAP_ID, name: "Updated name") { ... }}

# 7. Remove a map item and a map
mutation M { removeMapItem(id: ITEM_ID) { ... }}
mutation M { removeMap(id: MAP_ID) { ... }}

# 8. Additional operations
# 8.1 Creating multiple map items at once
STRING = to_urlsafe_base64(
  to_json(
    [
      { item_1_key: "Value - 1" },
      { item_2_key: "Value - 2" }
    ]
  )
)

mutation M { createMapItems(items: #{ STRING }) { id, attribute }}
-> [{ id: 1 }, { id: 2 }]

Notes
The map name must be unique, it will be casted to lowercase for validation.

Defining types for your attributes (optional)

You can define types, but it's totally optional, and it doesn't actually do anything either. So why define these, well, you could do this to show a certain input field in your UI. For example, if you want to show a date selector.

mutation M { createMap(
  name: "Author",
  attributes: [ "date_of_birth" ]
  types: { date_of_birth: "date" }
) { ... }}

Storing map settings

This attribute is there in case you need to store some extra data for a map, e.g. if you want to sort your data in a particular way in your UI.

mutation M { updateMap(
  id: MAP_ID,

  settings: { ui_sort_by: "author", ui_sort_dir: "asc" }
) { ... }}

Public API

All items for a single map:

GET /public/:user_id/:map_name

:user_id, your case-insensitive user id
:map_name, your case-insensitive map name

One item for a single map:

GET /public/:user_id/:map_name/:map_item_id

Options

GET /public/:user_id/:map_name?sort_by=author

**sort_by**, e.g. 'author', when not specified, it is sorted by insertion date.  
**sort_direction**, 'asc' or 'desc', default is 'asc'.  
**timestamps**, include this to add the timestamps of the item.  

Responses

The API will always return data in one of the following formats.

Data

{
  "data": {
    "attribute": "example"
  }
}

In the case of a GraphQL query or mutation.

{
  "data": {
    "query_or_mutation_name": {
      "attribute": "example"
    }
  }
}

Errors

{
  "errors": [
    { "message": "Error message" }
  ]
}

Development

echo 'export SECRET_KEY=...' >> .env
echo 'export AUTH0_DOMAIN=...' >> .env
echo 'export AUTH0_CLIENT_ID=...' >> .env

source .env

mix deps.get
mix test

mix ecto.create
mix run --no-halt