In appreciation of simplicity.
RSEB2NFGUBTFESE5BJKDUVQL5Y5ZVGY5O4CJX2LNP63MS3NRHHZQC 2OIPAQCBDIUJBXB4K2QVP3IEBIUOCQHSWSWFVMVSVZC7GHX2VK7AC AXKKXBWN4EMUOLV43WN52JSKJPBV7TLSGLNJW5EZXHSJNKCYUWOQC ADMKQQGCGVSHHIMVQ4XFRDCG544SBJCYALSKZV45CQQBZ4ACUH2AC EQXRXRZDYCM7BDAVBOXQYPG6C7IJT3OFGNIXCDAHJJBRKAXNGL7AC 2KZPOGRBY6KBMO76F55ZKIVOLSG3O63VP3RHRZVANXYT3OLZ3OWQC GKGVYBZGPJXO7N7GLHLRNYQPXFHBQSNQN53OKRFCXLQEYDTC5I4QC Y35QCWYW2OTZ27ZVTH2BA3XJTUCJ2WMLKU32ZCOCDY3AW7TIZXRAC NMWWP4ZNOKHZKSJ6F5KYEREWXXR5F4UD35WOKI3EH42AZWVCTCJAC NVOCQVASZWTKQJG7GPH7KHKZZR7NUG4WLV5YY4KAIRPCJRWCZPIAC 7DBNV3GV773FH5ZLQWFX4RBOS4Q3CIK2RYZNNABY3ZOETYZCXRNQC A2J7B4SCCJYKQV3G2LDHEFNE2GUICO3N3Y5FKF4EUZW5AG7PTDWAC N4NDAZYTLSI2W22KT3SYXL257DBMSH3UT2BXOYM7LH7FSZAY4RLAC <form method="post" action="${postAction}"><table id="info"><tr><td>Login:</td><td><input type="text" name="login" size="20" /></td></tr><tr><td>Password:</td><td><input type="password" name="password" size="20" /></td></tr><tr><td></td><td><input type="submit" value="${submitText}" /></td></tr></table></form>
<apply template="base"><ifLoggedIn><p>This is a simple demo page served using<a href="http://snapframework.com/docs/tutorials/heist">Heist</a>and the <a href="http://snapframework.com/">Snap</a> web framework.</p><p>Congrats! You're logged in as '<loggedInUser/>'</p><p><a href="/logout">Logout</a></p></ifLoggedIn><ifLoggedOut><apply template="_login"/></ifLoggedOut></apply>
<h1>Snap Example App Login</h1><p><loginError/></p><bind tag="postAction">/login</bind><bind tag="submitText">Login</bind><apply template="userform"/><p>Don't have a login yet? <a href="/new_user">Create a new user</a></p>
html {padding: 0;margin: 0;background-color: #ffffff;font-family: Verdana, Helvetica, sans-serif;}body {padding: 0;margin: 0;}a {text-decoration: underline;}a :hover {cursor: pointer;text-decoration: underline;}img {border: none;}#content {padding-left: 1em;}#info {font-size: 60%;}
exposed-modules:AnankeAnanke.TimeLogAnanke.Interval
exposed-modules: AnankeAnanke.TimeLogAnanke.Intervalbuild-depends: base >= 4.4 && < 5, lens >= 3.7.6 && < 3.11, bifunctors, aeson >= 0.7.0.2, text >= 0.11 && < 0.12, time >= 1.1 && < 1.5, iso8601-time == 0.1.1, containers == 0.5.*
build-depends:base >= 4.4 && < 5,lens >= 3.7.6 && < 3.11,bifunctors,aeson >= 0.7.0.2,text >= 0.11 && < 0.12,time >= 1.1 && < 1.5,iso8601-time == 0.1.1,containers == 0.5.*
build-depends:base,hspec >= 1.8.1,ananke,aeson,bifunctors,containers,iso8601-time,text,time
build-depends: base, hspec >= 1.8.1, ananke, aeson, bifunctors, containers, iso8601-time, text, time
build-depends:base,lens,text,bytestring >= 0.9.1 && < 0.11,snap >= 0.13 && < 0.14,snap-core >= 0.9 && < 0.11,snap-server >= 0.9 && < 0.11,snap-loader-static >= 0.9 && < 0.10,heist >= 0.13 && < 0.14,MonadCatchIO-transformers >= 0.2.1 && < 0.4,postgresql-simple >= 0.3.10,snaplet-postgresql-simple >= 0.4.1,anankeif flag(development)build-depends:snap-loader-dynamic == 0.10.*cpp-options: -DDEVELOPMENT-- In development mode, speed is already going to suffer, so skip-- the fancy optimization flags. Additionally, disable all-- warnings. The hint library doesn't give an option to execute-- compiled code when there were also warnings, so disabling-- warnings allows quicker workflow.ghc-options: -threaded -welseif impl(ghc >= 6.12.0)ghc-options: -threaded -Wall -fwarn-tabs -funbox-strict-fields -O2-fno-warn-orphans -fno-warn-unused-do-bindelseghc-options: -threaded -Wall -fwarn-tabs -funbox-strict-fields -O2-fno-warn-orphans
build-depends: base, text, time, mtl, bytestring >= 0.9.1 && < 0.11, scotty >=0.7 && <0.8, sqlite == 0.5.2.2, ananke
{-# LANGUAGE TemplateHaskell #-}-------------------------------------------------------------------------------- | This module defines our application's state type and an alias for its-- handler monad.module Application where------------------------------------------------------------------------------import Control.Lensimport Snap.Snapletimport Snap.Snaplet.Heistimport Snap.Snaplet.Authimport Snap.Snaplet.Session------------------------------------------------------------------------------data App = App{ _heist :: Snaplet (Heist App), _sess :: Snaplet SessionManager, _auth :: Snaplet (AuthManager App)}makeLenses ''Appinstance HasHeist App whereheistLens = subSnaplet heist------------------------------------------------------------------------------type AppHandler = Handler App App, _db :: Snaplet Postgresimport Snap.Snaplet.PostgresqlSimple
{-# LANGUAGE OverloadedStrings #-}-------------------------------------------------------------------------------- | This module is where all the routes and handlers are defined for your-- site. The 'app' function is the initializer that combines everything-- together and is exported by this module.module Site( app) where------------------------------------------------------------------------------import Control.Applicativeimport Data.ByteString (ByteString)import qualified Data.Text as Timport Snap.Coreimport Snap.Snapletimport Snap.Snaplet.Authimport Snap.Snaplet.Auth.Backends.JsonFileimport Snap.Snaplet.Heistimport Snap.Snaplet.Session.Backends.CookieSessionimport Snap.Util.FileServeimport Heistimport qualified Heist.Interpreted as I------------------------------------------------------------------------------import Application-------------------------------------------------------------------------------- | Render login formhandleLogin :: Maybe T.Text -> Handler App (AuthManager App) ()handleLogin authError = heistLocal (I.bindSplices errs) $ render "login"whereerrs = maybe noSplices splice authErrorsplice err = "loginError" ## I.textSplice err-------------------------------------------------------------------------------- | Handle login submithandleLoginSubmit :: Handler App (AuthManager App) ()handleLoginSubmit =loginUser "login" "password" Nothing(\_ -> handleLogin err) (redirect "/")whereerr = Just "Unknown user or password"-------------------------------------------------------------------------------- | Logs out and redirects the user to the site index.handleLogout :: Handler App (AuthManager App) ()handleLogout = logout >> redirect "/"-------------------------------------------------------------------------------- | Handle new user form submithandleNewUser :: Handler App (AuthManager App) ()handleNewUser = method GET handleForm <|> method POST handleFormSubmitwherehandleForm = render "new_user"handleFormSubmit = registerUser "login" "password" >> redirect "/"-------------------------------------------------------------------------------- | The application's routes.routes :: [(ByteString, Handler App App ())]routes = [ ("/login", with auth handleLoginSubmit), ("/logout", with auth handleLogout), ("/new_user", with auth handleNewUser), ("", serveDirectory "static")]-------------------------------------------------------------------------------- | The application initializer.app :: SnapletInit App Appapp = makeSnaplet "app" "An snaplet example application." Nothing $ doh <- nestSnaplet "" heist $ heistInit "templates"s <- nestSnaplet "sess" sess $initCookieSessionManager "site_key.txt" "sess" (Just 3600)-- NOTE: We're using initJsonFileAuthManager here because it's easy and-- doesn't require any kind of database server to run. In practice,-- you'll probably want to change this to a more robust auth backend.a <- nestSnaplet "auth" auth $initJsonFileAuthManager defAuthSettings sess "users.json"addRoutes routesaddAuthSplices h authreturn $ App h s a pgpg <- nestSnaplet "pg" db pgsInitimport Snap.Snaplet.PostgresqlSimple
NOTE: Don't modify this file unless you know what you are doing. If you arenew to snap, start with Site.hs and Application.hs. This file containsboilerplate needed for dynamic reloading and is not meant for generalconsumption.
import Data.Text.Lazyimport Web.Scottyimport Data.Time.Clockimport Database.SQLiteimport Control.Monad.Trans (liftIO)import Ananke
Occasionally if we modify the way the dynamic reloader works and you want toupgrade, you might have to swap out this file for a newer version. But inmost cases you'll never need to modify this code.
main :: IO ()main = dodb <- openConnection "ananke.db"_ <- defineTableOpt db True eventTablescotty port $ do{--Log the start time of a work interval.Log completion of the current work interval.Record change of a work interval start.Record change of a work interval end.Given a trusted token, authorize another token.--}post "/logStart/:btcAddr" $ doaddr <- param "btcAddr"timestamp <- liftIO getCurrentTimeliftIO $ recordStart db (btcAddr addr) timestamp
------------------------------------------------------------------------------import Control.Exception (SomeException, try)import qualified Data.Text as Timport Snap.Http.Serverimport Snap.Snapletimport Snap.Snaplet.Configimport Snap.Coreimport System.IOimport Site
recordStart :: SQLiteHandle -> BtcAddr -> UTCTime -> IO ()recordStart = undefined
-------------------------------------------------------------------------------- | This is the entry point for this web server application. It supports-- easily switching between interpreting source and running statically compiled-- code.---- In either mode, the generated program should be run from the root of the-- project tree. When it is run, it locates its templates, static content, and-- source files in development mode, relative to the current working directory.---- When compiled with the development flag, only changes to the libraries, your-- cabal file, or this file should require a recompile to be picked up.-- Everything else is interpreted at runtime. There are a few consequences of-- this.---- First, this is much slower. Running the interpreter takes a significant-- chunk of time (a couple tenths of a second on the author's machine, at this-- time), regardless of the simplicity of the loaded code. In order to-- recompile and re-load server state as infrequently as possible, the source-- directories are watched for updates, as are any extra directories specified-- below.---- Second, the generated server binary is MUCH larger, since it links in the-- GHC API (via the hint library).---- Third, and the reason you would ever want to actually compile with-- development mode, is that it enables a faster development cycle. You can-- simply edit a file, save your changes, and hit reload to see your changes-- reflected immediately.---- When this is compiled without the development flag, all the actions are-- statically compiled in. This results in faster execution, a smaller binary-- size, and having to recompile the server for any code change.--main :: IO ()main = do-- Depending on the version of loadSnapTH in scope, this either enables-- dynamic reloading, or compiles it without. The last argument to-- loadSnapTH is a list of additional directories to watch for changes to-- trigger reloads in development mode. It doesn't need to include source-- directories, those are picked up automatically by the splice.(conf, site, cleanup) <- $(loadSnapTH [| getConf |]'getActions["snaplets/heist/templates"])
newtype BtcAddrParam = BtcAddrParam { btcAddr :: BtcAddr }
-------------------------------------------------------------------------------- | This action loads the config used by this application. The loaded config-- is returned as the first element of the tuple produced by the loadSnapTH-- Splice. The type is not solidly fixed, though it must be an IO action that-- produces the same type as 'getActions' takes. It also must be an instance of-- Typeable. If the type of this is changed, a full recompile will be needed to-- pick up the change, even in development mode.---- This action is only run once, regardless of whether development or-- production mode is in use.getConf :: IO (Config Snap AppConfig)getConf = commandLineAppConfig defaultConfig-------------------------------------------------------------------------------- | This function generates the the site handler and cleanup action from the-- configuration. In production mode, this action is only run once. In-- development mode, this action is run whenever the application is reloaded.---- Development mode also makes sure that the cleanup actions are run-- appropriately before shutdown. The cleanup action returned from loadSnapTH-- should still be used after the server has stopped handling requests, as the-- cleanup actions are only automatically run when a reload is triggered.---- This sample doesn't actually use the config passed in, but more-- sophisticated code might.getActions :: Config Snap AppConfig -> IO (Snap (), IO ())getActions conf = do(msgs, site, cleanup) <- runSnaplet(appEnvironment =<< getOther conf) apphPutStrLn stderr $ T.unpack msgsreturn (site, cleanup)
reduceToIntervals ((LogEntry addr (StopWork end)) : (LogEntry _ (StartWork start)) : xs, intervals) =(xs, (LogInterval addr (interval start end)) : intervals)
reduceToIntervals ((LogEntry addr (StopWork end')) : (LogEntry _ (StartWork start')) : xs, acc) =(xs, (LogInterval addr (interval start' end')) : acc)