Skip to content

Commit 08a91ec

Browse files
committed
Add Arkesel phone verification library
1 parent 27fcf7d commit 08a91ec

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1347
-71
lines changed

apps/main-service/src/app/@common/modules/global.module.ts

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { Match } from '../validators/match.validator';
3232
useFactory: () =>
3333
({
3434
...environment.database,
35+
logging: true,
3536
} as TypeOrmModuleOptions),
3637
}),
3738
CacheModule.register<ClientOpts>({

apps/main-service/src/app/users/entities/user.entity.ts

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ export class User {
2424
@PrimaryGeneratedColumn('uuid')
2525
id: string;
2626

27+
@Column({ nullable: true })
28+
profileId: number;
29+
2730
@Field({ description: 'User Profile' })
2831
@OneToOne(() => Profile, (profile) => profile.user)
2932
@JoinColumn()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { InputType, Field } from '@nestjs/graphql';
2+
3+
import { IsEnum, IsNotEmpty } from 'class-validator';
4+
5+
import { OtpType } from '../enums/otp-type.enum';
6+
7+
@InputType()
8+
export class SendOtpInput {
9+
@Field(() => OtpType, {
10+
description: 'Type of medium the otp code is going to be sent via',
11+
})
12+
@IsNotEmpty()
13+
@IsEnum(OtpType)
14+
readonly type: OtpType;
15+
}

apps/main-service/src/app/verification/entities/verification.entity.ts

-7
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
2+
import { ProfileResolver } from './profile.resolver';
3+
4+
describe('ProfileResolver', () => {
5+
let resolver: ProfileResolver;
6+
7+
beforeEach(async () => {
8+
const module: TestingModule = await Test.createTestingModule({
9+
providers: [ProfileResolver],
10+
}).compile();
11+
12+
resolver = module.get<ProfileResolver>(ProfileResolver);
13+
});
14+
15+
it('should be defined', () => {
16+
expect(resolver).toBeDefined();
17+
});
18+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
2+
3+
import { Profile } from '../../users/entities/profile.entity';
4+
import { User } from '../../users/entities/user.entity';
5+
6+
import { ProfileService } from '../services/profile.service';
7+
8+
@Resolver(() => Profile)
9+
export class ProfileResolver {
10+
constructor(private readonly profileService: ProfileService) {}
11+
12+
@ResolveField('profile', () => Profile)
13+
profile(@Parent() user: User) {
14+
return this.profileService.findProfileById(user.profileId);
15+
}
16+
}

apps/main-service/src/app/verification/verification.resolver.spec.ts apps/main-service/src/app/verification/resolvers/verification.resolver.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Test, TestingModule } from '@nestjs/testing';
22
import { VerificationResolver } from './verification.resolver';
3-
import { VerificationService } from './verification.service';
3+
import { VerificationService } from '../services/verification.service';
44

55
describe('VerificationResolver', () => {
66
let resolver: VerificationResolver;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Resolver, Mutation, Args } from '@nestjs/graphql';
2+
import { UseGuards } from '@nestjs/common';
3+
4+
import { User } from '../../users/entities/user.entity';
5+
6+
import { VerifyEmailViaOtpInput } from '../dto/verify-email-via-otp.input';
7+
import { VerifyPhoneViaOtpInput } from '../dto/verify-phone-via-otp.input';
8+
9+
import { CurrentUser } from '../../@common/decorators/current-user.decorator';
10+
11+
import { GqlAuthGuard } from '../../@common/guards/gql-auth.guard';
12+
13+
import { VerificationService } from '../services/verification.service';
14+
import { SendOtpInput } from '../dto/send-otp.input';
15+
16+
@Resolver()
17+
@UseGuards(GqlAuthGuard)
18+
export class VerificationResolver {
19+
constructor(private readonly verificationService: VerificationService) {}
20+
21+
@Mutation(() => User, { name: 'verifyEmailViaOtp' })
22+
verifyEmailViaOtp(
23+
@CurrentUser() user: User,
24+
@Args('verifyEmailViaOtpInput')
25+
verifyEmailViaOtpInput: VerifyEmailViaOtpInput
26+
) {
27+
return this.verificationService.verifyEmailViaOtp(
28+
verifyEmailViaOtpInput,
29+
user
30+
);
31+
}
32+
33+
@Mutation(() => User, { name: 'verifyPhoneViaOtp' })
34+
verifyPhoneViaOtp(
35+
@CurrentUser() user: User,
36+
@Args('verifyPhoneViaOtpInput')
37+
verifyPhoneViaOtpInput: VerifyPhoneViaOtpInput
38+
) {
39+
return this.verificationService.verifyPhoneViaOtp(
40+
verifyPhoneViaOtpInput,
41+
user
42+
);
43+
}
44+
45+
@Mutation(() => User, { name: 'sendOtpForPhoneVerification' })
46+
sendOtpForPhoneVerification(
47+
@CurrentUser() user: User,
48+
@Args('sendOtpInput')
49+
sendOtpInput: SendOtpInput
50+
) {
51+
return this.verificationService.sendOtpForPhoneVerification(
52+
sendOtpInput,
53+
user
54+
);
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
2+
import { ProfileService } from './profile.service';
3+
4+
describe('ProfileService', () => {
5+
let service: ProfileService;
6+
7+
beforeEach(async () => {
8+
const module: TestingModule = await Test.createTestingModule({
9+
providers: [ProfileService],
10+
}).compile();
11+
12+
service = module.get<ProfileService>(ProfileService);
13+
});
14+
15+
it('should be defined', () => {
16+
expect(service).toBeDefined();
17+
});
18+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Injectable, NotFoundException } from '@nestjs/common';
2+
import { InjectRepository } from '@nestjs/typeorm';
3+
4+
import { Repository } from 'typeorm';
5+
6+
import { Profile } from '../../users/entities/profile.entity';
7+
8+
@Injectable()
9+
export class ProfileService {
10+
constructor(
11+
@InjectRepository(Profile)
12+
private readonly profileRepository: Repository<Profile>
13+
) {}
14+
15+
async findProfileById(id: number) {
16+
try {
17+
return await this.profileRepository.findOneOrFail({
18+
where: {
19+
id,
20+
},
21+
});
22+
} catch (e) {
23+
throw new NotFoundException('Profile not found!');
24+
}
25+
}
26+
}

apps/main-service/src/app/verification/verification.service.ts apps/main-service/src/app/verification/services/verification.service.ts

+40-10
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,23 @@ import { InjectRepository } from '@nestjs/typeorm';
1010
import { Cache } from 'cache-manager';
1111
import { Repository } from 'typeorm';
1212

13-
import { User } from '../users/entities/user.entity';
14-
import { UserVerification } from './entities/user-verification.entity';
13+
import { User } from '../../users/entities/user.entity';
14+
import { UserVerification } from '../entities/user-verification.entity';
1515

16-
import { VerifyEmailViaOtpInput } from './dto/verify-email-via-otp.input';
17-
import { VerifyPhoneViaOtpInput } from './dto/verify-phone-via-otp.input';
16+
import { VerifyEmailViaOtpInput } from '../dto/verify-email-via-otp.input';
17+
import { VerifyPhoneViaOtpInput } from '../dto/verify-phone-via-otp.input';
18+
import { PhoneVerificationService } from '@laze/nestjs-phone-verification';
19+
import { SendOtpInput } from '../dto/send-otp.input';
20+
import { OtpType } from '../enums/otp-type.enum';
21+
import { environment } from '../../../environments/environment';
1822

1923
@Injectable()
2024
export class VerificationService {
2125
private readonly logger: Logger;
2226

2327
constructor(
2428
@Inject(CACHE_MANAGER) private readonly cache: Cache,
29+
private readonly phoneVerificationService: PhoneVerificationService,
2530
@InjectRepository(UserVerification)
2631
private readonly userVerificationRepository: Repository<UserVerification>,
2732
@InjectRepository(User)
@@ -72,12 +77,15 @@ export class VerificationService {
7277
verifyPhoneViaOtpInput: VerifyPhoneViaOtpInput,
7378
user: User
7479
) {
75-
const { code, type } = verifyPhoneViaOtpInput;
76-
const key = `users.${user.id}.otp.${type}`;
80+
const { code } = verifyPhoneViaOtpInput;
7781

78-
const otp = await this.cache.get<string>(key);
82+
const res = await this.phoneVerificationService.verifyCode({
83+
code,
84+
number: user.phone,
85+
});
7986

80-
if (otp !== code) {
87+
if (!res.success) {
88+
this.logger.error(res);
8189
throw new BadRequestException('OTP Code is not valid!');
8290
}
8391

@@ -96,13 +104,35 @@ export class VerificationService {
96104

97105
fullUser.verification = verification;
98106

99-
await this.cache.del(key);
100-
101107
return fullUser;
102108
} catch (e) {
103109
this.logger.error(e.message, e);
104110

105111
throw new BadRequestException('Failed to verify phone via otp!');
106112
}
107113
}
114+
115+
async sendOtpForPhoneVerification(sendOtpInput: SendOtpInput, user: User) {
116+
const { type } = sendOtpInput;
117+
118+
const res = await this.phoneVerificationService.sendCode({
119+
number: user.phone,
120+
medium: type === OtpType.SMS ? 'sms' : 'voice',
121+
message: `Your ${environment.app.name} phone number verification code is %otp_code%. Do not share this code with anyone. Visit your ${environment.app.name} account now to verify.`,
122+
});
123+
124+
if (!res.success) {
125+
this.logger.error(res);
126+
throw new BadRequestException(
127+
'Failed to send OTP code. Please try again later!'
128+
);
129+
}
130+
131+
return await this.userRepository.findOneOrFail({
132+
where: {
133+
id: user.id,
134+
},
135+
relations: ['profile', 'verification'],
136+
});
137+
}
108138
}
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
11
import { Module } from '@nestjs/common';
22
import { TypeOrmModule } from '@nestjs/typeorm';
33

4+
import { SharedDataAccessPhoneVerificationModule } from '@laze/nestjs-phone-verification';
5+
46
import { UsersModule } from '../users/users.module';
57

68
import { UserVerification } from './entities/user-verification.entity';
79

8-
import { VerificationService } from './verification.service';
10+
import { VerificationService } from './services/verification.service';
11+
// import { ProfileService } from './services/profile.service';
912

10-
import { VerificationResolver } from './verification.resolver';
13+
import { VerificationResolver } from './resolvers/verification.resolver';
14+
// import { ProfileResolver } from './resolvers/profile.resolver';
1115

1216
@Module({
13-
imports: [TypeOrmModule.forFeature([UserVerification]), UsersModule],
14-
providers: [VerificationResolver, VerificationService],
17+
imports: [
18+
SharedDataAccessPhoneVerificationModule,
19+
TypeOrmModule.forFeature([UserVerification]),
20+
UsersModule,
21+
],
22+
providers: [
23+
VerificationResolver,
24+
VerificationService,
25+
// ProfileResolver,
26+
// ProfileService,
27+
],
1528
exports: [TypeOrmModule],
1629
})
1730
export class VerificationModule {}

apps/main-service/src/app/verification/verification.resolver.ts

-43
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"extends": ["../../../../.eslintrc.json"],
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7+
"rules": {}
8+
},
9+
{
10+
"files": ["*.ts", "*.tsx"],
11+
"rules": {}
12+
},
13+
{
14+
"files": ["*.js", "*.jsx"],
15+
"rules": {}
16+
}
17+
]
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# shared-data-access-arkesel
2+
3+
This library was generated with [Nx](https://nx.dev).
4+
5+
## Running unit tests
6+
7+
Run `nx test shared-data-access-arkesel` to execute the unit tests via [Jest](https://jestjs.io).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* eslint-disable */
2+
export default {
3+
displayName: 'shared-data-access-arkesel',
4+
preset: '../../../../jest.preset.js',
5+
globals: {
6+
'ts-jest': {
7+
tsconfig: '<rootDir>/tsconfig.spec.json',
8+
},
9+
},
10+
testEnvironment: 'node',
11+
transform: {
12+
'^.+\\.[tj]s$': 'ts-jest',
13+
},
14+
moduleFileExtensions: ['ts', 'js', 'html'],
15+
coverageDirectory: '../../../../coverage/libs/shared/data-access/arkesel',
16+
};

0 commit comments

Comments
 (0)