Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathan Childress and Chris Wilson authored and twopoint718 committed Dec 18, 2014
0 parents commit 38305ab
Show file tree
Hide file tree
Showing 35 changed files with 2,218 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.cabal-sandbox
cabal.sandbox.config
dist
.env
.anvil/
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2014 Jon Childress & Chris Wilson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: cabal run
147 changes: 147 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# API Server

An API Server in Haskell. Written as a demonstration of how to use
[hasql][hsql] and [scotty][stty] together in a realistic (we hope) api
server application.

[hsql]: http://hackage.haskell.org/package/hasql
[stty]: http://hackage.haskell.org/package/scotty

There's a `User` and a minimal `Resource` (which can easily be extended
or copied). Even these two resources interact in a nice way to
demonstrate things like required or optional parameters and token-based
authentication.


## Setup

This application is "cabalized" and so it should be the same as running any
other cabal-managed application. Make sure that you have [cabal][cabl] and
cabal-install installed and working. For the majority of users, installing
the [Haskell Platform][hplt] will be all you need.

[cabl]: https://www.haskell.org/cabal/
[hplt]: https://www.haskell.org/platform/

Next clone the repo and then initialize a new project. This project uses
[cabal freeze][frze] to lock-in the versions of the necessary dependencies.

[frze]: http://blog.johantibell.com/2014/04/announcing-cabal-120.html#dependency-freezing

git clone https://github.com/bendyworks/api_server.git
cd api_server

It is good practice to create a sandbox for installing packages into:

cabal sandbox init

Next install the project's dependencies. We'll also install the
dependencies for tests:

cabal install --only-dependencies --enable-tests

At this point, go get a snack, it's going to build for a while. Next we
need to initialize the database that the app depends upon. There's a script
which will set up the database (you can also call `db drop` to drop the
databases) This will create `api_dev` and `api_test` databases using the
[psql][] command:

[psql]: http://www.postgresql.org/docs/current/static/app-psql.html

./bin/db create

Lastly, we'll set some environment variables. I usually create a `.env`
file with some details like this:

export DATABASE_URL="postgresql://postgres:@localhost:5432/api_dev"
export SMTP_SERVER=localhost
export SMTP_PORT=25
export SMTP_LOGIN=""
export SMTP_PASSWORD=""
export HASK_ENV=DEVELOPMENT
export PORT=3000

⚠️ **Note**: The same environment variables will need to be set in production.

Just to make sure everything worked, we'll build the tests and run them:

cabal test --show-details=always --test-options="--color"

If you look in the cabal file (`api-server.cabal`) you can see that the
project is split into three pieces:

* _A library_ that contains the vast majority of the application logic.
* _An executable_ that uses the library and is basically there to gather
some environment variables to run the server.
* _A test suite_ that uses the library.

Undoubtedly, I will have missed something in these docs. Please file an
issue and we'll get the setup corrected.

## API Flow

The basic flow for the api is as follows:

REQ> POST /users

RSP> user_id: x, api_token: t

REQ> POST /users/$x
{ "resource_email": "[email protected]" }

RSP> "ok" (also sends an email to given address, with uuid)

REQ> GET /verify/$uuid

RSP> {resource_id: r, user_id: x}

REQ> POST /resource
Header: "Authorization: Token $t"
{ "resource_email": "[email protected]"
, "resource_name": "some name"
, "resource_optional": "optional text"
, "user_id": $x
}

RSP> { "resource_email": "[email protected]"
, "resource_name": "some name"
, "resource_optional": "optional text"
, "resource_id": $r
}

## Other documentation

There's a series of blog posts that describes how we built this application
and the kinds of things that we were hoping to achieve:

* [Part 1][prt1] - db concerns
* [Part 2][prt2] - encoding domain logic in types
* [Part 3][prt3] - type system as an aid to authentication

[prt1]: http://bendyworks.com/actually-using-the-database/
[prt2]: https://bendyworks.com/haskell-api-server-2/
[prt3]: https://bendyworks.com/authentication-via-haskell/

## Thanks

* Bendyworks for professional development time & the room to try new stuff
* Jon for putting in all the time that actually makes a project like this
work. He’s always been the one that fixes the half-baked stuff that I
write. Also, it has been immensely rewarding to pair-program on Haskell
professionally.
* All of the various excellent Haskell libraries and tooling.
* And last, but certainly not least, Joe Nelson for his utterly
indefatigable work on everything Haskell, all of which I depend on
constantly: [haskell-vim-now][hvin], [heroku-buildpack-ghc][hbpg],
[postgrest][rest] and [more][]. Also I want to thank Joe for hours of
deep conversations during [BayHac2014][byhc] about what’s possible using
databases, Haskell, and so much more. My understanding about how to
design applications like this wouldn’t have been possible without his
insights. His urging (even as a voice in the back of my head) has caused
me to finish more projects than anything else. Thanks Joe!

[hvin]: https://github.com/begriffs/haskell-vim-now
[hbpg]: https://github.com/begriffs/heroku-buildpack-ghc
[rest]: https://github.com/begriffs/postgrest
[more]: https://github.com/begriffs?tab=repositories
[byhc]: https://www.haskell.org/haskellwiki/BayHac2014
2 changes: 2 additions & 0 deletions Setup.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain
131 changes: 131 additions & 0 deletions api-server.cabal
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
name: api-server
version: 0.0.1
synopsis: API Server
license-file: LICENSE
author: Chris Wilson and Jon Childress
maintainer: [email protected]
category: Network
build-type: Simple
cabal-version: >= 1.10



library
default-language:
Haskell2010
ghc-options:
-W -Wall -Werror
hs-source-dirs:
lib
exposed-modules:
Api.Abstract
, Api.Controllers.Resource
, Api.Controllers.User
, Api.Helpers.Controller
, Api.Mailers.Verify
, Api.Mappers.PendingUserResource
, Api.Mappers.Resource
, Api.Mappers.User
, Api.Routes
, Api.Server
, Api.Types.Config
, Api.Types.Fields
, Api.Types.PendingUserResource
, Api.Types.Resource
, Api.Types.Server
, Api.Types.User
build-depends:
base
, aeson
, bytestring
, email-validate
, hasql
, hasql-backend
, hasql-postgres
, hspec-wai-json
, http-types
, mime-mail
, mtl
, network
, network-uri
, resource-pool
, scotty
, smtp-mail
, stm
, text
, time
, transformers
, vector
, wai
, wai-extra
, warp
default-extensions:
OverloadedStrings
, DeriveDataTypeable
, GeneralizedNewtypeDeriving
, MultiParamTypeClasses



executable api-server
default-language:
Haskell2010
ghc-options:
-W -Wall -Werror
hs-source-dirs:
src
main-is:
Main.hs
build-depends:
base
, bytestring
, api-server
, network-uri
, text



test-suite api-test
default-language:
Haskell2010
ghc-options:
-W -Wall -Werror
hs-source-dirs:
test, lib
main-is:
Spec.hs
type:
exitcode-stdio-1.0
build-depends:
base
, aeson
, aeson-qq
, bytestring
, email-validate
, hasql
, hasql-backend
, hasql-postgres
, hspec
, hspec-wai >= 0.5.1
, hspec-wai-json
, http-types
, mime-mail
, mtl
, network
, network-uri
, resource-pool
, scotty
, smtp-mail
, stm
, text
, time
, transformers
, vector
, wai
, wai-extra
, warp
default-extensions:
OverloadedStrings
, DeriveDataTypeable
, GeneralizedNewtypeDeriving
, MultiParamTypeClasses
48 changes: 48 additions & 0 deletions bin/db
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env bash

if [ -z "$(which psql)" ]; then
printf "Api requires the PostgreSQL database\n"
exit 1
fi

localdir="$(dirname $0)"
prefix='api_'
envs='dev test'

drop_dbs() {
for db in $envs; do
local name="$prefix$db"
if dropdb "$name"; then
printf "successfully dropped $name database\n"
fi
done
}

create_dbs() {
for db in $envs; do
local name="$prefix$db"
if createdb "$name"; then
psql "$name" < "$localdir/../db/schema.sql" > /dev/null
printf "successfully created $name database\n"
fi
done
}

db_console() {
psql "api_$1"
exit 0
}

usage() {
printf "USAGE: db [create|drop|dev|test]"
exit 1
}

while [ $# -gt 0 ]; do
case "$1" in
'drop' ) drop_dbs ; shift 1 ;;
'create' ) create_dbs ; shift 1 ;;
'dev' | 'test' ) db_console $1 ;;
* ) usage
esac
done
Loading

0 comments on commit 38305ab

Please sign in to comment.