diff --git a/src/core/interceptors/pagination.interceptor.ts b/src/core/interceptors/pagination.interceptor.ts index ef4002d..1c378af 100755 --- a/src/core/interceptors/pagination.interceptor.ts +++ b/src/core/interceptors/pagination.interceptor.ts @@ -21,7 +21,7 @@ export class PaginationInterceptor implements NestInterceptor { return next.handle().pipe( map((data) => { if (data && (data.paginate || data.count)) { - const { count, ...response } = data; + const { count, paginate, ...response } = data; this.logger.log(`paginate response from ${className}.${handlerName}`); const pager = this.formatPage(page, limit, count, request); diff --git a/src/modules/auth/DTO/complete-register.dto.ts b/src/modules/auth/DTO/complete-register.dto.ts index c96a5f1..940318c 100755 --- a/src/modules/auth/DTO/complete-register.dto.ts +++ b/src/modules/auth/DTO/complete-register.dto.ts @@ -7,7 +7,7 @@ export class CompleteRegistrationDto { @IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY }) @IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT }) @Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT }) - @ApiProperty({ description: "phone number", default: "09123456789" }) + @ApiProperty({ description: "phone number", default: "09922320740" }) phone: string; @ApiProperty({ description: "OTP code received via SMS", example: "56893" }) diff --git a/src/modules/auth/DTO/request-otp.dto.ts b/src/modules/auth/DTO/request-otp.dto.ts index e623cbb..d0ad5b3 100755 --- a/src/modules/auth/DTO/request-otp.dto.ts +++ b/src/modules/auth/DTO/request-otp.dto.ts @@ -7,6 +7,6 @@ export class RequestOtpDto { @IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY }) @Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT }) @IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT }) - @ApiProperty({ description: "phone number", default: "09123456789" }) + @ApiProperty({ description: "phone number", default: "09922320740" }) phone: string; } diff --git a/src/modules/auth/DTO/verify-otp.dto.ts b/src/modules/auth/DTO/verify-otp.dto.ts index 599e39f..b059f74 100755 --- a/src/modules/auth/DTO/verify-otp.dto.ts +++ b/src/modules/auth/DTO/verify-otp.dto.ts @@ -7,7 +7,7 @@ export class VerifyOtpDto { @IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY }) @Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT }) @IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT }) - @ApiProperty({ description: "phone number", default: "09123456789" }) + @ApiProperty({ description: "phone number", default: "09922320740" }) phone: string; @ApiProperty({ description: "OTP code received via SMS", example: "56893" }) diff --git a/src/modules/dashboards/dashboard.controller.ts b/src/modules/dashboards/dashboard.controller.ts index 83826cb..4acadf3 100755 --- a/src/modules/dashboards/dashboard.controller.ts +++ b/src/modules/dashboards/dashboard.controller.ts @@ -1,17 +1,26 @@ import { Controller, Get } from "@nestjs/common"; +import { ApiOperation } from "@nestjs/swagger"; import { DashboardService } from "./providers/dashboard.service"; import { AdminRoute } from "../../common/decorators/admin.decorator"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; +import { UserDec } from "../../common/decorators/user.decorator"; @Controller("dashboards") @AuthGuards() -@AdminRoute() export class DashboardController { constructor(private readonly dashboardService: DashboardService) {} + @ApiOperation({ summary: "Get dashboard data for admin" }) + @AdminRoute() @Get("reports") - async reports() { + reports() { return this.dashboardService.reports(); } + + @ApiOperation({ summary: "Get dashboard data for user" }) + @Get("summary") + getSummary(@UserDec("id") userId: string) { + return this.dashboardService.getSummary(userId); + } } diff --git a/src/modules/dashboards/dashboard.module.ts b/src/modules/dashboards/dashboard.module.ts index 91ec6b1..0ee2f3e 100755 --- a/src/modules/dashboards/dashboard.module.ts +++ b/src/modules/dashboards/dashboard.module.ts @@ -4,13 +4,14 @@ import { DashboardController } from "./dashboard.controller"; import { AdsModule } from "../ads/ads.module"; import { DanakServicesModule } from "../danak-services/danak-services.module"; import { InvoicesModule } from "../invoices/invoices.module"; +import { NotificationModule } from "../notifications/notifications.module"; import { TicketsModule } from "../tickets/tickets.module"; import { UsersModule } from "../users/users.module"; import { DashboardService } from "./providers/dashboard.service"; import { SubscriptionsModule } from "../subscriptions/subscriptions.module"; @Module({ - imports: [InvoicesModule, TicketsModule, UsersModule, AdsModule, DanakServicesModule, SubscriptionsModule], + imports: [InvoicesModule, TicketsModule, UsersModule, AdsModule, DanakServicesModule, SubscriptionsModule, NotificationModule], providers: [DashboardService], controllers: [DashboardController], }) diff --git a/src/modules/dashboards/providers/dashboard.service.ts b/src/modules/dashboards/providers/dashboard.service.ts index cd141a2..404b7df 100755 --- a/src/modules/dashboards/providers/dashboard.service.ts +++ b/src/modules/dashboards/providers/dashboard.service.ts @@ -1,8 +1,10 @@ import { Injectable } from "@nestjs/common"; import { AdsService } from "../../ads/providers/ads.service"; +// import { AnnouncementService } from "../../announcements/providers/announcement.service"; import { DanakServicesService } from "../../danak-services/providers/danak-services.service"; import { InvoicesService } from "../../invoices/providers/invoices.service"; +import { NotificationsService } from "../../notifications/providers/notifications.service"; import { SubscriptionsService } from "../../subscriptions/providers/subscriptions.service"; import { TicketsService } from "../../tickets/providers/tickets.service"; import { UsersService } from "../../users/providers/users.service"; @@ -16,10 +18,27 @@ export class DashboardService { private readonly ticketsService: TicketsService, private readonly adsService: AdsService, private readonly subscriptionsService: SubscriptionsService, + private readonly notificationsService: NotificationsService, + // private readonly announcementService: AnnouncementService, ) {} //************************************ */ + async getSummary(userId: string) { + const subscriptionCount = await this.subscriptionsService.countUserSubscriptions(userId); + const ticketCount = await this.ticketsService.countUserTickets(userId); + const invoiceCount = await this.invoicesService.countUserInvoices(userId); + // const announcementCount = await this.announcementService.countUserAnnouncements(userId); + const notificationCount = await this.notificationsService.countUserNotifications(userId); + return { + subscriptionCount, + ticketCount, + invoiceCount, + announcementCount: 0, + notificationCount, + }; + } + async reports() { const danakServicesCount = await this.getDanakServicesCount(); const customersCount = await this.getCustomersCount(); diff --git a/src/modules/invoices/providers/invoices.service.ts b/src/modules/invoices/providers/invoices.service.ts index e023a53..de158a4 100755 --- a/src/modules/invoices/providers/invoices.service.ts +++ b/src/modules/invoices/providers/invoices.service.ts @@ -417,6 +417,16 @@ export class InvoicesService { }); return count; } + //*********************************** */ + + async countUserInvoices(userId: string) { + const invoiceCount = await this.invoiceRepository.count({ + where: { + user: { id: userId }, + }, + }); + return invoiceCount; + } //*********************************** */ diff --git a/src/modules/notifications/providers/notifications.service.ts b/src/modules/notifications/providers/notifications.service.ts index 9fb7848..bc966d0 100755 --- a/src/modules/notifications/providers/notifications.service.ts +++ b/src/modules/notifications/providers/notifications.service.ts @@ -99,8 +99,15 @@ export class NotificationsService { //************************ */ + async countUserNotifications(userId: string) { + const notificationCount = await this.notificationRepository.count({ where: { recipient: { id: userId }, isRead: false } }); + return notificationCount; + } + + //************************ */ + async markAllAsRead(userId: string) { - await this.notificationRepository.update({ recipient: { id: userId } }, { isRead: true }); + await this.notificationRepository.delete({ recipient: { id: userId } }); return { message: CommonMessage.UPDATE_SUCCESS, }; diff --git a/src/modules/subscriptions/providers/subscriptions.service.ts b/src/modules/subscriptions/providers/subscriptions.service.ts index 2f0745f..457b443 100755 --- a/src/modules/subscriptions/providers/subscriptions.service.ts +++ b/src/modules/subscriptions/providers/subscriptions.service.ts @@ -284,6 +284,15 @@ export class SubscriptionsService { await queryRunner.release(); } } + //************************************ */ + + async countUserSubscriptions(userId: string) { + const subscriptionCount = await this.userSubscriptionsRepository.count({ + where: { user: { id: userId }, status: SubscriptionStatus.ACTIVE }, + }); + + return subscriptionCount; + } //************************************ */ diff --git a/src/modules/tickets/providers/tickets.service.ts b/src/modules/tickets/providers/tickets.service.ts index 5cd3c1b..6b307a2 100755 --- a/src/modules/tickets/providers/tickets.service.ts +++ b/src/modules/tickets/providers/tickets.service.ts @@ -318,26 +318,13 @@ export class TicketsService { } //******************************** */ + async countUserTickets(userId: string) { + const ticketCount = await this.ticketsRepository.count({ + where: { + user: { id: userId }, + status: Not(TicketStatus.CLOSED), + }, + }); + return ticketCount; + } } - -// async getTickets(queryDto: SearchTicketQueryDto, userId: string) { -// const { limit, skip } = PaginationUtils(queryDto); -// //TODO:add danak-service to query -// const queryBuilder = this.ticketsRepository -// .createQueryBuilder("ticket") -// .leftJoinAndSelect("ticket.category", "category") -// .leftJoinAndSelect("ticket.user", "user") -// .where("user.id = :userId", { userId }) -// .orderBy("ticket.createdAt", "DESC"); - -// if (queryDto.status) { -// queryBuilder.andWhere("ticket.status = :status", { status: queryDto.status }); -// } - -// const [tickets, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount(); - -// return { -// tickets, -// count, -// }; -// } diff --git a/src/modules/users/DTO/create-user.dto.ts b/src/modules/users/DTO/create-user.dto.ts index 51c50bf..1848584 100755 --- a/src/modules/users/DTO/create-user.dto.ts +++ b/src/modules/users/DTO/create-user.dto.ts @@ -7,7 +7,7 @@ export class CreateCustomerDto { @IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY }) @IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT }) @Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT }) - @ApiProperty({ description: "phone number", default: "09123456789" }) + @ApiProperty({ description: "phone number", default: "09922320740" }) phone: string; @IsNotEmpty({ message: AuthMessage.FIRST_NAME_NOT_EMPTY }) diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index 454c372..0707c7e 100755 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -26,75 +26,52 @@ import { VerifyOtpDto } from "../auth/DTO/verify-otp.dto"; @Controller("users") @ApiTags("users") +@AuthGuards() export class UsersController { constructor(private usersService: UsersService) {} - /************************************************************ */ - - @AuthGuards() + // @ApiOperation({ summary: "Get user profile" }) @Get("me") getMe(@UserDec("id") userId: string) { return this.usersService.getMe(userId); } - /************************************************************ */ - - @AuthGuards() + // @ApiOperation({ summary: "Get user financial info" }) @Get("me/financial-info") getFinancialInfo(@UserDec("id") userId: string) { return this.usersService.getMyFinancialInfo(userId); } - /************************************************************ */ - - @AuthGuards() @ApiOperation({ summary: "Update user profile" }) @Patch("update-profile") updateProfile(@Body() updateProfileDto: UpdateProfileDto, @UserDec("id") userId: string) { return this.usersService.updateProfile(userId, updateProfileDto); } - - /************************************************************ */ - - @AuthGuards() @ApiOperation({ summary: "change user email" }) @Patch("change-email") changeEmail(@Body() changeEmailDto: ChangeEmailDto, @UserDec("id") userId: string) { return this.usersService.changeEmail(userId, changeEmailDto); } - /************************************************************ */ - @ApiOperation({ summary: "Verify email" }) @Get("verify-email") verifyEmail(@Query() queryDto: EmailVerifyQueryDto, @Res({ passthrough: true }) rep: FastifyReply) { return this.usersService.verifyEmail(queryDto, rep); } - - /************************************************************ */ - - @AuthGuards() @ApiOperation({ summary: "change user phone" }) @Patch("change-phone") changePhone(@Body() changeDto: RequestOtpDto, @UserDec("id") userId: string) { return this.usersService.changePhone(userId, changeDto); } - /************************************************************ */ - - @AuthGuards() @ApiOperation({ summary: "Verify phone" }) @HttpCode(HttpStatus.OK) @Patch("verify-phone") verifyPhone(@Body() verifyOtpDto: VerifyOtpDto, @UserDec("id") userId: string) { return this.usersService.verifyPhone(verifyOtpDto, userId); } - - /************************************************************ */ - - @AuthGuards() @ApiOperation({ summary: "Check validity of user field" }) @HttpCode(HttpStatus.OK) @Post("check-validity") @@ -102,9 +79,6 @@ export class UsersController { return this.usersService.checkValidity(checkValidityDto, userId); } - /************************************************************ */ - - @AuthGuards() @ApiOperation({ summary: "Create user group ==> admin route" }) @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @Post("user-group") @@ -112,98 +86,68 @@ export class UsersController { return this.usersService.createUserGroup(createDto); } - /************************************************************ */ - @ApiOperation({ summary: "get all user group ==> admin route" }) @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) - @AuthGuards() @Get("user-group") UserGroups() { return this.usersService.getUserGroups(); } - /************************************************************ */ - @ApiOperation({ summary: "get all users ==> admin route" }) @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) - @AuthGuards() @Get() Users() { return this.usersService.findAllUsers(); } - /************************************************************ */ - @ApiOperation({ summary: "get all customers ==> admin route" }) @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) - @AuthGuards() @Get("customers") customers(@Query() queryDto: SearchCustomersDto) { return this.usersService.findAllCustomers(queryDto); } - /************************************************************ */ - @ApiOperation({ summary: "get all customers ==> admin route" }) @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) - @AuthGuards() @Get("customers/:id") customer(@Param() paramDto: ParamDto) { return this.usersService.findOneCustomer(paramDto.id); } - /************************************************************ */ - @ApiOperation({ summary: "get all admins ==> admin route" }) @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) - @AuthGuards() @Get("admins") getAdmins(@Query() queryDto: SearchAdminQueryDto) { return this.usersService.getAdmins(queryDto); } - /************************************************************ */ - @ApiOperation({ summary: "create admin ==> admin route" }) - @AuthGuards() @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @Post("admins") createAdmin(@Body() createDto: CreateAdminDto) { return this.usersService.createAdmin(createDto); } - /************************************************************ */ - @ApiOperation({ summary: "get all permissions ==> admin route" }) - @AuthGuards() @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @Get("permissions") getPermissions() { return this.usersService.getPermissions(); } - /************************************************************ */ - @ApiOperation({ summary: "get all roles ==> admin route" }) - @AuthGuards() @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @Get("roles") getRoles(@Query() queryDto: SearchRolesQueryDto) { return this.usersService.getRoles(queryDto); } - /************************************************************ */ - @ApiOperation({ summary: "create role ==> admin route" }) - @AuthGuards() @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS) @Post("roles") createRole(@Body() createDto: CreateRoleDto) { return this.usersService.createRole(createDto); } - /************************************************************ */ - - @AuthGuards() @ApiOperation({ summary: "Create real user data" }) @HttpCode(HttpStatus.CREATED) @Post("real-user") @@ -211,9 +155,6 @@ export class UsersController { return this.usersService.createRealUserData(createDto, userId); } - /************************************************************ */ - - @AuthGuards() @ApiOperation({ summary: "Create legal user data" }) @HttpCode(HttpStatus.CREATED) @Post("legal-user") @@ -221,15 +162,10 @@ export class UsersController { return this.usersService.createLegalUserData(createDto, userId); } - /************************************************************ */ - - @AuthGuards() @ApiOperation({ summary: "Create customer" }) @HttpCode(HttpStatus.CREATED) @Post("customer") createCustomer(@Body() createDto: CreateCustomerDto) { return this.usersService.createCustomer(createDto); } - - /************************************************************ */ } diff --git a/src/modules/wallets/providers/wallets.service.ts b/src/modules/wallets/providers/wallets.service.ts index dc37a9c..561b200 100755 --- a/src/modules/wallets/providers/wallets.service.ts +++ b/src/modules/wallets/providers/wallets.service.ts @@ -1,6 +1,5 @@ import { BadRequestException, Injectable } from "@nestjs/common"; // eslint-disable-next-line import/no-named-as-default -import dayjs from "dayjs"; import Decimal from "decimal.js"; import { QueryRunner } from "typeorm"; @@ -116,75 +115,29 @@ export class WalletsService { .take(limit) .getManyAndCount(); - // Current month totals const totals = await this.walletsTransactionRepo .createQueryBuilder("transaction") .leftJoin("transaction.wallet", "wallet") .where("wallet.userId = :userId", { userId }) .select([ `COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.DEPOSIT}' THEN transaction.amount ELSE 0 END), 0) AS "totalDeposits"`, - `COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.WITHDRAWAL}' THEN transaction.amount ELSE 0 END), 0) AS "totalWithdrawals"`, - ]) - .getRawOne(); - - const now = dayjs(); - const firstDayOfMonth = now.startOf("month").toDate(); - const firstDayOfPrevMonth = now.subtract(1, "month").startOf("month").toDate(); - const lastDayOfPrevMonth = now.subtract(1, "month").endOf("month").toDate(); - - const prevMonthTotals = await this.walletsTransactionRepo - .createQueryBuilder("transaction") - .leftJoin("transaction.wallet", "wallet") - .where("wallet.userId = :userId", { userId }) - .andWhere("transaction.createdAt >= :startDate", { startDate: firstDayOfPrevMonth }) - .andWhere("transaction.createdAt <= :endDate", { endDate: lastDayOfPrevMonth }) - .select([ - `COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.DEPOSIT}' THEN transaction.amount ELSE 0 END), 0) AS "totalDeposits"`, - `COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.WITHDRAWAL}' THEN transaction.amount ELSE 0 END), 0) AS "totalWithdrawals"`, + `COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.DEBIT}' THEN transaction.amount ELSE 0 END), 0) AS "totalWithdrawals"`, ]) .getRawOne(); const wallet = await this.walletsRepository.findOneBy({ user: { id: userId } }); if (!wallet) throw new BadRequestException(WalletMessage.WALLET_NOT_FOUND); - const prevMonthWallet = await this.walletsTransactionRepo - .createQueryBuilder("transaction") - .leftJoin("transaction.wallet", "wallet") - .where("wallet.userId = :userId", { userId }) - .andWhere("transaction.createdAt < :date", { date: firstDayOfMonth }) - .select([ - `COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.DEPOSIT}' THEN transaction.amount ELSE 0 END), 0) - - COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.WITHDRAWAL}' THEN transaction.amount ELSE 0 END), 0) AS "prevBalance"`, - ]) - .getRawOne(); - - //TODO:fix this - const currentDeposits = new Decimal(decimal(totals.totalDeposits) || 0); - const prevDeposits = new Decimal(decimal(prevMonthTotals.totalDeposits) || 0); - const depositsPercentChange = prevDeposits.isZero() ? null : currentDeposits.minus(prevDeposits).div(prevDeposits).mul(100); - - const currentWithdrawals = new Decimal(decimal(totals.totalWithdrawals) || 0); - const prevWithdrawals = new Decimal(decimal(prevMonthTotals.totalWithdrawals) || 0); - const withdrawalsPercentChange = prevWithdrawals.isZero() - ? null - : currentWithdrawals.minus(prevWithdrawals).div(prevWithdrawals).mul(100); - - const currentBalance = wallet.balance; - const prevBalance = decimal(prevMonthWallet.prevBalance) || new Decimal(0); - const prevBalanceDecimal = new Decimal(prevBalance); - const balancePercentChange = prevBalanceDecimal.isZero() - ? null - : currentBalance.minus(prevBalanceDecimal).div(prevBalanceDecimal).mul(100).toNumber(); - return { - balance: currentBalance, - balancePercentChange, + balance: wallet.balance, transactions, count, - totalDeposits: currentDeposits.toNumber(), - totalDepositsPercentChange: depositsPercentChange, - totalWithdrawals: currentWithdrawals.toNumber(), - totalWithdrawalsPercentChange: withdrawalsPercentChange, + totalDeposits: decimal(totals.totalDeposits) || 0, + totalWithdrawals: decimal(totals.totalWithdrawals) || 0, + //TODO:fix this + balancePercentChange: 5, + totalDepositsPercentChange: 4, + totalWithdrawalsPercentChange: 10, paginate: true, }; }