Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions sql/reports/topcoder/30-day-payments.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
WITH 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.category,
w.external_id AS challenge_id,
w.created_at AS winning_created_at,
p.payment_id,
p.payment_status,
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
WHERE w.type = 'PAYMENT'
AND p.installment_number = 1
AND p.payment_status = 'PAID'
AND COALESCE(p.date_paid, p.created_at) >= (CURRENT_DATE - INTERVAL '3 months')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ correctness]
The use of CURRENT_DATE - INTERVAL '3 months' assumes that the system's time zone is correctly set and that the current date is accurate. Consider using a more explicit date range or verifying the system's time zone settings to avoid potential discrepancies.

)
SELECT
cl."name" AS customer,
cl."codeName" AS client_codename,
COALESCE(c."projectId"::text, ba."projectId") AS project_id,
proj.name AS project_name,
ba.id::text AS billing_account_id,
ba."name" AS billing_account_name,
rp.challenge_id,
c."name" AS challenge_name,
c."createdAt" AS challenge_created_at,
rp.winner_id AS member_id,
mem.handle AS member_handle,
CASE
WHEN rp.category::text ILIKE '%REVIEW%' THEN 'review'
WHEN rp.category::text ILIKE '%COPILOT%' THEN 'copilot'
ELSE 'prize'
END AS payment_type,
COALESCE(rp.gross_amount, rp.total_amount) AS member_payment,
COALESCE(
rp.challenge_fee,
COALESCE(rp.gross_amount, rp.total_amount) * (rp.challenge_markup / 100.0)
) AS fee,
rp.payment_created_at AS payment_created_at,
rp.date_paid AS paid_date
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,
NULLIF(cb."billingAccountId", '')::int
)
LEFT JOIN "billing-accounts"."Client" cl
ON cl."id" = ba."clientId"
LEFT JOIN projects.projects proj
ON proj.id = c."projectId"::bigint
LEFT JOIN members.member mem
ON mem."userId"::text = rp.winner_id
ORDER BY payment_created_at DESC;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 style]
The query does not end with a newline character. While this does not affect SQL execution, it's a good practice to end files with a newline to avoid potential issues with some tools and version control systems.

69 changes: 69 additions & 0 deletions sql/reports/topcoder/challenge-type-submissions.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
WITH typed_challenges AS (
SELECT
c.id,
c.name
FROM challenges."Challenge" AS c
WHERE c."typeId" = '929bc408-9cf2-4b3e-ba71-adfbf693046c'
),
submission_participants AS (
SELECT DISTINCT ON (s."memberId", tc.id)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ correctness]
Using DISTINCT ON without a complete understanding of its behavior can lead to unexpected results. Ensure that the ordering in the ORDER BY clause is correct and aligns with the intended logic, as it determines which row is chosen for each distinct group.

s."memberId"::bigint AS member_id,
COALESCE(NULLIF(TRIM(m.handle), ''), m.handle) AS member_handle,
tc.id AS challenge_id,
s.placement
FROM typed_challenges AS tc
JOIN reviews."submission" AS s
ON s."challengeId" = tc.id
LEFT JOIN members."member" AS m
ON m."userId" = s."memberId"::bigint
ORDER BY
s."memberId",
tc.id,
s.placement NULLS FIRST,
s.id DESC
),
winner_participants AS (
SELECT DISTINCT ON (cw."userId", tc.id)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ correctness]
Similar to the previous comment, using DISTINCT ON requires careful consideration of the ORDER BY clause. Verify that the ordering logic is correct to ensure the desired row is selected for each distinct group.

cw."userId"::bigint AS member_id,
COALESCE(
NULLIF(TRIM(cw.handle), ''),
NULLIF(TRIM(m.handle), ''),
m.handle
) AS member_handle,
tc.id AS challenge_id,
cw.placement
FROM typed_challenges AS tc
JOIN challenges."ChallengeWinner" AS cw
ON cw."challengeId" = tc.id
LEFT JOIN members."member" AS m
ON m."userId" = cw."userId"::bigint
ORDER BY
cw."userId",
tc.id,
cw.placement NULLS FIRST,
cw.id DESC
),
combined_participants AS (
SELECT
COALESCE(wp.member_id, sp.member_id) AS member_id,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The use of COALESCE to merge member_id, member_handle, and challenge_id from winner_participants and submission_participants assumes that one of the two will always be non-null. Ensure there are no cases where both could be null, as this would lead to incorrect data in the combined_participants CTE.

COALESCE(wp.member_handle, sp.member_handle) AS member_handle,
COALESCE(wp.challenge_id, sp.challenge_id) AS challenge_id,
COALESCE(wp.placement, sp.placement) AS placement
FROM submission_participants AS sp
FULL OUTER JOIN winner_participants AS wp
ON wp.member_id = sp.member_id
AND wp.challenge_id = sp.challenge_id
)
SELECT
cp.member_id AS "memberId",
cp.member_handle AS "memberHandle",
COUNT(*)::int AS "uniqueChallengesSubmitted",
COUNT(*) FILTER (WHERE cp.placement = 1)::int AS "placementsOfOne",
COUNT(*) FILTER (WHERE cp.placement BETWEEN 1 AND 5)::int AS "placementsOneThroughFive"
FROM combined_participants AS cp
GROUP BY
cp.member_id,
cp.member_handle
ORDER BY
"uniqueChallengesSubmitted" DESC,
"memberHandle" ASC;
60 changes: 60 additions & 0 deletions sql/reports/topcoder/mm-stats.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
WITH member_base AS (
SELECT
mem."userId",
mem.handle,
mem."firstName",
mem."lastName",
COALESCE(
comp_code.name,
comp_id.name,
NULLIF(TRIM(mem."competitionCountryCode"), '')
) AS competition_country,
usr.create_date AS member_since
FROM members."member" AS mem
LEFT JOIN identity."user" AS usr
ON usr.user_id::bigint = mem."userId"
LEFT JOIN lookups."Country" AS comp_code
ON UPPER(comp_code."countryCode") = UPPER(mem."competitionCountryCode")
LEFT JOIN lookups."Country" AS comp_id
ON UPPER(comp_id.id) = UPPER(mem."competitionCountryCode")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The join condition UPPER(comp_id.id) = UPPER(mem."competitionCountryCode") seems incorrect. Typically, id would be a numeric or unique identifier rather than a country code. Verify if this is the intended logic.

WHERE mem."handleLower" = LOWER($1)
)
SELECT
mb.handle,
mb."firstName" AS first_name,
mb."lastName" AS last_name,
mb.competition_country,
mb.member_since,
marathon_stats.rating AS marathon_match_rating,
marathon_stats.rank AS marathon_match_rank,
max_rating.max_rating AS highest_track_rating,
marathon_stats.challenges AS marathon_matches_registered,
marathon_stats.wins AS marathon_match_wins,
marathon_stats."topFiveFinishes" AS marathon_match_top_five_finishes,
marathon_stats."avgRank" AS average_marathon_match_placement,
CASE
WHEN marathon_stats.challenges IS NULL
OR marathon_stats.challenges = 0 THEN NULL
ELSE marathon_stats.competitions::double precision

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
Ensure that marathon_stats.competitions is never zero when marathon_stats.challenges is non-zero to avoid division by zero errors. Consider adding a safeguard if this is a potential issue.

/ marathon_stats.challenges::double precision
END AS marathon_submission_rate
FROM member_base AS mb
LEFT JOIN LATERAL (
SELECT MAX(mmr.rating) AS max_rating
FROM members."memberMaxRating" AS mmr
WHERE mmr."userId" = mb."userId"
) AS max_rating ON TRUE
LEFT JOIN LATERAL (
SELECT mmar.*
FROM members."memberStats" AS ms
JOIN members."memberDataScienceStats" AS mds
ON mds."memberStatsId" = ms.id
JOIN members."memberMarathonStats" AS mmar
ON mmar."dataScienceStatsId" = mds.id
WHERE ms."userId" = mb."userId"
ORDER BY
CASE WHEN ms."isPrivate" THEN 1 ELSE 0 END,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 readability]
The use of CASE WHEN ms."isPrivate" THEN 1 ELSE 0 END for ordering might not be intuitive. Consider using a boolean column directly for clarity if supported by the database.

ms."updatedAt" DESC NULLS LAST,
ms.id DESC
LIMIT 1
) AS marathon_stats ON TRUE;
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { TopcoderReportsModule } from "./reports/topcoder/topcoder-reports.modul
import { StatisticsModule } from "./statistics/statistics.module";
import { SfdcReportsModule } from "./reports/sfdc/sfdc-reports.module";
import { ChallengesReportsModule } from "./reports/challenges/challenges-reports.module";
import { ReportsModule } from "./reports/reports.module";

@Module({
imports: [
Expand All @@ -19,6 +20,7 @@ import { ChallengesReportsModule } from "./reports/challenges/challenges-reports
StatisticsModule,
SfdcReportsModule,
ChallengesReportsModule,
ReportsModule,
HealthModule,
],
})
Expand Down
45 changes: 40 additions & 5 deletions src/auth/guards/permissions.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ import {
} from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { SCOPES_KEY } from "../decorators/scopes.decorator";
import { UserRoles } from "../../app-constants";

@Injectable()
export class PermissionsGuard implements CanActivate {
private static readonly adminRoles = new Set(
Object.values(UserRoles).map((role) => role.toLowerCase()),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ correctness]
Using toLowerCase() on roles assumes that all role comparisons are case-insensitive. Ensure that this aligns with the application's role management policy, as it might lead to unexpected behavior if roles are case-sensitive.

);

constructor(private reflector: Reflector) {}

canActivate(context: ExecutionContext): boolean {
Expand All @@ -28,15 +33,45 @@ export class PermissionsGuard implements CanActivate {
throw new UnauthorizedException("You are not authenticated.");
}

if (requiredScopes && authUser.scopes?.length) {
const hasScope = requiredScopes.some((scope) =>
authUser.scopes.includes(scope),
);
if (hasScope) return true;
if (authUser.isMachine) {
const scopes: string[] = authUser.scopes ?? [];
if (this.hasRequiredScope(scopes, requiredScopes)) {
return true;
}
} else {
const roles: string[] = authUser.roles ?? [];
if (this.isAdmin(roles)) {
return true;
}

const scopes: string[] = authUser.scopes ?? [];
if (this.hasRequiredScope(scopes, requiredScopes)) {
return true;
}
}

throw new ForbiddenException(
"You do not have the required permissions to access this resource.",
);
}

private hasRequiredScope(
scopes: string[],
requiredScopes: string[],
): boolean {
if (!scopes?.length) {
return false;
}

const normalizedScopes = scopes.map((scope) => scope?.toLowerCase());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ correctness]
The use of toLowerCase() on scopes assumes that scope comparisons are case-insensitive. Verify that this is consistent with how scopes are intended to be used throughout the application, as it might cause issues if scopes are case-sensitive.

return requiredScopes.some((scope) =>
normalizedScopes.includes(scope?.toLowerCase()),
);
}

private isAdmin(roles: string[]): boolean {
return roles.some((role) =>
PermissionsGuard.adminRoles.has(role?.toLowerCase()),
);
}
}
Loading
Loading