Skip to content

Commit 989fb37

Browse files
authored
feat: add featured archives GraphQL query (#3788)
1 parent ae58aa5 commit 989fb37

3 files changed

Lines changed: 121 additions & 0 deletions

File tree

__tests__/archive.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,18 @@ describe('archive queries', () => {
642642
}
643643
`;
644644

645+
const featuredArchivesQuery = `
646+
query FeaturedArchives($subjectType: String!, $subjectId: String!) {
647+
featuredArchives(subjectType: $subjectType, subjectId: $subjectId) {
648+
id
649+
scopeType
650+
scopeId
651+
periodType
652+
periodStart
653+
}
654+
}
655+
`;
656+
645657
beforeEach(async () => {
646658
await materializePeriodArchives({
647659
con,
@@ -704,6 +716,52 @@ describe('archive queries', () => {
704716
]);
705717
});
706718

719+
it('should return all relevant archives for a featured post', async () => {
720+
const res = await client.query(featuredArchivesQuery, {
721+
variables: {
722+
subjectType: ArchiveSubjectType.Post,
723+
subjectId: 'post-6',
724+
},
725+
});
726+
727+
expect(res.errors).toBeFalsy();
728+
expect(res.data.featuredArchives).toEqual([
729+
{
730+
id: expect.any(String),
731+
scopeType: ArchiveScopeType.Global,
732+
scopeId: null,
733+
periodType: ArchivePeriodType.Month,
734+
periodStart: marchPeriodStart.toISOString(),
735+
},
736+
{
737+
id: expect.any(String),
738+
scopeType: ArchiveScopeType.Source,
739+
scopeId: 'source-a',
740+
periodType: ArchivePeriodType.Month,
741+
periodStart: marchPeriodStart.toISOString(),
742+
},
743+
{
744+
id: expect.any(String),
745+
scopeType: ArchiveScopeType.Tag,
746+
scopeId: 'webdev',
747+
periodType: ArchivePeriodType.Month,
748+
periodStart: marchPeriodStart.toISOString(),
749+
},
750+
]);
751+
});
752+
753+
it('should return an empty list when no archives exist for the requested subject type', async () => {
754+
const res = await client.query(featuredArchivesQuery, {
755+
variables: {
756+
subjectType: ArchiveSubjectType.User,
757+
subjectId: 'author-good',
758+
},
759+
});
760+
761+
expect(res.errors).toBeFalsy();
762+
expect(res.data.featuredArchives).toEqual([]);
763+
});
764+
707765
it('should validate that tag archives require a scopeId', async () =>
708766
testQueryErrorCode(
709767
client,
@@ -739,4 +797,16 @@ describe('archive queries', () => {
739797
'GRAPHQL_VALIDATION_FAILED',
740798
'month must not be set for yearly archives',
741799
));
800+
801+
it('should accept other archive subject types even when no results exist', async () => {
802+
const res = await client.query(featuredArchivesQuery, {
803+
variables: {
804+
subjectType: ArchiveSubjectType.Squad,
805+
subjectId: 'source-a',
806+
},
807+
});
808+
809+
expect(res.errors).toBeFalsy();
810+
expect(res.data.featuredArchives).toEqual([]);
811+
});
742812
});

src/common/schema/archive.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,8 @@ export const archiveIndexQuerySchema = z
7474
});
7575
}
7676
});
77+
78+
export const featuredArchivesQuerySchema = z.object({
79+
subjectType: archiveSubjectTypeSchema,
80+
subjectId: z.string().min(1),
81+
});

src/schema/archive.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import { ArchivePeriodType, getArchivePeriodStart } from '../common/archive';
66
import {
77
archiveIndexQuerySchema,
88
archiveQuerySchema,
9+
featuredArchivesQuerySchema,
910
} from '../common/schema/archive';
11+
import { ArchiveItem } from '../entity/ArchiveItem';
1012
import graphorm from '../graphorm';
1113

1214
type GQLArchive = {
@@ -21,6 +23,7 @@ type GQLArchive = {
2123

2224
type ArchiveQueryArgs = z.infer<typeof archiveQuerySchema>;
2325
type ArchiveIndexQueryArgs = z.infer<typeof archiveIndexQuerySchema>;
26+
type FeaturedArchivesQueryArgs = z.infer<typeof featuredArchivesQuerySchema>;
2427

2528
export const typeDefs = /* GraphQL */ `
2629
type ArchiveItem {
@@ -60,6 +63,9 @@ export const typeDefs = /* GraphQL */ `
6063
periodType: String
6164
year: Int
6265
): [Archive!]! @cacheControl(maxAge: 3600)
66+
67+
featuredArchives(subjectType: String!, subjectId: String!): [Archive!]!
68+
@cacheControl(maxAge: 3600)
6369
}
6470
`;
6571

@@ -193,5 +199,45 @@ export const resolvers: IResolvers<unknown, BaseContext> = {
193199
true,
194200
);
195201
},
202+
featuredArchives: async (
203+
_,
204+
args: FeaturedArchivesQueryArgs,
205+
ctx: Context,
206+
info,
207+
): Promise<GQLArchive[]> => {
208+
const validatedArgsResult = featuredArchivesQuerySchema.safeParse(args);
209+
210+
if (!validatedArgsResult.success) {
211+
throw new ValidationError(validatedArgsResult.error.issues[0].message);
212+
}
213+
214+
const validatedArgs = validatedArgsResult.data;
215+
216+
return graphorm.query<GQLArchive>(
217+
ctx,
218+
info,
219+
(builder) => {
220+
builder.queryBuilder = builder.queryBuilder
221+
.innerJoin(
222+
ArchiveItem,
223+
'archiveItem',
224+
`"archiveItem"."archiveId" = ${builder.alias}.id`,
225+
)
226+
.andWhere(`${builder.alias}."subjectType" = :subjectType`, {
227+
subjectType: validatedArgs.subjectType,
228+
})
229+
.andWhere('"archiveItem"."subjectId" = :subjectId', {
230+
subjectId: validatedArgs.subjectId,
231+
})
232+
.distinct(true)
233+
.orderBy(`${builder.alias}."periodStart"`, 'DESC')
234+
.addOrderBy(`${builder.alias}."scopeType"`, 'ASC')
235+
.addOrderBy(`${builder.alias}."scopeId"`, 'ASC');
236+
237+
return builder;
238+
},
239+
true,
240+
);
241+
},
196242
},
197243
};

0 commit comments

Comments
 (0)