mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
feat: add check validity to the user signup
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import { Controller, Get, Query } from "@nestjs/common";
|
||||
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
import { Public } from "@edr/api-common";
|
||||
|
||||
import { CheckAvailabilityService } from "./check-availability.service";
|
||||
|
||||
@ApiTags("auth")
|
||||
@Controller("auth")
|
||||
@Public()
|
||||
export class CheckAvailabilityController {
|
||||
constructor(
|
||||
private readonly checkAvailabilityService: CheckAvailabilityService,
|
||||
) {}
|
||||
|
||||
@Get("check-availability")
|
||||
@ApiOperation({
|
||||
summary: "Check whether an email and/or phone number is already registered",
|
||||
})
|
||||
check(@Query("email") email?: string, @Query("phone") phone?: string) {
|
||||
return this.checkAvailabilityService.check({ email, phone });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
|
||||
import { User } from "@tria-plc/iamapi-common/entities/iam/user/user.entity";
|
||||
|
||||
export interface CheckAvailabilityQuery {
|
||||
email?: string;
|
||||
phone?: string;
|
||||
}
|
||||
|
||||
export interface CheckAvailabilityResult {
|
||||
emailTaken: boolean;
|
||||
phoneTaken: boolean;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class CheckAvailabilityService {
|
||||
constructor(
|
||||
@InjectRepository(User)
|
||||
private readonly userRepository: Repository<User>,
|
||||
) {}
|
||||
|
||||
async check({
|
||||
email,
|
||||
phone,
|
||||
}: CheckAvailabilityQuery): Promise<CheckAvailabilityResult> {
|
||||
if (!email && !phone) {
|
||||
throw new BadRequestException("email or phone is required");
|
||||
}
|
||||
|
||||
const matches = await this.userRepository.find({
|
||||
where: [
|
||||
...(email ? [{ email }] : []),
|
||||
...(phone ? [{ phoneNumber: phone }] : []),
|
||||
],
|
||||
select: { id: true, email: true, phoneNumber: true },
|
||||
});
|
||||
|
||||
return {
|
||||
emailTaken: email ? matches.some((user) => user.email === email) : false,
|
||||
phoneTaken: phone
|
||||
? matches.some((user) => user.phoneNumber === phone)
|
||||
: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { User } from '@tria-plc/iamapi-common/entities/iam/user/user.entity';
|
||||
|
||||
import { CheckAvailabilityController } from './check-availability.controller';
|
||||
import { CheckAvailabilityService } from './check-availability.service';
|
||||
import { FreightMeController } from './freight-me.controller';
|
||||
import { FreightMeService } from './freight-me.service';
|
||||
|
||||
@Module({
|
||||
controllers: [FreightMeController],
|
||||
providers: [FreightMeService],
|
||||
imports: [TypeOrmModule.forFeature([User])],
|
||||
controllers: [FreightMeController, CheckAvailabilityController],
|
||||
providers: [FreightMeService, CheckAvailabilityService],
|
||||
})
|
||||
export class FreightAuthModule {}
|
||||
|
||||
@@ -14,6 +14,7 @@ export const URL_CONSTANTS = {
|
||||
SET_PASSWORD: "/api/auth/set-password",
|
||||
ME: "/api/auth/me",
|
||||
GENERATE_VERIFICATION_CODE: "/users/generate-verification-code",
|
||||
CHECK_AVAILABILITY: "/api/auth/check-availability",
|
||||
},
|
||||
|
||||
OTP: {
|
||||
|
||||
@@ -132,12 +132,30 @@ export default function SignupPage() {
|
||||
|
||||
const passwordValue = watch("password") ?? "";
|
||||
|
||||
// Step 1 — form is valid: send a fresh code to the chosen channel, then
|
||||
// move to the OTP challenge.
|
||||
// Step 1 — form is valid: make sure the email/phone aren't already
|
||||
// registered, then send a fresh code to the chosen channel and move to
|
||||
// the OTP challenge.
|
||||
const requestOtp = async (data: FormData) => {
|
||||
setError(null);
|
||||
setSending(true);
|
||||
try {
|
||||
const availability = await api.auth.checkAvailability.call({
|
||||
email: data.email,
|
||||
phone: data.phone,
|
||||
});
|
||||
if (availability.emailTaken && availability.phoneTaken) {
|
||||
setError("An account with this email and phone number already exists.");
|
||||
return;
|
||||
}
|
||||
if (availability.emailTaken) {
|
||||
setError("An account with this email already exists.");
|
||||
return;
|
||||
}
|
||||
if (availability.phoneTaken) {
|
||||
setError("An account with this phone number already exists.");
|
||||
return;
|
||||
}
|
||||
|
||||
await api.auth.sendOTP.call(
|
||||
channel === "email" ? { email: data.email } : { phone: data.phone },
|
||||
);
|
||||
|
||||
@@ -66,6 +66,8 @@ import type {
|
||||
import type { ProfileResponse, UpdateProfilePayload } from "@/types/profile";
|
||||
import type {
|
||||
AuthUser,
|
||||
CheckAvailabilityPayload,
|
||||
CheckAvailabilityResponse,
|
||||
GenerateVerificationCodePayload,
|
||||
LoginPayload,
|
||||
LoginResponse,
|
||||
@@ -107,6 +109,11 @@ export const api = {
|
||||
"setPassword",
|
||||
authService.setPassword,
|
||||
),
|
||||
checkAvailability: endpoint<CheckAvailabilityPayload, CheckAvailabilityResponse>(
|
||||
"auth",
|
||||
"checkAvailability",
|
||||
authService.checkAvailability,
|
||||
),
|
||||
sendOTP: endpoint<OtpPayload, OtpResponse>(
|
||||
"auth",
|
||||
"sendOTP",
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { URL_CONSTANTS } from "@/constants/URLS";
|
||||
import type {
|
||||
AuthUser,
|
||||
GenerateVerificationCodePayload,
|
||||
LoginPayload,
|
||||
LoginResponse,
|
||||
OtpPayload,
|
||||
OtpResponse,
|
||||
SetPasswordPayload,
|
||||
SignupPayload,
|
||||
SignupResponse,
|
||||
AuthUser,
|
||||
CheckAvailabilityPayload,
|
||||
CheckAvailabilityResponse,
|
||||
GenerateVerificationCodePayload,
|
||||
LoginPayload,
|
||||
LoginResponse,
|
||||
OtpPayload,
|
||||
OtpResponse,
|
||||
SetPasswordPayload,
|
||||
SignupPayload,
|
||||
SignupResponse,
|
||||
} from "@/types/auth";
|
||||
import { client } from "@/utils/api";
|
||||
import { ApiResponse } from "@edr/types";
|
||||
@@ -23,7 +25,7 @@ export const authService = {
|
||||
},
|
||||
|
||||
createUser: async (body: SignupPayload) => {
|
||||
const res = await client.post<SignupResponse & ApiResponse<void>> (
|
||||
const res = await client.post<SignupResponse & ApiResponse<void>>(
|
||||
URL_CONSTANTS.USERS.SIGN_UP,
|
||||
body,
|
||||
);
|
||||
@@ -31,9 +33,7 @@ export const authService = {
|
||||
},
|
||||
|
||||
getMyInfo: async () => {
|
||||
const res = await client.get<AuthUser>(
|
||||
URL_CONSTANTS.USERS.ME,
|
||||
);
|
||||
const res = await client.get<AuthUser>(URL_CONSTANTS.USERS.ME);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
@@ -53,6 +53,14 @@ export const authService = {
|
||||
return res.data.data;
|
||||
},
|
||||
|
||||
checkAvailability: async (params: CheckAvailabilityPayload) => {
|
||||
const res = await client.get<ApiResponse<CheckAvailabilityResponse>>(
|
||||
URL_CONSTANTS.USERS.CHECK_AVAILABILITY,
|
||||
{ params },
|
||||
);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
sendOTP: async (body: OtpPayload) => {
|
||||
const res = await client.post<ApiResponse<OtpResponse>>(
|
||||
URL_CONSTANTS.OTP.SEND,
|
||||
|
||||
@@ -45,6 +45,16 @@ export interface OtpResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface CheckAvailabilityPayload {
|
||||
email?: string;
|
||||
phone?: string;
|
||||
}
|
||||
|
||||
export interface CheckAvailabilityResponse {
|
||||
emailTaken: boolean;
|
||||
phoneTaken: boolean;
|
||||
}
|
||||
|
||||
export interface SetPasswordPayload {
|
||||
newPassword: string;
|
||||
confirmPassword: string;
|
||||
|
||||
Reference in New Issue
Block a user