diff --git a/package.json b/package.json index 5a771dab..d47c43a6 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "firebase-admin": "^11.11.0", "handlebars": "^4.7.8", "helmet": "^7.0.0", - "helper-fns": "^2.6.27", + "helper-fns": "^2.6.28", "ioredis": "^5.3.2", "isomorphic-dompurify": "^1.9.0", "joi": "^17.11.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ea019033..6b804be4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -144,8 +144,8 @@ dependencies: specifier: ^7.0.0 version: 7.0.0 helper-fns: - specifier: ^2.6.27 - version: 2.6.27 + specifier: ^2.6.28 + version: 2.6.28 ioredis: specifier: ^5.3.2 version: 5.3.2 @@ -8661,8 +8661,8 @@ packages: readable-stream: 3.6.2 dev: false - /helper-fns@2.6.27: - resolution: {integrity: sha512-6wQ8Z+M05b9/hKk7Sxlt51d9cwc0xm/4ZAqHcOBQPrbBR45Vu8PFClkJ35e7RZTgkj2B+lEmVo8dijeXW3c5NA==} + /helper-fns@2.6.28: + resolution: {integrity: sha512-ZjtWUQM+lcZOsB5tR7vgpblzKlxQqcf7Ow8r3k5JmR0rPTPWSKiFYJ90ZOu0kwsN794UjQ2lY7K2CO/uW+3R5w==} dev: false /hexoid@1.0.0: diff --git a/src/common/@types/classes/common.response.ts b/src/common/@types/classes/common.response.ts index 7c1cfe46..83cd641c 100644 --- a/src/common/@types/classes/common.response.ts +++ b/src/common/@types/classes/common.response.ts @@ -3,7 +3,8 @@ export class AuthenticationResponse { * @example fb9eac5f-eb94-489b-8fca-24324558be18 */ user!: { - idx: string + idx?: string + id: number }; /** diff --git a/src/common/@types/global.d.ts b/src/common/@types/global.d.ts index a1820e37..bf82da84 100644 --- a/src/common/@types/global.d.ts +++ b/src/common/@types/global.d.ts @@ -47,6 +47,7 @@ declare global { JWT_ACCESS_EXPIRY: string JWT_REFRESH_EXPIRY: string JWT_SECRET: string + MAGIC_LINK_EXPIRY: string MAIL_HOST: string MAIL_PASSWORD: string diff --git a/src/common/@types/interfaces/authentication.interface.ts b/src/common/@types/interfaces/authentication.interface.ts index c0b311c3..521b9c0a 100644 --- a/src/common/@types/interfaces/authentication.interface.ts +++ b/src/common/@types/interfaces/authentication.interface.ts @@ -1,7 +1,7 @@ export interface OauthResponse { email: string - firstName?: string - lastName?: string + firstName: string + lastName: string accessToken: string } diff --git a/src/common/decorators/validation/is-date-field.validator.ts b/src/common/decorators/validation/is-date-field.validator.ts index 7339c18f..2081441d 100644 --- a/src/common/decorators/validation/is-date-field.validator.ts +++ b/src/common/decorators/validation/is-date-field.validator.ts @@ -16,7 +16,8 @@ export function IsDateField(options_?: DateFieldOptions) { arrayMinSize: 0, arrayMaxSize: Number.MAX_SAFE_INTEGER, ...options_, - }; + } satisfies DateFieldOptions; + const decoratorsToApply = [ IsDateString( { strict: true }, diff --git a/src/common/decorators/validation/is-date-format.validator.ts b/src/common/decorators/validation/is-date-format.validator.ts index 1c8713e7..1761b97d 100644 --- a/src/common/decorators/validation/is-date-format.validator.ts +++ b/src/common/decorators/validation/is-date-format.validator.ts @@ -31,9 +31,9 @@ class IsDateInFormatConstraint implements ValidatorConstraintInterface { const [format] = arguments_.constraints as string[]; if (isArray(value)) - return value.some(v => isMatch(v, format)); + return value.some(v => isMatch(v, format!)); - return isMatch(value, format); + return isMatch(value, format!); } defaultMessage(arguments_: ValidationArguments) { diff --git a/src/common/decorators/validation/is-number-field.decorator.ts b/src/common/decorators/validation/is-number-field.decorator.ts index c7c6e42b..817481bd 100644 --- a/src/common/decorators/validation/is-number-field.decorator.ts +++ b/src/common/decorators/validation/is-number-field.decorator.ts @@ -31,7 +31,8 @@ export function IsNumberField(options_?: NumberFieldOptions) { int: true, positive: true, ...options_, - }; + } satisfies NumberFieldOptions; + const decoratorsToApply = [ Type(() => Number), Min(options.min, { diff --git a/src/common/decorators/validation/is-string-field.decorator.ts b/src/common/decorators/validation/is-string-field.decorator.ts index 154ee4e2..acd3242d 100644 --- a/src/common/decorators/validation/is-string-field.decorator.ts +++ b/src/common/decorators/validation/is-string-field.decorator.ts @@ -21,7 +21,7 @@ import { Sanitize, Trim } from "./transform.decorator"; */ export function IsStringField(options_?: StringFieldOptions) { - const options: StringFieldOptions = { + const options = { required: true, each: false, sanitize: true, @@ -31,7 +31,8 @@ export function IsStringField(options_?: StringFieldOptions) { arrayMinSize: 0, arrayMaxSize: Number.MAX_SAFE_INTEGER, ...options_, - }; + } satisfies StringFieldOptions; + const decoratorsToApply = [ IsString({ message: validationI18nMessage("validation.isDataType", { diff --git a/src/common/decorators/validation/is-uuid.validator.ts b/src/common/decorators/validation/is-uuid.validator.ts index 000df438..32132ca2 100644 --- a/src/common/decorators/validation/is-uuid.validator.ts +++ b/src/common/decorators/validation/is-uuid.validator.ts @@ -9,11 +9,12 @@ import { validationI18nMessage } from "@lib/i18n"; * @returns A decorator function that takes in a target, propertyKey, and descriptor. */ export function IsUUIDField(options_?: UUIDFieldOptions) { - const options: UUIDFieldOptions = { + const options = { each: false, required: true, ...options_, - }; + } satisfies UUIDFieldOptions; + const decoratorsToApply = [ IsUUID("4", { message: validationI18nMessage("validation.isDataType", { diff --git a/src/common/guards/auth.guard.ts b/src/common/guards/auth.guard.ts index 9687b25c..442fac35 100644 --- a/src/common/guards/auth.guard.ts +++ b/src/common/guards/auth.guard.ts @@ -23,7 +23,12 @@ export class AuthGuard implements CanActivate { throw new UnauthorizedException(translate("exception.apiUnauthorizedResponse")); try { - const decoded: { idx: string } = this.jwt.verify(token.split(" ")[1]); + const tokenValue = token.split(" ")[1] + + if(!tokenValue){ + return false; + } + const decoded: { idx: string } = this.jwt.verify(tokenValue); request.idx = decoded.idx; diff --git a/src/common/guards/throttle.guard.ts b/src/common/guards/throttle.guard.ts index 22b57d82..c4e5988e 100644 --- a/src/common/guards/throttle.guard.ts +++ b/src/common/guards/throttle.guard.ts @@ -8,6 +8,13 @@ export class CustomThrottlerGuard extends ThrottlerGuard { protected errorMessage = THROTTLE_LIMIT_RESPONSE; protected async getTracker(request: Request): Promise { - return request.ips.length > 0 ? request.ips[0] : request.ip; + if (request.ips.length > 0 && request.ips[0]) { + return request.ips[0]; + } else if (request.ip) { + return request.ip; + } + throw new Error("Unable to get IP address"); + } + } diff --git a/src/common/helpers/helpers.utils.ts b/src/common/helpers/helpers.utils.ts index 307bf902..cb524ca3 100644 --- a/src/common/helpers/helpers.utils.ts +++ b/src/common/helpers/helpers.utils.ts @@ -11,6 +11,7 @@ import { from } from "rxjs"; import sharp from "sharp"; import type { User } from "@entities"; import type { AuthenticationResponse } from "@common/@types"; +import { REDIS_URI_REGEX } from "@common/constant"; const argon2Options: ArgonOptions & { raw?: false } = { type: argon2id, @@ -92,24 +93,34 @@ which is the hashed password to compare against. */ return new Date(format(currentUtcTime, "yyyy-MM-dd HH:mm:ss")); }, + + redisUrlToOptions(url: string): RedisOptions { + if(!REDIS_URI_REGEX.test(url)){ + throw new Error("Invalid redis url"); + } + + const separator = "://"; + if (url.includes("://:")) { - const array = url.split("://:")[1].split("@"); - const secondArray = array[1].split(":"); + const [_, credentials] = url.split(separator); + const [password, rest] = credentials.split("@"); + const [host, port] = rest.split(":"); return { - password: array[0], - host: secondArray[0], - port: Number.parseInt(secondArray[1], 10), + password, + host, + port: Number.parseInt(port, 10), }; } - const connectionString = url.split("://")[1]; - const array = connectionString.split(":"); + const connectionString = url.split(separator)[1]; + const [host, port] = connectionString.split(":"); return { - host: array[0], - port: Number.parseInt(array[1], 10), + host, + port: Number.parseInt(port, 10), }; + }, }; diff --git a/src/common/middlewares/ip.middleware.ts b/src/common/middlewares/ip.middleware.ts index 646e71c1..f139795f 100644 --- a/src/common/middlewares/ip.middleware.ts +++ b/src/common/middlewares/ip.middleware.ts @@ -6,7 +6,7 @@ import { getClientIp } from "@supercharge/request-ip"; @Injectable() export class RealIpMiddleware implements NestMiddleware { use(request: Request, _response: Response, next: NextFunction) { - request.realIp = getClientIp(request); + request.realIp = getClientIp(request)!; next(); } } diff --git a/src/common/misc/workers.ts b/src/common/misc/workers.ts index 7c54e0e2..5abd0bea 100644 --- a/src/common/misc/workers.ts +++ b/src/common/misc/workers.ts @@ -15,6 +15,6 @@ function workerFunction(data: { functionName: string; input: string }) { } } -const threadWorker = new ThreadWorker(workerFunction); +const threadWorker = new ThreadWorker(workerFunction as any); export default threadWorker; diff --git a/src/entities/post.entity.ts b/src/entities/post.entity.ts index 876d4b84..f651344b 100644 --- a/src/entities/post.entity.ts +++ b/src/entities/post.entity.ts @@ -46,7 +46,7 @@ export class Post extends BaseEntity { eager: false, index: true, }) - author: Rel>; + author!: Rel>; @OneToMany(() => Comment, comment => comment.post, { eager: false, @@ -84,7 +84,7 @@ export class Post extends BaseEntity { getReadingTime(content: string) { const avgWordsPerMin = 250; - const count = content.match(/\w+/g).length; + const count = content.match(/\w+/g)?.length ?? 0; return Math.ceil(count / avgWordsPerMin); } diff --git a/src/entities/referral.entity.ts b/src/entities/referral.entity.ts index 2d2f1970..ffac3d8e 100644 --- a/src/entities/referral.entity.ts +++ b/src/entities/referral.entity.ts @@ -9,12 +9,12 @@ export class Referral extends BaseEntity { @ManyToOne({ index: true, }) - referrer: Rel>; + referrer!: Rel>; @Property({ index: true, }) - mobileNumber: string; + mobileNumber!: string; @Index() @Enum(() => ReferralStatus) diff --git a/src/lib/aws/aws.s3.service.ts b/src/lib/aws/aws.s3.service.ts index 571a04be..e5eddf74 100644 --- a/src/lib/aws/aws.s3.service.ts +++ b/src/lib/aws/aws.s3.service.ts @@ -66,6 +66,9 @@ export class AwsS3Service { Prefix: prefix, }), )).pipe(map((listItems) => { + if(!listItems.Contents) return []; + + const mapList = listItems.Contents.map((value) => { const lastIndex = value.Key.lastIndexOf("/"); const path = value.Key.slice(0, lastIndex); @@ -159,8 +162,8 @@ export class AwsS3Service { content: Uint8Array | Buffer, options?: AwsS3PutItemOptions, ): Observable { - const filename = options.keepOriginalName ? originalFilename : this.generateFileName(originalFilename); - const { key, mime, path } = this.getOptions(options, filename); + const filename = options?.keepOriginalName ? originalFilename : this.generateFileName(originalFilename); + const { key, mime, path } = this.getOptions(filename,options); return from(this.s3Client.send( new PutObjectCommand({ Bucket: this.bucket, @@ -274,7 +277,7 @@ export class AwsS3Service { filename: string, options?: AwsS3PutItemOptions, ): Observable { - const { key, mime, path, acl } = this.getOptions(options, filename); + const { key, mime, path, acl } = this.getOptions(filename,options ); return from(this.s3Client.send( new CreateMultipartUploadCommand({ @@ -303,7 +306,7 @@ export class AwsS3Service { * file. * @returns An object with the properties `key`, `mime`, `path`, and `acl`. */ - private getOptions(options: AwsS3PutItemOptions, filename: string) { + private getOptions( filename: string,options?: AwsS3PutItemOptions) { let path = options?.path ?? undefined; const acl = options?.acl ?? "public-read"; diff --git a/src/lib/config/configs/minio.config.ts b/src/lib/config/configs/minio.config.ts index 864ef101..e20d65d6 100644 --- a/src/lib/config/configs/minio.config.ts +++ b/src/lib/config/configs/minio.config.ts @@ -11,7 +11,7 @@ export const minioConfigValidationSchema = { export const minio = registerAs("minio", () => ({ host: process.env.MINIO_HOST, - port: Number.parseInt(process.env.?MINIO_PORT, 10), + port: Number.parseInt(process.env.MINIO_PORT, 10), accessKey: process.env.MINIO_ACCESS_KEY, secretKey: process.env.MINIO_SECRET_KEY, useSSl: process.env.MINIO_USE_SSL === "true", diff --git a/src/lib/crud/crud.controller.ts b/src/lib/crud/crud.controller.ts index ee9fd3ca..8b503d3c 100644 --- a/src/lib/crud/crud.controller.ts +++ b/src/lib/crud/crud.controller.ts @@ -53,7 +53,7 @@ export function ControllerFactory< C extends RequiredEntityData, U extends EntityData, > implements Crud { - protected service: BaseService; + protected service!: BaseService; @Get(":idx") @SwaggerResponse({ diff --git a/src/lib/crud/crud.service.ts b/src/lib/crud/crud.service.ts index 639c0b7c..f748d5bf 100644 --- a/src/lib/crud/crud.service.ts +++ b/src/lib/crud/crud.service.ts @@ -14,7 +14,7 @@ export abstract class BaseService< CreateDto extends RequiredEntityData = RequiredEntityData, UpdateDto extends EntityData = EntityData, > implements Crud { - protected searchField: keyof Entity; + protected searchField!: keyof Entity; protected queryName = "entity"; protected constructor(private readonly repository: BaseRepository) {} @@ -65,11 +65,11 @@ export abstract class BaseService< return this.repository.qbOffsetPagination({ pageOptionsDto: { + ...dto, alias: this.queryName, order: QueryOrder.ASC, offset: dto.offset, searchField: this.searchField, - ...dto, }, qb, }); diff --git a/src/lib/i18n/translate.ts b/src/lib/i18n/translate.ts index d213a1f5..374c818e 100644 --- a/src/lib/i18n/translate.ts +++ b/src/lib/i18n/translate.ts @@ -2,7 +2,14 @@ import type { Path, TranslateOptions } from "nestjs-i18n"; import { I18nContext, i18nValidationMessage } from "nestjs-i18n"; export function translate(key: Path, options: TranslateOptions = {}) { - return I18nContext.current().t(key, options); + const i18nContext = I18nContext.current(); + + if (i18nContext) { + return i18nContext.t(key, options); + } + + // Handle the case when i18nContext is undefined + return ''; // or throw an error, return a default value, etc. } export function validationI18nMessage(key: Path, arguments_?: any) { diff --git a/src/lib/minio.module.ts b/src/lib/minio.module.ts index 5aaf6ce1..40273fbe 100644 --- a/src/lib/minio.module.ts +++ b/src/lib/minio.module.ts @@ -8,12 +8,12 @@ import { NestMinioModule } from "nestjs-minio"; imports: [ConfigModule], inject: [ConfigService], isGlobal: true, - useFactory: async (configService: ConfigService) => ({ - endPoint: configService.get("minio.host")!, - port: configService.get("minio.port"), - accessKey: configService.get("minio.accessKey"), - secretKey: configService.get("minio.secretKey"), - useSSL: configService.get("minio.ssl"), + useFactory: async (configService: ConfigService) => ({ + endPoint: configService.get("minio.host",{ infer: true }), + port: configService.get("minio.port",{ infer: true }), + accessKey: configService.get("minio.accessKey",{ infer: true }), + secretKey: configService.get("minio.secretKey",{ infer: true }), + useSSL: configService.get("minio.ssl",{ infer: true }), }), }), ], diff --git a/src/modules/auth/strategies/facebook.strategy.ts b/src/modules/auth/strategies/facebook.strategy.ts index 83d9cf8e..f47fc429 100644 --- a/src/modules/auth/strategies/facebook.strategy.ts +++ b/src/modules/auth/strategies/facebook.strategy.ts @@ -9,6 +9,7 @@ import type { VerifyCallback } from "passport-google-oauth20"; import { User } from "@entities"; import { BaseRepository } from "@common/database"; import type { OauthResponse } from "@common/@types"; +import { faker } from "@mikro-orm/seeder"; @Injectable() export class FacebookStrategy extends PassportStrategy(Strategy, "facebook") { @@ -44,8 +45,8 @@ export class FacebookStrategy extends PassportStrategy(Strategy, "facebook") { const { name, emails, username, photos } = profile; const user: OauthResponse = { email: emails![0]!.value, - firstName: name?.givenName, - lastName: name?.familyName, + firstName: name?.givenName ?? faker.color.human(), + lastName: name?.familyName ?? faker.animal.cetacean(), accessToken, }; // Check if the user already exists in your database @@ -62,8 +63,9 @@ export class FacebookStrategy extends PassportStrategy(Strategy, "facebook") { // If the user doesn't exist, create a new user const newUser = this.userRepo.create({ ...omit(user, ["accessToken"]), - avatar: photos?.[0]?.value ?? '', - username, + avatar: photos?.[0]?.value ?? "", + username: username ?? emails![0]!.value, + bio: faker.lorem.paragraph(), password: randomString({ length: 10, symbols: true, numbers: true }), }); diff --git a/src/modules/auth/strategies/google.strategy.ts b/src/modules/auth/strategies/google.strategy.ts index 0efc51f8..a0727827 100644 --- a/src/modules/auth/strategies/google.strategy.ts +++ b/src/modules/auth/strategies/google.strategy.ts @@ -8,6 +8,7 @@ import { Strategy } from "passport-google-oauth20"; import { User } from "@entities"; import { BaseRepository } from "@common/database"; import type { OauthResponse } from "@common/@types"; +import { faker } from "@mikro-orm/seeder"; @Injectable() export class GoogleStrategy extends PassportStrategy(Strategy, "google") { @@ -42,8 +43,8 @@ export class GoogleStrategy extends PassportStrategy(Strategy, "google") { const { name, emails, photos, username } = profile; const user: OauthResponse = { email: emails![0]!.value, - firstName: name?.givenName, - lastName: name?.familyName, + firstName: name?.givenName ?? faker.color.human(), + lastName: name?.familyName ?? faker.animal.cetacean(), accessToken, }; @@ -61,8 +62,9 @@ export class GoogleStrategy extends PassportStrategy(Strategy, "google") { // If the user doesn't exist, create a new user const newUser = this.userRepo.create({ ...omit(user, ["accessToken"]), - avatar: photos?.[0]?.value ?? '', - username, + avatar: photos?.[0]?.value ?? "", + username: username ?? emails![0]!.value, + bio: faker.lorem.paragraph(), password: randomString({ length: 10, symbols: true, numbers: true }), }); diff --git a/src/modules/chat/socket-connection.service.ts b/src/modules/chat/socket-connection.service.ts index 3fa5baf1..7dcd6bdb 100644 --- a/src/modules/chat/socket-connection.service.ts +++ b/src/modules/chat/socket-connection.service.ts @@ -26,6 +26,7 @@ export class SocketConnectionService { user = value; } + return user; }