Skip to content

Commit 89e6618

Browse files
lapc506claude
andauthored
feat(adoptions): implement adoption listing and application flow with state management (#8)
Adds the complete adoption flow backend including: - AdoptionListing and AdoptionApplication entities with full state machines - Listing lifecycle: DRAFT -> ACTIVE -> PAUSED -> ADOPTED/WITHDRAWN - Application lifecycle: SUBMITTED -> IN_REVIEW -> VISIT_SCHEDULED -> VISIT_COMPLETED -> APPROVED/REJECTED - Publisher validation, max 3 active applications per user, cascading close on approval - Push notifications via NotificationService for key events - GraphQL resolvers, DTOs with class-validator, cursor pagination for listings - New animal fields (size, ageCategory, isSterilized) and READY_FOR_ADOPTION status - Unit tests for both services covering state transitions, validations, and cascades Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1cb8d27 commit 89e6618

21 files changed

+1900
-1
lines changed

apps/backend/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@
112112
],
113113
"coverageDirectory": "../coverage",
114114
"testEnvironment": "node",
115+
"moduleNameMapper": {
116+
"^(\\.{1,2}/.*)\\.js$": "$1"
117+
},
115118
"setupFilesAfterEnv": [
116119
"<rootDir>/../jest.setup.js"
117120
]
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql';
2+
import { UseGuards } from '@nestjs/common';
3+
import { AdoptionApplication } from './entities/adoption-application.entity';
4+
import { AdoptionApplicationsService } from './adoption-applications.service';
5+
import { SubmitAdoptionApplicationInput } from './dto/submit-adoption-application.input';
6+
import { ApplicationStatus } from './enums/application-status.enum';
7+
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
8+
import { GqlUser } from '../auth/gql-user.decorator';
9+
import { User } from '../users/entities/user.entity';
10+
11+
@Resolver(() => AdoptionApplication)
12+
export class AdoptionApplicationsResolver {
13+
constructor(
14+
private readonly applicationsService: AdoptionApplicationsService,
15+
) {}
16+
17+
@Query(() => [AdoptionApplication], { name: 'adoptionApplications' })
18+
@UseGuards(JwtAuthGuard)
19+
async getAdoptionApplications(
20+
@Args('listingId', { type: () => ID }) listingId: string,
21+
@Args('status', { type: () => ApplicationStatus, nullable: true })
22+
status?: ApplicationStatus,
23+
): Promise<AdoptionApplication[]> {
24+
return this.applicationsService.findByListing(listingId, status);
25+
}
26+
27+
@Query(() => [AdoptionApplication], { name: 'myAdoptionApplications' })
28+
@UseGuards(JwtAuthGuard)
29+
async getMyAdoptionApplications(
30+
@GqlUser() user: User,
31+
): Promise<AdoptionApplication[]> {
32+
return this.applicationsService.findMyApplications(user.id);
33+
}
34+
35+
@Mutation(() => AdoptionApplication)
36+
@UseGuards(JwtAuthGuard)
37+
async submitAdoptionApplication(
38+
@GqlUser() user: User,
39+
@Args('input') input: SubmitAdoptionApplicationInput,
40+
): Promise<AdoptionApplication> {
41+
return this.applicationsService.submit(input, user.id);
42+
}
43+
44+
@Mutation(() => AdoptionApplication)
45+
@UseGuards(JwtAuthGuard)
46+
async reviewAdoptionApplication(
47+
@GqlUser() user: User,
48+
@Args('applicationId', { type: () => ID }) applicationId: string,
49+
): Promise<AdoptionApplication> {
50+
return this.applicationsService.review(applicationId, user.id);
51+
}
52+
53+
@Mutation(() => AdoptionApplication)
54+
@UseGuards(JwtAuthGuard)
55+
async scheduleVisit(
56+
@GqlUser() user: User,
57+
@Args('applicationId', { type: () => ID }) applicationId: string,
58+
@Args('scheduledDate') scheduledDate: Date,
59+
): Promise<AdoptionApplication> {
60+
return this.applicationsService.scheduleVisit(
61+
applicationId,
62+
user.id,
63+
scheduledDate,
64+
);
65+
}
66+
67+
@Mutation(() => AdoptionApplication)
68+
@UseGuards(JwtAuthGuard)
69+
async completeVisit(
70+
@GqlUser() user: User,
71+
@Args('applicationId', { type: () => ID }) applicationId: string,
72+
@Args('notes') notes: string,
73+
): Promise<AdoptionApplication> {
74+
return this.applicationsService.completeVisit(
75+
applicationId,
76+
user.id,
77+
notes,
78+
);
79+
}
80+
81+
@Mutation(() => AdoptionApplication)
82+
@UseGuards(JwtAuthGuard)
83+
async approveAdoptionApplication(
84+
@GqlUser() user: User,
85+
@Args('applicationId', { type: () => ID }) applicationId: string,
86+
): Promise<AdoptionApplication> {
87+
return this.applicationsService.approve(applicationId, user.id);
88+
}
89+
90+
@Mutation(() => AdoptionApplication)
91+
@UseGuards(JwtAuthGuard)
92+
async rejectAdoptionApplication(
93+
@GqlUser() user: User,
94+
@Args('applicationId', { type: () => ID }) applicationId: string,
95+
@Args('reason') reason: string,
96+
): Promise<AdoptionApplication> {
97+
return this.applicationsService.reject(applicationId, user.id, reason);
98+
}
99+
}

0 commit comments

Comments
 (0)