add collections
This commit is contained in:
@@ -9,30 +9,26 @@ import Control.Monad
|
|||||||
import Control.Monad.Trans (liftIO)
|
import Control.Monad.Trans (liftIO)
|
||||||
import Data.Aeson qualified as J
|
import Data.Aeson qualified as J
|
||||||
import Data.Attoparsec.Char8 as P
|
import Data.Attoparsec.Char8 as P
|
||||||
import Data.ByteString.Char8 qualified as B
|
|
||||||
import Data.ByteString.Lazy.Char8 qualified as LB
|
|
||||||
import Data.ByteString.Lazy.UTF8 qualified as LB
|
import Data.ByteString.Lazy.UTF8 qualified as LB
|
||||||
import Data.ByteString.UTF8 qualified as B
|
|
||||||
import Data.List
|
|
||||||
import Data.Map qualified as M
|
import Data.Map qualified as M
|
||||||
import Data.Map.Merge.Strict qualified as M
|
import Data.Map.Merge.Strict qualified as M
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import Data.String (IsString (fromString))
|
import Data.String (IsString (fromString))
|
||||||
import Data.Tagged (Tagged (..), untag)
|
import Data.Tagged (Tagged (..))
|
||||||
import Debug.Trace
|
import Debug.Trace
|
||||||
import Git qualified as G
|
import Git qualified as G
|
||||||
import Git.Libgit2 qualified as GB
|
import Git.Libgit2 qualified as GB
|
||||||
import Network.HTTP.Types.Method qualified as W
|
import Network.HTTP.Types
|
||||||
import Network.HTTP.Types.Status qualified as W
|
import Network.HTTP.Types.Status qualified as W
|
||||||
import Network.Wai qualified as W
|
import Network.Wai qualified as W
|
||||||
import Network.Wai.Handler.Warp qualified as W
|
import Network.Wai.Handler.Warp qualified as W
|
||||||
import Options.Applicative qualified as A
|
import Options.Applicative qualified as A
|
||||||
|
import Route qualified as R
|
||||||
import Safe
|
import Safe
|
||||||
import Store qualified as Q
|
import Store qualified as Q
|
||||||
import System.Directory (setCurrentDirectory)
|
import System.Directory (setCurrentDirectory)
|
||||||
import System.FilePath
|
import System.FilePath
|
||||||
import System.INotify
|
import System.INotify
|
||||||
import Text.Printf (printf)
|
|
||||||
import Version
|
import Version
|
||||||
|
|
||||||
data Args = Args
|
data Args = Args
|
||||||
@@ -87,17 +83,17 @@ fromAutoTypes path (U.Object ps) =
|
|||||||
("$id", J.toJSON @String (path <> ".schema.json")),
|
("$id", J.toJSON @String (path <> ".schema.json")),
|
||||||
("title", J.toJSON @String path),
|
("title", J.toJSON @String path),
|
||||||
("type", J.toJSON @String "object"),
|
("type", J.toJSON @String "object"),
|
||||||
("properties", J.toJSON (M.mapWithKey toProperty ps))
|
("properties", J.toJSON (M.map toProperty ps))
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
toProperty k (U.Scalar "string") = "string" :: String
|
toProperty (U.Scalar "string") = "string" :: String
|
||||||
toProperty k (U.Option (Just (U.Scalar "string"))) = "string?" :: String
|
toProperty (U.Option (Just (U.Scalar "string"))) = "string?" :: String
|
||||||
|
|
||||||
watch :: TMVar Repo -> FilePath -> G.RefName -> IO ()
|
watch :: TMVar Repo -> FilePath -> G.RefName -> IO ()
|
||||||
watch repoT root ref = do
|
watch repoT root ref = do
|
||||||
i <- initINotify
|
i <- initINotify
|
||||||
qT <- newTQueueIO
|
qT <- newTQueueIO
|
||||||
wd <-
|
_ <-
|
||||||
addWatch i [MoveIn] ".git/refs/heads" $ \e ->
|
addWatch i [MoveIn] ".git/refs/heads" $ \e ->
|
||||||
atomically (writeTQueue qT e)
|
atomically (writeTQueue qT e)
|
||||||
forever do
|
forever do
|
||||||
@@ -114,15 +110,12 @@ initRepo root ref = do
|
|||||||
repo <- G.openRepository GB.lgFactory G.defaultRepositoryOptions {G.repoPath = root}
|
repo <- G.openRepository GB.lgFactory G.defaultRepositoryOptions {G.repoPath = root}
|
||||||
G.runRepository GB.lgFactory repo do
|
G.runRepository GB.lgFactory repo do
|
||||||
Just cid <- fmap Tagged <$> G.resolveReference ref
|
Just cid <- fmap Tagged <$> G.resolveReference ref
|
||||||
c <- G.lookupCommit cid
|
|
||||||
cs <- mapM G.lookupCommit =<< G.listCommits Nothing cid
|
cs <- mapM G.lookupCommit =<< G.listCommits Nothing cid
|
||||||
fmap (Repo . reverse) $
|
fmap (Repo . reverse) $
|
||||||
foldM
|
foldM
|
||||||
( \cs c -> do
|
( \cs c -> do
|
||||||
let cid = G.commitOid c
|
let cid = G.commitOid c
|
||||||
fs <-
|
fs <- liftIO $ Q.withStore root ref do
|
||||||
fmap (filter ((== ".json") . takeExtension)) . liftIO $
|
|
||||||
Q.withStore root ref do
|
|
||||||
Q.withCommit cid (Q.listFiles "/")
|
Q.withCommit cid (Q.listFiles "/")
|
||||||
let cls =
|
let cls =
|
||||||
M.toList . M.unionsWith (++) $
|
M.toList . M.unionsWith (++) $
|
||||||
@@ -204,45 +197,35 @@ main = do
|
|||||||
A.execParser (A.info (args <**> A.helper) A.idm) >>= \case
|
A.execParser (A.info (args <**> A.helper) A.idm) >>= \case
|
||||||
Args {cmd = Serve} -> do
|
Args {cmd = Serve} -> do
|
||||||
W.runEnv 8081 $ \req respond -> do
|
W.runEnv 8081 $ \req respond -> do
|
||||||
case P.parseOnly routeP (W.rawPathInfo req) of
|
case P.parseOnly R.parser (W.rawPathInfo req) of
|
||||||
Right (SchemaJson path) -> do
|
Right (R.SchemaJson path) -> do
|
||||||
repo <- atomically (readTMVar repoT)
|
repo <- atomically (readTMVar repoT)
|
||||||
let [c] = filter ((== path) . (.path)) (last repo.commits).collections
|
let [c] = filter ((== path) . (.path)) (last repo.commits).collections
|
||||||
respond . W.responseLBS W.status200 [] $
|
respond . W.responseLBS W.status200 [] $
|
||||||
J.encode (fromAutoTypes path c.schema)
|
J.encode (fromAutoTypes path c.schema)
|
||||||
Right Query -> do
|
Right R.Query -> do
|
||||||
q <-
|
q <-
|
||||||
fromString @Q.Query . LB.toString
|
fromString @Q.Query . LB.toString
|
||||||
<$> W.lazyRequestBody req
|
<$> W.lazyRequestBody req
|
||||||
r <- liftIO $ Q.withStore root ref do Q.query q
|
r <- liftIO $ Q.withStore root ref do Q.query q
|
||||||
respond . W.responseLBS W.status200 [] $ J.encode r
|
respond . W.responseLBS W.status200 [] $ J.encode r
|
||||||
Right SchemaVersion -> do
|
Right R.SchemaVersion -> do
|
||||||
repo <- atomically (readTMVar repoT)
|
repo <- atomically (readTMVar repoT)
|
||||||
respond $
|
respond $
|
||||||
W.responseLBS W.status200 [] $
|
W.responseLBS W.status200 [] $
|
||||||
J.encode (last repo.commits).schemaVersion
|
J.encode (last repo.commits).schemaVersion
|
||||||
Right ListCollections -> do
|
Right R.Collections -> do
|
||||||
|
if
|
||||||
|
| W.requestMethod req == "POST" -> do
|
||||||
|
Right collection <- J.eitherDecode <$> W.lazyRequestBody req
|
||||||
|
Q.withStore root ref do
|
||||||
|
Q.writeFile (collection </> ".gitkeep") ""
|
||||||
|
Q.commit
|
||||||
|
respond $ W.responseLBS W.status200 [] "{}"
|
||||||
|
| W.requestMethod req == "GET" -> do
|
||||||
repo <- atomically (readTMVar repoT)
|
repo <- atomically (readTMVar repoT)
|
||||||
respond $
|
respond $
|
||||||
W.responseLBS W.status200 [] $
|
W.responseLBS W.status200 [] $
|
||||||
J.encode (map (.path) (last repo.commits).collections)
|
J.encode (map (.path) (last repo.commits).collections)
|
||||||
(traceShowId -> !_) ->
|
(traceShowId -> !_) ->
|
||||||
respond $ W.responseLBS W.status200 [] "not implemented"
|
respond $ W.responseLBS W.status200 [] "not implemented"
|
||||||
|
|
||||||
data Route
|
|
||||||
= SchemaJson String
|
|
||||||
| Query
|
|
||||||
| SchemaVersion
|
|
||||||
| ListCollections
|
|
||||||
deriving (Show)
|
|
||||||
|
|
||||||
routeP :: P.Parser Route
|
|
||||||
routeP =
|
|
||||||
( P.choice
|
|
||||||
[ pure ListCollections <* P.string "/collections",
|
|
||||||
pure SchemaVersion <* P.string "/schemaVersion",
|
|
||||||
SchemaJson <$> (P.string "/" *> P.manyTill P.anyChar (P.string ".schema.json")),
|
|
||||||
pure Query <* P.string "/"
|
|
||||||
]
|
|
||||||
)
|
|
||||||
<* P.endOfInput
|
|
||||||
|
|||||||
21
backend/app/Route.hs
Normal file
21
backend/app/Route.hs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
module Route (Route (..), parser) where
|
||||||
|
|
||||||
|
import Data.Attoparsec.Char8 qualified as P
|
||||||
|
|
||||||
|
data Route
|
||||||
|
= SchemaJson String
|
||||||
|
| Query
|
||||||
|
| SchemaVersion
|
||||||
|
| Collections
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
parser :: P.Parser Route
|
||||||
|
parser =
|
||||||
|
( P.choice
|
||||||
|
[ pure Collections <* P.string "/collections",
|
||||||
|
pure SchemaVersion <* P.string "/schemaVersion",
|
||||||
|
SchemaJson <$> (P.string "/" *> P.manyTill P.anyChar (P.string ".schema.json")),
|
||||||
|
pure Query <* P.string "/"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
<* P.endOfInput
|
||||||
@@ -10,10 +10,11 @@ build-type: Simple
|
|||||||
executable backend
|
executable backend
|
||||||
main-is: Main.hs
|
main-is: Main.hs
|
||||||
hs-source-dirs: app
|
hs-source-dirs: app
|
||||||
|
other-modules: Route
|
||||||
default-language: GHC2021
|
default-language: GHC2021
|
||||||
default-extensions:
|
default-extensions:
|
||||||
BlockArguments LambdaCase OverloadedStrings ViewPatterns
|
BlockArguments LambdaCase OverloadedStrings ViewPatterns
|
||||||
OverloadedRecordDot NoFieldSelectors
|
OverloadedRecordDot NoFieldSelectors MultiWayIf
|
||||||
|
|
||||||
ghc-options: -Wall -threaded
|
ghc-options: -Wall -threaded
|
||||||
build-depends:
|
build-depends:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
module Api
|
module Api
|
||||||
( fetchCollections,
|
( fetchCollections,
|
||||||
|
createCollection,
|
||||||
fetchSchema,
|
fetchSchema,
|
||||||
fetchSchemaVersion,
|
fetchSchemaVersion,
|
||||||
fetchPosts,
|
fetchPosts,
|
||||||
@@ -33,6 +34,15 @@ fetchCollections :: JSM (Either String [String])
|
|||||||
fetchCollections =
|
fetchCollections =
|
||||||
A.eitherDecode <$> fetch (fromString "http://localhost:8081/collections")
|
A.eitherDecode <$> fetch (fromString "http://localhost:8081/collections")
|
||||||
|
|
||||||
|
createCollection :: String -> JSM (Either String ())
|
||||||
|
createCollection collection =
|
||||||
|
A.eitherDecode
|
||||||
|
<$> fetch
|
||||||
|
( fromString "http://localhost:8081/collections"
|
||||||
|
& setRequestMethod "POST"
|
||||||
|
& setRequestBodyLBS (A.encode (A.toJSON collection))
|
||||||
|
)
|
||||||
|
|
||||||
fetchSchemaVersion :: JSM (Either String Version)
|
fetchSchemaVersion :: JSM (Either String Version)
|
||||||
fetchSchemaVersion =
|
fetchSchemaVersion =
|
||||||
A.eitherDecode <$> fetch (fromString "http://localhost:8081/schemaVersion")
|
A.eitherDecode <$> fetch (fromString "http://localhost:8081/schemaVersion")
|
||||||
|
|||||||
@@ -7,9 +7,12 @@ module Page.NewCollection
|
|||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
import Api
|
||||||
|
import Data.Aeson qualified as A
|
||||||
import Data.Text qualified as T
|
import Data.Text qualified as T
|
||||||
import Form qualified as F
|
import Form qualified as F
|
||||||
import Miso
|
import Miso
|
||||||
|
import Miso.String (toMisoString)
|
||||||
|
|
||||||
data Model = Model
|
data Model = Model
|
||||||
{ input :: T.Text
|
{ input :: T.Text
|
||||||
@@ -24,19 +27,29 @@ data Action
|
|||||||
= NoOp
|
= NoOp
|
||||||
| FormChanged T.Text
|
| FormChanged T.Text
|
||||||
| FormSubmitted T.Text
|
| FormSubmitted T.Text
|
||||||
|
| CollectionCreated (Either String ())
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
updateModel :: Action -> Model -> Effect Action Model
|
updateModel :: Action -> Model -> Effect Action Model
|
||||||
updateModel NoOp m = noEff m
|
updateModel NoOp m = noEff m
|
||||||
updateModel (FormChanged input) m = noEff m {input}
|
updateModel (FormChanged input) m = noEff m {input}
|
||||||
updateModel (FormSubmitted _) m = noEff m
|
updateModel (FormSubmitted collection) m =
|
||||||
|
m <# do
|
||||||
|
CollectionCreated <$> createCollection (T.unpack collection)
|
||||||
|
updateModel (CollectionCreated (Left err)) m =
|
||||||
|
m <# do
|
||||||
|
pure NoOp <* consoleLog (toMisoString err)
|
||||||
|
-- TODO reload collections in main app
|
||||||
|
updateModel (CollectionCreated (Right _)) m = noEff m
|
||||||
|
|
||||||
viewModel :: Model -> View Action
|
viewModel :: Model -> View Action
|
||||||
viewModel m = do
|
viewModel m = do
|
||||||
div_ [] $
|
div_ [] $
|
||||||
[ h3_ [] [text "new collection"],
|
[ h3_ [] [text "new collection"],
|
||||||
either FormChanged FormSubmitted
|
either FormChanged FormSubmitted
|
||||||
<$> F.runForm collectionForm m.input
|
<$> F.runForm collectionForm m.input,
|
||||||
|
pre_ [] [text (toMisoString (A.encode m.input))],
|
||||||
|
pre_ [] [text (toMisoString (A.encode (collectionForm.fill m.input)))]
|
||||||
]
|
]
|
||||||
|
|
||||||
collectionForm :: F.Form T.Text T.Text
|
collectionForm :: F.Form T.Text T.Text
|
||||||
|
|||||||
Reference in New Issue
Block a user