From bbd41b4ce89b6a7e572e5171556285014f005944 Mon Sep 17 00:00:00 2001 From: Rubin Bhandari Date: Thu, 12 Oct 2023 13:46:53 +0545 Subject: [PATCH] fix: ts strict --- package.json | 2 +- pnpm-lock.yaml | 8 +-- src/common/@types/classes/common.response.ts | 5 +- src/common/@types/classes/cursor.response.ts | 10 ++-- src/common/@types/global.d.ts | 1 + .../interfaces/authentication.interface.ts | 4 +- .../@types/interfaces/mail.interface.ts | 2 +- src/common/constant/string.constants.ts | 2 +- src/common/database/base.repository.ts | 2 +- src/common/database/mikro-orm.encrypted.ts | 2 +- src/common/database/user.subscriber.ts | 10 ++-- src/common/decorators/api-file.decorator.ts | 6 +- src/common/decorators/auth.decorator.ts | 4 +- .../validation/is-date-field.validator.ts | 3 +- .../validation/is-date-format.validator.ts | 4 +- .../validation/is-number-field.decorator.ts | 5 +- .../validation/is-string-field.decorator.ts | 5 +- .../validation/is-uuid.validator.ts | 5 +- .../validation/min-max-length.decorator.ts | 2 +- src/common/dtos/pagination.dto.ts | 2 +- src/common/guards/auth.guard.ts | 7 ++- src/common/guards/throttle.guard.ts | 9 ++- src/common/helpers/helpers.utils.ts | 29 ++++++--- src/common/middlewares/ip.middleware.ts | 2 +- src/common/misc/workers.ts | 2 +- src/entities/conversation.entity.ts | 2 +- src/entities/message.entity.ts | 2 +- src/entities/news-letter.entity.ts | 2 +- src/entities/otp-log.entity.ts | 2 +- src/entities/post.entity.ts | 4 +- src/entities/referral.entity.ts | 4 +- src/entities/refresh-token.entity.ts | 2 +- src/lib/aws/aws.s3.service.ts | 60 ++++++++++++------- src/lib/casl/casl-ability.factory.ts | 2 +- src/lib/crud/crud.controller.ts | 2 +- src/lib/crud/crud.service.ts | 4 +- src/lib/firebase-admin/firebase.service.ts | 2 +- src/lib/i18n/translate.ts | 9 ++- src/lib/minio.module.ts | 12 ++-- src/main.ts | 2 +- src/modules/auth/auth.controller.ts | 2 +- src/modules/auth/auth.service.ts | 37 +++++++++++- .../auth/strategies/facebook.strategy.ts | 14 +++-- .../auth/strategies/google.strategy.ts | 14 +++-- .../auth/strategies/jwt-2fa.strategy.ts | 6 +- src/modules/auth/strategies/jwt.strategy.ts | 6 +- .../auth/strategies/magic-login.strategy.ts | 11 ++-- .../category/dto/create-category.dto.ts | 4 +- src/modules/chat/chat.gateway.ts | 2 +- src/modules/chat/dto/message-seen.dto.ts | 2 +- src/modules/chat/socket-connection.service.ts | 1 + src/modules/newsletter/dto/subscribe.dto.ts | 2 +- src/modules/post/dtos/create-post.dto.ts | 2 +- src/modules/post/post.service.ts | 19 +++++- src/modules/tags/dto/create-tag.dto.ts | 4 +- src/modules/twofa/dtos/twofa.dto.ts | 2 +- src/modules/twofa/twofa.service.ts | 2 +- src/socket-io.adapter.ts | 2 +- 58 files changed, 238 insertions(+), 138 deletions(-) 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 f89b65ea..83cd641c 100644 --- a/src/common/@types/classes/common.response.ts +++ b/src/common/@types/classes/common.response.ts @@ -2,14 +2,15 @@ export class AuthenticationResponse { /** * @example fb9eac5f-eb94-489b-8fca-24324558be18 */ - user: { + user!: { idx?: string + id: number }; /** * @example eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXKYjj.eyJ */ - accessToken: string; + accessToken!: string; /** * @example eyJh3d06e6e3e152ae839a6623c3cb6f961a.eyJ diff --git a/src/common/@types/classes/cursor.response.ts b/src/common/@types/classes/cursor.response.ts index 02e8f67a..e7222200 100644 --- a/src/common/@types/classes/cursor.response.ts +++ b/src/common/@types/classes/cursor.response.ts @@ -7,19 +7,19 @@ export class CursorMeta { * @example AdVxY2F0ZWdvcnlfaWQ9MjMx */ @ApiProperty() - nextCursor: string; + nextCursor!: string; /** * @example false */ @ApiProperty() - hasNextPage: boolean; + hasNextPage!: boolean; /** * @example true */ @ApiProperty() - hasPreviousPage: boolean; + hasPreviousPage!: boolean; /** * @example "lorem ipsum" @@ -31,8 +31,8 @@ export class CursorMeta { export class CursorPaginationResponse implements PaginationAbstractResponse { @IsArray() @ApiProperty({ isArray: true }) - readonly data: T[]; + readonly data!: T[]; @ApiProperty({ type: () => CursorMeta }) - readonly meta: CursorMeta; + readonly meta!: CursorMeta; } 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/@types/interfaces/mail.interface.ts b/src/common/@types/interfaces/mail.interface.ts index 89bd0708..31a040c9 100644 --- a/src/common/@types/interfaces/mail.interface.ts +++ b/src/common/@types/interfaces/mail.interface.ts @@ -2,7 +2,7 @@ import type { EmailTemplate } from "../enums"; export interface MailPayload { template: string - replacements?: Record + replacements: Record to: string subject: string from: string diff --git a/src/common/constant/string.constants.ts b/src/common/constant/string.constants.ts index 1d4487ef..6f2ea648 100644 --- a/src/common/constant/string.constants.ts +++ b/src/common/constant/string.constants.ts @@ -15,7 +15,7 @@ const packageJson = readPackageSync(); export const APP_NAME = packageJson.name; export const SWAGGER_API_CURRENT_VERSION = packageJson.version; -export const SWAGGER_DESCRIPTION = packageJson.description; +export const SWAGGER_DESCRIPTION = packageJson.description!; export const SWAGGER_TITLE = `${capitalize(APP_NAME)} API Documentation`; export const SWAGGER_API_ENDPOINT = "doc"; diff --git a/src/common/database/base.repository.ts b/src/common/database/base.repository.ts index 64d4633e..12e43a44 100644 --- a/src/common/database/base.repository.ts +++ b/src/common/database/base.repository.ts @@ -487,7 +487,7 @@ export class BaseRepository extends EntityRepository { countWhere["$and"] = this.getFilters("createdAt", decoded, oppositeOrder); previousCount = await repo.count(countWhere); - // eslint-disable-next-line ts/dot-notation + // eslint-disable-next-line ts/dot-notation\ where["$and"] = this.getFilters("createdAt", decoded, queryOrder); } diff --git a/src/common/database/mikro-orm.encrypted.ts b/src/common/database/mikro-orm.encrypted.ts index 432a21df..c20d137b 100644 --- a/src/common/database/mikro-orm.encrypted.ts +++ b/src/common/database/mikro-orm.encrypted.ts @@ -6,7 +6,7 @@ export class EncryptedType extends Type { private readonly encKey = process.env.ENC_KEY; private readonly encIV = process.env.ENC_IV; - convertToDatabaseValue(value: string | undefined, _platform: Platform): string { + convertToDatabaseValue(value: string , _platform: Platform): string { if (value && !(typeof value.valueOf() === "string")) throw ValidationError.invalidType(EncryptedType, value, "JS"); diff --git a/src/common/database/user.subscriber.ts b/src/common/database/user.subscriber.ts index f5e97e66..70f599b1 100644 --- a/src/common/database/user.subscriber.ts +++ b/src/common/database/user.subscriber.ts @@ -13,12 +13,14 @@ export class UserSubscriber implements EventSubscriber { } async beforeCreate(arguments_: EventArgs): Promise { - if (arguments_.changeSet.payload?.password) + if (arguments_.changeSet?.payload?.password) arguments_.entity.password = await HelperService.hashString(arguments_.entity.password); } + async beforeUpdate(arguments_: EventArgs): Promise { - if (arguments_.changeSet.payload?.password) - arguments_.entity.password = await HelperService.hashString(arguments_.entity.password); - } + if (arguments_.changeSet?.payload?.password) + arguments_.entity.password = await HelperService.hashString(arguments_.entity.password); + } + } diff --git a/src/common/decorators/api-file.decorator.ts b/src/common/decorators/api-file.decorator.ts index db5a9d63..f2da68a8 100644 --- a/src/common/decorators/api-file.decorator.ts +++ b/src/common/decorators/api-file.decorator.ts @@ -27,7 +27,7 @@ interface ApiFilesOptions extends ApiFileOptions { * @returns A function that returns a decorator. */ export function ApiFile(options_?: ApiFileOptions) { - const options: ApiFileOptions = { fieldName: "file", required: false, ...options_ }; + const options = { fieldName: "file", required: false, ...options_ } satisfies ApiFilesOptions return applyDecorators( UseInterceptors(FileInterceptor(options.fieldName, options.localOptions)), @@ -54,12 +54,12 @@ export function ApiFile(options_?: ApiFileOptions) { * @returns A function that returns a decorator. */ export function ApiFiles(options_?: ApiFilesOptions) { - const options: ApiFilesOptions = { + const options = { fieldName: "files", required: false, maxCount: 10, ...options_, - }; + } satisfies ApiFilesOptions; return applyDecorators( UseInterceptors( diff --git a/src/common/decorators/auth.decorator.ts b/src/common/decorators/auth.decorator.ts index 4e959046..146b3b03 100644 --- a/src/common/decorators/auth.decorator.ts +++ b/src/common/decorators/auth.decorator.ts @@ -17,11 +17,11 @@ interface AuthGuard { */ export function Auth(options_?: AuthGuard) { - const options: AuthGuard = { + const options = { guards: [JwtAuthGuard, PoliciesGuard], unauthorizedResponse: API_UNAUTHORISED_RESPONSE, ...options_, - }; + } satisfies AuthGuard; return applyDecorators( UseGuards(...options.guards), 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 ff03307f..817481bd 100644 --- a/src/common/decorators/validation/is-number-field.decorator.ts +++ b/src/common/decorators/validation/is-number-field.decorator.ts @@ -21,7 +21,7 @@ import type { NumberFieldOptions } from "@common/@types"; */ export function IsNumberField(options_?: NumberFieldOptions) { - const options: NumberFieldOptions = { + const options = { min: 1, required: true, each: false, @@ -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/decorators/validation/min-max-length.decorator.ts b/src/common/decorators/validation/min-max-length.decorator.ts index 83fd9e3e..1a467ea7 100644 --- a/src/common/decorators/validation/min-max-length.decorator.ts +++ b/src/common/decorators/validation/min-max-length.decorator.ts @@ -9,7 +9,7 @@ import { validationI18nMessage } from "@lib/i18n"; * @returns A function that takes in a target, propertyKey, and descriptor */ export function MinMaxLength(options_?: MinMaxLengthOptions) { - const options: MinMaxLengthOptions = { minLength: 2, maxLength: 500, each: false, ...options_ }; + const options = { minLength: 2, maxLength: 500, each: false, ...options_ }; return applyDecorators( MinLength(options.minLength, { diff --git a/src/common/dtos/pagination.dto.ts b/src/common/dtos/pagination.dto.ts index 8782dd19..4b495320 100644 --- a/src/common/dtos/pagination.dto.ts +++ b/src/common/dtos/pagination.dto.ts @@ -23,7 +23,7 @@ export abstract class PaginationDto { * The search query */ @IsStringField({ required: false, minLength: 1, maxLength: 100 }) - search: string; + search?: string; /** * The `withDeleted` property is a boolean flag that 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/conversation.entity.ts b/src/entities/conversation.entity.ts index 4e43c2b1..7806f223 100644 --- a/src/entities/conversation.entity.ts +++ b/src/entities/conversation.entity.ts @@ -5,7 +5,7 @@ import { Message, User } from "./index"; @Entity() export class Conversation extends BaseEntity { @Property({ index: true }) - chatName: string; + chatName!: string; @ManyToMany(() => User, user => user.conversations, { index: true }) users = new Collection(this); diff --git a/src/entities/message.entity.ts b/src/entities/message.entity.ts index 844f8018..62dea382 100644 --- a/src/entities/message.entity.ts +++ b/src/entities/message.entity.ts @@ -12,7 +12,7 @@ export class Message extends BaseEntity { eager: false, index: true, }) - sender: Rel>; + sender!: Rel>; @ManyToOne({ eager: false, diff --git a/src/entities/news-letter.entity.ts b/src/entities/news-letter.entity.ts index ceba9b09..32ba6926 100644 --- a/src/entities/news-letter.entity.ts +++ b/src/entities/news-letter.entity.ts @@ -4,7 +4,7 @@ import { BaseEntity } from "@common/database"; @Entity() export class NewsLetter extends BaseEntity { @Property({ index: true, unique: true }) - name: string; + name!: string; @Property({ columnType: "text" }) content!: string; diff --git a/src/entities/otp-log.entity.ts b/src/entities/otp-log.entity.ts index d996d5b8..ae3b2318 100644 --- a/src/entities/otp-log.entity.ts +++ b/src/entities/otp-log.entity.ts @@ -18,7 +18,7 @@ export class OtpLog extends BaseEntity { eager: false, index: true, }) - user: Rel>; + user!: Rel>; @Property() isUsed? = false; 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/entities/refresh-token.entity.ts b/src/entities/refresh-token.entity.ts index aab9845d..212caf63 100644 --- a/src/entities/refresh-token.entity.ts +++ b/src/entities/refresh-token.entity.ts @@ -11,7 +11,7 @@ export class RefreshToken extends BaseEntity { @ManyToOne({ eager: false, }) - user: Rel>; + user!: Rel>; @Property() isRevoked? = false; diff --git a/src/lib/aws/aws.s3.service.ts b/src/lib/aws/aws.s3.service.ts index 571a04be..3d752dd6 100644 --- a/src/lib/aws/aws.s3.service.ts +++ b/src/lib/aws/aws.s3.service.ts @@ -16,8 +16,8 @@ import { Inject, Injectable } from "@nestjs/common"; import { lookup } from "mime-types"; import { omit } from "helper-fns"; -import type { Observable } from "rxjs"; -import { from, map, mergeMap } from "rxjs"; +import type { Observable, } from "rxjs"; +import { forkJoin, from, map, of, switchMap, throwError } from "rxjs"; import type { AwsS3, AwsS3MultiPart, @@ -48,9 +48,16 @@ export class AwsS3Service { * @returns An array of strings, which are the names of the buckets. */ listBucket(): Observable { - return from(this.s3Client.send(new ListBucketsCommand({}))).pipe(map(listBucket => listBucket.Buckets.map((value: Bucket) => value.Name))); + return from(this.s3Client.send(new ListBucketsCommand({}))).pipe( + switchMap(listBucket => { + if (!listBucket?.Buckets) return of([]); + + return of(listBucket.Buckets.map((value: Bucket) => value.Name ?? "")); + }) + ); } + /** * The function `listItemInBucket` retrieves a list of objects in an AWS S3 bucket and maps them to a * custom interface. @@ -66,6 +73,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); @@ -107,7 +117,15 @@ export class AwsS3Service { Bucket: this.bucket, Key: key, }), - )).pipe(map(item => item.Body)); + )).pipe(switchMap(item => { + + if(!item.Body) { + return throwError(() => "Item not found"); + + } + return of(item.Body) + + })); } /** @@ -159,8 +177,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, @@ -232,30 +250,33 @@ export class AwsS3Service { ); return listObjectsObservable.pipe( - map(lists => - lists.Contents.map(value => ({ + map(lists =>{ + if(!lists || !lists?.Contents) return [] + + return lists.Contents.map(value => ({ Key: value.Key, - })), + })); + } ), - mergeMap(listItems => - from( - Promise.all([ + switchMap(listItems => + forkJoin([ + from( this.s3Client.send( new DeleteObjectsCommand({ Bucket: this.bucket, Delete: { Objects: listItems, }, - }), + })), ), + from( this.s3Client.send( new DeleteObjectCommand({ Bucket: this.bucket, Key: directory, }), - ), + )), ]), - ), ), ); } @@ -274,7 +295,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({ @@ -283,8 +304,7 @@ export class AwsS3Service { ACL: acl, }), )).pipe(map(response => ({ - - uploadId: response.UploadId, + uploadId: response?.UploadId ?? "", path, pathWithFilename: key, filename, @@ -303,8 +323,8 @@ export class AwsS3Service { * file. * @returns An object with the properties `key`, `mime`, `path`, and `acl`. */ - private getOptions(options: AwsS3PutItemOptions, filename: string) { - let path = options?.path ?? undefined; + private getOptions( filename: string,options?: AwsS3PutItemOptions) { + let path = options?.path ?? filename; const acl = options?.acl ?? "public-read"; if (path) diff --git a/src/lib/casl/casl-ability.factory.ts b/src/lib/casl/casl-ability.factory.ts index 22f54760..fdfd2ebe 100644 --- a/src/lib/casl/casl-ability.factory.ts +++ b/src/lib/casl/casl-ability.factory.ts @@ -16,7 +16,7 @@ export class CaslAbilityFactory { /* Giving the user the ability to read and write to everything if they are an admin. */ - if (user.roles.includes(Roles.ADMIN)) + if (user.roles!.includes(Roles.ADMIN)) can(Action.Manage, "all"); // read-write access to everything else can(Action.Read, "all"); // read-only access to everything 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/firebase-admin/firebase.service.ts b/src/lib/firebase-admin/firebase.service.ts index b0cdeab9..5f2ac84d 100644 --- a/src/lib/firebase-admin/firebase.service.ts +++ b/src/lib/firebase-admin/firebase.service.ts @@ -11,7 +11,7 @@ interface NestFirebase { @Injectable() export class NestFirebaseService implements NestFirebase { - private _firebaseConnection: admin.app.App; + private _firebaseConnection!: admin.app.App; constructor( @Inject(MODULE_OPTIONS_TOKEN) private _NestFirebaseOptions: FirebaseModuleOptions, 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 cc2f1700..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/main.ts b/src/main.ts index f9faee05..b91522c0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -96,7 +96,7 @@ async function bootstrap() { module.hot.dispose(() => app.close()); } - const port = process.env.PORT ?? configService.get("app.port", { infer: true }); + const port = process.env.PORT ?? configService.get("app.port", { infer: true })!; await app.listen(port); diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 24082d01..3803d10d 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -184,6 +184,6 @@ export class AuthController { ): Observable { return fromAll ? this.authService.logoutFromAll(user) - : this.authService.logout(user, refreshToken.refreshToken); + : this.authService.logout(user, refreshToken!.refreshToken); } } diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 8abf59f0..6aa3d724 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -6,6 +6,7 @@ import { ForbiddenException, Injectable, NotFoundException, + UnauthorizedException, } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { init } from "@paralleldrive/cuid2"; @@ -79,7 +80,7 @@ export class AuthService { } return user && isPasswordLogin - ? HelperService.verifyHash(user.password, pass).pipe( + ? HelperService.verifyHash(user.password, pass!).pipe( map((isValid) => { if (isValid) return omit(user, ["password"]); @@ -198,7 +199,7 @@ export class AuthService { const otp = this.otpRepository.create({ user: userExists, otpCode: otpNumber, - expiresIn: new Date(Date.now() + (protocol.otpExpiryInMinutes * 60_000)), // prettier-ignore + expiresIn: new Date(Date.now() + (protocol?.otpExpiryInMinutes ?? 5 * 60_000)), // prettier-ignore }); return from( @@ -245,6 +246,19 @@ export class AuthService { ), ).pipe( switchMap((details) => { + + if(!details) { + + return throwError( + () => + new NotFoundException( + translate("exception.itemDoesNotExist", { + args: { item: "Otp" }, + }), + ), + ); + } + const user = details.user.getEntity(); this.userRepository.assign(user, { password }); @@ -332,6 +346,19 @@ export class AuthService { }), ).pipe( switchMap((userDetails) => { + + if(!userDetails) { + + return throwError( + () => + new NotFoundException( + translate("exception.itemDoesNotExist", { + args: { item: "Account" }, + }), + ), + ); + } + return HelperService.verifyHash(userDetails.password, oldPassword).pipe( switchMap((isValid) => { if (!isValid) { @@ -354,6 +381,10 @@ export class AuthService { } async findUser(condition: FilterQuery): Promise { - return this.userRepository.findOne(condition); + const user = await this.userRepository.findOne(condition); + + if (!user) + throw new UnauthorizedException(); + return user } } diff --git a/src/modules/auth/strategies/facebook.strategy.ts b/src/modules/auth/strategies/facebook.strategy.ts index eebb3fb7..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") { @@ -43,14 +44,14 @@ export class FacebookStrategy extends PassportStrategy(Strategy, "facebook") { ): Promise { const { name, emails, username, photos } = profile; const user: OauthResponse = { - email: emails[0].value, - firstName: name?.givenName, - lastName: name?.familyName, + email: emails![0]!.value, + firstName: name?.givenName ?? faker.color.human(), + lastName: name?.familyName ?? faker.animal.cetacean(), accessToken, }; // Check if the user already exists in your database const existingUser = await this.userRepo.findOne({ - email: emails[0].value, + email: emails![0]!.value, isDeleted: false, }); @@ -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 e3ee1aee..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") { @@ -41,15 +42,15 @@ export class GoogleStrategy extends PassportStrategy(Strategy, "google") { ): Promise { const { name, emails, photos, username } = profile; const user: OauthResponse = { - email: emails[0].value, - firstName: name?.givenName, - lastName: name?.familyName, + email: emails![0]!.value, + firstName: name?.givenName ?? faker.color.human(), + lastName: name?.familyName ?? faker.animal.cetacean(), accessToken, }; // Check if the user already exists in your database const existingUser = await this.userRepo.findOne({ - email: emails[0].value, + email: emails![0]!.value, isDeleted: false, }); @@ -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/auth/strategies/jwt-2fa.strategy.ts b/src/modules/auth/strategies/jwt-2fa.strategy.ts index 6a77aff2..88fd8976 100644 --- a/src/modules/auth/strategies/jwt-2fa.strategy.ts +++ b/src/modules/auth/strategies/jwt-2fa.strategy.ts @@ -1,8 +1,8 @@ -import { Injectable, UnauthorizedException } from "@nestjs/common"; +import type { JwtPayload } from "@common/@types"; +import { Injectable } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { PassportStrategy } from "@nestjs/passport"; import { ExtractJwt, Strategy } from "passport-jwt"; -import type { JwtPayload } from "@common/@types"; import { AuthService } from "../auth.service"; @Injectable() @@ -31,8 +31,6 @@ export class JwtTwofaStrategy extends PassportStrategy(Strategy, "jwt2fa") { // Accept the JWT and attempt to validate it using the user service const user = await this.authService.findUser({ id }); - if (!user) - throw new UnauthorizedException(); return user; } diff --git a/src/modules/auth/strategies/jwt.strategy.ts b/src/modules/auth/strategies/jwt.strategy.ts index 991ac3d3..b76c92de 100644 --- a/src/modules/auth/strategies/jwt.strategy.ts +++ b/src/modules/auth/strategies/jwt.strategy.ts @@ -1,8 +1,8 @@ -import { Injectable, UnauthorizedException } from "@nestjs/common"; +import type { JwtPayload } from "@common/@types"; +import { Injectable } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { PassportStrategy } from "@nestjs/passport"; import { ExtractJwt, Strategy } from "passport-jwt"; -import type { JwtPayload } from "@common/@types"; import { AuthService } from "../auth.service"; @Injectable() @@ -31,8 +31,6 @@ export class JwtStrategy extends PassportStrategy(Strategy) { // Accept the JWT and attempt to validate it using the user service const user = await this.authService.findUser(id); - if (!user) - throw new UnauthorizedException(); return user; } diff --git a/src/modules/auth/strategies/magic-login.strategy.ts b/src/modules/auth/strategies/magic-login.strategy.ts index 5ffe75d4..ddbc56d5 100644 --- a/src/modules/auth/strategies/magic-login.strategy.ts +++ b/src/modules/auth/strategies/magic-login.strategy.ts @@ -1,12 +1,12 @@ import type { Loaded } from "@mikro-orm/core"; -import { Injectable, Logger, UnauthorizedException } from "@nestjs/common"; +import { Injectable, Logger } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { PassportStrategy } from "@nestjs/passport"; import Strategy from "passport-magic-login"; -import { MailerService } from "@lib/mailer/mailer.service"; -import type { User } from "@entities"; import { EmailSubject, EmailTemplate } from "@common/@types"; +import type { User } from "@entities"; +import { MailerService } from "@lib/mailer/mailer.service"; import { AuthService } from "../auth.service"; interface MagicLoginPayload { @@ -63,7 +63,7 @@ export class MagicLoginStrategy extends PassportStrategy(Strategy, "magicLogin") callback: (callback_: null, user: Promise>) => void, ) => { // Get or create a user with the provided email from the database - callback(undefined, this.validate(payload.destination)); + callback(null, this.validate(payload.destination)); }, }); } @@ -78,9 +78,6 @@ export class MagicLoginStrategy extends PassportStrategy(Strategy, "magicLogin") // Accept the JWT and attempt to validate it using the user service const user = await this.authService.findUser({ email }); - if (!user) - throw new UnauthorizedException(); - return user; } } diff --git a/src/modules/category/dto/create-category.dto.ts b/src/modules/category/dto/create-category.dto.ts index b660979a..32694567 100644 --- a/src/modules/category/dto/create-category.dto.ts +++ b/src/modules/category/dto/create-category.dto.ts @@ -6,12 +6,12 @@ export class CreateCategoryDto { * @example "Lorem ipsum dolor sit" */ @IsStringField() - title: string; + title!: string; /** * Description of tag * @example "Lorem ipsum dolor sit" */ @IsStringField() - description: string; + description!: string; } diff --git a/src/modules/chat/chat.gateway.ts b/src/modules/chat/chat.gateway.ts index 49892ca6..ea349317 100644 --- a/src/modules/chat/chat.gateway.ts +++ b/src/modules/chat/chat.gateway.ts @@ -30,7 +30,7 @@ import { SocketConnectionService } from "./socket-connection.service"; namespace: "chat", }) export class ChatGateway implements OnGatewayConnection, OnGatewayInit, OnGatewayDisconnect { - @WebSocketServer() server: Namespace; + @WebSocketServer() server!: Namespace; private readonly logger = new Logger(ChatGateway.name); constructor( diff --git a/src/modules/chat/dto/message-seen.dto.ts b/src/modules/chat/dto/message-seen.dto.ts index 446e98c0..e8fb892f 100644 --- a/src/modules/chat/dto/message-seen.dto.ts +++ b/src/modules/chat/dto/message-seen.dto.ts @@ -2,5 +2,5 @@ import { IsStringField } from "@common/decorators"; export class MessageSeenDto { @IsStringField() - receiver: string; + receiver!: string; } 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; } diff --git a/src/modules/newsletter/dto/subscribe.dto.ts b/src/modules/newsletter/dto/subscribe.dto.ts index 466ff452..1c8dea76 100644 --- a/src/modules/newsletter/dto/subscribe.dto.ts +++ b/src/modules/newsletter/dto/subscribe.dto.ts @@ -7,5 +7,5 @@ export class SubscribeNewsletterDto { */ @IsEmailField() - email: string; + email!: string; } diff --git a/src/modules/post/dtos/create-post.dto.ts b/src/modules/post/dtos/create-post.dto.ts index 815276fb..48da6f48 100644 --- a/src/modules/post/dtos/create-post.dto.ts +++ b/src/modules/post/dtos/create-post.dto.ts @@ -50,7 +50,7 @@ export class CreatePostDto { */ @IsEnumField(PostStateEnum, { required: false }) - state: PostStateEnum; + state?: PostStateEnum; /** * Published status of post diff --git a/src/modules/post/post.service.ts b/src/modules/post/post.service.ts index f5dbd171..9d257ebc 100644 --- a/src/modules/post/post.service.ts +++ b/src/modules/post/post.service.ts @@ -184,7 +184,7 @@ export class PostService { switchMap(([post, user]) => { if (!user.favorites.contains(post)) { user.favorites.add(post); - post.favoritesCount += 1; + post.favoritesCount = (post.favoritesCount ?? 0) + 1; } return from(this.em.flush()).pipe(map(() => post)); @@ -222,7 +222,7 @@ export class PostService { switchMap(([post, user]) => { if (!user.favorites.contains(post)) { user.favorites.remove(post); - post.favoritesCount -= 1; + post.favoritesCount = (post.favoritesCount ?? 0) - 1; } return from(this.em.flush()).pipe(map(() => post)); @@ -246,7 +246,20 @@ export class PostService { }, }, ), - ).pipe(map(post => post.comments.getItems())); + ).pipe(switchMap(post => { + if(!post) { + return throwError( + () => + new NotFoundException( + translate("exception.itemDoesNotExist", { + args: { item: "Post" }, + }), + ), + ); + } + return of(post.comments.getItems()) + + })); } /** diff --git a/src/modules/tags/dto/create-tag.dto.ts b/src/modules/tags/dto/create-tag.dto.ts index 34b4dc2e..b55c9bee 100644 --- a/src/modules/tags/dto/create-tag.dto.ts +++ b/src/modules/tags/dto/create-tag.dto.ts @@ -6,12 +6,12 @@ export class CreateTagDto { * @example "Lorem ipsum" */ @IsStringField() - title: string; + title!: string; /** * Description of tag * @example "Lorem ipsum" */ @IsStringField() - description: string; + description!: string; } diff --git a/src/modules/twofa/dtos/twofa.dto.ts b/src/modules/twofa/dtos/twofa.dto.ts index 9194de47..d98a1095 100644 --- a/src/modules/twofa/dtos/twofa.dto.ts +++ b/src/modules/twofa/dtos/twofa.dto.ts @@ -6,5 +6,5 @@ export class TwofaDto { * @example 123456 */ @IsStringField({ minLength: 1, required: true }) - code: string; + code!: string; } diff --git a/src/modules/twofa/twofa.service.ts b/src/modules/twofa/twofa.service.ts index 0eafd395..2c69a71e 100644 --- a/src/modules/twofa/twofa.service.ts +++ b/src/modules/twofa/twofa.service.ts @@ -65,7 +65,7 @@ export class TwoFactorService { isTwoFactorCodeValid(twoFactorAuthenticationCode: string, user: User): boolean { return authenticator.verify({ token: twoFactorAuthenticationCode, - secret: user.twoFactorSecret, + secret: user.twoFactorSecret!, }); } diff --git a/src/socket-io.adapter.ts b/src/socket-io.adapter.ts index cab39458..996f9e85 100644 --- a/src/socket-io.adapter.ts +++ b/src/socket-io.adapter.ts @@ -7,7 +7,7 @@ import type { Adapter } from "socket.io-adapter"; import type { Namespace, Server, ServerOptions } from "socket.io"; export class SocketIOAdapter extends IoAdapter { - private adapterConstructor: ((nsp: Namespace) => Adapter); + private adapterConstructor!: ((nsp: Namespace) => Adapter); constructor( app: INestApplicationContext,