on master help sign in
backend persistence and redirects working
commitbb65d53a7897c8dd315494326d0fdd23a04ca43c authorOwen Shepherd <414owen@gmail.com> dateTue 17 Dec 2019, 12:53 (7 years ago) parenta5a5804
5 files changed+55−29
examples/url-shortener/backend/src/Backend.hs+40−9 examples/url-shortener/common/src/Common/Api.hs+1−2 examples/url-shortener/common/src/Common/Route.hs+2−3 examples/url-shortener/common/src/Common/Schema.hs+1−1 examples/url-shortener/frontend/src/Frontend.hs+11−14
examples/url-shortener/backend/src/Backend.hs+40−9
@@ -1,4 +1,3 @@
1−{-# LANGUAGE EmptyCase #-}
21{-# LANGUAGE FlexibleContexts #-}
32{-# LANGUAGE OverloadedStrings #-}
43{-# LANGUAGE LambdaCase #-}
@@ -6,6 +5,14 @@
65{-# LANGUAGE TypeFamilies #-}
76module Backend where
87
8+import Control.Monad.IO.Class
9+import Data.Word
10+import Data.ByteString.Lazy
11+import Database.PostgreSQL.Simple
12+import Database.Id.Class
13+import Data.Text (Text)
14+import qualified Data.Aeson as A
15+import Data.Pool
916import Prelude hiding (id)
1017import Common.Route
1118import Obelisk.Backend
@@ -13,22 +20,46 @@ import Obelisk.Route
1320import Snap.Core
1421import Gargoyle.PostgreSQL.Connect
1522
23+maxUrlSize :: Word64
24+maxUrlSize = 2000
25+
26+migration :: Query
27+migration = "CREATE TABLE IF NOT EXISTS urls\
28+ \ (id SERIAL PRIMARY KEY, url VARCHAR(?) NOT NULL);"
29+
1630backend :: Backend BackendRoute FrontendRoute
1731backend = Backend
1832 { _backend_run = \serve -> do
19− withDb "db" $ \pool ->
33+ withDb "db" $ \pool -> do
34+ _ <- withResource pool $ \dbcon -> execute dbcon migration [maxUrlSize]
2035 serve $ \case
21− BackendRoute_Missing :/ () -> do
22− modifyResponse $ setResponseStatus 400 "Malformed Request"
23− writeBS "400 Malformed Request"
24− r <- getResponse
25− finishWith r
36+
37+ BackendRoute_Get_Url :/ id -> do
38+ result <- liftIO $ withResource pool $ \dbcon ->
39+ query dbcon "SELECT (url) FROM urls WHERE id = ?;" [unId id]
40+ case result of
41+ [[url]] -> redirect url
42+ _ -> do
43+ modifyResponse $ setResponseStatus 404 "Not Found"
44+ writeText "That shortlink wasn't found ¯\\_(ツ)_/¯"
45+ r <- getResponse
46+ finishWith r
47+
2648 BackendRoute_Shorten :/ () -> do
49+ Just url <- A.decode <$> readRequestBody maxUrlSize
50+ [[id]] <- liftIO $ withResource pool $ \dbcon -> do
51+ _ <- execute dbcon "INSERT INTO urls (url) values (?);" [url :: Text]
52+ query_ dbcon "SELECT last_value FROM urls_id_seq;"
2753 modifyResponse $ setResponseStatus 200 "OK"
28− writeBS $ "\"OK\""
54+ writeBS $ toStrict $ A.encode $ ("/s/" <>) $ show (id :: Int)
55+ r <- getResponse
56+ finishWith r
57+
58+ _ -> do
59+ modifyResponse $ setResponseStatus 400 "Malformed Request"
60+ writeBS "400 Malformed Request"
2961 r <- getResponse
3062 finishWith r
31− _ -> error "no"
3263 return ()
3364 , _backend_routeEncoder = fullRouteEncoder
3465 }
examples/url-shortener/common/src/Common/Api.hs+1−2
@@ -3,7 +3,6 @@
33{-# LANGUAGE ConstraintKinds #-}
44{-# LANGUAGE GADTs #-}
55{-# LANGUAGE FlexibleContexts #-}
6−{-# LANGUAGE TemplateHaskell #-}
76{-# LANGUAGE FlexibleInstances #-}
87{-# LANGUAGE MultiParamTypeClasses #-}
98module Common.Api where
@@ -12,4 +11,4 @@ import Data.Aeson
1211import Data.Text (Text)
1312import GHC.Generics (Generic)
1413
15−data ShortenRequest = ShortenRequest Text deriving (Generic, ToJSON, FromJSON)
14+newtype ShortenRequest = ShortenRequest Text deriving (Generic, ToJSON, FromJSON)
examples/url-shortener/common/src/Common/Route.hs+2−3
@@ -5,7 +5,6 @@
55{-# LANGUAGE RankNTypes #-}
66{-# LANGUAGE TemplateHaskell #-}
77{-# LANGUAGE KindSignatures #-}
8−{-# LANGUAGE EmptyCase #-}
98{-# LANGUAGE MultiParamTypeClasses #-}
109{-# LANGUAGE FlexibleInstances #-}
1110{-# LANGUAGE OverloadedStrings #-}
@@ -31,7 +30,7 @@ import Common.Schema
3130
3231data BackendRoute :: * -> * where
3332 BackendRoute_Missing :: BackendRoute ()
34− BackendRoute_Url_Id :: BackendRoute (Id Url)
33+ BackendRoute_Get_Url :: BackendRoute (Id Url)
3534 -- TODO validate this or use a URL type
3635 BackendRoute_Shorten :: BackendRoute ()
3736
@@ -50,7 +49,7 @@ fullRouteEncoder = mkFullRouteEncoder
5049 (FullRoute_Backend BackendRoute_Missing :/ ())
5150 (\case
5251 BackendRoute_Shorten -> PathSegment "create" $ unitEncoder mempty
53− BackendRoute_Url_Id -> PathSegment "s" idPathSegmentEncoder
52+ BackendRoute_Get_Url -> PathSegment "s" idPathSegmentEncoder
5453 BackendRoute_Missing -> PathSegment "missing" $ unitEncoder mempty)
5554 (\case
5655 FrontendRoute_Main -> PathEnd $ unitEncoder mempty)
examples/url-shortener/common/src/Common/Schema.hs+1−1
@@ -5,7 +5,7 @@ import Data.Text (Text)
55import Database.Id.Class
66import GHC.Generics
77
8−data Url = Url { unUrl :: Text }
8+newtype Url = Url { unUrl :: Text }
99 deriving Generic
1010
1111instance HasId Url
examples/url-shortener/frontend/src/Frontend.hs+11−14
@@ -3,8 +3,6 @@
33{-# LANGUAGE DataKinds #-}
44{-# LANGUAGE LambdaCase #-}
55{-# LANGUAGE OverloadedStrings #-}
6−{-# LANGUAGE RecursiveDo #-}
7−{-# LANGUAGE TemplateHaskell #-}
86{-# LANGUAGE TypeApplications #-}
97module Frontend where
108
@@ -20,14 +18,14 @@ import Common.Route
2018import Obelisk.Generated.Static
2119
2220type AppWidget js t m =
23− ( DomBuilder t m
24− , MonadFix m
25− , MonadHold t m
26− , TriggerEvent t m
27− , PostBuild t m
28− , PerformEvent t m
29− , Prerender js t m
30− )
21+ ( DomBuilder t m
22+ , MonadFix m
23+ , MonadHold t m
24+ , TriggerEvent t m
25+ , PostBuild t m
26+ , PerformEvent t m
27+ , Prerender js t m
28+ )
3129
3230type FrontendWidget js t m =
3331 ( AppWidget js t m
@@ -47,7 +45,7 @@ urlInput = do
4745 let url = tagPromptlyDyn (_inputElement_value inputEl) click
4846 request <- prerender
4947 (pure never)
50− ((decodeXhrResponse <$>) <$> (performRequestAsync $ shortenRequest <$> url))
48+ ((decodeXhrResponse <$>) <$> performRequestAsync (shortenRequest <$> url))
5149 let requestEv = switchDyn request
5250 holdDyn NotStarted $
5351 leftmost [Loading <$ click, Loaded <$> requestEv]
@@ -64,7 +62,7 @@ createdLink = \case
6462 Loading -> text "loading"
6563 Loaded a -> case a of
6664 Nothing -> text "Error"
67− Just url -> text url
65+ Just url -> elAttr "a" ("href" =: url) $ text url
6866
6967app :: AppWidget js t m => m ()
7068app = do
@@ -77,6 +75,5 @@ frontend = Frontend
7775 { _frontend_head = do
7876 el "title" $ text "Url Shortener"
7977 elAttr "link" ("rel" =: "stylesheet" <> "href" =: static @"style.css") blank
80− , _frontend_body = do
81− app
78+ , _frontend_body = app
8279 }