Haskell calling HTML using Shakespeare without Yesod widgets - haskell

Like Yesod, so diving in deep. Have a question: using Yesod I can call eg. data type App with Warp (warp 3000 App).
How can I serve up HTML created using Shakespeare templates (like below). Got this from Michael Snoyman's book.
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
module Main where
import Text.Blaze.Html.Renderer.String (renderHtml)
import Text.Hamlet (HtmlUrl, hamlet)
import Data.Text (Text)
import Yesod
data WebRoutes = Home | Time | Stylesheet
render :: WebRoutes -> [(Text, Text)] -> Text
render Home _ = "/home"
render Time _ = "/time"
render Stylesheet _ = "/stylesheet"
template :: Text -> HtmlUrl WebRoutes
template title = [hamlet|
<html>
<head>
<title>#{varTitle}
<link rel=stylesheet href=#{Stylesheet}>
<body>
<h1 #headerId>#{title}
|]
varTitle :: String
varTitle = "Test outputd"
main :: IO ()
main = putStrLn $ renderHtml $ template "Test output" render

Related

Scotty and persistence - type error when using insert function

I've got the following application:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Main where
-- Scotty
import qualified Web.Scotty as S
import Network.Wai.Middleware.RequestLogger
import Network.Wai.Middleware.Static
import qualified Data.Text.Lazy as L
-- HTML rendering
import Text.Blaze.Html.Renderer.Text (renderHtml)
import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as A
import Control.Monad.IO.Class
-- Database
import Database.Persist
import Database.Persist.Sqlite
import Database.Persist.TH
import Control.Monad.Trans.Resource (runResourceT, ResourceT)
import Control.Monad.Logger
-- URL generation
import System.Random
import Control.Monad (replicateM)
-- JSON
import Data.Map (fromList)
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Link
shortUrl L.Text
URLKey shortUrl
Primary shortUrl
longUrl L.Text
counter Int
deriving Show
|]
getURL :: L.Text -> IO (Maybe (Entity Link))
getURL shortId = runSqlite "links.db" $ do
maybeOriginal <- getBy $ URLKey shortId
pure maybeOriginal
-- I don't know what type to give this, that's probably the problem
addURL short long = runSqlite "links.db" $ do
insert $ Link short long
main :: IO ()
main = do
-- Connect to db and run migration
runSqlite "links.db" $ do runMigration migrateAll
S.scotty 3000 $ do
...
S.post "/shorten" $ do
-- Get URL
url <- S.param "url" :: S.ActionM L.Text
-- Generate a random short URL
randStr <- liftIO $ getRandStr 5
-- Add the urls to the database
liftIO $ addURL (L.pack randStr) url
-- Send JSON response with ID
S.json $ fromList [("id" :: String, randStr)]
I get the following error:
shortener> build (lib + exe)
Preprocessing library for shortener-0.1.0.0..
Building library for shortener-0.1.0.0..
Preprocessing executable 'shortener-exe' for shortener-0.1.0.0..
Building executable 'shortener-exe' for shortener-0.1.0.0..
[2 of 2] Compiling Main
/home/henry/haskell/shortener/app/Main.hs:86:5: error:
• Couldn't match type ‘PersistEntityBackend (Int -> Link)’
with ‘SqlBackend’
arising from a use of ‘insert’
• In the first argument of ‘($)’, namely ‘insert’
In a stmt of a 'do' block: insert $ Link short long
In the second argument of ‘($)’, namely
‘do insert $ Link short long’
|
86 | insert $ Link short long
| ^^^^^^
-- While building package shortener-0.1.0.0 (scroll up to its section to see the error) using:
/home/henry/.stack/setup-exe-cache/x86_64-linux-tinfo6/Cabal-simple_mPHDZzAJ_3.4.1.0_ghc-9.0.2 --builddir=.stack-work/dist/x86_64-linux-tinfo6/Cabal-3.4.1.0 build lib:shortener exe:shortener-exe --ghc-options " -fdiagnostics-color=always"
Process exited with code: ExitFailure 1
I'm not sure how to resolve this type error and I haven't been able to find anything of use online. There was this answer with a similar problem, but the given type signature and several variations of it did not work.
It turns out I forgot a field when inserting. I had
insert $ Link short long
I needed
insert $ Link short long 0
for the counter field of Link.
Unfortunately the error didn't make that at all clear.

Use of 'approot'

If I set the approot like the following
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module Main where
import Yesod.Core
data Yello = Yello
mkYesod "Yello" [parseRoutes|
/home1 Home1R GET
/home2 Home2R GET
|]
instance Yesod Yello where
approot = ApprootStatic "http://localhost:3000/v1"
getHome1R :: Handler Html
getHome1R = defaultLayout [whamlet|Homepage 01|]
getHome2R :: Handler Html
getHome2R = defaultLayout [whamlet|<a href=#{Home1R}>Homepage 02|]
main :: IO ()
main = warp 3000 Yello
then #{Home1R} get's rendered to http://localhost:3000/v1/home1 but this little yesod-based web application is only reachable at http://localhost:3000 (w/o the /v1).
What's the purpose of approot?
Where to set/ add /v1?

Haskell scotty and persistence rest API

I'm new in Haskell. I woudl like to create simple crud rest api with scotty and persistence. I'm using sqlite with one table todo (title text, description text), I have some records in table. My point is to show all records on enpoint /todos in json format. When I print it I gets an error
• Couldn't match expected type ‘Data.Text.Internal.Lazy.Text’
with actual type ‘[Entity ToDo]’
• In the first argument of ‘text’, namely ‘(_ToDo)’
In a stmt of a 'do' block: text (_ToDo)
In the second argument of ‘($)’, namely
‘do _ToDo <- liftIO readToDo
text (_ToDo)’
|
69 | text(_ToDo)
| ^^^^^
My code:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Main where
import Data.Monoid ((<>))
import Web.Scotty
import qualified Web.Scotty as S
import Database.Persist
import Database.Persist.Sqlite
import Database.Persist.TH
import Data.Text (Text)
import Data.Time (UTCTime, getCurrentTime)
import qualified Data.Text as T
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Resource (runResourceT, ResourceT)
import Database.Persist.Sql
import Control.Monad (forM_)
import Control.Applicative
import Control.Monad.Logger
import Data.Aeson
import Data.Default.Class
import GHC.Generics
import Control.Monad.IO.Class (liftIO)
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
ToDo
title String
description String
deriving Show
|]
runDb :: SqlPersist (ResourceT (NoLoggingT IO)) a -> IO a
runDb = runNoLoggingT
. runResourceT
. withSqliteConn "todo.db"
. runSqlConn
instance ToJSON ToDo where
toJSON(ToDo title description) = object ["title" .= title, "description" .= description]
readToDo :: IO [Entity ToDo]
readToDo = (runDb $ selectList [] [LimitTo 10] )
routes :: ScottyM()
routes = do
S.get "/hello" $ do
text "hello world!"
S.get "/hello/:name" $ do
name <- param "name"
text ("hello" <> name <> "!")
S.get "/todos" $ do
_ToDo <- liftIO readToDo
text(_ToDo)
main = do
putStrLn "Starting server...."
scotty 7777 routes
How to correct this?
It looks like compiler accused you of having a bad conversion between the list of Entity ToDo-s and Data.Text.Lazy.
Please try to convert it by hand using pack function:
import Data.Text.Lazy (pack)
...
routes = do
S.get "/hello" $ do
text "hello world!"
S.get "/hello/:name" $ do
name <- param "name"
text ("hello" <> name <> "!")
S.get "/todos" $ do
_ToDo <- liftIO readToDo
text(pack . show $ _ToDo) -- explicit conversion
...

can you load a widget with parameters in a hamlet file in yesod?

what is the best way to load html from a widget if your looping through a data set?
i.e. in a hamlet file
$forall Entity id val <- collection
^{myWidget (p1 val) (p2 val)}
Note that Hamlet template can embed only hamlet template. If you want to embed a widget, use whamlet.
Also, yes - you can pass parameters to a widget. An example stack script demonstrating the concept:
#!/usr/bin/env stack
{- stack
--resolver lts-9.0
--install-ghc
runghc
--package yesod-core
--package yesod
--package shakespeare
-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Yesod
data App = App
mkYesod "App" [parseRoutes|
/ HomeR GET
|]
instance Yesod App
data Person = Person { id :: Int, name :: String, email :: String } deriving (Show, Eq, Ord)
collections = [Person 1 "Sibi" "sibi#domain.com", Person 2 "Michael" "michael#domain.com"]
whamlet1 :: Widget
whamlet1 = [whamlet| <p>hello
$forall Person _ pname pemail <- collections
^{whamlet2 pname pemail}
|]
whamlet2 :: String -> String -> Widget
whamlet2 pname pemail = [whamlet| <h1> #{pname} #{pemail} |]
getHomeR = defaultLayout $ do
setTitle "My Page Title"
toWidget [lucius| h1 { color: green; } |]
whamlet1
main = warp 3000 App
The widgets chapter on Yesod book explains them more deeply.

Is jsonToRepJson broken?

I'm just starting doing some Yesod + Haskell stuff.
Is jsonToRepJson broken or something?
I made this code below but I always get an error in jsonToRepJson part.
It seems it doesn't get the expected type?
Any help would be great! Thanks :3
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
import Yesod
import Data.Text
data APP = APP
instance Yesod APP
mkYesod "APP" [parseRoutes|
/ TestR GET
|]
getTestR :: Handler RepJson
getTestR = jsonToRepJson $ object ["test".= ("test"::Text)]
main::IO()
main = warpDebug 3001 APP
this is what I get when I use runhaskell
api.hs:18:12:
Couldn't match expected type `RepJson' with actual type `Value'
Expected type: Handler RepJson
Actual type: HandlerT APP IO Value
In the expression:
jsonToRepJson $ object ["test" .= ("test" :: Text)]
In an equation for `getTestR':
getTestR = jsonToRepJson $ object ["test" .= ("test" :: Text)]
You must convert your value toJSON.
Eg.:
jsonToRepJson $ object [("result", toJSON resultValue)]
:)
You can read about that change in Yesod 1.2
What I did is, I used the TypeContent handler.
If I understood correctly what I've read, repSelect allows us to easily handle what type of data representation the client asks for.
It reads the request header and checks if it asks for JSON, then it will spit out JSON data, if it needs HTML it will then give the HTML page. Providing that you yourself had added the specific data needed using providRep.
Here's my code.
mkYesod "APP" [parseRoutes|
/ TestR GET
|]
getTestR::Handler TypedContent
getTestR = do
selectRep $ do
provideRep $ jsonToRepJson $ object $ (["test" .= ("test"::Text)])
main::IO()
main = warpDebug 3001 APP

Resources