Files
acms/backend/app/Main.hs

241 lines
7.4 KiB
Haskell
Raw Normal View History

2024-05-28 22:04:34 +02:00
module Main where
import AutoTypes qualified as U
import AutoTypes.Unify qualified as U
import Control.Applicative ((<**>))
2024-06-05 18:02:33 +02:00
import Control.Concurrent
import Control.Concurrent.STM
2024-05-28 22:04:34 +02:00
import Control.Monad
import Control.Monad.Trans (liftIO)
import Data.Aeson qualified as J
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.UTF8 qualified as B
import Data.List
import Data.Map qualified as M
2024-06-05 22:47:49 +02:00
import Data.Map.Merge.Strict qualified as M
import Data.Maybe
2024-05-28 22:04:34 +02:00
import Data.String (IsString (fromString))
2024-06-05 18:02:33 +02:00
import Data.Tagged (Tagged (..), untag)
2024-05-28 22:04:34 +02:00
import Debug.Trace
import Git qualified as G
import Git.Libgit2 qualified as GB
import Network.HTTP.Types.Method qualified as W
import Network.HTTP.Types.Status qualified as W
import Network.Wai qualified as W
import Network.Wai.Handler.Warp qualified as W
import Options.Applicative qualified as A
2024-06-05 22:47:49 +02:00
import Safe
2024-06-03 11:22:10 +02:00
import Store qualified as Q
2024-05-28 22:04:34 +02:00
import System.Directory (setCurrentDirectory)
import System.FilePath
2024-06-05 18:02:33 +02:00
import System.INotify
2024-05-28 22:04:34 +02:00
import Text.Printf (printf)
2024-06-05 22:47:49 +02:00
import Version
2024-05-28 22:04:34 +02:00
data Args = Args
{ cmd :: Cmd
}
args :: A.Parser Args
args = Args <$> cmd'
data Cmd = Serve
cmd' :: A.Parser Cmd
cmd' =
A.hsubparser . mconcat $
[ A.command "serve" . A.info serveCmd $
A.progDesc "Run webserver"
]
serveCmd :: A.Parser Cmd
serveCmd = pure Serve
data Repo = Repo
{ commits :: [Commit]
}
deriving (Show)
data Commit = Commit
{ id :: G.CommitOid GB.LgRepo,
2024-06-05 22:47:49 +02:00
collections :: [Collection],
schemaVersion :: Version
2024-05-28 22:04:34 +02:00
}
deriving (Show)
data Collection = Collection
{ path :: FilePath,
files :: [FilePath],
2024-06-06 09:25:05 +02:00
schema :: U.T
2024-05-28 22:04:34 +02:00
}
deriving (Show)
data Schema = Schema {unSchema :: J.Value}
deriving (Show)
instance J.ToJSON Schema where
toJSON = J.toJSON . (.unSchema)
fromAutoTypes :: String -> U.T -> Schema
fromAutoTypes path (U.Object ps) =
Schema $
J.object
[ ("$schema", J.toJSON @String "https://json-schema.org/draft/2020-12/schema"),
("$id", J.toJSON @String (path <> ".schema.json")),
("title", J.toJSON @String path),
("type", J.toJSON @String "object"),
("properties", J.toJSON (M.mapWithKey toProperty ps))
]
where
toProperty k (U.Scalar "string") = "string" :: String
2024-06-05 18:02:33 +02:00
watch :: TMVar Repo -> FilePath -> G.RefName -> IO ()
watch repoT root ref = do
i <- initINotify
qT <- newTQueueIO
wd <-
addWatch i [MoveIn] ".git/refs/heads" $ \e ->
atomically (writeTQueue qT e)
forever do
repo <- initRepo root ref
atomically do putTMVar repoT repo
_ <- atomically do
let loop = maybe (pure ()) (const loop) =<< tryReadTQueue qT
readTQueue qT >> loop
_ <- atomically do takeTMVar repoT
pure ()
initRepo :: FilePath -> G.RefName -> IO Repo
initRepo root ref = do
2024-05-28 22:04:34 +02:00
repo <- G.openRepository GB.lgFactory G.defaultRepositoryOptions {G.repoPath = root}
2024-06-05 18:02:33 +02:00
G.runRepository GB.lgFactory repo do
2024-05-28 22:04:34 +02:00
Just cid <- fmap Tagged <$> G.resolveReference ref
c <- G.lookupCommit cid
cs <- mapM G.lookupCommit =<< G.listCommits Nothing cid
2024-06-05 22:47:49 +02:00
fmap (Repo . reverse) $
foldM
( \cs c -> do
let cid = G.commitOid c
fs <-
fmap (filter ((== ".json") . takeExtension)) . liftIO $
Q.withStore root ref do
Q.withCommit cid (Q.listFiles "/")
let cls =
M.toList . M.unionsWith (++) $
map (\f -> M.singleton (takeDirectory f) [f]) fs
colls <- forM cls $ \(path, (file : files)) -> do
(value : values) <- do
liftIO $ Q.withStore root ref do
mapM (Q.withCommit cid . Q.readFile) (file : files)
2024-06-06 09:25:05 +02:00
let schema = U.autoTypes' value values
2024-06-05 22:47:49 +02:00
pure $ Collection path files schema
let schemaVersion =
2024-06-06 09:25:05 +02:00
case headMay cs of
2024-06-05 22:47:49 +02:00
Nothing -> Version 1 0 0
Just c' ->
let Version major' minor' patch' = c'.schemaVersion
schemas' =
M.fromList
( (\coll -> (coll.path, coll.schema))
<$> c'.collections
)
schemas =
M.fromList
( (\coll -> (coll.path, coll.schema))
<$> c.collections
)
in case compareSchemas schemas' schemas of
Just Major -> Version (major' + 1) 0 0
Just Minor -> Version major' (minor' + 1) 0
Just Patch -> Version major' minor' (patch' + 1)
Nothing -> Version major' minor' patch'
c = Commit cid colls schemaVersion
pure (c : cs)
)
[]
cs
compareSchemas ::
2024-06-06 09:25:05 +02:00
M.Map String U.T ->
M.Map String U.T ->
2024-06-05 22:47:49 +02:00
Maybe SchemaDifference
compareSchemas schemas' schemas =
maximumMay
. catMaybes
. M.elems
. M.map (uncurry compareSchemas')
$ M.merge
(M.mapMissing (\_ schema' -> (Just schema', Nothing)))
(M.mapMissing (\_ schema -> (Nothing, Just schema)))
(M.zipWithMatched (\_ schema' schema -> (Just schema', Just schema)))
schemas'
schemas
where
2024-06-06 09:25:05 +02:00
compareSchemas' Nothing Nothing = Nothing
compareSchemas' Nothing (Just _) = Just Minor
compareSchemas' (Just _) Nothing = Just Major
2024-06-05 22:47:49 +02:00
compareSchemas' (Just schema') (Just schema) = compareSchema schema' schema
2024-06-06 09:25:05 +02:00
compareSchema :: U.T -> U.T -> Maybe SchemaDifference
compareSchema (U.Object kts') (U.Object kts) = compareSchemas kts' kts
compareSchema t' t
| t' == t = Nothing
| t' `elem` (U.unify1 t' t) = Just Patch
| t `elem` U.unify1 t' t = Just Minor
| otherwise = Just Major
2024-06-05 22:47:49 +02:00
data SchemaDifference
= Major
| Minor
| Patch
2024-06-06 09:25:05 +02:00
deriving (Show, Eq, Ord)
2024-06-05 18:02:33 +02:00
main :: IO ()
main = do
setCurrentDirectory "../blog"
let root = "."
ref = "refs/heads/master"
repoT <- newEmptyTMVarIO
_ <- forkIO do watch repoT root ref
2024-05-28 22:04:34 +02:00
A.execParser (A.info (args <**> A.helper) A.idm) >>= \case
Args {cmd = Serve} -> do
2024-05-31 10:42:26 +02:00
W.runEnv 8081 $ \req respond -> do
2024-05-28 22:04:34 +02:00
case P.parseOnly routeP (W.rawPathInfo req) of
Right (SchemaJson path) -> do
2024-06-05 18:02:33 +02:00
repo <- atomically (readTMVar repoT)
let [c] = filter ((== path) . (.path)) (last repo.commits).collections
2024-06-06 09:25:05 +02:00
respond . W.responseLBS W.status200 [] $
J.encode (fromAutoTypes path c.schema)
2024-06-03 11:22:10 +02:00
Right Query -> do
q <-
fromString @Q.Query . LB.toString
<$> W.lazyRequestBody req
2024-06-05 22:47:49 +02:00
r <- liftIO $ Q.withStore root ref do Q.query q
2024-06-03 11:22:10 +02:00
respond . W.responseLBS W.status200 [] $ J.encode r
2024-06-05 22:47:49 +02:00
Right SchemaVersion -> do
repo <- atomically (readTMVar repoT)
respond $
W.responseLBS W.status200 [] $
J.encode (last repo.commits).schemaVersion
(traceShowId -> !_) ->
2024-06-03 11:22:10 +02:00
respond $ W.responseLBS W.status200 [] "not implemented"
2024-05-28 22:04:34 +02:00
data Route
= SchemaJson String
2024-06-03 11:22:10 +02:00
| Query
2024-06-05 22:47:49 +02:00
| SchemaVersion
2024-05-28 22:04:34 +02:00
deriving (Show)
routeP :: P.Parser Route
routeP =
2024-06-03 11:22:10 +02:00
( P.choice
[ SchemaJson <$> (P.string "/" *> P.manyTill P.anyChar (P.string ".schema.json")),
2024-06-05 22:47:49 +02:00
pure SchemaVersion <* P.string "/schemaVersion",
pure Query <* P.string "/"
2024-06-03 11:22:10 +02:00
]
2024-05-28 22:04:34 +02:00
)
<* P.endOfInput