Search code examples
haskellhaskell-snap-framework

How to use Auth and PostgresqlSimple in one handler?


Alright, so there are a few problems actually, I'll try to describe each of them in isolation of the other, but in a sense they are all related.

I want to insert a user id, which I want to get from the AuthManager. Okay that's fair enough, we can just use currentUser and then do some maybe voodoo to get the actual unUid. But then is when the problem starts. Okay, so we want to use execute to exute a query:

execute "INSERT INTO events(title,recurring) VALUES(?,?);" (eventname,recurring)

boom. Okay, then when doing cabal install, it fails saying that there is no instance of HasPostgres available for the type I declared. Makes perfect sense since the type of my handler is ...-> ...-> Handler App (AuthManager App). This is the first problem: I don't know how to correctly make an instance so that the types are correct. As I tried, just copying the initial instance in Application.hs doesn't help us at all.

That brings us to other problems that arise when trying to find an alternative solution. So the solutions I tried is by just substituting the execute call with a with pg $ execute call. This has the following result:

Couldn't match type `App' with `AuthManager App'
Expected type: Handler App (AuthManager App) GHC.Int.Int64
  Actual type: Handler App App GHC.Int.Int64

So again, to no avail. The next thing I tried is removing (AuthManager App) type, and just substituting it by App, so then I could replace AuthManager calls with with auth $ call. Then I realised this isn't the solution either, because once you get AuthManager out of there, you need to drop it from every function that calls it.

I need some help with how to fix this. Can I somehow lift the type to fix this? Or alternatively what is a good solution to this problem?


EDIT

Okay, so I've tried to remove AuthManager App out of my type so I can use postgres after all. Now this doesn't go as smoothly either as one would expect. The next part comes from a regular application generated by Snap init:

handleLoginSubmit :: Handler App App ()
handleLoginSubmit =
with auth $ loginUser "login" "password" Nothing
          (\_ ->handleLogin err) (handleFoo)
where
  err = Just "Unknown user or password"

Okay so both handleLoginand handleFoo now have types Handler App App (), but yet Haskell complains it expects the type Handler App (AuthManager App) ():

Couldn't match type `App' with `AuthManager App'
Expected type: Handler App (AuthManager App) ()
  Actual type: Handler App App ()


--Definition of App:
data App = App
{ _heist :: Snaplet (Heist App)
, _sess :: Snaplet SessionManager
, _auth :: Snaplet (AuthManager App)
, _pg   :: Snaplet Postgres
}

Solution

  • You want to use this type signature.

    yourHandler :: Handler App App ()
        with pg $ execute ...
        with auth $ loginUser ...
    

    The with function lets you raise a Handler b v function, which only has access to the v snaplet's data, into Handler b b, which has access to everything. And the only thing it needs to do that is the lens from b to v.