-
Notifications
You must be signed in to change notification settings - Fork 0
Add new Member Payment Accrual report, and tweak Topgear hourly report based on their feedback #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| WITH provided_dates AS ( | ||
| SELECT | ||
| NULLIF($1, '')::timestamptz AS start_date, | ||
| NULLIF($2, '')::timestamptz AS end_date | ||
| ), | ||
| params AS ( | ||
| SELECT | ||
| COALESCE( | ||
| pd.start_date, | ||
| CASE | ||
| WHEN pd.end_date IS NOT NULL THEN pd.end_date - INTERVAL '3 months' | ||
| ELSE CURRENT_DATE - INTERVAL '3 months' | ||
| END | ||
| ) AS start_date, | ||
| COALESCE(pd.end_date, CURRENT_DATE) AS end_date | ||
| FROM provided_dates pd | ||
| ), | ||
| latest_payment AS ( | ||
| SELECT | ||
| p.winnings_id, | ||
| MAX(p.version) AS max_version | ||
| FROM finance.payment p | ||
| GROUP BY p.winnings_id | ||
| ), | ||
| recent_payments AS ( | ||
| SELECT | ||
| w.winning_id, | ||
| w.winner_id, | ||
| w.type, | ||
| w.description, | ||
| w.category, | ||
| w.external_id AS challenge_id, | ||
| w.created_at AS winning_created_at, | ||
| p.payment_id, | ||
| p.payment_status, | ||
| p.payment_method_id, | ||
| p.installment_number, | ||
| p.billing_account, | ||
| p.total_amount, | ||
| p.gross_amount, | ||
| p.challenge_fee, | ||
| p.challenge_markup, | ||
| p.date_paid, | ||
| p.created_at AS payment_created_at | ||
| FROM finance.winnings w | ||
| JOIN finance.payment p | ||
| ON p.winnings_id = w.winning_id | ||
| JOIN latest_payment lp | ||
| ON lp.winnings_id = p.winnings_id | ||
| AND lp.max_version = p.version | ||
| JOIN params pr ON TRUE | ||
| WHERE w.type = 'PAYMENT' | ||
| AND p.created_at >= pr.start_date | ||
| AND p.created_at <= pr.end_date | ||
| ) | ||
| SELECT | ||
| rp.payment_created_at AS payment_created_at, | ||
| rp.payment_id, | ||
| rp.description AS payment_description, | ||
| rp.challenge_id, | ||
| rp.payment_status, | ||
| rp.type AS payment_type, | ||
| mem.handle AS payee_handle, | ||
| pm.name AS payment_method, | ||
| ba."name" AS billing_account_name, | ||
| cl."name" AS customer_name, | ||
| ba."subcontractingEndCustomer" AS reporting_account_name, | ||
| rp.winner_id AS member_id, | ||
| to_char(c."createdAt", 'YYYY-MM-DD') AS challenge_created_date, | ||
| rp.gross_amount AS user_payment_gross_amount | ||
| FROM recent_payments rp | ||
| LEFT JOIN challenges."Challenge" c | ||
| ON c."id" = rp.challenge_id | ||
| LEFT JOIN challenges."ChallengeBilling" cb | ||
| ON cb."challengeId" = c."id" | ||
| LEFT JOIN "billing-accounts"."BillingAccount" ba | ||
| ON ba."id" = COALESCE( | ||
| NULLIF(rp.billing_account, '')::int, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| NULLIF(cb."billingAccountId", '')::int | ||
| ) | ||
| LEFT JOIN "billing-accounts"."Client" cl | ||
| ON cl."id" = ba."clientId" | ||
| LEFT JOIN finance.payment_method pm | ||
| ON pm.payment_method_id = rp.payment_method_id | ||
| LEFT JOIN members.member mem | ||
| ON mem."userId"::text = rp.winner_id | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| ORDER BY payment_created_at DESC; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,7 @@ WITH base_challenges AS ( | |
| WHERE cb."challengeId" = c.id | ||
| AND cb."billingAccountId" = '80000062' | ||
| ) ba ON TRUE | ||
| WHERE c."createdAt" >= now() - interval '4 months' | ||
| WHERE c."updatedAt" >= now() - interval '100 days' | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [❗❗ |
||
| AND ba.billing_account_id IS NOT NULL | ||
| ), | ||
| project_details AS ( | ||
|
|
@@ -288,5 +288,6 @@ LEFT JOIN LATERAL ( | |
| AND bc."createdAt" > '2025-01-01T00:00:00Z' | ||
| ) cp ON TRUE | ||
| WHERE bc.billing_account_id = '80000062' | ||
| AND bc."createdAt" >= now() - interval '4 months' | ||
| ORDER BY bc."createdAt" DESC; | ||
| AND (pd.latest_actual_end_date >= now() - interval '100 days' | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| OR bc.status='ACTIVE') | ||
| ORDER BY bc."updatedAt" DESC; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,32 @@ const { jwtAuthenticator: authenticator } = middleware; | |
|
|
||
| const logger = new Logger("AuthMiddleware"); | ||
|
|
||
| function resolveAuthorizationHeader(headers: Record<string, unknown>): string { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| const headerCandidates = [ | ||
| headers["authorization"], | ||
| headers["x-authorization"], | ||
| headers["x-forwarded-authorization"], | ||
| headers["x-original-authorization"], | ||
| ]; | ||
|
|
||
| for (const value of headerCandidates) { | ||
| if (!value) { | ||
| continue; | ||
| } | ||
|
|
||
| if (Array.isArray(value)) { | ||
| const first = value.find(Boolean); | ||
| if (typeof first === "string") { | ||
| return first; | ||
| } | ||
| } else if (typeof value === "string") { | ||
| return value; | ||
| } | ||
| } | ||
|
|
||
| return ""; | ||
| } | ||
|
|
||
| function decodeTokenPayload(token: string): Record<string, unknown> | null { | ||
| try { | ||
| const parts = token.split("."); | ||
|
|
@@ -55,7 +81,10 @@ export class AuthMiddleware implements NestMiddleware { | |
| } | ||
|
|
||
| use(req: any, res: Response, next: NextFunction) { | ||
| if (req.headers.authorization) { | ||
| const authorizationHeader = resolveAuthorizationHeader(req.headers ?? {}); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
|
|
||
| if (authorizationHeader) { | ||
| req.headers.authorization = authorizationHeader; | ||
| this.jwtAuthenticator(req, res, (err) => { | ||
| if (err) { | ||
| const token = req.headers.authorization?.replace(/^Bearer\s+/i, ""); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -449,9 +449,10 @@ export const REPORTS_DIRECTORY: ReportsDirectory = { | |
| "Weekly distinct registrants and submitters for the last five weeks", | ||
| ), | ||
| report( | ||
| "30 Day Payments", | ||
| "/topcoder/30-day-payments", | ||
| "Member payments for the last 30 days", | ||
| "Member Payment Accrual", | ||
| "/topcoder/member-payment-accrual", | ||
| "Member payment accruals for the provided date range (defaults to last 3 months)", | ||
| [paymentsStartDateParam, paymentsEndDateParam], | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| ), | ||
| report( | ||
| "90 Day Member Spend", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,4 +18,16 @@ export class ReportsController { | |
| getReports(): ReportsDirectory { | ||
| return REPORTS_DIRECTORY; | ||
| } | ||
|
|
||
| @Get("/directory") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| @UseGuards(PermissionsGuard) | ||
| @Scopes(AppScopes.AllReports) | ||
| @ApiBearerAuth() | ||
| @ApiOperation({ | ||
| summary: | ||
| "List available report endpoints grouped by sub-path (alias for /v6/reports)", | ||
| }) | ||
| getReportsDirectory(): ReportsDirectory { | ||
| return REPORTS_DIRECTORY; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { ApiPropertyOptional } from "@nestjs/swagger"; | ||
| import { IsDateString, IsOptional } from "class-validator"; | ||
|
|
||
| export class MemberPaymentAccrualQueryDto { | ||
| @ApiPropertyOptional({ | ||
| description: | ||
| "Start date (inclusive) for filtering payment creation date in ISO 8601 format", | ||
| example: "2024-01-01T00:00:00.000Z", | ||
| }) | ||
| @IsOptional() | ||
| @IsDateString() | ||
| startDate?: string; | ||
|
|
||
| @ApiPropertyOptional({ | ||
| description: | ||
| "End date (inclusive) for filtering payment creation date in ISO 8601 format", | ||
| example: "2024-01-31T23:59:59.000Z", | ||
| }) | ||
| @IsOptional() | ||
| @IsDateString() | ||
| endDate?: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ import { | |
| import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger"; | ||
| import { TopcoderReportsService } from "./topcoder-reports.service"; | ||
| import { RegistrantCountriesQueryDto } from "./dto/registrant-countries.dto"; | ||
| import { MemberPaymentAccrualQueryDto } from "./dto/member-payment-accrual.dto"; | ||
| import { TopcoderReportsGuard } from "../../auth/guards/topcoder-reports.guard"; | ||
| import { CsvResponseInterceptor } from "../../common/interceptors/csv-response.interceptor"; | ||
|
|
||
|
|
@@ -67,12 +68,14 @@ export class TopcoderReportsController { | |
| return this.reports.getWeeklyMemberParticipation(); | ||
| } | ||
|
|
||
| @Get("/30-day-payments") | ||
| @Get("/member-payment-accrual") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [❗❗ |
||
| @ApiOperation({ | ||
| summary: "Member payments for the last 30 days", | ||
| summary: | ||
| "Member payment accruals for the provided date range (defaults to last 3 months)", | ||
| }) | ||
| get30DayPayments() { | ||
| return this.reports.get30DayPayments(); | ||
| getMemberPaymentAccrual(@Query() query: MemberPaymentAccrualQueryDto) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| const { startDate, endDate } = query; | ||
| return this.reports.getMemberPaymentAccrual(startDate, endDate); | ||
| } | ||
|
|
||
| @Get("/90-day-member-spend") | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[❗❗
correctness]Casting user input directly to
timestamptzwithout validation can lead to runtime errors if the input is not a valid timestamp. Consider adding input validation or error handling to ensure robustness.