Загрузить файлы в «/»
This commit is contained in:
parent
de4bf06883
commit
21c7bc53cd
5 changed files with 227 additions and 0 deletions
166
App.hs
Normal file
166
App.hs
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
{-# LANGUAGE DataKinds #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE TypeOperators #-}
|
||||||
|
|
||||||
|
module Main where
|
||||||
|
|
||||||
|
import Servant ( PlainText
|
||||||
|
, JSON
|
||||||
|
, QueryParam'
|
||||||
|
, QueryFlag
|
||||||
|
, Required
|
||||||
|
, Headers
|
||||||
|
, StdMethod(GET)
|
||||||
|
, Verb
|
||||||
|
, Header
|
||||||
|
, NoContent(..)
|
||||||
|
, err302
|
||||||
|
, err303
|
||||||
|
, errHeaders
|
||||||
|
, serveDirectoryWebApp
|
||||||
|
|
||||||
|
, Get
|
||||||
|
, Post
|
||||||
|
, ReqBody
|
||||||
|
, Proxy(..)
|
||||||
|
, Raw
|
||||||
|
, Accept(..)
|
||||||
|
, addHeader
|
||||||
|
, throwError
|
||||||
|
, type (:>)
|
||||||
|
, type (:<|>)
|
||||||
|
, err404
|
||||||
|
, (:<|>)(..)
|
||||||
|
)
|
||||||
|
import qualified Data.Text.IO as TIO
|
||||||
|
import qualified Data.Map.Strict as M
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import Servant.Server (Handler, Server, Application, serve)
|
||||||
|
import Network.Wai.Handler.Warp (run)
|
||||||
|
import Servant.HTML.Lucid (HTML(..))
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import Lucid.Base
|
||||||
|
import Lucid.Html5
|
||||||
|
import Data.Maybe (mapMaybe)
|
||||||
|
import Data.Time (getCurrentTime)
|
||||||
|
import Control.Monad.IO.Class (liftIO)
|
||||||
|
import qualified Data.ByteString.Char8 as BS
|
||||||
|
import IndexHtml (index)
|
||||||
|
import StudentHtml (studentPage)
|
||||||
|
import Common (Students, Key)
|
||||||
|
import Control.Exception (catch)
|
||||||
|
|
||||||
|
type Redirect =
|
||||||
|
Headers '[Header "Location" T.Text] NoContent
|
||||||
|
|
||||||
|
type Redirect302 =
|
||||||
|
Headers '[Header "Location" T.Text] NoContent
|
||||||
|
|
||||||
|
type API =
|
||||||
|
Verb 'GET 302 '[PlainText] Redirect
|
||||||
|
:<|> "student" :> QueryParam' '[Required] "student_id" T.Text :> Get '[HTML] (Html ())
|
||||||
|
:<|> "ban" :> QueryParam' '[Required] "student_id" T.Text :> Get '[HTML] NoContent
|
||||||
|
:<|> "accept" :> QueryParam' '[Required] "student_id" T.Text
|
||||||
|
:> QueryParam' '[Required] "homework" T.Text
|
||||||
|
:> QueryFlag "task_1"
|
||||||
|
:> QueryFlag "task_2"
|
||||||
|
:> QueryFlag "task_3"
|
||||||
|
:> QueryFlag "task_4"
|
||||||
|
:> QueryFlag "task_5"
|
||||||
|
:> QueryFlag "task_6"
|
||||||
|
:> Get '[HTML] NoContent
|
||||||
|
:<|> "index.html" :> Get '[HTML] (Html ())
|
||||||
|
:<|> "static" :> Raw
|
||||||
|
|
||||||
|
appendCsvLine :: FilePath -> [T.Text] -> IO ()
|
||||||
|
appendCsvLine fp cols =
|
||||||
|
TIO.appendFile fp (T.intercalate "," (map csvEscape cols) <> "\n")
|
||||||
|
where
|
||||||
|
csvEscape t
|
||||||
|
| T.any (`elem` [',','"','\n','\r']) t = "\"" <> T.replace "\"" "\"\"" t <> "\""
|
||||||
|
| otherwise = t
|
||||||
|
|
||||||
|
|
||||||
|
pairs :: [String] -> Students
|
||||||
|
pairs = mapMaybe $ \s ->
|
||||||
|
case split s of
|
||||||
|
(w1:w2:idTok:_) -> Just (idTok, (w1 <> " " <> w2))
|
||||||
|
_ -> Nothing
|
||||||
|
where split = T.splitOn "," . T.pack
|
||||||
|
|
||||||
|
api = (Proxy :: Proxy API)
|
||||||
|
|
||||||
|
redirectTo :: T.Text -> Handler a
|
||||||
|
redirectTo url =
|
||||||
|
throwError $ err303
|
||||||
|
{ errHeaders = [("Location", BS.pack (T.unpack url))] }
|
||||||
|
|
||||||
|
rootRedirect :: Handler NoContent
|
||||||
|
rootRedirect = redirectTo "/index.html"
|
||||||
|
|
||||||
|
handlerBan :: Students -> Key -> Handler NoContent
|
||||||
|
handlerBan them sid = do
|
||||||
|
case lookup sid them of
|
||||||
|
Nothing -> throwError err404
|
||||||
|
Just _ -> pure ()
|
||||||
|
now <- liftIO getCurrentTime
|
||||||
|
liftIO $ appendCsvLine "ban.csv" [T.pack (show now), sid]
|
||||||
|
rootRedirect
|
||||||
|
|
||||||
|
handlerAccept
|
||||||
|
:: Students -> Key -> T.Text
|
||||||
|
-> Bool -> Bool -> Bool -> Bool -> Bool -> Bool
|
||||||
|
-> Handler NoContent
|
||||||
|
handlerAccept them sid homework t1 t2 t3 t4 t5 t6 = do
|
||||||
|
case lookup sid them of
|
||||||
|
Nothing -> throwError err404
|
||||||
|
Just _ -> pure ()
|
||||||
|
let tasks =
|
||||||
|
mapMaybe (\(n, ok) -> if ok then Just n else Nothing)
|
||||||
|
[("1", t1),("2", t2),("3", t3),("4", t4),("5", t5),("6", t6)]
|
||||||
|
now <- liftIO getCurrentTime
|
||||||
|
liftIO $ appendCsvLine "accept.csv"
|
||||||
|
[ T.pack (show now), sid, homework, T.intercalate ";" tasks ]
|
||||||
|
rootRedirect
|
||||||
|
|
||||||
|
server :: Students -> Server API
|
||||||
|
server students = rootRedirect
|
||||||
|
:<|> handlerStudent
|
||||||
|
:<|> handlerBan students
|
||||||
|
:<|> handlerAccept students
|
||||||
|
:<|> handlerIndex
|
||||||
|
:<|> serveDirectoryWebApp "static"
|
||||||
|
where
|
||||||
|
rootRedirect :: Handler Redirect
|
||||||
|
rootRedirect = return $ addHeader "/index.html" NoContent
|
||||||
|
|
||||||
|
handlerIndex = return $ index students
|
||||||
|
|
||||||
|
handlerStudent sid = do
|
||||||
|
bans <- liftIO $ loadBanCounts "ban.csv"
|
||||||
|
let banCount = M.findWithDefault 0 sid bans
|
||||||
|
case lookup sid students of
|
||||||
|
Nothing -> throwError err404
|
||||||
|
Just _ -> return $ studentPage students sid banCount
|
||||||
|
|
||||||
|
|
||||||
|
type BanCounts = M.Map Key Int
|
||||||
|
|
||||||
|
loadBanCounts :: FilePath -> IO BanCounts
|
||||||
|
loadBanCounts fp = do
|
||||||
|
txt <- TIO.readFile fp `catch` \(_ :: IOError) -> pure ""
|
||||||
|
let sids = mapMaybe sidFromLine (T.lines txt)
|
||||||
|
pure $ M.fromListWith (+) [(sid, 1) | sid <- sids]
|
||||||
|
where
|
||||||
|
sidFromLine :: T.Text -> Maybe Key
|
||||||
|
sidFromLine line =
|
||||||
|
case T.splitOn "," line of
|
||||||
|
(_ts : sid : _) -> Just sid
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
students <- pairs . lines <$> readFile "groups.csv"
|
||||||
|
run 4000 $ serve (Proxy :: Proxy API) (server students)
|
||||||
26
Common.hs
Normal file
26
Common.hs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module Common where
|
||||||
|
import Lucid.Base
|
||||||
|
import Lucid.Html5
|
||||||
|
import qualified Data.Text as T
|
||||||
|
type Name = T.Text
|
||||||
|
type Key = T.Text
|
||||||
|
type Students = [(Key, Name)]
|
||||||
|
|
||||||
|
layout :: Html() -> Html ()
|
||||||
|
layout bodyContent =
|
||||||
|
doctypehtml_ $ do
|
||||||
|
head_ $ do
|
||||||
|
link_ [ rel_ "stylesheet", href_ "https://unpkg.com/milligram/dist/milligram.min.css" ]
|
||||||
|
style_ $ T.unlines
|
||||||
|
[ "form { text-align:center; width: 70%; margin: 0 auto; }"
|
||||||
|
, "h1 { text-align: center; }"
|
||||||
|
, "h2 { text-align: center; }"
|
||||||
|
, "h3 { text-align: center; }"
|
||||||
|
, "p { text-align: center; }"
|
||||||
|
, "label { display: inline-flex; align-items: center; gap: 6px;}"
|
||||||
|
]
|
||||||
|
title_ "Каждому по заслугам"
|
||||||
|
meta_ [charset_ "utf-8"]
|
||||||
|
meta_ [name_ "viewport", content_ "width=device-width, initial-scale=1"]
|
||||||
|
body_ bodyContent
|
||||||
18
IndexHtml.hs
Normal file
18
IndexHtml.hs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module IndexHtml where
|
||||||
|
import Lucid.Base
|
||||||
|
import Lucid.Html5
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import Common (Students, layout)
|
||||||
|
|
||||||
|
index :: Students -> (Html())
|
||||||
|
index students = layout $ do
|
||||||
|
h1_ "Каждому по заслугам"
|
||||||
|
h2_ "Сервис записи работ студентов"
|
||||||
|
h3_ "Выберите студента"
|
||||||
|
form_ [method_ "GET", action_ "./student" ]$ do
|
||||||
|
input_ [type_ "search", name_ "student_id", list_ listId]
|
||||||
|
datalist_ [id_ listId] $ do
|
||||||
|
mapM_ (\x -> option_ [value_ $ fst x] (toHtml $ snd x)) students
|
||||||
|
input_ [type_ "submit"]
|
||||||
|
where listId = "students"
|
||||||
1
groups.csv
Normal file
1
groups.csv
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Иван,Иванович,Иванов,99И999
|
||||||
|
16
shell.nix
Normal file
16
shell.nix
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
|
||||||
|
pkgs.mkShell {
|
||||||
|
buildInputs = [
|
||||||
|
(pkgs.haskellPackages.ghcWithPackages (p: with p; [
|
||||||
|
servant
|
||||||
|
wai
|
||||||
|
warp
|
||||||
|
servant-server
|
||||||
|
lucid
|
||||||
|
servant-lucid
|
||||||
|
haskell-language-server
|
||||||
|
]))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue