├── VERSION ├── backend ├── static ├── frontend.jsexe ├── frontendJs │ └── frontend.jsexe ├── src-bin │ └── main.hs ├── src │ ├── Database │ │ └── Beam │ │ │ └── Postgres │ │ │ └── Extended.hs │ ├── Backend │ │ └── Conduit │ │ │ ├── Validation.hs │ │ │ ├── Database │ │ │ ├── Users │ │ │ │ ├── Follow.hs │ │ │ │ └── User.hs │ │ │ ├── Tags │ │ │ │ └── Tag.hs │ │ │ ├── Articles │ │ │ │ ├── ArticleTag.hs │ │ │ │ ├── Favorite.hs │ │ │ │ └── Article.hs │ │ │ ├── Comments │ │ │ │ └── Comment.hs │ │ │ └── Tags.hs │ │ │ ├── Claim.hs │ │ │ ├── Errors.hs │ │ │ └── Database.hs │ ├── SetCookieOrphan.hs │ └── Backend.hs └── backend.cabal ├── talk ├── code │ ├── backend │ │ ├── static │ │ ├── frontend.jsexe │ │ ├── frontendJs │ │ │ └── frontend.jsexe │ │ ├── src-bin │ │ │ └── main.hs │ │ ├── src │ │ │ └── Backend.hs │ │ └── backend.cabal │ ├── cabal.project │ ├── config │ │ ├── common │ │ │ └── route │ │ └── readme.md │ ├── static │ │ └── obelisk.jpg │ ├── .gitignore │ ├── frontend │ │ ├── src │ │ │ ├── Frontend │ │ │ │ ├── .ExWorkflows.hs.swp │ │ │ │ ├── ExDom.hs │ │ │ │ ├── ExFoldDyn.hs │ │ │ │ ├── ExMergeEvents.hs │ │ │ │ ├── ExEventWriter.hs │ │ │ │ ├── ExWorkflow.hs │ │ │ │ ├── ExRoutes.hs │ │ │ │ └── ExReader.hs │ │ │ └── Frontend.hs │ │ ├── src-bin │ │ │ └── main.hs │ │ └── frontend.cabal │ ├── common │ │ ├── src │ │ │ └── Common │ │ │ │ ├── Api.hs │ │ │ │ └── Route.hs │ │ └── common.cabal │ ├── .obelisk │ │ └── impl │ │ │ ├── github.json │ │ │ └── default.nix │ └── default.nix ├── custom.css ├── reveal.js │ ├── .travis.yml │ ├── lib │ │ ├── font │ │ │ ├── league-gothic │ │ │ │ ├── LICENSE │ │ │ │ ├── league-gothic.eot │ │ │ │ ├── league-gothic.ttf │ │ │ │ ├── league-gothic.woff │ │ │ │ └── league-gothic.css │ │ │ └── source-sans-pro │ │ │ │ ├── source-sans-pro-italic.eot │ │ │ │ ├── source-sans-pro-italic.ttf │ │ │ │ ├── source-sans-pro-italic.woff │ │ │ │ ├── source-sans-pro-regular.eot │ │ │ │ ├── source-sans-pro-regular.ttf │ │ │ │ ├── source-sans-pro-regular.woff │ │ │ │ ├── source-sans-pro-semibold.eot │ │ │ │ ├── source-sans-pro-semibold.ttf │ │ │ │ ├── source-sans-pro-semibold.woff │ │ │ │ ├── source-sans-pro-semibolditalic.eot │ │ │ │ ├── source-sans-pro-semibolditalic.ttf │ │ │ │ ├── source-sans-pro-semibolditalic.woff │ │ │ │ └── source-sans-pro.css │ │ ├── js │ │ │ ├── html5shiv.js │ │ │ └── classList.js │ │ └── css │ │ │ └── zenburn.css │ ├── test │ │ ├── examples │ │ │ ├── assets │ │ │ │ ├── image1.png │ │ │ │ └── image2.png │ │ │ ├── barebones.html │ │ │ ├── embedded-media.html │ │ │ └── slide-transitions.html │ │ ├── simple.md │ │ ├── test-markdown.js │ │ ├── test-pdf.js │ │ ├── test-markdown-options.js │ │ ├── test-markdown-external.js │ │ ├── test-markdown-options.html │ │ ├── test-markdown-external.html │ │ ├── test-markdown.html │ │ ├── test-pdf.html │ │ ├── test.html │ │ ├── test-markdown-element-attributes.js │ │ └── test-markdown-slide-attributes.js │ ├── .gitignore │ ├── plugin │ │ ├── multiplex │ │ │ ├── client.js │ │ │ ├── package.json │ │ │ ├── master.js │ │ │ └── index.js │ │ ├── markdown │ │ │ └── example.md │ │ ├── math │ │ │ └── math.js │ │ ├── notes-server │ │ │ ├── index.js │ │ │ └── client.js │ │ └── print-pdf │ │ │ └── print-pdf.js │ ├── bower.json │ ├── css │ │ └── theme │ │ │ ├── source │ │ │ ├── night.scss │ │ │ ├── serif.scss │ │ │ ├── league.scss │ │ │ ├── sky.scss │ │ │ ├── beige.scss │ │ │ ├── black.scss │ │ │ ├── white.scss │ │ │ ├── simple.scss │ │ │ ├── moon.scss │ │ │ ├── solarized.scss │ │ │ └── blood.scss │ │ │ ├── template │ │ │ ├── settings.scss │ │ │ └── mixins.scss │ │ │ └── README.md │ ├── CONTRIBUTING.md │ ├── LICENSE │ ├── package.json │ └── index.html └── images │ └── reflex-logo.svg ├── cabal.project ├── config ├── common │ └── route ├── backend │ ├── pgConnStr │ └── jwtKey └── readme.md ├── docs.css ├── data61.png ├── dep ├── mmark │ ├── default.nix │ ├── update.sh │ └── git.json ├── megaparsec │ ├── default.nix │ ├── update.sh │ └── git.json ├── modern-uri │ ├── default.nix │ ├── update.sh │ └── git.json ├── servant-reflex │ ├── default.nix │ ├── update.sh │ └── git.json ├── servant-snap │ ├── default.nix │ ├── update.sh │ └── git.json ├── neat-interpolation │ ├── default.nix │ ├── update.sh │ └── git.json ├── reflex-dom-storage │ ├── default.nix │ ├── update.sh │ └── git.json ├── servant-auth │ ├── update.sh │ ├── git.json │ └── default.nix └── dep.nix ├── expected_setup.png ├── static ├── obelisk.jpg ├── avatars │ ├── dashy.png │ ├── pinkie.png │ └── fluttershy.png ├── ionicons │ └── fonts │ │ ├── ionicons.eot │ │ ├── ionicons.ttf │ │ └── ionicons.woff └── gfonts │ ├── Titillium_Web │ ├── TitilliumWeb-Black.ttf │ ├── TitilliumWeb-Bold.ttf │ ├── TitilliumWeb-Italic.ttf │ ├── TitilliumWeb-Light.ttf │ ├── TitilliumWeb-Regular.ttf │ ├── TitilliumWeb-SemiBold.ttf │ ├── TitilliumWeb-BoldItalic.ttf │ ├── TitilliumWeb-ExtraLight.ttf │ ├── TitilliumWeb-LightItalic.ttf │ ├── TitilliumWeb-ExtraLightItalic.ttf │ └── TitilliumWeb-SemiBoldItalic.ttf │ ├── Source_Sans_Pro │ ├── SourceSansPro-Black.ttf │ ├── SourceSansPro-Bold.ttf │ ├── SourceSansPro-Light.ttf │ ├── SourceSansPro-Italic.ttf │ ├── SourceSansPro-Regular.ttf │ ├── SourceSansPro-SemiBold.ttf │ ├── SourceSansPro-BoldItalic.ttf │ ├── SourceSansPro-ExtraLight.ttf │ ├── SourceSansPro-BlackItalic.ttf │ ├── SourceSansPro-LightItalic.ttf │ ├── SourceSansPro-SemiBoldItalic.ttf │ └── SourceSansPro-ExtraLightItalic.ttf │ └── Merriweather_Sans │ ├── MerriweatherSans-Bold.ttf │ ├── MerriweatherSans-Light.ttf │ ├── MerriweatherSans-Italic.ttf │ ├── MerriweatherSans-Regular.ttf │ ├── MerriweatherSans-BoldItalic.ttf │ ├── MerriweatherSans-ExtraBold.ttf │ ├── MerriweatherSans-LightItalic.ttf │ └── MerriweatherSans-ExtraBoldItalic.ttf ├── workshop ├── basic.png ├── 01-foldDyn │ ├── counter.png │ ├── foldDyn.png │ ├── merge.png │ ├── foldDyn.gv │ ├── counter.gv │ └── merge.gv ├── basic.gv ├── 9001-bonus.md └── 9001-bonus.html ├── docs └── reflex-frp.pdf ├── .gitignore ├── .gitmodules ├── devenv ├── docker │ ├── Dockerfile_db │ ├── Dockerfile_obelisk │ ├── compose.yml │ ├── entrypoint.sh │ ├── build.sh │ └── ob ├── nix │ └── hoogle-server ├── cachix_push.sh ├── common │ └── nix.conf ├── vbox │ ├── build.sh │ ├── setup.sql │ └── export.sh └── release_checklist.md ├── links-to-html.lua ├── common ├── src │ └── Common │ │ └── Conduit │ │ ├── Api │ │ ├── Validation.hs │ │ ├── Attribute.hs │ │ ├── Tags.hs │ │ ├── Users │ │ │ ├── Credentials.hs │ │ │ └── Registrant.hs │ │ ├── Profiles.hs │ │ ├── Errors.hs │ │ ├── Articles │ │ │ ├── CreateComment.hs │ │ │ ├── Comment.hs │ │ │ ├── Articles.hs │ │ │ ├── Article.hs │ │ │ └── Attributes.hs │ │ ├── User │ │ │ ├── Update.hs │ │ │ └── Account.hs │ │ ├── Profiles │ │ │ └── Profile.hs │ │ ├── User.hs │ │ ├── Users.hs │ │ ├── Namespace.hs │ │ └── Articles.hs │ │ └── Api.hs └── common.cabal ├── .obelisk └── impl │ ├── github.json │ └── default.nix ├── frontend ├── src-bin │ └── main.hs ├── src │ └── Frontend │ │ ├── Register.hs │ │ ├── Editor.hs │ │ ├── Profile.hs │ │ ├── Nav.hs │ │ ├── Head.hs │ │ └── LocalStorageKey.hs └── frontend.cabal ├── .dir-locals.el ├── default.nix ├── README.md ├── CHANGELOG.md └── README.html /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.0 -------------------------------------------------------------------------------- /backend/static: -------------------------------------------------------------------------------- 1 | ../static -------------------------------------------------------------------------------- /talk/code/backend/static: -------------------------------------------------------------------------------- 1 | ../static -------------------------------------------------------------------------------- /cabal.project: -------------------------------------------------------------------------------- 1 | optional-packages: 2 | * 3 | -------------------------------------------------------------------------------- /config/common/route: -------------------------------------------------------------------------------- 1 | http://localhost:8001 2 | -------------------------------------------------------------------------------- /docs.css: -------------------------------------------------------------------------------- 1 | img { 2 | max-width: 100%; 3 | } 4 | -------------------------------------------------------------------------------- /backend/frontend.jsexe: -------------------------------------------------------------------------------- 1 | ../frontend-js/bin/frontend.jsexe -------------------------------------------------------------------------------- /talk/code/cabal.project: -------------------------------------------------------------------------------- 1 | optional-packages: 2 | * 3 | -------------------------------------------------------------------------------- /talk/code/config/common/route: -------------------------------------------------------------------------------- 1 | http://localhost:8001 2 | -------------------------------------------------------------------------------- /config/backend/pgConnStr: -------------------------------------------------------------------------------- 1 | postgres://conduit:conduit@localhost/conduit -------------------------------------------------------------------------------- /talk/code/backend/frontend.jsexe: -------------------------------------------------------------------------------- 1 | ../frontend-js/bin/frontend.jsexe -------------------------------------------------------------------------------- /backend/frontendJs/frontend.jsexe: -------------------------------------------------------------------------------- 1 | ../../frontend-js/bin/frontend.jsexe -------------------------------------------------------------------------------- /talk/code/backend/frontendJs/frontend.jsexe: -------------------------------------------------------------------------------- 1 | ../../frontend-js/bin/frontend.jsexe -------------------------------------------------------------------------------- /data61.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/data61.png -------------------------------------------------------------------------------- /dep/mmark/default.nix: -------------------------------------------------------------------------------- 1 | import ../dep.nix { name = "mmark"; gitpath = ./git.json; } 2 | -------------------------------------------------------------------------------- /dep/megaparsec/default.nix: -------------------------------------------------------------------------------- 1 | import ../dep.nix { name = "megaparsec"; gitpath = ./git.json; } 2 | -------------------------------------------------------------------------------- /dep/modern-uri/default.nix: -------------------------------------------------------------------------------- 1 | import ../dep.nix { name = "modern-uri"; gitpath = ./git.json; } 2 | -------------------------------------------------------------------------------- /dep/servant-reflex/default.nix: -------------------------------------------------------------------------------- 1 | import ../dep.nix { name = "servant-reflex"; gitpath = ./git.json; } 2 | -------------------------------------------------------------------------------- /dep/servant-snap/default.nix: -------------------------------------------------------------------------------- 1 | import ../dep.nix { name = "servant-snap"; gitpath = ./git.json; } 2 | -------------------------------------------------------------------------------- /expected_setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/expected_setup.png -------------------------------------------------------------------------------- /static/obelisk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/obelisk.jpg -------------------------------------------------------------------------------- /workshop/basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/workshop/basic.png -------------------------------------------------------------------------------- /docs/reflex-frp.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/docs/reflex-frp.pdf -------------------------------------------------------------------------------- /dep/neat-interpolation/default.nix: -------------------------------------------------------------------------------- 1 | import ../dep.nix { name = "neat-interpolation"; gitpath = ./git.json; } 2 | -------------------------------------------------------------------------------- /dep/reflex-dom-storage/default.nix: -------------------------------------------------------------------------------- 1 | import ../dep.nix { name = "reflex-dom-storage"; gitpath = ./git.json; } 2 | -------------------------------------------------------------------------------- /static/avatars/dashy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/avatars/dashy.png -------------------------------------------------------------------------------- /static/avatars/pinkie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/avatars/pinkie.png -------------------------------------------------------------------------------- /talk/code/static/obelisk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/code/static/obelisk.jpg -------------------------------------------------------------------------------- /talk/custom.css: -------------------------------------------------------------------------------- 1 | pre { 2 | width: 100% !important; 3 | } 4 | 5 | iframe { 6 | background-color: white; 7 | } 8 | -------------------------------------------------------------------------------- /talk/reveal.js/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4 4 | after_script: 5 | - npm run build -- retire 6 | -------------------------------------------------------------------------------- /static/avatars/fluttershy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/avatars/fluttershy.png -------------------------------------------------------------------------------- /workshop/01-foldDyn/counter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/workshop/01-foldDyn/counter.png -------------------------------------------------------------------------------- /workshop/01-foldDyn/foldDyn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/workshop/01-foldDyn/foldDyn.png -------------------------------------------------------------------------------- /workshop/01-foldDyn/merge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/workshop/01-foldDyn/merge.png -------------------------------------------------------------------------------- /static/ionicons/fonts/ionicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/ionicons/fonts/ionicons.eot -------------------------------------------------------------------------------- /static/ionicons/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/ionicons/fonts/ionicons.ttf -------------------------------------------------------------------------------- /static/ionicons/fonts/ionicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/ionicons/fonts/ionicons.woff -------------------------------------------------------------------------------- /talk/code/.gitignore: -------------------------------------------------------------------------------- 1 | dist-newstyle 2 | result 3 | result-android 4 | result-ios 5 | result-exe 6 | .attr-cache 7 | ghcid-output.txt 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist-newstyle 2 | result 3 | result-android 4 | result-ios 5 | result-exe 6 | .attr-cache 7 | ghcid-output.txt 8 | /Dockerfile* 9 | -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/league-gothic/LICENSE: -------------------------------------------------------------------------------- 1 | SIL Open Font License (OFL) 2 | http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL 3 | -------------------------------------------------------------------------------- /backend/src-bin/main.hs: -------------------------------------------------------------------------------- 1 | import Backend 2 | import Frontend 3 | import Obelisk.Backend 4 | 5 | main :: IO () 6 | main = runBackend backend frontend 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "reflex-realworld-example"] 2 | path = reflex-realworld-example 3 | url = https://github.com/qfpl/reflex-realworld-example.git 4 | -------------------------------------------------------------------------------- /talk/reveal.js/test/examples/assets/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/test/examples/assets/image1.png -------------------------------------------------------------------------------- /talk/reveal.js/test/examples/assets/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/test/examples/assets/image2.png -------------------------------------------------------------------------------- /talk/reveal.js/test/simple.md: -------------------------------------------------------------------------------- 1 | ## Slide 1.1 2 | 3 | ```js 4 | var a = 1; 5 | ``` 6 | 7 | 8 | ## Slide 1.2 9 | 10 | 11 | 12 | ## Slide 2 13 | -------------------------------------------------------------------------------- /talk/code/backend/src-bin/main.hs: -------------------------------------------------------------------------------- 1 | import Backend 2 | import Frontend 3 | import Obelisk.Backend 4 | 5 | main :: IO () 6 | main = runBackend backend frontend 7 | -------------------------------------------------------------------------------- /static/gfonts/Titillium_Web/TitilliumWeb-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Titillium_Web/TitilliumWeb-Black.ttf -------------------------------------------------------------------------------- /static/gfonts/Titillium_Web/TitilliumWeb-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Titillium_Web/TitilliumWeb-Bold.ttf -------------------------------------------------------------------------------- /static/gfonts/Titillium_Web/TitilliumWeb-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Titillium_Web/TitilliumWeb-Italic.ttf -------------------------------------------------------------------------------- /static/gfonts/Titillium_Web/TitilliumWeb-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Titillium_Web/TitilliumWeb-Light.ttf -------------------------------------------------------------------------------- /talk/code/frontend/src/Frontend/.ExWorkflows.hs.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/code/frontend/src/Frontend/.ExWorkflows.hs.swp -------------------------------------------------------------------------------- /static/gfonts/Source_Sans_Pro/SourceSansPro-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Source_Sans_Pro/SourceSansPro-Black.ttf -------------------------------------------------------------------------------- /static/gfonts/Source_Sans_Pro/SourceSansPro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Source_Sans_Pro/SourceSansPro-Bold.ttf -------------------------------------------------------------------------------- /static/gfonts/Source_Sans_Pro/SourceSansPro-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Source_Sans_Pro/SourceSansPro-Light.ttf -------------------------------------------------------------------------------- /static/gfonts/Titillium_Web/TitilliumWeb-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Titillium_Web/TitilliumWeb-Regular.ttf -------------------------------------------------------------------------------- /static/gfonts/Titillium_Web/TitilliumWeb-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Titillium_Web/TitilliumWeb-SemiBold.ttf -------------------------------------------------------------------------------- /static/gfonts/Source_Sans_Pro/SourceSansPro-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Source_Sans_Pro/SourceSansPro-Italic.ttf -------------------------------------------------------------------------------- /static/gfonts/Source_Sans_Pro/SourceSansPro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Source_Sans_Pro/SourceSansPro-Regular.ttf -------------------------------------------------------------------------------- /static/gfonts/Source_Sans_Pro/SourceSansPro-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Source_Sans_Pro/SourceSansPro-SemiBold.ttf -------------------------------------------------------------------------------- /static/gfonts/Titillium_Web/TitilliumWeb-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Titillium_Web/TitilliumWeb-BoldItalic.ttf -------------------------------------------------------------------------------- /static/gfonts/Titillium_Web/TitilliumWeb-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Titillium_Web/TitilliumWeb-ExtraLight.ttf -------------------------------------------------------------------------------- /static/gfonts/Titillium_Web/TitilliumWeb-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Titillium_Web/TitilliumWeb-LightItalic.ttf -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/league-gothic/league-gothic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/lib/font/league-gothic/league-gothic.eot -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/league-gothic/league-gothic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/lib/font/league-gothic/league-gothic.ttf -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/league-gothic/league-gothic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/lib/font/league-gothic/league-gothic.woff -------------------------------------------------------------------------------- /static/gfonts/Merriweather_Sans/MerriweatherSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Merriweather_Sans/MerriweatherSans-Bold.ttf -------------------------------------------------------------------------------- /static/gfonts/Merriweather_Sans/MerriweatherSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Merriweather_Sans/MerriweatherSans-Light.ttf -------------------------------------------------------------------------------- /static/gfonts/Source_Sans_Pro/SourceSansPro-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Source_Sans_Pro/SourceSansPro-BoldItalic.ttf -------------------------------------------------------------------------------- /static/gfonts/Source_Sans_Pro/SourceSansPro-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Source_Sans_Pro/SourceSansPro-ExtraLight.ttf -------------------------------------------------------------------------------- /talk/code/common/src/Common/Api.hs: -------------------------------------------------------------------------------- 1 | module Common.Api where 2 | 3 | commonStuff :: String 4 | commonStuff = "Here is a string defined in code common to the frontend and backend." 5 | -------------------------------------------------------------------------------- /static/gfonts/Merriweather_Sans/MerriweatherSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Merriweather_Sans/MerriweatherSans-Italic.ttf -------------------------------------------------------------------------------- /static/gfonts/Merriweather_Sans/MerriweatherSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Merriweather_Sans/MerriweatherSans-Regular.ttf -------------------------------------------------------------------------------- /static/gfonts/Source_Sans_Pro/SourceSansPro-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Source_Sans_Pro/SourceSansPro-BlackItalic.ttf -------------------------------------------------------------------------------- /static/gfonts/Source_Sans_Pro/SourceSansPro-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Source_Sans_Pro/SourceSansPro-LightItalic.ttf -------------------------------------------------------------------------------- /static/gfonts/Titillium_Web/TitilliumWeb-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Titillium_Web/TitilliumWeb-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /static/gfonts/Titillium_Web/TitilliumWeb-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Titillium_Web/TitilliumWeb-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /devenv/docker/Dockerfile_db: -------------------------------------------------------------------------------- 1 | FROM postgres:9 2 | 3 | ENV POSTGRES_USER conduit 4 | ENV POSTGRES_PASSWORD conduit 5 | COPY devenv/common/dbdump.sql /docker-entrypoint-initdb.d/00-dbdump.sql 6 | -------------------------------------------------------------------------------- /links-to-html.lua: -------------------------------------------------------------------------------- 1 | function Link(el) 2 | if string.match(el.target, "http") then 3 | else 4 | el.target = string.gsub(el.target, "%.md", ".html") 5 | end 6 | return el 7 | end 8 | -------------------------------------------------------------------------------- /static/gfonts/Merriweather_Sans/MerriweatherSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Merriweather_Sans/MerriweatherSans-BoldItalic.ttf -------------------------------------------------------------------------------- /static/gfonts/Merriweather_Sans/MerriweatherSans-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Merriweather_Sans/MerriweatherSans-ExtraBold.ttf -------------------------------------------------------------------------------- /static/gfonts/Source_Sans_Pro/SourceSansPro-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Source_Sans_Pro/SourceSansPro-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /dep/mmark/update.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env nix-shell 2 | #! nix-shell -p nix-prefetch-git -i bash 3 | HERE=$(dirname $0) 4 | nix-prefetch-git https://github.com/mmark-md/mmark.git $1 > $HERE/git.json 5 | -------------------------------------------------------------------------------- /static/gfonts/Merriweather_Sans/MerriweatherSans-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Merriweather_Sans/MerriweatherSans-LightItalic.ttf -------------------------------------------------------------------------------- /static/gfonts/Source_Sans_Pro/SourceSansPro-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Source_Sans_Pro/SourceSansPro-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-italic.eot -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-italic.ttf -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/Validation.hs: -------------------------------------------------------------------------------- 1 | module Common.Conduit.Api.Validation where 2 | 3 | import Data.Map (Map) 4 | import Data.Text (Text) 5 | 6 | type ValidationErrors = Map Text [Text] 7 | -------------------------------------------------------------------------------- /static/gfonts/Merriweather_Sans/MerriweatherSans-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/static/gfonts/Merriweather_Sans/MerriweatherSans-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-italic.woff -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-regular.eot -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-regular.ttf -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-regular.woff -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibold.eot -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibold.ttf -------------------------------------------------------------------------------- /dep/megaparsec/update.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env nix-shell 2 | #! nix-shell -p nix-prefetch-git -i bash 3 | HERE=$(dirname $0) 4 | nix-prefetch-git https://github.com/mrkkrp/megaparsec.git $1 > $HERE/git.json 5 | -------------------------------------------------------------------------------- /dep/modern-uri/update.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env nix-shell 2 | #! nix-shell -p nix-prefetch-git -i bash 3 | HERE=$(dirname $0) 4 | nix-prefetch-git https://github.com/mrkkrp/modern-uri.git $1 > $HERE/git.json 5 | -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibold.woff -------------------------------------------------------------------------------- /dep/servant-auth/update.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env nix-shell 2 | #! nix-shell -p nix-prefetch-git -i bash 3 | HERE=$(dirname $0) 4 | nix-prefetch-git https://github.com/benkolera/servant-auth.git $1 > $HERE/git.json 5 | -------------------------------------------------------------------------------- /dep/servant-snap/update.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env nix-shell 2 | #! nix-shell -p nix-prefetch-git -i bash 3 | HERE=$(dirname $0) 4 | nix-prefetch-git https://github.com/antislava/servant-snap.git $1 > $HERE/git.json 5 | -------------------------------------------------------------------------------- /dep/reflex-dom-storage/update.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env nix-shell 2 | #! nix-shell -p nix-prefetch-git -i bash 3 | HERE=$(dirname $0) 4 | nix-prefetch-git https://github.com/qfpl/reflex-dom-storage.git $1 > $HERE/git.json 5 | -------------------------------------------------------------------------------- /dep/servant-reflex/update.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env nix-shell 2 | #! nix-shell -p nix-prefetch-git -i bash 3 | HERE=$(dirname $0) 4 | nix-prefetch-git https://github.com/imalsogreg/servant-reflex.git $1 > $HERE/git.json 5 | -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibolditalic.eot -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibolditalic.ttf -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qfpl/reflex-realworld-workshop/HEAD/talk/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibolditalic.woff -------------------------------------------------------------------------------- /dep/neat-interpolation/update.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env nix-shell 2 | #! nix-shell -p nix-prefetch-git -i bash 3 | HERE=$(dirname $0) 4 | nix-prefetch-git https://github.com/nikita-volkov/neat-interpolation.git $1 > $HERE/git.json 5 | -------------------------------------------------------------------------------- /talk/reveal.js/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | *.iws 4 | *.eml 5 | out/ 6 | .DS_Store 7 | .svn 8 | log/*.log 9 | tmp/** 10 | node_modules/ 11 | package-lock.json 12 | .sass-cache 13 | css/reveal.min.css 14 | js/reveal.min.js 15 | -------------------------------------------------------------------------------- /dep/dep.nix: -------------------------------------------------------------------------------- 1 | { name, gitpath, nixpkgs ? import {} }: self: super: 2 | let gitinfo = nixpkgs.pkgs.lib.importJSON gitpath; 3 | in self.callCabal2nix name (nixpkgs.pkgs.fetchgit { 4 | inherit (gitinfo) url rev sha256; 5 | }) {} 6 | -------------------------------------------------------------------------------- /.obelisk/impl/github.json: -------------------------------------------------------------------------------- 1 | { 2 | "owner": "obsidiansystems", 3 | "repo": "obelisk", 4 | "branch": "develop", 5 | "rev": "20ba787575807808d5872689a4787ae75d446de5", 6 | "sha256": "1iqvlz6z8wbd1sfq4bz5jw9swsxgpprk9cwq9s7d4lp1542iidhc" 7 | } 8 | -------------------------------------------------------------------------------- /talk/code/.obelisk/impl/github.json: -------------------------------------------------------------------------------- 1 | { 2 | "owner": "obsidiansystems", 3 | "repo": "obelisk", 4 | "branch": "master", 5 | "rev": "ca6edae87909d0d3c31f6c4a89a2f70bcb209924", 6 | "sha256": "1v40jv4zdvwnzv4113pi3sbfirgk1s2672slxzz4gq3ldkzphx7f" 7 | } 8 | -------------------------------------------------------------------------------- /config/backend/jwtKey: -------------------------------------------------------------------------------- 1 | Ug3DSsjIdC6ptLtVBoyqdKaIhi90GOHJ11srNN27FezBWAskC5NPbbFiCYW4UwsLfzuwrb4VmOGIRUMCcghFzVi4X6dqkR46e4fXVWpAsILFmBKVmVf4qiTNnQMzM3Nwv3cd44pfPcuNyerH5mfcJR8Yibn2NGyn0YSXtbzcSmS2jjMKZkSszaXe1yyb0O5U4b39ko4G4PqASOoHweAUxl5QEZLMuK5wCaGvi3xKsauub8hrOOTUpjWHhDO9vChg -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/Attribute.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TypeFamilies #-} 2 | module Common.Conduit.Api.Attribute where 3 | 4 | import Data.Functor.Identity (Identity) 5 | 6 | type family Attribute f a where 7 | Attribute Identity a = a 8 | Attribute Maybe a = Maybe a 9 | -------------------------------------------------------------------------------- /dep/mmark/git.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/mmark-md/mmark.git", 3 | "rev": "91fc7ac557367ab7350516d30d34fce1dcded7ba", 4 | "date": "2019-04-30T11:08:56+02:00", 5 | "sha256": "1sjcq2zr4zif42ayi6gcmwg5z74gdxga67bs1dkwzmmqcy4qmcig", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /.obelisk/impl/default.nix: -------------------------------------------------------------------------------- 1 | # DO NOT HAND-EDIT THIS FILE 2 | import ((import {}).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 | )) 8 | -------------------------------------------------------------------------------- /devenv/nix/hoogle-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | set -x 4 | 5 | cd $(dirname $0)/../.. 6 | if [ ! -e VERSION ]; then 7 | echo "Couldn't find version. Dying.!" 8 | exit 1; 9 | fi 10 | VERSION=$(cat VERSION) 11 | 12 | nix-shell -A shells.ghc --run "hoogle server" 13 | -------------------------------------------------------------------------------- /talk/reveal.js/lib/js/html5shiv.js: -------------------------------------------------------------------------------- 1 | document.createElement('header'); 2 | document.createElement('nav'); 3 | document.createElement('section'); 4 | document.createElement('article'); 5 | document.createElement('aside'); 6 | document.createElement('footer'); 7 | document.createElement('hgroup'); -------------------------------------------------------------------------------- /dep/megaparsec/git.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/mrkkrp/megaparsec.git", 3 | "rev": "9fff501f7794c01e2cf4a7a492f1cfef67fab19a", 4 | "date": "2018-11-08T21:53:07+07:00", 5 | "sha256": "0a9g6gpc8m9qrvldwn4chs0yqnr4dps93achg1df72lxknrpp0iy", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /dep/modern-uri/git.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/mrkkrp/modern-uri.git", 3 | "rev": "b51576d58084e5cbba3e5cbcae0addd01561af1e", 4 | "date": "2018-09-24T11:04:26+07:00", 5 | "sha256": "09bp9zip5784r8hr2q3y2padlsv3bxqfh2d5vvyixfd0bhw2rpkw", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /dep/servant-auth/git.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/benkolera/servant-auth.git", 3 | "rev": "d2dbe8e5ed1df232245ea11f1a75249fac077609", 4 | "date": "2019-05-03T16:18:26+10:00", 5 | "sha256": "02lzmi18scm8wxg9ggzd9sxmrvab3h51gccmjsx5m1g77an7cgjr", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /dep/servant-snap/git.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/antislava/servant-snap.git", 3 | "rev": "3f899473a12890777caa226ab249498ab5442837", 4 | "date": "2018-11-29T11:07:50+01:00", 5 | "sha256": "1zk4fcrfknr798cr2p6lz8rbvlsar43wfdjkcxncmkrmf5hg5nyn", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /talk/code/.obelisk/impl/default.nix: -------------------------------------------------------------------------------- 1 | # DO NOT HAND-EDIT THIS FILE 2 | import ((import {}).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 | )) 8 | -------------------------------------------------------------------------------- /dep/servant-reflex/git.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/imalsogreg/servant-reflex.git", 3 | "rev": "44595630e2d1597911ecb204e792d17db7e4a4ee", 4 | "date": "2019-04-14T16:43:41-04:00", 5 | "sha256": "009d8vr6mxfm9czywhb8haq8pwvnl9ha2cdmaagk1hp6q4yhfq1n", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /dep/reflex-dom-storage/git.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/qfpl/reflex-dom-storage.git", 3 | "rev": "63eaa03ca7bf3f7019cf671ba252a14c1bdfa49e", 4 | "date": "2019-04-26T16:38:32+10:00", 5 | "sha256": "1bh91f9z73ziphxf33df7s3jc6lns0hr47vdxjk89qkcfgxxaf3l", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /dep/neat-interpolation/git.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/nikita-volkov/neat-interpolation.git", 3 | "rev": "95c009643e89dd5db67d715078a007f7de79de27", 4 | "date": "2018-10-04T19:21:49+03:00", 5 | "sha256": "0c7wqym619nq13xrf43w6bay0yl4jnxjaj4a0akmfw3srdcz07yf", 6 | "fetchSubmodules": false 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src-bin/main.hs: -------------------------------------------------------------------------------- 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 11 | -------------------------------------------------------------------------------- /talk/code/frontend/src-bin/main.hs: -------------------------------------------------------------------------------- 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 11 | -------------------------------------------------------------------------------- /dep/servant-auth/default.nix: -------------------------------------------------------------------------------- 1 | { nixpkgs ? import {} }: self: super: 2 | let 3 | gitinfo = nixpkgs.pkgs.lib.importJSON ./git.json; 4 | servant-auth-src = nixpkgs.pkgs.fetchgit { 5 | inherit (gitinfo) url rev sha256; 6 | }; 7 | in self.callCabal2nix "servant-auth-snap" "${servant-auth-src}/servant-auth-snap" {} 8 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/Tags.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds, TypeOperators #-} 2 | module Common.Conduit.Api.Tags (TagsApi) where 3 | 4 | import Data.Text (Text) 5 | import Servant.API (Get, JSON) 6 | 7 | import Common.Conduit.Api.Namespace (Namespace) 8 | 9 | type TagsApi token = Get '[JSON] (Namespace "tags" [Text]) 10 | -------------------------------------------------------------------------------- /talk/reveal.js/test/test-markdown.js: -------------------------------------------------------------------------------- 1 | Reveal.addEventListener( 'ready', function() { 2 | 3 | QUnit.module( 'Markdown' ); 4 | 5 | QUnit.test( 'Vertical separator', function( assert ) { 6 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section>section' ).length, 2, 'found two slides' ); 7 | }); 8 | 9 | } ); 10 | 11 | Reveal.initialize(); 12 | -------------------------------------------------------------------------------- /devenv/docker/Dockerfile_obelisk: -------------------------------------------------------------------------------- 1 | FROM nixos/nix 2 | 3 | COPY devenv/common/nix.conf /etc/nix/nix.conf 4 | COPY devenv/docker/entrypoint.sh /entrypoint.sh 5 | 6 | COPY . /root/prebuild 7 | WORKDIR /root/prebuild 8 | 9 | RUN nix-env -f . -iA obelisk.command 10 | RUN nix-build . -A shells.ghc 11 | RUN nix-env -i bash-interactive su-exec 12 | 13 | WORKDIR /workshop 14 | -------------------------------------------------------------------------------- /devenv/cachix_push.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | set -x 4 | 5 | cd $(dirname $0)/../ 6 | if [ ! -e VERSION ]; then 7 | echo "Couldn't find version. Dying.!" 8 | exit 1; 9 | fi 10 | VERSION=$(cat VERSION) 11 | 12 | nix-build -A shells.ghc | cachix push qfpl 13 | nix-store -q --requisites $(nix-instantiate -A shells.ghc) | grep cabal2nix | cachix push qfpl 14 | -------------------------------------------------------------------------------- /config/readme.md: -------------------------------------------------------------------------------- 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/ 10 | -------------------------------------------------------------------------------- /talk/code/common/common.cabal: -------------------------------------------------------------------------------- 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 18 | -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/league-gothic/league-gothic.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'League Gothic'; 3 | src: url('league-gothic.eot'); 4 | src: url('league-gothic.eot?#iefix') format('embedded-opentype'), 5 | url('league-gothic.woff') format('woff'), 6 | url('league-gothic.ttf') format('truetype'); 7 | 8 | font-weight: normal; 9 | font-style: normal; 10 | } -------------------------------------------------------------------------------- /talk/reveal.js/test/test-pdf.js: -------------------------------------------------------------------------------- 1 | Reveal.addEventListener( 'ready', function() { 2 | 3 | // Only one test for now, we're mainly ensuring that there 4 | // are no execution errors when running PDF mode 5 | 6 | QUnit.test( 'Reveal.isReady', function( assert ) { 7 | assert.strictEqual( Reveal.isReady(), true, 'returns true' ); 8 | }); 9 | 10 | } ); 11 | 12 | Reveal.initialize({ pdf: true }); 13 | -------------------------------------------------------------------------------- /talk/code/config/readme.md: -------------------------------------------------------------------------------- 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/ 10 | -------------------------------------------------------------------------------- /devenv/common/nix.conf: -------------------------------------------------------------------------------- 1 | sandbox = false 2 | substituters = https://cache.nixos.org https://qfpl.cachix.org https://nixcache.reflex-frp.org https://hydra.qfpl.io 3 | trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= ryantrinkle.com-1:JJiAKaRv9mWgpVAz8dwewnZe0AzzEAzPkagE9SP5NWI= qfpl.cachix.org-1:JTTxGW07zLPAGglHlMbMC3kQpCd6eFRgbtnuorCogYw= qfpl.io:xME0cdnyFcOlMD1nwmn6VrkkGgDNLLpMXoMYl58bz5g= 4 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((markdown-mode 5 | (markdown-command . "pandoc -c file:///home/bkolera/src/github/qfpl/reflex-realworld-workshop/docs.css --lua-filter=/home/bkolera/src/github/qfpl/reflex-realworld-workshop/links-to-html.lua --from gfm --metadata pagetitle=\"Workshop Docs\" -t html5 --mathjax --highlight-style pygments --standalone"))) 6 | -------------------------------------------------------------------------------- /workshop/01-foldDyn/foldDyn.gv: -------------------------------------------------------------------------------- 1 | digraph H { 2 | rankdir="LR"; 3 | 4 | foldDyn [ 5 | shape=plaintext 6 | label=< 7 | 8 | 9 | 10 | 11 | 12 | 13 |
foldDyn
a -> b -> bDynamic t b
b
Event t a
14 | >]; 15 | } 16 | -------------------------------------------------------------------------------- /talk/reveal.js/plugin/multiplex/client.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var multiplex = Reveal.getConfig().multiplex; 3 | var socketId = multiplex.id; 4 | var socket = io.connect(multiplex.url); 5 | 6 | socket.on(multiplex.id, function(data) { 7 | // ignore data from sockets that aren't ours 8 | if (data.socketId !== socketId) { return; } 9 | if( window.location.host === 'localhost:1947' ) return; 10 | 11 | Reveal.setState(data.state); 12 | }); 13 | }()); 14 | -------------------------------------------------------------------------------- /talk/code/backend/src/Backend.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE EmptyCase #-} 2 | {-# LANGUAGE FlexibleContexts #-} 3 | {-# LANGUAGE LambdaCase #-} 4 | {-# LANGUAGE RankNTypes #-} 5 | {-# LANGUAGE TypeFamilies #-} 6 | module Backend where 7 | 8 | import Common.Route 9 | import Obelisk.Backend 10 | 11 | backend :: Backend BackendRoute FrontendRoute 12 | backend = Backend 13 | { _backend_run = \serve -> serve $ const $ return () 14 | , _backend_routeEncoder = backendRouteEncoder 15 | } 16 | -------------------------------------------------------------------------------- /talk/code/default.nix: -------------------------------------------------------------------------------- 1 | { system ? builtins.currentSystem # TODO: Get rid of this system cruft 2 | , iosSdkVersion ? "10.2" 3 | }: 4 | with import ./.obelisk/impl { inherit system iosSdkVersion; }; 5 | project ./. ({ ... }: { 6 | android.applicationId = "systems.obsidian.obelisk.examples.minimal"; 7 | android.displayName = "Obelisk Minimal Example"; 8 | ios.bundleIdentifier = "systems.obsidian.obelisk.examples.minimal"; 9 | ios.bundleName = "Obelisk Minimal Example"; 10 | }) 11 | -------------------------------------------------------------------------------- /talk/reveal.js/plugin/multiplex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reveal-js-multiplex", 3 | "version": "1.0.0", 4 | "description": "reveal.js multiplex server", 5 | "homepage": "http://revealjs.com", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "engines": { 10 | "node": "~4.1.1" 11 | }, 12 | "dependencies": { 13 | "express": "~4.13.3", 14 | "grunt-cli": "~0.1.13", 15 | "mustache": "~2.2.1", 16 | "socket.io": "~1.3.7" 17 | }, 18 | "license": "MIT" 19 | } 20 | -------------------------------------------------------------------------------- /talk/code/frontend/src/Frontend/ExDom.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE ScopedTypeVariables #-} 3 | module Frontend.ExDom where 4 | 5 | import Reflex.Dom.Core 6 | import Control.Monad.Fix (MonadFix) 7 | 8 | exDom :: (DomBuilder t m, PostBuild t m, MonadHold t m, MonadFix m) => m () 9 | exDom = do 10 | buttonClickE :: Event t () <- button "Click me" 11 | clickCountDyn :: Dynamic t Int <- count buttonClickE 12 | text " Clicked: " 13 | display clickCountDyn 14 | text " times!" 15 | -------------------------------------------------------------------------------- /talk/reveal.js/plugin/markdown/example.md: -------------------------------------------------------------------------------- 1 | # Markdown Demo 2 | 3 | 4 | 5 | ## External 1.1 6 | 7 | Content 1.1 8 | 9 | Note: This will only appear in the speaker notes window. 10 | 11 | 12 | ## External 1.2 13 | 14 | Content 1.2 15 | 16 | 17 | 18 | ## External 2 19 | 20 | Content 2.1 21 | 22 | 23 | 24 | ## External 3.1 25 | 26 | Content 3.1 27 | 28 | 29 | ## External 3.2 30 | 31 | Content 3.2 32 | 33 | 34 | ## External 3.3 35 | 36 | ![External Image](https://s3.amazonaws.com/static.slid.es/logo/v2/slides-symbol-512x512.png) 37 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/Users/Credentials.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass #-} 2 | {-# LANGUAGE DeriveGeneric #-} 3 | {-# LANGUAGE StandaloneDeriving #-} 4 | module Common.Conduit.Api.Users.Credentials where 5 | 6 | import Data.Aeson (FromJSON, ToJSON) 7 | import Data.Text (Text) 8 | import GHC.Generics (Generic) 9 | 10 | data Credentials = Credentials 11 | { email :: Text 12 | , password :: Text 13 | } deriving (Generic) 14 | 15 | instance FromJSON Credentials 16 | instance ToJSON Credentials 17 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/Users/Registrant.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass #-} 2 | {-# LANGUAGE DeriveGeneric #-} 3 | {-# LANGUAGE StandaloneDeriving #-} 4 | module Common.Conduit.Api.Users.Registrant where 5 | 6 | import Data.Aeson (FromJSON, ToJSON) 7 | import Data.Text (Text) 8 | import GHC.Generics (Generic) 9 | 10 | data Registrant = Registrant 11 | { username :: Text 12 | , email :: Text 13 | , password :: Text 14 | } deriving (Generic) 15 | 16 | instance FromJSON Registrant 17 | instance ToJSON Registrant 18 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/Profiles.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds, DeriveAnyClass, DeriveGeneric, StandaloneDeriving, TypeOperators #-} 2 | module Common.Conduit.Api.Profiles 3 | ( ProfilesApi 4 | , Profile(Profile) 5 | ) where 6 | 7 | import Data.Text (Text) 8 | import Servant.API ((:>), Capture, Get, JSON) 9 | import Servant.Auth (Auth, JWT) 10 | 11 | import Common.Conduit.Api.Namespace (Namespace) 12 | import Common.Conduit.Api.Profiles.Profile 13 | 14 | type ProfilesApi token = Auth '[JWT] token :> Capture "username" Text :> Get '[JSON] (Namespace "profile" Profile) 15 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/Errors.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass, DeriveGeneric, OverloadedStrings, StandaloneDeriving #-} 2 | module Common.Conduit.Api.Errors 3 | ( ErrorBody(..) 4 | ) where 5 | 6 | import Data.Aeson (ToJSON, FromJSON) 7 | import Data.Text (Text) 8 | import GHC.Generics (Generic) 9 | 10 | data ErrorBody errors = ErrorBody 11 | { message :: Text 12 | , errors :: Maybe errors 13 | } deriving (Generic, Show) 14 | 15 | deriving instance ToJSON errors => ToJSON (ErrorBody errors) 16 | deriving instance FromJSON errors => FromJSON (ErrorBody errors) 17 | -------------------------------------------------------------------------------- /talk/code/frontend/src/Frontend/ExFoldDyn.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE ScopedTypeVariables #-} 3 | module Frontend.ExFoldDyn where 4 | 5 | import Reflex.Dom.Core 6 | import Data.Monoid (Endo(..),appEndo) 7 | import Control.Monad.Fix (MonadFix) 8 | 9 | exFoldDyn :: (DomBuilder t m, PostBuild t m, MonadHold t m, MonadFix m) => m () 10 | exFoldDyn = do 11 | buttonClickE :: Event t () <- button "Click me" 12 | let addE = Endo (+1) <$ buttonClickE 13 | clickCountDyn :: Dynamic t Int <- foldDyn appEndo 0 addE 14 | text " Clicked: " 15 | display clickCountDyn 16 | text " times!" 17 | -------------------------------------------------------------------------------- /talk/reveal.js/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reveal.js", 3 | "version": "3.7.0", 4 | "main": [ 5 | "js/reveal.js", 6 | "css/reveal.css" 7 | ], 8 | "homepage": "http://revealjs.com", 9 | "license": "MIT", 10 | "description": "The HTML Presentation Framework", 11 | "authors": [ 12 | "Hakim El Hattab " 13 | ], 14 | "dependencies": { 15 | "headjs": "~1.0.3" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/hakimel/reveal.js.git" 20 | }, 21 | "ignore": [ 22 | "**/.*", 23 | "node_modules", 24 | "bower_components", 25 | "test" 26 | ] 27 | } -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/Articles/CreateComment.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass #-} 2 | {-# LANGUAGE DeriveGeneric #-} 3 | {-# LANGUAGE StandaloneDeriving #-} 4 | module Common.Conduit.Api.Articles.CreateComment where 5 | 6 | import Data.Aeson (FromJSON (..), ToJSON (..)) 7 | import Data.Text (Text) 8 | import GHC.Generics (Generic) 9 | 10 | data CreateComment = CreateComment 11 | { body :: Text } 12 | 13 | deriving instance Generic CreateComment 14 | deriving instance ToJSON CreateComment 15 | deriving instance FromJSON CreateComment 16 | -------------------------------------------------------------------------------- /devenv/vbox/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | set -x 4 | cd $(dirname $0)/../../ 5 | if [ ! -e VERSION ]; then 6 | echo "Couldn't find version. Dying.!" 7 | exit 1; 8 | fi 9 | VERSION=$(cat VERSION) 10 | 11 | rm -f Dockerfile_* result .ghc.environment* 12 | echo -n "postgres://conduit:conduit@localhost/conduit" > config/backend/pgConnStr 13 | echo -n "postgres://conduit:conduit@localhost/conduit" > reflex-realworld-example/config/backend/pgConnStr 14 | 15 | VBoxManage unregistervm --delete "Reflex Realworld workshop (NixOS)" 2> /dev/null && true 16 | nix-build devenv/vbox 17 | VBoxManage import --vsys 0 --unit 5 --ignore result/reflex-realworld-workshop.ova 18 | -------------------------------------------------------------------------------- /devenv/vbox/setup.sql: -------------------------------------------------------------------------------- 1 | SET statement_timeout = 0; 2 | SET lock_timeout = 0; 3 | SET idle_in_transaction_session_timeout = 0; 4 | SET client_encoding = 'UTF8'; 5 | SET standard_conforming_strings = on; 6 | SELECT pg_catalog.set_config('search_path', '', false); 7 | SET check_function_bodies = false; 8 | SET client_min_messages = warning; 9 | SET row_security = off; 10 | 11 | -- 12 | -- Name: conduit; Type: DATABASE; Schema: -; Owner: conduit 13 | -- 14 | 15 | CREATE DATABASE conduit WITH TEMPLATE = template0 ENCODING = 'UTF8' LC_COLLATE = 'en_US.UTF-8' LC_CTYPE = 'en_US.UTF-8'; 16 | 17 | ALTER DATABASE conduit OWNER TO conduit; 18 | CREATE ROLE conduit WITH LOGIN ENCRYPTED PASSWORD 'conduit'; 19 | -------------------------------------------------------------------------------- /workshop/basic.gv: -------------------------------------------------------------------------------- 1 | digraph H { 2 | rankdir="LR"; 3 | 4 | foldDyn [ 5 | shape=plaintext 6 | label=< 7 | 8 | 9 | 10 | 11 |
foldDyn
a -> b -> bDynamic t b
Event t a
12 | >]; 13 | 14 | button [ 15 | shape=plaintext 16 | label=< 17 | 18 | 19 | 20 |
button :: DomBuilder m => m (Event t ())
Event t ()
21 | >]; 22 | 23 | 24 | button:output -> foldDyn:event; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /talk/code/backend/backend.cabal: -------------------------------------------------------------------------------- 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 29 | -------------------------------------------------------------------------------- /devenv/vbox/export.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | set -x 4 | 5 | cd $(dirname $0)/../../ 6 | if [ ! -e VERSION ]; then 7 | echo "Couldn't find version. Dying.!" 8 | exit 1; 9 | fi 10 | VERSION=$(cat VERSION) 11 | RELEASE=2 12 | 13 | OUT_DIR=~/.cache/reflex-realworld-workshop/ 14 | mkdir -p $OUT_DIR 15 | OVA_VERSION="$VERSION.$RELEASE" 16 | OVA_NAME=reflex-realworld-workshop.$OVA_VERSION.ova 17 | OVA=$OUT_DIR/$OVA_NAME 18 | 19 | rm -f $OVA 20 | VBoxManage export "Reflex Realworld workshop (NixOS)" -o $OVA 21 | aws s3 cp $OVA s3://reflex-realworld-workshop/$OVA_NAME 22 | echo $OVA_VERSION > $OUT_DIR/OVA_LATEST_VERSION 23 | aws s3 cp $OUT_DIR/OVA_LATEST_VERSION s3://reflex-realworld-workshop/OVA_LATEST_VERSION 24 | -------------------------------------------------------------------------------- /backend/src/Database/Beam/Postgres/Extended.hs: -------------------------------------------------------------------------------- 1 | module Database.Beam.Postgres.Extended 2 | ( PgQAgg 3 | , PgQExpr 4 | , module Database.Beam 5 | , module Database.Beam.Postgres 6 | , module Database.Beam.Postgres.Conduit 7 | , module Database.Beam.Postgres.Full 8 | , module Database.Beam.Postgres.Syntax 9 | , module Database.Beam.Query.Internal 10 | ) where 11 | 12 | import Database.Beam hiding (insert, runDelete, runInsert, runUpdate) 13 | import Database.Beam.Postgres 14 | import Database.Beam.Postgres.Conduit 15 | import Database.Beam.Postgres.Full 16 | import Database.Beam.Postgres.Syntax 17 | import Database.Beam.Query.Internal 18 | 19 | type PgQExpr = QExpr PgExpressionSyntax 20 | type PgQAgg = QAgg PgExpressionSyntax 21 | -------------------------------------------------------------------------------- /talk/code/frontend/src/Frontend/ExMergeEvents.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE ScopedTypeVariables #-} 3 | module Frontend.ExMergeEvents where 4 | 5 | import Reflex.Dom.Core 6 | import Data.Monoid (Endo(..),appEndo) 7 | import Control.Monad.Fix (MonadFix) 8 | 9 | exMergeEvents :: (DomBuilder t m, PostBuild t m, MonadHold t m, MonadFix m) => m () 10 | exMergeEvents = do 11 | addClickE <- button "Click me" 12 | resetClickE <- button "Reset me" 13 | 14 | let addE = Endo (+1) <$ addClickE 15 | let resetE = Endo (const 0) <$ resetClickE 16 | let updatesE = addE <> resetE 17 | 18 | clickCountDyn <- foldDyn appEndo 0 updatesE 19 | text " Clicked: " 20 | display clickCountDyn 21 | text " times!" 22 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/Articles/Comment.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass, DeriveGeneric, StandaloneDeriving #-} 2 | module Common.Conduit.Api.Articles.Comment where 3 | 4 | import Common.Conduit.Api.Profiles (Profile) 5 | import Data.Aeson (FromJSON (..), ToJSON (..)) 6 | import Data.Text (Text) 7 | import Data.Time (UTCTime) 8 | import GHC.Generics (Generic) 9 | 10 | data Comment = Comment 11 | { id :: Int 12 | , createdAt :: UTCTime 13 | , updatedAt :: UTCTime 14 | , body :: Text 15 | , author :: Profile 16 | } deriving (Eq) 17 | 18 | deriving instance Generic Comment 19 | deriving instance ToJSON Comment 20 | deriving instance FromJSON Comment 21 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/User/Update.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveGeneric #-} 2 | {-# LANGUAGE DeriveAnyClass #-} 3 | {-# LANGUAGE StandaloneDeriving #-} 4 | module Common.Conduit.Api.User.Update where 5 | 6 | import Data.Aeson (ToJSON) 7 | import Data.Aeson (FromJSON) 8 | import Data.Text (Text) 9 | import GHC.Generics (Generic) 10 | 11 | data UpdateUser = UpdateUser 12 | { password :: Maybe Text 13 | , email :: Maybe Text 14 | , username :: Maybe Text 15 | , bio :: Maybe Text 16 | , image :: Maybe Text -- Danger: This means that we can't unset this thing 17 | } deriving Generic 18 | 19 | deriving instance ToJSON UpdateUser 20 | deriving instance FromJSON UpdateUser 21 | -------------------------------------------------------------------------------- /devenv/docker/compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | obWorkshop: 4 | image: benkolera/reflex-realworld-workshop:${VERSION} 5 | command: ob run 6 | volumes: 7 | - ${PWD}:/workshop 8 | ports: 9 | - "8001:8001" 10 | obExample: 11 | image: benkolera/reflex-realworld-workshop:${VERSION} 12 | command: ob run 13 | volumes: 14 | - ${PWD}/reflex-realworld-example:/workshop 15 | ports: 16 | - "8000:8000" 17 | hoogle: 18 | image: benkolera/reflex-realworld-workshop:${VERSION} 19 | command: nix-shell -A shells.ghc --run 'hoogle server' 20 | volumes: 21 | - ${PWD}:/workshop 22 | ports: 23 | - "8080:8080" 24 | pg: 25 | image: benkolera/reflex-realworld-workshop-pg:${VERSION} 26 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/Profiles/Profile.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass #-} 2 | {-# LANGUAGE DeriveGeneric #-} 3 | {-# LANGUAGE DataKinds #-} 4 | {-# LANGUAGE StandaloneDeriving #-} 5 | {-# LANGUAGE TypeOperators #-} 6 | module Common.Conduit.Api.Profiles.Profile 7 | ( Profile(..) 8 | ) where 9 | 10 | import Data.Aeson (FromJSON, ToJSON) 11 | import Data.Text (Text) 12 | import GHC.Generics (Generic) 13 | 14 | 15 | data Profile = Profile 16 | { id :: Int 17 | , username :: Text 18 | , bio :: Text 19 | , image :: Maybe Text 20 | , following :: Bool 21 | } deriving (Generic, Eq, Show) 22 | 23 | deriving instance ToJSON Profile 24 | deriving instance FromJSON Profile 25 | -------------------------------------------------------------------------------- /backend/src/Backend/Conduit/Validation.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Backend.Conduit.Validation 3 | ( requiredText 4 | , ValidationErrors 5 | ) where 6 | 7 | import Data.Functor.Compose (Compose (Compose)) 8 | import qualified Data.Map as Map 9 | import Data.Text (Text) 10 | import qualified Data.Text as Text 11 | import Data.Validation (Validation (Failure, Success)) 12 | 13 | import Common.Conduit.Api.Validation (ValidationErrors) 14 | 15 | requiredText 16 | :: (Applicative m) 17 | => Text 18 | -> Text 19 | -> Compose m (Validation ValidationErrors) Text 20 | requiredText attr value = 21 | Compose $ 22 | pure $ 23 | if Text.null value 24 | then Failure (Map.singleton attr ["Required"]) 25 | else Success value 26 | -------------------------------------------------------------------------------- /talk/reveal.js/test/test-markdown-options.js: -------------------------------------------------------------------------------- 1 | Reveal.addEventListener( 'ready', function() { 2 | 3 | QUnit.module( 'Markdown' ); 4 | 5 | QUnit.test( 'Options are set', function( assert ) { 6 | assert.strictEqual( marked.defaults.smartypants, true ); 7 | }); 8 | 9 | QUnit.test( 'Smart quotes are activated', function( assert ) { 10 | var text = document.querySelector( '.reveal .slides>section>p' ).textContent; 11 | 12 | assert.strictEqual( /['"]/.test( text ), false ); 13 | assert.strictEqual( /[“”‘’]/.test( text ), true ); 14 | }); 15 | 16 | } ); 17 | 18 | Reveal.initialize({ 19 | dependencies: [ 20 | { src: '../plugin/markdown/marked.js' }, 21 | // Test loading JS files with query strings 22 | { src: '../plugin/markdown/markdown.js?query=string' }, 23 | ], 24 | markdown: { 25 | smartypants: true 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/Articles/Articles.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass #-} 2 | {-# LANGUAGE DeriveGeneric #-} 3 | {-# LANGUAGE StandaloneDeriving #-} 4 | module Common.Conduit.Api.Articles.Articles where 5 | 6 | import Data.Aeson (FromJSON (..), 7 | ToJSON (..)) 8 | import GHC.Generics (Generic) 9 | 10 | import Common.Conduit.Api.Articles.Article (Article) 11 | 12 | data Articles = Articles 13 | { articles :: [Article] 14 | , articlesCount :: Int 15 | } deriving Show 16 | 17 | fromList :: [Article] -> Articles 18 | fromList = Articles <$> id <*> length 19 | 20 | deriving instance Generic Articles 21 | deriving instance ToJSON Articles 22 | deriving instance FromJSON Articles 23 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/User/Account.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass #-} 2 | {-# LANGUAGE DeriveGeneric #-} 3 | {-# LANGUAGE StandaloneDeriving #-} 4 | module Common.Conduit.Api.User.Account where 5 | 6 | import Data.Aeson (FromJSON (..), ToJSON (..)) 7 | import Data.Text (Text) 8 | import GHC.Generics (Generic) 9 | 10 | newtype Token = Token { getToken :: Text } deriving (Show, Generic) 11 | 12 | data Account = Account 13 | { email :: Text 14 | , token :: Token 15 | , username :: Text 16 | , bio :: Text 17 | , image :: Maybe Text 18 | } deriving (Show, Generic) 19 | 20 | deriving instance ToJSON Account 21 | deriving instance FromJSON Account 22 | 23 | instance ToJSON Token where 24 | toJSON = toJSON . getToken 25 | 26 | instance FromJSON Token where 27 | parseJSON = fmap Token . parseJSON 28 | -------------------------------------------------------------------------------- /talk/reveal.js/test/test-markdown-external.js: -------------------------------------------------------------------------------- 1 | Reveal.addEventListener( 'ready', function() { 2 | 3 | QUnit.module( 'Markdown' ); 4 | 5 | QUnit.test( 'Vertical separator', function( assert ) { 6 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section>section' ).length, 2, 'found two slides' ); 7 | }); 8 | 9 | QUnit.test( 'Horizontal separator', function( assert ) { 10 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section' ).length, 2, 'found two slides' ); 11 | }); 12 | 13 | QUnit.test( 'Language highlighter', function( assert ) { 14 | assert.strictEqual( document.querySelectorAll( '.hljs-keyword' ).length, 1, 'got rendered highlight tag.' ); 15 | assert.strictEqual( document.querySelector( '.hljs-keyword' ).innerHTML, 'var', 'the same keyword: var.' ); 16 | }); 17 | 18 | } ); 19 | 20 | Reveal.initialize(); 21 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/User.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds, DeriveGeneric, TypeOperators #-} 2 | module Common.Conduit.Api.User 3 | ( UserApi 4 | , module Namespace 5 | , module Account 6 | , module UpdateUser 7 | ) where 8 | 9 | import Servant.API ((:<|>), (:>), Get, JSON, Put, ReqBody) 10 | import Servant.Auth (Auth, JWT) 11 | 12 | import Common.Conduit.Api.Namespace as Namespace (Namespace (Namespace)) 13 | import Common.Conduit.Api.User.Account as Account (Account (Account), Token (Token)) 14 | import Common.Conduit.Api.User.Update as UpdateUser (UpdateUser (UpdateUser)) 15 | 16 | type UserApi token = 17 | ( 18 | Auth '[JWT] token 19 | :> Get '[JSON] (Namespace "user" Account) 20 | ) :<|> ( 21 | Auth '[JWT] token 22 | :> ReqBody '[JSON] (Namespace "user" UpdateUser) 23 | :> Put '[JSON] (Namespace "user" Account) 24 | ) 25 | -------------------------------------------------------------------------------- /talk/reveal.js/test/examples/barebones.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | reveal.js - Barebones 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 | 18 |
19 |

Barebones Presentation

20 |

This example contains the bare minimum includes and markup required to run a reveal.js presentation.

21 |
22 | 23 |
24 |

No Theme

25 |

There's no theme included, so it will fall back on browser defaults.

26 |
27 | 28 |
29 | 30 |
31 | 32 | 33 | 34 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /talk/code/frontend/frontend.cabal: -------------------------------------------------------------------------------- 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-generated-static 14 | , text 15 | exposed-modules: 16 | Frontend 17 | ghc-options: -Wall 18 | 19 | executable frontend 20 | main-is: main.hs 21 | hs-source-dirs: src-bin 22 | build-depends: base 23 | , common 24 | , obelisk-frontend 25 | , obelisk-route 26 | , reflex-dom 27 | , obelisk-generated-static 28 | , frontend 29 | --TODO: Make these ghc-options optional 30 | ghc-options: -threaded 31 | if os(darwin) 32 | ghc-options: -dynamic 33 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/Articles/Article.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass, DeriveGeneric, StandaloneDeriving #-} 2 | module Common.Conduit.Api.Articles.Article where 3 | 4 | import Common.Conduit.Api.Profiles (Profile) 5 | import Data.Aeson (FromJSON (..), ToJSON (..)) 6 | import Data.Set (Set) 7 | import Data.Text (Text) 8 | import Data.Time (UTCTime) 9 | import GHC.Generics (Generic) 10 | 11 | data Article = Article 12 | { id :: Int 13 | , slug :: Text 14 | , title :: Text 15 | , description :: Text 16 | , body :: Text 17 | , tagList :: Set Text 18 | , createdAt :: UTCTime 19 | , updatedAt :: UTCTime 20 | , favorited :: Bool 21 | , favoritesCount :: Int 22 | , author :: Profile 23 | } deriving Show 24 | 25 | deriving instance Generic Article 26 | deriving instance ToJSON Article 27 | deriving instance FromJSON Article 28 | -------------------------------------------------------------------------------- /talk/code/frontend/src/Frontend/ExEventWriter.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE FlexibleContexts #-} 3 | {-# LANGUAGE ScopedTypeVariables #-} 4 | module Frontend.ExEventWriter where 5 | 6 | import Reflex.Dom.Core 7 | import Data.Monoid (Endo(..),appEndo) 8 | import Control.Monad.Fix (MonadFix) 9 | 10 | exEventWriter :: (DomBuilder t m, PostBuild t m, MonadHold t m, MonadFix m) => m () 11 | exEventWriter = do 12 | (_,updatesE) <- runEventWriterT $ do 13 | addButton 14 | resetButton 15 | 16 | clickCountDyn <- foldDyn appEndo 0 updatesE 17 | text " Clicked: " 18 | display clickCountDyn 19 | text " times!" 20 | 21 | addButton :: (DomBuilder t m, EventWriter t (Endo Int) m) => m () 22 | addButton = do 23 | addClickE <- button "Click me" 24 | tellEvent $ Endo (+1) <$ addClickE 25 | 26 | resetButton :: (DomBuilder t m, EventWriter t (Endo Int) m) => m () 27 | resetButton = do 28 | resetClickE <- button "Reset me" 29 | tellEvent $ Endo (const 0) <$ resetClickE 30 | -------------------------------------------------------------------------------- /talk/reveal.js/test/test-markdown-options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | reveal.js - Test Markdown Options 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds, DeriveGeneric, TypeOperators #-} 2 | module Common.Conduit.Api 3 | ( module Common.Conduit.Api 4 | , module Articles 5 | , module Namespace 6 | , module Profiles 7 | , module Tags 8 | , module User 9 | , module Users 10 | ) where 11 | 12 | import Data.Proxy (Proxy (..)) 13 | import Servant.API ((:<|>), (:>)) 14 | 15 | import Common.Conduit.Api.Articles as Articles 16 | import Common.Conduit.Api.Namespace as Namespace 17 | import Common.Conduit.Api.Profiles as Profiles 18 | import Common.Conduit.Api.User as User 19 | import Common.Conduit.Api.Users as Users 20 | import Common.Conduit.Api.Tags as Tags 21 | 22 | type Api token = "api" :> TopLevelApi token 23 | 24 | type TopLevelApi token 25 | = ("users" :> UsersApi token) 26 | :<|> ("user" :> UserApi token) 27 | :<|> ("articles" :> ArticlesApi token) 28 | :<|> ("profiles" :> ProfilesApi token) 29 | :<|> ("tags" :> TagsApi token) 30 | 31 | api :: Proxy (Api token) 32 | api = Proxy 33 | -------------------------------------------------------------------------------- /devenv/docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | if [ $(id -u) != 0 ]; then 4 | echo "You must run this docker container as root (i.e without the --user flag)" >&2; 5 | exit 1; 6 | fi; 7 | if [ ! -d /workshop ]; then 8 | echo "Please run this with the workshop code as a volume at /workshop. Eg. docker run -ti -p 8000:8000 -v $(pwd):/workshop -w /workshop benkolera/reflex-realworld-workshop:latest $@" >&2 9 | exit 1; 10 | fi; 11 | 12 | # Lets cheat a little, and just create users and groups for the uids/gids that 13 | # own the volume. 14 | WORK_UID=$(stat -c '%u' /workshop); 15 | WORK_GID=$(stat -c '%g' /workshop); 16 | 17 | if getent group $WORK_GID; then 18 | WORK_GROUP=$(getent group $WORK_GID | cut -f1 -d':'); 19 | else 20 | WORK_GROUP="workshop"; 21 | addgroup -g $WORK_GID $WORK_GROUP; 22 | fi 23 | 24 | if getent passwd $WORK_UID; then 25 | WORK_USER=$(getent passwd $WORK_UID | cut -f1 -d':'); 26 | else 27 | WORK_USER="workshop"; 28 | adduser -D -u $WORK_UID $WORK_USER $WORK_GROUP; 29 | fi 30 | 31 | su-exec $WORK_USER:$WORK_GROUP $@ 32 | -------------------------------------------------------------------------------- /talk/reveal.js/test/test-markdown-external.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | reveal.js - Test Markdown 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /backend/src/Backend/Conduit/Database/Users/Follow.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass, DeriveGeneric, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses #-} 2 | {-# LANGUAGE NamedFieldPuns, StandaloneDeriving, TypeFamilies, TypeSynonymInstances #-} 3 | module Backend.Conduit.Database.Users.Follow 4 | ( FollowT(..) 5 | , Follow 6 | ) where 7 | 8 | import GHC.Generics (Generic) 9 | 10 | import Backend.Conduit.Database.Users.User (UserT) 11 | 12 | import Database.Beam (Beamable, Identity, PrimaryKey, Table (..)) 13 | 14 | data FollowT f = Follow 15 | { follower :: PrimaryKey UserT f 16 | , followee :: PrimaryKey UserT f 17 | } 18 | 19 | deriving instance Generic (FollowT f) 20 | deriving instance Beamable FollowT 21 | 22 | type Follow = FollowT Identity 23 | 24 | instance Table FollowT where 25 | data PrimaryKey FollowT f 26 | = FollowId 27 | (PrimaryKey UserT f) 28 | (PrimaryKey UserT f) 29 | primaryKey = FollowId <$> follower <*> followee 30 | 31 | deriving instance Generic (PrimaryKey FollowT f) 32 | deriving instance Beamable (PrimaryKey FollowT) 33 | -------------------------------------------------------------------------------- /talk/reveal.js/css/theme/source/night.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Black theme for reveal.js. 3 | * 4 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(https://fonts.googleapis.com/css?family=Montserrat:700); 16 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400,700,400italic,700italic); 17 | 18 | 19 | // Override theme settings (see ../template/settings.scss) 20 | $backgroundColor: #111; 21 | 22 | $mainFont: 'Open Sans', sans-serif; 23 | $linkColor: #e7ad52; 24 | $linkColorHover: lighten( $linkColor, 20% ); 25 | $headingFont: 'Montserrat', Impact, sans-serif; 26 | $headingTextShadow: none; 27 | $headingLetterSpacing: -0.03em; 28 | $headingTextTransform: none; 29 | $selectionBackgroundColor: #e7ad52; 30 | 31 | 32 | // Theme template ------------------------------ 33 | @import "../template/theme"; 34 | // --------------------------------------------- -------------------------------------------------------------------------------- /talk/reveal.js/plugin/multiplex/master.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Don't emit events from inside of notes windows 4 | if ( window.location.search.match( /receiver/gi ) ) { return; } 5 | 6 | var multiplex = Reveal.getConfig().multiplex; 7 | 8 | var socket = io.connect( multiplex.url ); 9 | 10 | function post() { 11 | 12 | var messageData = { 13 | state: Reveal.getState(), 14 | secret: multiplex.secret, 15 | socketId: multiplex.id 16 | }; 17 | 18 | socket.emit( 'multiplex-statechanged', messageData ); 19 | 20 | }; 21 | 22 | // post once the page is loaded, so the client follows also on "open URL". 23 | window.addEventListener( 'load', post ); 24 | 25 | // Monitor events that trigger a change in state 26 | Reveal.addEventListener( 'slidechanged', post ); 27 | Reveal.addEventListener( 'fragmentshown', post ); 28 | Reveal.addEventListener( 'fragmenthidden', post ); 29 | Reveal.addEventListener( 'overviewhidden', post ); 30 | Reveal.addEventListener( 'overviewshown', post ); 31 | Reveal.addEventListener( 'paused', post ); 32 | Reveal.addEventListener( 'resumed', post ); 33 | 34 | }()); 35 | -------------------------------------------------------------------------------- /talk/code/frontend/src/Frontend/ExWorkflow.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE FlexibleContexts #-} 4 | {-# LANGUAGE ScopedTypeVariables #-} 5 | {-# LANGUAGE PatternSynonyms #-} 6 | module Frontend.ExWorkflow where 7 | 8 | import Reflex.Dom.Core 9 | import Data.Text (Text) 10 | import Data.Functor (void) 11 | import Control.Monad.Fix (MonadFix) 12 | 13 | exWorkflow :: ( DomBuilder t m, MonadHold t m , MonadFix m ) => m () 14 | exWorkflow = void $ workflow page1 15 | 16 | page1, page2, page3 :: (DomBuilder t m) => Workflow t m Text 17 | page1 = Workflow . el "div" $ do 18 | el "div" $ text "This is page 1" 19 | pg2 <- button "Switch to page 2" 20 | return ("Page 1", page2 <$ pg2) 21 | 22 | page2 = Workflow . el "div" $ do 23 | el "div" $ text "This is page 2" 24 | pg3 <- button "Switch to page 3" 25 | pg1 <- button "No wait, I want to go back to page 1" 26 | return ("Page 2", leftmost [page3 <$ pg3, page1 <$ pg1]) 27 | 28 | page3 = Workflow . el "div" $ do 29 | el "div" $ text "You have arrived on page 3" 30 | pg1 <- button "Start over" 31 | return ("Page 3", page1 <$ pg1) 32 | -------------------------------------------------------------------------------- /talk/reveal.js/test/examples/embedded-media.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | reveal.js - Embedded Media 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 | 21 |
22 |

Embedded Media Test

23 |
24 | 25 |
26 | 27 |
28 | 29 |
30 |

Empty Slide

31 |
32 | 33 |
34 | 35 |
36 | 37 | 38 | 39 | 40 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /backend/src/Backend/Conduit/Database/Tags/Tag.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass, DeriveGeneric, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses #-} 2 | {-# LANGUAGE NamedFieldPuns, StandaloneDeriving, TypeFamilies, TypeSynonymInstances #-} 3 | module Backend.Conduit.Database.Tags.Tag 4 | ( TagT(..) 5 | , Tag 6 | , TagId 7 | , PrimaryKey(..) 8 | ) where 9 | 10 | import Data.Text (Text) 11 | import Database.Beam (Beamable, Columnar, Identity, PrimaryKey, Table (..)) 12 | import GHC.Generics (Generic) 13 | 14 | newtype TagT f = Tag 15 | { name :: Columnar f Text 16 | } 17 | 18 | deriving instance Generic (TagT f) 19 | deriving instance Beamable TagT 20 | 21 | type Tag = TagT Identity 22 | 23 | deriving instance Show Tag 24 | deriving instance Eq Tag 25 | 26 | instance Table TagT where 27 | data PrimaryKey TagT f = TagId 28 | { unTagId :: Columnar f Text 29 | } 30 | primaryKey = TagId . name 31 | 32 | deriving instance Generic (PrimaryKey TagT f) 33 | deriving instance Beamable (PrimaryKey TagT) 34 | 35 | type TagId = PrimaryKey TagT Identity 36 | 37 | deriving instance Show TagId 38 | deriving instance Eq TagId 39 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/Users.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE DeriveGeneric #-} 3 | {-# LANGUAGE TypeOperators #-} 4 | module Common.Conduit.Api.Users 5 | ( UsersApi 6 | , module Namespace 7 | , module User 8 | , module Users 9 | ) where 10 | 11 | import Servant.API ((:<|>), (:>), 12 | JSON, Post, 13 | PostCreated, ReqBody) 14 | 15 | import Common.Conduit.Api.Namespace as Namespace (Namespace(Namespace)) 16 | import Common.Conduit.Api.User.Account as User (Account(Account)) 17 | import Common.Conduit.Api.Users.Credentials as Users (Credentials(Credentials)) 18 | import Common.Conduit.Api.Users.Registrant as Users (Registrant(Registrant)) 19 | 20 | type UsersApi token = 21 | ( "login" 22 | :> ReqBody '[JSON] (Namespace "user" Credentials) 23 | :> Post '[JSON] (Namespace "user" Account) 24 | ) :<|> ( 25 | ReqBody '[JSON] (Namespace "user" Registrant) 26 | :> PostCreated '[JSON] (Namespace "user" Account) 27 | ) 28 | -------------------------------------------------------------------------------- /talk/reveal.js/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | Please keep the [issue tracker](http://github.com/hakimel/reveal.js/issues) limited to **bug reports**, **feature requests** and **pull requests**. 4 | 5 | 6 | ### Personal Support 7 | If you have personal support or setup questions the best place to ask those are [StackOverflow](http://stackoverflow.com/questions/tagged/reveal.js). 8 | 9 | 10 | ### Bug Reports 11 | When reporting a bug make sure to include information about which browser and operating system you are on as well as the necessary steps to reproduce the issue. If possible please include a link to a sample presentation where the bug can be tested. 12 | 13 | 14 | ### Pull Requests 15 | - Should follow the coding style of the file you work in, most importantly: 16 | - Tabs to indent 17 | - Single-quoted strings 18 | - Should be made towards the **dev branch** 19 | - Should be submitted from a feature/topic branch (not your master) 20 | 21 | 22 | ### Plugins 23 | Please do not submit plugins as pull requests. They should be maintained in their own separate repository. More information here: https://github.com/hakimel/reveal.js/wiki/Plugin-Guidelines 24 | -------------------------------------------------------------------------------- /talk/reveal.js/css/theme/source/serif.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple theme for reveal.js presentations, similar 3 | * to the default theme. The accent color is brown. 4 | * 5 | * This theme is Copyright (C) 2012-2013 Owen Versteeg, http://owenversteeg.com - it is MIT licensed. 6 | */ 7 | 8 | 9 | // Default mixins and settings ----------------- 10 | @import "../template/mixins"; 11 | @import "../template/settings"; 12 | // --------------------------------------------- 13 | 14 | 15 | 16 | // Override theme settings (see ../template/settings.scss) 17 | $mainFont: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 18 | $mainColor: #000; 19 | $headingFont: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 20 | $headingColor: #383D3D; 21 | $headingTextShadow: none; 22 | $headingTextTransform: none; 23 | $backgroundColor: #F0F1EB; 24 | $linkColor: #51483D; 25 | $linkColorHover: lighten( $linkColor, 20% ); 26 | $selectionBackgroundColor: #26351C; 27 | 28 | .reveal a { 29 | line-height: 1.3em; 30 | } 31 | 32 | 33 | // Theme template ------------------------------ 34 | @import "../template/theme"; 35 | // --------------------------------------------- 36 | -------------------------------------------------------------------------------- /talk/reveal.js/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2018 Hakim El Hattab, http://hakim.se, and reveal.js contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /devenv/docker/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -x 5 | if [ ! -e VERSION ]; then 6 | echo "Run from the project root, dummy!" 7 | fi 8 | VERSION=$(cat VERSION) 9 | TAG_PREFIX="benkolera/reflex-realworld-workshop" 10 | PG_TAG_PREFIX="$TAG_PREFIX-pg" 11 | OB_TAG_PREFIX="$TAG_PREFIX" 12 | OUT_DIR=~/.cache/reflex-realworld-workshop/ 13 | OUT_PG_NAME=reflex-realworld-workshop-docker-db.$VERSION.tar 14 | OUT_OB_NAME=reflex-realworld-workshop-docker.$VERSION.tar 15 | 16 | ln -f devenv/docker/Dockerfile_obelisk 17 | ln -f devenv/docker/Dockerfile_db 18 | mkdir -p $OUT_DIR 19 | rm -fr .ghc.environment* result 20 | 21 | docker login 22 | docker build . -f Dockerfile_db -t $PG_TAG_PREFIX:$VERSION 23 | docker tag $PG_TAG_PREFIX:$VERSION $PG_TAG_PREFIX:latest 24 | docker build . -f Dockerfile_obelisk -t $OB_TAG_PREFIX:$VERSION 25 | docker tag $OB_TAG_PREFIX:$VERSION $OB_TAG_PREFIX:latest 26 | docker push $PG_TAG_PREFIX:$VERSION 27 | docker push $OB_TAG_PREFIX:$VERSION 28 | docker push $PG_TAG_PREFIX:latest 29 | docker push $OB_TAG_PREFIX:latest 30 | docker save $PG_TAG_PREFIX | gzip -c > $OUT_DIR/$OUT_PG_NAME.gz 31 | docker save $OB_TAG_PREFIX | gzip -c > $OUT_DIR/$OUT_OB_NAME.gz 32 | -------------------------------------------------------------------------------- /talk/reveal.js/css/theme/template/settings.scss: -------------------------------------------------------------------------------- 1 | // Base settings for all themes that can optionally be 2 | // overridden by the super-theme 3 | 4 | // Background of the presentation 5 | $backgroundColor: #2b2b2b; 6 | 7 | // Primary/body text 8 | $mainFont: 'Lato', sans-serif; 9 | $mainFontSize: 40px; 10 | $mainColor: #eee; 11 | 12 | // Vertical spacing between blocks of text 13 | $blockMargin: 20px; 14 | 15 | // Headings 16 | $headingMargin: 0 0 $blockMargin 0; 17 | $headingFont: 'League Gothic', Impact, sans-serif; 18 | $headingColor: #eee; 19 | $headingLineHeight: 1.2; 20 | $headingLetterSpacing: normal; 21 | $headingTextTransform: uppercase; 22 | $headingTextShadow: none; 23 | $headingFontWeight: normal; 24 | $heading1TextShadow: $headingTextShadow; 25 | 26 | $heading1Size: 3.77em; 27 | $heading2Size: 2.11em; 28 | $heading3Size: 1.55em; 29 | $heading4Size: 1.00em; 30 | 31 | // Links and actions 32 | $linkColor: #13DAEC; 33 | $linkColorHover: lighten( $linkColor, 20% ); 34 | 35 | // Text selection 36 | $selectionBackgroundColor: #FF5E99; 37 | $selectionColor: #fff; 38 | 39 | // Generates the presentation background, can be overridden 40 | // to return a background image or gradient 41 | @mixin bodyBackground() { 42 | background: $backgroundColor; 43 | } -------------------------------------------------------------------------------- /devenv/release_checklist.md: -------------------------------------------------------------------------------- 1 | # Checklist for new workshop version 2 | 3 | - Reflex Realworld Example 4 | - Check that reflex-realworld-example db and data.sql aren't out of date. 5 | - `pg_dump --data-only -h localhost -U conduit conduit > devenv/common/data.sql` 6 | - Check that things build in a nix-shell 7 | - `nix-shell --command 'cabal new-build'` 8 | - Commit and push example 9 | - Workshop 10 | - Bump submodule in workshop 11 | - Generate a new devenv/common/dbdump.sql 12 | - `pg_dump -h localhost -U conduit conduit > devenv/common/dbdump.sql` 13 | - Copy and code diffs across 14 | - Repush cachix with devenv/cachix\_push.sh 15 | - Rebuild and push docker images with devenv/docker/build.sh (don't run concurrently with vm build) 16 | - Rebuild OVA 17 | - `devenv/vbox/build.sh` 18 | - Import and load up VM. Manual Steps: 19 | - Open up spacemacs. 20 | - Add toolbar launchers for emacs and vscode. 21 | - Set chromium as default browser 22 | - Install local file links chromium plugin (this is ok for a dodgy test vm) 23 | - Setup bookmarks for hoogle/workshop/examples 24 | - Ob run in both workshop and example. Test both 25 | - Run the hoogle server and test 26 | - `devenv/vbox/export.sh` 27 | -------------------------------------------------------------------------------- /talk/reveal.js/css/theme/source/league.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * League theme for reveal.js. 3 | * 4 | * This was the default theme pre-3.0.0. 5 | * 6 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 7 | */ 8 | 9 | 10 | // Default mixins and settings ----------------- 11 | @import "../template/mixins"; 12 | @import "../template/settings"; 13 | // --------------------------------------------- 14 | 15 | 16 | 17 | // Include theme-specific fonts 18 | @import url(../../lib/font/league-gothic/league-gothic.css); 19 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 20 | 21 | // Override theme settings (see ../template/settings.scss) 22 | $headingTextShadow: 0px 0px 6px rgba(0,0,0,0.2); 23 | $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); 24 | 25 | // Background generator 26 | @mixin bodyBackground() { 27 | @include radial-gradient( rgba(28,30,32,1), rgba(85,90,95,1) ); 28 | } 29 | 30 | 31 | 32 | // Theme template ------------------------------ 33 | @import "../template/theme"; 34 | // --------------------------------------------- -------------------------------------------------------------------------------- /talk/reveal.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reveal.js", 3 | "version": "3.7.0", 4 | "description": "The HTML Presentation Framework", 5 | "homepage": "http://revealjs.com", 6 | "subdomain": "revealjs", 7 | "main": "js/reveal.js", 8 | "scripts": { 9 | "test": "grunt test", 10 | "start": "grunt serve", 11 | "build": "grunt" 12 | }, 13 | "author": { 14 | "name": "Hakim El Hattab", 15 | "email": "hakim.elhattab@gmail.com", 16 | "web": "http://hakim.se" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/hakimel/reveal.js.git" 21 | }, 22 | "engines": { 23 | "node": ">=4.0.0" 24 | }, 25 | "devDependencies": { 26 | "express": "^4.16.2", 27 | "grunt": "^1.0.1", 28 | "grunt-autoprefixer": "^3.0.4", 29 | "grunt-cli": "^1.2.0", 30 | "grunt-contrib-connect": "^1.0.2", 31 | "grunt-contrib-cssmin": "^2.2.1", 32 | "grunt-contrib-jshint": "^1.1.0", 33 | "grunt-contrib-qunit": "^2.0.0", 34 | "grunt-contrib-uglify": "^3.3.0", 35 | "grunt-contrib-watch": "^1.0.0", 36 | "grunt-sass": "^2.0.0", 37 | "grunt-retire": "^1.0.7", 38 | "grunt-zip": "~0.17.1", 39 | "mustache": "^2.3.0", 40 | "socket.io": "^1.7.3" 41 | }, 42 | "license": "MIT" 43 | } 44 | -------------------------------------------------------------------------------- /backend/src/Backend/Conduit/Database/Articles/ArticleTag.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass, DeriveGeneric, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses #-} 2 | {-# LANGUAGE NamedFieldPuns, StandaloneDeriving, TypeFamilies, TypeSynonymInstances #-} 3 | module Backend.Conduit.Database.Articles.ArticleTag 4 | ( ArticleTagT(..) 5 | , ArticleTag 6 | ) where 7 | 8 | import Database.Beam (Beamable, Identity, PrimaryKey, Table (..)) 9 | import GHC.Generics (Generic) 10 | 11 | import Backend.Conduit.Database.Articles.Article (ArticleT) 12 | import Backend.Conduit.Database.Tags.Tag (TagT) 13 | 14 | data ArticleTagT f = ArticleTag 15 | { article :: PrimaryKey ArticleT f 16 | , tag :: PrimaryKey TagT f 17 | } deriving (Generic) 18 | 19 | type ArticleTag = ArticleTagT Identity 20 | 21 | deriving instance Show ArticleTag 22 | 23 | deriving instance Eq ArticleTag 24 | 25 | instance Beamable ArticleTagT 26 | 27 | instance Beamable (PrimaryKey ArticleTagT) 28 | 29 | instance Table ArticleTagT where 30 | data PrimaryKey ArticleTagT f 31 | = ArticleTagId 32 | (PrimaryKey ArticleT f) 33 | (PrimaryKey TagT f) 34 | deriving Generic 35 | primaryKey = 36 | ArticleTagId 37 | <$> article 38 | <*> tag 39 | -------------------------------------------------------------------------------- /backend/src/SetCookieOrphan.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -fno-warn-orphans #-} 2 | module SetCookieOrphan where 3 | import qualified Data.ByteString.Builder as BS 4 | import qualified Data.ByteString.Lazy as LBS 5 | import Data.Text.Encoding (decodeUtf8With, encodeUtf8) 6 | import Data.Text.Encoding.Error (lenientDecode) 7 | import Web.Cookie (SetCookie, parseSetCookie, renderSetCookie) 8 | import Web.HttpApiData (FromHttpApiData (..), ToHttpApiData (..)) 9 | 10 | instance ToHttpApiData SetCookie where 11 | toUrlPiece = decodeUtf8With lenientDecode . toHeader 12 | toHeader = LBS.toStrict . BS.toLazyByteString . renderSetCookie 13 | 14 | -- | /Note:/ this instance works correctly for alphanumeric name and value 15 | -- 16 | -- >>> parseUrlPiece "SESSID=r2t5uvjq435r4q7ib3vtdjq120" :: Either Text SetCookie 17 | -- Right (SetCookie {setCookieName = "SESSID", setCookieValue = "r2t5uvjq435r4q7ib3vtdjq120", setCookiePath = Nothing, setCookieExpires = Nothing, setCookieMaxAge = Nothing, setCookieDomain = Nothing, setCookieHttpOnly = False, setCookieSecure = False, setCookieSameSite = Nothing}) 18 | instance FromHttpApiData SetCookie where 19 | parseUrlPiece = parseHeader . encodeUtf8 20 | parseHeader = Right . parseSetCookie 21 | -------------------------------------------------------------------------------- /talk/code/frontend/src/Frontend.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE TypeApplications #-} 4 | {-# LANGUAGE LambdaCase #-} 5 | module Frontend where 6 | 7 | import Obelisk.Frontend 8 | import Obelisk.Route 9 | import Obelisk.Route.Frontend 10 | import Reflex.Dom.Core 11 | 12 | import Common.Route 13 | 14 | import Frontend.ExDom (exDom) 15 | import Frontend.ExFoldDyn (exFoldDyn) 16 | import Frontend.ExMergeEvents (exMergeEvents) 17 | import Frontend.ExEventWriter (exEventWriter) 18 | import Frontend.ExReader (exReader) 19 | import Frontend.ExRoutes (exRoutes) 20 | import Frontend.ExWorkflow (exWorkflow) 21 | 22 | frontend :: Frontend (R FrontendRoute) 23 | frontend = Frontend 24 | { _frontend_head = do 25 | el "title" $ text "Obelisk Minimal Example" 26 | el "style" $ text "body { font-size: 1.5em; }" 27 | , _frontend_body = do 28 | subRoute_ $ \case 29 | FrontendRoute_Main -> blank 30 | FrontendRoute_ExDom -> exDom 31 | FrontendRoute_ExFoldDyn -> exFoldDyn 32 | FrontendRoute_ExMergeEvents -> exMergeEvents 33 | FrontendRoute_ExEventWriter -> exEventWriter 34 | FrontendRoute_ExReader -> exReader 35 | FrontendRoute_ExRoutes -> exRoutes 36 | FrontendRoute_ExWorkflow -> exWorkflow 37 | } 38 | -------------------------------------------------------------------------------- /devenv/docker/ob: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | set -x 4 | cd $(dirname $0)/../../ 5 | if [ ! -e VERSION ]; then 6 | echo "Couldn't find version. Dying.!" 7 | exit 1; 8 | fi 9 | export VERSION=$(cat VERSION) 10 | export PWD=$(pwd) 11 | echo -n "postgres://conduit:conduit@pg/conduit" > config/backend/pgConnStr 12 | echo -n "postgres://conduit:conduit@pg/conduit" > reflex-realworld-example/config/backend/pgConnStr 13 | 14 | COMPOSE="docker-compose -f devenv/docker/compose.yml" 15 | 16 | case $1 in 17 | repl) 18 | docker run -ti -v $PWD:/workshop benkolera/reflex-realworld-workshop:$VERSION ob repl; 19 | ;; 20 | hoogle) 21 | shift; 22 | # No idea why $@ didn't work here. Must be something about bash that I don't understand 23 | docker run -ti -v $PWD:/workshop benkolera/reflex-realworld-workshop:$VERSION nix-shell -A shells.ghc --run "hoogle $1 $2 $3 $4" 24 | ;; 25 | run-workshop) 26 | $COMPOSE up -d pg 27 | $COMPOSE up -d hoogle 28 | $COMPOSE up obWorkshop 29 | ;; 30 | run-example) 31 | $COMPOSE up -d pg 32 | $COMPOSE up -d hoogle 33 | $COMPOSE up obExample 34 | ;; 35 | stop) 36 | $COMPOSE down -v --remove-orphans 37 | ;; 38 | *) 39 | echo "Unknown Command. Try repl, hoogle search , run-workshop, run-example, stop" 40 | exit 1; 41 | esac 42 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/Articles/Attributes.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveGeneric #-} 2 | {-# LANGUAGE DeriveAnyClass #-} 3 | {-# LANGUAGE FlexibleInstances #-} 4 | {-# LANGUAGE StandaloneDeriving #-} 5 | {-# LANGUAGE TypeSynonymInstances #-} 6 | module Common.Conduit.Api.Articles.Attributes where 7 | 8 | import Data.Aeson (FromJSON, ToJSON) 9 | import Data.Functor.Identity (Identity) 10 | import Data.Set (Set) 11 | import Data.Text (Text) 12 | import GHC.Generics (Generic) 13 | import Common.Conduit.Api.Attribute (Attribute) 14 | 15 | data ArticleAttributes f = ArticleAttributes 16 | { title :: Attribute f Text 17 | , description :: Attribute f Text 18 | , body :: Attribute f Text 19 | , tagList :: Attribute f (Set Text) 20 | } 21 | 22 | type CreateArticle = ArticleAttributes Identity 23 | 24 | deriving instance Generic CreateArticle 25 | deriving instance ToJSON CreateArticle 26 | deriving instance FromJSON CreateArticle 27 | 28 | type UpdateArticle = ArticleAttributes Maybe 29 | 30 | deriving instance Generic UpdateArticle 31 | deriving instance ToJSON UpdateArticle 32 | deriving instance FromJSON UpdateArticle 33 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/Namespace.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE KindSignatures #-} 3 | {-# LANGUAGE ScopedTypeVariables #-} 4 | {-# LANGUAGE TypeSynonymInstances #-} 5 | module Common.Conduit.Api.Namespace 6 | ( Namespace(..) 7 | , unNamespace 8 | ) where 9 | 10 | import Data.Aeson (FromJSON (..), ToJSON (..), object, 11 | withObject, (.:), (.=)) 12 | import Data.Proxy (Proxy (Proxy)) 13 | import Data.Text (Text) 14 | import qualified Data.Text as Text 15 | import GHC.TypeLits (KnownSymbol, Symbol, symbolVal) 16 | 17 | newtype Namespace (ns :: Symbol) a = 18 | Namespace a 19 | deriving (Show) 20 | 21 | unNamespace :: Namespace ns a -> a 22 | unNamespace (Namespace a) = a 23 | 24 | symbolToText :: KnownSymbol a => Proxy a -> Text 25 | symbolToText = Text.pack . symbolVal 26 | 27 | instance (KnownSymbol ns, ToJSON a) => ToJSON (Namespace ns a) where 28 | toJSON (Namespace a) = object [symbolToText (Proxy :: Proxy ns) .= a] 29 | 30 | instance (KnownSymbol ns, FromJSON a) => FromJSON (Namespace ns a) where 31 | parseJSON = 32 | withObject "Namespace" $ \v -> 33 | Namespace <$> v .: symbolToText (Proxy :: Proxy ns) 34 | -------------------------------------------------------------------------------- /talk/reveal.js/lib/css/zenburn.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Zenburn style from voldmar.ru (c) Vladimir Epifanov 4 | based on dark.css by Ivan Sagalaev 5 | 6 | */ 7 | 8 | .hljs { 9 | display: block; 10 | overflow-x: auto; 11 | padding: 0.5em; 12 | background: #3f3f3f; 13 | color: #dcdcdc; 14 | } 15 | 16 | .hljs-keyword, 17 | .hljs-selector-tag, 18 | .hljs-tag { 19 | color: #e3ceab; 20 | } 21 | 22 | .hljs-template-tag { 23 | color: #dcdcdc; 24 | } 25 | 26 | .hljs-number { 27 | color: #8cd0d3; 28 | } 29 | 30 | .hljs-variable, 31 | .hljs-template-variable, 32 | .hljs-attribute { 33 | color: #efdcbc; 34 | } 35 | 36 | .hljs-literal { 37 | color: #efefaf; 38 | } 39 | 40 | .hljs-subst { 41 | color: #8f8f8f; 42 | } 43 | 44 | .hljs-title, 45 | .hljs-name, 46 | .hljs-selector-id, 47 | .hljs-selector-class, 48 | .hljs-section, 49 | .hljs-type { 50 | color: #efef8f; 51 | } 52 | 53 | .hljs-symbol, 54 | .hljs-bullet, 55 | .hljs-link { 56 | color: #dca3a3; 57 | } 58 | 59 | .hljs-deletion, 60 | .hljs-string, 61 | .hljs-built_in, 62 | .hljs-builtin-name { 63 | color: #cc9393; 64 | } 65 | 66 | .hljs-addition, 67 | .hljs-comment, 68 | .hljs-quote, 69 | .hljs-meta { 70 | color: #7f9f7f; 71 | } 72 | 73 | 74 | .hljs-emphasis { 75 | font-style: italic; 76 | } 77 | 78 | .hljs-strong { 79 | font-weight: bold; 80 | } 81 | -------------------------------------------------------------------------------- /talk/reveal.js/css/theme/source/sky.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Sky theme for reveal.js. 3 | * 4 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | 15 | // Include theme-specific fonts 16 | @import url(https://fonts.googleapis.com/css?family=Quicksand:400,700,400italic,700italic); 17 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700); 18 | 19 | 20 | // Override theme settings (see ../template/settings.scss) 21 | $mainFont: 'Open Sans', sans-serif; 22 | $mainColor: #333; 23 | $headingFont: 'Quicksand', sans-serif; 24 | $headingColor: #333; 25 | $headingLetterSpacing: -0.08em; 26 | $headingTextShadow: none; 27 | $backgroundColor: #f7fbfc; 28 | $linkColor: #3b759e; 29 | $linkColorHover: lighten( $linkColor, 20% ); 30 | $selectionBackgroundColor: #134674; 31 | 32 | // Fix links so they are not cut off 33 | .reveal a { 34 | line-height: 1.3em; 35 | } 36 | 37 | // Background generator 38 | @mixin bodyBackground() { 39 | @include radial-gradient( #add9e4, #f7fbfc ); 40 | } 41 | 42 | 43 | 44 | // Theme template ------------------------------ 45 | @import "../template/theme"; 46 | // --------------------------------------------- 47 | -------------------------------------------------------------------------------- /backend/src/Backend/Conduit/Claim.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveGeneric #-} 2 | module Backend.Conduit.Claim 3 | ( Claim(..) 4 | , fromUser 5 | , deriveToken 6 | ) where 7 | 8 | import Control.Monad.Except (ExceptT (ExceptT)) 9 | import Control.Monad.IO.Class (MonadIO, liftIO) 10 | import Crypto.JOSE (Error) 11 | import Data.Aeson (FromJSON, ToJSON) 12 | import Data.Text (Text) 13 | import Data.Text.Lazy (toStrict) 14 | import Data.Text.Lazy.Encoding (decodeUtf8) 15 | import Database.Beam (primaryKey) 16 | import GHC.Generics (Generic) 17 | import Servant.Auth.Server (FromJWT, JWTSettings, ToJWT) 18 | import qualified Servant.Auth.Server as ServantAuth 19 | 20 | import Backend.Conduit.Database.Users.User (PrimaryKey (unUserId), User) 21 | 22 | newtype Claim = 23 | Claim { id :: Int } 24 | deriving (Generic) 25 | 26 | instance ToJSON Claim 27 | instance FromJSON Claim 28 | instance ToJWT Claim 29 | instance FromJWT Claim 30 | 31 | fromUser :: User -> Claim 32 | fromUser = 33 | Claim . unUserId . primaryKey 34 | 35 | deriveToken :: MonadIO m => JWTSettings -> User -> ExceptT Error m Text 36 | deriveToken settings user = 37 | fmap (toStrict . decodeUtf8) . ExceptT . liftIO $ ServantAuth.makeJWT (fromUser user) settings Nothing 38 | -------------------------------------------------------------------------------- /talk/reveal.js/css/theme/source/beige.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Beige theme for reveal.js. 3 | * 4 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | 15 | // Include theme-specific fonts 16 | @import url(../../lib/font/league-gothic/league-gothic.css); 17 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 18 | 19 | 20 | // Override theme settings (see ../template/settings.scss) 21 | $mainColor: #333; 22 | $headingColor: #333; 23 | $headingTextShadow: none; 24 | $backgroundColor: #f7f3de; 25 | $linkColor: #8b743d; 26 | $linkColorHover: lighten( $linkColor, 20% ); 27 | $selectionBackgroundColor: rgba(79, 64, 28, 0.99); 28 | $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); 29 | 30 | // Background generator 31 | @mixin bodyBackground() { 32 | @include radial-gradient( rgba(247,242,211,1), rgba(255,255,255,1) ); 33 | } 34 | 35 | 36 | 37 | // Theme template ------------------------------ 38 | @import "../template/theme"; 39 | // --------------------------------------------- -------------------------------------------------------------------------------- /talk/reveal.js/css/theme/source/black.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Black theme for reveal.js. This is the opposite of the 'white' theme. 3 | * 4 | * By Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(../../lib/font/source-sans-pro/source-sans-pro.css); 16 | 17 | 18 | // Override theme settings (see ../template/settings.scss) 19 | $backgroundColor: #222; 20 | 21 | $mainColor: #fff; 22 | $headingColor: #fff; 23 | 24 | $mainFontSize: 42px; 25 | $mainFont: 'Source Sans Pro', Helvetica, sans-serif; 26 | $headingFont: 'Source Sans Pro', Helvetica, sans-serif; 27 | $headingTextShadow: none; 28 | $headingLetterSpacing: normal; 29 | $headingTextTransform: uppercase; 30 | $headingFontWeight: 600; 31 | $linkColor: #42affa; 32 | $linkColorHover: lighten( $linkColor, 15% ); 33 | $selectionBackgroundColor: lighten( $linkColor, 25% ); 34 | 35 | $heading1Size: 2.5em; 36 | $heading2Size: 1.6em; 37 | $heading3Size: 1.3em; 38 | $heading4Size: 1.0em; 39 | 40 | section.has-light-background { 41 | &, h1, h2, h3, h4, h5, h6 { 42 | color: #222; 43 | } 44 | } 45 | 46 | 47 | // Theme template ------------------------------ 48 | @import "../template/theme"; 49 | // --------------------------------------------- -------------------------------------------------------------------------------- /talk/reveal.js/css/theme/source/white.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * White theme for reveal.js. This is the opposite of the 'black' theme. 3 | * 4 | * By Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(../../lib/font/source-sans-pro/source-sans-pro.css); 16 | 17 | 18 | // Override theme settings (see ../template/settings.scss) 19 | $backgroundColor: #fff; 20 | 21 | $mainColor: #222; 22 | $headingColor: #222; 23 | 24 | $mainFontSize: 42px; 25 | $mainFont: 'Source Sans Pro', Helvetica, sans-serif; 26 | $headingFont: 'Source Sans Pro', Helvetica, sans-serif; 27 | $headingTextShadow: none; 28 | $headingLetterSpacing: normal; 29 | $headingTextTransform: uppercase; 30 | $headingFontWeight: 600; 31 | $linkColor: #2a76dd; 32 | $linkColorHover: lighten( $linkColor, 15% ); 33 | $selectionBackgroundColor: lighten( $linkColor, 25% ); 34 | 35 | $heading1Size: 2.5em; 36 | $heading2Size: 1.6em; 37 | $heading3Size: 1.3em; 38 | $heading4Size: 1.0em; 39 | 40 | section.has-dark-background { 41 | &, h1, h2, h3, h4, h5, h6 { 42 | color: #fff; 43 | } 44 | } 45 | 46 | 47 | // Theme template ------------------------------ 48 | @import "../template/theme"; 49 | // --------------------------------------------- -------------------------------------------------------------------------------- /backend/src/Backend/Conduit/Database/Articles/Favorite.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass, DeriveGeneric, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses #-} 2 | {-# LANGUAGE NamedFieldPuns, StandaloneDeriving, TypeFamilies, TypeSynonymInstances #-} 3 | module Backend.Conduit.Database.Articles.Favorite 4 | ( FavoriteT(..) 5 | , Favorite 6 | , PrimaryKey(FavoriteId) 7 | ) where 8 | 9 | import Database.Beam (Beamable, Identity, PrimaryKey, Table (..)) 10 | import GHC.Generics (Generic) 11 | 12 | import Backend.Conduit.Database.Articles.Article (ArticleT) 13 | import Backend.Conduit.Database.Users.User (UserT) 14 | 15 | data FavoriteT f = Favorite 16 | { article :: PrimaryKey ArticleT f 17 | , user :: PrimaryKey UserT f 18 | } 19 | 20 | deriving instance Generic (FavoriteT f) 21 | deriving instance Beamable FavoriteT 22 | 23 | type Favorite = FavoriteT Identity 24 | 25 | deriving instance Show Favorite 26 | 27 | instance Table FavoriteT where 28 | data PrimaryKey FavoriteT f 29 | = FavoriteId 30 | (PrimaryKey ArticleT f) 31 | (PrimaryKey UserT f) 32 | primaryKey = FavoriteId <$> article <*> user 33 | 34 | deriving instance Generic (PrimaryKey FavoriteT f) 35 | deriving instance Beamable (PrimaryKey FavoriteT) 36 | 37 | type FavoriteId = PrimaryKey FavoriteT Identity 38 | 39 | deriving instance Show FavoriteId 40 | deriving instance Eq FavoriteId 41 | -------------------------------------------------------------------------------- /common/common.cabal: -------------------------------------------------------------------------------- 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 | , aeson 11 | , categories 12 | , containers 13 | , insert-ordered-containers 14 | , lens 15 | , mtl 16 | , obelisk-executable-config 17 | , servant 18 | , servant-auth 19 | , servant-auth-client 20 | , text 21 | , time 22 | 23 | exposed-modules: 24 | Common.Route 25 | Common.Conduit.Api 26 | Common.Conduit.Api.Attribute 27 | Common.Conduit.Api.Namespace 28 | Common.Conduit.Api.Articles 29 | Common.Conduit.Api.Articles.Articles 30 | Common.Conduit.Api.Articles.Comment 31 | Common.Conduit.Api.Articles.Article 32 | Common.Conduit.Api.Articles.Attributes 33 | Common.Conduit.Api.Articles.CreateComment 34 | Common.Conduit.Api.Errors 35 | Common.Conduit.Api.Profiles 36 | Common.Conduit.Api.Profiles.Profile 37 | Common.Conduit.Api.Tags 38 | Common.Conduit.Api.User 39 | Common.Conduit.Api.User.Update 40 | Common.Conduit.Api.User.Account 41 | Common.Conduit.Api.Users 42 | Common.Conduit.Api.Users.Credentials 43 | Common.Conduit.Api.Users.Registrant 44 | Common.Conduit.Api.Validation 45 | -------------------------------------------------------------------------------- /talk/reveal.js/test/test-markdown.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | reveal.js - Test Markdown 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /talk/reveal.js/css/theme/source/simple.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple theme for reveal.js presentations, similar 3 | * to the default theme. The accent color is darkblue. 4 | * 5 | * This theme is Copyright (C) 2012 Owen Versteeg, https://github.com/StereotypicalApps. It is MIT licensed. 6 | * reveal.js is Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 7 | */ 8 | 9 | 10 | // Default mixins and settings ----------------- 11 | @import "../template/mixins"; 12 | @import "../template/settings"; 13 | // --------------------------------------------- 14 | 15 | 16 | 17 | // Include theme-specific fonts 18 | @import url(https://fonts.googleapis.com/css?family=News+Cycle:400,700); 19 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 20 | 21 | 22 | // Override theme settings (see ../template/settings.scss) 23 | $mainFont: 'Lato', sans-serif; 24 | $mainColor: #000; 25 | $headingFont: 'News Cycle', Impact, sans-serif; 26 | $headingColor: #000; 27 | $headingTextShadow: none; 28 | $headingTextTransform: none; 29 | $backgroundColor: #fff; 30 | $linkColor: #00008B; 31 | $linkColorHover: lighten( $linkColor, 20% ); 32 | $selectionBackgroundColor: rgba(0, 0, 0, 0.99); 33 | 34 | section.has-dark-background { 35 | &, h1, h2, h3, h4, h5, h6 { 36 | color: #fff; 37 | } 38 | } 39 | 40 | 41 | // Theme template ------------------------------ 42 | @import "../template/theme"; 43 | // --------------------------------------------- -------------------------------------------------------------------------------- /backend/src/Backend/Conduit/Database/Users/User.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass, DeriveGeneric, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses #-} 2 | {-# LANGUAGE NamedFieldPuns, StandaloneDeriving, TypeFamilies, TypeSynonymInstances #-} 3 | module Backend.Conduit.Database.Users.User 4 | ( UserT(..) 5 | , User 6 | , UserId 7 | , PrimaryKey(UserId, unUserId) 8 | ) where 9 | 10 | import Prelude hiding (id) 11 | 12 | import Data.Text (Text) 13 | import Database.Beam (Beamable, Columnar, Identity, PrimaryKey, Table (..)) 14 | import GHC.Generics (Generic) 15 | 16 | data UserT f = User 17 | { id :: Columnar f Int 18 | , password :: Columnar f Text 19 | , email :: Columnar f Text 20 | , username :: Columnar f Text 21 | , bio :: Columnar f Text 22 | , image :: Columnar f (Maybe Text) 23 | } 24 | 25 | deriving instance Generic (UserT f) 26 | deriving instance Beamable UserT 27 | 28 | type User = UserT Identity 29 | 30 | deriving instance Show User 31 | deriving instance Eq User 32 | deriving instance Ord User 33 | 34 | instance Table UserT where 35 | data PrimaryKey UserT f = UserId 36 | { unUserId :: Columnar f Int 37 | } 38 | primaryKey = UserId . id 39 | 40 | deriving instance Generic (PrimaryKey UserT f) 41 | deriving instance Beamable (PrimaryKey UserT) 42 | 43 | type UserId = PrimaryKey UserT Identity 44 | 45 | deriving instance Show UserId 46 | deriving instance Eq UserId 47 | deriving instance Ord UserId 48 | -------------------------------------------------------------------------------- /talk/reveal.js/css/theme/source/moon.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Solarized Dark theme for reveal.js. 3 | * Author: Achim Staebler 4 | */ 5 | 6 | 7 | // Default mixins and settings ----------------- 8 | @import "../template/mixins"; 9 | @import "../template/settings"; 10 | // --------------------------------------------- 11 | 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(../../lib/font/league-gothic/league-gothic.css); 16 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 17 | 18 | /** 19 | * Solarized colors by Ethan Schoonover 20 | */ 21 | html * { 22 | color-profile: sRGB; 23 | rendering-intent: auto; 24 | } 25 | 26 | // Solarized colors 27 | $base03: #002b36; 28 | $base02: #073642; 29 | $base01: #586e75; 30 | $base00: #657b83; 31 | $base0: #839496; 32 | $base1: #93a1a1; 33 | $base2: #eee8d5; 34 | $base3: #fdf6e3; 35 | $yellow: #b58900; 36 | $orange: #cb4b16; 37 | $red: #dc322f; 38 | $magenta: #d33682; 39 | $violet: #6c71c4; 40 | $blue: #268bd2; 41 | $cyan: #2aa198; 42 | $green: #859900; 43 | 44 | // Override theme settings (see ../template/settings.scss) 45 | $mainColor: $base1; 46 | $headingColor: $base2; 47 | $headingTextShadow: none; 48 | $backgroundColor: $base03; 49 | $linkColor: $blue; 50 | $linkColorHover: lighten( $linkColor, 20% ); 51 | $selectionBackgroundColor: $magenta; 52 | 53 | 54 | 55 | // Theme template ------------------------------ 56 | @import "../template/theme"; 57 | // --------------------------------------------- 58 | -------------------------------------------------------------------------------- /backend/src/Backend/Conduit/Database/Comments/Comment.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass, DeriveGeneric, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses #-} 2 | {-# LANGUAGE NamedFieldPuns, StandaloneDeriving, TypeFamilies, TypeSynonymInstances #-} 3 | module Backend.Conduit.Database.Comments.Comment 4 | ( CommentT(..) 5 | , Comment 6 | , CommentId 7 | , PrimaryKey(..) 8 | ) where 9 | 10 | import Prelude hiding (id) 11 | 12 | import Data.Text (Text) 13 | import Data.Time (UTCTime) 14 | import Database.Beam (Beamable, Columnar, Identity, PrimaryKey, Table (..)) 15 | import GHC.Generics (Generic) 16 | 17 | import Backend.Conduit.Database.Articles.Article (ArticleT) 18 | import Backend.Conduit.Database.Users.User (UserT) 19 | 20 | data CommentT f = Comment 21 | { id :: Columnar f Int 22 | , createdAt :: Columnar f UTCTime 23 | , updatedAt :: Columnar f UTCTime 24 | , body :: Columnar f Text 25 | , article :: PrimaryKey ArticleT f 26 | , author :: PrimaryKey UserT f 27 | } 28 | 29 | deriving instance Generic (CommentT f) 30 | deriving instance Beamable CommentT 31 | 32 | type Comment = CommentT Identity 33 | 34 | deriving instance Show Comment 35 | 36 | instance Table CommentT where 37 | data PrimaryKey CommentT f = CommentId 38 | { unCommentId :: Columnar f Int 39 | } 40 | primaryKey = CommentId . id 41 | 42 | deriving instance Generic (PrimaryKey CommentT f) 43 | deriving instance Beamable (PrimaryKey CommentT) 44 | 45 | type CommentId = PrimaryKey CommentT Identity 46 | -------------------------------------------------------------------------------- /talk/reveal.js/lib/js/classList.js: -------------------------------------------------------------------------------- 1 | /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/ 2 | if(typeof document!=="undefined"&&!("classList" in document.createElement("a"))){(function(j){var a="classList",f="prototype",m=(j.HTMLElement||j.Element)[f],b=Object,k=String[f].trim||function(){return this.replace(/^\s+|\s+$/g,"")},c=Array[f].indexOf||function(q){var p=0,o=this.length;for(;p m () 33 | register = noUserWidget $ elClass "div" "auth-page" $ do 34 | elClass "div" "container-page" $ 35 | elClass "div" "row" $ 36 | elClass "div" "col-md-6 offset-md-3 col-xs-12" $ do 37 | elClass "h1" "text-xs-center" $ text "Sign Up - TODO" 38 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { system ? builtins.currentSystem # TODO: Get rid of this system cruft 2 | , iosSdkVersion ? "10.2" 3 | }: 4 | with import ./.obelisk/impl { inherit system iosSdkVersion; }; 5 | project ./. ({ pkgs, ... }: { 6 | android.applicationId = "io.qfpl.obelisk.reflex.realworld.workshop"; 7 | android.displayName = "Reflex Realworld Workshop"; 8 | ios.bundleIdentifier = "io.qfpl.obelisk.reflex.realworld.workshop"; 9 | ios.bundleName = "Reflex Realworld Workshop"; 10 | withHoogle = true; 11 | overrides = with pkgs.haskell.lib; (self: super: { 12 | entropy = self.callHackage "entropy" "0.4.1.3" {}; 13 | scrypt = dontCheck super.scrypt; 14 | reflex-dom-storage = (import ./dep/reflex-dom-storage) self super; 15 | servant-reflex = (import ./dep/servant-reflex) self super; 16 | servant-snap = (import ./dep/servant-snap) self super; 17 | servant-auth-snap = (import ./dep/servant-auth {}) self super; 18 | mmark = overrideCabal 19 | ((import ./dep/mmark) self super) 20 | (drv: { 21 | doHaddock = false; 22 | doCheck = false; 23 | }); 24 | megaparsec = pkgs.haskell.lib.dontCheck ((import ./dep/megaparsec) self super); 25 | modern-uri = pkgs.haskell.lib.dontCheck ((import ./dep/modern-uri) self super); 26 | neat-interpolation = pkgs.haskell.lib.dontCheck ((import ./dep/neat-interpolation) self super); 27 | email-validate = pkgs.haskell.lib.dontCheck super.email-validate; 28 | validation = doJailbreak super.validation; # newer hedgehog in nixpkgs. 29 | servant = pkgs.haskell.lib.dontCheck super.servant; 30 | }); 31 | }) 32 | -------------------------------------------------------------------------------- /talk/reveal.js/lib/font/source-sans-pro/source-sans-pro.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Source Sans Pro'; 3 | src: url('source-sans-pro-regular.eot'); 4 | src: url('source-sans-pro-regular.eot?#iefix') format('embedded-opentype'), 5 | url('source-sans-pro-regular.woff') format('woff'), 6 | url('source-sans-pro-regular.ttf') format('truetype'); 7 | font-weight: normal; 8 | font-style: normal; 9 | } 10 | 11 | @font-face { 12 | font-family: 'Source Sans Pro'; 13 | src: url('source-sans-pro-italic.eot'); 14 | src: url('source-sans-pro-italic.eot?#iefix') format('embedded-opentype'), 15 | url('source-sans-pro-italic.woff') format('woff'), 16 | url('source-sans-pro-italic.ttf') format('truetype'); 17 | font-weight: normal; 18 | font-style: italic; 19 | } 20 | 21 | @font-face { 22 | font-family: 'Source Sans Pro'; 23 | src: url('source-sans-pro-semibold.eot'); 24 | src: url('source-sans-pro-semibold.eot?#iefix') format('embedded-opentype'), 25 | url('source-sans-pro-semibold.woff') format('woff'), 26 | url('source-sans-pro-semibold.ttf') format('truetype'); 27 | font-weight: 600; 28 | font-style: normal; 29 | } 30 | 31 | @font-face { 32 | font-family: 'Source Sans Pro'; 33 | src: url('source-sans-pro-semibolditalic.eot'); 34 | src: url('source-sans-pro-semibolditalic.eot?#iefix') format('embedded-opentype'), 35 | url('source-sans-pro-semibolditalic.woff') format('woff'), 36 | url('source-sans-pro-semibolditalic.ttf') format('truetype'); 37 | font-weight: 600; 38 | font-style: italic; 39 | } -------------------------------------------------------------------------------- /workshop/9001-bonus.md: -------------------------------------------------------------------------------- 1 | # Bonus Exercises 2 | 3 | If you finish early or are after different challenges. Try the following: 4 | 5 | ## Implement Form Disable while Submitting 6 | 7 | Mark all inputs and the submit button as disabled while the backend call is pending. 8 | 9 | ## Implement the user feed tab on the home page 10 | 11 | If logged in, the default homepage view should be the output of /api/articles/feed. This is tricky because you have to use higher order FRP to switch in a different backend call depending on the selection. 12 | 13 | ## Create Post Form 14 | 15 | See if you can create the create post form from scratch. 16 | 17 | For even more bonus marks, see if you can get a live preview from typing events in the markdown text area. You may need to throttle these events to get decent UX out of it. 18 | 19 | ## Client Side Validation 20 | 21 | Use Data.Validation and Data.Functor.Compose together to validate as you collect form input values. Connect failed validations with onBlur events to display / hide them when things fail / are resolved. 22 | 23 | ## Implement Pagination 24 | 25 | This hasn't been done in the example yet. But the article list calls take a limit and an offset and they return you a row count when they return. Can you run with this and implement pagination? 26 | 27 | ## Implement Favourite / UnFavourite of articles 28 | 29 | This is hard because these calls aren't implemented in the API yet! You'll have to figure out all the servant layers to make this happen. 30 | 31 | It's also a revision of the behaviour that we saw in the article comments: that we are updating our local state based on the backend call. 32 | 33 | Same goes for profile follow / unfollow. 34 | -------------------------------------------------------------------------------- /talk/reveal.js/css/theme/README.md: -------------------------------------------------------------------------------- 1 | ## Dependencies 2 | 3 | Themes are written using Sass to keep things modular and reduce the need for repeated selectors across files. Make sure that you have the reveal.js development environment including the Grunt dependencies installed before proceeding: https://github.com/hakimel/reveal.js#full-setup 4 | 5 | ## Creating a Theme 6 | 7 | To create your own theme, start by duplicating a ```.scss``` file in [/css/theme/source](https://github.com/hakimel/reveal.js/blob/master/css/theme/source). It will be automatically compiled by Grunt from Sass to CSS (see the [Gruntfile](https://github.com/hakimel/reveal.js/blob/master/Gruntfile.js)) when you run `npm run build -- css-themes`. 8 | 9 | Each theme file does four things in the following order: 10 | 11 | 1. **Include [/css/theme/template/mixins.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/mixins.scss)** 12 | Shared utility functions. 13 | 14 | 2. **Include [/css/theme/template/settings.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/settings.scss)** 15 | Declares a set of custom variables that the template file (step 4) expects. Can be overridden in step 3. 16 | 17 | 3. **Override** 18 | This is where you override the default theme. Either by specifying variables (see [settings.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/settings.scss) for reference) or by adding any selectors and styles you please. 19 | 20 | 4. **Include [/css/theme/template/theme.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/theme.scss)** 21 | The template theme file which will generate final CSS output based on the currently defined variables. 22 | -------------------------------------------------------------------------------- /talk/reveal.js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | reveal.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 23 | 24 | 25 |
26 |
27 |
Slide 1
28 |
Slide 2
29 |
30 |
31 | 32 | 33 | 34 | 35 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /talk/code/frontend/src/Frontend/ExRoutes.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE FlexibleContexts #-} 4 | {-# LANGUAGE ScopedTypeVariables #-} 5 | {-# LANGUAGE PatternSynonyms #-} 6 | module Frontend.ExRoutes where 7 | 8 | import Reflex.Dom.Core 9 | import Control.Monad.Fix (MonadFix) 10 | import Common.Route (ExampleSubRoute(..), FrontendRoute(..)) 11 | import Obelisk.Route.Frontend (R, RoutedT, SetRoute, subRoute_,askRoute, maybeRoute, routeLink, pattern (:/), RouteToUrl) 12 | 13 | exRoutes 14 | :: ( DomBuilder t m, PostBuild t m, MonadHold t m 15 | , MonadFix m, SetRoute t (R FrontendRoute) m 16 | , RouteToUrl (R FrontendRoute) m) 17 | => RoutedT t (Maybe (R ExampleSubRoute)) m () 18 | exRoutes = do 19 | currRouteDyn <- askRoute 20 | el "p" $ do 21 | text "Current Route: " 22 | display currRouteDyn 23 | 24 | _ <- maybeRoute homeWidget $ subRoute_ $ \case 25 | ExampleSubRoute_A -> aWidget 26 | ExampleSubRoute_B -> bWidget 27 | pure () 28 | 29 | homeWidget 30 | :: ( DomBuilder t m, SetRoute t (R FrontendRoute) m 31 | , RouteToUrl (R FrontendRoute) m) 32 | => m () 33 | homeWidget = do 34 | routeLink 35 | (FrontendRoute_ExRoutes :/ (Just $ ExampleSubRoute_A :/ ())) 36 | (text "Goto A") 37 | 38 | aWidget 39 | :: ( DomBuilder t m, SetRoute t (R FrontendRoute) m 40 | , RouteToUrl (R FrontendRoute) m) 41 | => m () 42 | aWidget = do 43 | routeLink (FrontendRoute_ExRoutes :/ Nothing) $ text "Go Home" 44 | text " " 45 | routeLink 46 | (FrontendRoute_ExRoutes :/ (Just $ ExampleSubRoute_B :/ ())) 47 | (text "Goto B") 48 | 49 | bWidget :: (DomBuilder t m) => m () 50 | bWidget = text "Game Over" 51 | -------------------------------------------------------------------------------- /talk/reveal.js/css/theme/source/solarized.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Solarized Light theme for reveal.js. 3 | * Author: Achim Staebler 4 | */ 5 | 6 | 7 | // Default mixins and settings ----------------- 8 | @import "../template/mixins"; 9 | @import "../template/settings"; 10 | // --------------------------------------------- 11 | 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(../../lib/font/league-gothic/league-gothic.css); 16 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 17 | 18 | 19 | /** 20 | * Solarized colors by Ethan Schoonover 21 | */ 22 | html * { 23 | color-profile: sRGB; 24 | rendering-intent: auto; 25 | } 26 | 27 | // Solarized colors 28 | $base03: #002b36; 29 | $base02: #073642; 30 | $base01: #586e75; 31 | $base00: #657b83; 32 | $base0: #839496; 33 | $base1: #93a1a1; 34 | $base2: #eee8d5; 35 | $base3: #fdf6e3; 36 | $yellow: #b58900; 37 | $orange: #cb4b16; 38 | $red: #dc322f; 39 | $magenta: #d33682; 40 | $violet: #6c71c4; 41 | $blue: #268bd2; 42 | $cyan: #2aa198; 43 | $green: #859900; 44 | 45 | // Override theme settings (see ../template/settings.scss) 46 | $mainColor: $base00; 47 | $headingColor: $base01; 48 | $headingTextShadow: none; 49 | $backgroundColor: $base3; 50 | $linkColor: $blue; 51 | $linkColorHover: lighten( $linkColor, 20% ); 52 | $selectionBackgroundColor: $magenta; 53 | 54 | // Background generator 55 | // @mixin bodyBackground() { 56 | // @include radial-gradient( rgba($base3,1), rgba(lighten($base3, 20%),1) ); 57 | // } 58 | 59 | 60 | 61 | // Theme template ------------------------------ 62 | @import "../template/theme"; 63 | // --------------------------------------------- 64 | -------------------------------------------------------------------------------- /frontend/src/Frontend/Editor.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts, LambdaCase, MultiParamTypeClasses, OverloadedStrings, PatternSynonyms #-} 2 | {-# LANGUAGE ScopedTypeVariables #-} 3 | {-# OPTIONS_GHC -fno-warn-unused-imports -fno-warn-redundant-constraints #-} 4 | module Frontend.Editor where 5 | 6 | import Control.Lens 7 | import Reflex.Dom.Core 8 | 9 | import qualified Data.Map as Map 10 | import qualified Data.Set as Set 11 | import Obelisk.Route.Frontend (pattern (:/), R, SetRoute, setRoute) 12 | 13 | import qualified Common.Conduit.Api.Articles.Article as Article 14 | import Common.Conduit.Api.Articles.Attributes (ArticleAttributes (..), CreateArticle) 15 | import Common.Conduit.Api.Namespace (Namespace (Namespace), unNamespace) 16 | import qualified Common.Conduit.Api.User.Account as Account 17 | import Common.Route (DocumentSlug (..), FrontendRoute (..)) 18 | import qualified Frontend.Conduit.Client as Client 19 | import Frontend.FrontendStateT 20 | import Frontend.Utils (buttonClass) 21 | 22 | editor 23 | :: forall t m js s 24 | . ( DomBuilder t m 25 | , PostBuild t m 26 | , Prerender js t m 27 | , SetRoute t (R FrontendRoute) m 28 | , HasFrontendState t s m 29 | , HasLoggedInAccount s 30 | ) 31 | => m () 32 | editor = elClass "div" "editor-page" $ do 33 | elClass "div" "container" $ 34 | elClass "div" "row" $ 35 | elClass "div" "col-xs-12 col-md-10 offset-md-1" $ do 36 | elClass "h1" "text-xs-center" $ text "TODO" 37 | -------------------------------------------------------------------------------- /backend/src/Backend/Conduit/Database/Articles/Article.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass, DeriveGeneric, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses #-} 2 | {-# LANGUAGE NamedFieldPuns, StandaloneDeriving, TypeFamilies, TypeSynonymInstances #-} 3 | module Backend.Conduit.Database.Articles.Article 4 | ( ArticleT(..) 5 | , Article 6 | , ArticleId 7 | , PrimaryKey(..) 8 | ) where 9 | 10 | import Prelude hiding (id) 11 | 12 | import Data.Text (Text) 13 | import Data.Time (UTCTime) 14 | import Database.Beam (Beamable, Columnar, Identity, PrimaryKey, Table (..)) 15 | import GHC.Generics (Generic) 16 | 17 | import Backend.Conduit.Database.Users.User (UserT) 18 | 19 | data ArticleT f = Article 20 | { id :: Columnar f Int 21 | , slug :: Columnar f Text 22 | , title :: Columnar f Text 23 | , description :: Columnar f Text 24 | , body :: Columnar f Text 25 | , createdAt :: Columnar f UTCTime 26 | , updatedAt :: Columnar f UTCTime 27 | , author :: PrimaryKey UserT f 28 | } 29 | 30 | deriving instance Generic (ArticleT f) 31 | deriving instance Beamable ArticleT 32 | 33 | type Article = ArticleT Identity 34 | 35 | deriving instance Show Article 36 | deriving instance Eq Article 37 | deriving instance Ord Article 38 | 39 | instance Table ArticleT where 40 | data PrimaryKey ArticleT f = ArticleId 41 | { unArticleId :: Columnar f Int 42 | } 43 | primaryKey = ArticleId . id 44 | 45 | deriving instance Generic (PrimaryKey ArticleT f) 46 | deriving instance Beamable (PrimaryKey ArticleT) 47 | 48 | type ArticleId = PrimaryKey ArticleT Identity 49 | 50 | deriving instance Show ArticleId 51 | deriving instance Eq ArticleId 52 | deriving instance Ord ArticleId 53 | -------------------------------------------------------------------------------- /talk/reveal.js/css/theme/template/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin vertical-gradient( $top, $bottom ) { 2 | background: $top; 3 | background: -moz-linear-gradient( top, $top 0%, $bottom 100% ); 4 | background: -webkit-gradient( linear, left top, left bottom, color-stop(0%,$top), color-stop(100%,$bottom) ); 5 | background: -webkit-linear-gradient( top, $top 0%, $bottom 100% ); 6 | background: -o-linear-gradient( top, $top 0%, $bottom 100% ); 7 | background: -ms-linear-gradient( top, $top 0%, $bottom 100% ); 8 | background: linear-gradient( top, $top 0%, $bottom 100% ); 9 | } 10 | 11 | @mixin horizontal-gradient( $top, $bottom ) { 12 | background: $top; 13 | background: -moz-linear-gradient( left, $top 0%, $bottom 100% ); 14 | background: -webkit-gradient( linear, left top, right top, color-stop(0%,$top), color-stop(100%,$bottom) ); 15 | background: -webkit-linear-gradient( left, $top 0%, $bottom 100% ); 16 | background: -o-linear-gradient( left, $top 0%, $bottom 100% ); 17 | background: -ms-linear-gradient( left, $top 0%, $bottom 100% ); 18 | background: linear-gradient( left, $top 0%, $bottom 100% ); 19 | } 20 | 21 | @mixin radial-gradient( $outer, $inner, $type: circle ) { 22 | background: $outer; 23 | background: -moz-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 24 | background: -webkit-gradient( radial, center center, 0px, center center, 100%, color-stop(0%,$inner), color-stop(100%,$outer) ); 25 | background: -webkit-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 26 | background: -o-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 27 | background: -ms-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 28 | background: radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 29 | } -------------------------------------------------------------------------------- /backend/src/Backend/Conduit/Database/Tags.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts, OverloadedStrings #-} 2 | module Backend.Conduit.Database.Tags 3 | ( create 4 | , query 5 | , TagT(..) 6 | , Tag 7 | , TagId 8 | ) where 9 | 10 | import Control.Monad.IO.Class (MonadIO) 11 | import Control.Monad.Reader.Class (MonadReader, ask) 12 | import Control.Monad.Trans.Control (MonadBaseControl) 13 | import Data.Foldable (toList) 14 | import Data.Set (Set) 15 | import Data.Text (Text) 16 | import Database.Beam.Postgres.Extended (all_, conflictingFields, insertExpressions, insertReturning, 17 | onConflict, onConflictUpdateInstead, runInsertReturning, runSelect, 18 | select, val_) 19 | import Database.PostgreSQL.Simple (Connection) 20 | 21 | import Backend.Conduit.Database (ConduitDb (..), conduitDb, rowList) 22 | import Backend.Conduit.Database.Tags.Tag (Tag, TagId, TagT (Tag)) 23 | import qualified Backend.Conduit.Database.Tags.Tag as Tag 24 | 25 | query 26 | :: (MonadReader Connection m, MonadIO m, MonadBaseControl IO m) 27 | => m [Tag] 28 | query = do 29 | conn <- ask 30 | runSelect conn (select (all_ (conduitTags conduitDb))) rowList 31 | 32 | create 33 | :: (MonadReader Connection m, MonadIO m, MonadBaseControl IO m) 34 | => Set Text 35 | -> m [Tag] 36 | create names = do 37 | conn <- ask 38 | runInsertReturning 39 | conn 40 | (insertReturning 41 | (conduitTags conduitDb) 42 | (insertExpressions (map (Tag . val_) (toList names))) 43 | (onConflict (conflictingFields Tag.name) (onConflictUpdateInstead Tag.name)) 44 | (Just id)) 45 | rowList 46 | -------------------------------------------------------------------------------- /talk/code/frontend/src/Frontend/ExReader.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE FlexibleContexts #-} 3 | {-# LANGUAGE ScopedTypeVariables #-} 4 | {-# LANGUAGE RecursiveDo #-} 5 | module Frontend.ExReader where 6 | 7 | import Reflex.Dom.Core 8 | import Data.Monoid (Endo(..),appEndo) 9 | import qualified Data.Map as Map 10 | import Data.Text (Text) 11 | import Data.Bool (bool) 12 | import Control.Monad.Trans.Reader (runReaderT) 13 | import Control.Monad.Reader.Class (MonadReader, ask) 14 | import Control.Monad.Fix (MonadFix) 15 | 16 | exReader :: (DomBuilder t m, PostBuild t m, MonadHold t m, MonadFix m) => m () 17 | exReader = mdo 18 | (_,updatesE) <- flip runReaderT clickCountDyn 19 | . runEventWriterT 20 | $ do 21 | addButton 22 | resetButton 23 | 24 | clickCountDyn <- foldDyn appEndo 0 updatesE 25 | text " Clicked: " 26 | display clickCountDyn 27 | text " times!" 28 | 29 | addButton :: (DomBuilder t m, EventWriter t (Endo Int) m) => m () 30 | addButton = do 31 | addClickE <- button "Click me" 32 | tellEvent $ Endo (+1) <$ addClickE 33 | 34 | resetButton 35 | :: ( DomBuilder t m, EventWriter t (Endo Int) m 36 | , MonadReader (Dynamic t Int) m , PostBuild t m) 37 | => m () 38 | resetButton = do 39 | clickCountDyn <- ask 40 | let disabledDyn = (== 0) <$> clickCountDyn 41 | resetClickE <- buttonDynDisabled disabledDyn "Reset me" 42 | tellEvent $ Endo (const 0) <$ resetClickE 43 | 44 | buttonDynDisabled 45 | :: (DomBuilder t m, PostBuild t m) 46 | => Dynamic t Bool 47 | -> Text 48 | -> m (Event t ()) 49 | buttonDynDisabled disabledDyn dt = do 50 | (buttElt,_) <- elDynAttr' "button" 51 | (bool Map.empty ("disabled" =: "") <$> disabledDyn) 52 | (text dt) 53 | pure $ domEvent Click buttElt 54 | 55 | -------------------------------------------------------------------------------- /frontend/src/Frontend/Profile.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts, GADTs, LambdaCase, MultiParamTypeClasses, OverloadedStrings #-} 2 | {-# LANGUAGE PatternSynonyms #-} 3 | {-# OPTIONS_GHC -fno-warn-unused-imports -fno-warn-redundant-constraints #-} 4 | module Frontend.Profile where 5 | 6 | import Reflex.Dom.Core 7 | 8 | import Control.Monad.Fix (MonadFix) 9 | import Data.Bool (bool) 10 | import Data.Functor (void) 11 | import Obelisk.Route.Frontend (pattern (:/), R, RouteToUrl, RoutedT, SetRoute, askRoute) 12 | import Servant.Common.Req (QParam (QNone)) 13 | 14 | import Common.Conduit.Api.Articles.Articles (Articles (..)) 15 | import Common.Conduit.Api.Namespace (unNamespace) 16 | import qualified Common.Conduit.Api.Profiles.Profile as Profile 17 | import Common.Route (FrontendRoute (..), ProfileRoute (..), Username (..)) 18 | import Frontend.ArticlePreview (articlesPreview, profileImage) 19 | import qualified Frontend.Conduit.Client as Client 20 | import Frontend.FrontendStateT 21 | import Frontend.Utils (buttonClass, routeLinkDynClass) 22 | 23 | profile 24 | :: ( DomBuilder t m 25 | , PostBuild t m 26 | , SetRoute t (R FrontendRoute) m 27 | , RouteToUrl (R FrontendRoute) m 28 | , HasLoggedInAccount s 29 | , HasFrontendState t s m 30 | , Prerender js t m 31 | , MonadHold t m 32 | , MonadFix m 33 | ) 34 | => Dynamic t Username 35 | -> RoutedT t (Maybe (R ProfileRoute)) m () 36 | profile _usernameDyn = do 37 | elClass "div" "profile-page" $ do 38 | elClass "div" "user-info" $ 39 | elClass "div" "container" $ 40 | el "h1" $ text "TODO" 41 | -------------------------------------------------------------------------------- /workshop/01-foldDyn/counter.gv: -------------------------------------------------------------------------------- 1 | digraph H { 2 | rankdir="LR"; 3 | 4 | foldDyn [ 5 | shape=plaintext 6 | label=< 7 | 8 | 9 | 10 | 11 | 12 |
foldDyn
() -> Int -> IntDynamic t Int
Int
Event t ()
> 13 | ]; 14 | 15 | count [ 16 | shape=plaintext; 17 | label=< 18 | 19 | 20 | 21 |
reducer :: () -> Int -> Int
reducer _ old = old + 1
22 | > 23 | ]; 24 | 25 | button [ 26 | shape=plaintext 27 | label=< 28 | 29 | 30 | 31 |
button :: DomBuilder t m => m (Event t ())
Event t ()
32 | >]; 33 | 34 | display [ 35 | shape=plaintext 36 | label=< 37 | 38 | 42 | 43 |
display
39 | :: (DomBuilder t m, PostBuild t m, Show a)
40 | => Dynamic t a
41 | -> m ()
Dynamic t am ()
44 | > 45 | ]; 46 | 47 | button [ 48 | shape=plaintext 49 | label=< 50 | 51 | 52 | 53 |
button :: DomBuilder t m => Text -> m (Event t ())
m (Event t ())
54 | >]; 55 | 56 | button:output -> foldDyn:event; 57 | 0 -> foldDyn:init; 58 | count -> foldDyn:reducer; 59 | foldDyn:output -> display:input; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /talk/reveal.js/test/test-pdf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | reveal.js - Test PDF exports 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /talk/reveal.js/plugin/math/math.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A plugin which enables rendering of math equations inside 3 | * of reveal.js slides. Essentially a thin wrapper for MathJax. 4 | * 5 | * @author Hakim El Hattab 6 | */ 7 | var RevealMath = window.RevealMath || (function(){ 8 | 9 | var options = Reveal.getConfig().math || {}; 10 | options.mathjax = options.mathjax || 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js'; 11 | options.config = options.config || 'TeX-AMS_HTML-full'; 12 | options.tex2jax = options.tex2jax || { 13 | inlineMath: [['$','$'],['\\(','\\)']] , 14 | skipTags: ['script','noscript','style','textarea','pre'] }; 15 | 16 | loadScript( options.mathjax + '?config=' + options.config, function() { 17 | 18 | MathJax.Hub.Config({ 19 | messageStyle: 'none', 20 | tex2jax: options.tex2jax, 21 | skipStartupTypeset: true 22 | }); 23 | 24 | // Typeset followed by an immediate reveal.js layout since 25 | // the typesetting process could affect slide height 26 | MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub ] ); 27 | MathJax.Hub.Queue( Reveal.layout ); 28 | 29 | // Reprocess equations in slides when they turn visible 30 | Reveal.addEventListener( 'slidechanged', function( event ) { 31 | 32 | MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, event.currentSlide ] ); 33 | 34 | } ); 35 | 36 | } ); 37 | 38 | function loadScript( url, callback ) { 39 | 40 | var head = document.querySelector( 'head' ); 41 | var script = document.createElement( 'script' ); 42 | script.type = 'text/javascript'; 43 | script.src = url; 44 | 45 | // Wrapper for callback to make sure it only fires once 46 | var finish = function() { 47 | if( typeof callback === 'function' ) { 48 | callback.call(); 49 | callback = null; 50 | } 51 | } 52 | 53 | script.onload = finish; 54 | 55 | // IE 56 | script.onreadystatechange = function() { 57 | if ( this.readyState === 'loaded' ) { 58 | finish(); 59 | } 60 | } 61 | 62 | // Normal browsers 63 | head.appendChild( script ); 64 | 65 | } 66 | 67 | })(); 68 | -------------------------------------------------------------------------------- /talk/reveal.js/plugin/multiplex/index.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var express = require('express'); 3 | var fs = require('fs'); 4 | var io = require('socket.io'); 5 | var crypto = require('crypto'); 6 | 7 | var app = express(); 8 | var staticDir = express.static; 9 | var server = http.createServer(app); 10 | 11 | io = io(server); 12 | 13 | var opts = { 14 | port: process.env.PORT || 1948, 15 | baseDir : __dirname + '/../../' 16 | }; 17 | 18 | io.on( 'connection', function( socket ) { 19 | socket.on('multiplex-statechanged', function(data) { 20 | if (typeof data.secret == 'undefined' || data.secret == null || data.secret === '') return; 21 | if (createHash(data.secret) === data.socketId) { 22 | data.secret = null; 23 | socket.broadcast.emit(data.socketId, data); 24 | }; 25 | }); 26 | }); 27 | 28 | [ 'css', 'js', 'plugin', 'lib' ].forEach(function(dir) { 29 | app.use('/' + dir, staticDir(opts.baseDir + dir)); 30 | }); 31 | 32 | app.get("/", function(req, res) { 33 | res.writeHead(200, {'Content-Type': 'text/html'}); 34 | 35 | var stream = fs.createReadStream(opts.baseDir + '/index.html'); 36 | stream.on('error', function( error ) { 37 | res.write('

reveal.js multiplex server.

Generate token'); 38 | res.end(); 39 | }); 40 | stream.on('readable', function() { 41 | stream.pipe(res); 42 | }); 43 | }); 44 | 45 | app.get("/token", function(req,res) { 46 | var ts = new Date().getTime(); 47 | var rand = Math.floor(Math.random()*9999999); 48 | var secret = ts.toString() + rand.toString(); 49 | res.send({secret: secret, socketId: createHash(secret)}); 50 | }); 51 | 52 | var createHash = function(secret) { 53 | var cipher = crypto.createCipher('blowfish', secret); 54 | return(cipher.final('hex')); 55 | }; 56 | 57 | // Actually listen 58 | server.listen( opts.port || null ); 59 | 60 | var brown = '\033[33m', 61 | green = '\033[32m', 62 | reset = '\033[0m'; 63 | 64 | console.log( brown + "reveal.js:" + reset + " Multiplex running on port " + green + opts.port + reset ); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reflex Realworld Workshop 2 | 3 | ![CSIRO's Data61 Logo](https://raw.githubusercontent.com/qfpl/assets/master/data61-transparent-bg.png) 4 | 5 |
6 | 7 | [![Gitter](https://badges.gitter.im/reflex-realworld-workshop/community.svg)](https://gitter.im/reflex-realworld-workshop/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 8 | 9 | This workshop is a step by step walkthrough of how to implement the [Real World Demo App](https://github.com/gothinkster/realworld/tree/master/api) spec in Obelisk/Reflex. The full implementation is available in the submodule sitting in reflex-realworld-example if you want to see the finished product or peek at it for answers. :) 10 | 11 | This is being presented at YOW Lambda Jam 2019, but I hope that this continues as a living resource and example after the conference, so if it becomes out of date with the latest Obelisk, please file an issue or PR! 12 | 13 | ## Keeping in Touch / Getting assistance 14 | 15 | When I release new changes to the workshop, I will release them as github releases. If you are doing the workshop at Lambda Jam, I highly recommend watching this repo for releases so I can let you know to pull new code if necessary. 16 | 17 | If you have any issues or questions with the material, please feel free to reach out via a github issue, the gitter room or #QFPL on freenode. 18 | 19 | ## Setup Instructions 20 | 21 | See [SETUP.md](./SETUP.md). 22 | 23 | ## Workshop 24 | 25 | See [workshop/00-introduction](workshop/00-introduction.md) 26 | 27 | ## Acknowledgements & Thanks 28 | 29 | - Brad Parker, who did all of the backend work in his [servant-beam-realworld-example-app](https://github.com/bradparker/servant-beam-realworld-example-app/). Thanks for letting me steal your code! :) 30 | - [Data 61](https://www.data61.csiro.au/) & [Advance Queensland](https://advance.qld.gov.au/), who joint fund the [Queensland Functional Programming Lab](https://qfpl.io) so that these kinds of materials can be created for the community. If you have ideas for other things that we could offer training in, please reach out to us at contact@qfpl.io! 31 | -------------------------------------------------------------------------------- /talk/reveal.js/plugin/notes-server/index.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var express = require('express'); 3 | var fs = require('fs'); 4 | var io = require('socket.io'); 5 | var Mustache = require('mustache'); 6 | 7 | var app = express(); 8 | var staticDir = express.static; 9 | var server = http.createServer(app); 10 | 11 | io = io(server); 12 | 13 | var opts = { 14 | port : 1947, 15 | baseDir : __dirname + '/../../' 16 | }; 17 | 18 | io.on( 'connection', function( socket ) { 19 | 20 | socket.on( 'new-subscriber', function( data ) { 21 | socket.broadcast.emit( 'new-subscriber', data ); 22 | }); 23 | 24 | socket.on( 'statechanged', function( data ) { 25 | delete data.state.overview; 26 | socket.broadcast.emit( 'statechanged', data ); 27 | }); 28 | 29 | socket.on( 'statechanged-speaker', function( data ) { 30 | delete data.state.overview; 31 | socket.broadcast.emit( 'statechanged-speaker', data ); 32 | }); 33 | 34 | }); 35 | 36 | [ 'css', 'js', 'images', 'plugin', 'lib' ].forEach( function( dir ) { 37 | app.use( '/' + dir, staticDir( opts.baseDir + dir ) ); 38 | }); 39 | 40 | app.get('/', function( req, res ) { 41 | 42 | res.writeHead( 200, { 'Content-Type': 'text/html' } ); 43 | fs.createReadStream( opts.baseDir + '/index.html' ).pipe( res ); 44 | 45 | }); 46 | 47 | app.get( '/notes/:socketId', function( req, res ) { 48 | 49 | fs.readFile( opts.baseDir + 'plugin/notes-server/notes.html', function( err, data ) { 50 | res.send( Mustache.to_html( data.toString(), { 51 | socketId : req.params.socketId 52 | })); 53 | }); 54 | 55 | }); 56 | 57 | // Actually listen 58 | server.listen( opts.port || null ); 59 | 60 | var brown = '\033[33m', 61 | green = '\033[32m', 62 | reset = '\033[0m'; 63 | 64 | var slidesLocation = 'http://localhost' + ( opts.port ? ( ':' + opts.port ) : '' ); 65 | 66 | console.log( brown + 'reveal.js - Speaker Notes' + reset ); 67 | console.log( '1. Open the slides at ' + green + slidesLocation + reset ); 68 | console.log( '2. Click on the link in your JS console to go to the notes page' ); 69 | console.log( '3. Advance through your slides and your notes will advance automatically' ); 70 | -------------------------------------------------------------------------------- /talk/reveal.js/css/theme/source/blood.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Blood theme for reveal.js 3 | * Author: Walther http://github.com/Walther 4 | * 5 | * Designed to be used with highlight.js theme 6 | * "monokai_sublime.css" available from 7 | * https://github.com/isagalaev/highlight.js/ 8 | * 9 | * For other themes, change $codeBackground accordingly. 10 | * 11 | */ 12 | 13 | // Default mixins and settings ----------------- 14 | @import "../template/mixins"; 15 | @import "../template/settings"; 16 | // --------------------------------------------- 17 | 18 | // Include theme-specific fonts 19 | 20 | @import url(https://fonts.googleapis.com/css?family=Ubuntu:300,700,300italic,700italic); 21 | 22 | // Colors used in the theme 23 | $blood: #a23; 24 | $coal: #222; 25 | $codeBackground: #23241f; 26 | 27 | $backgroundColor: $coal; 28 | 29 | // Main text 30 | $mainFont: Ubuntu, 'sans-serif'; 31 | $mainColor: #eee; 32 | 33 | // Headings 34 | $headingFont: Ubuntu, 'sans-serif'; 35 | $headingTextShadow: 2px 2px 2px $coal; 36 | 37 | // h1 shadow, borrowed humbly from 38 | // (c) Default theme by Hakim El Hattab 39 | $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); 40 | 41 | // Links 42 | $linkColor: $blood; 43 | $linkColorHover: lighten( $linkColor, 20% ); 44 | 45 | // Text selection 46 | $selectionBackgroundColor: $blood; 47 | $selectionColor: #fff; 48 | 49 | 50 | // Theme template ------------------------------ 51 | @import "../template/theme"; 52 | // --------------------------------------------- 53 | 54 | // some overrides after theme template import 55 | 56 | .reveal p { 57 | font-weight: 300; 58 | text-shadow: 1px 1px $coal; 59 | } 60 | 61 | .reveal h1, 62 | .reveal h2, 63 | .reveal h3, 64 | .reveal h4, 65 | .reveal h5, 66 | .reveal h6 { 67 | font-weight: 700; 68 | } 69 | 70 | .reveal p code { 71 | background-color: $codeBackground; 72 | display: inline-block; 73 | border-radius: 7px; 74 | } 75 | 76 | .reveal small code { 77 | vertical-align: baseline; 78 | } -------------------------------------------------------------------------------- /talk/reveal.js/plugin/notes-server/client.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // don't emit events from inside the previews themselves 4 | if( window.location.search.match( /receiver/gi ) ) { return; } 5 | 6 | var socket = io.connect( window.location.origin ), 7 | socketId = Math.random().toString().slice( 2 ); 8 | 9 | console.log( 'View slide notes at ' + window.location.origin + '/notes/' + socketId ); 10 | 11 | window.open( window.location.origin + '/notes/' + socketId, 'notes-' + socketId ); 12 | 13 | /** 14 | * Posts the current slide data to the notes window 15 | */ 16 | function post() { 17 | 18 | var slideElement = Reveal.getCurrentSlide(), 19 | notesElement = slideElement.querySelector( 'aside.notes' ); 20 | 21 | var messageData = { 22 | notes: '', 23 | markdown: false, 24 | socketId: socketId, 25 | state: Reveal.getState() 26 | }; 27 | 28 | // Look for notes defined in a slide attribute 29 | if( slideElement.hasAttribute( 'data-notes' ) ) { 30 | messageData.notes = slideElement.getAttribute( 'data-notes' ); 31 | } 32 | 33 | // Look for notes defined in an aside element 34 | if( notesElement ) { 35 | messageData.notes = notesElement.innerHTML; 36 | messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string'; 37 | } 38 | 39 | socket.emit( 'statechanged', messageData ); 40 | 41 | } 42 | 43 | // When a new notes window connects, post our current state 44 | socket.on( 'new-subscriber', function( data ) { 45 | post(); 46 | } ); 47 | 48 | // When the state changes from inside of the speaker view 49 | socket.on( 'statechanged-speaker', function( data ) { 50 | Reveal.setState( data.state ); 51 | } ); 52 | 53 | // Monitor events that trigger a change in state 54 | Reveal.addEventListener( 'slidechanged', post ); 55 | Reveal.addEventListener( 'fragmentshown', post ); 56 | Reveal.addEventListener( 'fragmenthidden', post ); 57 | Reveal.addEventListener( 'overviewhidden', post ); 58 | Reveal.addEventListener( 'overviewshown', post ); 59 | Reveal.addEventListener( 'paused', post ); 60 | Reveal.addEventListener( 'resumed', post ); 61 | 62 | // Post the initial state 63 | post(); 64 | 65 | }()); 66 | -------------------------------------------------------------------------------- /talk/reveal.js/plugin/print-pdf/print-pdf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * phantomjs script for printing presentations to PDF. 3 | * 4 | * Example: 5 | * phantomjs print-pdf.js "http://revealjs.com?print-pdf" reveal-demo.pdf 6 | * 7 | * @author Manuel Bieh (https://github.com/manuelbieh) 8 | * @author Hakim El Hattab (https://github.com/hakimel) 9 | * @author Manuel Riezebosch (https://github.com/riezebosch) 10 | */ 11 | 12 | // html2pdf.js 13 | var system = require( 'system' ); 14 | 15 | var probePage = new WebPage(); 16 | var printPage = new WebPage(); 17 | 18 | var inputFile = system.args[1] || 'index.html?print-pdf'; 19 | var outputFile = system.args[2] || 'slides.pdf'; 20 | 21 | if( outputFile.match( /\.pdf$/gi ) === null ) { 22 | outputFile += '.pdf'; 23 | } 24 | 25 | console.log( 'Export PDF: Reading reveal.js config [1/4]' ); 26 | 27 | probePage.open( inputFile, function( status ) { 28 | 29 | console.log( 'Export PDF: Preparing print layout [2/4]' ); 30 | 31 | var config = probePage.evaluate( function() { 32 | return Reveal.getConfig(); 33 | } ); 34 | 35 | if( config ) { 36 | 37 | printPage.paperSize = { 38 | width: Math.floor( config.width * ( 1 + config.margin ) ), 39 | height: Math.floor( config.height * ( 1 + config.margin ) ), 40 | border: 0 41 | }; 42 | 43 | printPage.open( inputFile, function( status ) { 44 | console.log( 'Export PDF: Preparing pdf [3/4]') 45 | printPage.evaluate( function() { 46 | Reveal.isReady() ? window.callPhantom() : Reveal.addEventListener( 'pdf-ready', window.callPhantom ); 47 | } ); 48 | } ); 49 | 50 | printPage.onCallback = function( data ) { 51 | // For some reason we need to "jump the queue" for syntax highlighting to work. 52 | // See: http://stackoverflow.com/a/3580132/129269 53 | setTimeout( function() { 54 | console.log( 'Export PDF: Writing file [4/4]' ); 55 | printPage.render( outputFile ); 56 | console.log( 'Export PDF: Finished successfully!' ); 57 | phantom.exit(); 58 | }, 0 ); 59 | }; 60 | } 61 | else { 62 | 63 | console.log( 'Export PDF: Unable to read reveal.js config. Make sure the input address points to a reveal.js page.' ); 64 | phantom.exit( 1 ); 65 | 66 | } 67 | } ); 68 | -------------------------------------------------------------------------------- /backend/backend.cabal: -------------------------------------------------------------------------------- 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 | , aeson 12 | , beam-core 13 | , beam-postgres 14 | , bytestring 15 | , conduit 16 | , common 17 | , containers 18 | , cookie 19 | , errors 20 | , frontend 21 | , jose 22 | , http-api-data 23 | , lens 24 | , monad-control 25 | , mtl 26 | , obelisk-backend 27 | , obelisk-route 28 | , obelisk-executable-config 29 | , postgresql-simple 30 | , resource-pool 31 | , scrypt 32 | , servant-auth-snap 33 | , servant-snap 34 | , snap-core 35 | , text 36 | , time 37 | , transformers 38 | , validation 39 | , vector 40 | exposed-modules: 41 | Backend 42 | Backend.Conduit 43 | Backend.Conduit.Claim 44 | Backend.Conduit.Database 45 | Backend.Conduit.Database.Articles 46 | Backend.Conduit.Database.Articles.Article 47 | Backend.Conduit.Database.Articles.ArticleTag 48 | Backend.Conduit.Database.Articles.Favorite 49 | Backend.Conduit.Database.Comments 50 | Backend.Conduit.Database.Comments.Comment 51 | Backend.Conduit.Database.Tags 52 | Backend.Conduit.Database.Tags.Tag 53 | Backend.Conduit.Database.Users 54 | Backend.Conduit.Database.Users.Follow 55 | Backend.Conduit.Database.Users.User 56 | Backend.Conduit.Errors 57 | Backend.Conduit.Validation 58 | Database.Beam.Postgres.Extended 59 | SetCookieOrphan 60 | 61 | ghc-options: -Wall 62 | 63 | executable backend 64 | main-is: main.hs 65 | hs-source-dirs: src-bin 66 | if impl(ghcjs) 67 | buildable: False 68 | build-depends: base 69 | , backend 70 | , common 71 | , frontend 72 | , obelisk-backend 73 | -------------------------------------------------------------------------------- /common/src/Common/Conduit/Api/Articles.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds, DeriveGeneric, TypeOperators #-} 2 | module Common.Conduit.Api.Articles 3 | ( ArticlesApi 4 | , ArticleApi 5 | , module Article 6 | , module Articles 7 | , module Attributes 8 | , module Comment 9 | , module Namespace 10 | ) where 11 | 12 | import Servant.API 13 | 14 | import Data.Text (Text) 15 | import Servant.Auth (Auth, JWT) 16 | 17 | import Common.Conduit.Api.Articles.Article as Article (Article (Article)) 18 | import Common.Conduit.Api.Articles.Articles as Articles (Articles (Articles)) 19 | import Common.Conduit.Api.Articles.Attributes as Attributes (ArticleAttributes (ArticleAttributes), CreateArticle, 20 | UpdateArticle) 21 | import Common.Conduit.Api.Articles.Comment as Comment (Comment (Comment)) 22 | import Common.Conduit.Api.Articles.CreateComment as Comment (CreateComment (CreateComment)) 23 | import Common.Conduit.Api.Namespace as Namespace (Namespace (Namespace)) 24 | 25 | type ArticlesApi token = 26 | ( 27 | Auth '[JWT] token 28 | :> QueryParam "limit" Integer 29 | :> QueryParam "offset" Integer 30 | :> QueryParams "tag" Text 31 | :> QueryParams "author" Text 32 | :> QueryParams "favorited" Text 33 | :> Get '[JSON] Articles 34 | ) :<|> ( 35 | Auth '[JWT] token 36 | :> ReqBody '[JSON] (Namespace "article" CreateArticle) 37 | :> PostCreated '[JSON] (Namespace "article" Article) 38 | ) :<|> ( 39 | "feed" 40 | :> Auth '[JWT] token 41 | :> QueryParam "limit" Integer 42 | :> QueryParam "offset" Integer 43 | :> Get '[JSON] Articles 44 | ) 45 | :<|> ArticleApi token 46 | 47 | 48 | type ArticleApi token = ( 49 | Auth '[JWT] token 50 | :> Capture "slug" Text 51 | :> ( 52 | Get '[JSON] (Namespace "article" Article) 53 | :<|> "comments" :> ( 54 | Get '[JSON] (Namespace "comments" [Comment]) 55 | :<|> ( 56 | ReqBody '[JSON] (Namespace "comment" CreateComment) 57 | :> PostCreated '[JSON] (Namespace "comment" Comment) 58 | ) :<|> ( 59 | Capture "commentId" Int 60 | :> DeleteNoContent '[JSON] NoContent) 61 | ) 62 | ) 63 | ) 64 | -------------------------------------------------------------------------------- /backend/src/Backend.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds, DeriveGeneric, EmptyCase, FlexibleContexts, LambdaCase, OverloadedStrings #-} 2 | {-# LANGUAGE PatternSynonyms, RankNTypes, StandaloneDeriving, TypeFamilies, TypeOperators #-} 3 | module Backend where 4 | 5 | import Control.Lens 6 | 7 | import qualified Crypto.JOSE as HOSE 8 | import qualified Crypto.JOSE.Types as HOSE 9 | import Control.Monad.IO.Class (liftIO) 10 | import Data.Maybe (maybe) 11 | import Data.Text (Text) 12 | import qualified Data.Text as T 13 | import Data.Text.Encoding (encodeUtf8) 14 | import Obelisk.Backend 15 | import Obelisk.ExecutableConfig (get) 16 | import Obelisk.Route 17 | import Servant (serveSnapWithContext) 18 | import SetCookieOrphan () 19 | 20 | import Backend.Conduit (jwtSettings, mkContext, mkEnv, runConduitServerM, server) 21 | import Common.Conduit.Api (api) 22 | import Common.Route (BackendRoute (..), FrontendRoute, backendRouteEncoder) 23 | import Backend.Conduit.Database (openConduitDb) 24 | 25 | getYolo :: Text -> IO Text 26 | getYolo l = maybe (error . T.unpack $ "Please fill in config: " <> l) T.strip <$> get l 27 | 28 | backend :: Backend BackendRoute FrontendRoute 29 | backend = Backend 30 | { _backend_run = \serve -> do 31 | pgConnStr <- getYolo "config/backend/pgConnStr" 32 | jwtKey <- getYolo "config/backend/jwtKey" 33 | let jwk = 34 | HOSE.fromKeyMaterial 35 | . HOSE.OctKeyMaterial 36 | . HOSE.OctKeyParameters 37 | . HOSE.Base64Octets 38 | . encodeUtf8 39 | $ jwtKey 40 | env <- mkEnv pgConnStr jwk 41 | let context = mkContext (env ^. jwtSettings) 42 | liftIO $ putStrLn "About to test the db connection. If ob run dies, check out config/backend/pgConnStr" 43 | _ <- openConduitDb (encodeUtf8 pgConnStr) 44 | serve $ \case 45 | (BackendRoute_Missing :/ ()) -> return () 46 | (BackendRoute_Api :/ _) -> runConduitServerM env $ serveSnapWithContext api context server 47 | , _backend_routeEncoder = backendRouteEncoder 48 | } 49 | -------------------------------------------------------------------------------- /frontend/frontend.cabal: -------------------------------------------------------------------------------- 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 | , aeson 11 | , containers 12 | , dependent-sum 13 | , lens 14 | , errors 15 | , obelisk-frontend 16 | , obelisk-route 17 | , reflex-dom 18 | , obelisk-generated-static 19 | , lucid 20 | , mmark 21 | , text 22 | , primitive 23 | , ref-tf 24 | , reflex 25 | , monad-control 26 | , dependent-map 27 | , dependent-sum-template 28 | , transformers 29 | , mtl 30 | , constraints 31 | , jsaddle 32 | , ghcjs-dom 33 | , jsaddle-dom 34 | , data-default 35 | , servant 36 | , servant-auth 37 | , servant-client-core 38 | , reflex-dom-storage 39 | , servant-reflex 40 | exposed-modules: 41 | Frontend 42 | , Frontend.Article 43 | , Frontend.ArticlePreview 44 | , Frontend.Conduit.Client 45 | , Frontend.Conduit.Client.Internal 46 | , Frontend.Editor 47 | , Frontend.FrontendStateT 48 | , Frontend.Head 49 | , Frontend.HomePage 50 | , Frontend.LocalStorageKey 51 | , Frontend.Login 52 | , Frontend.Nav 53 | , Frontend.Profile 54 | , Frontend.Register 55 | , Frontend.Settings 56 | , Frontend.Utils 57 | 58 | ghc-options: -Wall 59 | if impl(ghcjs) 60 | ghcjs-options: +RTS -K2048M -RTS -Wall 61 | 62 | executable frontend 63 | main-is: main.hs 64 | hs-source-dirs: src-bin 65 | build-depends: base 66 | , common 67 | , obelisk-frontend 68 | , obelisk-route 69 | , reflex-dom 70 | , obelisk-generated-static 71 | , frontend 72 | --TODO: Make these ghc-options optional 73 | ghc-options: -threaded 74 | if os(darwin) 75 | ghc-options: -dynamic 76 | 77 | if impl(ghcjs) 78 | ghcjs-options: +RTS -K2048M -RTS -Wall 79 | -------------------------------------------------------------------------------- /talk/reveal.js/test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | reveal.js - Tests 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /frontend/src/Frontend/Nav.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts, LambdaCase, MultiParamTypeClasses, OverloadedStrings, PatternSynonyms #-} 2 | {-# LANGUAGE TypeFamilies #-} 3 | module Frontend.Nav where 4 | 5 | import Reflex.Dom.Core 6 | 7 | import Data.Bool (bool) 8 | import Data.Functor (void) 9 | import Obelisk.Route (pattern (:/), R) 10 | import Obelisk.Route.Frontend (RouteToUrl, Routed, SetRoute, askRoute) 11 | 12 | import qualified Common.Conduit.Api.User.Account as Account 13 | import Common.Route (FrontendRoute (..), Username (..)) 14 | import Frontend.FrontendStateT 15 | import Frontend.Utils (routeLinkDynClass) 16 | 17 | nav 18 | :: ( DomBuilder t m 19 | , PostBuild t m 20 | , MonadHold t m 21 | , Routed t (R FrontendRoute) m 22 | , RouteToUrl (R FrontendRoute) m 23 | , SetRoute t (R FrontendRoute) m 24 | , HasFrontendState t s m 25 | , HasLoggedInAccount s 26 | ) 27 | => m () 28 | nav = do 29 | rDyn <- askRoute 30 | loggedIn <- reviewFrontendState loggedInAccount 31 | elClass "nav" "navbar navbar-light" $ 32 | elClass "div" "container" $ do 33 | routeLinkDynClass "navbar-brand" (constDyn $ FrontendRoute_Home :/ ()) $ text "conduit" 34 | elClass "ul" "nav navbar-nav pull-xs-right" $ do 35 | navItem (FrontendRoute_Home :/ ()) rDyn $ text "Home" 36 | void $ widgetHold 37 | loggedOutMenu 38 | (maybe loggedOutMenu loggedInMenu <$> updated loggedIn) 39 | 40 | where 41 | loggedOutMenu = do 42 | rDyn <- askRoute 43 | navItem (FrontendRoute_Login :/ ()) rDyn $ do 44 | text "Sign in" 45 | navItem (FrontendRoute_Register :/ ()) rDyn $ do 46 | text "Sign up" 47 | 48 | loggedInMenu a = do 49 | rDyn <- askRoute 50 | navItem (FrontendRoute_Editor :/ Nothing) rDyn $ do 51 | elClass "i" "ion-compose" blank 52 | text " " 53 | text "New Post" 54 | navItem (FrontendRoute_Settings :/ ()) rDyn $ do 55 | elClass "i" "ion-gear-a" blank 56 | text " " 57 | text "Settings" 58 | navItem 59 | (FrontendRoute_Profile :/ (Username (Account.username a),Nothing)) 60 | rDyn $ text $ Account.username a 61 | 62 | navItem r rDyn = elClass "li" "nav-item" . routeLinkDynClass 63 | (("nav-link " <>) . bool "" " active" . (== r) <$> rDyn) 64 | (constDyn r) 65 | -------------------------------------------------------------------------------- /frontend/src/Frontend/Head.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds, OverloadedStrings, TypeApplications #-} 2 | module Frontend.Head where 3 | 4 | import Reflex.Dom.Core 5 | 6 | import Data.Char (isSpace) 7 | import Data.Foldable (fold, traverse_) 8 | import qualified Data.Map as Map 9 | import Data.Text (Text, pack) 10 | import qualified Data.Text as T 11 | 12 | import Obelisk.Generated.Static 13 | 14 | styleLink :: DomBuilder t m => Text -> m () 15 | styleLink href = 16 | elAttr "link" (Map.fromList [("href",href),("rel","stylesheet"),("type","text/css")]) blank 17 | 18 | data FontType = Light | LightItalic | Regular | SemiBold | SemiBoldItalic | Bold | BoldItalic deriving (Eq, Ord, Enum, Show) 19 | 20 | htmlHead :: (DomBuilder t m) => m () 21 | htmlHead = do 22 | el "title" $ text "Conduit" 23 | -- these are not the typesafe links so that the fonts load relatively to the css. 24 | styleLink "/static/ionicons/css/ionicons.min.css" 25 | elAttr "style" ("type"=:"text/css") $ traverse_ (uncurry gfontsFontFamily) 26 | [ ("Merriweather Sans",[Regular, Bold]) 27 | , ("Source Sans Pro",[Light .. BoldItalic]) 28 | , ("Source Serif Pro",[Regular, Bold]) 29 | , ("Titillium Web",[Regular, Bold]) 30 | ] 31 | styleLink (static @"main.css") 32 | 33 | gfontsFontFamily :: DomBuilder t m => Text -> [FontType] -> m () 34 | gfontsFontFamily ffName = traverse_ (gfontsFontFace ffName) 35 | 36 | -- This would be better done with Clay 37 | gfontsFontFace :: DomBuilder t m => Text -> FontType -> m () 38 | gfontsFontFace familyName fontType = traverse_ text 39 | [ "@font-face {" 40 | , " font-family: '" <> T.toLower familyName <> "';" 41 | , " font-style: " <> fontStyle <> ";" 42 | , " font-weight: " <> fontWeight <> ";" 43 | , fold 44 | [ " src: url(/static/gfonts/",snaked,"/",noSpaces,"-",styleName,".ttf)" 45 | , ";" 46 | ] 47 | , "} " 48 | ] 49 | where 50 | styleName = pack $ show fontType 51 | fontWeight = case fontType of 52 | Light -> "300" 53 | LightItalic -> "300" 54 | Regular -> "400" 55 | SemiBold -> "600" 56 | SemiBoldItalic -> "600" 57 | Bold -> "700" 58 | BoldItalic -> "700" 59 | fontStyle = case fontType of 60 | LightItalic -> "italic" 61 | SemiBoldItalic -> "italic" 62 | BoldItalic -> "italic" 63 | _ -> "normal" 64 | 65 | noSpaces = T.filter (not . isSpace) familyName 66 | snaked = T.replace " " "_" familyName 67 | -------------------------------------------------------------------------------- /frontend/src/Frontend/LocalStorageKey.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveGeneric, FlexibleInstances, GADTs, GeneralizedNewtypeDeriving, MultiParamTypeClasses #-} 2 | {-# LANGUAGE OverloadedStrings, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TypeFamilies #-} 3 | {-# LANGUAGE UndecidableInstances #-} 4 | {-# OPTIONS_GHC -fno-warn-orphans #-} 5 | module Frontend.LocalStorageKey where 6 | 7 | import Data.Aeson (FromJSON (..), ToJSON (..)) 8 | import Data.Dependent.Map (Some (This)) 9 | import Data.Functor.Identity (Identity (Identity)) 10 | import Data.GADT.Aeson (FromJSONTag (..), GKey (..), ToJSONTag (..)) 11 | import Data.GADT.Compare.TH (deriveGCompare, deriveGEq) 12 | import Data.GADT.Show.TH (deriveGShow) 13 | 14 | import Control.Monad.Trans (lift) 15 | import Obelisk.Route.Frontend (RouteToUrl (..), Routed (..), RoutedT, SetRoute (..)) 16 | import Reflex (EventWriterT) 17 | import Reflex.Dom.Storage.Base (StorageT (..)) 18 | import Reflex.Dom.Storage.Class (HasStorage (..)) 19 | 20 | import Common.Conduit.Api.User.Account (Token) 21 | 22 | data LocalStorageTag a where 23 | LocalStorageJWT :: LocalStorageTag Token 24 | 25 | deriveGEq ''LocalStorageTag 26 | deriveGCompare ''LocalStorageTag 27 | deriveGShow ''LocalStorageTag 28 | 29 | instance GKey LocalStorageTag where 30 | toKey (This LocalStorageJWT) = "conduit_jwt" 31 | 32 | fromKey t = 33 | case t of 34 | "conduit_jwt" -> Just (This LocalStorageJWT) 35 | _ -> Nothing 36 | 37 | keys _ = [This LocalStorageJWT] 38 | 39 | instance ToJSONTag LocalStorageTag Identity where 40 | toJSONTagged LocalStorageJWT (Identity x) = toJSON x 41 | 42 | instance FromJSONTag LocalStorageTag Identity where 43 | parseJSONTagged LocalStorageJWT x = Identity <$> parseJSON x 44 | 45 | instance (Monad m, SetRoute t r m) => SetRoute t r (StorageT t k m) where 46 | setRoute = lift . setRoute 47 | modifyRoute = lift . modifyRoute 48 | 49 | instance (Monad m, RouteToUrl r m) => RouteToUrl r (StorageT t k m) where 50 | askRouteToUrl = lift askRouteToUrl 51 | 52 | instance (Monad m, Routed t r m) => Routed t r (StorageT t k m) where 53 | askRoute = lift askRoute 54 | 55 | instance HasStorage t k m => HasStorage t k (RoutedT t r m) where 56 | askStorage = lift askStorage 57 | tellStorage = lift . tellStorage 58 | 59 | instance HasStorage t k m => HasStorage t k (EventWriterT t w m) where 60 | askStorage = lift askStorage 61 | tellStorage = lift . tellStorage 62 | -------------------------------------------------------------------------------- /talk/reveal.js/test/examples/slide-transitions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | reveal.js - Slide Transitions 8 | 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 |
30 |

Default

31 |
32 | 33 |
34 |

Default

35 |
36 | 37 |
38 |

data-transition: zoom

39 |
40 | 41 |
42 |

data-transition: zoom-in fade-out

43 |
44 | 45 |
46 |

Default

47 |
48 | 49 |
50 |

data-transition: convex

51 |
52 | 53 |
54 |

data-transition: convex-in concave-out

55 |
56 | 57 |
58 |
59 |

Default

60 |
61 |
62 |

data-transition: concave

63 |
64 |
65 |

data-transition: convex-in fade-out

66 |
67 |
68 |

Default

69 |
70 |
71 | 72 |
73 |

data-transition: none

74 |
75 | 76 |
77 |

Default

78 |
79 | 80 |
81 | 82 |
83 | 84 | 85 | 86 | 87 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /backend/src/Backend/Conduit/Errors.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings, StandaloneDeriving, DeriveGeneric, DeriveAnyClass #-} 2 | module Backend.Conduit.Errors 3 | ( ErrorBody(..) 4 | , ConduitErrorsT 5 | , failedValidation 6 | , forbidden 7 | , internalServerError 8 | , internalServerErrorShow 9 | , notAuthorized 10 | , notFound 11 | , runConduitErrorsT 12 | ) where 13 | 14 | import Control.Monad.Except (ExceptT, runExceptT) 15 | import Data.Aeson (ToJSON, encode) 16 | import Data.Text (Text, pack) 17 | import Servant (ServantErr (..), err401, err403, err404, err500, errBody, throwError) 18 | import Snap.Core (MonadSnap) 19 | 20 | import Common.Conduit.Api.Errors 21 | 22 | type ConduitErrorsT m = ExceptT ServantErr m 23 | 24 | runConduitErrorsT :: MonadSnap m => ConduitErrorsT m a -> m a 25 | runConduitErrorsT = (either throwError pure =<<) . runExceptT 26 | 27 | notAuthorized :: ServantErr 28 | notAuthorized = 29 | err401 {errBody = encode body} 30 | where 31 | body :: ErrorBody () 32 | body = ErrorBody 33 | { message = "Not Authorized" 34 | , errors = Nothing 35 | } 36 | 37 | forbidden :: ServantErr 38 | forbidden = 39 | err403 {errBody = encode body} 40 | where 41 | body :: ErrorBody () 42 | body = ErrorBody 43 | { message = "Forbidden" 44 | , errors = Nothing 45 | } 46 | 47 | notFound :: Text -> ServantErr 48 | notFound resourceName = 49 | err404 {errBody = encode body} 50 | where 51 | body :: ErrorBody () 52 | body = ErrorBody 53 | { message = resourceName <> " not found" 54 | , errors = Nothing 55 | } 56 | 57 | failedValidation :: ToJSON failures => failures -> ServantErr 58 | failedValidation failures = 59 | ServantErr 60 | { errHTTPCode = 422 61 | , errReasonPhrase = "Unprocessable Entity" 62 | , errBody = encode (body failures) 63 | , errHeaders = [] 64 | } 65 | where 66 | body :: failures -> ErrorBody failures 67 | body fs = ErrorBody 68 | { message = "Failed validation" 69 | , errors = Just fs 70 | } 71 | 72 | internalServerError :: Text -> ServantErr 73 | internalServerError msg = 74 | err500 {errBody = encode body} 75 | where 76 | body :: ErrorBody [Text] 77 | body = ErrorBody 78 | { message = "Internal server error" 79 | , errors = Just [msg] 80 | } 81 | 82 | internalServerErrorShow :: Show e => Text -> e -> ServantErr 83 | internalServerErrorShow msg e = 84 | err500 {errBody = encode body} 85 | where 86 | body :: ErrorBody [Text] 87 | body = ErrorBody 88 | { message = "Internal server error" 89 | , errors = Just [msg, pack . show $ e] 90 | } 91 | -------------------------------------------------------------------------------- /workshop/9001-bonus.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Workshop Docs 8 | 14 | 15 | 18 | 19 | 20 |

Bonus Exercises

21 |

If you finish early or are after different challenges. Try the following:

22 |

Implement Form Disable while Submitting

23 |

Mark all inputs and the submit button as disabled while the backend call is pending.

24 |

Implement the user feed tab on the home page

25 |

If logged in, the default homepage view should be the output of /api/articles/feed. This is tricky because you have to use higher order FRP to switch in a different backend call depending on the selection.

26 |

Create Post Form

27 |

See if you can create the create post form from scratch.

28 |

For even more bonus marks, see if you can get a live preview from typing events in the markdown text area. You may need to throttle these events to get decent UX out of it.

29 |

Client Side Validation

30 |

Use Data.Validation and Data.Functor.Compose together to validate as you collect form input values. Connect failed validations with onBlur events to display / hide them when things fail / are resolved.

31 |

Implement Pagination

32 |

This hasn't been done in the example yet. But the article list calls take a limit and an offset and they return you a row count when they return. Can you run with this and implement pagination?

33 |

Implement Favourite / UnFavourite of articles

34 |

This is hard because these calls aren't implemented in the API yet! You'll have to figure out all the servant layers to make this happen.

35 |

It's also a revision of the behaviour that we saw in the article comments: that we are updating our local state based on the backend call.

36 |

Same goes for profile follow / unfollow.

37 | 38 | 39 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All changes to the workshop will be documented here with instructions for how you should update local code / vms when upgrading. 3 | 4 | For each new version, there will also be a github release: so please watch this repo for new releases if you are keen to be notified! :) 5 | 6 | ## Version 0.1.0.1 - 2019-05-13-02:00 7 | Just finalised some code changes to example that will get us to the end of the workshop. 8 | 9 | Adds tag loading to the home page and selecting feed/global. 10 | 11 | Adds some test data in that makes it a bit easier to test the feed and tags. 12 | 13 | Workshop instructions should come this evening. Just tidying it all off still. :) 14 | 15 | ### Upgrade instructions 16 | 17 | - If you are on the VM and have already downloaded it, please: 18 | - Do a git pull in the ~/reflex-realworld-workshop directory 19 | - To update the DB to the latest schema: `psql -h localhost -U conduit -c "DROP SCHEMA PUBLIC CASCADE; CREATE SCHEMA PUBLIC;" && psql -h localhost -U conduit conduit -f ~/reflex-realworld-workshop/devenv/common/dbdump.sql` 20 | - `ob run` in both ~/reflex-realworld-workshop and ~/reflex-realworld-workshop/reflex-realword-example` 21 | - If the update worked, on http://localhost:8000 you should see tags in the list of "being cool" and "parties" and clicking on them should filter the list down to a single article. 22 | - If you are using to docker setup): 23 | - git pull in your local checkout 24 | - devenv/docker/ob stop 25 | - `docker pull benkolera/reflex-realworld-workshop-pg:0.1.0` 26 | - devenv/docker/ob run-example 27 | - If the update worked, on http://localhost:8000 you should see tags in the list of "being cool" and "parties" and clicking on them should filter the list down to a single article. 28 | - If you are using nix/NixOS: 29 | - git pull in your checkout 30 | - If you are using the docker database: 31 | - devenv/docker/ob stop 32 | - `docker pull benkolera/reflex-realworld-workshop-pg:0.1.0` 33 | - Run the database again 34 | - If you have a local postgres DB: 35 | - `psql -h localhost -U conduit -c "DROP SCHEMA PUBLIC CASCADE; CREATE SCHEMA PUBLIC;" && psql -h localhost -U conduit conduit -f ~/reflex-realworld-workshop/devenv/common/dbdump.sql` 36 | - ob run in the example subfolder. 37 | - If the update worked, on http://localhost:8000 you should see tags in the list of "being cool" and "parties" and clicking on them should filter the list down to a single article. 38 | 39 | ## Version 0.1.0 - 2019-05-08 40 | This is the first release without any content. This is the skeleton of the code and all the dependencies. If you follow the [setup instruction](./SETUP.md) to a clean ob run, then it should just be a git pull later to get the exercises and it should just work without any big rebuilds or waits. 41 | 42 | - Released initial version 43 | -------------------------------------------------------------------------------- /talk/reveal.js/test/test-markdown-element-attributes.js: -------------------------------------------------------------------------------- 1 | Reveal.addEventListener( 'ready', function() { 2 | 3 | QUnit.module( 'Markdown' ); 4 | 5 | QUnit.test( 'Vertical separator', function( assert ) { 6 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section>section' ).length, 4, 'found four slides' ); 7 | }); 8 | 9 | QUnit.test( 'Attributes on element header in vertical slides', function( assert ) { 10 | assert.strictEqual( document.querySelectorAll( '.reveal .slides section>section h2.fragment.fade-out' ).length, 1, 'found one vertical slide with class fragment.fade-out on header' ); 11 | assert.strictEqual( document.querySelectorAll( '.reveal .slides section>section h2.fragment.shrink' ).length, 1, 'found one vertical slide with class fragment.shrink on header' ); 12 | }); 13 | 14 | QUnit.test( 'Attributes on element paragraphs in vertical slides', function( assert ) { 15 | assert.strictEqual( document.querySelectorAll( '.reveal .slides section>section p.fragment.grow' ).length, 2, 'found a vertical slide with two paragraphs with class fragment.grow' ); 16 | }); 17 | 18 | QUnit.test( 'Attributes on element list items in vertical slides', function( assert ) { 19 | assert.strictEqual( document.querySelectorAll( '.reveal .slides section>section li.fragment.grow' ).length, 3, 'found a vertical slide with three list items with class fragment.grow' ); 20 | }); 21 | 22 | QUnit.test( 'Attributes on element paragraphs in horizontal slides', function( assert ) { 23 | assert.strictEqual( document.querySelectorAll( '.reveal .slides section p.fragment.highlight-red' ).length, 4, 'found a horizontal slide with four paragraphs with class fragment.grow' ); 24 | }); 25 | 26 | QUnit.test( 'Attributes on element list items in horizontal slides', function( assert ) { 27 | assert.strictEqual( document.querySelectorAll( '.reveal .slides section li.fragment.highlight-green' ).length, 5, 'found a horizontal slide with five list items with class fragment.roll-in' ); 28 | }); 29 | 30 | QUnit.test( 'Attributes on element image in horizontal slides', function( assert ) { 31 | assert.strictEqual( document.querySelectorAll( '.reveal .slides section img.reveal.stretch' ).length, 1, 'found a horizontal slide with stretched image, class img.reveal.stretch' ); 32 | }); 33 | 34 | QUnit.test( 'Attributes on elements in vertical slides with default element attribute separator', function( assert ) { 35 | assert.strictEqual( document.querySelectorAll( '.reveal .slides section h2.fragment.highlight-red' ).length, 2, 'found two h2 titles with fragment highlight-red in vertical slides with default element attribute separator' ); 36 | }); 37 | 38 | QUnit.test( 'Attributes on elements in single slides with default element attribute separator', function( assert ) { 39 | assert.strictEqual( document.querySelectorAll( '.reveal .slides section p.fragment.highlight-blue' ).length, 3, 'found three elements with fragment highlight-blue in single slide with default element attribute separator' ); 40 | }); 41 | 42 | } ); 43 | 44 | Reveal.initialize(); 45 | -------------------------------------------------------------------------------- /README.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Workshop Docs 8 | 14 | 15 | 18 | 19 | 20 |

Reflex Realworld Workshop

21 |

Data61 Logo

22 |
23 | 24 |

Gitter

25 |

This workshop is a step by step walkthrough of how to implement the Real World Demo App spec in Obelisk/Reflex. The full implementation is available in the submodule sitting in reflex-realworld-example if you want to see the finished product or peek at it for answers. :)

26 |

This is being presented at YOW Lambda Jam 2019, but I hope that this continues as a living resource and example after the conference, so if it becomes out of date with the latest Obelisk, please file an issue or PR!

27 |

Keeping in Touch / Getting assistance

28 |

When I release new changes to the workshop, I will release them as github releases. If you are doing the workshop at Lambda Jam, I highly recommend watching this repo for releases so I can let you know to pull new code if necessary.

29 |

If you have any issues or questions with the material, please feel free to reach out via a github issue, the gitter room or #QFPL on freenode.

30 |

Setup Instructions

31 |

See SETUP.md.

32 |

Workshop

33 |

See workshop/00-introduction

34 |

Acknowledgements & Thanks

35 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /workshop/01-foldDyn/merge.gv: -------------------------------------------------------------------------------- 1 | digraph H { 2 | rankdir="LR"; 3 | 4 | foldDyn [ 5 | shape=plaintext 6 | label=< 7 | 8 | 9 | 10 | 11 | 12 |
foldDyn
e -> s -> sDynamic t s
s
Event t e
> 13 | ]; 14 | 15 | appEndo [ 16 | shape=plaintext; 17 | label=< 18 | 19 | 20 |
appEndo :: Endo a -> a -> a
21 | > 22 | ]; 23 | 24 | resetEndo [ 25 | shape=plaintext 26 | label=< 27 | 28 | 29 |
Endo $ const 0
30 | > 31 | ]; 32 | 33 | incEndo [ 34 | shape=plaintext 35 | label=< 36 | 37 | 38 |
Endo (+1)
39 | > 40 | ]; 41 | 42 | mkReset [ 43 | shape=plaintext 44 | label=< 45 | 46 | 47 | 48 | 49 |
bEvent t b
<$
Event t a
50 | > 51 | ]; 52 | 53 | mkInc [ 54 | shape=plaintext 55 | label=< 56 | 57 | 58 | 59 | 60 |
bEvent t b
<$
Event t a
61 | > 62 | ]; 63 | 64 | mappend [ 65 | shape=plaintext 66 | label=< 67 | 68 | 69 | 70 | 71 |
Event t aEvent t a
<>
Event t a
72 | > 73 | ]; 74 | 75 | 76 | incButton [ 77 | shape=plaintext 78 | label=< 79 | 80 | 81 | 82 |
incButton
Event t ()
83 | > 84 | ]; 85 | 86 | resetButton [ 87 | shape=plaintext 88 | label=< 89 | 90 | 91 | 92 |
resetButton
Event t ()
93 | > 94 | ]; 95 | 96 | resetButton:output -> mkReset:inputE; 97 | incButton:output -> mkInc:inputE; 98 | incEndo -> mkInc:inputVal; 99 | resetEndo -> mkReset:inputVal; 100 | appEndo -> foldDyn:reducer; 101 | mkInc -> mappend:inputA; 102 | mkReset -> mappend:inputB; 103 | mappend:output -> foldDyn:event; 104 | 0 -> foldDyn:init; 105 | 106 | } 107 | -------------------------------------------------------------------------------- /talk/code/common/src/Common/Route.hs: -------------------------------------------------------------------------------- 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 | FrontendRoute_ExDom :: FrontendRoute () 36 | FrontendRoute_ExFoldDyn :: FrontendRoute () 37 | FrontendRoute_ExMergeEvents :: FrontendRoute () 38 | FrontendRoute_ExEventWriter :: FrontendRoute () 39 | FrontendRoute_ExReader :: FrontendRoute () 40 | FrontendRoute_ExRoutes :: FrontendRoute (Maybe (R ExampleSubRoute)) 41 | FrontendRoute_ExWorkflow :: FrontendRoute () 42 | -- This type is used to define frontend routes, i.e. ones for which the backend will serve the frontend. 43 | 44 | data ExampleSubRoute :: * -> * where 45 | ExampleSubRoute_A :: ExampleSubRoute () 46 | ExampleSubRoute_B :: ExampleSubRoute () 47 | 48 | backendRouteEncoder 49 | :: Encoder (Either Text) Identity (R (Sum BackendRoute (ObeliskRoute FrontendRoute))) PageName 50 | backendRouteEncoder = handleEncoder (const (InL BackendRoute_Missing :/ ())) $ 51 | pathComponentEncoder $ \case 52 | InL backendRoute -> case backendRoute of 53 | BackendRoute_Missing -> PathSegment "missing" $ unitEncoder mempty 54 | InR obeliskRoute -> obeliskRouteSegment obeliskRoute $ \case 55 | -- The encoder given to PathEnd determines how to parse query parameters, 56 | -- in this example, we have none, so we insist on it. 57 | FrontendRoute_Main -> PathEnd $ unitEncoder mempty 58 | FrontendRoute_ExDom -> PathSegment "dom" $ unitEncoder mempty 59 | FrontendRoute_ExFoldDyn -> PathSegment "fold_dyn" $ unitEncoder mempty 60 | FrontendRoute_ExMergeEvents -> PathSegment "merge_events" $ unitEncoder mempty 61 | FrontendRoute_ExEventWriter -> PathSegment "event_writer" $ unitEncoder mempty 62 | FrontendRoute_ExReader -> PathSegment "reader" $ unitEncoder mempty 63 | FrontendRoute_ExWorkflow -> PathSegment "workflow" $ unitEncoder mempty 64 | FrontendRoute_ExRoutes -> PathSegment "routes" $ 65 | maybeEncoder (unitEncoder mempty) $ pathComponentEncoder $ \case 66 | ExampleSubRoute_A -> PathSegment "a" $ unitEncoder mempty 67 | ExampleSubRoute_B -> PathSegment "b" $ unitEncoder mempty 68 | 69 | 70 | concat <$> mapM deriveRouteComponent 71 | [ ''BackendRoute 72 | , ''FrontendRoute 73 | , ''ExampleSubRoute 74 | ] 75 | -------------------------------------------------------------------------------- /talk/images/reflex-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml -------------------------------------------------------------------------------- /talk/reveal.js/test/test-markdown-slide-attributes.js: -------------------------------------------------------------------------------- 1 | Reveal.addEventListener( 'ready', function() { 2 | 3 | QUnit.module( 'Markdown' ); 4 | 5 | QUnit.test( 'Vertical separator', function( assert ) { 6 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section>section' ).length, 6, 'found six vertical slides' ); 7 | }); 8 | 9 | QUnit.test( 'Id on slide', function( assert ) { 10 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section>section#slide2' ).length, 1, 'found one slide with id slide2' ); 11 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section>section a[href="#/slide2"]' ).length, 1, 'found one slide with a link to slide2' ); 12 | }); 13 | 14 | QUnit.test( 'data-background attributes', function( assert ) { 15 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section>section[data-background="#A0C66B"]' ).length, 1, 'found one vertical slide with data-background="#A0C66B"' ); 16 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section>section[data-background="#ff0000"]' ).length, 1, 'found one vertical slide with data-background="#ff0000"' ); 17 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section[data-background="#C6916B"]' ).length, 1, 'found one slide with data-background="#C6916B"' ); 18 | }); 19 | 20 | QUnit.test( 'data-transition attributes', function( assert ) { 21 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section>section[data-transition="zoom"]' ).length, 1, 'found one vertical slide with data-transition="zoom"' ); 22 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section>section[data-transition="fade"]' ).length, 1, 'found one vertical slide with data-transition="fade"' ); 23 | assert.strictEqual( document.querySelectorAll( '.reveal .slides section [data-transition="zoom"]' ).length, 1, 'found one slide with data-transition="zoom"' ); 24 | }); 25 | 26 | QUnit.test( 'data-background attributes with default separator', function( assert ) { 27 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section>section[data-background="#A7C66B"]' ).length, 1, 'found one vertical slide with data-background="#A0C66B"' ); 28 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section>section[data-background="#f70000"]' ).length, 1, 'found one vertical slide with data-background="#ff0000"' ); 29 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section[data-background="#C7916B"]' ).length, 1, 'found one slide with data-background="#C6916B"' ); 30 | }); 31 | 32 | QUnit.test( 'data-transition attributes with default separator', function( assert ) { 33 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section>section[data-transition="concave"]' ).length, 1, 'found one vertical slide with data-transition="zoom"' ); 34 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section>section[data-transition="page"]' ).length, 1, 'found one vertical slide with data-transition="fade"' ); 35 | assert.strictEqual( document.querySelectorAll( '.reveal .slides section [data-transition="concave"]' ).length, 1, 'found one slide with data-transition="zoom"' ); 36 | }); 37 | 38 | QUnit.test( 'data-transition attributes with inline content', function( assert ) { 39 | assert.strictEqual( document.querySelectorAll( '.reveal .slides>section[data-background="#ff0000"]' ).length, 3, 'found three horizontal slides with data-background="#ff0000"' ); 40 | }); 41 | 42 | } ); 43 | 44 | Reveal.initialize(); 45 | -------------------------------------------------------------------------------- /backend/src/Backend/Conduit/Database.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveGeneric, FlexibleContexts, MultiParamTypeClasses, OverloadedStrings #-} 2 | module Backend.Conduit.Database 3 | ( ConduitDb(..) 4 | , QueryError(..) 5 | , conduitDb 6 | , maybeRow 7 | , openConduitDb 8 | , rowList 9 | , singleRow 10 | ) where 11 | 12 | import Control.Exception (Exception) 13 | import Control.Monad.Error.Class (MonadError, throwError) 14 | import Data.ByteString (ByteString) 15 | import Data.Conduit (ConduitT, (.|)) 16 | import qualified Data.Conduit as Conduit 17 | import qualified Data.Conduit.List as Conduit 18 | import Database.Beam (Database, DatabaseSettings, MonadIO, TableEntity, dbModification, 19 | defaultDbSettings, fieldNamed, liftIO, modifyTable, 20 | tableModification, withDbModification) 21 | import Database.Beam.Postgres (Postgres) 22 | import Database.PostgreSQL.Simple (Connection, connectPostgreSQL) 23 | import GHC.Generics (Generic) 24 | 25 | 26 | 27 | import Backend.Conduit.Database.Articles.Article (ArticleT) 28 | import qualified Backend.Conduit.Database.Articles.Article as Article 29 | import Backend.Conduit.Database.Articles.ArticleTag (ArticleTagT) 30 | import Backend.Conduit.Database.Articles.Favorite (FavoriteT) 31 | import Backend.Conduit.Database.Comments.Comment (CommentT) 32 | import qualified Backend.Conduit.Database.Comments.Comment as Comment 33 | import Backend.Conduit.Database.Tags.Tag (TagT) 34 | import Backend.Conduit.Database.Users.Follow (FollowT) 35 | import Backend.Conduit.Database.Users.User (UserT) 36 | 37 | 38 | data ConduitDb f = ConduitDb 39 | { conduitArticleTags :: f (TableEntity ArticleTagT) 40 | , conduitArticles :: f (TableEntity ArticleT) 41 | , conduitComments :: f (TableEntity CommentT) 42 | , conduitFavorites :: f (TableEntity FavoriteT) 43 | , conduitFollows :: f (TableEntity FollowT) 44 | , conduitTags :: f (TableEntity TagT) 45 | , conduitUsers :: f (TableEntity UserT) 46 | } deriving (Generic) 47 | 48 | instance Database Postgres ConduitDb 49 | 50 | newtype QueryError = UnexpectedAmountOfRows Int 51 | deriving Show 52 | 53 | instance Exception QueryError 54 | 55 | conduitDb :: DatabaseSettings Postgres ConduitDb 56 | conduitDb = 57 | defaultDbSettings `withDbModification` 58 | dbModification 59 | { conduitArticles = 60 | modifyTable id $ 61 | tableModification 62 | { Article.createdAt = fieldNamed "created_at" 63 | , Article.updatedAt = fieldNamed "updated_at" 64 | } 65 | , conduitComments = 66 | modifyTable id $ 67 | tableModification 68 | { Comment.createdAt = fieldNamed "created_at" 69 | , Comment.updatedAt = fieldNamed "updated_at" 70 | } 71 | } 72 | 73 | openConduitDb :: MonadIO m => ByteString -> m Connection 74 | openConduitDb = liftIO . connectPostgreSQL 75 | 76 | maybeRow :: Monad m => ConduitT () a m () -> m (Maybe a) 77 | maybeRow c = Conduit.runConduit (c .| Conduit.await) 78 | 79 | singleRow :: (MonadError QueryError m) => ConduitT () a m () -> m a 80 | singleRow c = maybe (throwError (UnexpectedAmountOfRows 0)) pure =<< maybeRow c 81 | 82 | rowList :: Monad m => ConduitT () a m () -> m [a] 83 | rowList c = Conduit.runConduit (c .| Conduit.consume) 84 | --------------------------------------------------------------------------------