Remove pre-release HTTP compatibility surface

This commit is contained in:
ms2k 2026-07-19 17:49:03 +09:00
commit f28810ea33
3 changed files with 104 additions and 329 deletions

View file

@ -2,7 +2,6 @@ module Nuthatch.Api
( Api
, HealthEndpoint
, ReadyEndpoint
, BrowserScriptEndpoint
, AppEndpoint
, StaticAssetsEndpoint
, ProductApi
@ -12,17 +11,10 @@ module Nuthatch.Api
, LoginSuccess
, LogoutEndpoint
, LogoutSuccess
, BrowseEndpoint
, DownloadEndpoint
, DownloadSuccess
, UploadEndpoint
, DeleteEndpoint
, DeletePrefixEndpoint
, api
, HTML
, JavaScript
, HtmlPage (..)
, BrowserScript (..)
, SessionContext (..)
, SessionAuth
, BrowsePrefix (..)
@ -32,10 +24,7 @@ module Nuthatch.Api
, HealthResponse (..)
, ReadyResponse (..)
, BrowseResponse (..)
, BrowseView (..)
, DeleteRequest (..)
, DeletePrefixRequest (..)
, DeletePrefixResponse (..)
, SessionResponse (..)
, DeletePlanResponse (..)
, DeleteBatchItem (..)
@ -81,26 +70,15 @@ import Servant.Server (HasServer (..))
import Servant.Server.Experimental.Auth (AuthServerData)
data HTML
data JavaScript
instance Accept HTML where
contentType _ = "text" // "html" /: ("charset", "utf-8")
instance Accept JavaScript where
contentType _ = "text" // "javascript" /: ("charset", "utf-8")
newtype HtmlPage = HtmlPage { unHtmlPage :: Text }
deriving stock (Eq, Show)
instance MimeRender HTML HtmlPage where
mimeRender _ = BL.fromStrict . TE.encodeUtf8 . unHtmlPage
newtype BrowserScript = BrowserScript { unBrowserScript :: Text }
deriving stock (Eq, Show)
instance MimeRender JavaScript BrowserScript where
mimeRender _ = BL.fromStrict . TE.encodeUtf8 . unBrowserScript
data SessionContext = SessionContext
{ sessionUser :: SessionUser
, sessionCsrf :: Maybe CsrfToken
@ -152,18 +130,6 @@ instance ToJSON BrowseResponse where
, "partial" .= browsePartial response
]
data BrowseView = BrowseView
{ browseViewHtml :: HtmlPage
, browseViewJson :: BrowseResponse
}
deriving stock (Eq, Show)
instance MimeRender HTML BrowseView where
mimeRender proxy = mimeRender proxy . browseViewHtml
instance ToJSON BrowseView where
toJSON = toJSON . browseViewJson
data DeleteRequest = DeleteRequest
{ deleteRoot :: Root
, deletePath :: RelativePath
@ -180,28 +146,6 @@ instance FromJSON DeleteRequest where
<*> o .: "operation_id"
<*> o .: "csrf"
data DeletePrefixRequest = DeletePrefixRequest
{ deletePrefixRoot :: Root
, deletePrefixPath :: RelativePath
, deletePrefixCsrf :: CsrfToken
}
deriving stock (Eq)
instance FromJSON DeletePrefixRequest where
parseJSON = withObject "DeletePrefixRequest" $ \o ->
DeletePrefixRequest
<$> o .: "root"
<*> o .: "path"
<*> o .: "csrf"
newtype DeletePrefixResponse = DeletePrefixResponse
{ deletedObjectCount :: Int
}
deriving stock (Eq, Show)
instance ToJSON DeletePrefixResponse where
toJSON response = object ["deleted" .= deletedObjectCount response]
data UploadResponse = UploadResponse
{ uploadResponseOperationId :: OperationId
, uploadResponseResult :: MutationResult
@ -462,9 +406,6 @@ type HealthEndpoint =
type ReadyEndpoint =
"readyz" :> Throws ReadyError :> Get '[JSON] ReadyResponse
type BrowserScriptEndpoint =
"nuthatch.js" :> Get '[JavaScript] BrowserScript
type AppEndpoint =
"app"
:> AuthProtect SessionAuth
@ -524,16 +465,6 @@ type LogoutEndpoint =
:> Header' '[Required, Strict] "X-CSRF-Token" CsrfToken
:> Post '[JSON] LogoutSuccess
type BrowseEndpoint =
AuthProtect SessionAuth
:> Throws AuthenticationError
:> Throws BrowseError
:> QueryParam "root" Root
:> QueryParam "prefix" BrowsePrefix
:> QueryParam "q" SearchQuery
:> QueryParam "continuation" PaginationToken
:> Get '[HTML, JSON] BrowseView
type DownloadSuccess =
Headers
'[ Header "Content-Disposition" Text
@ -543,46 +474,6 @@ type DownloadSuccess =
]
(SourceIO BS.ByteString)
type DownloadEndpoint =
"download"
:> AuthProtect SessionAuth
:> Throws AuthenticationError
:> Throws DownloadError
:> QueryParam' '[Required, Strict] "root" Root
:> QueryParam' '[Required, Strict] "path" RelativePath
:> Header "Range" Text
:> StreamGet NoFraming OctetStream DownloadSuccess
type UploadEndpoint =
"upload"
:> AuthProtect SessionAuth
:> Throws AuthenticationError
:> Throws UploadError
:> QueryParam' '[Required, Strict] "root" Root
:> QueryParam' '[Required, Strict] "path" RelativePath
:> QueryParam' '[Required, Strict] "operation_id" OperationId
:> Header' '[Required, Strict] "X-Declared-Size" DeclaredSize
:> Header' '[Required, Strict] "X-Content-SHA256" ContentSha256
:> Header' '[Required, Strict] "X-CSRF-Token" CsrfToken
:> StreamBody NoFraming OctetStream (SourceIO BS.ByteString)
:> Post '[JSON] UploadResponse
type DeleteEndpoint =
"delete"
:> AuthProtect SessionAuth
:> Throws AuthenticationError
:> Throws DeleteError
:> ReqBody '[JSON] DeleteRequest
:> Post '[JSON] DeleteResponse
type DeletePrefixEndpoint =
"delete-prefix"
:> AuthProtect SessionAuth
:> Throws AuthenticationError
:> Throws DeletePrefixError
:> ReqBody '[JSON] DeletePrefixRequest
:> Post '[JSON] DeletePrefixResponse
type ProductApi =
"api" :>
( "session"
@ -597,7 +488,7 @@ type ProductApi =
:> QueryParam "prefix" BrowsePrefix
:> QueryParam "q" SearchQuery
:> QueryParam "continuation" PaginationToken
:> Get '[JSON] BrowseView
:> Get '[JSON] BrowseResponse
:<|> "download"
:> AuthProtect SessionAuth
:> Throws AuthenticationError
@ -643,17 +534,11 @@ type ProductApi =
type Api =
HealthEndpoint
:<|> ReadyEndpoint
:<|> BrowserScriptEndpoint
:<|> AppEndpoint
:<|> StaticAssetsEndpoint
:<|> LoginEndpoint
:<|> OidcCallbackEndpoint
:<|> LogoutEndpoint
:<|> BrowseEndpoint
:<|> DownloadEndpoint
:<|> UploadEndpoint
:<|> DeleteEndpoint
:<|> DeletePrefixEndpoint
:<|> ProductApi
api :: Proxy Api

View file

@ -94,7 +94,6 @@ import Nuthatch.Streaming
, UploadStreamError (..)
)
import Nuthatch.Types
import Nuthatch.Ui (browserScript, productPage)
import Servant
import Servant.Server.Experimental.Auth
( AuthHandler
@ -157,17 +156,11 @@ server :: Env -> Server Api
server env =
pure (HealthResponse True)
:<|> handleReady env
:<|> pure (BrowserScript browserScript)
:<|> handleAppRequest
:<|> staticAssetsServer
:<|> handleLoginRequest env
:<|> handleOidcCallback env
:<|> handleLogoutRequest env
:<|> handleBrowseRequest env
:<|> handleDownloadRequest env
:<|> handleUploadRequest env
:<|> handleDeleteRequest env
:<|> handleDeletePrefixRequest env
:<|>
( handleSessionRequest
:<|> handleBrowseRequest env
@ -396,7 +389,7 @@ handleBrowseRequest
-> Maybe BrowsePrefix
-> Maybe SearchQuery
-> Maybe PaginationToken
-> Handler BrowseView
-> Handler BrowseResponse
handleBrowseRequest env session selectedRoot selectedPrefix search continuation = do
let user = sessionUser session
root = fromMaybe Personal selectedRoot
@ -414,14 +407,10 @@ handleBrowseRequest env session selectedRoot selectedPrefix search continuation
(throwOperationError status502Error . BrowseObjectStoreFailed)
pure
result
let response = BrowseResponse
{ browseEntries = entries
, browseContinuation = PaginationToken <$> nextContinuation
, browsePartial = partial
}
pure BrowseView
{ browseViewHtml = HtmlPage (productPage entries)
, browseViewJson = response
pure BrowseResponse
{ browseEntries = entries
, browseContinuation = PaginationToken <$> nextContinuation
, browsePartial = partial
}
handleDownloadRequest
@ -624,22 +613,6 @@ handleDeleteRequest env session request = do
cfgIdempotencyWindowSec (envConfig env)
}
handleDeletePrefixRequest
:: Env
-> SessionContext
-> DeletePrefixRequest
-> Handler DeletePrefixResponse
handleDeletePrefixRequest env session request = do
requireCsrf "delete_prefix" DeletePrefixCsrfRejected session (deletePrefixCsrf request)
liftIO $ audit "delete_prefix" "rejected"
(Just (unUserId (suUserId (sessionUser session))))
([ ("root", A.toJSON (rootLabel (deletePrefixRoot request)))
, ("reason", A.String "paged_delete_required")
] <> objectKeyAudit (envConfig env) (deletePrefixPath request))
throwOperationError err409
(DeletePrefixMutationRejected
"use delete-plan and delete-batch with client operation ids")
handleDeletePlanRequest
:: Env
-> SessionContext

View file

@ -1,55 +0,0 @@
module Nuthatch.Ui
( browserScript
, productPage
, htmlResponse
, escapeHtml
) where
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Lazy (fromStrict)
import qualified Data.Text.Lazy.Encoding as TLE
import Network.HTTP.Types (Status, hContentType)
import Network.Wai (Response, responseLBS)
import Nuthatch.Types
htmlResponse :: Status -> Text -> Response
htmlResponse status body =
responseLBS status
[(hContentType, "text/html; charset=utf-8")]
(TLE.encodeUtf8 (fromStrict body))
escapeHtml :: Text -> Text
escapeHtml =
T.concatMap $ \case
'&' -> "&amp;"
'<' -> "&lt;"
'>' -> "&gt;"
'"' -> "&quot;"
'\'' -> "&#39;"
c -> T.singleton c
productPage :: [ListEntry] -> Text
productPage entries =
T.unlines
[ "<!doctype html>"
, "<html lang=\"en\"><head>"
, "<meta charset=\"utf-8\">"
, "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
, "<meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'self'; object-src 'none'; base-uri 'none'\">"
, "<title>Nuthatch</title>"
, "</head><body><main>"
, "<h1>Nuthatch</h1>"
, "<p>The browser application has moved to <a href=\"/app\">/app</a>.</p>"
, "<ul>"
, T.concat
[ "<li>" <> escapeHtml (leRelativePath entry) <> "</li>"
| entry <- entries
]
, "</ul>"
, "</main></body></html>"
]
browserScript :: Text
browserScript =
"const nuthatchApp = '/app'; if (window.location.pathname === '/') window.location.assign(nuthatchApp);"