Skip to content

Commit ab00ce9

Browse files
committed
added quest admin overview
1 parent 47f2d83 commit ab00ce9

8 files changed

Lines changed: 535 additions & 3 deletions

File tree

backend/app/graphql/resolvers/quest.ts

Lines changed: 156 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import {
22
Company,
3+
User,
34
} from "@generated/type-graphql";
45
import {
6+
Arg,
57
Authorized,
68
Ctx,
9+
Field,
710
Info,
11+
Int,
12+
ObjectType,
813
Query,
914
Resolver,
1015
} from "type-graphql";
@@ -21,25 +26,62 @@ import {
2126
toSelect,
2227
} from "../helpers/resolver";
2328
import {
29+
listQuestApplicants,
2430
listQuestCompanies,
2531
} from "../../services/quest-service";
2632
import {
2733
getCurrentSeasonId,
2834
scopeCompanyApplicationsToSeason,
2935
} from "../../services/season-service";
36+
import {
37+
Role,
38+
} from "../../helpers/auth";
3039
import {
3140
transformSelect as transformSelectCompanies,
3241
} from "./company";
3342

43+
@ObjectType()
44+
export class QuestApplicantEntry {
45+
@Field(() => User)
46+
user!: User;
47+
48+
@Field(() => Int)
49+
points!: number;
50+
51+
@Field(() => Date)
52+
scannedAt!: Date;
53+
}
54+
55+
@ObjectType()
56+
export class QuestCompanyStats {
57+
@Field(() => Company)
58+
company!: Company;
59+
60+
@Field(() => Int)
61+
applicantCount!: number;
62+
}
63+
3464
@Resolver(() => Company)
3565
export class QuestResolver {
3666
@Authorized()
3767
@Query(() => [ Company ])
3868
async questCompanies(
3969
@Ctx() ctx: Context,
4070
@Info() info: GraphQLResolveInfo,
71+
@Arg("seasonUid", () => String, { nullable: true })
72+
seasonUid: string | null = null,
4173
): Promise<Company[]> {
42-
const seasonId = await getCurrentSeasonId(ctx.prisma);
74+
let seasonId: number | null;
75+
76+
if (seasonUid) {
77+
const season = await ctx.prisma.season.findUnique({
78+
where: { uid: seasonUid },
79+
select: { id: true },
80+
});
81+
seasonId = season?.id ?? null;
82+
} else {
83+
seasonId = await getCurrentSeasonId(ctx.prisma);
84+
}
4385

4486
if (null === seasonId) {
4587
return [];
@@ -53,4 +95,117 @@ export class QuestResolver {
5395

5496
return listQuestCompanies(ctx.prisma, seasonId, select) as unknown as Promise<Company[]>;
5597
}
98+
99+
@Authorized(Role.Admin)
100+
@Query(() => [ QuestApplicantEntry ])
101+
async questApplicants(
102+
@Ctx() ctx: Context,
103+
@Arg("seasonUid", () => String)
104+
seasonUid: string,
105+
@Arg("companyUid", () => String)
106+
companyUid: string,
107+
): Promise<QuestApplicantEntry[]> {
108+
const season = await ctx.prisma.season.findUnique({
109+
where: { uid: seasonUid },
110+
select: { id: true },
111+
});
112+
113+
if (!season) {
114+
return [];
115+
}
116+
117+
const company = await ctx.prisma.company.findUnique({
118+
where: { uid: companyUid },
119+
select: { id: true },
120+
});
121+
122+
if (!company) {
123+
return [];
124+
}
125+
126+
const applicants = await listQuestApplicants(ctx.prisma, company.id, season.id);
127+
128+
if (0 === applicants.length) {
129+
return [];
130+
}
131+
132+
const applicantUserIds = applicants.map((a) => a.userId);
133+
134+
const users = await ctx.prisma.user.findMany({
135+
where: { id: { in: applicantUserIds } },
136+
select: {
137+
id: true,
138+
uid: true,
139+
firstName: true,
140+
lastName: true,
141+
email: true,
142+
phone: true,
143+
},
144+
});
145+
146+
const usersById = new Map(users.map((u) => [ u.id, u ]));
147+
148+
return applicants
149+
.map((a) => {
150+
const user = usersById.get(a.userId);
151+
if (!user) {
152+
return null;
153+
}
154+
return {
155+
user: user as unknown as User,
156+
points: a.points,
157+
scannedAt: a.scannedAt,
158+
};
159+
})
160+
.filter((entry): entry is QuestApplicantEntry => null !== entry)
161+
;
162+
}
163+
164+
@Authorized(Role.Admin)
165+
@Query(() => [ QuestCompanyStats ])
166+
async questCompaniesWithStats(
167+
@Ctx() ctx: Context,
168+
@Arg("seasonUid", () => String)
169+
seasonUid: string,
170+
): Promise<QuestCompanyStats[]> {
171+
const season = await ctx.prisma.season.findUnique({
172+
where: { uid: seasonUid },
173+
select: { id: true },
174+
});
175+
176+
if (!season) {
177+
return [];
178+
}
179+
180+
const companies = await listQuestCompanies(ctx.prisma, season.id, {
181+
id: true,
182+
uid: true,
183+
brandName: true,
184+
});
185+
186+
if (0 === companies.length) {
187+
return [];
188+
}
189+
190+
const companyIds = companies.map((c) => c.id as number);
191+
192+
const counts = await ctx.prisma.companyScannedUser.groupBy({
193+
by: [ "companyId" ],
194+
where: {
195+
seasonId: season.id,
196+
questEntered: true,
197+
companyId: { in: companyIds },
198+
},
199+
_count: { userId: true },
200+
});
201+
202+
const countByCompanyId = new Map(
203+
counts.map((row) => [ row.companyId, row._count.userId ]),
204+
);
205+
206+
return companies.map((c) => ({
207+
company: c as unknown as Company,
208+
applicantCount: countByCompanyId.get(c.id as number) ?? 0,
209+
}));
210+
}
56211
}

codegen.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export default {
3333
"./pages/profile/me/reservations/**/*.vue",
3434
"./pages/admin/season/*.vue",
3535
"./pages/admin/season/[season]/applications/approval.vue",
36+
"./pages/admin/season/[season]/quest-applicants.vue",
3637
"./pages/participants.vue",
3738
"./pages/gate-guardian/**/*.vue",
3839
"./pages/admin/users/scanners.vue",

graphql/client/gql.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const documents = {
2727
"\n mutation LinkUnmatchedInternship($input: LinkUnmatchedInternshipInput!) {\n linkUnmatchedInternship(input: $input) {\n uid\n externalCompany\n position\n workingPeriodStart\n workingPeriodEnd\n places\n signed\n description\n }\n }\n ": types.LinkUnmatchedInternshipDocument,
2828
"\n query PageAdminSeasonLiveVoteResults_Data($seasonUid: String!) {\n liveVoteResults(seasonUid: $seasonUid) {\n option\n voteCount\n }\n\n liveVoteComments(seasonUid: $seasonUid) {\n id\n comment\n createdAt\n forUser {\n name\n }\n }\n }": types.PageAdminSeasonLiveVoteResults_DataDocument,
2929
"\n mutation PageAdminSeasonLiveVoteResults_DeleteComment($commentId: Int!) {\n deleteLiveVoteComment(commentId: $commentId)\n }\n ": types.PageAdminSeasonLiveVoteResults_DeleteCommentDocument,
30+
"\n query QuestCompaniesWithStatsForAdmin($seasonUid: String!) {\n questCompaniesWithStats(seasonUid: $seasonUid) {\n company {\n uid\n brandName\n }\n applicantCount\n }\n }\n ": types.QuestCompaniesWithStatsForAdminDocument,
31+
"\n query QuestApplicants($seasonUid: String!, $companyUid: String!) {\n questApplicants(seasonUid: $seasonUid, companyUid: $companyUid) {\n user {\n uid\n name\n email\n phone\n }\n points\n scannedAt\n }\n }\n ": types.QuestApplicantsDocument,
3032
"\n query PageAdminSeasonRatingsCompanies_Data($season: String!) {\n season(uid: $season) {\n applications {\n forCompany {\n uid\n legalName\n brandName\n ratings {\n averageRating\n component\n }\n }\n }\n }\n }\n ": types.PageAdminSeasonRatingsCompanies_DataDocument,
3133
"\n query PageAdminSeasonRatingsQrCodesData($season: String!) {\n season(uid: $season) {\n name\n startsAt\n endsAt\n applications {\n forCompany {\n uid\n legalName\n brandName\n }\n approval {\n booth\n talkParticipants\n workshopParticipants\n fusionParticipants\n panel\n }\n }\n }\n }\n ": types.PageAdminSeasonRatingsQrCodesDataDocument,
3234
"\n query PageAdminSeasonReservationsScannedData($season: String!) {\n gateGuardianScanList(season: $season) {\n eventType\n eventId\n forUser {\n uid\n name\n email\n phone\n }\n scannedBy {\n uid\n name\n }\n forCalendarItem {\n uid\n companies {\n uid\n brandName\n }\n forTalk {\n uid\n titleHr\n titleEn\n }\n forWorkshop {\n uid\n titleHr\n titleEn\n }\n forPanel {\n uid\n name\n }\n }\n scannedAt\n }\n }\n ": types.PageAdminSeasonReservationsScannedDataDocument,
@@ -130,6 +132,14 @@ export function graphql(source: "\n query PageAdminSeasonLiveVoteResults_Data
130132
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
131133
*/
132134
export function graphql(source: "\n mutation PageAdminSeasonLiveVoteResults_DeleteComment($commentId: Int!) {\n deleteLiveVoteComment(commentId: $commentId)\n }\n "): (typeof documents)["\n mutation PageAdminSeasonLiveVoteResults_DeleteComment($commentId: Int!) {\n deleteLiveVoteComment(commentId: $commentId)\n }\n "];
135+
/**
136+
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
137+
*/
138+
export function graphql(source: "\n query QuestCompaniesWithStatsForAdmin($seasonUid: String!) {\n questCompaniesWithStats(seasonUid: $seasonUid) {\n company {\n uid\n brandName\n }\n applicantCount\n }\n }\n "): (typeof documents)["\n query QuestCompaniesWithStatsForAdmin($seasonUid: String!) {\n questCompaniesWithStats(seasonUid: $seasonUid) {\n company {\n uid\n brandName\n }\n applicantCount\n }\n }\n "];
139+
/**
140+
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
141+
*/
142+
export function graphql(source: "\n query QuestApplicants($seasonUid: String!, $companyUid: String!) {\n questApplicants(seasonUid: $seasonUid, companyUid: $companyUid) {\n user {\n uid\n name\n email\n phone\n }\n points\n scannedAt\n }\n }\n "): (typeof documents)["\n query QuestApplicants($seasonUid: String!, $companyUid: String!) {\n questApplicants(seasonUid: $seasonUid, companyUid: $companyUid) {\n user {\n uid\n name\n email\n phone\n }\n points\n scannedAt\n }\n }\n "];
133143
/**
134144
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
135145
*/

0 commit comments

Comments
 (0)