2024-05-31 10:42:26 +02:00
|
|
|
module Main where
|
|
|
|
|
|
|
|
|
|
#ifndef ghcjs_HOST_OS
|
2024-06-04 00:26:31 +02:00
|
|
|
import Data.Maybe
|
2024-06-03 11:22:10 +02:00
|
|
|
import Data.String
|
2024-05-31 10:42:26 +02:00
|
|
|
import Language.Javascript.JSaddle.Warp as JSaddle
|
|
|
|
|
import Network.HTTP.Simple
|
|
|
|
|
#else
|
2024-06-03 11:22:10 +02:00
|
|
|
import Data.ByteString.Char8 qualified as B
|
|
|
|
|
import Data.Maybe
|
|
|
|
|
import Data.String
|
2024-05-31 10:42:26 +02:00
|
|
|
import JavaScript.Web.XMLHttpRequest
|
2024-06-03 11:22:10 +02:00
|
|
|
import Miso.String qualified as J
|
2024-05-31 10:42:26 +02:00
|
|
|
#endif
|
|
|
|
|
|
2024-06-03 11:22:10 +02:00
|
|
|
import Data.Aeson qualified as A
|
2024-06-04 00:26:31 +02:00
|
|
|
import Data.Aeson.Key qualified as AK
|
|
|
|
|
import Data.Aeson.KeyMap qualified as AM
|
2024-06-03 11:22:10 +02:00
|
|
|
import Data.ByteString.Lazy.Char8 qualified as LB
|
|
|
|
|
import Data.Default
|
|
|
|
|
import Data.Function
|
2024-06-04 00:26:31 +02:00
|
|
|
import Data.List
|
2024-06-03 11:22:10 +02:00
|
|
|
import Data.Map qualified as M
|
2024-06-04 00:26:31 +02:00
|
|
|
import Data.Text qualified as T
|
2024-06-04 09:28:10 +02:00
|
|
|
import Form qualified as F
|
2024-06-03 11:22:10 +02:00
|
|
|
import GHC.Generics (Generic)
|
|
|
|
|
import Miso
|
|
|
|
|
import Miso.String (toMisoString)
|
|
|
|
|
|
|
|
|
|
data Model = Model
|
|
|
|
|
{ schema :: Maybe (Either String Schema),
|
|
|
|
|
posts :: Maybe (Either String [A.Value])
|
|
|
|
|
}
|
|
|
|
|
deriving (Show, Eq, Generic, Default)
|
|
|
|
|
|
|
|
|
|
data Schema = Schema
|
|
|
|
|
{ id :: String,
|
|
|
|
|
schema :: String,
|
|
|
|
|
title :: String,
|
|
|
|
|
type_ :: SchemaType
|
|
|
|
|
}
|
|
|
|
|
deriving (Show, Eq)
|
|
|
|
|
|
|
|
|
|
instance A.FromJSON Schema where
|
|
|
|
|
parseJSON =
|
|
|
|
|
A.withObject
|
|
|
|
|
"Schema"
|
|
|
|
|
( \v ->
|
|
|
|
|
Schema
|
|
|
|
|
<$> v A..: "$id"
|
|
|
|
|
<*> v A..: "$schema"
|
|
|
|
|
<*> v A..: "title"
|
|
|
|
|
<*> A.parseJSON (A.Object v)
|
|
|
|
|
)
|
2024-05-31 10:42:26 +02:00
|
|
|
|
2024-06-03 11:22:10 +02:00
|
|
|
data SchemaType = Object (M.Map String String)
|
|
|
|
|
deriving (Show, Eq)
|
|
|
|
|
|
|
|
|
|
instance A.FromJSON SchemaType where
|
|
|
|
|
parseJSON =
|
|
|
|
|
A.withObject
|
|
|
|
|
"SchemaType"
|
|
|
|
|
( \v ->
|
|
|
|
|
v A..: "type" >>= \case
|
|
|
|
|
("object" :: String) -> Object <$> v A..: "properties"
|
|
|
|
|
)
|
2024-05-31 10:42:26 +02:00
|
|
|
|
|
|
|
|
data Action
|
2024-06-03 11:22:10 +02:00
|
|
|
= NoOp
|
|
|
|
|
| Init
|
|
|
|
|
| FetchSchema
|
|
|
|
|
| SetSchema (Either String Schema)
|
|
|
|
|
| FetchPosts
|
|
|
|
|
| SetPosts (Either String [A.Value])
|
2024-06-04 00:26:31 +02:00
|
|
|
| FormChanged A.Value
|
|
|
|
|
| FormSubmitted A.Value
|
2024-05-31 10:42:26 +02:00
|
|
|
deriving (Show, Eq)
|
|
|
|
|
|
|
|
|
|
#ifndef ghcjs_HOST_OS
|
|
|
|
|
runApp :: JSM () -> IO ()
|
|
|
|
|
runApp f = JSaddle.debugOr 8080 (f >> syncPoint) JSaddle.jsaddleApp
|
|
|
|
|
#else
|
|
|
|
|
runApp :: IO () -> IO ()
|
|
|
|
|
runApp app = app
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
main :: IO ()
|
|
|
|
|
main = runApp $ startApp App {..}
|
|
|
|
|
where
|
2024-06-03 11:22:10 +02:00
|
|
|
initialAction = Init
|
|
|
|
|
model = def
|
2024-05-31 10:42:26 +02:00
|
|
|
update = updateModel
|
|
|
|
|
view = viewModel
|
|
|
|
|
events = defaultEvents
|
|
|
|
|
subs = []
|
|
|
|
|
mountPoint = Nothing
|
|
|
|
|
logLevel = Off
|
|
|
|
|
|
|
|
|
|
updateModel :: Action -> Model -> Effect Action Model
|
|
|
|
|
updateModel action m =
|
|
|
|
|
case action of
|
2024-06-03 11:22:10 +02:00
|
|
|
NoOp -> noEff m
|
|
|
|
|
Init -> batchEff m [pure FetchSchema, pure FetchPosts]
|
2024-05-31 10:42:26 +02:00
|
|
|
FetchSchema -> m <# do SetSchema <$> fetchSchema
|
2024-06-03 11:22:10 +02:00
|
|
|
SetSchema schema ->
|
|
|
|
|
let setSchema :: Either String Schema -> Model -> Model
|
|
|
|
|
setSchema schema m = m {schema = Just schema}
|
|
|
|
|
in noEff (setSchema schema m)
|
|
|
|
|
FetchPosts -> m <# do SetPosts <$> fetchPosts
|
|
|
|
|
SetPosts posts ->
|
|
|
|
|
let setPosts :: Either String [A.Value] -> Model -> Model
|
|
|
|
|
setPosts posts m = m {posts = Just posts}
|
|
|
|
|
in noEff (setPosts posts m)
|
2024-06-04 00:26:31 +02:00
|
|
|
FormChanged _ -> noEff m
|
|
|
|
|
FormSubmitted _ -> noEff m
|
2024-05-31 10:42:26 +02:00
|
|
|
|
2024-06-03 11:22:10 +02:00
|
|
|
fetchSchema :: JSM (Either String Schema)
|
|
|
|
|
fetchSchema =
|
|
|
|
|
A.eitherDecode <$> fetch (fromString "http://localhost:8081/posts.schema.json")
|
2024-05-31 10:42:26 +02:00
|
|
|
|
2024-06-03 11:22:10 +02:00
|
|
|
fetchPosts :: JSM (Either String [A.Value])
|
|
|
|
|
fetchPosts =
|
|
|
|
|
A.eitherDecode
|
|
|
|
|
<$> fetch
|
|
|
|
|
( fromString "http://localhost:8081"
|
|
|
|
|
& setRequestMethod "POST"
|
|
|
|
|
& setRequestBodyLBS "SELECT posts FROM posts"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
fetch :: Request -> JSM LB.ByteString
|
|
|
|
|
fetch req = LB.fromStrict . getResponseBody <$> httpBS req
|
|
|
|
|
|
|
|
|
|
#ifdef ghcjs_HOST_OS
|
|
|
|
|
httpBS :: Request -> JSM (Response B.ByteString)
|
|
|
|
|
httpBS req = xhrByteString req
|
|
|
|
|
|
|
|
|
|
instance IsString Request where
|
|
|
|
|
fromString uri =
|
|
|
|
|
Request
|
|
|
|
|
{ reqMethod = GET,
|
|
|
|
|
reqURI = J.pack uri,
|
|
|
|
|
reqLogin = Nothing,
|
|
|
|
|
reqHeaders = [],
|
|
|
|
|
reqWithCredentials = False,
|
|
|
|
|
reqData = NoData
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setRequestMethod :: B.ByteString -> Request -> Request
|
|
|
|
|
setRequestMethod "POST" req = req {reqMethod = POST}
|
|
|
|
|
|
|
|
|
|
setRequestBodyLBS :: LB.ByteString -> Request -> Request
|
|
|
|
|
setRequestBodyLBS body req = req {reqData = StringData (J.pack (LB.unpack body))}
|
|
|
|
|
|
|
|
|
|
getResponseBody :: Response B.ByteString -> B.ByteString
|
|
|
|
|
getResponseBody = fromMaybe "" . contents
|
2024-05-31 10:42:26 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
viewModel :: Model -> View Action
|
2024-06-03 11:22:10 +02:00
|
|
|
viewModel model =
|
|
|
|
|
div_ [] $
|
|
|
|
|
[ maybe (text "..") (either err viewSchema) model.schema,
|
2024-06-04 00:26:31 +02:00
|
|
|
maybe (text "..") (either err viewPosts) model.posts,
|
|
|
|
|
maybe
|
|
|
|
|
(text "..")
|
|
|
|
|
( either
|
|
|
|
|
err
|
|
|
|
|
( fmap (either FormChanged FormSubmitted)
|
2024-06-04 09:28:10 +02:00
|
|
|
. flip F.runForm (A.Object AM.empty)
|
2024-06-04 00:26:31 +02:00
|
|
|
. schemaForm
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
model.schema
|
2024-06-03 11:22:10 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
err :: String -> View Action
|
|
|
|
|
err = text . toMisoString . ("err! " <>)
|
|
|
|
|
|
|
|
|
|
viewSchema :: Schema -> View Action
|
|
|
|
|
viewSchema schema =
|
|
|
|
|
case schema.type_ of
|
|
|
|
|
Object properties ->
|
|
|
|
|
ol_ [] $
|
|
|
|
|
( \(k, v) ->
|
|
|
|
|
li_ [] $
|
|
|
|
|
[ text (toMisoString k),
|
|
|
|
|
text ":",
|
|
|
|
|
text (toMisoString v)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
<$> (M.toList properties)
|
|
|
|
|
|
2024-06-04 09:28:10 +02:00
|
|
|
schemaForm :: Schema -> F.Form A.Value A.Value
|
2024-06-04 00:26:31 +02:00
|
|
|
schemaForm schema =
|
2024-06-04 09:28:10 +02:00
|
|
|
fmap mergeJson . sequence $
|
2024-06-04 00:26:31 +02:00
|
|
|
case schema.type_ of
|
|
|
|
|
Object properties ->
|
|
|
|
|
( \(AK.fromString -> k, "string") ->
|
2024-06-04 09:28:10 +02:00
|
|
|
A.Object . AM.singleton k
|
|
|
|
|
<$> ( F.mapValues (getO k) (setO k) $
|
|
|
|
|
jsonString (AK.toString k)
|
|
|
|
|
)
|
2024-06-04 00:26:31 +02:00
|
|
|
)
|
|
|
|
|
<$> (M.toList properties)
|
|
|
|
|
|
|
|
|
|
mergeJson :: [A.Value] -> A.Value
|
|
|
|
|
mergeJson = foldl' mergeObject (A.Object AM.empty)
|
|
|
|
|
|
|
|
|
|
mergeObject :: A.Value -> A.Value -> A.Value
|
|
|
|
|
mergeObject (A.Object kvs) (A.Object kvs') = A.Object (AM.union kvs kvs')
|
|
|
|
|
|
|
|
|
|
fromJson :: A.Value -> T.Text
|
|
|
|
|
fromJson (A.String x) = x
|
|
|
|
|
fromJson _ = ""
|
|
|
|
|
|
|
|
|
|
toJson :: T.Text -> A.Value -> A.Value
|
|
|
|
|
toJson x _ = A.String x
|
|
|
|
|
|
|
|
|
|
getO :: AK.Key -> A.Value -> A.Value
|
|
|
|
|
getO k (A.Object kvs) = fromMaybe A.Null (AM.lookup k kvs)
|
|
|
|
|
|
|
|
|
|
setO :: AK.Key -> A.Value -> A.Value -> A.Value
|
|
|
|
|
setO k v (A.Object kvs) = A.Object (AM.insert k v kvs)
|
|
|
|
|
|
2024-06-04 09:28:10 +02:00
|
|
|
jsonString :: String -> F.Form A.Value A.Value
|
|
|
|
|
jsonString = fmap A.String . F.mapValues fromJson toJson . F.string
|
2024-06-04 00:26:31 +02:00
|
|
|
|
2024-06-03 11:22:10 +02:00
|
|
|
viewPosts :: [A.Value] -> View Action
|
|
|
|
|
viewPosts posts = ol_ [] (viewPost <$> posts)
|
|
|
|
|
where
|
|
|
|
|
viewPost post = pre_ [] [text (toMisoString (A.encode post))]
|