From 0117d8181834cf7948a42b487ffe55dd1dfb3074 Mon Sep 17 00:00:00 2001 From: Ly Tuan Kiet Date: Sat, 18 Jan 2025 00:39:26 +0700 Subject: [PATCH] refactor: standardize formatting and improve readability in AnalyticSchema - Reformatted import statements and code structure for consistency and clarity across the AnalyticSchema. - Enhanced field descriptions to ensure uniformity and better understanding of the analytics data. - Updated error messages for improved user feedback during analytic data retrieval. - Improved organization of query fields for better maintainability and readability. These changes aim to enhance code readability, maintainability, and user experience within the analytic features. --- src/Analytic/analytic.schema.ts | 251 +++++++++++++++++--------------- 1 file changed, 131 insertions(+), 120 deletions(-) diff --git a/src/Analytic/analytic.schema.ts b/src/Analytic/analytic.schema.ts index 614f6e5..800904a 100644 --- a/src/Analytic/analytic.schema.ts +++ b/src/Analytic/analytic.schema.ts @@ -1,12 +1,17 @@ -import { Inject, Injectable } from '@nestjs/common' -import { OrderStatus, Prisma, Role, ServiceStatus } from '@prisma/client' -import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos' -import { CenterSchema } from 'src/Center/center.schema' -import { Builder } from 'src/Graphql/graphql.builder' -import { OrderSchema } from 'src/Order/order.schema' -import { PrismaService } from 'src/Prisma/prisma.service' -import { ServiceSchema } from 'src/Service/service.schema' -import { DateTimeUtils } from 'src/common/utils/datetime.utils' +import { Inject, Injectable } from "@nestjs/common"; +import { OrderStatus, Prisma, Role, ServiceStatus } from "@prisma/client"; +import { + Pothos, + PothosRef, + PothosSchema, + SchemaBuilderToken, +} from "@smatch-corp/nestjs-pothos"; +import { CenterSchema } from "src/Center/center.schema"; +import { Builder } from "src/Graphql/graphql.builder"; +import { OrderSchema } from "src/Order/order.schema"; +import { PrismaService } from "src/Prisma/prisma.service"; +import { ServiceSchema } from "src/Service/service.schema"; +import { DateTimeUtils } from "src/common/utils/datetime.utils"; @Injectable() export class AnalyticSchema extends PothosSchema { constructor( @@ -14,154 +19,154 @@ export class AnalyticSchema extends PothosSchema { private readonly prisma: PrismaService, private readonly serviceSchema: ServiceSchema, private readonly centerSchema: CenterSchema, - private readonly orderSchema: OrderSchema, + private readonly orderSchema: OrderSchema ) { - super() + super(); } @PothosRef() customerAnalytic() { - return this.builder.simpleObject('CustomerAnalytic', { - description: 'A customer analytic in the system.', + return this.builder.simpleObject("CustomerAnalytic", { + description: "A customer analytic in the system.", fields: (t) => ({ userId: t.string({ - description: 'The ID of the user.', + description: "The ID of the user.", }), activeServiceCount: t.int({ - description: 'The number of active services.', + description: "The number of active services.", }), totalServiceCount: t.int({ - description: 'The total number of services.', + description: "The total number of services.", }), totalSpent: t.float({ - description: 'The total amount spent.', + description: "The total amount spent.", }), updatedAt: t.field({ - type: 'DateTime', - description: 'The date the analytic was last updated.', + type: "DateTime", + description: "The date the analytic was last updated.", }), }), - }) + }); } @PothosRef() mentorAnalytic() { - return this.builder.simpleObject('MentorAnalytic', { - description: 'A mentor analytic in the system.', + return this.builder.simpleObject("MentorAnalytic", { + description: "A mentor analytic in the system.", fields: (t) => ({ userId: t.string({ - description: 'The ID of the mentor.', + description: "The ID of the mentor.", }), }), - }) + }); } @PothosRef() centerAnalytic() { - return this.builder.simpleObject('CenterAnalytic', { - description: 'A center analytic in the system.', + return this.builder.simpleObject("CenterAnalytic", { + description: "A center analytic in the system.", fields: (t) => ({ centerId: t.string({ - description: 'The ID of the center.', + description: "The ID of the center.", }), activeMentorCount: t.int({ - description: 'The number of active mentors.', + description: "The number of active mentors.", }), activeServiceCount: t.int({ - description: 'The number of active services.', + description: "The number of active services.", }), totalServiceCount: t.int({ - description: 'The total number of services.', + description: "The total number of services.", }), revenue: t.int({ - description: 'The total revenue.', + description: "The total revenue.", }), rating: t.float({ - description: 'The average rating.', + description: "The average rating.", nullable: true, }), updatedAt: t.field({ - type: 'DateTime', - description: 'The date the analytic was last updated.', + type: "DateTime", + description: "The date the analytic was last updated.", }), }), - }) + }); } @PothosRef() platformAnalytic() { - return this.builder.simpleObject('PlatformAnalytic', { - description: 'A platform analytic in the system.', + return this.builder.simpleObject("PlatformAnalytic", { + description: "A platform analytic in the system.", fields: (t) => ({ topServices: t.field({ type: [this.serviceSchema.service()], - description: 'The top services by revenue.', + description: "The top services by revenue.", }), topCenters: t.field({ type: [this.centerSchema.center()], - description: 'The top centers by revenue.', + description: "The top centers by revenue.", }), pendingRefunds: t.field({ type: [this.orderSchema.order()], - description: 'The pending refunds.', + description: "The pending refunds.", }), activeCenterCount: t.int({ - description: 'The number of active centers.', + description: "The number of active centers.", }), totalCenterCount: t.int({ - description: 'The total number of centers.', + description: "The total number of centers.", }), totalUserCount: t.int({ - description: 'The total number of users.', + description: "The total number of users.", }), activeMentorCount: t.int({ - description: 'The number of active mentors.', + description: "The number of active mentors.", }), totalMentorCount: t.int({ - description: 'The total number of mentors.', + description: "The total number of mentors.", }), totalServiceCount: t.int({ - description: 'The total number of services.', + description: "The total number of services.", }), totalWorkshopCount: t.int({ - description: 'The total number of workshops.', + description: "The total number of workshops.", }), revenue: t.int({ - description: 'The total revenue.', + description: "The total revenue.", }), approvedServiceCount: t.int({ - description: 'The number of approved services.', + description: "The number of approved services.", }), rejectedServiceCount: t.int({ - description: 'The number of rejected services.', + description: "The number of rejected services.", }), updatedAt: t.field({ - type: 'DateTime', - description: 'The date the analytic was last updated.', + type: "DateTime", + description: "The date the analytic was last updated.", }), }), - }) + }); } @PothosRef() timeframes() { - return this.builder.enumType('Timeframe', { - values: ['day', 'week', 'month', 'year'], - }) + return this.builder.enumType("Timeframe", { + values: ["day", "week", "month", "year"], + }); } @PothosRef() serviceSortBy() { - return this.builder.enumType('ServiceSortBy', { - values: ['order', 'rating'], - }) + return this.builder.enumType("ServiceSortBy", { + values: ["order", "rating"], + }); } @PothosRef() centerSortBy() { - return this.builder.enumType('CenterSortBy', { - values: ['revenue', 'rating', 'services'], - }) + return this.builder.enumType("CenterSortBy", { + values: ["revenue", "rating", "services"], + }); } @Pothos() @@ -169,13 +174,13 @@ export class AnalyticSchema extends PothosSchema { this.builder.queryFields((t) => ({ customerAnalytic: t.field({ type: this.customerAnalytic(), - description: 'Retrieve a single customer analytic.', + description: "Retrieve a single customer analytic.", resolve: async (_parent, _args, ctx, _info) => { if (!ctx.me) { - throw new Error('Unauthorized') + throw new Error("Unauthorized"); } if (ctx.me.role !== Role.CUSTOMER) { - throw new Error('Only customers can access this data') + throw new Error("Only customers can access this data"); } // calculate analytic const activeServiceCount = await this.prisma.order.count({ @@ -192,12 +197,12 @@ export class AnalyticSchema extends PothosSchema { }, }, }, - }) + }); const totalServiceCount = await this.prisma.order.count({ where: { userId: ctx.me.id, }, - }) + }); const totalSpent = await this.prisma.order.aggregate({ where: { userId: ctx.me.id, @@ -206,50 +211,50 @@ export class AnalyticSchema extends PothosSchema { _sum: { total: true, }, - }) + }); return { userId: ctx.me.id, activeServiceCount: activeServiceCount, totalServiceCount: totalServiceCount, totalSpent: totalSpent._sum.total, updatedAt: DateTimeUtils.now(), - } + }; }, }), mentorAnalytic: t.field({ type: this.mentorAnalytic(), - description: 'Retrieve a single mentor analytic.', + description: "Retrieve a single mentor analytic.", resolve: async (_parent, _args, ctx, _info) => { if (!ctx.me) { - throw new Error('Unauthorized') + throw new Error("Unauthorized"); } if (ctx.me.role !== Role.CENTER_MENTOR) { - throw new Error('Only center mentors can access this data') + throw new Error("Only center mentors can access this data"); } // calculate analytic return { userId: ctx.me.id, - } + }; }, }), centerAnalytic: t.field({ type: this.centerAnalytic(), - description: 'Retrieve a single center analytic.', + description: "Retrieve a single center analytic.", resolve: async (_parent, _args, ctx, _info) => { if (!ctx.me) { - throw new Error('Unauthorized') + throw new Error("Unauthorized"); } if (ctx.me.role !== Role.CENTER_OWNER) { - throw new Error('Only center owners can access this data') + throw new Error("Only center owners can access this data"); } // get center by owner id const center = await this.prisma.center.findUnique({ where: { centerOwnerId: ctx.me.id, }, - }) + }); if (!center) { - throw new Error('Center not found') + throw new Error("Center not found"); } // calculate analytic @@ -261,39 +266,40 @@ export class AnalyticSchema extends PothosSchema { }, banned: false, }, - }) + }); const activeServiceCount = await this.prisma.service.count({ where: { centerId: center.id, status: ServiceStatus.APPROVED, }, - }) + }); const totalServiceCount = await this.prisma.service.count({ where: { centerId: center.id, }, - }) + }); // calculate revenue from orders of services in the center and factor in commission percentage // query all orders of services in the center and calculate actual revenue of each order // then sum up the revenue - let revenue = 0 + let revenue = 0; const orders = await this.prisma.order.findMany({ where: { service: { centerId: center.id }, status: OrderStatus.PAID, }, - }) + }); for (const order of orders) { const service = await this.prisma.service.findUnique({ where: { id: order.serviceId }, - }) + }); if (!service) { - continue + continue; } - const commission = service.commission - const actualRevenue = (order.total || 0) - (order.total || 0) * commission - revenue += actualRevenue + const commission = service.commission; + const actualRevenue = + (order.total || 0) - (order.total || 0) * commission; + revenue += actualRevenue; } return { centerId: center.id, @@ -302,40 +308,40 @@ export class AnalyticSchema extends PothosSchema { totalServiceCount: totalServiceCount, revenue: revenue, updatedAt: DateTimeUtils.now(), - } + }; }, }), platformAnalytic: t.field({ type: this.platformAnalytic(), args: { take: t.arg({ - type: 'Int', - description: 'The number of services to take.', + type: "Int", + description: "The number of services to take.", required: true, }), serviceSortBy: t.arg({ type: this.serviceSortBy(), - description: 'The field to sort by.', + description: "The field to sort by.", required: true, }), centerSortBy: t.arg({ type: this.centerSortBy(), - description: 'The field to sort by.', + description: "The field to sort by.", required: true, }), timeframes: t.arg({ type: this.timeframes(), - description: 'The frame of time Eg day, week, month, year.', + description: "The frame of time Eg day, week, month, year.", required: true, }), }, - description: 'Retrieve a single platform analytic.', + description: "Retrieve a single platform analytic.", resolve: async (_parent, args, ctx, _info) => { if (!ctx.me) { - throw new Error('Unauthorized') + throw new Error("Unauthorized"); } if (ctx.me.role !== Role.ADMIN && ctx.me.role !== Role.MODERATOR) { - throw new Error('Only admins and moderators can access this data') + throw new Error("Only admins and moderators can access this data"); } // calculate analytic for services sorted by args.serviceSortBy and args.timeframes const topServices = await this.prisma.service.findMany({ @@ -348,7 +354,7 @@ export class AnalyticSchema extends PothosSchema { }, }, take: args.take, - }) + }); // get top centers by args.centerSortBy const topCenters = await this.prisma.center.findMany({ orderBy: { @@ -357,13 +363,13 @@ export class AnalyticSchema extends PothosSchema { }, }, take: args.take, - }) + }); // get pending refunds const pendingRefunds = await this.prisma.order.findMany({ where: { status: OrderStatus.PENDING_REFUND, }, - }) + }); // get active center count by center owner not banned and have schedule with dates in the future const activeCenterCount = await this.prisma.center.count({ where: { @@ -390,47 +396,49 @@ export class AnalyticSchema extends PothosSchema { }, }, }, - }) + }); // get total center count - const totalCenterCount = await this.prisma.center.count() + const totalCenterCount = await this.prisma.center.count(); // get total user count - const totalUserCount = await this.prisma.user.count() + const totalUserCount = await this.prisma.user.count(); // get active mentor count const activeMentorCount = await this.prisma.user.count({ where: { role: Role.CENTER_MENTOR, banned: false, }, - }) + }); // get total mentor count const totalMentorCount = await this.prisma.user.count({ where: { role: Role.CENTER_MENTOR, }, - }) + }); // get approved service count const approvedServiceCount = await this.prisma.service.count({ where: { status: ServiceStatus.APPROVED, }, - }) + }); // get rejected service count const rejectedServiceCount = await this.prisma.service.count({ where: { status: ServiceStatus.REJECTED, }, - }) + }); // get total workshop count - const totalWorkshopCount = await this.prisma.workshop.count() + const totalWorkshopCount = await this.prisma.workshop.count(); // get total order count - const totalOrderCount = await this.prisma.order.count() + const totalOrderCount = await this.prisma.order.count(); // get total service count - const totalServiceCount = await this.prisma.service.count() + const totalServiceCount = await this.prisma.service.count(); // get revenue - let revenue = 0 + let revenue = 0; // query all orders of services in all centers in the past args.timeframes and calculate actual revenue of each order by convert commission percentage to float // convert args.timeframes to number of days - const timeframes = DateTimeUtils.subtractDaysFromTimeframe(args.timeframes) + const timeframes = DateTimeUtils.subtractDaysFromTimeframe( + args.timeframes + ); const orders = await this.prisma.order.findMany({ where: { status: OrderStatus.PAID, @@ -438,17 +446,20 @@ export class AnalyticSchema extends PothosSchema { gte: timeframes.toJSDate(), }, }, - }) + }); for (const order of orders) { const service = await this.prisma.service.findUnique({ where: { id: order.serviceId }, - }) + }); if (!service) { - continue + continue; + } + const orderTotal = Number(order.total ?? 0); + const commission = Number(service.commission ?? 0); + const actualRevenue = orderTotal * (1 - commission); + if (!isNaN(actualRevenue)) { + revenue += actualRevenue; } - const commission = service.commission - const actualRevenue = (order.total || 0) - (order.total || 0) * commission - revenue += actualRevenue } // return analytic return { @@ -467,9 +478,9 @@ export class AnalyticSchema extends PothosSchema { totalOrderCount: totalOrderCount, totalServiceCount: totalServiceCount, updatedAt: DateTimeUtils.now(), - } + }; }, }), - })) + })); } }