@@ -11,6 +11,8 @@ import {
1111 User ,
1212} from '../entity' ;
1313import { AGENTS_DIGEST_SOURCE } from '../entity/Source' ;
14+ import { ChannelHighlightDefinition } from '../entity/ChannelHighlightDefinition' ;
15+ import { PostHighlight } from '../entity/PostHighlight' ;
1416import { ArchivePeriodType , ArchiveScopeType } from '../common/archive' ;
1517import { getUserProfileUrl } from '../common/users' ;
1618import createOrGetConnection from '../db' ;
@@ -98,6 +100,29 @@ const getSourceSitemapUrl = (prefix: string, handle: string): string =>
98100const getSquadSitemapUrl = ( prefix : string , handle : string ) : string =>
99101 `${ prefix } /squads/${ encodeURIComponent ( handle ) } ` ;
100102
103+ const getHighlightsSitemapUrl = ( prefix : string , channel ?: string ) : string =>
104+ channel
105+ ? `${ prefix } /highlights/${ encodeURIComponent ( channel ) } `
106+ : `${ prefix } /highlights` ;
107+
108+ type SitemapUrlEntry = {
109+ url : string ;
110+ lastmod ?: string ;
111+ } ;
112+
113+ const getSitemapUrlSetXml = (
114+ entries : SitemapUrlEntry [ ] ,
115+ ) : string => `<?xml version="1.0" encoding="UTF-8"?>
116+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
117+ ${ entries
118+ . map ( ( { url, lastmod } ) =>
119+ lastmod
120+ ? ` <url><loc>${ escapeXml ( url ) } </loc><lastmod>${ escapeXml ( lastmod ) } </lastmod></url>`
121+ : ` <url><loc>${ escapeXml ( url ) } </loc></url>` ,
122+ )
123+ . join ( '\n' ) }
124+ </urlset>` ;
125+
101126const streamReplicaQuery = async < T extends ObjectLiteral > (
102127 con : DataSource ,
103128 buildQuery : ( source : EntityManager ) => SelectQueryBuilder < T > ,
@@ -299,6 +324,26 @@ const buildCollectionsSitemapQuery = (
299324 . limit ( DEFAULT_SITEMAP_LIMIT ) ,
300325 ) ;
301326
327+ const buildHighlightsSitemapQuery = (
328+ source : DataSource | EntityManager ,
329+ ) : SelectQueryBuilder < ChannelHighlightDefinition > =>
330+ source
331+ . createQueryBuilder ( )
332+ . select ( 'chd.channel' , 'channel' )
333+ . addSelect ( 'MAX(ph."highlightedAt")' , 'lastmod' )
334+ . from ( ChannelHighlightDefinition , 'chd' )
335+ . leftJoin (
336+ PostHighlight ,
337+ 'ph' ,
338+ 'ph.channel = chd.channel AND ph."retiredAt" IS NULL' ,
339+ )
340+ . where ( 'chd.mode != :disabledMode' , { disabledMode : 'disabled' } )
341+ . groupBy ( 'chd.channel' )
342+ . addGroupBy ( 'chd."order"' )
343+ . orderBy ( 'chd."order"' , 'ASC' )
344+ . addOrderBy ( 'chd.channel' , 'ASC' )
345+ . limit ( DEFAULT_SITEMAP_LIMIT ) ;
346+
302347const buildTagsSitemapQuery = (
303348 source : DataSource | EntityManager ,
304349) : SelectQueryBuilder < Keyword > =>
@@ -617,6 +662,42 @@ const buildArchivePagesIndexEntries = (
617662 } )
618663 . join ( '\n' ) ;
619664
665+ const buildHighlightsSitemapXml = async ( con : DataSource ) : Promise < string > => {
666+ const prefix = getSitemapUrlPrefix ( ) ;
667+ const queryRunner = con . createQueryRunner ( 'slave' ) ;
668+
669+ try {
670+ const rows = await buildHighlightsSitemapQuery (
671+ queryRunner . manager ,
672+ ) . getRawMany < { channel : string ; lastmod ?: string | Date | null } > ( ) ;
673+
674+ const channelEntries = rows . map ( ( row ) => ( {
675+ url : getHighlightsSitemapUrl ( prefix , row . channel ) ,
676+ lastmod : getSitemapRowLastmod ( row ) ,
677+ } ) ) ;
678+ const rootLastmod = channelEntries . reduce < string | undefined > (
679+ ( latest , entry ) => {
680+ if ( ! entry . lastmod ) {
681+ return latest ;
682+ }
683+
684+ return ! latest || entry . lastmod > latest ? entry . lastmod : latest ;
685+ } ,
686+ undefined ,
687+ ) ;
688+
689+ return getSitemapUrlSetXml ( [
690+ {
691+ url : getHighlightsSitemapUrl ( prefix ) ,
692+ lastmod : rootLastmod ,
693+ } ,
694+ ...channelEntries ,
695+ ] ) ;
696+ } finally {
697+ await queryRunner . release ( ) ;
698+ }
699+ } ;
700+
620701const getSitemapIndexXml = (
621702 postsSitemapCount : number ,
622703 evergreenSitemapCount : number ,
@@ -645,6 +726,9 @@ ${evergreenSitemaps}
645726 <sitemap>
646727 <loc>${ escapeXml ( `${ prefix } /api/sitemaps/collections.xml` ) } </loc>
647728 </sitemap>
729+ <sitemap>
730+ <loc>${ escapeXml ( `${ prefix } /api/sitemaps/highlights.xml` ) } </loc>
731+ </sitemap>
648732 <sitemap>
649733 <loc>${ escapeXml ( `${ prefix } /api/sitemaps/tags.xml` ) } </loc>
650734 </sitemap>
@@ -785,6 +869,15 @@ export default async function (fastify: FastifyInstance): Promise<void> {
785869 ) ;
786870 } ) ;
787871
872+ fastify . get ( '/highlights.xml' , async ( _ , res ) => {
873+ const con = await createOrGetConnection ( ) ;
874+
875+ return res
876+ . type ( 'application/xml' )
877+ . header ( 'cache-control' , SITEMAP_CACHE_CONTROL )
878+ . send ( await buildHighlightsSitemapXml ( con ) ) ;
879+ } ) ;
880+
788881 fastify . get ( '/tags.txt' , async ( _ , res ) => {
789882 const con = await createOrGetConnection ( ) ;
790883 const prefix = getSitemapUrlPrefix ( ) ;
0 commit comments