Add SPA static file serving from backend

Backend (Server.hs):
- serveStaticOrSpa: serves static files from --static-dir
- SPA fallback: extensionless paths serve index.html
- MIME type mapping for html/css/js/png/svg/ico/woff2

Frontend:
- ES module imports with import map for Mithril CDN
- Build script: tsc + copy index.html + static assets

CLI:
- --static-dir flag (default: frontend/dist)
- scripts/run passes through extra args
- Dockerfile CMD points at /usr/local/share/sis/static
This commit is contained in:
2026-07-15 14:59:31 -04:00
parent 5075ef2718
commit 72b1ee1d3d
12 changed files with 1297 additions and 114 deletions
+25 -14
View File
@@ -2,7 +2,7 @@
{- | Entry point for the Sis chore tracker server.
Starts a Warp HTTP server and serves the JSON API.
Starts a Warp HTTP server, serves the JSON API and the SPA frontend.
-}
module Main (main) where
@@ -14,6 +14,7 @@ import Sis.Server qualified as Sis
data Options = Options
{ optPort :: Int
, optStaticDir :: FilePath
}
optionsParser :: Opt.Parser Options
@@ -28,26 +29,36 @@ optionsParser =
<> Opt.value 8080
<> Opt.showDefault
)
<*> Opt.strOption
( Opt.long "static-dir"
<> Opt.metavar "DIR"
<> Opt.help "Directory containing the SPA frontend static files"
<> Opt.value "frontend/dist"
<> Opt.showDefault
)
main :: IO ()
main = do
opts <- Opt.execParser $
Opt.info (optionsParser Opt.<**> Opt.helper) $
Opt.fullDesc
<> Opt.progDesc "Sis — shared household chore tracker"
<> Opt.header "sis-server"
opts <-
Opt.execParser $
Opt.info (optionsParser Opt.<**> Opt.helper) $
Opt.fullDesc
<> Opt.progDesc "Sis — shared household chore tracker"
<> Opt.header "sis-server"
-- Install a SIGTERM handler so Docker stop works cleanly.
_ <- Signals.installHandler
Signals.sigTERM
(Signals.Catch (putStrLn "[sis] shutting down"))
Nothing
_ <-
Signals.installHandler
Signals.sigTERM
(Signals.Catch (putStrLn "[sis] shutting down"))
Nothing
let waiApp = Sis.app
let waiApp = Sis.app (optStaticDir opts)
let settings =
Warp.setPort (optPort opts)
$ Warp.setBeforeMainLoop (putStrLn $ "[sis] listening on port " ++ show (optPort opts))
Warp.defaultSettings
Warp.setPort (optPort opts) $
Warp.setBeforeMainLoop
(putStrLn $ "[sis] listening on port " ++ show (optPort opts))
Warp.defaultSettings
Warp.runSettings settings waiApp