Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e9ae82f
feat: add chapter name filtering to nonprofits query
amandayu255 Dec 14, 2025
69c72c6
feat: add chapter names filtering to nonprofits query
amandayu255 Dec 14, 2025
95c6306
feat: add function to retrieve chapter IDs by names
amandayu255 Dec 14, 2025
96557af
Merge branch 'develop' into feature/61-filter-nonprofits
soramicha Dec 17, 2025
6a2acdd
feat: add nonprofitChapterProjects query to retrieve projects by nonp…
amandayu255 Dec 18, 2025
cd1b889
feat: add NonprofitChapterProject type and query for chapter projects
amandayu255 Dec 18, 2025
43f65c6
feat: implement Docker-based test database setup for isolated testing
amandayu255 Dec 18, 2025
23afe67
ran prettier formatting
amandayu255 Dec 18, 2025
f2cd0ca
Merge branch 'develop' into feature/61-filter-nonprofits
amandayu255 Dec 18, 2025
7807039
feat: enhance filtering logic for nonprofits with dynamic conditions
amandayu255 Dec 18, 2025
4f57c72
feat: add support for pagination in nonprofit queries
amandayu255 Dec 27, 2025
3929bfd
feat: add NonprofitChapterInfo type and update Nonprofit schema to in…
amandayu255 Dec 27, 2025
4ebcc75
feat: refactor nonprofit resolvers to simplify logic and improve perf…
amandayu255 Dec 27, 2025
5d128b4
fix: add missing newline at end of nonprofit schema file
amandayu255 Dec 27, 2025
7a791c3
feat: enhance nonprofit filtering logic with dynamic join conditions
amandayu255 Dec 27, 2025
9c5e368
feat: add support for advanced filtering options in nonprofit queries
amandayu255 Dec 27, 2025
1d3988d
Merge branch 'develop' into feature/61-filter-nonprofits
amandayu255 Dec 27, 2025
dd59d92
fix: ensure proper formatting in configuration files and add missing …
amandayu255 Dec 27, 2025
4d684b8
fix: improve type safety in nonprofit resolvers by refining chapter f…
amandayu255 Dec 27, 2025
26b7cc8
fix: refine nonprofit query logic to handle chapter filtering and ens…
amandayu255 Jan 18, 2026
cf0f11c
fix: remove unused chapterIds parameter from nonprofit query schema
amandayu255 Jan 18, 2026
c0d3a4a
fix: remove chapterIds from GetNonprofitsFilters and improve project …
amandayu255 Jan 18, 2026
b6ca53b
fix: optimize nonprofit query logic for improved chapter filtering ac…
amandayu255 Jan 18, 2026
0742bbf
Merge branch 'develop' into feature/61-filter-nonprofits
amandayu255 Jan 18, 2026
b01f47a
fix: enhance nonprofit query logic for better chapter filtering and u…
amandayu255 Jan 19, 2026
7936aad
fix: enhance nonprofit query logic for improved chapter filtering and…
amandayu255 Jan 19, 2026
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
73 changes: 38 additions & 35 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,37 +1,40 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"ignorePatterns": [
"node_modules/**",
"*.config.mts",
"prisma/**",
"legacy**",
"generated/**"
],
"rules": {
"@typescript-eslint/no-unused-expressions": [
"error",
{ "allowShortCircuit": true, "allowTernary": true, "allowTaggedTemplates": true }
]
},
"overrides": [
{
"files": ["tests/**/*.ts", "tests/**/*.js"],
"rules": {
"@typescript-eslint/no-require-imports": "off",
"@typescript-eslint/no-unused-expressions": "off"
}
}
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"ignorePatterns": [
"node_modules/**",
"*.config.mts",
"prisma/**",
"legacy**",
"generated/**"
],
"rules": {
"@typescript-eslint/no-unused-expressions": [
"error",
{
"allowShortCircuit": true,
"allowTernary": true,
"allowTaggedTemplates": true
}
]
}

},
"overrides": [
{
"files": ["tests/**/*.ts", "tests/**/*.js"],
"rules": {
"@typescript-eslint/no-require-imports": "off",
"@typescript-eslint/no-unused-expressions": "off"
}
}
]
}
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ jobs:

- name: Stop test database
if: always()
run: docker compose down -v
run: docker compose down -v
98 changes: 49 additions & 49 deletions .github/workflows/develop_operations-api.yml
Original file line number Diff line number Diff line change
@@ -1,49 +1,49 @@
# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions
name: Build and deploy Node.js app to Azure Web App - operations-api
on:
push:
branches:
- develop
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read #This is required for actions/checkout
steps:
- uses: actions/checkout@v4
- name: Set up Node.js version
uses: actions/setup-node@v3
with:
node-version: '24.x'
- name: Upload artifact for deployment job
uses: actions/upload-artifact@v4
with:
name: node-app
path: .
deploy:
runs-on: ubuntu-latest
needs: build
steps:
- name: Download artifact from build job
uses: actions/download-artifact@v4
with:
name: node-app
- name: 'Deploy to Azure Web App'
id: deploy-to-webapp
uses: azure/webapps-deploy@v3
with:
app-name: 'operations-api'
slot-name: 'Production'
package: .
publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_FD3F1E6E02E34288BF14598390887468 }}
# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions

name: Build and deploy Node.js app to Azure Web App - operations-api

on:
push:
branches:
- develop
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read #This is required for actions/checkout

steps:
- uses: actions/checkout@v4

- name: Set up Node.js version
uses: actions/setup-node@v3
with:
node-version: '24.x'

- name: Upload artifact for deployment job
uses: actions/upload-artifact@v4
with:
name: node-app
path: .

deploy:
runs-on: ubuntu-latest
needs: build

steps:
- name: Download artifact from build job
uses: actions/download-artifact@v4
with:
name: node-app

- name: 'Deploy to Azure Web App'
id: deploy-to-webapp
uses: azure/webapps-deploy@v3
with:
app-name: 'operations-api'
slot-name: 'Production'
package: .
publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_FD3F1E6E02E34288BF14598390887468 }}
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,8 @@ Command to enter in terminal to access tables:

- docker compose exec -T test-db psql -U postgres -d test_db
- \dt
- SQL commands such as (SELECT * FROM volunteers;)
- SQL commands such as (SELECT \* FROM volunteers;)

To pause test, insert this: await new Promise(() => {});

It helps you see when data is created in the local database!
It helps you see when data is created in the local database!
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ services:
POSTGRES_PASSWORD: test_password
POSTGRES_DB: test_db
ports:
- "5433:5432" # Map to port 5433 to avoid conflicts with local PostgreSQL
- '5433:5432' # Map to port 5433 to avoid conflicts with local PostgreSQL
volumes:
- test-db-data:/var/lib/postgresql/data
- ./db-init:/docker-entrypoint-initdb.d # Initialization scripts
- ./db-init:/docker-entrypoint-initdb.d # Initialization scripts
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d test_db"]
test: ['CMD-SHELL', 'pg_isready -U postgres -d test_db']
interval: 5s
timeout: 5s
retries: 5
Expand Down
102 changes: 96 additions & 6 deletions src/api/graphql/resolvers/nonprofits.resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,71 @@ import {
getNonprofitsWithFilters,
updateNonprofit,
updateNonprofitSchema,
getChapterIdsByNames,
} from '../../../core';
import { GraphQLError } from 'graphql';
import { z } from 'zod';
import type {
StatusType,
NonprofitSortOption,
} from '../../../core/services/nonprofits.service';
import { prisma } from '../../../config/database';
import { status_type } from '@prisma/client';

// Infer TypeScript types directly from your Zod schemas
type CreateNonprofitInput = z.infer<typeof createNonprofitSchema>;
type UpdateNonprofitInput = z.infer<typeof updateNonprofitSchema>;

interface NonprofitsQueryArgs {
chapterIds?: string[];
chapterNames?: string[];
statuses?: StatusType[];
sort?: NonprofitSortOption[];
}

interface GetNonprofitsWithFiltersArgs {
statuses?: StatusType[];
sort?: NonprofitSortOption[];
chapterIds?: string[];
}

interface NonprofitParent {
nonprofit_id: string;
}

interface ChapterRow {
project_status: status_type;
chapter_id: string | null;
chapters: { chapter_id: string; name: string } | null;
}

export const nonprofitResolvers = {
Query: {
nonprofits: (
nonprofits: async (
_parent: unknown,
{ chapterIds, statuses, sort }: NonprofitsQueryArgs
{ chapterNames, statuses, sort }: NonprofitsQueryArgs
) => {
return getNonprofitsWithFilters({ chapterIds, statuses, sort });
let resolvedChapterIds: string[] | undefined;

if (chapterNames?.length) {
resolvedChapterIds = await getChapterIdsByNames(chapterNames);
if (!resolvedChapterIds.length) return [];
}

const serviceArgs: GetNonprofitsWithFiltersArgs = {
statuses,
sort,
...(resolvedChapterIds?.length
? { chapterIds: resolvedChapterIds }
: {}),
};

const nonprofits = await getNonprofitsWithFilters(serviceArgs);

return nonprofits.map((np) => ({
...np,
chapters: [],
}));
},

nonprofit: async (_parent: unknown, { id }: { id: string }) => {
const nonprofit = await getNonprofitById(id);
if (!nonprofit) {
Expand All @@ -45,9 +84,58 @@ export const nonprofitResolvers = {
{ code: 'NOT_FOUND' }
);
}
return nonprofit;

return {
...nonprofit,
chapters: [],
};
},
},

Nonprofit: {
chapters: async (parent: NonprofitParent) => {
const rows = (await prisma.nonprofit_chapter_project.findMany({
where: { nonprofit_id: parent.nonprofit_id },
select: {
project_status: true,
chapter_id: true,
chapters: {
select: {
chapter_id: true,
name: true,
},
},
},
orderBy: { created_at: 'desc' },
})) as ChapterRow[];

if (!rows.length) return [];

const rowsWithChapter = rows.filter(
(
r
): r is ChapterRow & {
chapters: { chapter_id: string; name: string };
} => r.chapters !== null
);

const seen = new Set<string>();
const deduped = rowsWithChapter.filter((r) => {
const id = r.chapters.chapter_id;
if (seen.has(id)) return false;
seen.add(id);
return true;
});

return deduped.map((r) => ({
chapter_id: r.chapters.chapter_id,
chapter_name: r.chapters.name,
project_status:
r.project_status === status_type.ACTIVE ? 'ACTIVE' : 'INACTIVE',
}));
},
},

Mutation: {
createNonprofit: (
_parent: unknown,
Expand All @@ -56,6 +144,7 @@ export const nonprofitResolvers = {
const validatedInput = createNonprofitSchema.parse(input);
return createNonprofit(validatedInput);
},

updateNonprofit: (
_parent: unknown,
{
Expand All @@ -66,6 +155,7 @@ export const nonprofitResolvers = {
const validatedInput = updateNonprofitSchema.parse(input);
return updateNonprofit(nonprofit_id, validatedInput);
},

deleteNonprofit: (_parent: unknown, { id }: { id: string }) => {
return deleteNonprofit(id);
},
Expand Down
10 changes: 9 additions & 1 deletion src/api/graphql/schemas/nonprofits.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ export const nonprofitSchemaString = `
STATUS
}

type NonprofitChapterInfo {
chapter_id: ID!
chapter_name: String!
project_status: StatusType!
}

type Nonprofit {
nonprofit_id: ID!
name: String!
Expand All @@ -16,14 +22,16 @@ export const nonprofitSchemaString = `
created_at: String!
updated_at: String!
status: StatusType!
chapters: [NonprofitChapterInfo!]!
}

type Query {
nonprofits(
chapterIds: [ID!]
chapterNames: [String!]
statuses: [StatusType!]
sort: [NonprofitSortOption!]
): [Nonprofit!]!

nonprofit(id: ID!): Nonprofit
}

Expand Down
Loading