Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
a95a263
Updated auth
dburkhart07 Mar 29, 2025
d9e698e
Tried fixing JWT Strategy
dburkhart07 Mar 29, 2025
ea324ba
Updated auth
dburkhart07 Mar 30, 2025
f68d4a3
Finished general authentication for both frontend and backend pages
dburkhart07 Mar 31, 2025
d8a1414
Final commit for this branch
dburkhart07 Apr 1, 2025
bafe7b1
Revisions made with Sam!!!
dburkhart07 Aug 10, 2025
1a12082
Full implementation of backend role-based auth
dburkhart07 Jan 19, 2026
d9e48b4
prettier
dburkhart07 Jan 19, 2026
d0d28fe
Fixed user flow to use a cognito id hardcoded into the database
dburkhart07 Jan 23, 2026
b5321ad
prettier
dburkhart07 Jan 23, 2026
b61e462
Added requested changes
dburkhart07 Jan 26, 2026
dd41b44
Added in decorator and guard for a bypass gaurd
dburkhart07 Jan 26, 2026
63a41a4
Final commit
dburkhart07 Jan 26, 2026
396cc51
Resolved comments
dburkhart07 Jan 28, 2026
7d9fc00
Fixed addition of audience
dburkhart07 Jan 30, 2026
3f4a147
Fixed bugs wth id loader
dburkhart07 Feb 1, 2026
a300045
Final commit
dburkhart07 Feb 1, 2026
9fe9255
Final commit
dburkhart07 Feb 1, 2026
8c932c2
remove ids from routes
amywng Feb 2, 2026
d4059f9
prettier
amywng Feb 2, 2026
bf7993e
fix tests
amywng Feb 14, 2026
3d84e4b
fix form requests file
amywng Feb 14, 2026
49a64e8
fix problems with merge
amywng Feb 14, 2026
4a18517
more cleanup
amywng Feb 14, 2026
0ccaa38
review comments
amywng Feb 16, 2026
c05987c
add exception propagation test
amywng Feb 16, 2026
f30cd58
SSF-122 auth page frontend (#94)
Juwang110 Feb 16, 2026
4814889
review comments
amywng Feb 17, 2026
5c5828c
fix error
amywng Feb 17, 2026
f892adc
fix error
amywng Feb 17, 2026
228aa8e
fix things from merge
amywng Feb 17, 2026
1eefd3f
clean up
amywng Feb 17, 2026
8cea380
fix yarnfile
amywng Feb 17, 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
1 change: 0 additions & 1 deletion apps/backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
ConfirmForgotPasswordCommand,
ConfirmSignUpCommand,
ForgotPasswordCommand,
ListUsersCommand,
SignUpCommand,
} from '@aws-sdk/client-cognito-identity-provider';

Expand Down
4 changes: 3 additions & 1 deletion apps/backend/src/foodRequests/request.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,9 @@ describe('RequestsController', () => {

// Mock Photos
const mockStream = new Readable();
mockStream._read = () => {};
mockStream._read = () => {
// no-op
};

const photos: Express.Multer.File[] = [
{
Expand Down
5 changes: 2 additions & 3 deletions apps/backend/src/foodRequests/request.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { OrderStatus } from '../orders/types';
import { OrderDetailsDto } from './dtos/order-details.dto';

@Controller('requests')
// @UseInterceptors()
export class RequestsController {
constructor(
private requestsService: RequestsService,
Expand All @@ -41,14 +40,14 @@ export class RequestsController {
}

@Roles(Role.PANTRY, Role.ADMIN)
@Get('/get-all-requests/:pantryId')
@Get('/:pantryId/all')
async getAllPantryRequests(
@Param('pantryId', ParseIntPipe) pantryId: number,
): Promise<FoodRequest[]> {
return this.requestsService.find(pantryId);
}

@Get('/all-order-details/:requestId')
@Get('/:requestId/order-details')
async getAllOrderDetailsFromRequest(
@Param('requestId', ParseIntPipe) requestId: number,
): Promise<OrderDetailsDto[]> {
Expand Down
53 changes: 49 additions & 4 deletions apps/backend/src/pantries/pantries.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
ServeAllergicChildren,
} from './types';
import { ApplicationStatus } from '../shared/types';
import { NotFoundException, UnauthorizedException } from '@nestjs/common';
import { User } from '../users/user.entity';

const mockPantriesService = mock<PantriesService>();
const mockOrdersService = mock<OrdersService>();
Expand Down Expand Up @@ -121,12 +123,24 @@ describe('PantriesController', () => {
});

describe('getPantry', () => {
it('should return a single pantry by id', async () => {
mockPantriesService.findOne.mockResolvedValueOnce(mockPantry as Pantry);
it('should return a pantry by ID', async () => {
const mockUser: Partial<User> = {
id: 1,
firstName: 'Test',
lastName: 'User',
email: 'test@test.com',
};
const mockPantry: Partial<Pantry> = {
pantryId: 1,
pantryName: 'Test Pantry',
pantryUser: mockUser as User,
};

mockPantriesService.findOne.mockResolvedValue(mockPantry as Pantry);

const result = await controller.getPantry(1);

expect(result).toEqual(mockPantry as Pantry);
expect(result).toEqual(mockPantry);
expect(result.pantryUser).toEqual(mockUser);
expect(mockPantriesService.findOne).toHaveBeenCalledWith(1);
});

Expand Down Expand Up @@ -232,4 +246,35 @@ describe('PantriesController', () => {
expect(mockOrdersService.getOrdersByPantry).toHaveBeenCalledWith(24);
});
});

describe('getCurrentUserPantryId', () => {
it('returns pantryId when req.currentUser is present', async () => {
const req = { user: { id: 1 } };
const pantry: Partial<Pantry> = { pantryId: 10 };
mockPantriesService.findByUserId.mockResolvedValueOnce(pantry as Pantry);

const result = await controller.getCurrentUserPantryId(req);

expect(result).toEqual(10);
expect(mockPantriesService.findByUserId).toHaveBeenCalledWith(1);
});

it('throws UnauthorizedException when unauthenticated', async () => {
await expect(controller.getCurrentUserPantryId({})).rejects.toThrow(
new UnauthorizedException('Not authenticated'),
);
});

it('propagates NotFoundException from service', async () => {
const req = { user: { id: 999 } };
mockPantriesService.findByUserId.mockRejectedValueOnce(
new NotFoundException('Pantry for User 999 not found'),
);

const promise = controller.getCurrentUserPantryId(req);
await expect(promise).rejects.toBeInstanceOf(NotFoundException);
await expect(promise).rejects.toThrow('Pantry for User 999 not found');
expect(mockPantriesService.findByUserId).toHaveBeenCalledWith(999);
});
});
});
14 changes: 14 additions & 0 deletions apps/backend/src/pantries/pantries.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
ParseIntPipe,
Patch,
Post,
Req,
UnauthorizedException,
} from '@nestjs/common';
import { Pantry } from './pantries.entity';
import { PantriesService } from './pantries.service';
Expand Down Expand Up @@ -33,6 +35,18 @@ export class PantriesController {
private ordersService: OrdersService,
) {}

@Roles(Role.PANTRY)
@Get('/my-id')
async getCurrentUserPantryId(@Req() req): Promise<number> {
const currentUser = req.user;
if (!currentUser) {
throw new UnauthorizedException('Not authenticated');
}

const pantry = await this.pantriesService.findByUserId(currentUser.id);
return pantry.pantryId;
}

@Roles(Role.ADMIN)
@Get('/pending')
async getPendingPantries(): Promise<Pantry[]> {
Expand Down
21 changes: 21 additions & 0 deletions apps/backend/src/pantries/pantries.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ describe('PantriesService', () => {
expect(result).toBe(mockPendingPantry);
expect(mockRepository.findOne).toHaveBeenCalledWith({
where: { pantryId: 1 },
relations: ['pantryUser'],
});
});

Expand Down Expand Up @@ -280,4 +281,24 @@ describe('PantriesService', () => {
expect(mockRepository.save).toHaveBeenCalled();
});
});

// TODO: once pantry service tests are fixed, uncomment this out
// describe('findByUserId', () => {
// it('should return a pantry by user id', async () => {
// const userId = 10;
// const pantry = await service.findByUserId(userId);

// expect(pantry.pantryId).toBe(1);
// expect(pantry.pantryName).toBe('Community Food Pantry Downtown');
// expect(mockRepository.findOne).toHaveBeenCalledWith({
// where: { pantryUser: { id: userId } },
// });
// });

// it('should throw NotFoundException if pantry not found', async () => {
// await expect(service.findByUserId(999)).rejects.toThrow(
// new NotFoundException('Pantry for User 999 not found'),
// );
// });
// });
});
18 changes: 17 additions & 1 deletion apps/backend/src/pantries/pantries.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ export class PantriesService {
async findOne(pantryId: number): Promise<Pantry> {
validateId(pantryId, 'Pantry');

const pantry = await this.repo.findOne({ where: { pantryId } });
const pantry = await this.repo.findOne({
where: { pantryId },
relations: ['pantryUser'],
});

if (!pantry) {
throw new NotFoundException(`Pantry ${pantryId} not found`);
Expand Down Expand Up @@ -125,4 +128,17 @@ export class PantriesService {

return pantries;
}

async findByUserId(userId: number): Promise<Pantry> {
validateId(userId, 'User');

const pantry = await this.repo.findOne({
where: { pantryUser: { id: userId } },
});

if (!pantry) {
throw new NotFoundException(`Pantry for User ${userId} not found`);
Copy link
Member

Choose a reason for hiding this comment

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

I think we should add a test for this case, I may have missed it but I don't see one

Copy link
Member Author

Choose a reason for hiding this comment

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

we haven't refactored the pantries service tests yet but i added one commented out for now

}
return pantry;
}
}
15 changes: 9 additions & 6 deletions apps/frontend/src/api/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,7 @@ export class ApiClient {
userId: number,
body: { role: string },
): Promise<void> {
return this.axiosInstance
.put(`/api/users/${userId}/role`, body)
.then(() => {});
return this.axiosInstance.put(`/api/users/${userId}/role`, body);
}

public async getOrderFoodRequest(requestId: number): Promise<FoodRequest> {
Expand Down Expand Up @@ -232,7 +230,7 @@ export class ApiClient {
requestId: number,
): Promise<OrderDetails[]> {
return this.axiosInstance
.get(`/api/requests/all-order-details/${requestId}`)
.get(`/api/requests/${requestId}/order-details`)
.then((response) => response.data) as Promise<OrderDetails[]>;
}

Expand Down Expand Up @@ -262,7 +260,7 @@ export class ApiClient {
}

public async getPantryRequests(pantryId: number): Promise<FoodRequest[]> {
const data = await this.get(`/api/requests/get-all-requests/${pantryId}`);
const data = await this.get(`/api/requests/${pantryId}/all`);
return data as FoodRequest[];
}

Expand All @@ -278,14 +276,19 @@ export class ApiClient {

if (response.status === 200) {
alert('Delivery confirmation submitted successfully');
window.location.href = '/request-form/1';
window.location.href = '/request-form';
} else {
alert(`Failed to submit: ${response.statusText}`);
}
} catch (error) {
alert(`Error submitting delivery confirmation: ${error}`);
}
}

public async getCurrentUserPantryId(): Promise<number> {
const data = await this.get('/api/pantries/my-id');
return data as number;
}
}

export default new ApiClient();
21 changes: 3 additions & 18 deletions apps/frontend/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import VolunteerManagement from '@containers/volunteerManagement';
import FoodManufacturerOrderDashboard from '@containers/foodManufacturerOrderDashboard';
import DonationManagement from '@containers/donationManagement';
import AdminDonation from '@containers/adminDonation';
import { pantryIdLoader } from '@loaders/pantryIdLoader';
import Homepage from '@containers/homepage';
import AdminOrderManagement from '@containers/adminOrderManagement';
import { Amplify } from 'aws-amplify';
Expand Down Expand Up @@ -81,14 +80,6 @@ const router = createBrowserRouter([
</ProtectedRoute>
),
},
{
path: '/pantry-dashboard/:pantryId',
element: (
<ProtectedRoute>
<PantryDashboard />
</ProtectedRoute>
),
},
{
path: '/pantry-past-orders',
element: (
Expand All @@ -114,13 +105,12 @@ const router = createBrowserRouter([
),
},
{
path: '/pantry-dashboard/:pantryId',
path: '/pantry-dashboard',
element: (
<ProtectedRoute>
<PantryDashboard />
</ProtectedRoute>
),
loader: pantryIdLoader,
},
{
path: '/pantry-past-orders',
Expand Down Expand Up @@ -155,13 +145,13 @@ const router = createBrowserRouter([
),
},
{
path: '/request-form/:pantryId',
path: '/request-form',
element: (
<ProtectedRoute>
<FormRequests />
</ProtectedRoute>
),
loader: pantryIdLoader,
action: submitFoodRequestFormModal,
},
{
path: '/donation-management',
Expand Down Expand Up @@ -203,11 +193,6 @@ const router = createBrowserRouter([
</ProtectedRoute>
),
},
// Actions
{
path: '/food-request',
action: submitFoodRequestFormModal,
},
{
path: '/confirm-delivery',
action: submitDeliveryConfirmationFormModal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ const NewVolunteerModal: React.FC<NewVolunteerModalProps> = ({
const [error, setError] = useState('');

const handleSubmit = async () => {
console.log('RAW phone value:', phone);
if (!firstName || !lastName || !email || !phone || phone === '+1') {
setError('Please fill in all fields. *');
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,10 @@ export const submitDeliveryConfirmationFormModal: ActionFunction = async ({
confirmDeliveryData,
);
alert('Delivery confirmation submitted successfully');
window.location.href = `/request-form/${pantryId}`;
} catch (error) {
alert(`Error submitting delivery confirmation: ${error}`);
window.location.href = `/request-form/${pantryId}`;
}
window.location.href = '/request-form';
};

export default DeliveryConfirmationModal;
5 changes: 2 additions & 3 deletions apps/frontend/src/components/forms/pantryApplicationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const PantryApplicationForm: React.FC = () => {
const [secondaryContactPhone, setSecondaryContactPhone] =
useState<string>('');
const [activities, setActivities] = useState<string[]>([]);
const allergenClientsExactOption: string = 'I have an exact number';
const allergenClientsExactOption = 'I have an exact number';

const [allergenClients, setAllergenClients] = useState<string | undefined>();
const [restrictions, setRestrictions] = useState<string[]>([]);
Expand Down Expand Up @@ -1226,7 +1226,7 @@ export const submitPantryApplicationForm: ActionFunction = async ({

const data = Object.fromEntries(pantryApplicationData);

let submissionSuccessful: boolean = false;
let submissionSuccessful = false;

await ApiClient.postPantry(data as PantryApplicationDto).then(
() => (submissionSuccessful = true),
Expand All @@ -1242,7 +1242,6 @@ export const submitPantryApplicationForm: ActionFunction = async ({
);
} else {
alert('Form submission failed; please try again');
console.log(error);
}
},
);
Expand Down
Loading