- Backend (Development):
http://localhost:3000 - Frontend (Development):
http://localhost:5173 - API Documentation:
http://localhost:3000/api
Protected endpoints require a Bearer token in the header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Endpoint: POST /auth/register
Request Body:
{
"email": "donor@restaurant.com",
"password": "securepass123",
"name": "Annapurna Restaurant",
"role": "DONOR",
"phoneNumber": "+919876543210"
}Success Response (201 Created):
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "donor@restaurant.com",
"name": "Annapurna Restaurant",
"role": "DONOR"
}
},
"message": "User registered successfully"
}Error Response (400 Bad Request):
{
"success": false,
"message": "Validation failed",
"errors": [
"email must be a valid email address",
"password must be at least 6 characters"
],
"statusCode": 400
}Endpoint: POST /auth/login
Request Body:
{
"email": "donor@restaurant.com",
"password": "securepass123"
}Success Response (200 OK):
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "donor@restaurant.com",
"name": "Annapurna Restaurant",
"role": "DONOR"
}
},
"message": "Login successful"
}Error Response (401 Unauthorized):
{
"success": false,
"message": "Invalid email or password",
"statusCode": 401
}Endpoint: POST /donations
Authentication: Required (Bearer Token)
Request Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/jsonRequest Body:
{
"foodType": "Vegetable Biryani",
"quantity": 50,
"unit": "servings",
"preparationTime": "2025-01-12T10:00:00Z",
"latitude": 17.6868,
"longitude": 83.2185,
"address": "Beach Road, Visakhapatnam, Andhra Pradesh",
"imageUrl": "https://example.com/food.jpg",
"specialInstructions": "Keep refrigerated. Contains nuts."
}Success Response (201 Created):
{
"success": true,
"data": {
"id": "660e8400-e29b-41d4-a716-446655440001",
"foodType": "Vegetable Biryani",
"quantity": 50,
"unit": "servings",
"status": "ACTIVE",
"location": {
"latitude": 17.6868,
"longitude": 83.2185,
"address": "Beach Road, Visakhapatnam, Andhra Pradesh"
},
"preparationTime": "2025-01-12T10:00:00Z",
"createdAt": "2025-01-12T10:30:00Z"
},
"message": "Donation created successfully"
}Endpoint: GET /donations
Authentication: Required (Bearer Token)
Query Parameters (Optional):
| Parameter | Type | Description | Example |
|---|---|---|---|
| latitude | number | NGO's current latitude | 17.6868 |
| longitude | number | NGO's current longitude | 83.2185 |
| radius | number | Search radius in kilometers (default: 5) | 10 |
Example Request:
GET /donations?latitude=17.6868&longitude=83.2185&radius=5
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Success Response (200 OK):
{
"success": true,
"data": [
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"foodType": "Vegetable Biryani",
"quantity": 50,
"unit": "servings",
"status": "AVAILABLE",
"location": {
"latitude": 17.6868,
"longitude": 83.2185,
"address": "Beach Road, Visakhapatnam, Andhra Pradesh"
},
"donor": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Annapurna Restaurant",
"trustScore": 4.5
},
"preparationTime": "2025-01-12T10:00:00Z",
"expiryTime": "2025-01-12T18:00:00Z",
"distance": 2.3,
"imageUrl": "https://example.com/food.jpg",
"specialInstructions": "Keep refrigerated. Contains nuts.",
"createdAt": "2025-01-12T10:30:00Z"
}
],
"message": "Food listings retrieved successfully"
}Error Response (401 Unauthorized):
{
"success": false,
"message": "Unauthorized - Please login first",
"statusCode": 401
}Implementation Notes:
- Returns only donations with
status = "AVAILABLE" - Sorted by creation time (newest first)
- If latitude/longitude provided, includes
distancefield in km - If radius provided, filters results to within specified radius
- Uses PostGIS
ST_DWithinfor geospatial filtering
Endpoint: PATCH /donations/:id/claim
Authentication: Required (Bearer Token - NGO role only)
URL Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | Yes | The donation ID to claim |
Request Body:
{
"estimatedPickupTime": "2025-01-12T15:00:00Z"
}Example Request:
PATCH /donations/660e8400-e29b-41d4-a716-446655440001/claim
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
{
"estimatedPickupTime": "2025-01-12T15:00:00Z"
}Success Response (200 OK):
{
"success": true,
"data": {
"id": "660e8400-e29b-41d4-a716-446655440001",
"foodType": "Vegetable Biryani",
"quantity": 50,
"unit": "servings",
"status": "CLAIMED",
"claimedBy": {
"id": "770e8400-e29b-41d4-a716-446655440010",
"name": "Helping Hands NGO",
"contactNumber": "+919876543210"
},
"claimedAt": "2025-01-12T14:00:00Z",
"estimatedPickupTime": "2025-01-12T15:00:00Z",
"donor": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Annapurna Restaurant",
"contactNumber": "+919123456789",
"address": "Beach Road, Visakhapatnam"
},
"location": {
"latitude": 17.6868,
"longitude": 83.2185,
"address": "Beach Road, Visakhapatnam"
}
},
"message": "Food donation claimed successfully"
}Error Response (400 Bad Request - Already Claimed):
{
"success": false,
"message": "This donation has already been claimed",
"errors": ["Food status is CLAIMED. Cannot claim again."],
"statusCode": 400
}Error Response (404 Not Found):
{
"success": false,
"message": "Donation not found",
"statusCode": 404
}Error Response (403 Forbidden):
{
"success": false,
"message": "Only NGOs can claim food donations",
"statusCode": 403
}Business Rules:
- Only users with role
NGOcan claim donations - Cannot claim if status is
CLAIMED,COLLECTED, orEXPIRED - Updates status from
AVAILABLEtoCLAIMED - Records which NGO claimed it (
claimedBy) and timestamp (claimedAt) - Returns donor contact information for coordination
- Temporary lock during claim to prevent race conditions
Endpoint: PATCH /donations/:id/status
Authentication: Required (Bearer Token - Volunteer role only)
URL Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | Yes | The donation ID to update |
Request Body:
{
"status": "PICKED_UP"
}Valid Status Values:
PICKED_UP- When volunteer picks up food from donorDELIVERED- When volunteer delivers food to NGO
Status Flow:
AVAILABLE → CLAIMED → PICKED_UP → DELIVERED
↑ You are here ↑
Example Request:
PATCH /donations/660e8400-e29b-41d4-a716-446655440001/status
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
{
"status": "PICKED_UP"
}Success Response (200 OK):
{
"success": true,
"data": {
"id": "660e8400-e29b-41d4-a716-446655440001",
"foodType": "Vegetable Biryani",
"quantity": 50,
"unit": "servings",
"status": "PICKED_UP",
"pickedUpAt": "2025-02-06T14:30:00Z",
"volunteer": {
"id": "880e8400-e29b-41d4-a716-446655440020",
"name": "Ravi Kumar"
},
"donor": {
"name": "Annapurna Restaurant",
"address": "Beach Road, Visakhapatnam"
}
},
"message": "Status updated successfully"
}Error Response (400 Bad Request - Invalid Transition):
{
"success": false,
"message": "Invalid status transition",
"errors": ["Cannot change from AVAILABLE to DELIVERED. Must follow: CLAIMED → PICKED_UP → DELIVERED"],
"statusCode": 400
}Error Response (404 Not Found):
{
"success": false,
"message": "Donation not found",
"statusCode": 404
}Error Response (403 Forbidden):
{
"success": false,
"message": "Only volunteers can update donation status",
"statusCode": 403
}Endpoint: GET /auth/profile
Authentication: Required (Bearer Token)
Example Request:
GET /auth/profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Success Response (200 OK):
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "volunteer@example.com",
"name": "Ravi Kumar",
"role": "VOLUNTEER",
"phoneNumber": "+919876543210",
"isVerified": true,
"karmaPoints": 150,
"badges": ["🌱 Newcomer", "🦸 Local Hero"],
"level": 2,
"nextLevelPoints": 100,
"createdAt": "2025-01-10T10:00:00Z"
},
"message": "Profile retrieved successfully"
}Karma System:
- Volunteer delivers food: +50 karma points
- Donor contributes food: +30 karma points (awarded when delivered)
Badge Thresholds:
| Karma Points | Badge | Emoji |
|---|---|---|
| 50 | Newcomer | 🌱 |
| 100 | Local Hero | 🦸 |
| 250 | Champion | 🏆 |
| 500 | Legend | ⭐ |
| 1000 | Superhero | 💫 |
Level Calculation:
- Level 1: 0-99 points
- Level 2: 100-249 points
- Level 3: 250-499 points
- Level 4: 500-999 points
- Level 5: 1000+ points
Business Rules:
- Only users with role
VOLUNTEERcan update status - Status must follow proper flow: CLAIMED → PICKED_UP → DELIVERED
- Cannot skip steps (e.g., cannot go from CLAIMED directly to DELIVERED)
- Timestamps are automatically recorded:
pickedUpAtwhen status becomes PICKED_UPdeliveredAtwhen status becomes DELIVERED
Error Response (400 Bad Request):
{
"success": false,
"message": "Validation failed",
"errors": [
"quantity must be a positive number",
"latitude must be a number"
],
"statusCode": 400
}Error Response (401 Unauthorized):
{
"success": false,
"message": "Unauthorized - Please login first",
"statusCode": 401
}All API errors follow this structure:
{
"success": false,
"message": "Human-readable error message",
"errors": ["Detailed error 1", "Detailed error 2"],
"statusCode": 400
}| Code | Meaning | When Used |
|---|---|---|
| 200 | OK | Successful GET, PUT, DELETE |
| 201 | Created | Successful POST (resource created) |
| 400 | Bad Request | Validation error, malformed request |
| 401 | Unauthorized | Missing or invalid authentication token |
| 403 | Forbidden | Valid token but insufficient permissions |
| 404 | Not Found | Resource doesn't exist |
| 500 | Internal Server Error | Server-side error |
-
Start Backend Server:
cd backend npm run start:dev -
Open Swagger UI:
- Go to:
http://localhost:3000/api
- Go to:
-
Test an Endpoint:
- Click on any endpoint (e.g.,
POST /auth/register) - Click "Try it out" button
- Fill in the request body
- Click "Execute"
- See the response below
- Click on any endpoint (e.g.,
-
Test Protected Endpoints:
- First, login and copy the token from response
- Click "Authorize" button at top
- Enter:
Bearer <your-token> - Click "Authorize"
- Now you can test protected endpoints
// Import types from shared folder
import { User, UserRole } from '../../../shared/types/user.types';
import { FoodListing, Location } from '../../../shared/types/donation.types';
import { ApiResponse, ApiError } from '../../../shared/types/common.types';
// Import DTOs
import { LoginDto, RegisterDto, AuthResponse } from '../../../shared/dtos/auth.dto';
import { CreateDonationDto } from '../../../shared/dtos/donation.dto';async function login(credentials: LoginDto) {
try {
const response = await fetch('http://localhost:3000/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(credentials)
});
const data: ApiResponse = await response.json();
if (data.success && data.data) {
// Store token
localStorage.setItem('token', data.data.token);
return data.data.user;
} else {
throw new Error(data.message || 'Login failed');
}
} catch (error) {
console.error('Login error:', error);
throw error;
}
}async function createDonation(donation: CreateDonationDto) {
const token = localStorage.getItem('token');
const response = await fetch('http://localhost:3000/donations', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(donation)
});
const data = await response.json();
if (!data.success) {
throw new Error(data.message);
}
return data.data;
}import { FoodListing } from '../../../shared/types/donation.types';
import { ApiResponse } from '../../../shared/types/common.types';
async function getAvailableDonations(
latitude?: number,
longitude?: number,
radius: number = 5
): Promise<FoodListing[]> {
const token = localStorage.getItem('token');
// Build URL with query parameters
let url = 'http://localhost:3000/donations';
const params = new URLSearchParams();
if (latitude && longitude) {
params.append('latitude', latitude.toString());
params.append('longitude', longitude.toString());
params.append('radius', radius.toString());
}
if (params.toString()) {
url += `?${params.toString()}`;
}
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
}
});
const data: ApiResponse<FoodListing[]> = await response.json();
if (!data.success) {
throw new Error(data.message || 'Failed to fetch donations');
}
return data.data || [];
}async function claimDonation(
donationId: string,
pickupTime: string
) {
const token = localStorage.getItem('token');
const response = await fetch(
`http://localhost:3000/donations/${donationId}/claim`,
{
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
estimatedPickupTime: pickupTime
})
}
);
const data = await response.json();
if (!data.success) {
// Handle specific error cases
if (data.statusCode === 400) {
throw new Error('This food has already been claimed');
} else if (data.statusCode === 403) {
throw new Error('Only NGOs can claim donations');
}
throw new Error(data.message || 'Failed to claim donation');
}
return data.data;
}
// Usage in React component:
// const donations = await getAvailableDonations(myLat, myLong, 10);
// await claimDonation(donation.id, '2025-01-12T15:00:00Z');import { DonationStatus } from '../../../shared/types/donation.types';
/**
* Update donation status in volunteer workflow
* @param donationId - The donation to update
* @param status - New status ('PICKED_UP' or 'DELIVERED')
*/
async function updateDonationStatus(
donationId: string,
status: 'PICKED_UP' | 'DELIVERED'
) {
const token = localStorage.getItem('token');
const response = await fetch(
`http://localhost:3000/donations/${donationId}/status`,
{
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ status })
}
);
const data = await response.json();
if (!data.success) {
// Handle specific errors
if (data.statusCode === 400) {
throw new Error('Invalid status transition: ' + data.message);
} else if (data.statusCode === 403) {
throw new Error('Only volunteers can update status');
}
throw new Error(data.message || 'Failed to update status');
}
return data.data;
}
// Usage in Volunteer Component:
// Step 1: Volunteer picks up food from donor
await updateDonationStatus(donation.id, 'PICKED_UP');
// Step 2: Volunteer delivers food to NGO
await updateDonationStatus(donation.id, 'DELIVERED');function VolunteerTasks() {
const [tasks, setTasks] = useState([]);
const handlePickup = async (donationId: string) => {
try {
await updateDonationStatus(donationId, 'PICKED_UP');
toast.success('Pickup confirmed!');
// Refresh task list
} catch (error) {
toast.error(error.message);
}
};
const handleDelivery = async (donationId: string) => {
try {
await updateDonationStatus(donationId, 'DELIVERED');
toast.success('Delivery completed!');
// Refresh task list
} catch (error) {
toast.error(error.message);
}
};
return (
<div>
{tasks.map(task => (
<div key={task.id}>
<h3>{task.foodType}</h3>
<p>Status: {task.status}</p>
{task.status === 'CLAIMED' && (
<button onClick={() => handlePickup(task.id)}>
Confirm Pickup
</button>
)}
{task.status === 'PICKED_UP' && (
<button onClick={() => handleDelivery(task.id)}>
Confirm Delivery
</button>
)}
</div>
))}
</div>
);
}import { Controller, Post, Body } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { RegisterDto, LoginDto } from './dto/auth.dto';
@ApiTags('Authentication')
@Controller('auth')
export class AuthController {
@Post('register')
@ApiOperation({ summary: 'Register a new user' })
async register(@Body() dto: RegisterDto) {
const user = await this.authService.register(dto);
return {
success: true,
data: user,
message: 'User registered successfully'
};
}
@Post('login')
@ApiOperation({ summary: 'Login user' })
async login(@Body() dto: LoginDto) {
const result = await this.authService.login(dto);
return {
success: true,
data: result,
message: 'Login successful'
};
}
}Always return responses in this format:
// Success
return {
success: true,
data: yourData,
message: 'Operation successful'
};
// Error (throw exception)
throw new BadRequestException({
success: false,
message: 'Email already exists',
statusCode: 400
});import { Controller, Get, Query, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
@ApiTags('Donations')
@Controller('donations')
export class DonationsController {
constructor(private readonly donationsService: DonationsService) {}
@Get()
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: 'Get all available food donations' })
@ApiQuery({ name: 'latitude', required: false, type: Number })
@ApiQuery({ name: 'longitude', required: false, type: Number })
@ApiQuery({ name: 'radius', required: false, type: Number, description: 'Radius in km (default: 5)' })
async getAvailableDonations(
@Query('latitude') latitude?: number,
@Query('longitude') longitude?: number,
@Query('radius') radius: number = 5,
) {
const donations = await this.donationsService.findAvailable(
latitude,
longitude,
radius
);
return {
success: true,
data: donations,
message: 'Food listings retrieved successfully'
};
}
}import { Controller, Patch, Param, Body, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { RolesGuard } from '../auth/guards/roles.guard';
import { Roles } from '../auth/decorators/roles.decorator';
import { CurrentUser } from '../auth/decorators/current-user.decorator';
import { ClaimDonationDto } from './dto/claim-donation.dto';
@ApiTags('Donations')
@Controller('donations')
export class DonationsController {
@Patch(':id/claim')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('NGO')
@ApiBearerAuth()
@ApiOperation({ summary: 'Claim a food donation (NGO only)' })
async claimDonation(
@Param('id') id: string,
@Body() dto: ClaimDonationDto,
@CurrentUser() user: User,
) {
const claimed = await this.donationsService.claimDonation(
id,
user.id,
dto.estimatedPickupTime
);
return {
success: true,
data: claimed,
message: 'Food donation claimed successfully'
};
}
}import { Controller, Patch, Param, Body, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth, ApiResponse } from '@nestjs/swagger';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { UpdateStatusDto } from './dto/donation.dto';
@ApiTags('Donations')
@Controller('donations')
export class DonationsController {
@Patch(':id/status')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: 'Update donation status (Volunteer workflow)' })
@ApiResponse({ status: 200, description: 'Status updated successfully' })
@ApiResponse({ status: 400, description: 'Invalid status transition' })
@ApiResponse({ status: 404, description: 'Donation not found' })
async updateStatus(
@Param('id') id: string,
@Body() dto: UpdateStatusDto,
) {
const updated = await this.donationsService.updateStatus(id, dto.status);
return {
success: true,
data: updated,
message: 'Status updated successfully'
};
}
}Add to backend/src/donations/dto/donation.dto.ts:
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum } from 'class-validator';
export enum DonationStatus {
AVAILABLE = 'AVAILABLE',
CLAIMED = 'CLAIMED',
PICKED_UP = 'PICKED_UP',
DELIVERED = 'DELIVERED',
EXPIRED = 'EXPIRED',
}
export class UpdateStatusDto {
@ApiProperty({
enum: DonationStatus,
example: DonationStatus.PICKED_UP,
description: 'New status for the donation'
})
@IsEnum(DonationStatus)
status: DonationStatus;
}Add to backend/src/donations/donations.service.ts:
async updateStatus(id: string, status: string) {
const donation = this.donations.find(d => d.id === id);
if (!donation) {
throw new NotFoundException({
success: false,
message: 'Donation not found',
statusCode: 404,
});
}
// Define valid status transitions
const validTransitions = {
'CLAIMED': ['PICKED_UP'],
'PICKED_UP': ['DELIVERED'],
};
const currentStatus = donation.status;
const allowedNext = validTransitions[currentStatus] || [];
if (!allowedNext.includes(status)) {
throw new BadRequestException({
success: false,
message: 'Invalid status transition',
errors: [
`Cannot change from ${currentStatus} to ${status}. ` +
`Valid next status: ${allowedNext.join(', ') || 'none'}`
],
statusCode: 400,
});
}
// Update status and record timestamp
donation.status = status;
if (status === 'PICKED_UP') {
donation.pickedUpAt = new Date();
donation.volunteer = {
id: 'mock-volunteer-id',
name: 'Mock Volunteer'
};
} else if (status === 'DELIVERED') {
donation.deliveredAt = new Date();
}
return donation;
}Key Points for Implementation:
- Status can only move forward: CLAIMED → PICKED_UP → DELIVERED
- Each transition records a timestamp
- Invalid transitions throw clear error messages
- Volunteer information is attached when status becomes PICKED_UP
Create file: backend/src/donations/dto/claim-donation.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { IsDateString } from 'class-validator';
export class ClaimDonationDto {
@ApiProperty({
example: '2025-01-12T15:00:00Z',
description: 'Estimated time when NGO will pick up the food (ISO 8601 format)'
})
@IsDateString()
estimatedPickupTime: string;
}// In donations.service.ts
async findAvailable(
latitude?: number,
longitude?: number,
radius: number = 5
): Promise<FoodListing[]> {
const query = this.donationRepository
.createQueryBuilder('donation')
.leftJoinAndSelect('donation.donor', 'donor')
.where('donation.status = :status', { status: 'AVAILABLE' });
// If location provided, filter by distance using PostGIS
if (latitude && longitude) {
query.andWhere(
`ST_DWithin(
donation.location::geography,
ST_MakePoint(:longitude, :latitude)::geography,
:radius * 1000
)`,
{ latitude, longitude, radius }
);
// Add distance field
query.addSelect(
`ST_Distance(
donation.location::geography,
ST_MakePoint(:longitude, :latitude)::geography
) / 1000`,
'distance'
);
}
query.orderBy('donation.createdAt', 'DESC');
return query.getMany();
}
async claimDonation(
donationId: string,
ngoId: string,
pickupTime: string
): Promise<FoodListing> {
const donation = await this.donationRepository.findOne({
where: { id: donationId },
relations: ['donor']
});
if (!donation) {
throw new NotFoundException('Donation not found');
}
if (donation.status !== 'AVAILABLE') {
throw new BadRequestException('This donation has already been claimed');
}
donation.status = 'CLAIMED';
donation.claimedBy = ngoId;
donation.claimedAt = new Date();
donation.estimatedPickupTime = new Date(pickupTime);
return this.donationRepository.save(donation);
}- Swagger UI: http://localhost:3000/api
- Backend Server: http://localhost:3000
- Frontend App: http://localhost:5173
If you encounter issues:
- Check this documentation first
- Verify Swagger UI shows your endpoint
- Test endpoint in Swagger before frontend
- Check browser console for CORS errors
- Contact Yuvahasini for integration issues