I did some more work on whereami. There were a few big things:

  • I chose Scotty as the web framework. I'd started using a few years ago for a different project, and Taylor reconfirmed it, so it seemed like a good idea.
  • I replaced my use of the Geodesics package. I'd wanted to use it so later I could do more GIS stuff with it, but it ended up adding a lot of extra complexity (and it wasn't Aeson-compatible). My new data type is a simple record:
data Coordinates = Coordinates {
      latitude :: Double
    , longitude :: Double
    , altitude :: Double
} deriving (Generic, Read, Show)
  • I added the first route with a GET and POST.
  • I wrote a simple Python test client:
"""Simple Python client for testing the API."""

import json
import requests
import sys

URI = ''
OAK = {"latitude": 37.8044, "longitude": -122.2711, "altitude": 13.0}

def to_coordinates(lat, lon, alt):
    return {'latitude': lat, 'longitude': lon, 'altitude': altitude}

def print_response(res):
    if res['success']:
        c = res['coordinates']
        print(f"  OK: {c['latitude']}° {c['longitude']}° at {c['altitude']}m")
        print(f"FAIL: {res['message']}")

def get_coordinates():
    return requests.get(URI).json()

def set_coordinates(lat, lon, alt):
    return requests.post(URI, json=to_coordinates(lat, lon, alt)).json()

def set_oakland():
    return requests.post(URI, json=OAK).json()

COMMANDS = {'get': get_coordinates, 'set': set_oakland}

if __name__ == '__main__':
    if len(sys.argv) == 1:
        if sys.argv[1] in COMMANDS:
            print('invalid command: {}'.format(', '.join(COMMANDS.keys())))

Lo and behold:

(0) <hephaestus:kyle> $ stack run
Setting phasers to stun... (port 4000) (ctrl-c to quit)
^Zzsh: suspended (signal)  stack run
(147) <hephaestus:kyle> $ bg
[1]  + continued  stack run
(0) <hephaestus:kyle> $ ./client.py; ./client.py set ; ./client.py 
  OK: 0.0° 0.0° at 0.0m
  OK: 37.8044° -122.2711° at 13.0m
  OK: 0.0° 0.0° at 0.0m

With that said, there's a long list of TODOs for this first milestone, which is just having GET and POST endpoint taking / returning JSON and with basic auth on the POST and that doesn't persist a history of coordinates.

  • The big issue right now is that the IORef isn't updating. Then I tried switching to STM, but that didn't work either. Note that in the client run above, the last two lines should match. I suspect this has something to do with threads.
  • I have to do unsafePerformIO in some places, because I'm storing the current position as an IORefSTM.TVar. It's probably not the best choice, but it's where I'm at now. My end goal for this milestone is to remove all the unsafe bits. The reason I'm using the unsafe parts, though, is that TVars operate in the IO (via atomically) monad, and Scotty uses its own set of monads (ActionM and ScottyM). I think this requires the use of monad transformers.

Once this is sorted out, I can move to basic auth. It does feel good to write me some Haskell, though.

Tags: , ,