KrishnaCosmic commited on
Commit
f0cbaa4
·
1 Parent(s): 21a3ed1

apply new changes

Browse files
src/app/api/contributor/dashboard-summary/route.ts CHANGED
@@ -8,102 +8,67 @@
8
  import { NextRequest, NextResponse } from "next/server";
9
  import { getCurrentUser } from "@/lib/auth";
10
  import { db } from "@/db";
11
- import { issues, userRepositories, users } from "@/db/schema";
12
  import { eq } from "drizzle-orm";
13
- import { fetchGitHubContributions } from "@/lib/github-contributions";
14
 
15
  export async function GET(request: NextRequest) {
16
  try {
17
  const user = await getCurrentUser(request);
18
  if (!user) {
 
19
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
20
  }
21
 
22
  console.log("[Dashboard Summary] User:", user.username);
23
 
24
- // Fetch GitHub contributions in parallel with local data
25
- console.log("[Dashboard Summary] Starting parallel queries...");
26
- const [allItems, trackedRepos, userRecord, githubData] = await Promise.all([
27
- // Get all issues/PRs by this contributor from the database
28
- (async () => {
29
- console.log("[Dashboard Summary] Fetching issues...");
30
- const items = await db.select().from(issues).where(eq(issues.authorName, user.username));
31
- console.log("[Dashboard Summary] Found %d issues", items.length);
32
- return items;
33
- })(),
34
- // Get tracked repos
35
- (async () => {
36
- console.log("[Dashboard Summary] Fetching tracked repos...");
37
- const repos = await db.select({ repoFullName: userRepositories.repoFullName })
38
- .from(userRepositories)
39
- .where(eq(userRepositories.userId, user.id));
40
- console.log("[Dashboard Summary] Found %d tracked repos", repos.length);
41
- return repos;
42
- })(),
43
- // Get user's GitHub token
44
- (async () => {
45
- console.log("[Dashboard Summary] Fetching user token...");
46
- const userData = await db.select({ githubAccessToken: users.githubAccessToken })
47
- .from(users)
48
- .where(eq(users.id, user.id))
49
- .limit(1);
50
- console.log("[Dashboard Summary] User token fetch complete");
51
- return userData;
52
- })(),
53
- // Fetch GitHub contributions (will use cache or make API call)
54
- (async () => {
55
- console.log("[Dashboard Summary] Fetching GitHub contributions...");
56
- const ghData = await fetchGitHubContributions(user.username, null).catch(err => {
57
- console.log("[Dashboard Summary] GitHub fetch failed:", err);
58
- return null;
59
- });
60
- return ghData;
61
- })()
62
- ]);
63
 
64
- // Calculate metrics from database records
65
- const prs = allItems.filter(item => item.isPR);
66
- const issueItems = allItems.filter(item => !item.isPR);
67
 
68
- // PR metrics
69
- const openPRs = prs.filter(pr => pr.state === 'open').length;
70
- const mergedPRs = prs.filter(pr => pr.state === 'closed').length;
71
 
72
- // Issue metrics
73
- const openIssues = issueItems.filter(i => i.state === 'open').length;
74
- const closedIssues = issueItems.filter(i => i.state === 'closed').length;
75
 
76
- // Get unique repositories contributed to
77
- const uniqueRepos = new Set(allItems.map(item => item.repoName).filter(Boolean));
78
- for (const tracked of trackedRepos) {
79
- uniqueRepos.add(tracked.repoFullName);
80
- }
81
-
82
- // Use GitHub API totalContributions if available, otherwise fallback to local count
83
- const totalContributions = githubData?.totalContributions ?? allItems.length;
84
- const dataSource = githubData ? 'github' : 'local';
85
 
86
- return NextResponse.json({
87
- totalContributions,
88
- totalPRs: prs.length,
89
- openPRs,
90
- mergedPRs,
91
- totalIssues: issueItems.length,
92
- openIssues,
93
- closedIssues,
94
- repositoriesContributed: uniqueRepos.size,
95
- repositories: Array.from(uniqueRepos),
96
- source: dataSource, // Indicates where the totalContributions came from
97
- });
 
 
 
 
98
 
99
  } catch (error: any) {
100
- console.error("Contributor dashboard-summary error:", error);
101
- console.error("Error stack:", error?.stack);
102
- console.error("Error message:", error?.message);
103
 
104
  return NextResponse.json({
105
- error: "Failed to fetch dashboard data. Please try again.",
106
- details: error?.message || "Unknown error"
107
  }, { status: 500 });
108
  }
109
  }
 
8
  import { NextRequest, NextResponse } from "next/server";
9
  import { getCurrentUser } from "@/lib/auth";
10
  import { db } from "@/db";
11
+ import { issues } from "@/db/schema";
12
  import { eq } from "drizzle-orm";
 
13
 
14
  export async function GET(request: NextRequest) {
15
  try {
16
  const user = await getCurrentUser(request);
17
  if (!user) {
18
+ console.log("[Dashboard Summary] No authenticated user");
19
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
20
  }
21
 
22
  console.log("[Dashboard Summary] User:", user.username);
23
 
24
+ // Fetch all contributor data from the database
25
+ try {
26
+ console.log("[Dashboard Summary] Fetching issues for user:", user.username);
27
+ const allItems = await db.select().from(issues).where(eq(issues.authorName, user.username));
28
+ console.log("[Dashboard Summary] Found %d items", allItems.length);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
+ // Calculate metrics from database records
31
+ const prs = allItems.filter(item => item.isPR);
32
+ const issueItems = allItems.filter(item => !item.isPR);
33
 
34
+ // PR metrics
35
+ const openPRs = prs.filter(pr => pr.state === 'open').length;
36
+ const mergedPRs = prs.filter(pr => pr.state === 'closed').length;
37
 
38
+ // Issue metrics
39
+ const openIssues = issueItems.filter(i => i.state === 'open').length;
40
+ const closedIssues = issueItems.filter(i => i.state === 'closed').length;
41
 
42
+ // Get unique repositories contributed to
43
+ const uniqueRepos = new Set(allItems.map(item => item.repoName).filter(Boolean));
44
+
45
+ console.log("[Dashboard Summary] Calculated: %d PRs, %d Issues, %d repos", prs.length, issueItems.length, uniqueRepos.size);
 
 
 
 
 
46
 
47
+ return NextResponse.json({
48
+ totalContributions: allItems.length,
49
+ totalPRs: prs.length,
50
+ openPRs,
51
+ mergedPRs: prs.length - openPRs,
52
+ totalIssues: issueItems.length,
53
+ openIssues,
54
+ closedIssues: issueItems.length - openIssues,
55
+ repositoriesContributed: uniqueRepos.size,
56
+ repositories: Array.from(uniqueRepos),
57
+ });
58
+ } catch (dbError: any) {
59
+ console.error("[Dashboard Summary] Database error:", dbError);
60
+ console.error("[Dashboard Summary] Error message:", dbError?.message);
61
+ throw dbError;
62
+ }
63
 
64
  } catch (error: any) {
65
+ console.error("[Dashboard Summary] Error:", error);
66
+ console.error("[Dashboard Summary] Error message:", error?.message);
67
+ console.error("[Dashboard Summary] Error stack:", error?.stack);
68
 
69
  return NextResponse.json({
70
+ error: "Failed to fetch dashboard data",
71
+ details: error?.message || "Internal server error"
72
  }, { status: 500 });
73
  }
74
  }
src/db/index.ts CHANGED
@@ -32,25 +32,39 @@ let _db: LibSQLDatabase<typeof schema> | null = null;
32
 
33
  function getClient(): Client {
34
  if (!_client) {
 
 
 
 
 
 
 
 
35
  // Determine replica path (works on Hugging Face Spaces and local)
36
  const replicaPath = process.env.REPLICA_DB_PATH || path.join(process.cwd(), "replica.db");
37
 
38
  console.log(`[DB] Setting up Turso Embedded Replicas:`);
39
  console.log(` • Local replica: ${replicaPath}`);
40
- console.log(` • Remote sync: ${process.env.TURSO_DATABASE_URL?.slice(0, 30)}...`);
41
  console.log(` • Sync interval: ${process.env.SYNC_INTERVAL || "60"}s`);
42
 
43
- _client = createClient({
44
- // Local embedded replica for fast reads
45
- url: `file:${replicaPath}`,
46
- // Remote Turso database for syncing
47
- syncUrl: process.env.TURSO_DATABASE_URL!,
48
- authToken: process.env.TURSO_AUTH_TOKEN,
49
- // Sync interval: 60 seconds (adjust as needed)
50
- syncInterval: parseInt(process.env.SYNC_INTERVAL || "60"),
51
- // Optional: Encryption key for replica (if you want encrypted local storage)
52
- encryptionKey: process.env.REPLICA_ENCRYPTION_KEY,
53
- });
 
 
 
 
 
 
54
  }
55
  return _client;
56
  }
@@ -99,9 +113,16 @@ export async function getReplicaStatus(): Promise<{
99
 
100
  export const db = new Proxy({} as LibSQLDatabase<typeof schema>, {
101
  get(_target, prop, receiver) {
102
- if (!_db) {
103
- _db = drizzle(getClient(), { schema });
 
 
 
 
 
 
 
 
104
  }
105
- return Reflect.get(_db, prop, receiver);
106
  },
107
  });
 
32
 
33
  function getClient(): Client {
34
  if (!_client) {
35
+ // Check environment variables
36
+ if (!process.env.TURSO_DATABASE_URL) {
37
+ console.error("[DB] TURSO_DATABASE_URL not set. Using file-only mode.");
38
+ }
39
+ if (!process.env.TURSO_AUTH_TOKEN) {
40
+ console.warn("[DB] TURSO_AUTH_TOKEN not set. Remote sync will not work.");
41
+ }
42
+
43
  // Determine replica path (works on Hugging Face Spaces and local)
44
  const replicaPath = process.env.REPLICA_DB_PATH || path.join(process.cwd(), "replica.db");
45
 
46
  console.log(`[DB] Setting up Turso Embedded Replicas:`);
47
  console.log(` • Local replica: ${replicaPath}`);
48
+ console.log(` • Remote sync URL: ${process.env.TURSO_DATABASE_URL?.slice(0, 30)}...`);
49
  console.log(` • Sync interval: ${process.env.SYNC_INTERVAL || "60"}s`);
50
 
51
+ try {
52
+ _client = createClient({
53
+ // Local embedded replica for fast reads
54
+ url: `file:${replicaPath}`,
55
+ // Remote Turso database for syncing (optional)
56
+ syncUrl: process.env.TURSO_DATABASE_URL,
57
+ authToken: process.env.TURSO_AUTH_TOKEN,
58
+ // Sync interval: 60 seconds (adjust as needed)
59
+ syncInterval: parseInt(process.env.SYNC_INTERVAL || "60"),
60
+ // Optional: Encryption key for replica (if you want encrypted local storage)
61
+ encryptionKey: process.env.REPLICA_ENCRYPTION_KEY,
62
+ });
63
+ console.log("[DB] ✅ Client initialized successfully");
64
+ } catch (error: any) {
65
+ console.error("[DB] ❌ Failed to initialize client:", error?.message);
66
+ throw error;
67
+ }
68
  }
69
  return _client;
70
  }
 
113
 
114
  export const db = new Proxy({} as LibSQLDatabase<typeof schema>, {
115
  get(_target, prop, receiver) {
116
+ try {
117
+ if (!_db) {
118
+ const client = getClient();
119
+ _db = drizzle(client, { schema });
120
+ console.log("[DB] ✅ Drizzle instance initialized");
121
+ }
122
+ return Reflect.get(_db, prop, receiver);
123
+ } catch (error: any) {
124
+ console.error("[DB Proxy] Error:", error?.message);
125
+ throw error;
126
  }
 
127
  },
128
  });
src/lib/db/queries/issues.ts CHANGED
@@ -204,78 +204,102 @@ export async function getIssuesWithTriage(filters: IssueFilters, page = 1, limit
204
 
205
  console.log("[getIssuesWithTriage] Starting query with filters:", filters, "page:", page, "limit:", limit);
206
 
207
- // Build conditions (identical to getIssues)
208
- const conditions = [];
209
- if (filters.repoId) conditions.push(eq(issues.repoId, filters.repoId));
210
- if (filters.repoName) conditions.push(eq(issues.repoName, filters.repoName));
211
- if (filters.authorName) conditions.push(eq(issues.authorName, filters.authorName));
212
- if (filters.state) conditions.push(eq(issues.state, filters.state));
213
- if (filters.isPR !== undefined) conditions.push(eq(issues.isPR, filters.isPR));
214
- if (filters.search) {
215
- conditions.push(or(
216
- like(issues.title, `%${filters.search}%`),
217
- like(issues.bodySummary, `%${filters.search}%`)
218
- ));
219
- }
220
-
221
- if (filters.userId) {
222
- const userRepos = await db.select({ id: repositories.id })
223
- .from(repositories)
224
- .where(and(
225
- eq(repositories.userId, filters.userId),
226
- eq(repositories.addedByUser, true)
227
  ));
228
- const repoIds = userRepos.map(r => r.id);
229
-
230
- if (repoIds.length > 0) {
231
- conditions.push(sql`${issues.repoId} IN (${sql.join(repoIds.map(id => sql`'${id}'`), sql`, `)})`);
232
- } else {
233
- return { issues: [], total: 0, page: safePage, limit, totalPages: 0 };
234
  }
235
- }
236
-
237
- const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
238
-
239
- console.log("[getIssuesWithTriage] Built where clause, fetching count...");
240
-
241
- // Get total count
242
- const countResult = await db.select({ count: count() })
243
- .from(issues)
244
- .where(whereClause);
245
- const total = countResult[0]?.count || 0;
246
- const totalPages = Math.ceil(total / limit);
247
-
248
- console.log("[getIssuesWithTriage] Total count:", total, "totalPages:", totalPages);
249
-
250
- // ✅ Single JOIN query: issues LEFT JOIN triageData
251
- // Database executes this in one round-trip, no N+1
252
- console.log("[getIssuesWithTriage] Executing JOIN query...");
253
- const results = await db.select({
254
- issue: issues,
255
- triage: triageData,
256
- })
257
- .from(issues)
258
- .leftJoin(triageData, eq(triageData.issueId, issues.id))
259
- .where(whereClause)
260
- .orderBy(desc(issues.createdAt))
261
- .limit(limit)
262
- .offset(offset);
263
 
264
- console.log("[getIssuesWithTriage] Query returned %d results", results.length);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
 
266
- // Transform [ {issue, triage}, {issue, triage}, ... ] into [ {issue, triage}, ... ]
267
- const issuesWithTriage = results.map(row => ({
268
- ...row.issue,
269
- triage: row.triage || null,
270
- }));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
- return {
273
- issues: issuesWithTriage,
274
- total,
275
- page: safePage,
276
- limit,
277
- totalPages,
278
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  }
280
 
281
  // =============================================================================
 
204
 
205
  console.log("[getIssuesWithTriage] Starting query with filters:", filters, "page:", page, "limit:", limit);
206
 
207
+ try {
208
+ // Build conditions (identical to getIssues)
209
+ const conditions = [];
210
+ if (filters.repoId) conditions.push(eq(issues.repoId, filters.repoId));
211
+ if (filters.repoName) conditions.push(eq(issues.repoName, filters.repoName));
212
+ if (filters.authorName) conditions.push(eq(issues.authorName, filters.authorName));
213
+ if (filters.state) conditions.push(eq(issues.state, filters.state));
214
+ if (filters.isPR !== undefined) conditions.push(eq(issues.isPR, filters.isPR));
215
+ if (filters.search) {
216
+ conditions.push(or(
217
+ like(issues.title, `%${filters.search}%`),
218
+ like(issues.bodySummary, `%${filters.search}%`)
 
 
 
 
 
 
 
 
219
  ));
 
 
 
 
 
 
220
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
+ if (filters.userId) {
223
+ const userRepos = await db.select({ id: repositories.id })
224
+ .from(repositories)
225
+ .where(and(
226
+ eq(repositories.userId, filters.userId),
227
+ eq(repositories.addedByUser, true)
228
+ ));
229
+ const repoIds = userRepos.map(r => r.id);
230
+
231
+ if (repoIds.length > 0) {
232
+ conditions.push(sql`${issues.repoId} IN (${sql.join(repoIds.map(id => sql`'${id}'`), sql`, `)})`);
233
+ } else {
234
+ return { issues: [], total: 0, page: safePage, limit, totalPages: 0 };
235
+ }
236
+ }
237
 
238
+ const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
239
+
240
+ console.log("[getIssuesWithTriage] Built where clause, fetching count...");
241
+
242
+ // Get total count
243
+ const countResult = await db.select({ count: count() })
244
+ .from(issues)
245
+ .where(whereClause);
246
+ const total = countResult[0]?.count || 0;
247
+ const totalPages = Math.ceil(total / limit);
248
+
249
+ console.log("[getIssuesWithTriage] Total count:", total, "totalPages:", totalPages);
250
+
251
+ // Fetch issues using simple query
252
+ console.log("[getIssuesWithTriage] Executing issues query...");
253
+ const issueResults = await db.select()
254
+ .from(issues)
255
+ .where(whereClause)
256
+ .orderBy(desc(issues.createdAt))
257
+ .limit(limit)
258
+ .offset(offset);
259
+
260
+ console.log("[getIssuesWithTriage] Query returned %d results", issueResults.length);
261
+
262
+ // Fetch triage data for these issues
263
+ if (issueResults.length === 0) {
264
+ return {
265
+ issues: [],
266
+ total,
267
+ page: safePage,
268
+ limit,
269
+ totalPages,
270
+ };
271
+ }
272
 
273
+ console.log("[getIssuesWithTriage] Fetching triage data for %d issues", issueResults.length);
274
+ const issueIds = issueResults.map(i => i.id);
275
+ const triageDataResults = await db.select()
276
+ .from(triageData)
277
+ .where(sql`${triageData.issueId} IN (${sql.join(issueIds.map(id => sql`'${id}'`), sql`, `)})`);
278
+
279
+ console.log("[getIssuesWithTriage] Found %d triage records", triageDataResults.length);
280
+
281
+ // Create a map of triage data by issueId for quick lookup
282
+ const triageMap = new Map(triageDataResults.map(t => [t.issueId, t]));
283
+
284
+ // Merge issues with their triage data
285
+ const issuesWithTriage = issueResults.map(issue => ({
286
+ ...issue,
287
+ triage: triageMap.get(issue.id) || null,
288
+ }));
289
+
290
+ return {
291
+ issues: issuesWithTriage,
292
+ total,
293
+ page: safePage,
294
+ limit,
295
+ totalPages,
296
+ };
297
+ } catch (error: any) {
298
+ console.error("[getIssuesWithTriage] Query failed:", error);
299
+ console.error("[getIssuesWithTriage] Error message:", error?.message);
300
+ console.error("[getIssuesWithTriage] Error stack:", error?.stack);
301
+ throw error;
302
+ }
303
  }
304
 
305
  // =============================================================================