Feat: metrics show

swadhinbiswas committed 9 months ago
4d98eb4
1 parent 5462706
commit 4d98eb4
2 files changed
+109 -3
Showing 2 changed files+109-3
β–ΌREADME.md
+5-3
45
45
## πŸš€ Features
46
46
47
47
### Git Hosting & Collaboration
48
+
48
49
- **Full Git Support** - SSH and HTTP/HTTPS protocols with authentication
49
50
- **Repository Management** - Create, fork, archive, and transfer repositories
50
51
- **Branch Protection** - Enforce PR requirements, approvals, and code review policies
54
55
- **Organizations** - Team management with role-based access control (RBAC)
55
56
56
57
### CI/CD & Automation
58
+
57
59
- **GitHub Actions Compatible** - Run workflows defined in `.github/workflows/`
58
60
- **Self-hosted Runners** - Docker-based job execution with artifact support
59
61
- **Merge Queue** - Automated PR merge management with conflict detection
61
63
- **Secrets Management** - Encrypted storage for CI/CD credentials
62
64
63
65
### Security Features βœ…
66
+
64
67
- **Rate Limiting** - Prevents brute force attacks (5 login attempts per 15min)
65
68
- **CSRF Protection** - Double-submit cookie pattern for all state-changing operations
66
69
- **Input Validation** - Zod schemas prevent injection attacks
69
72
- **Audit Logging** - Complete activity tracking for compliance
70
73
71
74
### Modern Tech Stack
75
+
72
76
- **Astro + React** - Fast, modern frontend with island architecture
73
77
- **TailwindCSS + shadcn/ui** - Beautiful, accessible UI components
74
78
- **Universal Database Adapter** - PostgreSQL, MySQL, SQLite, MongoDB, Turso, PlanetScale
78
82
79
83
---
80
84
81
-
82
85
---
83
86
84
87
## πŸ— Architecture
100
103
## πŸ“¦ Quick Start
101
104
102
105
### Prerequisites
106
+
103
107
- **Node.js** 18+ or **Bun** 1.0+
104
108
- **Database**: PostgreSQL 14+ (recommended) / MySQL 8+ / SQLite 3.35+
105
109
- **Git** 2.30+
237
241
238
242
Each PR builds on the previous one. When #123 merges, #124 automatically rebases.
239
243
240
-Each PR builds on the previous one. When #123 merges, #124 automatically rebases.
241
-
242
244
#### Creating a Stack
243
245
244
246
**Using CLI:**
245
247
β–Όsrc/pages/api/user/metrics.ts
+104-0
1
+
2
+import type { APIRoute } from "astro";
3
+import { getDatabase, schema } from "@/db";
4
+import { eq, and, sql, desc, gte } from "drizzle-orm";
5
+import { withErrorHandler } from "@/lib/errors";
6
+import { success, unauthorized } from "@/lib/api";
7
+
8
+import { getUserFromRequest } from "@/lib/auth";
9
+
10
+export const GET: APIRoute = withErrorHandler(async ({ request, locals }) => {
11
+ // Try locals first (session auth), then fallback to header token (CLI auth)
12
+ let userId = locals.user?.id;
13
+
14
+ if (!userId) {
15
+ const tokenPayload = await getUserFromRequest(request);
16
+ if (!tokenPayload) {
17
+ return unauthorized();
18
+ }
19
+ userId = tokenPayload.userId;
20
+ }
21
+
22
+ const url = new URL(request.url);
23
+ const weeks = parseInt(url.searchParams.get("weeks") || "4");
24
+
25
+ // Calculate date range
26
+ const now = new Date();
27
+ const startDate = new Date();
28
+ startDate.setDate(now.getDate() - (weeks * 7));
29
+
30
+ const db = getDatabase();
31
+
32
+ // 1. Authored PRs Stats
33
+ const authoredPrs = await db.query.pullRequests.findMany({
34
+ where: and(
35
+ eq(schema.pullRequests.authorId, userId),
36
+ gte(schema.pullRequests.createdAt, startDate)
37
+ )
38
+ });
39
+
40
+ const mergedPrs = authoredPrs.filter(pr => pr.isMerged);
41
+
42
+ // Calculate avg time to merge
43
+ let totalMergeTime = 0;
44
+ mergedPrs.forEach(pr => {
45
+ if (pr.mergedAt && pr.createdAt) {
46
+ totalMergeTime += (pr.mergedAt.getTime() - pr.createdAt.getTime());
47
+ }
48
+ });
49
+
50
+ // Convert to hours
51
+ const avgTimeToMerge = mergedPrs.length > 0
52
+ ? (totalMergeTime / mergedPrs.length) / (1000 * 60 * 60)
53
+ : 0;
54
+
55
+ // 2. Review Stats
56
+ const reviews = await db.query.pullRequestReviews.findMany({
57
+ where: and(
58
+ eq(schema.pullRequestReviews.reviewerId, userId),
59
+ gte(schema.pullRequestReviews.createdAt, startDate)
60
+ )
61
+ });
62
+
63
+ const approvals = reviews.filter(r => r.state === "approved").length;
64
+ const changesRequested = reviews.filter(r => r.state === "changes_requested").length;
65
+
66
+ // 3. Calculate Trends (Weekly)
67
+ const trends = [];
68
+ for (let i = 0; i < weeks; i++) {
69
+ const weekStart = new Date();
70
+ weekStart.setDate(now.getDate() - ((i + 1) * 7));
71
+ const weekEnd = new Date();
72
+ weekEnd.setDate(now.getDate() - (i * 7));
73
+
74
+ const weekLabel = `${weekStart.toLocaleDateString()} - ${weekEnd.toLocaleDateString()}`;
75
+
76
+ const weekAuthored = authoredPrs.filter(
77
+ pr => pr.createdAt >= weekStart && pr.createdAt < weekEnd
78
+ ).length;
79
+
80
+ const weekReviewed = reviews.filter(
81
+ r => r.createdAt >= weekStart && r.createdAt < weekEnd
82
+ ).length;
83
+
84
+ trends.unshift({
85
+ week: weekLabel,
86
+ authored: weekAuthored,
87
+ reviewed: weekReviewed
88
+ });
89
+ }
90
+
91
+ return success({
92
+ authored: {
93
+ total: authoredPrs.length,
94
+ merged: mergedPrs.length,
95
+ avgTimeToMerge // in hours
96
+ },
97
+ reviewed: {
98
+ total: reviews.length,
99
+ approvals,
100
+ changesRequested
101
+ },
102
+ trends
103
+ });
104
+});
0
105