diff --git a/__tests__/archive.ts b/__tests__/archive.ts index 2a3bec9716..6be5079c13 100644 --- a/__tests__/archive.ts +++ b/__tests__/archive.ts @@ -642,6 +642,18 @@ describe('archive queries', () => { } `; + const featuredArchivesQuery = ` + query FeaturedArchives($subjectType: String!, $subjectId: String!) { + featuredArchives(subjectType: $subjectType, subjectId: $subjectId) { + id + scopeType + scopeId + periodType + periodStart + } + } + `; + beforeEach(async () => { await materializePeriodArchives({ con, @@ -704,6 +716,52 @@ describe('archive queries', () => { ]); }); + it('should return all relevant archives for a featured post', async () => { + const res = await client.query(featuredArchivesQuery, { + variables: { + subjectType: ArchiveSubjectType.Post, + subjectId: 'post-6', + }, + }); + + expect(res.errors).toBeFalsy(); + expect(res.data.featuredArchives).toEqual([ + { + id: expect.any(String), + scopeType: ArchiveScopeType.Global, + scopeId: null, + periodType: ArchivePeriodType.Month, + periodStart: marchPeriodStart.toISOString(), + }, + { + id: expect.any(String), + scopeType: ArchiveScopeType.Source, + scopeId: 'source-a', + periodType: ArchivePeriodType.Month, + periodStart: marchPeriodStart.toISOString(), + }, + { + id: expect.any(String), + scopeType: ArchiveScopeType.Tag, + scopeId: 'webdev', + periodType: ArchivePeriodType.Month, + periodStart: marchPeriodStart.toISOString(), + }, + ]); + }); + + it('should return an empty list when no archives exist for the requested subject type', async () => { + const res = await client.query(featuredArchivesQuery, { + variables: { + subjectType: ArchiveSubjectType.User, + subjectId: 'author-good', + }, + }); + + expect(res.errors).toBeFalsy(); + expect(res.data.featuredArchives).toEqual([]); + }); + it('should validate that tag archives require a scopeId', async () => testQueryErrorCode( client, @@ -739,4 +797,16 @@ describe('archive queries', () => { 'GRAPHQL_VALIDATION_FAILED', 'month must not be set for yearly archives', )); + + it('should accept other archive subject types even when no results exist', async () => { + const res = await client.query(featuredArchivesQuery, { + variables: { + subjectType: ArchiveSubjectType.Squad, + subjectId: 'source-a', + }, + }); + + expect(res.errors).toBeFalsy(); + expect(res.data.featuredArchives).toEqual([]); + }); }); diff --git a/src/common/schema/archive.ts b/src/common/schema/archive.ts index 8d0125536c..a3bf272509 100644 --- a/src/common/schema/archive.ts +++ b/src/common/schema/archive.ts @@ -74,3 +74,8 @@ export const archiveIndexQuerySchema = z }); } }); + +export const featuredArchivesQuerySchema = z.object({ + subjectType: archiveSubjectTypeSchema, + subjectId: z.string().min(1), +}); diff --git a/src/schema/archive.ts b/src/schema/archive.ts index b54e578146..0d48a2f545 100644 --- a/src/schema/archive.ts +++ b/src/schema/archive.ts @@ -6,7 +6,9 @@ import { ArchivePeriodType, getArchivePeriodStart } from '../common/archive'; import { archiveIndexQuerySchema, archiveQuerySchema, + featuredArchivesQuerySchema, } from '../common/schema/archive'; +import { ArchiveItem } from '../entity/ArchiveItem'; import graphorm from '../graphorm'; type GQLArchive = { @@ -21,6 +23,7 @@ type GQLArchive = { type ArchiveQueryArgs = z.infer; type ArchiveIndexQueryArgs = z.infer; +type FeaturedArchivesQueryArgs = z.infer; export const typeDefs = /* GraphQL */ ` type ArchiveItem { @@ -60,6 +63,9 @@ export const typeDefs = /* GraphQL */ ` periodType: String year: Int ): [Archive!]! @cacheControl(maxAge: 3600) + + featuredArchives(subjectType: String!, subjectId: String!): [Archive!]! + @cacheControl(maxAge: 3600) } `; @@ -193,5 +199,45 @@ export const resolvers: IResolvers = { true, ); }, + featuredArchives: async ( + _, + args: FeaturedArchivesQueryArgs, + ctx: Context, + info, + ): Promise => { + const validatedArgsResult = featuredArchivesQuerySchema.safeParse(args); + + if (!validatedArgsResult.success) { + throw new ValidationError(validatedArgsResult.error.issues[0].message); + } + + const validatedArgs = validatedArgsResult.data; + + return graphorm.query( + ctx, + info, + (builder) => { + builder.queryBuilder = builder.queryBuilder + .innerJoin( + ArchiveItem, + 'archiveItem', + `"archiveItem"."archiveId" = ${builder.alias}.id`, + ) + .andWhere(`${builder.alias}."subjectType" = :subjectType`, { + subjectType: validatedArgs.subjectType, + }) + .andWhere('"archiveItem"."subjectId" = :subjectId', { + subjectId: validatedArgs.subjectId, + }) + .distinct(true) + .orderBy(`${builder.alias}."periodStart"`, 'DESC') + .addOrderBy(`${builder.alias}."scopeType"`, 'ASC') + .addOrderBy(`${builder.alias}."scopeId"`, 'ASC'); + + return builder; + }, + true, + ); + }, }, };