feat(users/Profpatsch/whatcd-resolver): add html streaming & maps

For all big page reloads, we want the `<head>` of the page to start
being transmitted even while doing database requests.

So let’s use the `Wai.ResponseStream` to do exactly that. The handler
provides the contents of `<head>`, we start streaming that, meanwhile
it calculates the `<body>` and once that is ready transmits it.

This means we can load all our static resources before the page
even starts sending any body data, meaning the css and html is already
there when we reach `</html>`.

Sweet.

The `<title>` in `artistPage` was depending on the table data, so I
moved it into a separate SELECT.

We can do all of it in parallel as well. Sweet.

~~~

This also adds static file prefetching of source maps if provided.

Change-Id: Ib824430594733b4c8e86ee1096c8afba2df1a66d
Reviewed-on: https://cl.tvl.fyi/c/depot/+/13221
Reviewed-by: Profpatsch <mail@profpatsch.de>
Tested-by: BuildkiteCI
This commit is contained in:
Profpatsch 2025-03-08 13:34:47 +01:00
parent b32a95c206
commit b1403a5e94
3 changed files with 320 additions and 173 deletions

View file

@ -71,7 +71,7 @@ redactedGetArtist ::
( MonadOtel m,
MonadThrow m,
MonadRedacted m,
HasField "artistId" r Text,
HasField "artistId" r Int,
HasField "page" r (Maybe Natural)
) =>
r ->
@ -83,7 +83,7 @@ redactedGetArtist dat parser =
span
( T3
(label @"action" "artist")
(label @"actionArgs" [("id", buildBytes utf8B dat.artistId)])
(label @"actionArgs" [("id", buildBytes intDecimalB dat.artistId)])
(getLabel @"page" dat)
)
parser
@ -184,7 +184,7 @@ redactedRefreshArtist ::
MonadThrow m,
MonadOtel m,
MonadRedacted m,
HasField "artistId" dat Text
HasField "artistId" dat Int
) =>
dat ->
m (Transaction m (Label "newTorrents" [Label "torrentId" Int]))
@ -610,8 +610,9 @@ getTorrentById dat = do
data GetBestTorrentsFilter = GetBestTorrentsFilter
{ onlyDownloaded :: Bool,
onlyArtist :: Maybe (Label "artistRedactedId" Natural),
onlyTheseTorrents :: Maybe ([Label "torrentId" Int])
onlyArtist :: Maybe (Label "artistRedactedId" Int),
onlyTheseTorrents :: Maybe ([Label "torrentId" Int]),
limitResults :: Maybe Natural
}
-- | Find the best torrent for each torrent group (based on the seeding_weight)
@ -662,6 +663,7 @@ getBestTorrents opts = do
JOIN redacted.torrents t ON t.id = f.id
JOIN redacted.torrent_groups tg ON tg.id = t.torrent_group
ORDER BY seeding_weight DESC
LIMIT ?::int
|]
( do
let (onlyArtistB, onlyArtistId) = case opts.onlyArtist of
@ -672,9 +674,10 @@ getBestTorrents opts = do
Just a -> (False, a <&> (.torrentId) & PGArray)
( opts.onlyDownloaded :: Bool,
onlyArtistB :: Bool,
onlyArtistId & fromIntegral @Natural @Int,
onlyArtistId :: Int,
onlyTheseTorrentsB :: Bool,
onlyTheseTorrents
onlyTheseTorrents,
opts.limitResults <&> naturalToInteger :: Maybe Integer
)
)
( do
@ -714,6 +717,29 @@ getBestTorrents opts = do
}
)
getArtistNameById :: (MonadPostgres m, HasField "artistId" r Int) => r -> Transaction m (Maybe Text)
getArtistNameById dat = do
queryFirstRowWithMaybe
[sql|
WITH json as (
SELECT
-- TODO: different endpoints handle this differently (e.g. action=search and action=artist), we should unify this while parsing
COALESCE(
t.full_json_result->'artists',
tg.full_json_result->'artists',
'[]'::jsonb
) as artists
FROM redacted.torrents t
JOIN redacted.torrent_groups tg ON tg.id = t.torrent_group
)
select name from json
join lateral jsonb_to_recordset(artists) as x(id int, name text) on true
where id = ?::int
limit 1
|]
(getLabel @"artistId" dat)
(Dec.fromField @Text)
-- | Do a request to the redacted API. If you know what that is, you know how to find the API docs.
mkRedactedApiRequest ::
( MonadThrow m,