Skip to content

Commit 911c0ea

Browse files
authored
Merge pull request #219 from BigBen-7/bigben7-issues
fix: userId length caps, pagination, social links, error boundary
2 parents 234d402 + 3c13fcb commit 911c0ea

40 files changed

Lines changed: 260 additions & 44 deletions

File tree

backend/STATUS.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Backend Module Status
2+
3+
| Module | Status |
4+
|--------|--------|
5+
| achievements | ✅ Live |
6+
| activity | ✅ Live |
7+
| admin | ✅ Live |
8+
| analytics | ✅ Live |
9+
| api-key | ✅ Live |
10+
| audit-log | ✅ Live |
11+
| auth | ✅ Live |
12+
| badge | ✅ Live |
13+
| cache | ✅ Live |
14+
| common | ✅ Live |
15+
| config | ✅ Live |
16+
| content | ✅ Live |
17+
| content-rating | ✅ Live |
18+
| daily-reward | ✅ Live |
19+
| feedback | ✅ Live |
20+
| gameMechanics | ✅ Live |
21+
| geostats | ✅ Live |
22+
| hint | ✅ Live |
23+
| in-app-notifications | ✅ Live |
24+
| maintenance-mode | ✅ Live |
25+
| migration | ✅ Live |
26+
| milestone | ✅ Live |
27+
| multiplayer-queue | ✅ Live |
28+
| nft-claim | ✅ Live |
29+
| nft-marketplace-stub | ✅ Live |
30+
| progress | ✅ Live |
31+
| promo-code | ✅ Live |
32+
| puzzle | ✅ Live |
33+
| puzzle-access-log | ✅ Live |
34+
| puzzle-category | ✅ Live |
35+
| puzzle-comment | ✅ Live |
36+
| puzzle-dependency | ✅ Live |
37+
| puzzle-draft | ✅ Live |
38+
| puzzle-fork | ✅ Live |
39+
| puzzle-review | ✅ Live |
40+
| puzzle-submission | ✅ Live |
41+
| puzzle-test-case | ✅ Live |
42+
| puzzle-translation | ✅ Live |
43+
| puzzle-versioning | ✅ Live |
44+
| quiz | ✅ Live |
45+
| rate-limiter | ✅ Live |
46+
| referral | ✅ Live |
47+
| reports | ✅ Live |
48+
| reward-shop | ✅ Live |
49+
| rewards | ✅ Live |
50+
| session | ✅ Live |
51+
| streak | ✅ Live |
52+
| time-trial | ✅ Live |
53+
| token-verification | ✅ Live |
54+
| user | ✅ Live |
55+
| user-activity-log | ✅ Live |
56+
| user-inventory | ✅ Live |
57+
| user-ranking | ✅ Live |
58+
| user-reaction | ✅ Live |
59+
| user-report-card | ✅ Live |
60+
| user-settings | ✅ Live |
61+
| user-token-history | ✅ Live |
62+
| wallet | ✅ Live |

backend/src/analytics/analytics.controller.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,15 @@ export class AnalyticsController implements OnModuleInit {
5151
}
5252

5353
@Get('puzzles/most-solved')
54-
async getMostSolvedPuzzles(): Promise<
55-
Array<{ puzzleId: string; solveCount: number }>
56-
> {
54+
async getMostSolvedPuzzles(
55+
@Query('limit') limit?: string,
56+
@Query('offset') offset?: string,
57+
): Promise<Array<{ puzzleId: string; solveCount: number }>> {
5758
this.logger.log('Handling request for most solved puzzles.');
58-
return this.analyticsService.getMostSolvedPuzzlesAsync();
59+
return this.analyticsService.getMostSolvedPuzzlesAsync(
60+
limit ? Number(limit) : undefined,
61+
offset ? Number(offset) : undefined,
62+
);
5963
}
6064

6165
@Get('puzzles/:puzzleId/average-solve-time')

backend/src/analytics/analytics.service.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,18 @@ export class AnalyticsService {
6262
*/
6363
async getMostSolvedPuzzlesAsync(
6464
limit?: number,
65+
offset?: number,
6566
): Promise<Array<{ puzzleId: string; solveCount: number }>> {
6667
this.logger.log('Fetching most solved puzzles...');
6768
const sql = limit
6869
? `SELECT puzzle_id, solve_count FROM puzzle_stats_mv
69-
ORDER BY solve_count DESC LIMIT $1`
70-
: `SELECT puzzle_id, solve_count FROM puzzle_stats_mv
71-
ORDER BY solve_count DESC`;
72-
const params = limit ? [limit] : [];
70+
ORDER BY solve_count DESC LIMIT $1 OFFSET $2`
71+
: offset
72+
? `SELECT puzzle_id, solve_count FROM puzzle_stats_mv
73+
ORDER BY solve_count DESC OFFSET $1`
74+
: `SELECT puzzle_id, solve_count FROM puzzle_stats_mv
75+
ORDER BY solve_count DESC`;
76+
const params = limit ? [limit, offset ?? 0] : offset ? [offset] : [];
7377
const { rows } = await this.pool.query(sql, params);
7478
return rows.map((r) => ({
7579
puzzleId: r.puzzle_id as string,

backend/src/api-key/api-key.service.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ export interface ApiKey {
1818
status: ApiKeyStatus;
1919
createdAt: Date;
2020
expiresAt?: Date;
21+
monthlyRequestQuota: number;
22+
rateLimitPerMinute: number;
23+
requestsThisMonth: number;
24+
scopedEndpoints: string[];
2125
}
2226

2327
@Injectable()
@@ -45,6 +49,9 @@ export class ApiKeyService {
4549
ownerLabel: string,
4650
isAdmin: boolean,
4751
expiresAt?: Date,
52+
monthlyRequestQuota = 1000,
53+
rateLimitPerMinute = 100,
54+
scopedEndpoints: string[] = [],
4855
): ApiKey {
4956
if (!isAdmin) {
5057
throw new UnauthorizedException(
@@ -62,12 +69,42 @@ export class ApiKeyService {
6269
status: ApiKeyStatus.ACTIVE,
6370
createdAt: new Date(),
6471
expiresAt,
72+
monthlyRequestQuota,
73+
rateLimitPerMinute,
74+
requestsThisMonth: 0,
75+
scopedEndpoints,
6576
};
6677
this.apiKeys.set(newKey, apiKey);
6778
this.logger.log(`Generated new API key for ${ownerLabel}: ${newKey}`);
6879
return apiKey;
6980
}
7081

82+
checkQuota(key: string): boolean {
83+
const apiKey = this.apiKeys.get(key);
84+
if (!apiKey) return false;
85+
return apiKey.requestsThisMonth < apiKey.monthlyRequestQuota;
86+
}
87+
88+
incrementRequestCount(key: string): void {
89+
const apiKey = this.apiKeys.get(key);
90+
if (apiKey) {
91+
apiKey.requestsThisMonth += 1;
92+
this.apiKeys.set(key, apiKey);
93+
}
94+
}
95+
96+
getQuotaUsage(key: string): { used: number; limit: number; remaining: number } {
97+
const apiKey = this.apiKeys.get(key);
98+
if (!apiKey) {
99+
return { used: 0, limit: 0, remaining: 0 };
100+
}
101+
return {
102+
used: apiKey.requestsThisMonth,
103+
limit: apiKey.monthlyRequestQuota,
104+
remaining: Math.max(0, apiKey.monthlyRequestQuota - apiKey.requestsThisMonth),
105+
};
106+
}
107+
71108
revokeApiKey(key: string, isAdmin: boolean): ApiKey {
72109
if (!isAdmin) {
73110
throw new UnauthorizedException(
Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,40 @@
1-
export class ApiKey {}
1+
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
2+
3+
@Entity('api_keys')
4+
export class ApiKey {
5+
@PrimaryGeneratedColumn('uuid')
6+
id: string;
7+
8+
@Column({ unique: true })
9+
key: string;
10+
11+
@Column()
12+
ownerLabel: string;
13+
14+
@Column({ default: 'active' })
15+
status: string;
16+
17+
@Column({ nullable: true })
18+
expiresAt: Date;
19+
20+
@Column({ default: 1000 })
21+
monthlyRequestQuota: number;
22+
23+
@Column({ default: 0 })
24+
requestsThisMonth: number;
25+
26+
@Column({ default: 100 })
27+
rateLimitPerMinute: number;
28+
29+
@Column({ type: 'text', nullable: true })
30+
scopedEndpoints: string;
31+
32+
@Column({ default: false })
33+
isAdmin: boolean;
34+
35+
@CreateDateColumn()
36+
createdAt: Date;
37+
38+
@UpdateDateColumn()
39+
updatedAt: Date;
40+
}

backend/src/audit-log/entities/audit-log.entity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export class AuditLog {
55
@PrimaryGeneratedColumn('uuid')
66
id: string;
77

8-
@Column()
8+
@Column({ length: 128 })
99
userId: string;
1010

1111
@Column()

backend/src/badge/entities/user-badge.entity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export class UserBadge {
1212
@PrimaryGeneratedColumn()
1313
id: number;
1414

15-
@Column()
15+
@Column({ length: 128 })
1616
userId: number;
1717

1818
@ManyToOne(() => Badge, (badge) => badge.userBadges, { eager: true })

backend/src/content-rating/entities/content-rating.entity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export class ContentRating {
1414
@PrimaryGeneratedColumn('uuid')
1515
id: string;
1616

17-
@Column()
17+
@Column({ length: 128 })
1818
@Index()
1919
userId: string;
2020

backend/src/daily-reward/entities/daily-reward-log.entity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export class DailyRewardLog {
66
id: string;
77

88
@Index()
9-
@Column()
9+
@Column({ length: 128 })
1010
userId: string;
1111

1212
@Column({ default: 1 })

backend/src/feedback/entities/feedback.entity.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export class Feedback {
3434
@Column({
3535
type: "uuid",
3636
nullable: true,
37+
length: 128,
3738
})
3839
userId: string // null if anonymous
3940

0 commit comments

Comments
 (0)