feat: verified resources endpoint [v2] (#21006)
* feat: verified resources endpoint [v2] * add tests and fixes * chore: add endpoint throttler decorator and handling * chore: code review comments * chore: code review comments * chore: code review comments * bump platform libraries * chore: add tests and fix issues
This commit is contained in:
1 parent
c400c36306
commit
0f0c2b8b92
27 files changed
+4618
-4
No files matched your search
+196
@@ -0,0 +1,196 @@
|
||||
import { API_KEY_OR_ACCESS_TOKEN_HEADER } from "@/lib/docs/headers";
|
||||
import { Throttle } from "@/lib/endpoint-throttler-decorator";
|
||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
import { RequestEmailVerificationInput } from "@/modules/verified-resources/inputs/request-email-verification.input";
|
||||
import { RequestPhoneVerificationInput } from "@/modules/verified-resources/inputs/request-phone-verification.input";
|
||||
import { VerifyEmailInput } from "@/modules/verified-resources/inputs/verify-email.input";
|
||||
import { VerifyPhoneInput } from "@/modules/verified-resources/inputs/verify-phone.input";
|
||||
import { RequestEmailVerificationOutput } from "@/modules/verified-resources/outputs/request-email-verification-output";
|
||||
import { RequestPhoneVerificationOutput } from "@/modules/verified-resources/outputs/request-phone-verification-output";
|
||||
import {
|
||||
UserVerifiedEmailOutput,
|
||||
UserVerifiedEmailOutputData,
|
||||
UserVerifiedEmailsOutput,
|
||||
} from "@/modules/verified-resources/outputs/verified-email.output";
|
||||
import {
|
||||
UserVerifiedPhoneOutput,
|
||||
UserVerifiedPhoneOutputData,
|
||||
UserVerifiedPhonesOutput,
|
||||
} from "@/modules/verified-resources/outputs/verified-phone.output";
|
||||
import { VerifiedResourcesService } from "@/modules/verified-resources/services/verified-resources.service";
|
||||
import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Query, UseGuards } from "@nestjs/common";
|
||||
import { ApiHeader, ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
import { plainToClass } from "class-transformer";
|
||||
|
||||
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { SkipTakePagination } from "@calcom/platform-types";
|
||||
|
||||
@Controller({
|
||||
path: "/v2/verified-resources",
|
||||
})
|
||||
@UseGuards(ApiAuthGuard)
|
||||
@ApiTags("Verified Resources")
|
||||
export class UserVerifiedResourcesController {
|
||||
constructor(private readonly verifiedResourcesService: VerifiedResourcesService) {}
|
||||
@ApiOperation({
|
||||
summary: "Request Email Verification Code",
|
||||
description: `Sends a verification code to the email.`,
|
||||
})
|
||||
@ApiHeader(API_KEY_OR_ACCESS_TOKEN_HEADER)
|
||||
@Post("/emails/verification-code/request")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Throttle({ limit: 5, ttl: 60000, blockDuration: 60000, name: "users_verified_resources_emails_requests" })
|
||||
async requestEmailVerificationCode(
|
||||
@Body() body: RequestEmailVerificationInput,
|
||||
@GetUser("username") username: string,
|
||||
@GetUser("locale") locale: string
|
||||
): Promise<RequestEmailVerificationOutput> {
|
||||
const verificationCodeRequest = await this.verifiedResourcesService.requestEmailVerificationCode(
|
||||
{ username, locale },
|
||||
body.email
|
||||
);
|
||||
|
||||
return {
|
||||
status: verificationCodeRequest ? SUCCESS_STATUS : ERROR_STATUS,
|
||||
};
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: "Request Phone Number Verification Code",
|
||||
description: `Sends a verification code to the phone number.`,
|
||||
})
|
||||
@ApiHeader(API_KEY_OR_ACCESS_TOKEN_HEADER)
|
||||
@Post("/phones/verification-code/request")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Throttle({ limit: 3, ttl: 60000, blockDuration: 60000, name: "users_verified_resources_phones_requests" })
|
||||
async requestPhoneVerificationCode(
|
||||
@Body() body: RequestPhoneVerificationInput
|
||||
): Promise<RequestPhoneVerificationOutput> {
|
||||
const verificationCodeRequest = await this.verifiedResourcesService.requestPhoneVerificationCode(
|
||||
body.phone
|
||||
);
|
||||
|
||||
return {
|
||||
status: verificationCodeRequest ? SUCCESS_STATUS : ERROR_STATUS,
|
||||
};
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: "Verify an email.",
|
||||
description: `Use code to verify an email`,
|
||||
})
|
||||
@ApiHeader(API_KEY_OR_ACCESS_TOKEN_HEADER)
|
||||
@Post("/emails/verification-code/verify")
|
||||
@Throttle({ limit: 3, ttl: 60000, blockDuration: 60000, name: "users_verified_resources_phones_verify" })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async verifyEmail(
|
||||
@Body() body: VerifyEmailInput,
|
||||
@GetUser("id") userId: number
|
||||
): Promise<UserVerifiedEmailOutput> {
|
||||
const verifiedEmail = await this.verifiedResourcesService.verifyEmail(userId, body.email, body.code);
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: plainToClass(UserVerifiedEmailOutputData, verifiedEmail),
|
||||
};
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: "Verify a phone number.",
|
||||
description: `Use code to verify a phone number`,
|
||||
})
|
||||
@ApiHeader(API_KEY_OR_ACCESS_TOKEN_HEADER)
|
||||
@Post("/phones/verification-code/verify")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async verifyPhoneNumber(
|
||||
@Body() body: VerifyPhoneInput,
|
||||
@GetUser("id") userId: number
|
||||
): Promise<UserVerifiedPhoneOutput> {
|
||||
const verifiedPhone = await this.verifiedResourcesService.verifyPhone(userId, body.phone, body.code);
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: plainToClass(UserVerifiedPhoneOutputData, verifiedPhone),
|
||||
};
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: "Get list of verified emails.",
|
||||
})
|
||||
@ApiHeader(API_KEY_OR_ACCESS_TOKEN_HEADER)
|
||||
@Get("/emails")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getVerifiedEmails(
|
||||
@GetUser("id") userId: number,
|
||||
@Query() pagination: SkipTakePagination
|
||||
): Promise<UserVerifiedEmailsOutput> {
|
||||
const verifiedEmails = await this.verifiedResourcesService.getUserVerifiedEmails(
|
||||
userId,
|
||||
pagination?.skip ?? 0,
|
||||
pagination?.take ?? 250
|
||||
);
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: verifiedEmails.map((verifiedEmail) => plainToClass(UserVerifiedEmailOutputData, verifiedEmail)),
|
||||
};
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: "Get list of verified phone numbers.",
|
||||
})
|
||||
@ApiHeader(API_KEY_OR_ACCESS_TOKEN_HEADER)
|
||||
@Get("/phones")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getVerifiedPhoneNumbers(
|
||||
@GetUser("id") userId: number,
|
||||
@Query() pagination: SkipTakePagination
|
||||
): Promise<UserVerifiedPhonesOutput> {
|
||||
const verifiedPhoneNumbers = await this.verifiedResourcesService.getUserVerifiedPhoneNumbers(
|
||||
userId,
|
||||
pagination?.skip ?? 0,
|
||||
pagination?.take ?? 250
|
||||
);
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: verifiedPhoneNumbers.map((verifiedPhoneNumber) =>
|
||||
plainToClass(UserVerifiedPhoneOutputData, verifiedPhoneNumber)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: "Get verified email by id.",
|
||||
})
|
||||
@ApiHeader(API_KEY_OR_ACCESS_TOKEN_HEADER)
|
||||
@Get("/emails/:id")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getVerifiedEmailById(
|
||||
@GetUser("id") userId: number,
|
||||
@Param("id") id: number
|
||||
): Promise<UserVerifiedEmailOutput> {
|
||||
const verifiedEmail = await this.verifiedResourcesService.getUserVerifiedEmailById(userId, id);
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: plainToClass(UserVerifiedEmailOutputData, verifiedEmail),
|
||||
};
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: "Get verified phone number by id.",
|
||||
})
|
||||
@ApiHeader(API_KEY_OR_ACCESS_TOKEN_HEADER)
|
||||
@Get("/phones/:id")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getVerifiedPhoneById(
|
||||
@GetUser("id") userId: number,
|
||||
@Param("id") id: number
|
||||
): Promise<UserVerifiedPhoneOutput> {
|
||||
const verifiedPhoneNumber = await this.verifiedResourcesService.getUserVerifiedPhoneNumberById(
|
||||
userId,
|
||||
id
|
||||
);
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: plainToClass(UserVerifiedPhoneOutputData, verifiedPhoneNumber),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Expose } from "class-transformer";
|
||||
import { IsEmail } from "class-validator";
|
||||
|
||||
export class RequestEmailVerificationInput {
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
description: "Email to verify.",
|
||||
example: "acme@example.com",
|
||||
})
|
||||
@IsEmail()
|
||||
@Expose()
|
||||
email!: string;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Expose } from "class-transformer";
|
||||
import { IsPhoneNumber } from "class-validator";
|
||||
|
||||
export class RequestPhoneVerificationInput {
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
description: "Phone number to verify.",
|
||||
example: "+372 5555 6666",
|
||||
})
|
||||
@IsPhoneNumber()
|
||||
@Expose()
|
||||
phone!: string;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Expose } from "class-transformer";
|
||||
import { IsEmail, IsString } from "class-validator";
|
||||
|
||||
export class VerifyEmailInput {
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
description: "Email to verify.",
|
||||
example: "example@acme.com",
|
||||
})
|
||||
@IsEmail()
|
||||
@Expose()
|
||||
email!: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
description: "verification code sent to the email to verify",
|
||||
example: "1ABG2C",
|
||||
})
|
||||
@Expose()
|
||||
@IsString()
|
||||
code!: string;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Expose } from "class-transformer";
|
||||
import { IsEmail, IsPhoneNumber, IsString } from "class-validator";
|
||||
|
||||
export class VerifyPhoneInput {
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
description: "phone number to verify.",
|
||||
example: "+37255556666",
|
||||
})
|
||||
@IsPhoneNumber()
|
||||
@Expose()
|
||||
phone!: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
description: "verification code sent to the phone number to verify",
|
||||
example: "1ABG2C",
|
||||
})
|
||||
@Expose()
|
||||
@IsString()
|
||||
code!: string;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsEnum } from "class-validator";
|
||||
|
||||
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
export class RequestEmailVerificationOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsEnum } from "class-validator";
|
||||
|
||||
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
export class RequestPhoneVerificationOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import { ScheduleOutput } from "@/ee/schedules/schedules_2024_04_15/outputs/schedule.output";
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Expose, Type } from "class-transformer";
|
||||
import {
|
||||
IsArray,
|
||||
IsEnum,
|
||||
IsNotEmptyObject,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator";
|
||||
|
||||
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
export class UserVerifiedEmailOutputData {
|
||||
@Expose()
|
||||
@IsNumber()
|
||||
@ApiProperty({
|
||||
description: "The unique identifier for the verified email.",
|
||||
example: 789,
|
||||
})
|
||||
id!: number;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
description: "The verified email address.",
|
||||
example: "user@example.com",
|
||||
format: "email",
|
||||
})
|
||||
email!: string;
|
||||
|
||||
@Expose()
|
||||
@IsNumber()
|
||||
@ApiProperty({
|
||||
description: "The ID of the associated user, if applicable.",
|
||||
example: 45,
|
||||
})
|
||||
userId!: number;
|
||||
}
|
||||
|
||||
export class TeamVerifiedEmailOutputData {
|
||||
@Expose()
|
||||
@IsNumber()
|
||||
@ApiProperty({
|
||||
description: "The unique identifier for the verified email.",
|
||||
example: 789,
|
||||
})
|
||||
id!: number;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
description: "The verified email address.",
|
||||
example: "user@example.com",
|
||||
format: "email",
|
||||
})
|
||||
email!: string;
|
||||
|
||||
@Expose()
|
||||
@IsNumber()
|
||||
@ApiProperty({
|
||||
description: "The ID of the associated team, if applicable.",
|
||||
example: 89,
|
||||
})
|
||||
teamId!: number;
|
||||
|
||||
@Expose()
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
@ApiProperty({
|
||||
description: "The ID of the associated user, if applicable.",
|
||||
example: 45,
|
||||
nullable: true,
|
||||
required: false,
|
||||
})
|
||||
userId?: number | null;
|
||||
}
|
||||
|
||||
export class TeamVerifiedEmailOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({
|
||||
type: ScheduleOutput,
|
||||
})
|
||||
@IsNotEmptyObject()
|
||||
@Type(() => TeamVerifiedEmailOutputData)
|
||||
data!: TeamVerifiedEmailOutputData;
|
||||
}
|
||||
|
||||
export class UserVerifiedEmailOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({
|
||||
type: ScheduleOutput,
|
||||
})
|
||||
@IsNotEmptyObject()
|
||||
@Type(() => UserVerifiedEmailOutputData)
|
||||
data!: UserVerifiedEmailOutputData;
|
||||
}
|
||||
|
||||
export class TeamVerifiedEmailsOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({
|
||||
type: ScheduleOutput,
|
||||
})
|
||||
@IsNotEmptyObject()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => TeamVerifiedEmailOutputData)
|
||||
data!: TeamVerifiedEmailOutputData[];
|
||||
}
|
||||
|
||||
export class UserVerifiedEmailsOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({
|
||||
type: ScheduleOutput,
|
||||
})
|
||||
@IsNotEmptyObject()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => UserVerifiedEmailOutputData)
|
||||
data!: UserVerifiedEmailOutputData[];
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import { ScheduleOutput } from "@/ee/schedules/schedules_2024_04_15/outputs/schedule.output";
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Expose, Type } from "class-transformer";
|
||||
import {
|
||||
IsArray,
|
||||
IsEnum,
|
||||
IsNotEmptyObject,
|
||||
IsNumber,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
IsOptional,
|
||||
} from "class-validator";
|
||||
|
||||
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
export class UserVerifiedPhoneOutputData {
|
||||
@Expose()
|
||||
@IsNumber()
|
||||
@ApiProperty({
|
||||
description: "The unique identifier for the verified email.",
|
||||
example: 789,
|
||||
})
|
||||
id!: number;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
description: "The verified phone number.",
|
||||
example: "+37255556666",
|
||||
format: "phone",
|
||||
})
|
||||
phoneNumber!: string;
|
||||
|
||||
@Expose()
|
||||
@IsNumber()
|
||||
@ApiProperty({
|
||||
description: "The ID of the associated user, if applicable.",
|
||||
example: 45,
|
||||
})
|
||||
userId!: number;
|
||||
}
|
||||
|
||||
export class TeamVerifiedPhoneOutputData {
|
||||
@Expose()
|
||||
@IsNumber()
|
||||
@ApiProperty({
|
||||
description: "The unique identifier for the verified email.",
|
||||
example: 789,
|
||||
})
|
||||
id!: number;
|
||||
|
||||
@Expose()
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
description: "The verified phone number.",
|
||||
example: "+37255556666",
|
||||
format: "phone",
|
||||
})
|
||||
phoneNumber!: string;
|
||||
|
||||
@Expose()
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
@ApiProperty({
|
||||
description: "The ID of the associated user, if applicable.",
|
||||
example: 45,
|
||||
nullable: true,
|
||||
required: false,
|
||||
})
|
||||
userId?: number | null;
|
||||
|
||||
@Expose()
|
||||
@IsNumber()
|
||||
@ApiProperty({
|
||||
description: "The ID of the associated team, if applicable.",
|
||||
example: 89,
|
||||
})
|
||||
teamId!: number;
|
||||
}
|
||||
|
||||
export class UserVerifiedPhoneOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({
|
||||
type: ScheduleOutput,
|
||||
})
|
||||
@IsNotEmptyObject()
|
||||
@Type(() => UserVerifiedPhoneOutputData)
|
||||
data!: UserVerifiedPhoneOutputData;
|
||||
}
|
||||
|
||||
export class TeamVerifiedPhoneOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({
|
||||
type: ScheduleOutput,
|
||||
})
|
||||
@IsNotEmptyObject()
|
||||
@Type(() => TeamVerifiedPhoneOutputData)
|
||||
data!: TeamVerifiedPhoneOutputData;
|
||||
}
|
||||
|
||||
export class UserVerifiedPhonesOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({
|
||||
type: ScheduleOutput,
|
||||
})
|
||||
@IsNotEmptyObject()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => UserVerifiedPhoneOutputData)
|
||||
data!: UserVerifiedPhoneOutputData[];
|
||||
}
|
||||
|
||||
export class TeamVerifiedPhonesOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({
|
||||
type: ScheduleOutput,
|
||||
})
|
||||
@IsNotEmptyObject()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => TeamVerifiedPhoneOutputData)
|
||||
data!: TeamVerifiedPhoneOutputData[];
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import { TeamsVerifiedResourcesRepository } from "@/modules/verified-resources/teams-verified-resources.repository";
|
||||
import { UsersVerifiedResourcesRepository } from "@/modules/verified-resources/users-verified-resources.repository";
|
||||
import { BadRequestException, ConflictException, Injectable } from "@nestjs/common";
|
||||
|
||||
import {
|
||||
verifyPhoneNumber,
|
||||
sendVerificationCode as sendPhoneVerificationCode,
|
||||
} from "@calcom/platform-libraries";
|
||||
import { sendEmailVerificationByCode, verifyEmailCodeHandler } from "@calcom/platform-libraries/emails";
|
||||
|
||||
@Injectable()
|
||||
export class VerifiedResourcesService {
|
||||
constructor(
|
||||
private readonly usersVerifiedResourcesRepository: UsersVerifiedResourcesRepository,
|
||||
private readonly teamsVerifiedResourcesRepository: TeamsVerifiedResourcesRepository
|
||||
) {}
|
||||
|
||||
async requestEmailVerificationCode(user: { username: string; locale: string }, email: string) {
|
||||
const res = await sendEmailVerificationByCode({
|
||||
email: email,
|
||||
language: user.locale,
|
||||
username: user.username,
|
||||
isVerifyingEmail: true,
|
||||
});
|
||||
|
||||
if (res.ok && !res.skipped) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
throw new BadRequestException("Email could not be verified.");
|
||||
}
|
||||
|
||||
if (res.skipped) {
|
||||
throw new ConflictException("Email is already verified.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async requestPhoneVerificationCode(phone: string) {
|
||||
try {
|
||||
await sendPhoneVerificationCode(phone);
|
||||
} catch (err) {
|
||||
throw new BadRequestException("Could not send verification code to this phone number.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async verifyPhone(userId: number, phone: string, code: string, teamId?: number) {
|
||||
const result = await verifyPhoneNumber(phone, code, userId, teamId);
|
||||
|
||||
if (result) {
|
||||
return teamId
|
||||
? await this.teamsVerifiedResourcesRepository.getTeamVerifiedPhoneNumber(userId, phone, teamId)
|
||||
: await this.usersVerifiedResourcesRepository.getUserVerifiedPhoneNumber(userId, phone);
|
||||
}
|
||||
|
||||
throw new BadRequestException("Could not verify phone number.");
|
||||
}
|
||||
|
||||
async verifyEmail(userId: number, email: string, code: string, teamId?: number) {
|
||||
const isValidToken = await verifyEmailCodeHandler({
|
||||
ctx: { user: { id: userId } },
|
||||
input: { email, code, teamId },
|
||||
});
|
||||
if (isValidToken) {
|
||||
const verifiedEmail = teamId
|
||||
? await this.teamsVerifiedResourcesRepository.getTeamVerifiedEmail(userId, email, teamId)
|
||||
: await this.usersVerifiedResourcesRepository.getUserVerifiedEmail(userId, email);
|
||||
return verifiedEmail;
|
||||
}
|
||||
throw new BadRequestException("Invalid Verification Code");
|
||||
}
|
||||
|
||||
async getUserVerifiedEmailById(userId: number, id: number) {
|
||||
return this.usersVerifiedResourcesRepository.getUserVerifiedEmailById(userId, id);
|
||||
}
|
||||
|
||||
async getTeamVerifiedEmailById(teamId: number, id: number) {
|
||||
return this.teamsVerifiedResourcesRepository.getTeamVerifiedEmailById(id, teamId);
|
||||
}
|
||||
|
||||
async getVerifiedEmail(userId: number, email: string) {
|
||||
return this.usersVerifiedResourcesRepository.getUserVerifiedEmail(userId, email);
|
||||
}
|
||||
|
||||
async getUserVerifiedEmails(userId: number, skip = 0, take = 250) {
|
||||
return this.usersVerifiedResourcesRepository.getUserVerifiedEmails(userId, skip, take);
|
||||
}
|
||||
|
||||
async getTeamVerifiedEmails(teamId: number, skip = 0, take = 250) {
|
||||
return this.teamsVerifiedResourcesRepository.getTeamVerifiedEmails(teamId, skip, take);
|
||||
}
|
||||
|
||||
async getUserVerifiedPhoneNumberById(userId: number, id: number) {
|
||||
return this.usersVerifiedResourcesRepository.getUserVerifiedPhoneNumberById(userId, id);
|
||||
}
|
||||
|
||||
async getTeamVerifiedPhoneNumberById(teamId: number, id: number) {
|
||||
return this.teamsVerifiedResourcesRepository.getTeamVerifiedPhoneNumberById(id, teamId);
|
||||
}
|
||||
|
||||
async getUserVerifiedPhoneNumber(userId: number, phoneNumber: string) {
|
||||
return this.usersVerifiedResourcesRepository.getUserVerifiedEmail(userId, phoneNumber);
|
||||
}
|
||||
|
||||
async getTeamVerifiedPhoneNumber(userId: number, phoneNumber: string, teamId: number) {
|
||||
return this.teamsVerifiedResourcesRepository.getTeamVerifiedPhoneNumber(userId, phoneNumber, teamId);
|
||||
}
|
||||
|
||||
async getUserVerifiedPhoneNumbers(userId: number, skip = 0, take = 250) {
|
||||
return this.usersVerifiedResourcesRepository.getUserVerifiedPhoneNumbers(userId, skip, take);
|
||||
}
|
||||
|
||||
async getTeamVerifiedPhoneNumbers(teamId: number, skip = 0, take = 250) {
|
||||
return this.teamsVerifiedResourcesRepository.getTeamVerifiedPhoneNumbers(teamId, skip, take);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class TeamsVerifiedResourcesRepository {
|
||||
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
|
||||
|
||||
async getTeamVerifiedEmails(teamId: number, skip = 0, take = 250) {
|
||||
return this.dbRead.prisma.verifiedEmail.findMany({
|
||||
where: {
|
||||
teamId,
|
||||
},
|
||||
skip,
|
||||
take,
|
||||
});
|
||||
}
|
||||
|
||||
async getTeamVerifiedPhoneNumbers(teamId: number, skip = 0, take = 250) {
|
||||
return this.dbRead.prisma.verifiedNumber.findMany({
|
||||
where: {
|
||||
teamId,
|
||||
},
|
||||
skip,
|
||||
take,
|
||||
});
|
||||
}
|
||||
|
||||
async getTeamVerifiedEmail(userId: number, email: string, teamId: number) {
|
||||
return this.dbRead.prisma.verifiedEmail.findFirstOrThrow({
|
||||
where: {
|
||||
userId,
|
||||
email,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getTeamVerifiedPhoneNumber(userId: number, phone: string, teamId: number) {
|
||||
return this.dbRead.prisma.verifiedNumber.findFirstOrThrow({
|
||||
where: {
|
||||
userId,
|
||||
phoneNumber: phone,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getTeamVerifiedEmailById(id: number, teamId: number) {
|
||||
return this.dbRead.prisma.verifiedEmail.findFirst({
|
||||
where: {
|
||||
id,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getTeamVerifiedPhoneNumberById(id: number, teamId: number) {
|
||||
return this.dbRead.prisma.verifiedNumber.findFirst({
|
||||
where: {
|
||||
id,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class UsersVerifiedResourcesRepository {
|
||||
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
|
||||
|
||||
async getUserVerifiedEmails(userId: number, skip = 0, take = 250) {
|
||||
return this.dbRead.prisma.verifiedEmail.findMany({
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
skip,
|
||||
take,
|
||||
});
|
||||
}
|
||||
|
||||
async getUserVerifiedPhoneNumber(userId: number, phone: string) {
|
||||
return this.dbRead.prisma.verifiedNumber.findFirstOrThrow({
|
||||
where: {
|
||||
userId,
|
||||
phoneNumber: phone,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getUserVerifiedPhoneNumbers(userId: number, skip = 0, take = 250) {
|
||||
return this.dbRead.prisma.verifiedNumber.findMany({
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
skip,
|
||||
take,
|
||||
});
|
||||
}
|
||||
|
||||
async getUserVerifiedEmail(userId: number, email: string) {
|
||||
return this.dbRead.prisma.verifiedEmail.findFirstOrThrow({
|
||||
where: {
|
||||
userId,
|
||||
email,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getUserVerifiedEmailById(userId: number, id: number) {
|
||||
return this.dbRead.prisma.verifiedEmail.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getUserVerifiedPhoneNumberById(userId: number, id: number) {
|
||||
return this.dbRead.prisma.verifiedNumber.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { AppsRepository } from "@/modules/apps/apps.repository";
|
||||
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
||||
import { OrganizationsTeamsRepository } from "@/modules/organizations/teams/index/organizations-teams.repository";
|
||||
import { OrgTeamsVerifiedResourcesController } from "@/modules/organizations/teams/verified-resources/org-teams-verified-resources.controller";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { RedisModule } from "@/modules/redis/redis.module";
|
||||
import { StripeService } from "@/modules/stripe/stripe.service";
|
||||
import { TeamsVerifiedResourcesController } from "@/modules/teams/verified-resources/teams-verified-resources.controller";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { UserVerifiedResourcesController } from "@/modules/verified-resources/controllers/users-verified-resources.controller";
|
||||
import { VerifiedResourcesService } from "@/modules/verified-resources/services/verified-resources.service";
|
||||
import { TeamsVerifiedResourcesRepository } from "@/modules/verified-resources/teams-verified-resources.repository";
|
||||
import { UsersVerifiedResourcesRepository } from "@/modules/verified-resources/users-verified-resources.repository";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, RedisModule],
|
||||
controllers: [
|
||||
UserVerifiedResourcesController,
|
||||
TeamsVerifiedResourcesController,
|
||||
OrgTeamsVerifiedResourcesController,
|
||||
],
|
||||
providers: [
|
||||
VerifiedResourcesService,
|
||||
UsersVerifiedResourcesRepository,
|
||||
TeamsVerifiedResourcesRepository,
|
||||
MembershipsRepository,
|
||||
OrganizationsTeamsRepository,
|
||||
OrganizationsRepository,
|
||||
StripeService,
|
||||
AppsRepository,
|
||||
CredentialsRepository,
|
||||
UsersRepository,
|
||||
],
|
||||
exports: [VerifiedResourcesService],
|
||||
})
|
||||
export class VerifiedResourcesModule {}
|
||||
Reference in new issue
Block a user