on master help sign in
Added url-shortener example
commit24c0f64aaf98ccac0def8ab103e42e16bedebd05 authorOwen Shepherd <414owen@gmail.com> dateThu 12 Dec 2019, 18:17 (7 years ago) parent81c31de
21 files changed+259
examples/url-shortener/.gitignore+7 examples/url-shortener/.obelisk/impl/default.nix+7 examples/url-shortener/.obelisk/impl/github.json+7 examples/url-shortener/backend/backend.cabal+28 examples/url-shortener/backend/frontend.jsexe+1 examples/url-shortener/backend/frontendJs/frontend.jsexe+1 examples/url-shortener/backend/src-bin/main.hs+6 examples/url-shortener/backend/src/Backend.hs+25 examples/url-shortener/backend/static+1 examples/url-shortener/cabal.project+2 examples/url-shortener/common/common.cabal+17 examples/url-shortener/common/src/Common/Api.hs+4 examples/url-shortener/common/src/Common/Route.hs+51 examples/url-shortener/config/common/example+1 examples/url-shortener/config/common/route+1 examples/url-shortener/config/readme.md+9 examples/url-shortener/default.nix+16 examples/url-shortener/frontend/frontend.cabal+33 examples/url-shortener/frontend/src-bin/main.hs+10 examples/url-shortener/frontend/src/Frontend.hs+29 examples/url-shortener/static/style.css+3
examples/url-shortener/.gitignorenew file+7
@@ -0,0 +1,7 @@
1+dist-newstyle
2+result
3+result-android
4+result-ios
5+result-exe
6+.attr-cache
7+ghcid-output.txt
examples/url-shortener/.obelisk/impl/default.nixnew file+7
@@ -0,0 +1,7 @@
1+# DO NOT HAND-EDIT THIS FILE
2+import ((import <nixpkgs> {}).fetchFromGitHub (
3+ let json = builtins.fromJSON (builtins.readFile ./github.json);
4+ in { inherit (json) owner repo rev sha256;
5+ private = json.private or false;
6+ }
7+))
examples/url-shortener/.obelisk/impl/github.jsonnew file+7
@@ -0,0 +1,7 @@
1+{
2+ "owner": "obsidiansystems",
3+ "repo": "obelisk",
4+ "branch": "master",
5+ "rev": "a98ad63b88846412f73a43367e496fa58f77c785",
6+ "sha256": "0k5d6687s64ml2yg21c8bjnddgk86fcaq0k25cs41ip5x396akdj"
7+}
examples/url-shortener/backend/backend.cabalnew file+28
@@ -0,0 +1,28 @@
1+name: backend
2+version: 0.1
3+cabal-version: >= 1.8
4+build-type: Simple
5+
6+library
7+ hs-source-dirs: src
8+ if impl(ghcjs)
9+ buildable: False
10+ build-depends: base
11+ , common
12+ , frontend
13+ , obelisk-backend
14+ , obelisk-route
15+ exposed-modules:
16+ Backend
17+ ghc-options: -Wall
18+
19+executable backend
20+ main-is: main.hs
21+ hs-source-dirs: src-bin
22+ if impl(ghcjs)
23+ buildable: False
24+ build-depends: base
25+ , backend
26+ , common
27+ , frontend
28+ , obelisk-backend
examples/url-shortener/backend/frontend.jsexenew file+1
@@ -0,0 +1 @@
1+../frontend-js/bin/frontend.jsexe
examples/url-shortener/backend/frontendJs/frontend.jsexenew file+1
@@ -0,0 +1 @@
1+../../frontend-js/bin/frontend.jsexe
examples/url-shortener/backend/src-bin/main.hsnew file+6
@@ -0,0 +1,6 @@
1+import Backend
2+import Frontend
3+import Obelisk.Backend
4+
5+main :: IO ()
6+main = runBackend backend frontend
examples/url-shortener/backend/src/Backend.hsnew file+25
@@ -0,0 +1,25 @@
1+{-# LANGUAGE EmptyCase #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE LambdaCase #-}
5+{-# LANGUAGE RankNTypes #-}
6+{-# LANGUAGE TypeFamilies #-}
7+module Backend where
8+
9+import Common.Route
10+import Obelisk.Backend
11+import Obelisk.Route
12+import Snap.Core
13+
14+backend :: Backend BackendRoute FrontendRoute
15+backend = Backend
16+ { _backend_run = \serve -> do
17+ serve $ \case
18+ BackendRoute_Missing :/ () -> do
19+ modifyResponse $ setResponseStatus 400 "Malformed Request"
20+ writeBS "400 Malformed Request"
21+ r <- getResponse
22+ finishWith r
23+ return ()
24+ , _backend_routeEncoder = backendRouteEncoder
25+ }
examples/url-shortener/backend/staticnew file+1
@@ -0,0 +1 @@
1+../static
examples/url-shortener/cabal.projectnew file+2
@@ -0,0 +1,2 @@
1+optional-packages:
2+ *
examples/url-shortener/common/common.cabalnew file+17
@@ -0,0 +1,17 @@
1+name: common
2+version: 0.1
3+cabal-version: >= 1.2
4+build-type: Simple
5+
6+library
7+ hs-source-dirs: src
8+ build-depends: base
9+ , obelisk-route
10+ , mtl
11+ , text
12+ default-extensions:
13+ TypeFamilies
14+ PolyKinds
15+ exposed-modules:
16+ Common.Api
17+ Common.Route
examples/url-shortener/common/src/Common/Api.hsnew file+4
@@ -0,0 +1,4 @@
1+module Common.Api where
2+
3+commonStuff :: String
4+commonStuff = "Here is a string defined in Common.Api"
examples/url-shortener/common/src/Common/Route.hsnew file+51
@@ -0,0 +1,51 @@
1+{-# LANGUAGE EmptyCase #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE GADTs #-}
4+{-# LANGUAGE LambdaCase #-}
5+{-# LANGUAGE RankNTypes #-}
6+{-# LANGUAGE TemplateHaskell #-}
7+{-# LANGUAGE KindSignatures #-}
8+{-# LANGUAGE EmptyCase #-}
9+{-# LANGUAGE MultiParamTypeClasses #-}
10+{-# LANGUAGE FlexibleInstances #-}
11+{-# LANGUAGE OverloadedStrings #-}
12+
13+module Common.Route where
14+
15+{- -- You will probably want these imports for composing Encoders.
16+import Prelude hiding (id, (.))
17+import Control.Category
18+-}
19+
20+import Data.Text (Text)
21+import Data.Functor.Identity
22+import Data.Functor.Sum
23+
24+import Obelisk.Route
25+import Obelisk.Route.TH
26+
27+data BackendRoute :: * -> * where
28+ -- | Used to handle unparseable routes.
29+ BackendRoute_Missing :: BackendRoute ()
30+ -- You can define any routes that will be handled specially by the backend here.
31+ -- i.e. These do not serve the frontend, but do something different, such as serving static files.
32+
33+data FrontendRoute :: * -> * where
34+ FrontendRoute_Main :: FrontendRoute ()
35+ -- This type is used to define frontend routes, i.e. ones for which the backend will serve the frontend.
36+
37+backendRouteEncoder
38+ :: Encoder (Either Text) Identity (R (Sum BackendRoute (ObeliskRoute FrontendRoute))) PageName
39+backendRouteEncoder = handleEncoder (const (InL BackendRoute_Missing :/ ())) $
40+ pathComponentEncoder $ \case
41+ InL backendRoute -> case backendRoute of
42+ BackendRoute_Missing -> PathSegment "missing" $ unitEncoder mempty
43+ InR obeliskRoute -> obeliskRouteSegment obeliskRoute $ \case
44+ -- The encoder given to PathEnd determines how to parse query parameters,
45+ -- in this example, we have none, so we insist on it.
46+ FrontendRoute_Main -> PathEnd $ unitEncoder mempty
47+
48+concat <$> mapM deriveRouteComponent
49+ [ ''BackendRoute
50+ , ''FrontendRoute
51+ ]
examples/url-shortener/config/common/examplenew file+1
@@ -0,0 +1 @@
1+This string comes from config/common/example
examples/url-shortener/config/common/routenew file+1
@@ -0,0 +1 @@
1+http://localhost:8000
examples/url-shortener/config/readme.mdnew file+9
@@ -0,0 +1,9 @@
1+### Config
2+
3+Obelisk projects should contain a config folder with the following subfolders: common, frontend, and backend.
4+
5+Things that should never be transmitted to the frontend belong in backend/ (e.g., email credentials)
6+
7+Frontend-only configuration belongs in frontend/.
8+
9+Shared configuration files (e.g., the route config) belong in common/
examples/url-shortener/default.nixnew file+16
@@ -0,0 +1,16 @@
1+{ obelisk ? import ./.obelisk/impl {
2+ system = builtins.currentSystem;
3+ iosSdkVersion = "10.2";
4+ # You must accept the Android Software Development Kit License Agreement at
5+ # https://developer.android.com/studio/terms in order to build Android apps.
6+ # Uncomment and set this to `true` to indicate your acceptance:
7+ # config.android_sdk.accept_license = false;
8+ }
9+}:
10+with obelisk;
11+project ./. ({ ... }: {
12+ android.applicationId = "systems.obsidian.obelisk.examples.minimal";
13+ android.displayName = "Obelisk Minimal Example";
14+ ios.bundleIdentifier = "systems.obsidian.obelisk.examples.minimal";
15+ ios.bundleName = "Obelisk Minimal Example";
16+})
examples/url-shortener/frontend/frontend.cabalnew file+33
@@ -0,0 +1,33 @@
1+name: frontend
2+version: 0.1
3+cabal-version: >= 1.8
4+build-type: Simple
5+
6+library
7+ hs-source-dirs: src
8+ build-depends: base
9+ , common
10+ , obelisk-frontend
11+ , obelisk-route
12+ , reflex-dom
13+ , obelisk-executable-config-lookup
14+ , obelisk-generated-static
15+ , text
16+ exposed-modules:
17+ Frontend
18+ ghc-options: -Wall
19+
20+executable frontend
21+ main-is: main.hs
22+ hs-source-dirs: src-bin
23+ build-depends: base
24+ , common
25+ , obelisk-frontend
26+ , obelisk-route
27+ , reflex-dom
28+ , obelisk-generated-static
29+ , frontend
30+ --TODO: Make these ghc-options optional
31+ ghc-options: -threaded
32+ if os(darwin)
33+ ghc-options: -dynamic
examples/url-shortener/frontend/src-bin/main.hsnew file+10
@@ -0,0 +1,10 @@
1+import Frontend
2+import Common.Route
3+import Obelisk.Frontend
4+import Obelisk.Route.Frontend
5+import Reflex.Dom
6+
7+main :: IO ()
8+main = do
9+ let Right validFullEncoder = checkEncoder backendRouteEncoder
10+ run $ runFrontend validFullEncoder frontend
examples/url-shortener/frontend/src/Frontend.hsnew file+29
@@ -0,0 +1,29 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE TemplateHaskell #-}
4+{-# LANGUAGE TypeApplications #-}
5+module Frontend where
6+
7+import qualified Data.Text as T
8+import qualified Data.Text.Encoding as T
9+import Obelisk.Frontend
10+import Obelisk.Configs
11+import Obelisk.Route
12+import Reflex.Dom.Core
13+
14+import Common.Api
15+import Common.Route
16+import Obelisk.Generated.Static
17+
18+frontend :: Frontend (R FrontendRoute)
19+frontend = Frontend
20+ { _frontend_head = do
21+ el "title" $ text "Url Shortener"
22+ elAttr "link" ("rel" =: "stylesheet" <> "href" =: static @"style.css") blank
23+ , _frontend_body = do
24+ url <- el "label" $ do
25+ text "url: "
26+ inputElement def
27+ shorten <- button "shorten"
28+ return ()
29+ }
examples/url-shortener/static/style.cssnew file+3
@@ -0,0 +1,3 @@
1+html {
2+ background-color: #eee;
3+}