diff --git a/.vscode/settings.json b/.vscode/settings.json index 34f1904..2251aed 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,3 @@ { - "search.exclude": { - "**/.yarn": true, - "**/.pnp.*": true - }, - "eslint.nodePath": ".yarn/sdks", - "prettier.prettierPath": ".yarn/sdks/prettier/index.js", - "typescript.tsdk": ".yarn/sdks/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true, "cSpell.words": ["ectx"] } diff --git a/src/app/app.controller.ts b/src/app/app.controller.ts index 1c74bb2..69856d3 100644 --- a/src/app/app.controller.ts +++ b/src/app/app.controller.ts @@ -7,7 +7,7 @@ import { ApiUnauthorizedResponse, } from '@nestjs/swagger'; import { UserDocument } from 'src/auth/schemas'; -import { GetUser } from 'src/common/decorators'; +import { GetUser } from 'src/utilities/decorators'; import { AppService } from './app.service'; @Controller() diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 4c960c4..4b7fcb8 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -18,9 +18,7 @@ export class AuthService { private jwtService: JwtService, ) {} - async signUp( - signupInfo: SignupInfo, - ): Promise<{ ok: boolean; data: { token: string } }> { + async signUp(signupInfo: SignupInfo): Promise<{ token: string }> { const { phone, password, name } = signupInfo; // Check if user with phone number already exists as registered user @@ -37,7 +35,7 @@ export class AuthService { user.signedUpOn = new Date(); await user.save(); const token = this.jwtService.sign({ user_id: user._id }); - return { ok: true, data: { token } }; + return { token }; } // if user not found, create new user @@ -50,10 +48,7 @@ export class AuthService { }); const token = this.jwtService.sign({ user_id: newUser._id }); - return { - ok: true, - data: { token }, - }; + return { token }; } async logIn(loginInfo: LoginInfo) { @@ -72,8 +67,11 @@ export class AuthService { // Send jwt token back to user const token = this.jwtService.sign({ user_id: user._id }); return { - ok: true, - data: { token }, + token, + name: user.name, + phone: user.phone, + image: user.avatar, + id: user._id, }; } } diff --git a/src/auth/dto/signup.dto.ts b/src/auth/dto/signup.dto.ts index 5180b9d..3f96dd3 100644 --- a/src/auth/dto/signup.dto.ts +++ b/src/auth/dto/signup.dto.ts @@ -5,7 +5,7 @@ import { IsString, IsStrongPassword, } from 'class-validator'; -import { Match } from 'src/common/decorators'; +import { Match } from 'src/utilities/decorators'; export class SignupDto { @IsString() diff --git a/src/common/decorators/apiPaginatedResponse.decorator.ts b/src/common/decorators/apiPaginatedResponse.decorator.ts deleted file mode 100644 index e341995..0000000 --- a/src/common/decorators/apiPaginatedResponse.decorator.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Type, applyDecorators } from '@nestjs/common'; -import { ApiOkResponse, getSchemaPath } from '@nestjs/swagger'; - -import { PaginationDto } from '../dto'; - -export const ApiPaginatedResponse = >(model: TModel) => - applyDecorators( - ApiOkResponse({ - schema: { - title: `${model.name}PaginatedResponse`, - allOf: [ - { $ref: getSchemaPath(PaginationDto) }, - { - properties: { - results: { - type: 'array', - items: { $ref: getSchemaPath(model) }, - }, - }, - }, - ], - }, - }), - ); diff --git a/src/common/dto/index.ts b/src/common/dto/index.ts index 5f10edb..e9aa054 100644 --- a/src/common/dto/index.ts +++ b/src/common/dto/index.ts @@ -1 +1 @@ -export * from './pagination.dto'; +export * from './paginationRes.dto'; diff --git a/src/common/dto/paginationQuery.dto.ts b/src/common/dto/paginationQuery.dto.ts new file mode 100644 index 0000000..3843507 --- /dev/null +++ b/src/common/dto/paginationQuery.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { IsInt, Max, Min } from 'class-validator'; + +export class PaginationQueryDto { + @ApiProperty({ + description: 'Number of items per page', + default: 10, + minimum: 1, + maximum: 50, + }) + @IsInt() + @Transform(({ value }) => parseInt(value)) + @Min(1) + @Max(50) + limit = 10; + + @ApiProperty({ + description: 'Current page', + default: 1, + minimum: 1, + }) + @IsInt() + @Transform(({ value }) => parseInt(value)) + @Min(1) + page = 1; +} diff --git a/src/common/dto/pagination.dto.ts b/src/common/dto/paginationRes.dto.ts similarity index 92% rename from src/common/dto/pagination.dto.ts rename to src/common/dto/paginationRes.dto.ts index ebbabbd..5fe73cc 100644 --- a/src/common/dto/pagination.dto.ts +++ b/src/common/dto/paginationRes.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; -export class PaginationDto { +export class PaginationResDto { @ApiProperty({ description: 'Total number of items found in the collection', }) diff --git a/src/transaction-room/dto/transaction-rooms-res.dto.ts b/src/transaction-room/dto/transaction-rooms-res.dto.ts new file mode 100644 index 0000000..ec317a4 --- /dev/null +++ b/src/transaction-room/dto/transaction-rooms-res.dto.ts @@ -0,0 +1,7 @@ +import { Types } from 'mongoose'; + +export class TransactionRoomRes { + name: string; + _id: Types.ObjectId; + avatar?: string; +} diff --git a/src/transaction-room/schemas/transaction-room.schema.ts b/src/transaction-room/schemas/transaction-room.schema.ts index c70d3fc..d94b035 100644 --- a/src/transaction-room/schemas/transaction-room.schema.ts +++ b/src/transaction-room/schemas/transaction-room.schema.ts @@ -12,7 +12,8 @@ class roomDetails { @Prop() avatar?: string; - // TODO: Add more fields + @Prop({ type: Types.ObjectId, ref: 'Transaction' }) + lastTransaction?: Types.ObjectId; } @Schema({ timestamps: true }) diff --git a/src/transaction-room/transaction-room.controller.ts b/src/transaction-room/transaction-room.controller.ts index 34711a1..c85d662 100644 --- a/src/transaction-room/transaction-room.controller.ts +++ b/src/transaction-room/transaction-room.controller.ts @@ -1,4 +1,12 @@ -import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; +import { + Body, + Controller, + Get, + Param, + Post, + Query, + UseGuards, +} from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { ApiBadRequestResponse, @@ -8,11 +16,13 @@ import { ApiUnauthorizedResponse, } from '@nestjs/swagger'; import { Types } from 'mongoose'; +import { PaginationQueryDto } from 'src/common/dto/paginationQuery.dto'; import { User } from '../auth/schemas/user.schema'; -import { GetUser } from '../common/decorators'; -import { ValidateMongoId } from '../pipes'; +import { ApiPaginatedResponse, GetUser } from '../utilities/decorators'; +import { ValidateMongoId } from '../utilities/pipes'; import { createTransactionRoomDto } from './dto'; +import { TransactionRoomRes } from './dto/transaction-rooms-res.dto'; import { TransactionRoomService } from './transaction-room.service'; @Controller('transaction-room') @@ -42,12 +52,19 @@ export class TransactionRoomController { } @Get('all') + @ApiPaginatedResponse(TransactionRoomRes) + @ApiUnauthorizedResponse({ description: 'Unauthorized' }) async getAllTransactionRooms( @GetUser() user: User, + @Query() + paginationQuery: PaginationQueryDto, ) { + console.log('paginationQuery', paginationQuery); const _id = user._id; - return await this.transactionRoomService.getAllTransactionRooms(_id); + return await this.transactionRoomService.getAllTransactionRooms(_id, { + ...paginationQuery, + }); } @Get(':id') diff --git a/src/transaction-room/transaction-room.service.ts b/src/transaction-room/transaction-room.service.ts index f529095..c162159 100644 --- a/src/transaction-room/transaction-room.service.ts +++ b/src/transaction-room/transaction-room.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model, Types } from 'mongoose'; +import { PaginationResDto } from 'src/common/dto'; import { User } from '../auth/schemas/user.schema'; import { TransactionRoom } from './schemas'; @@ -79,13 +80,94 @@ export class TransactionRoomService { return newTransactionRoom; } - async getAllTransactionRooms(id: Types.ObjectId) { - // Find all transaction rooms where the given user is a member in the members array - const transactionRooms = await this.transactionRoomModel.find({ - members: { $in: [id] }, - }); + async getAllTransactionRooms( + id: Types.ObjectId, + { page, limit }: { page: number; limit: number }, + ): Promise> { + // { + // "_id": "6460b71dd930d70d3b6d3e3f", + // "members": [ + // "6460b70dd930d70d3b6d3e39", + // "6460b71dd930d70d3b6d3e3d" + // ], + // "roomDetails": [ + // { + // "userId": "6460b70dd930d70d3b6d3e39", + // "name": "Raj Da" + // }, + // { + // "userId": "6460b71dd930d70d3b6d3e3d", + // "name": "Sayeed" + // } + // ], + // "createdAt": "2023-05-14T10:25:33.059Z", + // "updatedAt": "2023-05-15T01:07:15.049Z", + // "__v": 9 + // }, + + const aggregateOptions = [ + { + $match: { + members: { $in: [id] }, + }, + }, + { + $addFields: { + name: { + $filter: { + input: '$roomDetails', + as: 'roomDetail', + cond: { $eq: ['$$roomDetail.userId', id] }, + }, + }, + }, + }, + { + $addFields: { + image: { + $filter: { + input: '$roomDetails', + as: 'roomDetail', + cond: { $eq: ['$$roomDetail.userId', id] }, + }, + }, + }, + }, + { + $project: { + _id: 1, + name: { $arrayElemAt: ['$name.name', 0] }, + image: { $arrayElemAt: ['$image.image', 0] }, + }, + }, + ]; + + const transactionRooms = await this.transactionRoomModel.aggregate([ + ...aggregateOptions, + { + $limit: limit, + }, + { + $skip: (page - 1) * limit, + }, + // { + // $sort: { + // "lastTransaction.createdAt": -1 + // } + // } + ]); // TODO)): add last transaction details + + const total = await this.transactionRoomModel.countDocuments( + aggregateOptions, + ); // Return the transaction rooms - return transactionRooms; // TODO)): add pagination and aggregate with proper room details + return { + total, + page, + limit, + pages: Math.ceil(total / limit), + results: transactionRooms, + }; } } diff --git a/src/transaction/dto/transaction-query.dto.ts b/src/transaction/dto/transaction-query.dto.ts index ad94a2e..93aacf5 100644 --- a/src/transaction/dto/transaction-query.dto.ts +++ b/src/transaction/dto/transaction-query.dto.ts @@ -3,7 +3,7 @@ import { Transform } from 'class-transformer'; import { IsInt, IsNotEmpty, IsString } from 'class-validator'; import { Types } from 'mongoose'; -import { IsValidMongoId } from 'src/common/decorators'; +import { IsValidMongoId } from 'src/utilities/decorators'; export class TransactionQueryDto { @IsNotEmpty() diff --git a/src/transaction/transaction.controller.ts b/src/transaction/transaction.controller.ts index b3148bf..7ca4da7 100644 --- a/src/transaction/transaction.controller.ts +++ b/src/transaction/transaction.controller.ts @@ -19,8 +19,8 @@ import { import { Types } from 'mongoose'; import { User } from '../auth/schemas/user.schema'; -import { GetUser } from '../common/decorators'; -import { ValidateMongoId } from '../pipes'; +import { GetUser } from '../utilities/decorators'; +import { ValidateMongoId } from '../utilities/pipes'; import { TransactionQueryDto, createTransactionDto, diff --git a/src/common/decorators/GetUser.decorator.ts b/src/utilities/decorators/GetUser.decorator.ts similarity index 100% rename from src/common/decorators/GetUser.decorator.ts rename to src/utilities/decorators/GetUser.decorator.ts diff --git a/src/common/decorators/IsValidMongoId.decorator.ts b/src/utilities/decorators/IsValidMongoId.decorator.ts similarity index 95% rename from src/common/decorators/IsValidMongoId.decorator.ts rename to src/utilities/decorators/IsValidMongoId.decorator.ts index b962c28..7b60207 100644 --- a/src/common/decorators/IsValidMongoId.decorator.ts +++ b/src/utilities/decorators/IsValidMongoId.decorator.ts @@ -1,6 +1,6 @@ import { ValidationOptions, registerDecorator } from 'class-validator'; import { Types } from 'mongoose'; -import { MongoIdException } from 'src/exceptions'; +import { MongoIdException } from 'src/utilities/exceptions'; /** * Validates that a given value is a valid MongoDB ObjectId. diff --git a/src/utilities/decorators/apiPaginatedResponse.decorator.ts b/src/utilities/decorators/apiPaginatedResponse.decorator.ts new file mode 100644 index 0000000..a097c8a --- /dev/null +++ b/src/utilities/decorators/apiPaginatedResponse.decorator.ts @@ -0,0 +1,38 @@ +import { applyDecorators, Type } from '@nestjs/common'; +import { ApiOkResponse, getSchemaPath } from '@nestjs/swagger'; + +import { PaginationResDto } from '../../common/dto'; + +export const ApiPaginatedResponse = >(model: TModel) => + applyDecorators( + ApiOkResponse({ + schema: { + title: `${model.name}PaginatedResponse`, + allOf: [ + { $ref: getSchemaPath(PaginationResDto) }, + { + properties: { + page: { + type: 'number', + default: 1, + }, + limit: { + type: 'number', + default: 10, + }, + total: { + type: 'number', + }, + pages: { + type: 'number', + }, + results: { + type: 'array', + items: { $ref: getSchemaPath(model) }, + }, + }, + }, + ], + }, + }), + ); diff --git a/src/common/decorators/index.ts b/src/utilities/decorators/index.ts similarity index 100% rename from src/common/decorators/index.ts rename to src/utilities/decorators/index.ts diff --git a/src/common/decorators/match.decorator.ts b/src/utilities/decorators/match.decorator.ts similarity index 100% rename from src/common/decorators/match.decorator.ts rename to src/utilities/decorators/match.decorator.ts diff --git a/src/exceptions/index.ts b/src/utilities/exceptions/index.ts similarity index 100% rename from src/exceptions/index.ts rename to src/utilities/exceptions/index.ts diff --git a/src/exceptions/mongo-id.exception.filter.ts b/src/utilities/exceptions/mongo-id.exception.filter.ts similarity index 100% rename from src/exceptions/mongo-id.exception.filter.ts rename to src/utilities/exceptions/mongo-id.exception.filter.ts diff --git a/src/pipes/index.ts b/src/utilities/pipes/index.ts similarity index 100% rename from src/pipes/index.ts rename to src/utilities/pipes/index.ts diff --git a/src/pipes/validate-mongo-id.pipe.ts b/src/utilities/pipes/validate-mongo-id.pipe.ts similarity index 100% rename from src/pipes/validate-mongo-id.pipe.ts rename to src/utilities/pipes/validate-mongo-id.pipe.ts