Remove pre-release HTTP compatibility surface
This commit is contained in:
parent
15eb0038d2
commit
f28810ea33
3 changed files with 104 additions and 329 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
'&' -> "&"
|
||||
'<' -> "<"
|
||||
'>' -> ">"
|
||||
'"' -> """
|
||||
'\'' -> "'"
|
||||
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);"
|
||||
Loading…
Reference in a new issue