Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
343aa70
fixing entity field initialization errors
Juwang110 Feb 2, 2026
394a422
fixing remaining errors
Juwang110 Feb 7, 2026
0583b2f
fix remaining tests, add create request dto
Juwang110 Feb 8, 2026
031b490
strict mode enabled and prettier
Juwang110 Feb 9, 2026
13c9100
minor refactoring
Juwang110 Feb 9, 2026
c4eeca0
minor refactoring
Juwang110 Feb 12, 2026
a539c86
making up to date with main
Juwang110 Feb 12, 2026
cb04748
fixing strict mode errors introduced by pr from main
Juwang110 Feb 12, 2026
1d3e6bf
removing sneaky manufacturer file
Juwang110 Feb 12, 2026
bbe2ed0
resetting package.json
Juwang110 Feb 13, 2026
588a446
Reset yarn.lock
Juwang110 Feb 13, 2026
070cae7
updating package.json and yarn
Juwang110 Feb 13, 2026
8b5a043
update yarn.lock
Juwang110 Feb 13, 2026
c1cd48c
merge with main
Juwang110 Feb 15, 2026
3c05744
fixing up new type errors introduced by merge main
Juwang110 Feb 15, 2026
8460942
up to date with main
Juwang110 Feb 18, 2026
8b06050
updating lock
Juwang110 Feb 18, 2026
1ac5475
review comments
Juwang110 Feb 18, 2026
38d2ae0
migration to make donation item food type not null
Juwang110 Feb 19, 2026
a7733ba
refactoring based off comments
Juwang110 Feb 19, 2026
da192b8
comment refactoring
Juwang110 Feb 20, 2026
97f276b
comments
Juwang110 Feb 23, 2026
85e0fe3
fixing tests
Juwang110 Feb 23, 2026
aad1765
update yarn to be main
Juwang110 Feb 23, 2026
98f852b
Merge branch 'main' into jw/SSF-131-strict-mode
Juwang110 Feb 23, 2026
b3e0bbc
fix test
Juwang110 Feb 23, 2026
62847f6
comments
Juwang110 Feb 24, 2026
b0e5ea7
comments
Juwang110 Feb 25, 2026
4e63adc
Merge branch 'main' into jw/SSF-131-strict-mode
Juwang110 Feb 26, 2026
ac78172
new fixes based on main
Juwang110 Feb 26, 2026
e475019
fix frontend type
Juwang110 Feb 26, 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
22 changes: 11 additions & 11 deletions apps/backend/src/allocations/allocations.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,28 @@ import { Order } from '../orders/order.entity';
@Entity('allocations')
export class Allocation {
@PrimaryGeneratedColumn({ name: 'allocation_id' })
allocationId: number;
allocationId!: number;

@Column({ name: 'order_id', type: 'int', nullable: false })
orderId: number;
@Column({ name: 'order_id', type: 'int' })
orderId!: number;

@ManyToOne(() => Order, (order) => order.allocations)
@JoinColumn({ name: 'order_id' })
order: Order;
order!: Order;

@Column({ name: 'item_id', type: 'int', nullable: false })
itemId: number;
@Column({ name: 'item_id', type: 'int' })
itemId!: number;

@ManyToOne(() => DonationItem, (item) => item.allocations)
@JoinColumn({ name: 'item_id' })
item: DonationItem;
item!: DonationItem;

@Column({ name: 'allocated_quantity', type: 'int' })
allocatedQuantity: number;
allocatedQuantity!: number;

@Column({ name: 'reserved_at', type: 'timestamp' })
reservedAt: Date;
reservedAt!: Date;

@Column({ name: 'fulfilled_at', type: 'timestamp' })
fulfilledAt: Date;
@Column({ name: 'fulfilled_at', type: 'timestamp', nullable: true })
fulfilledAt!: Date | null;
}
2 changes: 1 addition & 1 deletion apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { ScheduleModule } from '@nestjs/schedule';
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: async (configService: ConfigService) =>
configService.get('typeorm'),
configService.getOrThrow('typeorm'),
}),
ScheduleModule.forRoot(),
UsersModule,
Expand Down
24 changes: 18 additions & 6 deletions apps/backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ export class AuthController {
// By default, creates a standard user
try {
await this.authService.signup(signUpDto);
} catch (e) {
throw new BadRequestException(e.message);
} catch (e: unknown) {
const message =
e instanceof Error
? e.message
: 'Unexpected error occurred when signing up user';
throw new BadRequestException(message);
}

const user = await this.usersService.create(
Expand All @@ -45,8 +49,12 @@ export class AuthController {
verifyUser(@Body() body: VerifyUserDto): void {
try {
this.authService.verifyUser(body.email, body.verificationCode);
} catch (e) {
throw new BadRequestException(e.message);
} catch (e: unknown) {
const message =
e instanceof Error
? e.message
: 'Unexpected error occurred when verifying user';
throw new BadRequestException(message);
}
}

Expand Down Expand Up @@ -76,8 +84,12 @@ export class AuthController {

try {
await this.authService.deleteUser(user.email);
} catch (e) {
throw new BadRequestException(e.message);
} catch (e: unknown) {
const message =
e instanceof Error
? e.message
: 'Unexpected error occurred when deleting user';
throw new BadRequestException(message);
}

this.usersService.remove(user.id);
Expand Down
5 changes: 5 additions & 0 deletions apps/backend/src/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ describe('AuthService', () => {
let service: AuthService;

beforeEach(async () => {
process.env.AWS_ACCESS_KEY_ID = 'test';
process.env.AWS_SECRET_ACCESS_KEY = 'test';
process.env.COGNITO_CLIENT_SECRET = 'test';
process.env.AWS_REGION = 'us-east-1';

const module: TestingModule = await Test.createTestingModule({
providers: [AuthService],
}).compile();
Expand Down
101 changes: 90 additions & 11 deletions apps/backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { Injectable } from '@nestjs/common';
import {
Injectable,
InternalServerErrorException,
NotFoundException,
} from '@nestjs/common';
import {
AdminDeleteUserCommand,
AdminInitiateAuthCommand,
AdminInitiateAuthCommandOutput,
CognitoIdentityProviderClient,
ConfirmForgotPasswordCommand,
ConfirmSignUpCommand,
ForgotPasswordCommand,
SignUpCommand,
AuthenticationResultType,
} from '@aws-sdk/client-cognito-identity-provider';

import CognitoAuthConfig from './aws-exports';
Expand All @@ -17,6 +23,18 @@ import { createHmac } from 'crypto';
import { RefreshTokenDto } from './dtos/refresh-token.dto';
import { Role } from '../users/types';
import { ConfirmPasswordDto } from './dtos/confirm-password.dto';
import { validateEnv } from '../utils/validation.utils';

type SignInAuthResult = AuthenticationResultType & {
AccessToken: string;
RefreshToken: string;
IdToken: string;
};

type RefreshAuthResult = AuthenticationResultType & {
AccessToken: string;
IdToken: string;
};

@Injectable()
export class AuthService {
Expand All @@ -27,12 +45,12 @@ export class AuthService {
this.providerClient = new CognitoIdentityProviderClient({
region: CognitoAuthConfig.region,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
accessKeyId: validateEnv('AWS_ACCESS_KEY_ID'),
secretAccessKey: validateEnv('AWS_SECRET_ACCESS_KEY'),
},
});

this.clientSecret = process.env.COGNITO_CLIENT_SECRET;
this.clientSecret = validateEnv('COGNITO_CLIENT_SECRET');
}

// Computes secret hash to authenticate this backend to Cognito
Expand Down Expand Up @@ -69,8 +87,19 @@ export class AuthService {
],
});

const response = await this.providerClient.send(signUpCommand);
return response.UserConfirmed;
try {
const response = await this.providerClient.send(signUpCommand);

if (response.UserConfirmed == null) {
throw new InternalServerErrorException(
'Missing UserConfirmed from Cognito',
);
}

return response.UserConfirmed;
} catch (err: unknown) {
throw new InternalServerErrorException('Failed to sign up user');
}
}

async verifyUser(email: string, verificationCode: string): Promise<void> {
Expand Down Expand Up @@ -98,10 +127,13 @@ export class AuthService {

const response = await this.providerClient.send(signInCommand);

const authResult =
this.validateAuthenticationResultTokensForSignIn(response);

return {
accessToken: response.AuthenticationResult.AccessToken,
refreshToken: response.AuthenticationResult.RefreshToken,
idToken: response.AuthenticationResult.IdToken,
accessToken: authResult.AccessToken,
refreshToken: authResult.RefreshToken,
idToken: authResult.IdToken,
};
}

Expand All @@ -122,10 +154,13 @@ export class AuthService {

const response = await this.providerClient.send(refreshCommand);

const authResult =
this.validateAuthenticationResultTokensForRefresh(response);

return {
accessToken: response.AuthenticationResult.AccessToken,
accessToken: authResult.AccessToken,
refreshToken: refreshToken,
idToken: response.AuthenticationResult.IdToken,
idToken: authResult.IdToken,
};
}

Expand Down Expand Up @@ -163,4 +198,48 @@ export class AuthService {

await this.providerClient.send(adminDeleteUserCommand);
}

private validateAuthenticationResultTokensForSignIn(
commandOutput: AdminInitiateAuthCommandOutput,
): SignInAuthResult {
const result = commandOutput.AuthenticationResult;

if (result == null) {
throw new NotFoundException(
'No associated authentication result for sign in',
);
}

if (
result.AccessToken == null ||
result.RefreshToken == null ||
result.IdToken == null
) {
throw new NotFoundException(
'Necessary Authentication Result tokens not found for sign in ',
);
}

return result as SignInAuthResult;
}

private validateAuthenticationResultTokensForRefresh(
commandOutput: AdminInitiateAuthCommandOutput,
): RefreshAuthResult {
const result = commandOutput.AuthenticationResult;

if (result == null) {
throw new NotFoundException(
'No associated authentication result for refresh',
);
}

if (result.AccessToken == null || result.IdToken == null) {
throw new NotFoundException(
'Necessary Authentication Result tokens not found for refresh',
);
}

return result as RefreshAuthResult;
}
}
7 changes: 7 additions & 0 deletions apps/backend/src/auth/authenticated-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Request } from 'express';
import { User } from '../users/user.entity';

// user does not have to be provided by the client but is added automatically by the auth backend
export interface AuthenticatedRequest extends Request {
user: User;
}
6 changes: 3 additions & 3 deletions apps/backend/src/auth/dtos/confirm-password.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { IsEmail, IsString } from 'class-validator';

export class ConfirmPasswordDto {
@IsEmail()
email: string;
email!: string;

@IsString()
newPassword: string;
newPassword!: string;

@IsString()
confirmationCode: string;
confirmationCode!: string;
}
2 changes: 1 addition & 1 deletion apps/backend/src/auth/dtos/delete-user.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { IsPositive } from 'class-validator';

export class DeleteUserDto {
@IsPositive()
userId: number;
userId!: number;
}
2 changes: 1 addition & 1 deletion apps/backend/src/auth/dtos/forgot-password.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { IsEmail } from 'class-validator';

export class ForgotPasswordDto {
@IsEmail()
email: string;
email!: string;
}
4 changes: 2 additions & 2 deletions apps/backend/src/auth/dtos/refresh-token.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { IsString } from 'class-validator';

export class RefreshTokenDto {
@IsString()
refreshToken: string;
refreshToken!: string;

@IsString()
userSub: string;
userSub!: string;
}
6 changes: 3 additions & 3 deletions apps/backend/src/auth/dtos/sign-in-response.dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export class SignInResponseDto {
accessToken: string;
accessToken!: string;

refreshToken: string;
refreshToken!: string;

idToken: string;
idToken!: string;
}
4 changes: 2 additions & 2 deletions apps/backend/src/auth/dtos/sign-in.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { IsEmail, IsString } from 'class-validator';

export class SignInDto {
@IsEmail()
email: string;
email!: string;

@IsString()
password: string;
password!: string;
}
10 changes: 5 additions & 5 deletions apps/backend/src/auth/dtos/sign-up.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@ import { IsEmail, IsNotEmpty, IsString, IsPhoneNumber } from 'class-validator';

export class SignUpDto {
@IsString()
firstName: string;
firstName!: string;

@IsString()
lastName: string;
lastName!: string;

@IsEmail()
email: string;
email!: string;

@IsString()
password: string;
password!: string;

@IsString()
@IsNotEmpty()
@IsPhoneNumber('US', {
message:
'phone must be a valid phone number (make sure all the digits are correct)',
})
phone: string;
phone!: string;
}
4 changes: 2 additions & 2 deletions apps/backend/src/auth/dtos/verify-user.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { IsEmail, IsString } from 'class-validator';

export class VerifyUserDto {
@IsEmail()
email: string;
email!: string;

@IsString()
verificationCode: string;
verificationCode!: string;
}
7 changes: 4 additions & 3 deletions apps/backend/src/aws/aws-s3.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { validateEnv } from '../utils/validation.utils';

@Injectable()
export class AWSS3Service {
Expand All @@ -9,15 +10,15 @@ export class AWSS3Service {

constructor() {
this.region = process.env.AWS_REGION || 'us-east-2';
this.bucket = process.env.AWS_BUCKET_NAME;
this.bucket = validateEnv('AWS_BUCKET_NAME');
if (!this.bucket) {
throw new Error('AWS_BUCKET_NAME is not defined');
}
this.client = new S3Client({
region: this.region,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
accessKeyId: validateEnv('AWS_ACCESS_KEY_ID'),
secretAccessKey: validateEnv('AWS_SECRET_ACCESS_KEY'),
},
});
}
Expand Down
Loading