Skip to content

Commit

Permalink
Merge pull request #1161 from hngprojects/dev
Browse files Browse the repository at this point in the history
[enhancment]: merge development to staging
  • Loading branch information
Homoakin619 authored Aug 24, 2024
2 parents d794188 + 2e69350 commit 23d7d94
Show file tree
Hide file tree
Showing 12 changed files with 74 additions and 144 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
"postinstall": "npm install --platform=linux --arch=x64 sharp"
},
"dependencies": {
"@css-inline/css-inline-linux-x64-gnu": "^0.14.1",
"@css-inline/css-inline": "^0.14.1",
"@css-inline/css-inline-linux-x64-gnu": "^0.14.1",
"@faker-js/faker": "^8.4.1",
"@google/generative-ai": "^0.17.0",
"@nestjs-modules/mailer": "^2.0.2",
Expand Down
6 changes: 3 additions & 3 deletions src/modules/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,10 @@ export default class RegistrationController {
@ApiOperation({ summary: 'Google Authentication' })
@ApiBody({ type: GoogleAuthPayloadDto })
@ApiResponse({ status: 200, description: 'Verify Payload sent from google', type: AuthResponseDto })
@ApiBadRequestResponse({ description: 'Invalid Google token' })
@ApiBadRequestResponse({ description: 'Google authentication failed' })
@HttpCode(200)
async googleAuth(@Body() body: GoogleAuthPayload, @Query('mobile') isMobile: string) {
return this.authService.googleAuth({ googleAuthPayload: body, isMobile });
async googleAuth(@Body() body: GoogleAuthPayload) {
return this.authService.googleAuth(body);
}

@skipAuth()
Expand Down
7 changes: 2 additions & 5 deletions src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@ import { EmailModule } from '../email/email.module';
import { OtpService } from '../otp/otp.service';
import { EmailService } from '../email/email.service';
import { Otp } from '../otp/entities/otp.entity';
import { GoogleStrategy } from './strategies/google.strategy';
import { GoogleAuthService } from './google-auth.service';
import { Profile } from '../profile/entities/profile.entity';
import QueueService from '../email/queue.service';
import { OrganisationsService } from '../organisations/organisations.service';
import { Organisation } from '../organisations/entities/organisations.entity';
import { OrganisationUserRole } from '../role/entities/organisation-user-role.entity';
import { Role } from '../role/entities/role.entity';
import { ProfileService } from '../profile/profile.service';

@Module({
controllers: [RegistrationController],
Expand All @@ -30,9 +28,8 @@ import { Role } from '../role/entities/role.entity';
UserService,
OtpService,
EmailService,
GoogleStrategy,
GoogleAuthService,
OrganisationsService,
ProfileService,
],
imports: [
TypeOrmModule.forFeature([User, Otp, Profile, Organisation, OrganisationUserRole, Role]),
Expand Down
44 changes: 35 additions & 9 deletions src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import { ForgotPasswordDto } from './dto/forgot-password.dto';
import { LoginDto } from './dto/login.dto';
import { RequestSigninTokenDto } from './dto/request-signin-token.dto';
import { OtpDto } from '../otp/dto/otp.dto';
import { GoogleAuthService } from './google-auth.service';
import GoogleAuthPayload from './interfaces/GoogleAuthPayloadInterface';
import { GoogleVerificationPayloadInterface } from './interfaces/GoogleVerificationPayloadInterface';
import { CustomHttpException } from '../../helpers/custom-http-filter';
import { UpdatePasswordDto } from './dto/updatePasswordDto';
import { TokenPayload } from 'google-auth-library';
import { OrganisationsService } from '../organisations/organisations.service';
import { ProfileService } from '../profile/profile.service';
import { UpdateProfileDto } from '../profile/dto/update-profile.dto';

@Injectable()
export default class AuthenticationService {
Expand All @@ -27,8 +28,8 @@ export default class AuthenticationService {
private jwtService: JwtService,
private otpService: OtpService,
private emailService: EmailService,
private googleAuthService: GoogleAuthService,
private organisationService: OrganisationsService
private organisationService: OrganisationsService,
private profileService: ProfileService
) {}

async createNewUser(createUserDto: CreateUserDTO) {
Expand All @@ -48,7 +49,7 @@ export default class AuthenticationService {
if (!user) {
throw new CustomHttpException(SYS_MSG.FAILED_TO_CREATE_USER, HttpStatus.BAD_REQUEST);
}
const newOrganisationPaload = {
const newOrganisationPayload = {
name: `${user.first_name}'s Organisation`,
description: '',
email: user.email,
Expand All @@ -59,7 +60,7 @@ export default class AuthenticationService {
state: '',
};

const newOrganisation = await this.organisationService.create(newOrganisationPaload, user.id);
const newOrganisation = await this.organisationService.create(newOrganisationPayload, user.id);

const userOranisations = await this.organisationService.getAllUserOrganisations(user.id);
const isSuperAdmin = userOranisations.map(instance => instance.user_role).includes('super-admin');
Expand Down Expand Up @@ -283,11 +284,22 @@ export default class AuthenticationService {
};
}

async googleAuth({ googleAuthPayload, isMobile }: { googleAuthPayload: GoogleAuthPayload; isMobile: string }) {
async googleAuth(googleAuthPayload: GoogleAuthPayload) {
const idToken = googleAuthPayload.id_token;

if (!idToken) {
throw new CustomHttpException(SYS_MSG.INVALID_CREDENTIALS, HttpStatus.UNAUTHORIZED);
}

const request = await fetch(`https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=${idToken}`);
const verifyTokenResponse: GoogleVerificationPayloadInterface = await request.json();

if (request.status === 400) {
throw new CustomHttpException(SYS_MSG.INVALID_CREDENTIALS, HttpStatus.UNAUTHORIZED);
}
if (request.status === 500) {
throw new CustomHttpException(SYS_MSG.SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR);
}
const verifyTokenResponse: TokenPayload = await request.json();

const userEmail = verifyTokenResponse.email;
const userExists = await this.userService.getUserRecord({ identifier: userEmail, identifierType: 'email' });
Expand All @@ -298,6 +310,7 @@ export default class AuthenticationService {
first_name: verifyTokenResponse.given_name || '',
last_name: verifyTokenResponse?.family_name || '',
password: '',
profile_pic_url: verifyTokenResponse?.picture || '',
};
return await this.createUserGoogle(userCreationPayload);
}
Expand All @@ -311,6 +324,13 @@ export default class AuthenticationService {
first_name: userExists.first_name,
last_name: userExists.last_name,
});

if (!userExists.profile.profile_pic_url || userExists.profile.profile_pic_url !== verifyTokenResponse.picture) {
const updateDto = new UpdateProfileDto();
updateDto.profile_pic_url = verifyTokenResponse.picture;
await this.profileService.updateProfile(userExists.profile.id, updateDto);
}

return {
message: SYS_MSG.LOGIN_SUCCESSFUL,
access_token: accessToken,
Expand Down Expand Up @@ -353,9 +373,14 @@ export default class AuthenticationService {
first_name: userPayload.first_name,
last_name: userPayload.last_name,
});
if (userPayload.profile_pic_url) {
const updateDto = new UpdateProfileDto();
updateDto.profile_pic_url = userPayload.profile_pic_url;

await this.profileService.updateProfile(newUser.profile.id, updateDto);
}
return {
status_code: HttpStatus.OK,
status_code: HttpStatus.CREATED,
message: SYS_MSG.USER_CREATED,
access_token: accessToken,
data: {
Expand All @@ -365,6 +390,7 @@ export default class AuthenticationService {
first_name: newUser.first_name,
last_name: newUser.last_name,
is_superadmin: isSuperAdmin,
avatar_url: newUser.profile.profile_pic_url,
},
organisations: userOranisations,
},
Expand Down
9 changes: 9 additions & 0 deletions src/modules/auth/dto/create-user.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ export class CreateUserDTO {
@IsString()
last_name: string;

@ApiProperty({
description: 'The URL for the user profile picture',
example: 'https://example.com/profile-pic.jpg',
required: false,
})
@IsOptional()
@IsString()
profile_pic_url?: string;

@ApiProperty({
description:
'The password for the user account.\
Expand Down
25 changes: 0 additions & 25 deletions src/modules/auth/google-auth.service.ts

This file was deleted.

18 changes: 0 additions & 18 deletions src/modules/auth/interfaces/GoogleAuthPayloadInterface.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,3 @@
export default interface GoogleAuthPayload {
access_token: string;

expires_in: number;

refresh_token: string;

scope: string;

token_type: string;

id_token: string;

expires_at: number;

provider: string;

type: string;

providerAccountId: string;
}
27 changes: 0 additions & 27 deletions src/modules/auth/interfaces/GoogleVerificationPayloadInterface.ts

This file was deleted.

48 changes: 0 additions & 48 deletions src/modules/auth/strategies/google.strategy.ts

This file was deleted.

12 changes: 5 additions & 7 deletions src/modules/auth/tests/auth.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,26 @@ import { User, UserType } from '../../user/entities/user.entity';
import { Otp } from '../../otp/entities/otp.entity';
import UserResponseDTO from '../../user/dto/user-response.dto';
import { LoginDto } from '../dto/login.dto';
import { GoogleAuthService } from '../google-auth.service';
import { Profile } from '../../profile/entities/profile.entity';
import { CustomHttpException } from '../../../helpers/custom-http-filter';
import { OrganisationsService } from '../../../modules/organisations/organisations.service';
import { Organisation } from '../../../modules/organisations/entities/organisations.entity';
import { ProfileService } from '../../profile/profile.service';

jest.mock('speakeasy');

describe('AuthenticationService', () => {
let service: AuthenticationService;
let userServiceMock: jest.Mocked<UserService>;
let profileServiceMock: jest.Mocked<ProfileService>;
let jwtServiceMock: jest.Mocked<JwtService>;
let otpServiceMock: jest.Mocked<OtpService>;
let emailServiceMock: jest.Mocked<EmailService>;
let googleAuthServiceMock: jest.Mocked<GoogleAuthService>;
let organisationServiceMock: jest.Mocked<OrganisationsService>;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthenticationService,

{
provide: UserService,
useValue: {
Expand All @@ -44,9 +42,9 @@ describe('AuthenticationService', () => {
},
},
{
provide: GoogleAuthService,
provide: ProfileService,
useValue: {
verifyToken: jest.fn(),
updateProfile: jest.fn(),
},
},
{
Expand Down Expand Up @@ -81,10 +79,10 @@ describe('AuthenticationService', () => {

service = module.get<AuthenticationService>(AuthenticationService);
userServiceMock = module.get(UserService) as jest.Mocked<UserService>;
profileServiceMock = module.get(ProfileService) as jest.Mocked<ProfileService>;
jwtServiceMock = module.get(JwtService) as jest.Mocked<JwtService>;
otpServiceMock = module.get(OtpService) as jest.Mocked<OtpService>;
emailServiceMock = module.get(EmailService) as jest.Mocked<EmailService>;
googleAuthServiceMock = module.get(GoogleAuthService) as jest.Mocked<GoogleAuthService>;
organisationServiceMock = module.get(OrganisationsService) as jest.Mocked<OrganisationsService>;
});

Expand Down
9 changes: 8 additions & 1 deletion src/modules/comments/comments.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Controller, Body, Post, Request } from '@nestjs/common';
import { Controller, Body, Post, Request, Get, Param } from '@nestjs/common';
import { CommentsService } from './comments.service';
import { CreateCommentDto } from './dtos/create-comment.dto';
import { CommentResponseDto } from './dtos/comment-response.dto';
Expand All @@ -18,4 +18,11 @@ export class CommentsController {
const { userId } = req.user;
return await this.commentsService.addComment(createCommentDto, userId);
}

@ApiOperation({ summary: 'Get a comment' })
@ApiResponse({ status: 200, description: 'The comment has been retrieved successfully.' })
@Get(':id')
async getAComment(@Param('id') id: string): Promise<any> {
return await this.commentsService.getAComment(id);
}
}
11 changes: 11 additions & 0 deletions src/modules/comments/comments.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,15 @@ export class CommentsService {
commentedBy,
};
}

async getAComment(commentId: string) {
const comment = await this.commentRepository.findOneBy({ id: commentId });
if (!comment) {
throw new CustomHttpException('Comment not found', HttpStatus.NOT_FOUND);
}
return {
message: 'Comment retrieved successfully',
data: { comment },
};
}
}

0 comments on commit 23d7d94

Please sign in to comment.