Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 74 additions & 16 deletions src/api/graphql/resolvers/volunteers.resolvers.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,96 @@
// src/api/graphql/resolvers/volunteers.resolvers.ts

import { z } from 'zod';
import {
createVolunteer,
createVolunteerSchema,
deleteVolunteer,
getAllVolunteers,
getVolunteerById,
updateVolunteer,
updateVolunteerSchema,
} from '../../../core';
import { GraphQLError } from 'graphql';
import { z } from 'zod';
import {
PrismaClient,
Prisma,
volunteer_status as VStatus,
} from '@prisma/client';

const prisma = new PrismaClient();

// Infer TypeScript types directly from your Zod schemas
type CreateVolunteerInput = z.infer<typeof createVolunteerSchema>;
type UpdateVolunteerInput = z.infer<typeof updateVolunteerSchema>;

export interface VolunteerFilterInput {
chapters?: string[];
roles?: string[];
statuses?: (import('@prisma/client').volunteer_status | string)[];
}

export const volunteerResolvers = {
Query: {
volunteers: () => getAllVolunteers(),
// GraphQL:
// volunteers(filter, limit, offset): [Volunteer!]!
volunteers: async (
_parent: unknown,
args: {
filter?: VolunteerFilterInput;
limit?: number;
offset?: number;
}
) => {
const { filter, limit = 50, offset = 0 } = args;

const where: Prisma.volunteersWhereInput = {};

// status filter
if (filter?.statuses?.length) {
const statuses = filter.statuses
.map((s) => (typeof s === 'string' ? s.toUpperCase() : s))
.filter((s): s is VStatus => s === 'STUDENT' || s === 'PROFESSIONAL');

if (statuses.length) {
where.volunteer_status = { in: statuses };
}
}

// chapter filter via related chapters model
if (filter?.chapters?.length) {
where.chapters = {
is: {
name: { in: filter.chapters },
},
};
}

// role filter via volunteer_assignment → roles
if (filter?.roles?.length) {
where.volunteer_assignment = {
some: {
roles: {
name: { in: filter.roles },
},
},
};
}

// Tests expect an array of volunteers here
return prisma.volunteers.findMany({
where,
skip: offset,
take: limit,
orderBy: [{ last_name: 'asc' }, { first_name: 'asc' }],
});
},

// GraphQL: volunteer(id: ID!): Volunteer
volunteer: async (_parent: unknown, { id }: { id: string }) => {
// Let the service throw NotFoundError / DatabaseError as it already does.
// GraphQL will surface the error message in `errors[0].message`.
const volunteer = await getVolunteerById(id);
if (!volunteer) {
throw new GraphQLError(
'Volunteer not found',
undefined, // nodes
undefined, // source
undefined, // positions
undefined, // path
undefined, // originalError
{ code: 'NOT_FOUND' } // extensions
);
}
return volunteer;
},
},

Mutation: {
createVolunteer: (
_parent: unknown,
Expand Down
19 changes: 17 additions & 2 deletions src/api/graphql/schemas/volunteers.schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export const volunteerSchemaString = `
# Using the enum from your Prisma schema
enum StatusType {
STUDENT
PROFESSIONAL
Expand All @@ -11,6 +10,12 @@ export const volunteerSchemaString = `
BOTH
}

input VolunteerFilterInput {
chapters: [String!]
roles: [String!]
statuses: [String!]
}

type Volunteer {
volunteer_id: ID!
first_name: String!
Expand All @@ -27,8 +32,18 @@ export const volunteerSchemaString = `
updated_at: String!
}

type VolunteerConnection {
nodes: [Volunteer!]!
totalCount: Int!
}

type Query {
volunteers: [Volunteer!]!
volunteers(
filter: VolunteerFilterInput
limit: Int = 50
offset: Int = 0
): [Volunteer!]!

volunteer(id: ID!): Volunteer
}

Expand Down
13 changes: 8 additions & 5 deletions tests/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@ beforeEach(async () => {

// Skip cleanup for unit tests since they mock the database
if (!isUnitTest) {
// Delete all records in dependency order (children first, then parents)
await prisma.chapters.deleteMany({});
await prisma.volunteer_assignment.deleteMany({});
await prisma.volunteer_role_project.deleteMany({});
await prisma.volunteer_history.deleteMany({});

await prisma.nonprofit_chapter_project.deleteMany({});
await prisma.sponsor_chapter.deleteMany({});

await prisma.projects.deleteMany({});
// Add other models as needed:
// await prisma.users.deleteMany({});
// await prisma.events.deleteMany({});
await prisma.chapters.deleteMany({});
}
} catch (error) {
console.error('Failed to clean database between tests:', error);
Expand Down