index page & http redirect
This commit is contained in:
parent
5dab23a97c
commit
15abe78b5e
7 changed files with 45 additions and 8 deletions
5
public/static/index.md
Normal file
5
public/static/index.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
## simple ranked choice voting
|
||||||
|
|
||||||
|
[create a poll](/create)
|
||||||
|
|
||||||
|
[source code](https://gitlab.com/winesj/rcv-site)
|
||||||
|
|
@ -75,4 +75,5 @@ body {
|
||||||
|
|
||||||
#ballot-submit {
|
#ballot-submit {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ executable server
|
||||||
build-depends:
|
build-depends:
|
||||||
acid-state,
|
acid-state,
|
||||||
aeson,
|
aeson,
|
||||||
|
async,
|
||||||
base,
|
base,
|
||||||
bytestring,
|
bytestring,
|
||||||
containers,
|
containers,
|
||||||
|
|
@ -49,6 +50,7 @@ executable server
|
||||||
lucid-htmx,
|
lucid-htmx,
|
||||||
mtl,
|
mtl,
|
||||||
network-uri,
|
network-uri,
|
||||||
|
commonmark,
|
||||||
random,
|
random,
|
||||||
safecopy,
|
safecopy,
|
||||||
servant,
|
servant,
|
||||||
|
|
@ -62,7 +64,7 @@ executable server
|
||||||
wai-app-static,
|
wai-app-static,
|
||||||
wai-extra,
|
wai-extra,
|
||||||
warp,
|
warp,
|
||||||
warp-tls
|
warp-tls == 3.4.0
|
||||||
default-language:
|
default-language:
|
||||||
Haskell2010
|
Haskell2010
|
||||||
other-modules:
|
other-modules:
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ type RCVAPI =
|
||||||
:<|> "poll" :> Capture "pollId" P.PollId :> Get '[SL.HTML] (L.Html ())
|
:<|> "poll" :> Capture "pollId" P.PollId :> Get '[SL.HTML] (L.Html ())
|
||||||
:<|> "poll" :> Capture "pollId" P.PollId :> ReqBody '[JSON] B.Ballot :> Post '[SL.HTML] (L.Html ())
|
:<|> "poll" :> Capture "pollId" P.PollId :> ReqBody '[JSON] B.Ballot :> Post '[SL.HTML] (L.Html ())
|
||||||
:<|> "poll" :> Capture "pollId" P.PollId :> "results" :> Get '[SL.HTML] (L.Html ())
|
:<|> "poll" :> Capture "pollId" P.PollId :> "results" :> Get '[SL.HTML] (L.Html ())
|
||||||
|
:<|> Get '[SL.HTML] (L.Html ())
|
||||||
:<|> StaticAPI
|
:<|> StaticAPI
|
||||||
|
|
||||||
type StaticAPI =
|
type StaticAPI =
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ data Env = Env
|
||||||
{
|
{
|
||||||
db :: Ac.AcidState DB.DB,
|
db :: Ac.AcidState DB.DB,
|
||||||
script :: T.Text,
|
script :: T.Text,
|
||||||
|
index :: T.Text,
|
||||||
gen :: R.AtomicGenM R.StdGen
|
gen :: R.AtomicGenM R.StdGen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
36
src/Main.hs
36
src/Main.hs
|
|
@ -8,12 +8,14 @@ import Lucid as L
|
||||||
import Lucid.Htmx
|
import Lucid.Htmx
|
||||||
import Network.Wai.Application.Static (defaultWebAppSettings, StaticSettings (ss404Handler))
|
import Network.Wai.Application.Static (defaultWebAppSettings, StaticSettings (ss404Handler))
|
||||||
import Servant
|
import Servant
|
||||||
|
import qualified Control.Concurrent.Async as A
|
||||||
import qualified Control.Monad.Reader as Rd
|
import qualified Control.Monad.Reader as Rd
|
||||||
import qualified Data.Acid as Ac
|
import qualified Data.Acid as Ac
|
||||||
import qualified Data.Hashable as H
|
import qualified Data.Hashable as H
|
||||||
import qualified Data.List.NonEmpty as LN
|
import qualified Data.List.NonEmpty as LN
|
||||||
import qualified Data.Map.Strict as M
|
import qualified Data.Map.Strict as M
|
||||||
import qualified Data.Maybe as My
|
import qualified Data.Maybe as My
|
||||||
|
import qualified Commonmark as C
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Database as DB
|
import qualified Database as DB
|
||||||
import qualified Error as Er
|
import qualified Error as Er
|
||||||
|
|
@ -24,9 +26,13 @@ import qualified Poll as P
|
||||||
import qualified System.Environment as S
|
import qualified System.Environment as S
|
||||||
import qualified Data.Text.IO as TIO
|
import qualified Data.Text.IO as TIO
|
||||||
import qualified System.Random.Stateful as R
|
import qualified System.Random.Stateful as R
|
||||||
|
import qualified Network.Wai as NW
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
import qualified Control.Monad as M
|
import qualified Control.Monad as M
|
||||||
|
import qualified Network.Wai.Middleware.ForceSSL as TLS
|
||||||
|
import qualified Network.HTTP.Types.Status as TS
|
||||||
import LucidUtils
|
import LucidUtils
|
||||||
|
import qualified Data.Text.Lazy as TL
|
||||||
|
|
||||||
throwOrLift :: AppM a -> Maybe a -> AppM a
|
throwOrLift :: AppM a -> Maybe a -> AppM a
|
||||||
throwOrLift err = My.maybe err pure
|
throwOrLift err = My.maybe err pure
|
||||||
|
|
@ -54,6 +60,7 @@ results pollId = do
|
||||||
let results' = (reverse . IR.solve $ votesList) ++ noVotesAsBallot voteless
|
let results' = (reverse . IR.solve $ votesList) ++ noVotesAsBallot voteless
|
||||||
fullPage $ do
|
fullPage $ do
|
||||||
h2_ "results"
|
h2_ "results"
|
||||||
|
toHtml $ T.append (T.pack . show . length . P.votes $ poll) " ballots submitted"
|
||||||
maybe "" (h3_ . toHtml) . P.title . P.createInfo $ poll
|
maybe "" (h3_ . toHtml) . P.title . P.createInfo $ poll
|
||||||
h3_ . toHtml . P.question . P.createInfo $ poll
|
h3_ . toHtml . P.question . P.createInfo $ poll
|
||||||
with div_ [id_ "results"] . mconcat $ zipWith nthPlaceFor results' nthPlaces
|
with div_ [id_ "results"] . mconcat $ zipWith nthPlaceFor results' nthPlaces
|
||||||
|
|
@ -105,7 +112,6 @@ vote pollId ballot = do
|
||||||
where
|
where
|
||||||
ballot' = B.Ballot . filter (== "") . B.options $ ballot
|
ballot' = B.Ballot . filter (== "") . B.options $ ballot
|
||||||
|
|
||||||
|
|
||||||
server :: ServerT A.RCVAPI AppM
|
server :: ServerT A.RCVAPI AppM
|
||||||
server = createPage
|
server = createPage
|
||||||
:<|> makePoll
|
:<|> makePoll
|
||||||
|
|
@ -114,12 +120,12 @@ server = createPage
|
||||||
:<|> getPollForBallot
|
:<|> getPollForBallot
|
||||||
:<|> vote
|
:<|> vote
|
||||||
:<|> results
|
:<|> results
|
||||||
|
:<|> indexPage
|
||||||
:<|> serveDirectoryWith (defaultWebAppSettings "public")
|
:<|> serveDirectoryWith (defaultWebAppSettings "public")
|
||||||
|
|
||||||
emptyHiddenInput :: L.Html ()
|
emptyHiddenInput :: L.Html ()
|
||||||
emptyHiddenInput = input_ [hidden_ "", name_ "options", value_ ""]
|
emptyHiddenInput = input_ [hidden_ "", name_ "options", value_ ""]
|
||||||
|
|
||||||
|
|
||||||
getPollForBallot :: P.PollId -> AppM (L.Html ())
|
getPollForBallot :: P.PollId -> AppM (L.Html ())
|
||||||
getPollForBallot pollId = do
|
getPollForBallot pollId = do
|
||||||
db <- Rd.asks db
|
db <- Rd.asks db
|
||||||
|
|
@ -137,6 +143,7 @@ getPollForBallot pollId = do
|
||||||
with div_ [classes_ ["draggable-options", "sortable", "options", "child-borders", "border-primary", "background-primary"]]
|
with div_ [classes_ ["draggable-options", "sortable", "options", "child-borders", "border-primary", "background-primary"]]
|
||||||
(emptyHiddenInput <> emptyHiddenInput)
|
(emptyHiddenInput <> emptyHiddenInput)
|
||||||
input_ [id_ "ballot-submit", type_ "submit", classes_ ["paper-btn", "btn-primary"], value_ "submit", form_ "drag-into-vote"]
|
input_ [id_ "ballot-submit", type_ "submit", classes_ ["paper-btn", "btn-primary"], value_ "submit", form_ "drag-into-vote"]
|
||||||
|
a_ [href_ (T.concat [T.pack . show . P.asWord $ pollId, "/results"])] "skip voting and see results"
|
||||||
where
|
where
|
||||||
toFormInput :: T.Text -> L.Html ()
|
toFormInput :: T.Text -> L.Html ()
|
||||||
toFormInput option = with div_ [classes_ []] $ input_ [type_ "hidden", value_ option, name_ "options"] <> toHtml option
|
toFormInput option = with div_ [classes_ []] $ input_ [type_ "hidden", value_ option, name_ "options"] <> toHtml option
|
||||||
|
|
@ -168,6 +175,9 @@ createPage = do
|
||||||
with button_ [hxGet_ "create/newInput", hxTarget_ "this", hxSwap_ "beforebegin"] "add option"
|
with button_ [hxGet_ "create/newInput", hxTarget_ "this", hxSwap_ "beforebegin"] "add option"
|
||||||
input_ [type_ "submit", classes_ ["paper-btn", "btn-primary"], value_ "submit"]
|
input_ [type_ "submit", classes_ ["paper-btn", "btn-primary"], value_ "submit"]
|
||||||
|
|
||||||
|
indexPage :: AppM (L.Html ())
|
||||||
|
indexPage = fullPage . toHtmlRaw =<< Rd.asks index
|
||||||
|
|
||||||
api :: Proxy A.RCVAPI
|
api :: Proxy A.RCVAPI
|
||||||
api = Proxy
|
api = Proxy
|
||||||
|
|
||||||
|
|
@ -176,13 +186,17 @@ getEnv = do
|
||||||
db <- DB.openLocalDB
|
db <- DB.openLocalDB
|
||||||
script <- TIO.readFile "public/static/script.js"
|
script <- TIO.readFile "public/static/script.js"
|
||||||
let gen = R.globalStdGen
|
let gen = R.globalStdGen
|
||||||
|
index <- convertMarkdown "public/static/index.md"
|
||||||
pure $ Env {..}
|
pure $ Env {..}
|
||||||
|
|
||||||
runWithEnv :: Env -> AppM a -> Handler a
|
runWithEnv :: Env -> AppM a -> Handler a
|
||||||
runWithEnv = flip Rd.runReaderT
|
runWithEnv = flip Rd.runReaderT
|
||||||
|
|
||||||
tlsSettings :: WTLS.TLSSettings
|
tlsSettings :: WTLS.TLSSettings
|
||||||
tlsSettings = WTLS.tlsSettings "/etc/letsencrypt/live/rankedchoice.net/cert.pem" "/etc/letsencrypt/live/rankedchoice.net/privkey.pem"
|
tlsSettings = WTLS.tlsSettingsChain
|
||||||
|
"/etc/letsencrypt/live/rankedchoice.net/cert.pem"
|
||||||
|
["/etc/letsencrypt/live/rankedchoice.net/fullchain.pem"]
|
||||||
|
"/etc/letsencrypt/live/rankedchoice.net/privkey.pem"
|
||||||
|
|
||||||
warpSettings :: W.Settings
|
warpSettings :: W.Settings
|
||||||
warpSettings = W.setPort 443 W.defaultSettings
|
warpSettings = W.setPort 443 W.defaultSettings
|
||||||
|
|
@ -194,12 +208,19 @@ examplePoll = P.CreatePollInfo {
|
||||||
options = "red" LN.:| ["blue", "green", "yellow", "orange", "pink", "purple", "grey", "black"]
|
options = "red" LN.:| ["blue", "green", "yellow", "orange", "pink", "purple", "grey", "black"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convertMarkdown :: FilePath -> IO T.Text
|
||||||
|
convertMarkdown path = do
|
||||||
|
file <- TIO.readFile path
|
||||||
|
case C.commonmark path file of
|
||||||
|
Left err -> liftIO . fail . show $ err
|
||||||
|
Right (rst :: C.Html ()) -> pure . TL.toStrict . C.renderHtml $ rst
|
||||||
|
|
||||||
|
emptyApp _ respond = respond $ NW.responseLBS TS.status200 [] "redirecting to https"
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
env <- getEnv
|
env <- getEnv
|
||||||
opts <- S.getArgs
|
opts <- S.getArgs
|
||||||
-- let gen = R.globalStdGen
|
|
||||||
-- pollId <- P.PollId <$> R.uniformWord64 gen
|
|
||||||
M.void . liftIO $ Ac.update (db env) (DB.CreatePoll examplePoll (P.PollId 7))
|
M.void . liftIO $ Ac.update (db env) (DB.CreatePoll examplePoll (P.PollId 7))
|
||||||
M.void . liftIO $ Ac.update (db env) (DB.PostBallot (P.PollId 7) (B.Ballot ["blue", "green", "yellow", "orange", "pink"]))
|
M.void . liftIO $ Ac.update (db env) (DB.PostBallot (P.PollId 7) (B.Ballot ["blue", "green", "yellow", "orange", "pink"]))
|
||||||
M.void . liftIO $ Ac.update (db env) (DB.PostBallot (P.PollId 7) (B.Ballot ["blue", "red", "pink", "purple", "green"]))
|
M.void . liftIO $ Ac.update (db env) (DB.PostBallot (P.PollId 7) (B.Ballot ["blue", "red", "pink", "purple", "green"]))
|
||||||
|
|
@ -207,5 +228,8 @@ main = do
|
||||||
print pollids
|
print pollids
|
||||||
let application = serve api . hoistServer api (runWithEnv env) $ server
|
let application = serve api . hoistServer api (runWithEnv env) $ server
|
||||||
case opts of
|
case opts of
|
||||||
["--with-tls"] -> WTLS.runTLS tlsSettings warpSettings application
|
["--with-tls"] -> do
|
||||||
|
httpsSite <- A.async $ WTLS.runTLS tlsSettings warpSettings application
|
||||||
|
httpSite <- A.async $ W.run 80 $ TLS.forceSSL emptyApp
|
||||||
|
M.void $ A.waitAny [httpsSite, httpSite]
|
||||||
_ -> W.run 8080 application
|
_ -> W.run 8080 application
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,10 @@ import qualified Data.Bifunctor as Bi
|
||||||
maximumTextLength :: Int
|
maximumTextLength :: Int
|
||||||
maximumTextLength = 280
|
maximumTextLength = 280
|
||||||
|
|
||||||
newtype PollId = PollId Word64 deriving (Show, Eq, Ord, Generic, FromJSON, ToJSON, DS.NFData)
|
newtype PollId = PollId
|
||||||
|
{
|
||||||
|
asWord :: Word64
|
||||||
|
} deriving (Show, Eq, Ord, Generic, FromJSON, ToJSON, DS.NFData)
|
||||||
|
|
||||||
instance FromHttpApiData PollId where
|
instance FromHttpApiData PollId where
|
||||||
parseUrlPiece = Bi.second PollId . parseUrlPiece
|
parseUrlPiece = Bi.second PollId . parseUrlPiece
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue