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
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20.16.x
node-version: 24.12.0

- name: Install PNPM
run: |
Expand All @@ -41,4 +41,5 @@ jobs:

- name: Run Build
run: |
mv prisma.config.ts prisma.config.ts.bak

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard the Prisma config rename to prevent avoidable CI failures.

Line 44 fails the job if prisma.config.ts is absent (or already renamed). That makes the build step brittle.

🛠️ Suggested hardening
       - name: Run Build
         run: |
-          mv prisma.config.ts prisma.config.ts.bak
+          if [ -f prisma.config.ts ]; then
+            mv prisma.config.ts prisma.config.ts.bak
+          fi
           pnpm run build
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
mv prisma.config.ts prisma.config.ts.bak
if [ -f prisma.config.ts ]; then
mv prisma.config.ts prisma.config.ts.bak
fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml at line 44, The CI job in .github/workflows/ci.yml
currently runs the command "mv prisma.config.ts prisma.config.ts.bak"
unconditionally which will fail if prisma.config.ts is missing or already
renamed; change the step to guard the rename by checking for the file's
existence before attempting the move (or use the mv option that skips when
target is absent) so the job no longer errors when prisma.config.ts is not
present; update the step containing the "mv prisma.config.ts
prisma.config.ts.bak" invocation to perform that existence check or safe-move
behavior.

pnpm run build
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "dist/server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "npx prisma generate --schema ./prisma/schema && tsup src --out-dir dist --sourcemap inline",
"build": "pnpm exec prisma generate --schema ./prisma/schema && tsup src --out-dir dist --sourcemap inline",
"dev": "ts-node-dev --respawn -r tsconfig-paths/register src/server.ts",
"start": "node -r tsconfig-paths/register dist/server.js",
"prisma:migrate": "prisma migrate dev --schema ./prisma/schema",
Expand All @@ -32,7 +32,6 @@
"google-auth-library": "^10.5.0",
"jsonwebtoken": "^9.0.3",
"pino-pretty": "^13.1.3",
"prisma": "^7.4.2",
"resend": "^6.9.3",
"uuid": "^13.0.0",
"zod": "^4.1.12"
Expand All @@ -45,6 +44,7 @@
"eslint-plugin-perfectionist": "^4.15.1",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-unused-imports": "^4.2.0",
"prisma": "^7.4.2",
"ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0",
"tsconfig-paths": "^4.2.0",
Expand Down
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { OrganizationsRepository } from '@/adapters/outbound/prisma/repositories
import { Route } from '@/adapters/inbound/http/decorators/route-decorator'
import type { FastifyReply, FastifyRequest } from 'fastify'
import { GetAllOrganizationsUseCase } from '@/core/use-cases/organizations/get-all-organizations'
import { getAllOrganizationsQuerySchema } from './schema'

export class GetAllOrganizationsController {
private organizationRepository: OrganizationsRepository
Expand All @@ -14,7 +15,9 @@ export class GetAllOrganizationsController {

@Route('GET', '/organizations')
async execute(request: FastifyRequest, reply: FastifyReply): Promise<void> {
const response = await this.useCase.execute()
const { name, ong_type } = getAllOrganizationsQuerySchema.parse(request.query)

const response = await this.useCase.execute({ name, ong_type })

return reply.status(200).send(response)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { z } from 'zod'

const ongCategoryEnum = z.enum([
'animais',
'meioAmbiente',
'educacao',
'saude',
'direitosHumanos',
'combateAFome',
'criancasEAdolescentes',
'idosos',
'pessoasComDeficiencia',
'moradoresDeRua',
'igualdadeDeGenero',
'refugiadosEImigrantes',
'protecaoAnimal',
'desenvolvimentoComunitario',
'culturaEArte',
'esporteEInclusao',
'voluntariadoEDoacoes',
'tecnologiaSocial',
'direitosDasMulheres',
'outros'
])

export const getAllOrganizationsQuerySchema = z.object({
name: z.string().optional(),
ong_type: ongCategoryEnum.optional()
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Prisma } from '@prisma-generated'
import { OngCategory, Prisma } from '@prisma-generated'
import { randomUUID } from 'crypto'
import {
OrganizationInterface,
Expand Down Expand Up @@ -65,8 +65,14 @@ export class OrganizationsRepository implements OrganizationInterface {
})
}

getAllOrganizations = async () => {
getAllOrganizations = async (filters?: { name?: string; ong_type?: OngCategory }) => {
return await prisma.organization.findMany({
where: {
organization_profile: {
...(filters?.name && { name: { contains: filters.name, mode: 'insensitive' } }),
...(filters?.ong_type && { ong_type: filters.ong_type })
}
},
Comment on lines +70 to +75

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify optional relation cardinality and current repository filter shape.
rg -n "organization_profile OrganizationProfile\\?" prisma/schema/account.prisma -C3
rg -n "getAllOrganizations\\s*=\\s*async" src/adapters/outbound/prisma/repositories/organization-repository.ts -A30

Repository: ifsp-projects/backend

Length of output: 1177


🏁 Script executed:

#!/bin/bash
# 1. Check if organization_profile can be null/missing (relation cardinality)
rg "organization_profile" prisma/schema/account.prisma -B5 -A5

# 2. Check if there are tests for getAllOrganizations
fd ".*test.*" -e ts -e js | xargs grep -l "getAllOrganizations" 2>/dev/null | head -5

# 3. Look for how organizations are created/seeded
fd ".*seed.*" -e ts -e js | head -5

# 4. Check for any migration or validation logic around organization_profile
rg "organization_profile" src/ -l | head -10

Repository: ifsp-projects/backend

Length of output: 1197


🏁 Script executed:

#!/bin/bash
# 1. Check the interface/port definition
cat src/core/domain/ports/repositories/organization-repository.ts

# 2. Look for test files
fd "getAllOrganizations|organization.*test" . -e ts -e js

# 3. Check how this method is used in controllers
rg "getAllOrganizations" src/ -B2 -A2

Repository: ifsp-projects/backend

Length of output: 9161


🏁 Script executed:

#!/bin/bash
# 1. Check the actual usage in the controller to see what return type is expected
cat src/adapters/inbound/http/controllers/organizations/get-all-organizations/index.ts

# 2. Look for any test files to understand intended behavior
find . -type f -name "*.test.*" -o -name "*.spec.*" | xargs grep -l "getAllOrganizations" 2>/dev/null | head -5

# 3. Check if there's any documentation or comments about the relation
rg "organization_profile" src/adapters/outbound/prisma/repositories/organization-repository.ts -B3 -A10

Repository: ifsp-projects/backend

Length of output: 2745


🏁 Script executed:

#!/bin/bash
# Verify if organizations can actually exist without profiles
# Check creation patterns and any data about orgs without profiles

# 1. Look at how organizations are typically created
rg "prisma.organization.create|Organization.create" src/ -B3 -A5

# 2. Check if there are migrations that might shed light on this
find . -name "*.sql" -o -path "*/migrations/*" 2>/dev/null | head -10

# 3. Check the organizational structure to understand the domain
cat src/core/domain/ports/repositories/organization-profile-repository.ts | head -30

# 4. Look for any explicit checks or comments about organization without profile
rg "organization.*profile.*null|organization.*without.*profile" src/ -i

Repository: ifsp-projects/backend

Length of output: 3141


🏁 Script executed:

#!/bin/bash
# Critical check: Does Prisma's empty object filter actually exclude organizations without profiles?
# Let's look at any documentation or tests that show the intended behavior

# 1. Check the return type expectations
rg "getAllOrganizations.*Promise" src/core -A1

# 2. Look for any seeders or fixtures that might show the expected data state
find . -name "*seed*" -o -name "*fixture*" | grep -E "\.(ts|js)$"

# 3. Check if there's any explicit statement about org-profile requirement
rg "organization_profile.*required|must.*profile|without.*profile" src/ -i

# 4. Check the interface return type - it returns Organization[], not OrganizationWithProfileInclude
# This is a key detail. Let's verify what Organization vs OrganizationWithProfileInclude is
rg "type Organization[^P]|interface Organization" src/ -B2 -A5

Repository: ifsp-projects/backend

Length of output: 2793


🏁 Script executed:

#!/bin/bash
# Check the actual return type mismatch and Prisma behavior documentation
# The critical issue: does where: { organization_profile: {} } exclude orgs without profiles?

# 1. Verify the type mismatch - interface says Organization[], implementation includes profile
rg "getAllOrganizations.*=" src/adapters/outbound/prisma/repositories/organization-repository.ts -A25 | grep -E "return|include|where"

# 2. Check if there's any documentation or comments about Prisma relation filtering
rg "organization_profile.*where|where.*organization_profile" src/ -B2 -A2

# 3. Look for any GitHub issues or comments about this
find . -name "*.md" -o -name "*.txt" | xargs grep -i "organization.*profile.*filter" 2>/dev/null

# 4. Check the return type signature consistency
cat > /tmp/check_types.ts << 'EOF'
// The interface definition says:
// getAllOrganizations: (...) => Promise<Organization[]>

// But the implementation does:
// return await prisma.organization.findMany({
//   where: { organization_profile: { ... } },
//   include: { organization_profile: true }
// })

// This should return OrganizationWithProfileInclude[] or Organization[]?
// Type mismatch suggests the implementation is wrong OR the interface is wrong
EOF

# 5. Verify: when organization_profile is in where clause with empty object, 
# does Prisma exclude orgs without profile? This is the core issue.
# From Prisma docs: where relation: {} on optional relation DOES match records WITH the relation
# But to get ALL records (with or without), relation filter should not be in where at all
echo "ANALYSIS: In Prisma, where: { optional_relation: {...} } only matches records WITH that relation"
echo "Therefore, the current code WILL exclude organizations without profiles"

Repository: ifsp-projects/backend

Length of output: 389


🏁 Script executed:

#!/bin/bash
# Check if the return type is actually correct or if there's a mismatch
# The interface says Organization[] but implementation returns with includes

# 1. Look at what getAllOrganizations actually returns in practice
rg "getAllOrganizations" src/core/use-cases/organizations/get-all-organizations/ -A10

# 2. Check how the response is used by the controller
rg "useCase.execute" src/adapters/inbound/http/controllers/organizations/get-all-organizations/ -B3 -A5

# 3. Check if Organization type includes organization_profile or not
# This will tell us if the implementation matches the interface
rg "export.*Organization[^P].*=|type Organization[^P]" src/ -l

# 4. Let's check the generated Prisma types
find . -path "*prisma*" -name "*.ts" | grep -i "index\|types" | head -5

Repository: ifsp-projects/backend

Length of output: 3621


🏁 Script executed:

#!/bin/bash
# Final verification: confirm Prisma's behavior with empty relation objects in where clauses
# and verify if this impacts actual behavior

# 1. Check if organization_profile is ACTUALLY optional or if it's always created with organization
rg "organization_profile" prisma/schema/account.prisma -B5 -A5 | grep -E "model Organization|model OrganizationProfile|organization_profile"

# 2. Look at the full getAllOrganizations implementation once more for precision
sed -n '68,80p' src/adapters/outbound/prisma/repositories/organization-repository.ts

# 3. Check if any tests actually call getAllOrganizations without filters
find . -type f \( -name "*.test.ts" -o -name "*.spec.ts" \) -exec grep -l "getAllOrganizations" {} \;

Repository: ifsp-projects/backend

Length of output: 715


Filtering always requires organization_profile, unintentionally excluding organizations without profiles.

When no filters are provided to getAllOrganizations, the where: { organization_profile: {} } clause still matches only organizations that have a profile, silently excluding organizations without one. Since organization_profile is optional, this causes unexpected data loss for plain GET /organizations requests.

Conditionally apply the organization_profile filter only when actual filters exist (name or ong_type). When no filters are provided, omit the where clause entirely to return all organizations.

Suggested fix
   getAllOrganizations = async (filters?: { name?: string; ong_type?: OngCategory }) => {
+    const name = filters?.name?.trim()
+    const hasProfileFilters = Boolean(name || filters?.ong_type)
+
     return await prisma.organization.findMany({
-      where: {
-        organization_profile: {
-          ...(filters?.name && { name: { contains: filters.name, mode: 'insensitive' } }),
-          ...(filters?.ong_type && { ong_type: filters.ong_type })
-        }
-      },
+      where: hasProfileFilters
+        ? {
+            organization_profile: {
+              is: {
+                ...(name && { name: { contains: name, mode: 'insensitive' } }),
+                ...(filters?.ong_type && { ong_type: filters.ong_type })
+              }
+            }
+          }
+        : undefined,
       include: {
         organization_profile: true
       }
     })
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/adapters/outbound/prisma/repositories/organization-repository.ts` around
lines 70 - 75, The current getAllOrganizations query always includes a where: {
organization_profile: { ... } } clause which excludes organizations without
profiles; change getAllOrganizations so you only add the organization_profile
predicate when filters?.name or filters?.ong_type are present: build the Prisma
where object conditionally (omit organization_profile entirely when no filters)
and only merge ...(filters?.name && ...) and ...(filters?.ong_type && ...) into
organization_profile when filters exist, ensuring the final query omits the
where key when no profile filters are applied.

include: {
organization_profile: true
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Organization, Prisma } from '@prisma-generated'
import { OngCategory, Organization, Prisma } from '@prisma-generated'

export type OrganizationWithProfileInclude = Prisma.OrganizationGetPayload<{
include: {
Expand All @@ -10,7 +10,7 @@ export interface OrganizationInterface {
createOrganization: (
payload: Prisma.OrganizationUncheckedCreateInput
) => Promise<Organization>
getAllOrganizations: () => Promise<Organization[]>
getAllOrganizations: (filters?: { name?: string; ong_type?: OngCategory }) => Promise<Organization[]>
getOrganizationById: (
id: string
) => Promise<OrganizationWithProfileInclude | null>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { OrganizationDoesNotExistError } from '@/core/domain/exceptions/organizations'
import { OrganizationsRepository } from '@/adapters/outbound/prisma/repositories/organization-repository'
import { OngCategory } from '@prisma-generated'
import { GetAllOrganizationsUseCaseReturn } from './types'

export class GetAllOrganizationsUseCase {
constructor(
protected readonly organizationsRepository: OrganizationsRepository
) {}
) { }

execute = async (): Promise<GetAllOrganizationsUseCaseReturn> => {
const data = await this.organizationsRepository.getAllOrganizations()
execute = async (filters?: { name?: string; ong_type?: OngCategory }): Promise<GetAllOrganizationsUseCaseReturn> => {
const data = await this.organizationsRepository.getAllOrganizations(filters)

if (!data) {
throw new OrganizationDoesNotExistError()
Expand Down
Loading