KrishnaCosmic commited on
Commit
c7355e0
·
1 Parent(s): 117459b

Add missing API endpoints for contributor, messaging, and RAG

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. scripts/migrate-data.ts +0 -949
  2. src/app/api/ai/chat/route.ts +0 -41
  3. src/app/api/ai/mentor-match/route.ts +0 -32
  4. src/app/api/ai/rag/route.ts +0 -36
  5. src/app/api/ai/triage/route.ts +0 -41
  6. src/app/api/auth/github/callback/route.ts +0 -101
  7. src/app/api/auth/github/route.ts +0 -15
  8. src/app/api/auth/me/route.ts +0 -22
  9. src/app/api/auth/select-role/route.ts +0 -51
  10. src/app/api/chat/route.ts +0 -43
  11. src/app/api/contributor/claim-activity/[issueId]/route.ts +0 -32
  12. src/app/api/contributor/claim-issue/[issueId]/route.ts +0 -32
  13. src/app/api/contributor/claim-issue/route.ts +0 -38
  14. src/app/api/contributor/dashboard-summary/route.ts +0 -44
  15. src/app/api/contributor/my-claimed-issues/route.ts +0 -27
  16. src/app/api/contributor/my-issues/route.ts +0 -36
  17. src/app/api/contributor/route.ts +0 -54
  18. src/app/api/issues/[id]/messages/route.ts +0 -139
  19. src/app/api/issues/route.ts +0 -142
  20. src/app/api/maintainer/dashboard-summary/route.ts +0 -39
  21. src/app/api/maintainer/issues/route.ts +0 -44
  22. src/app/api/maintainer/route.ts +0 -49
  23. src/app/api/maintainer/templates/route.ts +0 -25
  24. src/app/api/messages/route.ts +0 -63
  25. src/app/api/messaging/conversations/route.ts +0 -36
  26. src/app/api/messaging/history/[userId]/route.ts +0 -35
  27. src/app/api/messaging/mark-read/[userId]/route.ts +0 -31
  28. src/app/api/messaging/poll/[userId]/route.ts +0 -33
  29. src/app/api/messaging/route.ts +0 -34
  30. src/app/api/messaging/send/route.ts +0 -40
  31. src/app/api/messaging/unread-count/route.ts +0 -26
  32. src/app/api/profile/[id]/connected-repos/route.ts +0 -23
  33. src/app/api/profile/[username]/featured-badges/route.ts +0 -20
  34. src/app/api/profile/[username]/repos/route.ts +0 -31
  35. src/app/api/profile/[username]/route.ts +0 -19
  36. src/app/api/profile/route.ts +0 -59
  37. src/app/api/rag/chat/route.ts +0 -37
  38. src/app/api/rag/index/route.ts +0 -37
  39. src/app/api/rag/search/route.ts +0 -37
  40. src/app/api/rag/suggestions/route.ts +0 -40
  41. src/app/api/repositories/contributor/route.ts +0 -98
  42. src/app/api/repositories/route.ts +0 -122
  43. src/app/api/spark/badges/user/[username]/route.ts +0 -16
  44. src/app/api/spark/gamification/calendar/[username]/route.ts +0 -16
  45. src/app/api/spark/gamification/streak/[username]/route.ts +0 -16
  46. src/app/api/triage/route.ts +0 -181
  47. src/app/favicon.ico +0 -0
  48. src/app/globals.css +0 -26
  49. src/app/layout.tsx +0 -34
  50. src/app/page.tsx +0 -65
scripts/migrate-data.ts DELETED
@@ -1,949 +0,0 @@
1
- /**
2
- * MongoDB to Turso Data Migration Script
3
- *
4
- * Migrates all data from MongoDB Atlas to Turso (SQLite).
5
- * MongoDB remains READ-ONLY - no deletions or modifications.
6
- *
7
- * Usage:
8
- * npm run migrate:data
9
- * npm run migrate:data -- --dry-run
10
- *
11
- * Required env vars:
12
- * MONGO_URL, TURSO_DATABASE_URL, TURSO_AUTH_TOKEN
13
- */
14
-
15
- import { MongoClient, ObjectId } from "mongodb";
16
- import { createClient } from "@libsql/client";
17
- import { drizzle } from "drizzle-orm/libsql";
18
- import { sql } from "drizzle-orm";
19
- import * as schema from "../src/db/schema";
20
- import { v4 as uuidv4 } from "uuid";
21
- import * as dotenv from "dotenv";
22
-
23
- dotenv.config({ path: ".env.local" });
24
-
25
- // =============================================================================
26
- // Types
27
- // =============================================================================
28
-
29
- interface MongoUser {
30
- _id?: ObjectId;
31
- id?: string;
32
- githubId: number;
33
- username: string;
34
- avatarUrl: string;
35
- role?: string;
36
- repositories?: string[];
37
- githubAccessToken?: string;
38
- createdAt?: Date | string;
39
- updatedAt?: Date | string;
40
- }
41
-
42
- interface MongoRepository {
43
- _id?: ObjectId;
44
- id?: string;
45
- githubRepoId: number;
46
- name: string;
47
- owner: string;
48
- userId: string;
49
- createdAt?: Date | string;
50
- }
51
-
52
- interface MongoIssue {
53
- _id?: ObjectId;
54
- id?: string;
55
- githubIssueId: number;
56
- number: number;
57
- title: string;
58
- body?: string;
59
- authorName: string;
60
- repoId: string;
61
- repoName: string;
62
- owner?: string;
63
- repo?: string;
64
- htmlUrl?: string;
65
- state?: string;
66
- isPR?: boolean;
67
- createdAt?: Date | string;
68
- }
69
-
70
- interface MongoMessage {
71
- _id?: ObjectId;
72
- id?: string;
73
- senderId: string;
74
- receiverId: string;
75
- content: string;
76
- read?: boolean;
77
- timestamp?: Date | string;
78
- }
79
-
80
- interface MongoProfile {
81
- _id?: ObjectId;
82
- user_id: string;
83
- username: string;
84
- avatar_url?: string;
85
- bio?: string;
86
- skills?: string[];
87
- location?: string;
88
- website?: string;
89
- twitter?: string;
90
- available_for_mentoring?: boolean;
91
- mentoring_topics?: string[];
92
- connected_repos?: string[];
93
- profile_visibility?: string;
94
- show_email?: boolean;
95
- github_stats?: object;
96
- stats_updated_at?: Date | string;
97
- created_at?: Date | string;
98
- updated_at?: Date | string;
99
- }
100
-
101
- interface MongoTriageData {
102
- _id?: ObjectId;
103
- id?: string;
104
- issueId: string;
105
- classification: string;
106
- summary: string;
107
- suggestedLabel: string;
108
- sentiment: string;
109
- analyzedAt?: Date | string;
110
- }
111
-
112
- interface MongoTemplate {
113
- _id?: ObjectId;
114
- id?: string;
115
- name: string;
116
- body: string;
117
- ownerId: string;
118
- triggerClassification?: string;
119
- createdAt?: Date | string;
120
- }
121
-
122
- interface MongoChatMessage {
123
- role: string;
124
- content: string;
125
- timestamp?: Date | string;
126
- githubCommentId?: string;
127
- githubCommentUrl?: string;
128
- }
129
-
130
- interface MongoChatHistory {
131
- _id?: ObjectId;
132
- id?: string;
133
- userId: string;
134
- sessionId: string;
135
- messages?: MongoChatMessage[];
136
- createdAt?: Date | string;
137
- }
138
-
139
- interface MongoMentor {
140
- _id?: ObjectId;
141
- id?: string;
142
- userId: string;
143
- username: string;
144
- expertiseLevel?: string;
145
- availabilityHoursPerWeek?: number;
146
- timezone?: string;
147
- isActive?: boolean;
148
- bio?: string;
149
- avatarUrl?: string;
150
- techStack?: string[];
151
- languages?: string[];
152
- frameworks?: string[];
153
- preferredTopics?: string[];
154
- menteeCount?: number;
155
- sessionsCompleted?: number;
156
- avgRating?: number;
157
- totalRatings?: number;
158
- maxMentees?: number;
159
- createdAt?: Date | string;
160
- updatedAt?: Date | string;
161
- }
162
-
163
- interface MongoTrophy {
164
- _id?: ObjectId;
165
- id?: string;
166
- userId: string;
167
- username: string;
168
- trophyType: string;
169
- name: string;
170
- description: string;
171
- icon: string;
172
- color: string;
173
- rarity: string;
174
- svgData?: string;
175
- isPublic?: boolean;
176
- shareUrl?: string;
177
- earnedFor?: string;
178
- milestoneValue?: number;
179
- awardedAt?: Date | string;
180
- }
181
-
182
- interface MongoIssueChat {
183
- _id?: ObjectId;
184
- id?: string;
185
- issueId: string;
186
- userId: string;
187
- sessionId: string;
188
- messages?: MongoChatMessage[];
189
- createdAt?: Date | string;
190
- updatedAt?: Date | string;
191
- }
192
-
193
- interface MongoResource {
194
- _id?: ObjectId;
195
- id?: string;
196
- repoName: string;
197
- sourceType?: string;
198
- sourceId?: string;
199
- resourceType: string;
200
- title: string;
201
- content: string;
202
- description?: string;
203
- language?: string;
204
- sharedBy: string;
205
- sharedById: string;
206
- tags?: string[];
207
- saveCount?: number;
208
- helpfulCount?: number;
209
- createdAt?: Date | string;
210
- updatedAt?: Date | string;
211
- }
212
-
213
- // =============================================================================
214
- // Utilities
215
- // =============================================================================
216
-
217
- function toIsoString(date: Date | string | undefined): string {
218
- if (!date) return new Date().toISOString();
219
- if (typeof date === "string") return date;
220
- return date.toISOString();
221
- }
222
-
223
- function extractId(doc: { _id?: ObjectId; id?: string }): string {
224
- if (doc.id) return doc.id;
225
- if (doc._id) return doc._id.toString();
226
- return uuidv4();
227
- }
228
-
229
- function log(message: string, type: "info" | "success" | "error" | "warn" = "info") {
230
- const icons = { info: "ℹ️", success: "✅", error: "❌", warn: "⚠️" };
231
- const time = new Date().toISOString().split("T")[1].split(".")[0];
232
- console.log(`[${time}] ${icons[type]} ${message}`);
233
- }
234
-
235
- // =============================================================================
236
- // Migration Functions
237
- // =============================================================================
238
-
239
- async function migrateUsers(
240
- mongoDb: ReturnType<MongoClient["db"]>,
241
- tursoDb: ReturnType<typeof drizzle>,
242
- isDryRun: boolean
243
- ): Promise<Map<string, string>> {
244
- log("Migrating Users...");
245
- const userIdMap = new Map<string, string>();
246
- const users = await mongoDb.collection<MongoUser>("users").find().toArray();
247
- log(`Found ${users.length} users`);
248
-
249
- let success = 0, skipped = 0;
250
- for (const mongoUser of users) {
251
- const userId = extractId(mongoUser);
252
- const originalId = mongoUser._id?.toString() || mongoUser.id || "";
253
- userIdMap.set(originalId, userId);
254
-
255
- if (!isDryRun) {
256
- try {
257
- await tursoDb.insert(schema.users).values({
258
- id: userId,
259
- githubId: mongoUser.githubId,
260
- username: mongoUser.username,
261
- avatarUrl: mongoUser.avatarUrl,
262
- role: mongoUser.role || null,
263
- githubAccessToken: mongoUser.githubAccessToken || null,
264
- createdAt: toIsoString(mongoUser.createdAt),
265
- updatedAt: toIsoString(mongoUser.updatedAt),
266
- }).onConflictDoNothing();
267
-
268
- // Migrate user repositories array
269
- if (mongoUser.repositories?.length) {
270
- for (const repoName of mongoUser.repositories) {
271
- await tursoDb.insert(schema.userRepositories).values({
272
- id: uuidv4(),
273
- userId: userId,
274
- repoFullName: repoName,
275
- addedAt: toIsoString(mongoUser.createdAt),
276
- }).onConflictDoNothing();
277
- }
278
- }
279
- success++;
280
- } catch (e) {
281
- skipped++;
282
- }
283
- } else {
284
- log(`[DRY] User: ${mongoUser.username}`);
285
- success++;
286
- }
287
- }
288
- log(`Users: ${success} migrated, ${skipped} skipped`, "success");
289
- return userIdMap;
290
- }
291
-
292
- async function migrateRepositories(
293
- mongoDb: ReturnType<MongoClient["db"]>,
294
- tursoDb: ReturnType<typeof drizzle>,
295
- userIdMap: Map<string, string>,
296
- isDryRun: boolean
297
- ): Promise<Map<string, string>> {
298
- log("Migrating Repositories...");
299
- const repoIdMap = new Map<string, string>();
300
- const repos = await mongoDb.collection<MongoRepository>("repositories").find().toArray();
301
- log(`Found ${repos.length} repositories`);
302
-
303
- let success = 0, skipped = 0;
304
- for (const repo of repos) {
305
- const repoId = extractId(repo);
306
- const originalId = repo._id?.toString() || repo.id || "";
307
- repoIdMap.set(originalId, repoId);
308
-
309
- const mappedUserId = userIdMap.get(repo.userId) || repo.userId;
310
-
311
- if (!isDryRun) {
312
- try {
313
- await tursoDb.insert(schema.repositories).values({
314
- id: repoId,
315
- githubRepoId: repo.githubRepoId,
316
- name: repo.name,
317
- owner: repo.owner,
318
- userId: mappedUserId,
319
- createdAt: toIsoString(repo.createdAt),
320
- }).onConflictDoNothing();
321
- success++;
322
- } catch (e) {
323
- skipped++;
324
- }
325
- } else {
326
- log(`[DRY] Repo: ${repo.owner}/${repo.name}`);
327
- success++;
328
- }
329
- }
330
- log(`Repositories: ${success} migrated, ${skipped} skipped`, "success");
331
- return repoIdMap;
332
- }
333
-
334
- async function migrateIssues(
335
- mongoDb: ReturnType<MongoClient["db"]>,
336
- tursoDb: ReturnType<typeof drizzle>,
337
- repoIdMap: Map<string, string>,
338
- isDryRun: boolean
339
- ): Promise<Map<string, string>> {
340
- log("Migrating Issues...");
341
- const issueIdMap = new Map<string, string>();
342
- const issues = await mongoDb.collection<MongoIssue>("issues").find().toArray();
343
- log(`Found ${issues.length} issues`);
344
-
345
- let success = 0, skipped = 0;
346
- for (const issue of issues) {
347
- const issueId = extractId(issue);
348
- const originalId = issue._id?.toString() || issue.id || "";
349
- issueIdMap.set(originalId, issueId);
350
-
351
- const mappedRepoId = repoIdMap.get(issue.repoId) || issue.repoId;
352
-
353
- if (!isDryRun) {
354
- try {
355
- await tursoDb.insert(schema.issues).values({
356
- id: issueId,
357
- githubIssueId: issue.githubIssueId,
358
- number: issue.number,
359
- title: issue.title,
360
- body: issue.body || null,
361
- authorName: issue.authorName,
362
- repoId: mappedRepoId,
363
- repoName: issue.repoName,
364
- owner: issue.owner || null,
365
- repo: issue.repo || null,
366
- htmlUrl: issue.htmlUrl || null,
367
- state: issue.state || "open",
368
- isPR: issue.isPR || false,
369
- createdAt: toIsoString(issue.createdAt),
370
- }).onConflictDoNothing();
371
- success++;
372
- } catch (e) {
373
- skipped++;
374
- }
375
- } else {
376
- log(`[DRY] Issue: #${issue.number} - ${issue.title.substring(0, 30)}...`);
377
- success++;
378
- }
379
- }
380
- log(`Issues: ${success} migrated, ${skipped} skipped`, "success");
381
- return issueIdMap;
382
- }
383
-
384
- async function migrateMessages(
385
- mongoDb: ReturnType<MongoClient["db"]>,
386
- tursoDb: ReturnType<typeof drizzle>,
387
- userIdMap: Map<string, string>,
388
- isDryRun: boolean
389
- ): Promise<void> {
390
- log("Migrating Messages...");
391
- const messages = await mongoDb.collection<MongoMessage>("messages").find().toArray();
392
- log(`Found ${messages.length} messages`);
393
-
394
- let success = 0, skipped = 0;
395
- for (const msg of messages) {
396
- const msgId = extractId(msg);
397
- const senderId = userIdMap.get(msg.senderId) || msg.senderId;
398
- const receiverId = userIdMap.get(msg.receiverId) || msg.receiverId;
399
-
400
- if (!isDryRun) {
401
- try {
402
- await tursoDb.insert(schema.messages).values({
403
- id: msgId,
404
- senderId: senderId,
405
- receiverId: receiverId,
406
- content: msg.content,
407
- read: msg.read || false,
408
- timestamp: toIsoString(msg.timestamp),
409
- }).onConflictDoNothing();
410
- success++;
411
- } catch (e) {
412
- skipped++;
413
- }
414
- } else {
415
- success++;
416
- }
417
- }
418
- log(`Messages: ${success} migrated, ${skipped} skipped`, "success");
419
- }
420
-
421
- async function migrateProfiles(
422
- mongoDb: ReturnType<MongoClient["db"]>,
423
- tursoDb: ReturnType<typeof drizzle>,
424
- userIdMap: Map<string, string>,
425
- isDryRun: boolean
426
- ): Promise<void> {
427
- log("Migrating Profiles...");
428
- const profiles = await mongoDb.collection<MongoProfile>("profiles").find().toArray();
429
- log(`Found ${profiles.length} profiles`);
430
-
431
- let success = 0, skipped = 0;
432
- for (const profile of profiles) {
433
- const userId = userIdMap.get(profile.user_id) || profile.user_id;
434
-
435
- if (!isDryRun) {
436
- try {
437
- await tursoDb.insert(schema.profiles).values({
438
- userId: userId,
439
- username: profile.username,
440
- avatarUrl: profile.avatar_url || null,
441
- bio: profile.bio || null,
442
- location: profile.location || null,
443
- website: profile.website || null,
444
- twitter: profile.twitter || null,
445
- availableForMentoring: profile.available_for_mentoring || false,
446
- profileVisibility: profile.profile_visibility || "public",
447
- showEmail: profile.show_email || false,
448
- githubStats: profile.github_stats ? JSON.stringify(profile.github_stats) : null,
449
- statsUpdatedAt: profile.stats_updated_at ? toIsoString(profile.stats_updated_at) : null,
450
- createdAt: toIsoString(profile.created_at),
451
- updatedAt: toIsoString(profile.updated_at),
452
- }).onConflictDoNothing();
453
-
454
- // Migrate skills
455
- if (profile.skills?.length) {
456
- for (const skill of profile.skills) {
457
- await tursoDb.insert(schema.profileSkills).values({
458
- profileId: userId,
459
- skill: skill,
460
- }).onConflictDoNothing();
461
- }
462
- }
463
-
464
- // Migrate mentoring topics
465
- if (profile.mentoring_topics?.length) {
466
- for (const topic of profile.mentoring_topics) {
467
- await tursoDb.insert(schema.profileMentoringTopics).values({
468
- profileId: userId,
469
- topic: topic,
470
- }).onConflictDoNothing();
471
- }
472
- }
473
-
474
- // Migrate connected repos
475
- if (profile.connected_repos?.length) {
476
- for (const repo of profile.connected_repos) {
477
- await tursoDb.insert(schema.profileConnectedRepos).values({
478
- profileId: userId,
479
- repoName: repo,
480
- }).onConflictDoNothing();
481
- }
482
- }
483
- success++;
484
- } catch (e) {
485
- skipped++;
486
- }
487
- } else {
488
- success++;
489
- }
490
- }
491
- log(`Profiles: ${success} migrated, ${skipped} skipped`, "success");
492
- }
493
-
494
- async function migrateTriageData(
495
- mongoDb: ReturnType<MongoClient["db"]>,
496
- tursoDb: ReturnType<typeof drizzle>,
497
- issueIdMap: Map<string, string>,
498
- isDryRun: boolean
499
- ): Promise<void> {
500
- log("Migrating Triage Data...");
501
- const triageData = await mongoDb.collection<MongoTriageData>("triageData").find().toArray();
502
- log(`Found ${triageData.length} triage records`);
503
-
504
- let success = 0, skipped = 0;
505
- for (const triage of triageData) {
506
- const triageId = extractId(triage);
507
- const issueId = issueIdMap.get(triage.issueId) || triage.issueId;
508
-
509
- if (!isDryRun) {
510
- try {
511
- await tursoDb.insert(schema.triageData).values({
512
- id: triageId,
513
- issueId: issueId,
514
- classification: triage.classification,
515
- summary: triage.summary,
516
- suggestedLabel: triage.suggestedLabel,
517
- sentiment: triage.sentiment,
518
- analyzedAt: toIsoString(triage.analyzedAt),
519
- }).onConflictDoNothing();
520
- success++;
521
- } catch (e) {
522
- skipped++;
523
- }
524
- } else {
525
- success++;
526
- }
527
- }
528
- log(`Triage Data: ${success} migrated, ${skipped} skipped`, "success");
529
- }
530
-
531
- async function migrateTemplates(
532
- mongoDb: ReturnType<MongoClient["db"]>,
533
- tursoDb: ReturnType<typeof drizzle>,
534
- userIdMap: Map<string, string>,
535
- isDryRun: boolean
536
- ): Promise<void> {
537
- log("Migrating Templates...");
538
- const templates = await mongoDb.collection<MongoTemplate>("templates").find().toArray();
539
- log(`Found ${templates.length} templates`);
540
-
541
- let success = 0, skipped = 0;
542
- for (const template of templates) {
543
- const templateId = extractId(template);
544
- const ownerId = userIdMap.get(template.ownerId) || template.ownerId;
545
-
546
- if (!isDryRun) {
547
- try {
548
- await tursoDb.insert(schema.templates).values({
549
- id: templateId,
550
- name: template.name,
551
- body: template.body,
552
- ownerId: ownerId,
553
- triggerClassification: template.triggerClassification || null,
554
- createdAt: toIsoString(template.createdAt),
555
- }).onConflictDoNothing();
556
- success++;
557
- } catch (e) {
558
- skipped++;
559
- }
560
- } else {
561
- success++;
562
- }
563
- }
564
- log(`Templates: ${success} migrated, ${skipped} skipped`, "success");
565
- }
566
-
567
- async function migrateChatHistory(
568
- mongoDb: ReturnType<MongoClient["db"]>,
569
- tursoDb: ReturnType<typeof drizzle>,
570
- userIdMap: Map<string, string>,
571
- isDryRun: boolean
572
- ): Promise<void> {
573
- log("Migrating Chat History...");
574
- const chatHistories = await mongoDb.collection<MongoChatHistory>("chat_history").find().toArray();
575
- log(`Found ${chatHistories.length} chat histories`);
576
-
577
- let historySuccess = 0, historySkipped = 0;
578
- let messageSuccess = 0, messageSkipped = 0;
579
-
580
- for (const history of chatHistories) {
581
- const historyId = extractId(history);
582
- const userId = userIdMap.get(history.userId) || history.userId;
583
-
584
- if (!isDryRun) {
585
- try {
586
- // Insert chat history record
587
- await tursoDb.insert(schema.chatHistory).values({
588
- id: historyId,
589
- userId: userId,
590
- sessionId: history.sessionId,
591
- createdAt: toIsoString(history.createdAt),
592
- }).onConflictDoNothing();
593
- historySuccess++;
594
-
595
- // Insert related messages
596
- if (history.messages?.length) {
597
- for (const msg of history.messages) {
598
- const msgId = uuidv4();
599
- try {
600
- await tursoDb.insert(schema.chatHistoryMessages).values({
601
- id: msgId,
602
- chatHistoryId: historyId,
603
- role: msg.role,
604
- content: msg.content,
605
- timestamp: toIsoString(msg.timestamp),
606
- githubCommentId: msg.githubCommentId || null,
607
- githubCommentUrl: msg.githubCommentUrl || null,
608
- }).onConflictDoNothing();
609
- messageSuccess++;
610
- } catch (e) {
611
- messageSkipped++;
612
- }
613
- }
614
- }
615
- } catch (e) {
616
- historySkipped++;
617
- }
618
- } else {
619
- log(`[DRY] Chat History: ${historyId} with ${history.messages?.length || 0} messages`);
620
- historySuccess++;
621
- messageSuccess += history.messages?.length || 0;
622
- }
623
- }
624
- log(`Chat History: ${historySuccess} migrated, ${historySkipped} skipped`, "success");
625
- log(`Chat Messages: ${messageSuccess} migrated, ${messageSkipped} skipped`, "success");
626
- }
627
-
628
- async function migrateMentors(
629
- mongoDb: ReturnType<MongoClient["db"]>,
630
- tursoDb: ReturnType<typeof drizzle>,
631
- userIdMap: Map<string, string>,
632
- isDryRun: boolean
633
- ): Promise<Map<string, string>> {
634
- log("Migrating Mentors...");
635
- const mentorIdMap = new Map<string, string>();
636
- const mentors = await mongoDb.collection<MongoMentor>("mentors").find().toArray();
637
- log(`Found ${mentors.length} mentors`);
638
-
639
- let success = 0, skipped = 0;
640
- for (const mentor of mentors) {
641
- const mentorId = extractId(mentor);
642
- const originalId = mentor._id?.toString() || mentor.id || "";
643
- mentorIdMap.set(originalId, mentorId);
644
-
645
- const mappedUserId = userIdMap.get(mentor.userId) || mentor.userId;
646
-
647
- if (!isDryRun) {
648
- try {
649
- await tursoDb.insert(schema.mentors).values({
650
- id: mentorId,
651
- userId: mappedUserId,
652
- username: mentor.username,
653
- expertiseLevel: mentor.expertiseLevel || "intermediate",
654
- availabilityHoursPerWeek: mentor.availabilityHoursPerWeek || 5,
655
- timezone: mentor.timezone || null,
656
- isActive: mentor.isActive ?? true,
657
- bio: mentor.bio || null,
658
- avatarUrl: mentor.avatarUrl || null,
659
- menteeCount: mentor.menteeCount || 0,
660
- sessionsCompleted: mentor.sessionsCompleted || 0,
661
- avgRating: mentor.avgRating || 0,
662
- totalRatings: mentor.totalRatings || 0,
663
- maxMentees: mentor.maxMentees || 3,
664
- createdAt: toIsoString(mentor.createdAt),
665
- updatedAt: toIsoString(mentor.updatedAt),
666
- }).onConflictDoNothing();
667
-
668
- // Migrate tech stack
669
- if (mentor.techStack?.length) {
670
- for (const tech of mentor.techStack) {
671
- await tursoDb.insert(schema.mentorTechStack).values({
672
- mentorId: mentorId,
673
- tech: tech,
674
- }).onConflictDoNothing();
675
- }
676
- }
677
-
678
- // Migrate languages
679
- if (mentor.languages?.length) {
680
- for (const lang of mentor.languages) {
681
- await tursoDb.insert(schema.mentorLanguages).values({
682
- mentorId: mentorId,
683
- language: lang,
684
- }).onConflictDoNothing();
685
- }
686
- }
687
-
688
- // Migrate frameworks
689
- if (mentor.frameworks?.length) {
690
- for (const fw of mentor.frameworks) {
691
- await tursoDb.insert(schema.mentorFrameworks).values({
692
- mentorId: mentorId,
693
- framework: fw,
694
- }).onConflictDoNothing();
695
- }
696
- }
697
-
698
- // Migrate preferred topics
699
- if (mentor.preferredTopics?.length) {
700
- for (const topic of mentor.preferredTopics) {
701
- await tursoDb.insert(schema.mentorPreferredTopics).values({
702
- mentorId: mentorId,
703
- topic: topic,
704
- }).onConflictDoNothing();
705
- }
706
- }
707
- success++;
708
- } catch (e) {
709
- skipped++;
710
- }
711
- } else {
712
- log(`[DRY] Mentor: ${mentor.username}`);
713
- success++;
714
- }
715
- }
716
- log(`Mentors: ${success} migrated, ${skipped} skipped`, "success");
717
- return mentorIdMap;
718
- }
719
-
720
- async function migrateTrophies(
721
- mongoDb: ReturnType<MongoClient["db"]>,
722
- tursoDb: ReturnType<typeof drizzle>,
723
- userIdMap: Map<string, string>,
724
- isDryRun: boolean
725
- ): Promise<void> {
726
- log("Migrating Trophies...");
727
- const trophies = await mongoDb.collection<MongoTrophy>("trophies").find().toArray();
728
- log(`Found ${trophies.length} trophies`);
729
-
730
- let success = 0, skipped = 0;
731
- for (const trophy of trophies) {
732
- const trophyId = extractId(trophy);
733
- const mappedUserId = userIdMap.get(trophy.userId) || trophy.userId;
734
-
735
- if (!isDryRun) {
736
- try {
737
- await tursoDb.insert(schema.trophies).values({
738
- id: trophyId,
739
- userId: mappedUserId,
740
- username: trophy.username,
741
- trophyType: trophy.trophyType,
742
- name: trophy.name,
743
- description: trophy.description,
744
- icon: trophy.icon,
745
- color: trophy.color,
746
- rarity: trophy.rarity,
747
- svgData: trophy.svgData || null,
748
- isPublic: trophy.isPublic ?? true,
749
- shareUrl: trophy.shareUrl || null,
750
- earnedFor: trophy.earnedFor || null,
751
- milestoneValue: trophy.milestoneValue || null,
752
- awardedAt: toIsoString(trophy.awardedAt),
753
- }).onConflictDoNothing();
754
- success++;
755
- } catch (e) {
756
- skipped++;
757
- }
758
- } else {
759
- log(`[DRY] Trophy: ${trophy.name} for ${trophy.username}`);
760
- success++;
761
- }
762
- }
763
- log(`Trophies: ${success} migrated, ${skipped} skipped`, "success");
764
- }
765
-
766
- async function migrateIssueChats(
767
- mongoDb: ReturnType<MongoClient["db"]>,
768
- tursoDb: ReturnType<typeof drizzle>,
769
- userIdMap: Map<string, string>,
770
- issueIdMap: Map<string, string>,
771
- isDryRun: boolean
772
- ): Promise<void> {
773
- log("Migrating Issue Chats...");
774
- const issueChats = await mongoDb.collection<MongoIssueChat>("issue_chats").find().toArray();
775
- log(`Found ${issueChats.length} issue chats`);
776
-
777
- let chatSuccess = 0, chatSkipped = 0;
778
- let msgSuccess = 0, msgSkipped = 0;
779
-
780
- for (const chat of issueChats) {
781
- const chatId = extractId(chat);
782
- const mappedUserId = userIdMap.get(chat.userId) || chat.userId;
783
- const mappedIssueId = issueIdMap.get(chat.issueId) || chat.issueId;
784
-
785
- if (!isDryRun) {
786
- try {
787
- await tursoDb.insert(schema.issueChats).values({
788
- id: chatId,
789
- issueId: mappedIssueId,
790
- userId: mappedUserId,
791
- sessionId: chat.sessionId,
792
- createdAt: toIsoString(chat.createdAt),
793
- updatedAt: toIsoString(chat.updatedAt),
794
- }).onConflictDoNothing();
795
- chatSuccess++;
796
-
797
- // Migrate messages
798
- if (chat.messages?.length) {
799
- for (const msg of chat.messages) {
800
- const msgId = uuidv4();
801
- try {
802
- await tursoDb.insert(schema.issueChatMessages).values({
803
- id: msgId,
804
- issueChatId: chatId,
805
- role: msg.role,
806
- content: msg.content,
807
- timestamp: toIsoString(msg.timestamp),
808
- githubCommentId: msg.githubCommentId || null,
809
- githubCommentUrl: msg.githubCommentUrl || null,
810
- }).onConflictDoNothing();
811
- msgSuccess++;
812
- } catch (e) {
813
- msgSkipped++;
814
- }
815
- }
816
- }
817
- } catch (e) {
818
- chatSkipped++;
819
- }
820
- } else {
821
- log(`[DRY] Issue Chat: ${chatId} with ${chat.messages?.length || 0} messages`);
822
- chatSuccess++;
823
- msgSuccess += chat.messages?.length || 0;
824
- }
825
- }
826
- log(`Issue Chats: ${chatSuccess} migrated, ${chatSkipped} skipped`, "success");
827
- log(`Issue Chat Messages: ${msgSuccess} migrated, ${msgSkipped} skipped`, "success");
828
- }
829
-
830
- async function migrateResources(
831
- mongoDb: ReturnType<MongoClient["db"]>,
832
- tursoDb: ReturnType<typeof drizzle>,
833
- userIdMap: Map<string, string>,
834
- isDryRun: boolean
835
- ): Promise<void> {
836
- log("Migrating Resources...");
837
- const resources = await mongoDb.collection<MongoResource>("resources").find().toArray();
838
- log(`Found ${resources.length} resources`);
839
-
840
- let success = 0, skipped = 0;
841
- for (const resource of resources) {
842
- const resourceId = extractId(resource);
843
- const mappedUserId = userIdMap.get(resource.sharedById) || resource.sharedById;
844
-
845
- if (!isDryRun) {
846
- try {
847
- await tursoDb.insert(schema.resources).values({
848
- id: resourceId,
849
- repoName: resource.repoName,
850
- sourceType: resource.sourceType || "chat",
851
- sourceId: resource.sourceId || null,
852
- resourceType: resource.resourceType,
853
- title: resource.title,
854
- content: resource.content,
855
- description: resource.description || null,
856
- language: resource.language || null,
857
- sharedBy: resource.sharedBy,
858
- sharedById: mappedUserId,
859
- saveCount: resource.saveCount || 0,
860
- helpfulCount: resource.helpfulCount || 0,
861
- createdAt: toIsoString(resource.createdAt),
862
- updatedAt: toIsoString(resource.updatedAt),
863
- }).onConflictDoNothing();
864
-
865
- // Migrate tags
866
- if (resource.tags?.length) {
867
- for (const tag of resource.tags) {
868
- await tursoDb.insert(schema.resourceTags).values({
869
- resourceId: resourceId,
870
- tag: tag,
871
- }).onConflictDoNothing();
872
- }
873
- }
874
- success++;
875
- } catch (e) {
876
- skipped++;
877
- }
878
- } else {
879
- log(`[DRY] Resource: ${resource.title}`);
880
- success++;
881
- }
882
- }
883
- log(`Resources: ${success} migrated, ${skipped} skipped`, "success");
884
- }
885
-
886
- // =============================================================================
887
- // Main
888
- // =============================================================================
889
-
890
- async function main() {
891
- const args = process.argv.slice(2);
892
- const isDryRun = args.includes("--dry-run");
893
-
894
- if (isDryRun) {
895
- log("=== DRY RUN MODE ===", "warn");
896
- }
897
-
898
- const mongoUri = process.env.MONGO_URL || process.env.MONGODB_URI;
899
- const tursoUrl = process.env.TURSO_DATABASE_URL;
900
- const tursoToken = process.env.TURSO_AUTH_TOKEN;
901
-
902
- if (!mongoUri) {
903
- log("Missing MONGO_URL or MONGODB_URI", "error");
904
- process.exit(1);
905
- }
906
- if (!tursoUrl) {
907
- log("Missing TURSO_DATABASE_URL", "error");
908
- process.exit(1);
909
- }
910
-
911
- log("Connecting to MongoDB...");
912
- const mongoClient = new MongoClient(mongoUri);
913
- await mongoClient.connect();
914
- const dbName = process.env.DB_NAME || "opentriage_db";
915
- const mongoDb = mongoClient.db(dbName);
916
- log(`Connected to MongoDB (${dbName})`, "success");
917
-
918
- log("Connecting to Turso...");
919
- const tursoClient = createClient({ url: tursoUrl, authToken: tursoToken });
920
- const tursoDb = drizzle(tursoClient, { schema });
921
- log("Connected to Turso", "success");
922
-
923
- try {
924
- // Migrate in order (respecting foreign keys)
925
- const userIdMap = await migrateUsers(mongoDb, tursoDb, isDryRun);
926
- const repoIdMap = await migrateRepositories(mongoDb, tursoDb, userIdMap, isDryRun);
927
- const issueIdMap = await migrateIssues(mongoDb, tursoDb, repoIdMap, isDryRun);
928
- await migrateMessages(mongoDb, tursoDb, userIdMap, isDryRun);
929
- await migrateProfiles(mongoDb, tursoDb, userIdMap, isDryRun);
930
- await migrateTriageData(mongoDb, tursoDb, issueIdMap, isDryRun);
931
- await migrateTemplates(mongoDb, tursoDb, userIdMap, isDryRun);
932
- await migrateChatHistory(mongoDb, tursoDb, userIdMap, isDryRun);
933
-
934
- // New migrations for complete feature support
935
- const mentorIdMap = await migrateMentors(mongoDb, tursoDb, userIdMap, isDryRun);
936
- await migrateTrophies(mongoDb, tursoDb, userIdMap, isDryRun);
937
- await migrateIssueChats(mongoDb, tursoDb, userIdMap, issueIdMap, isDryRun);
938
- await migrateResources(mongoDb, tursoDb, userIdMap, isDryRun);
939
-
940
- log("=== Migration Complete ===", "success");
941
- if (isDryRun) {
942
- log("Run without --dry-run to perform actual migration", "info");
943
- }
944
- } finally {
945
- await mongoClient.close();
946
- }
947
- }
948
-
949
- main().catch(console.error);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/ai/chat/route.ts DELETED
@@ -1,41 +0,0 @@
1
- /**
2
- * AI Chat Proxy Route
3
- *
4
- * Forwards requests to AI_ENGINE_URL/chat
5
- */
6
-
7
- import { NextRequest, NextResponse } from "next/server";
8
- import { chat } from "@/lib/ai-client";
9
- import { getCurrentUser } from "@/lib/auth";
10
-
11
- export async function POST(request: NextRequest) {
12
- try {
13
- const user = await getCurrentUser(request);
14
- if (!user) {
15
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
16
- }
17
-
18
- const body = await request.json();
19
- const { message, history, context } = body;
20
-
21
- if (!message) {
22
- return NextResponse.json({ error: "Message is required" }, { status: 400 });
23
- }
24
-
25
- const result = await chat(message, history, {
26
- ...context,
27
- userId: user.id,
28
- username: user.username,
29
- role: user.role,
30
- });
31
-
32
- if (!result.success) {
33
- return NextResponse.json({ error: result.error }, { status: 502 });
34
- }
35
-
36
- return NextResponse.json(result.data);
37
- } catch (error) {
38
- console.error("AI Chat error:", error);
39
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
40
- }
41
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/ai/mentor-match/route.ts DELETED
@@ -1,32 +0,0 @@
1
- /**
2
- * AI Mentor Match Proxy Route
3
- *
4
- * Forwards requests to AI_ENGINE_URL/mentor-match
5
- */
6
-
7
- import { NextRequest, NextResponse } from "next/server";
8
- import { findMentorMatches } from "@/lib/ai-client";
9
- import { getCurrentUser } from "@/lib/auth";
10
-
11
- export async function POST(request: NextRequest) {
12
- try {
13
- const user = await getCurrentUser(request);
14
- if (!user) {
15
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
16
- }
17
-
18
- const body = await request.json();
19
- const { limit = 5 } = body;
20
-
21
- const result = await findMentorMatches(user.id, user.username, limit);
22
-
23
- if (!result.success) {
24
- return NextResponse.json({ error: result.error }, { status: 502 });
25
- }
26
-
27
- return NextResponse.json(result.data);
28
- } catch (error) {
29
- console.error("AI Mentor Match error:", error);
30
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
31
- }
32
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/ai/rag/route.ts DELETED
@@ -1,36 +0,0 @@
1
- /**
2
- * AI RAG Proxy Route
3
- *
4
- * Forwards requests to AI_ENGINE_URL/rag/chat
5
- */
6
-
7
- import { NextRequest, NextResponse } from "next/server";
8
- import { ragQuery } from "@/lib/ai-client";
9
- import { getCurrentUser } from "@/lib/auth";
10
-
11
- export async function POST(request: NextRequest) {
12
- try {
13
- const user = await getCurrentUser(request);
14
- if (!user) {
15
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
16
- }
17
-
18
- const body = await request.json();
19
- const { question, repoName } = body;
20
-
21
- if (!question) {
22
- return NextResponse.json({ error: "Question is required" }, { status: 400 });
23
- }
24
-
25
- const result = await ragQuery(question, repoName);
26
-
27
- if (!result.success) {
28
- return NextResponse.json({ error: result.error }, { status: 502 });
29
- }
30
-
31
- return NextResponse.json(result.data);
32
- } catch (error) {
33
- console.error("AI RAG error:", error);
34
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
35
- }
36
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/ai/triage/route.ts DELETED
@@ -1,41 +0,0 @@
1
- /**
2
- * AI Triage Proxy Route
3
- *
4
- * Forwards requests to AI_ENGINE_URL/triage
5
- */
6
-
7
- import { NextRequest, NextResponse } from "next/server";
8
- import { triageIssue } from "@/lib/ai-client";
9
- import { getCurrentUser } from "@/lib/auth";
10
-
11
- export async function POST(request: NextRequest) {
12
- try {
13
- const user = await getCurrentUser(request);
14
- if (!user) {
15
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
16
- }
17
-
18
- const body = await request.json();
19
- const { title, body: issueBody, authorName, isPR } = body;
20
-
21
- if (!title) {
22
- return NextResponse.json({ error: "Title is required" }, { status: 400 });
23
- }
24
-
25
- const result = await triageIssue({
26
- title,
27
- body: issueBody,
28
- authorName: authorName || "unknown",
29
- isPR: isPR || false,
30
- });
31
-
32
- if (!result.success) {
33
- return NextResponse.json({ error: result.error }, { status: 502 });
34
- }
35
-
36
- return NextResponse.json(result.data);
37
- } catch (error) {
38
- console.error("AI Triage error:", error);
39
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
40
- }
41
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/auth/github/callback/route.ts DELETED
@@ -1,101 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { db } from "@/db";
3
- import { users } from "@/db/schema";
4
- import { createJwtToken } from "@/lib/auth";
5
- import { generateId, now } from "@/lib/utils";
6
- import { eq } from "drizzle-orm";
7
-
8
- const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID!;
9
- const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET!;
10
- const FRONTEND_URL = process.env.FRONTEND_URL || "http://localhost:5173";
11
-
12
- /**
13
- * GET /api/auth/github/callback
14
- * Handle GitHub OAuth callback and create user session.
15
- */
16
- export async function GET(request: NextRequest) {
17
- const { searchParams } = new URL(request.url);
18
- const code = searchParams.get("code");
19
-
20
- if (!code) {
21
- return NextResponse.redirect(`${FRONTEND_URL}/?error=no_code`);
22
- }
23
-
24
- try {
25
- // Exchange code for access token
26
- const tokenResponse = await fetch(
27
- "https://github.com/login/oauth/access_token",
28
- {
29
- method: "POST",
30
- headers: {
31
- Accept: "application/json",
32
- "Content-Type": "application/json",
33
- },
34
- body: JSON.stringify({
35
- client_id: GITHUB_CLIENT_ID,
36
- client_secret: GITHUB_CLIENT_SECRET,
37
- code,
38
- }),
39
- }
40
- );
41
-
42
- const tokenData = await tokenResponse.json();
43
- const accessToken = tokenData.access_token;
44
-
45
- if (!accessToken) {
46
- console.error("No access token:", tokenData);
47
- return NextResponse.redirect(`${FRONTEND_URL}/?error=no_token`);
48
- }
49
-
50
- // Get user info from GitHub
51
- const userResponse = await fetch("https://api.github.com/user", {
52
- headers: {
53
- Authorization: `Bearer ${accessToken}`,
54
- },
55
- });
56
-
57
- const githubUser = await userResponse.json();
58
-
59
- // Check if user exists
60
- const existingUsers = await db
61
- .select()
62
- .from(users)
63
- .where(eq(users.githubId, githubUser.id))
64
- .limit(1);
65
-
66
- let userData;
67
-
68
- if (existingUsers.length > 0) {
69
- // Update existing user with new GitHub token
70
- await db
71
- .update(users)
72
- .set({ githubAccessToken: accessToken, updatedAt: now() })
73
- .where(eq(users.githubId, githubUser.id));
74
-
75
- userData = { ...existingUsers[0], githubAccessToken: accessToken };
76
- } else {
77
- // Create new user
78
- const newUser = {
79
- id: generateId(),
80
- githubId: githubUser.id,
81
- username: githubUser.login,
82
- avatarUrl: githubUser.avatar_url,
83
- role: null,
84
- githubAccessToken: accessToken,
85
- createdAt: now(),
86
- updatedAt: now(),
87
- };
88
-
89
- await db.insert(users).values(newUser);
90
- userData = newUser;
91
- }
92
-
93
- // Create JWT token
94
- const token = createJwtToken(userData.id, userData.role);
95
-
96
- return NextResponse.redirect(`${FRONTEND_URL}/?token=${token}`);
97
- } catch (error) {
98
- console.error("GitHub auth error:", error);
99
- return NextResponse.redirect(`${FRONTEND_URL}/?error=auth_failed`);
100
- }
101
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/auth/github/route.ts DELETED
@@ -1,15 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
-
3
- const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID!;
4
- const API_URL = process.env.API_URL || "http://localhost:3000";
5
-
6
- /**
7
- * GET /api/auth/github
8
- * Redirect to GitHub OAuth authorization page.
9
- */
10
- export async function GET(request: NextRequest) {
11
- const callbackUrl = `${API_URL}/api/auth/github/callback`;
12
- const githubUrl = `https://github.com/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}&redirect_uri=${callbackUrl}&scope=user:email,repo`;
13
-
14
- return NextResponse.redirect(githubUrl);
15
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/auth/me/route.ts DELETED
@@ -1,22 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { requireAuth } from "@/lib/auth";
3
-
4
- /**
5
- * GET /api/auth/me
6
- * Get current authenticated user information.
7
- */
8
- export async function GET(request: NextRequest) {
9
- const { user, error } = await requireAuth(request);
10
-
11
- if (error) {
12
- return error;
13
- }
14
-
15
- return NextResponse.json({
16
- id: user!.id,
17
- username: user!.username,
18
- avatarUrl: user!.avatarUrl,
19
- role: user!.role,
20
- githubId: user!.githubId,
21
- });
22
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/auth/select-role/route.ts DELETED
@@ -1,51 +0,0 @@
1
- /**
2
- * Role Selection Route
3
- *
4
- * POST /api/auth/select-role
5
- * Allows authenticated users to select their role (MAINTAINER or CONTRIBUTOR)
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { requireAuth, createJwtToken } from "@/lib/auth";
10
- import { updateUserRole } from "@/lib/db/queries/users";
11
-
12
- export async function POST(request: NextRequest) {
13
- const { user, error } = await requireAuth(request);
14
-
15
- if (error) {
16
- return error;
17
- }
18
-
19
- try {
20
- const body = await request.json();
21
- const { role } = body;
22
-
23
- // Validate role
24
- if (!role || !["MAINTAINER", "CONTRIBUTOR"].includes(role.toUpperCase())) {
25
- return NextResponse.json(
26
- { error: "Invalid role. Must be MAINTAINER or CONTRIBUTOR" },
27
- { status: 400 }
28
- );
29
- }
30
-
31
- const normalizedRole = role.toUpperCase();
32
-
33
- // Update user role in database
34
- await updateUserRole(user!.id, normalizedRole);
35
-
36
- // Generate new token with updated role
37
- const newToken = createJwtToken(user!.id, normalizedRole);
38
-
39
- return NextResponse.json({
40
- success: true,
41
- role: normalizedRole,
42
- token: newToken,
43
- });
44
- } catch (error) {
45
- console.error("Role selection error:", error);
46
- return NextResponse.json(
47
- { error: "Failed to update role" },
48
- { status: 500 }
49
- );
50
- }
51
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/chat/route.ts DELETED
@@ -1,43 +0,0 @@
1
- /**
2
- * Chat Route
3
- *
4
- * POST /api/chat
5
- * Proxy to /api/ai/chat for convenience
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { chat } from "@/lib/ai-client";
10
- import { getCurrentUser } from "@/lib/auth";
11
-
12
- export async function POST(request: NextRequest) {
13
- try {
14
- const user = await getCurrentUser(request);
15
- if (!user) {
16
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
17
- }
18
-
19
- const body = await request.json();
20
- const { message, sessionId, history, context } = body;
21
-
22
- if (!message) {
23
- return NextResponse.json({ error: "Message is required" }, { status: 400 });
24
- }
25
-
26
- const result = await chat(message, history, {
27
- ...context,
28
- sessionId,
29
- userId: user.id,
30
- username: user.username,
31
- role: user.role,
32
- });
33
-
34
- if (!result.success) {
35
- return NextResponse.json({ error: result.error }, { status: 502 });
36
- }
37
-
38
- return NextResponse.json(result.data);
39
- } catch (error) {
40
- console.error("Chat error:", error);
41
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
42
- }
43
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/contributor/claim-activity/[issueId]/route.ts DELETED
@@ -1,32 +0,0 @@
1
- /**
2
- * Claim Activity Route
3
- *
4
- * POST /api/contributor/claim-activity/[issueId]
5
- * Update activity timestamp for a claimed issue
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
-
11
- export async function POST(
12
- request: NextRequest,
13
- { params }: { params: Promise<{ issueId: string }> }
14
- ) {
15
- try {
16
- const user = await getCurrentUser(request);
17
- if (!user) {
18
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
19
- }
20
-
21
- const { issueId } = await params;
22
-
23
- // TODO: Implement with claimed_issues table
24
- return NextResponse.json({
25
- message: "Activity updated successfully (stub)",
26
- issueId,
27
- });
28
- } catch (error) {
29
- console.error("Claim activity error:", error);
30
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
31
- }
32
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/contributor/claim-issue/[issueId]/route.ts DELETED
@@ -1,32 +0,0 @@
1
- /**
2
- * Unclaim Issue Route
3
- *
4
- * DELETE /api/contributor/claim-issue/[issueId]
5
- * Unclaim a previously claimed issue
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
-
11
- export async function DELETE(
12
- request: NextRequest,
13
- { params }: { params: Promise<{ issueId: string }> }
14
- ) {
15
- try {
16
- const user = await getCurrentUser(request);
17
- if (!user) {
18
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
19
- }
20
-
21
- const { issueId } = await params;
22
-
23
- // TODO: Implement full unclaiming logic with claimed_issues table
24
- return NextResponse.json({
25
- message: "Issue unclaimed successfully (stub)",
26
- issueId,
27
- });
28
- } catch (error) {
29
- console.error("Unclaim issue error:", error);
30
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
31
- }
32
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/contributor/claim-issue/route.ts DELETED
@@ -1,38 +0,0 @@
1
- /**
2
- * Issue Claiming Routes
3
- *
4
- * POST /api/contributor/claim-issue
5
- * Claim an issue to work on
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
-
11
- // Note: Full implementation requires adding claimed_issues table to schema
12
- // This is a stub that prevents 404 errors
13
-
14
- export async function POST(request: NextRequest) {
15
- try {
16
- const user = await getCurrentUser(request);
17
- if (!user) {
18
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
19
- }
20
-
21
- const body = await request.json();
22
- const { issueId } = body;
23
-
24
- if (!issueId) {
25
- return NextResponse.json({ error: "issueId is required" }, { status: 400 });
26
- }
27
-
28
- // TODO: Implement full claiming logic with claimed_issues table
29
- return NextResponse.json({
30
- message: "Issue claim registered (stub)",
31
- issueId,
32
- claimedAt: new Date().toISOString(),
33
- });
34
- } catch (error) {
35
- console.error("Claim issue error:", error);
36
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
37
- }
38
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/contributor/dashboard-summary/route.ts DELETED
@@ -1,44 +0,0 @@
1
- /**
2
- * Contributor Dashboard Summary Route
3
- *
4
- * GET /api/contributor/dashboard-summary
5
- * Get dashboard statistics for the contributor
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
- import { getIssuesWithTriage } from "@/lib/db/queries/issues";
11
- import { getContributorRepositories } from "@/lib/db/queries/repositories";
12
-
13
- export async function GET(request: NextRequest) {
14
- try {
15
- const user = await getCurrentUser(request);
16
- if (!user) {
17
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
18
- }
19
-
20
- // Get all contributor's issues (no pagination for stats)
21
- const [repos, issuesData] = await Promise.all([
22
- getContributorRepositories(user.id, user.username),
23
- getIssuesWithTriage({ authorName: user.username }, 1, 1000), // Get all for accurate stats
24
- ]);
25
-
26
- const allIssues = issuesData.issues;
27
- const myIssues = allIssues.filter(i => !i.isPR);
28
- const myPRs = allIssues.filter(i => i.isPR);
29
-
30
- return NextResponse.json({
31
- totalContributions: allIssues.length,
32
- totalPRs: myPRs.length,
33
- openPRs: myPRs.filter(i => i.state === "open").length,
34
- mergedPRs: myPRs.filter(i => i.state === "closed").length,
35
- totalIssues: myIssues.length,
36
- openIssues: myIssues.filter(i => i.state === "open").length,
37
- closedIssues: myIssues.filter(i => i.state === "closed").length,
38
- repositoriesContributed: repos.length,
39
- });
40
- } catch (error) {
41
- console.error("Contributor dashboard-summary error:", error);
42
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
43
- }
44
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/contributor/my-claimed-issues/route.ts DELETED
@@ -1,27 +0,0 @@
1
- /**
2
- * My Claimed Issues Route
3
- *
4
- * GET /api/contributor/my-claimed-issues
5
- * Get all issues claimed by the current user
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
-
11
- export async function GET(request: NextRequest) {
12
- try {
13
- const user = await getCurrentUser(request);
14
- if (!user) {
15
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
16
- }
17
-
18
- // TODO: Implement with claimed_issues table
19
- return NextResponse.json({
20
- claims: [],
21
- count: 0,
22
- });
23
- } catch (error) {
24
- console.error("My claimed issues error:", error);
25
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
26
- }
27
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/contributor/my-issues/route.ts DELETED
@@ -1,36 +0,0 @@
1
- /**
2
- * Contributor My Issues Route
3
- *
4
- * GET /api/contributor/my-issues
5
- * Get paginated list of contributor's issues and PRs
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
- import { getIssuesWithTriage } from "@/lib/db/queries/issues";
11
-
12
- export async function GET(request: NextRequest) {
13
- try {
14
- const user = await getCurrentUser(request);
15
- if (!user) {
16
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
17
- }
18
-
19
- const { searchParams } = new URL(request.url);
20
- const page = parseInt(searchParams.get("page") || "1");
21
- const limit = parseInt(searchParams.get("limit") || "10");
22
-
23
- const issuesData = await getIssuesWithTriage({ authorName: user.username }, page, limit);
24
-
25
- return NextResponse.json({
26
- items: issuesData.issues,
27
- total: issuesData.total,
28
- page: issuesData.page,
29
- pages: issuesData.totalPages,
30
- limit: issuesData.limit,
31
- });
32
- } catch (error) {
33
- console.error("Contributor my-issues error:", error);
34
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
35
- }
36
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/contributor/route.ts DELETED
@@ -1,54 +0,0 @@
1
- /**
2
- * Contributor Dashboard Route
3
- *
4
- * Get dashboard stats and issues for contributors.
5
- */
6
-
7
- import { NextRequest, NextResponse } from "next/server";
8
- import { getCurrentUser } from "@/lib/auth";
9
- import { getIssuesWithTriage } from "@/lib/db/queries/issues";
10
- import { getContributorRepositories } from "@/lib/db/queries/repositories";
11
-
12
- export async function GET(request: NextRequest) {
13
- try {
14
- const user = await getCurrentUser(request);
15
- if (!user) {
16
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
17
- }
18
-
19
- const { searchParams } = new URL(request.url);
20
- const page = parseInt(searchParams.get("page") || "1");
21
- const limit = parseInt(searchParams.get("limit") || "10");
22
-
23
- // Get contributor's issues
24
- const [repos, issuesData] = await Promise.all([
25
- getContributorRepositories(user.id, user.username),
26
- getIssuesWithTriage({ authorName: user.username }, page, limit),
27
- ]);
28
-
29
- // Calculate stats
30
- const myIssues = issuesData.issues.filter(i => !i.isPR);
31
- const myPRs = issuesData.issues.filter(i => i.isPR);
32
-
33
- return NextResponse.json({
34
- stats: {
35
- totalIssues: myIssues.length,
36
- totalPRs: myPRs.length,
37
- openIssues: myIssues.filter(i => i.state === "open").length,
38
- openPRs: myPRs.filter(i => i.state === "open").length,
39
- repositoriesContributed: repos.length,
40
- },
41
- repositories: repos,
42
- issues: issuesData.issues,
43
- pagination: {
44
- page: issuesData.page,
45
- limit: issuesData.limit,
46
- total: issuesData.total,
47
- totalPages: issuesData.totalPages,
48
- }
49
- });
50
- } catch (error) {
51
- console.error("Contributor dashboard error:", error);
52
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
53
- }
54
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/issues/[id]/messages/route.ts DELETED
@@ -1,139 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { db } from "@/db";
3
- import { chatMessages, issues } from "@/db/schema";
4
- import { requireAuth } from "@/lib/auth";
5
- import { generateId, now } from "@/lib/utils";
6
- import { eq, desc, gt, and } from "drizzle-orm";
7
-
8
- type RouteContext = {
9
- params: Promise<{ id: string }>;
10
- };
11
-
12
- /**
13
- * GET /api/issues/[id]/messages
14
- * Get chat messages for an issue.
15
- */
16
- export async function GET(
17
- request: NextRequest,
18
- context: RouteContext
19
- ) {
20
- const { id: issueId } = await context.params;
21
- const { searchParams } = new URL(request.url);
22
- const afterId = searchParams.get("afterId");
23
- const limit = parseInt(searchParams.get("limit") || "50", 10);
24
-
25
- try {
26
- // Verify issue exists
27
- const issueRecords = await db
28
- .select()
29
- .from(issues)
30
- .where(eq(issues.id, issueId))
31
- .limit(1);
32
-
33
- if (issueRecords.length === 0) {
34
- return NextResponse.json(
35
- { error: "Issue not found" },
36
- { status: 404 }
37
- );
38
- }
39
-
40
- // Build query for messages
41
- // Note: We use sessionId to associate messages with issues
42
- // In a proper implementation, you'd have a session per issue
43
- let messagesQuery = db
44
- .select()
45
- .from(chatMessages)
46
- .where(eq(chatMessages.sessionId, issueId))
47
- .orderBy(desc(chatMessages.timestamp))
48
- .limit(limit);
49
-
50
- const messages = await messagesQuery;
51
-
52
- // Reverse to show oldest first
53
- messages.reverse();
54
-
55
- return NextResponse.json({
56
- messages,
57
- count: messages.length,
58
- issueId,
59
- });
60
- } catch (error) {
61
- console.error("GET /api/issues/[id]/messages error:", error);
62
- return NextResponse.json(
63
- { error: "Failed to fetch messages" },
64
- { status: 500 }
65
- );
66
- }
67
- }
68
-
69
- /**
70
- * POST /api/issues/[id]/messages
71
- * Send a chat message for an issue.
72
- *
73
- * Request body: { content: string, messageType?: string }
74
- */
75
- export async function POST(
76
- request: NextRequest,
77
- context: RouteContext
78
- ) {
79
- const { user, error } = await requireAuth(request);
80
- if (error) return error;
81
-
82
- const { id: issueId } = await context.params;
83
-
84
- try {
85
- const body = await request.json();
86
- const { content, messageType = "text" } = body;
87
-
88
- if (!content || typeof content !== "string") {
89
- return NextResponse.json(
90
- { error: "content is required" },
91
- { status: 400 }
92
- );
93
- }
94
-
95
- // Verify issue exists
96
- const issueRecords = await db
97
- .select()
98
- .from(issues)
99
- .where(eq(issues.id, issueId))
100
- .limit(1);
101
-
102
- if (issueRecords.length === 0) {
103
- return NextResponse.json(
104
- { error: "Issue not found" },
105
- { status: 404 }
106
- );
107
- }
108
-
109
- // Create the message
110
- const message = {
111
- id: generateId(),
112
- sessionId: issueId, // Using issueId as sessionId for issue-based chats
113
- senderId: user!.id,
114
- senderUsername: user!.username,
115
- isMentor: false,
116
- content,
117
- messageType,
118
- language: null,
119
- isAiGenerated: false,
120
- containsResource: false,
121
- extractedResourceId: null,
122
- timestamp: now(),
123
- editedAt: null,
124
- };
125
-
126
- await db.insert(chatMessages).values(message);
127
-
128
- return NextResponse.json({
129
- message: "Message sent",
130
- data: message,
131
- }, { status: 201 });
132
- } catch (error) {
133
- console.error("POST /api/issues/[id]/messages error:", error);
134
- return NextResponse.json(
135
- { error: "Failed to send message" },
136
- { status: 500 }
137
- );
138
- }
139
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/issues/route.ts DELETED
@@ -1,142 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { db } from "@/db";
3
- import { issues, triageData, repositories } from "@/db/schema";
4
- import { generateId, now } from "@/lib/utils";
5
- import { eq, and, desc } from "drizzle-orm";
6
-
7
- // =============================================================================
8
- // GET /api/issues - Get issues for a repository
9
- // =============================================================================
10
- //
11
- // Python equivalent (from routes/repository.py):
12
- // repos = await db.issues.find({"repoId": repo_id}, {"_id": 0}).to_list(1000)
13
- //
14
- export async function GET(request: NextRequest) {
15
- try {
16
- const { searchParams } = new URL(request.url);
17
- const repoId = searchParams.get("repoId");
18
- const state = searchParams.get("state"); // open, closed, all
19
- const isPR = searchParams.get("isPR"); // true, false
20
-
21
- // Build query conditions
22
- const conditions = [];
23
- if (repoId) {
24
- conditions.push(eq(issues.repoId, repoId));
25
- }
26
- if (state && state !== "all") {
27
- conditions.push(eq(issues.state, state));
28
- }
29
- if (isPR !== null && isPR !== undefined) {
30
- conditions.push(eq(issues.isPR, isPR === "true"));
31
- }
32
-
33
- // Execute query with Drizzle
34
- const result = await db
35
- .select()
36
- .from(issues)
37
- .where(conditions.length > 0 ? and(...conditions) : undefined)
38
- .orderBy(desc(issues.createdAt))
39
- .limit(100);
40
-
41
- return NextResponse.json(result, { status: 200 });
42
- } catch (error) {
43
- console.error("GET /api/issues error:", error);
44
- return NextResponse.json(
45
- { error: "Failed to fetch issues" },
46
- { status: 500 }
47
- );
48
- }
49
- }
50
-
51
- // =============================================================================
52
- // POST /api/issues - Create a new issue
53
- // =============================================================================
54
- //
55
- // Python equivalent (from routes/repository.py):
56
- // issue = Issue(
57
- // githubIssueId=gh_issue['id'],
58
- // number=gh_issue['number'],
59
- // title=gh_issue['title'],
60
- // body=gh_issue.get('body') or '',
61
- // authorName=gh_issue['user']['login'],
62
- // repoId=repository.id,
63
- // repoName=repository.name,
64
- // ...
65
- // )
66
- // issue_dict = issue.model_dump()
67
- // issue_dict['createdAt'] = issue_dict['createdAt'].isoformat()
68
- // await db.issues.insert_one(issue_dict)
69
- //
70
- export async function POST(request: NextRequest) {
71
- try {
72
- const body = await request.json();
73
-
74
- // Validate required fields
75
- const { githubIssueId, number, title, authorName, repoId, repoName } = body;
76
- if (!githubIssueId || !number || !title || !authorName || !repoId || !repoName) {
77
- return NextResponse.json(
78
- { error: "Missing required fields" },
79
- { status: 400 }
80
- );
81
- }
82
-
83
- // Check if repository exists
84
- const repo = await db
85
- .select()
86
- .from(repositories)
87
- .where(eq(repositories.id, repoId))
88
- .limit(1);
89
-
90
- if (repo.length === 0) {
91
- return NextResponse.json(
92
- { error: "Repository not found" },
93
- { status: 404 }
94
- );
95
- }
96
-
97
- // Check if issue already exists
98
- const existingIssue = await db
99
- .select()
100
- .from(issues)
101
- .where(eq(issues.githubIssueId, githubIssueId))
102
- .limit(1);
103
-
104
- if (existingIssue.length > 0) {
105
- return NextResponse.json(
106
- { error: "Issue already exists", issue: existingIssue[0] },
107
- { status: 409 }
108
- );
109
- }
110
-
111
- // Create new issue
112
- const newIssue = {
113
- id: generateId(),
114
- githubIssueId,
115
- number,
116
- title,
117
- body: body.body || "",
118
- authorName,
119
- repoId,
120
- repoName,
121
- owner: body.owner || "",
122
- repo: body.repo || "",
123
- htmlUrl: body.htmlUrl || "",
124
- state: body.state || "open",
125
- isPR: body.isPR || false,
126
- createdAt: now(),
127
- };
128
-
129
- await db.insert(issues).values(newIssue);
130
-
131
- return NextResponse.json(
132
- { message: "Issue created", issue: newIssue },
133
- { status: 201 }
134
- );
135
- } catch (error) {
136
- console.error("POST /api/issues error:", error);
137
- return NextResponse.json(
138
- { error: "Failed to create issue" },
139
- { status: 500 }
140
- );
141
- }
142
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/maintainer/dashboard-summary/route.ts DELETED
@@ -1,39 +0,0 @@
1
- /**
2
- * Maintainer Dashboard Summary Route
3
- *
4
- * GET /api/maintainer/dashboard-summary
5
- * Get dashboard statistics for maintainers
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
- import { getDashboardStats } from "@/lib/db/queries/issues";
11
- import { getMaintainerRepositories } from "@/lib/db/queries/repositories";
12
-
13
- export async function GET(request: NextRequest) {
14
- try {
15
- const user = await getCurrentUser(request);
16
- if (!user) {
17
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
18
- }
19
-
20
- if (user.role !== "MAINTAINER" && user.role !== "maintainer") {
21
- return NextResponse.json({ error: "Maintainer access required" }, { status: 403 });
22
- }
23
-
24
- // Get dashboard stats
25
- const [stats, repos] = await Promise.all([
26
- getDashboardStats(user.id),
27
- getMaintainerRepositories(user.id),
28
- ]);
29
-
30
- return NextResponse.json({
31
- ...stats,
32
- repositoriesCount: repos.length,
33
- repositories: repos,
34
- });
35
- } catch (error) {
36
- console.error("Maintainer dashboard-summary error:", error);
37
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
38
- }
39
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/maintainer/issues/route.ts DELETED
@@ -1,44 +0,0 @@
1
- /**
2
- * Maintainer Issues Route
3
- *
4
- * GET /api/maintainer/issues
5
- * Fetch issues for a maintainer with filtering and pagination.
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
- import { getIssues, getIssuesWithTriage, IssueFilters } from "@/lib/db/queries/issues";
11
-
12
- export async function GET(request: NextRequest) {
13
- try {
14
- const user = await getCurrentUser(request);
15
- if (!user) {
16
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
17
- }
18
-
19
- const { searchParams } = new URL(request.url);
20
- const page = parseInt(searchParams.get("page") || "1");
21
- const limit = parseInt(searchParams.get("limit") || "10");
22
- const state = searchParams.get("state") || undefined;
23
- const repoId = searchParams.get("repoId") || undefined;
24
- const search = searchParams.get("search") || undefined;
25
- const withTriage = searchParams.get("withTriage") === "true";
26
-
27
- const filters: IssueFilters = {
28
- userId: user.id, // Filter by user's repos
29
- state,
30
- repoId,
31
- search,
32
- isPR: false, // Only fetch issues, not PRs
33
- };
34
-
35
- const result = withTriage
36
- ? await getIssuesWithTriage(filters, page, limit)
37
- : await getIssues(filters, page, limit);
38
-
39
- return NextResponse.json(result);
40
- } catch (error) {
41
- console.error("GET /api/maintainer/issues error:", error);
42
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
43
- }
44
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/maintainer/route.ts DELETED
@@ -1,49 +0,0 @@
1
- /**
2
- * Maintainer Dashboard Route
3
- *
4
- * Get dashboard stats, issues, and templates for maintainers.
5
- */
6
-
7
- import { NextRequest, NextResponse } from "next/server";
8
- import { getCurrentUser } from "@/lib/auth";
9
- import { getDashboardStats, getIssuesWithTriage } from "@/lib/db/queries/issues";
10
- import { getMaintainerRepositories, getRepositoryStats } from "@/lib/db/queries/repositories";
11
-
12
- export async function GET(request: NextRequest) {
13
- try {
14
- const user = await getCurrentUser(request);
15
- if (!user) {
16
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
17
- }
18
-
19
- if (user.role !== "MAINTAINER" && user.role !== "maintainer") {
20
- return NextResponse.json({ error: "Maintainer access required" }, { status: 403 });
21
- }
22
-
23
- const { searchParams } = new URL(request.url);
24
- const page = parseInt(searchParams.get("page") || "1");
25
- const limit = parseInt(searchParams.get("limit") || "10");
26
-
27
- // Get dashboard data
28
- const [stats, repos, issuesData] = await Promise.all([
29
- getDashboardStats(user.id),
30
- getMaintainerRepositories(user.id),
31
- getIssuesWithTriage({ userId: user.id, state: "open" }, page, limit),
32
- ]);
33
-
34
- return NextResponse.json({
35
- stats,
36
- repositories: repos,
37
- issues: issuesData.issues,
38
- pagination: {
39
- page: issuesData.page,
40
- limit: issuesData.limit,
41
- total: issuesData.total,
42
- totalPages: issuesData.totalPages,
43
- }
44
- });
45
- } catch (error) {
46
- console.error("Maintainer dashboard error:", error);
47
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
48
- }
49
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/maintainer/templates/route.ts DELETED
@@ -1,25 +0,0 @@
1
- /**
2
- * Maintainer Templates Route
3
- *
4
- * GET /api/maintainer/templates
5
- * Fetch templates for a maintainer.
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
- import { getTemplatesByOwnerId } from "@/lib/db/queries/templates";
11
-
12
- export async function GET(request: NextRequest) {
13
- try {
14
- const user = await getCurrentUser(request);
15
- if (!user) {
16
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
17
- }
18
-
19
- const templates = await getTemplatesByOwnerId(user.id);
20
- return NextResponse.json(templates);
21
- } catch (error) {
22
- console.error("GET /api/maintainer/templates error:", error);
23
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
24
- }
25
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/messages/route.ts DELETED
@@ -1,63 +0,0 @@
1
- /**
2
- * Messages Route
3
- *
4
- * Get conversations and send messages.
5
- */
6
-
7
- import { NextRequest, NextResponse } from "next/server";
8
- import { getCurrentUser } from "@/lib/auth";
9
- import { getConversations, getChatHistory, sendMessage, markMessagesAsRead, getUnreadCount } from "@/lib/db/queries/messages";
10
-
11
- export async function GET(request: NextRequest) {
12
- try {
13
- const user = await getCurrentUser(request);
14
- if (!user) {
15
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
16
- }
17
-
18
- const { searchParams } = new URL(request.url);
19
- const otherUserId = searchParams.get("with");
20
-
21
- if (otherUserId) {
22
- // Get chat history with specific user
23
- const history = await getChatHistory(user.id, otherUserId);
24
- await markMessagesAsRead(user.id, otherUserId);
25
- return NextResponse.json({ messages: history });
26
- } else {
27
- // Get all conversations
28
- const conversations = await getConversations(user.id);
29
- const unreadCount = await getUnreadCount(user.id);
30
- return NextResponse.json({ conversations, unreadCount });
31
- }
32
- } catch (error) {
33
- console.error("Messages fetch error:", error);
34
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
35
- }
36
- }
37
-
38
- export async function POST(request: NextRequest) {
39
- try {
40
- const user = await getCurrentUser(request);
41
- if (!user) {
42
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
43
- }
44
-
45
- const body = await request.json();
46
- const { receiverId, content } = body;
47
-
48
- if (!receiverId || !content) {
49
- return NextResponse.json({ error: "receiverId and content are required" }, { status: 400 });
50
- }
51
-
52
- const message = await sendMessage({
53
- senderId: user.id,
54
- receiverId,
55
- content,
56
- });
57
-
58
- return NextResponse.json(message);
59
- } catch (error) {
60
- console.error("Message send error:", error);
61
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
62
- }
63
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/messaging/conversations/route.ts DELETED
@@ -1,36 +0,0 @@
1
- /**
2
- * Messaging Conversations Route
3
- *
4
- * GET /api/messaging/conversations
5
- * Get list of all conversations for the current user
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
- import { getConversations } from "@/lib/db/queries/messages";
11
-
12
- export async function GET(request: NextRequest) {
13
- try {
14
- const user = await getCurrentUser(request);
15
- if (!user) {
16
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
17
- }
18
-
19
- const conversations = await getConversations(user.id);
20
-
21
- // Format to match frontend expectations
22
- return NextResponse.json({
23
- conversations: conversations.map(c => ({
24
- user_id: c.partnerId,
25
- username: c.partnerUsername,
26
- avatar_url: c.partnerAvatar,
27
- last_message: c.lastMessage?.content?.substring(0, 50) || "",
28
- last_timestamp: c.lastMessage?.timestamp || null,
29
- unread_count: c.unreadCount
30
- }))
31
- });
32
- } catch (error) {
33
- console.error("Conversations error:", error);
34
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
35
- }
36
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/messaging/history/[userId]/route.ts DELETED
@@ -1,35 +0,0 @@
1
- /**
2
- * Messaging History Route
3
- *
4
- * GET /api/messaging/history/[userId]
5
- * Get chat history with a specific user
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
- import { getChatHistory, markMessagesAsRead } from "@/lib/db/queries/messages";
11
-
12
- export async function GET(
13
- request: NextRequest,
14
- { params }: { params: Promise<{ userId: string }> }
15
- ) {
16
- try {
17
- const user = await getCurrentUser(request);
18
- if (!user) {
19
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
20
- }
21
-
22
- const { userId: otherUserId } = await params;
23
-
24
- // Get chat history
25
- const history = await getChatHistory(user.id, otherUserId);
26
-
27
- // Mark messages as read
28
- await markMessagesAsRead(user.id, otherUserId);
29
-
30
- return NextResponse.json(history);
31
- } catch (error) {
32
- console.error("Chat history error:", error);
33
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
34
- }
35
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/messaging/mark-read/[userId]/route.ts DELETED
@@ -1,31 +0,0 @@
1
- /**
2
- * Messaging Mark Read Route
3
- *
4
- * POST /api/messaging/mark-read/[userId]
5
- * Mark all messages from a user as read
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
- import { markMessagesAsRead } from "@/lib/db/queries/messages";
11
-
12
- export async function POST(
13
- request: NextRequest,
14
- { params }: { params: Promise<{ userId: string }> }
15
- ) {
16
- try {
17
- const user = await getCurrentUser(request);
18
- if (!user) {
19
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
20
- }
21
-
22
- const { userId: otherUserId } = await params;
23
-
24
- await markMessagesAsRead(user.id, otherUserId);
25
-
26
- return NextResponse.json({ success: true, message: "Messages marked as read" });
27
- } catch (error) {
28
- console.error("Mark read error:", error);
29
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
30
- }
31
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/messaging/poll/[userId]/route.ts DELETED
@@ -1,33 +0,0 @@
1
- /**
2
- * Messaging Poll Route
3
- *
4
- * GET /api/messaging/poll/[userId]
5
- * Poll for new messages from a specific user
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
- import { pollNewMessages } from "@/lib/db/queries/messages";
11
-
12
- export async function GET(
13
- request: NextRequest,
14
- { params }: { params: Promise<{ userId: string }> }
15
- ) {
16
- try {
17
- const user = await getCurrentUser(request);
18
- if (!user) {
19
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
20
- }
21
-
22
- const { userId: otherUserId } = await params;
23
- const { searchParams } = new URL(request.url);
24
- const lastMessageId = searchParams.get("last_message_id") || undefined;
25
-
26
- const newMessages = await pollNewMessages(user.id, otherUserId, lastMessageId);
27
-
28
- return NextResponse.json(newMessages);
29
- } catch (error) {
30
- console.error("Poll messages error:", error);
31
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
32
- }
33
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/messaging/route.ts DELETED
@@ -1,34 +0,0 @@
1
- /**
2
- * Messaging Routes
3
- *
4
- * Full messaging API for the frontend
5
- */
6
-
7
- import { NextRequest, NextResponse } from "next/server";
8
- import { getCurrentUser } from "@/lib/auth";
9
- import {
10
- getConversations,
11
- getChatHistory,
12
- sendMessage,
13
- markMessagesAsRead,
14
- getUnreadCount,
15
- pollNewMessages
16
- } from "@/lib/db/queries/messages";
17
-
18
- // GET /api/messaging - Get conversations list
19
- export async function GET(request: NextRequest) {
20
- try {
21
- const user = await getCurrentUser(request);
22
- if (!user) {
23
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
24
- }
25
-
26
- const conversations = await getConversations(user.id);
27
- const unreadCount = await getUnreadCount(user.id);
28
-
29
- return NextResponse.json({ conversations, unreadCount });
30
- } catch (error) {
31
- console.error("Messaging error:", error);
32
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
33
- }
34
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/messaging/send/route.ts DELETED
@@ -1,40 +0,0 @@
1
- /**
2
- * Messaging Send Route
3
- *
4
- * POST /api/messaging/send
5
- * Send a message to another user
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
- import { sendMessage } from "@/lib/db/queries/messages";
11
-
12
- export async function POST(request: NextRequest) {
13
- try {
14
- const user = await getCurrentUser(request);
15
- if (!user) {
16
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
17
- }
18
-
19
- const body = await request.json();
20
- const { receiver_id, content } = body;
21
-
22
- if (!receiver_id || !content) {
23
- return NextResponse.json(
24
- { error: "receiver_id and content are required" },
25
- { status: 400 }
26
- );
27
- }
28
-
29
- const message = await sendMessage({
30
- senderId: user.id,
31
- receiverId: receiver_id,
32
- content,
33
- });
34
-
35
- return NextResponse.json(message);
36
- } catch (error) {
37
- console.error("Send message error:", error);
38
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
39
- }
40
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/messaging/unread-count/route.ts DELETED
@@ -1,26 +0,0 @@
1
- /**
2
- * Messaging Unread Count Route
3
- *
4
- * GET /api/messaging/unread-count
5
- * Get the count of unread messages for the authenticated user
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
- import { getUnreadCount } from "@/lib/db/queries/messages";
11
-
12
- export async function GET(request: NextRequest) {
13
- try {
14
- const user = await getCurrentUser(request);
15
- if (!user) {
16
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
17
- }
18
-
19
- const count = await getUnreadCount(user.id);
20
-
21
- return NextResponse.json({ count });
22
- } catch (error) {
23
- console.error("Unread count error:", error);
24
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
25
- }
26
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/profile/[id]/connected-repos/route.ts DELETED
@@ -1,23 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { db } from "@/db";
3
- import { profileConnectedRepos } from "@/db/schema";
4
- import { eq } from "drizzle-orm";
5
-
6
- export async function GET(
7
- request: NextRequest,
8
- context: { params: Promise<{ id: string }> }
9
- ) {
10
- try {
11
- const { id } = await context.params;
12
- // 'id' here corresponds to the profile's userId (since userId is PK for profiles)
13
- const repos = await db
14
- .select()
15
- .from(profileConnectedRepos)
16
- .where(eq(profileConnectedRepos.profileId, id));
17
-
18
- return NextResponse.json(repos);
19
- } catch (error) {
20
- console.error("GET /api/profile/:id/connected-repos error:", error);
21
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
22
- }
23
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/profile/[username]/featured-badges/route.ts DELETED
@@ -1,20 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { getUserBadges } from "@/lib/db/queries/gamification";
3
-
4
- export async function GET(
5
- request: NextRequest,
6
- context: { params: Promise<{ username: string }> }
7
- ) {
8
- try {
9
- const { username } = await context.params;
10
- const badges = await getUserBadges(username);
11
- // For now, just return top 5 badges as "featured"
12
- // In the future, we could add a "featured" flag to the trophy table
13
- const featured = badges.slice(0, 5);
14
-
15
- return NextResponse.json(featured);
16
- } catch (error) {
17
- console.error("GET /api/profile/:username/featured-badges error:", error);
18
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
19
- }
20
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/profile/[username]/repos/route.ts DELETED
@@ -1,31 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { db } from "@/db";
3
- import { repositories, users } from "@/db/schema";
4
- import { eq, desc } from "drizzle-orm";
5
-
6
- export async function GET(
7
- request: NextRequest,
8
- context: { params: Promise<{ username: string }> }
9
- ) {
10
- try {
11
- const { username } = await context.params;
12
-
13
- // Find user first to get ID
14
- const user = await db.select().from(users).where(eq(users.username, username)).limit(1);
15
-
16
- if (!user[0]) {
17
- return NextResponse.json({ error: "User not found" }, { status: 404 });
18
- }
19
-
20
- const repos = await db
21
- .select()
22
- .from(repositories)
23
- .where(eq(repositories.userId, user[0].id))
24
- .orderBy(desc(repositories.createdAt));
25
-
26
- return NextResponse.json(repos);
27
- } catch (error) {
28
- console.error("GET /api/profile/:username/repos error:", error);
29
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
30
- }
31
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/profile/[username]/route.ts DELETED
@@ -1,19 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { getProfileByUsername } from "@/lib/db/queries/users";
3
-
4
- export async function GET(
5
- request: NextRequest,
6
- context: { params: Promise<{ username: string }> }
7
- ) {
8
- try {
9
- const { username } = await context.params;
10
- const profile = await getProfileByUsername(username);
11
- if (!profile) {
12
- return NextResponse.json({ error: "Profile not found" }, { status: 404 });
13
- }
14
- return NextResponse.json(profile);
15
- } catch (error) {
16
- console.error("GET /api/profile/:username error:", error);
17
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
18
- }
19
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/profile/route.ts DELETED
@@ -1,59 +0,0 @@
1
- /**
2
- * Profile Route
3
- *
4
- * Get and update user profiles.
5
- */
6
-
7
- import { NextRequest, NextResponse } from "next/server";
8
- import { getCurrentUser } from "@/lib/auth";
9
- import { getProfileByUsername, createOrUpdateProfile } from "@/lib/db/queries/users";
10
-
11
- export async function GET(request: NextRequest) {
12
- try {
13
- const { searchParams } = new URL(request.url);
14
- const username = searchParams.get("username");
15
-
16
- if (!username) {
17
- return NextResponse.json({ error: "Username is required" }, { status: 400 });
18
- }
19
-
20
- const profile = await getProfileByUsername(username);
21
- if (!profile) {
22
- return NextResponse.json({ error: "Profile not found" }, { status: 404 });
23
- }
24
-
25
- return NextResponse.json(profile);
26
- } catch (error) {
27
- console.error("Profile fetch error:", error);
28
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
29
- }
30
- }
31
-
32
- export async function PUT(request: NextRequest) {
33
- try {
34
- const user = await getCurrentUser(request);
35
- if (!user) {
36
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
37
- }
38
-
39
- const body = await request.json();
40
- const { bio, location, website, twitter, skills, availableForMentoring, mentoringTopics } = body;
41
-
42
- const profile = await createOrUpdateProfile(user.id, {
43
- username: user.username,
44
- avatarUrl: user.avatarUrl,
45
- bio,
46
- location,
47
- website,
48
- twitter,
49
- skills,
50
- availableForMentoring,
51
- mentoringTopics,
52
- });
53
-
54
- return NextResponse.json(profile);
55
- } catch (error) {
56
- console.error("Profile update error:", error);
57
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
58
- }
59
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/rag/chat/route.ts DELETED
@@ -1,37 +0,0 @@
1
- /**
2
- * RAG Chat Route
3
- *
4
- * POST /api/rag/chat
5
- * Answer questions using RAG (Retrieval-Augmented Generation)
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
- import { ragChat } from "@/lib/ai-client";
11
-
12
- export async function POST(request: NextRequest) {
13
- try {
14
- const user = await getCurrentUser(request);
15
- if (!user) {
16
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
17
- }
18
-
19
- const body = await request.json();
20
- const { question, repo_name, top_k } = body;
21
-
22
- if (!question) {
23
- return NextResponse.json({ error: "question is required" }, { status: 400 });
24
- }
25
-
26
- const result = await ragChat(question, repo_name, top_k || 5);
27
-
28
- if (!result.success) {
29
- return NextResponse.json({ error: result.error }, { status: 502 });
30
- }
31
-
32
- return NextResponse.json(result.data);
33
- } catch (error) {
34
- console.error("RAG chat error:", error);
35
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
36
- }
37
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/rag/index/route.ts DELETED
@@ -1,37 +0,0 @@
1
- /**
2
- * RAG Index Route
3
- *
4
- * POST /api/rag/index
5
- * Index a repository for RAG search
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
- import { ragIndex } from "@/lib/ai-client";
11
-
12
- export async function POST(request: NextRequest) {
13
- try {
14
- const user = await getCurrentUser(request);
15
- if (!user) {
16
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
17
- }
18
-
19
- const body = await request.json();
20
- const { repo_name } = body;
21
-
22
- if (!repo_name) {
23
- return NextResponse.json({ error: "repo_name is required" }, { status: 400 });
24
- }
25
-
26
- const result = await ragIndex(repo_name);
27
-
28
- if (!result.success) {
29
- return NextResponse.json({ error: result.error }, { status: 502 });
30
- }
31
-
32
- return NextResponse.json(result.data);
33
- } catch (error) {
34
- console.error("RAG index error:", error);
35
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
36
- }
37
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/rag/search/route.ts DELETED
@@ -1,37 +0,0 @@
1
- /**
2
- * RAG Search Route
3
- *
4
- * POST /api/rag/search
5
- * Search documents using RAG
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
- import { ragSearch } from "@/lib/ai-client";
11
-
12
- export async function POST(request: NextRequest) {
13
- try {
14
- const user = await getCurrentUser(request);
15
- if (!user) {
16
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
17
- }
18
-
19
- const body = await request.json();
20
- const { query, repo_name, limit } = body;
21
-
22
- if (!query) {
23
- return NextResponse.json({ error: "query is required" }, { status: 400 });
24
- }
25
-
26
- const result = await ragSearch(query, repo_name, limit || 10);
27
-
28
- if (!result.success) {
29
- return NextResponse.json({ error: result.error }, { status: 502 });
30
- }
31
-
32
- return NextResponse.json(result.data);
33
- } catch (error) {
34
- console.error("RAG search error:", error);
35
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
36
- }
37
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/rag/suggestions/route.ts DELETED
@@ -1,40 +0,0 @@
1
- /**
2
- * RAG Suggestions Route
3
- *
4
- * GET /api/rag/suggestions
5
- * Get suggested questions for RAG chatbot
6
- */
7
-
8
- import { NextRequest, NextResponse } from "next/server";
9
- import { getCurrentUser } from "@/lib/auth";
10
- import { callAIEngine } from "@/lib/ai-client";
11
-
12
- export async function GET(request: NextRequest) {
13
- try {
14
- const user = await getCurrentUser(request);
15
- if (!user) {
16
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
17
- }
18
-
19
- const { searchParams } = new URL(request.url);
20
- const repoName = searchParams.get("repo_name");
21
-
22
- // Call AI engine with empty body for suggestions
23
- const AI_ENGINE_URL = process.env.AI_ENGINE_URL || "http://localhost:7860";
24
- const url = repoName
25
- ? `${AI_ENGINE_URL}/rag/suggestions?repo_name=${encodeURIComponent(repoName)}`
26
- : `${AI_ENGINE_URL}/rag/suggestions`;
27
-
28
- const response = await fetch(url);
29
-
30
- if (!response.ok) {
31
- return NextResponse.json({ error: "Failed to get suggestions" }, { status: 502 });
32
- }
33
-
34
- const data = await response.json();
35
- return NextResponse.json(data);
36
- } catch (error) {
37
- console.error("RAG suggestions error:", error);
38
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
39
- }
40
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/repositories/contributor/route.ts DELETED
@@ -1,98 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { db } from "@/db";
3
- import { issues, repositories } from "@/db/schema";
4
- import { eq, and, ne, desc, sql, count, max, inArray } from "drizzle-orm";
5
-
6
- // =============================================================================
7
- // GET /api/repositories/contributor - Get repos where user has PRs
8
- // =============================================================================
9
- //
10
- // This is the most complex query in the original Python backend.
11
- // It uses a MongoDB aggregation pipeline to find unique repos where
12
- // the user has contributed PRs but is NOT the owner.
13
- //
14
- // Original Python aggregation pipeline (from routes/repository.py):
15
- //
16
- // pipeline = [
17
- // {"$match": {"authorName": username, "isPR": True}},
18
- // {"$group": {
19
- // "_id": {"repoId": "$repoId", "repoName": "$repoName", "owner": "$owner", "repo": "$repo"},
20
- // "pr_count": {"$sum": 1},
21
- // "last_pr_at": {"$max": "$createdAt"}
22
- // }},
23
- // {"$project": {...}},
24
- // {"$sort": {"pr_count": -1}}
25
- // ]
26
- // contributed_repos = await db.issues.aggregate(pipeline).to_list(1000)
27
- //
28
- // Drizzle SQL equivalent using GROUP BY:
29
- //
30
- export async function GET(request: NextRequest) {
31
- try {
32
- const { searchParams } = new URL(request.url);
33
- const username = searchParams.get("username");
34
- const userId = searchParams.get("userId");
35
-
36
- if (!username || !userId) {
37
- return NextResponse.json(
38
- { error: "username and userId are required" },
39
- { status: 400 }
40
- );
41
- }
42
-
43
- // Step 1: Get user's own repo IDs to exclude
44
- const ownRepos = await db
45
- .select({ id: repositories.id })
46
- .from(repositories)
47
- .where(eq(repositories.userId, userId));
48
-
49
- const ownRepoIds = ownRepos.map(r => r.id);
50
-
51
- // Step 2: Aggregate PRs by repo - equivalent to MongoDB $group
52
- // SQL: SELECT repo_id, repo_name, COUNT(*) as pr_count, MAX(created_at) as last_pr_at
53
- // FROM issues WHERE author_name = ? AND is_pr = 1 GROUP BY repo_id, repo_name, owner, repo
54
- const contributedRepos = await db
55
- .select({
56
- repoId: issues.repoId,
57
- repoName: issues.repoName,
58
- owner: issues.owner,
59
- repo: issues.repo,
60
- prCount: count().as("pr_count"),
61
- lastPrAt: max(issues.createdAt).as("last_pr_at"),
62
- })
63
- .from(issues)
64
- .where(
65
- and(
66
- eq(issues.authorName, username),
67
- eq(issues.isPR, true)
68
- )
69
- )
70
- .groupBy(issues.repoId, issues.repoName, issues.owner, issues.repo)
71
- .orderBy(desc(sql`pr_count`));
72
-
73
- // Step 3: Filter out user's own repos
74
- const result = contributedRepos
75
- .filter(repo => !ownRepoIds.includes(repo.repoId))
76
- .map(repo => ({
77
- repoId: repo.repoId,
78
- repoName: repo.repoName,
79
- owner: repo.owner,
80
- repo: repo.repo,
81
- pr_count: repo.prCount,
82
- last_pr_at: repo.lastPrAt,
83
- role: "contributor",
84
- name: repo.repoName || `${repo.owner}/${repo.repo}`,
85
- }));
86
-
87
- return NextResponse.json(
88
- { repos: result, count: result.length },
89
- { status: 200 }
90
- );
91
- } catch (error) {
92
- console.error("GET /api/repositories/contributor error:", error);
93
- return NextResponse.json(
94
- { error: "Failed to fetch contributor repositories" },
95
- { status: 500 }
96
- );
97
- }
98
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/repositories/route.ts DELETED
@@ -1,122 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { db } from "@/db";
3
- import { repositories, issues, triageData } from "@/db/schema";
4
- import { generateId, now } from "@/lib/utils";
5
- import { eq, and, desc, count, sql } from "drizzle-orm";
6
-
7
- // =============================================================================
8
- // GET /api/repositories - Get user's repositories
9
- // =============================================================================
10
- //
11
- // Python equivalent (from routes/repository.py):
12
- // repos = await db.repositories.find({"userId": user['id']}, {"_id": 0}).to_list(1000)
13
- //
14
- import { getCurrentUser } from "@/lib/auth";
15
-
16
- // ...
17
-
18
- export async function GET(request: NextRequest) {
19
- try {
20
- const { searchParams } = new URL(request.url);
21
- let userId = searchParams.get("userId");
22
-
23
- // If no userId provided, try to get the current authenticated user
24
- if (!userId) {
25
- const currentUser = await getCurrentUser(request);
26
- if (currentUser) {
27
- userId = currentUser.id;
28
- } else {
29
- return NextResponse.json(
30
- { error: "userId is required or you must be logged in" },
31
- { status: 401 }
32
- );
33
- }
34
- }
35
-
36
- const repos = await db
37
- .select()
38
- .from(repositories)
39
- .where(eq(repositories.userId, userId))
40
- .orderBy(desc(repositories.createdAt));
41
-
42
- return NextResponse.json(repos, { status: 200 });
43
- } catch (error) {
44
- console.error("GET /api/repositories error:", error);
45
- return NextResponse.json(
46
- { error: "Failed to fetch repositories" },
47
- { status: 500 }
48
- );
49
- }
50
- }
51
-
52
- // =============================================================================
53
- // POST /api/repositories - Add a new repository
54
- // =============================================================================
55
- //
56
- // Python equivalent (from routes/repository.py):
57
- // repository = Repository(
58
- // githubRepoId=repo_data['id'],
59
- // name=request.repoFullName,
60
- // owner=owner,
61
- // userId=user['id']
62
- // )
63
- // repo_dict = repository.model_dump()
64
- // repo_dict['createdAt'] = repo_dict['createdAt'].isoformat()
65
- // await db.repositories.insert_one(repo_dict)
66
- //
67
- export async function POST(request: NextRequest) {
68
- try {
69
- const body = await request.json();
70
- const { githubRepoId, name, owner, userId } = body;
71
-
72
- // Validate required fields
73
- if (!githubRepoId || !name || !owner || !userId) {
74
- return NextResponse.json(
75
- { error: "Missing required fields: githubRepoId, name, owner, userId" },
76
- { status: 400 }
77
- );
78
- }
79
-
80
- // Check if repository already exists for this user
81
- const existing = await db
82
- .select()
83
- .from(repositories)
84
- .where(
85
- and(
86
- eq(repositories.githubRepoId, githubRepoId),
87
- eq(repositories.userId, userId)
88
- )
89
- )
90
- .limit(1);
91
-
92
- if (existing.length > 0) {
93
- return NextResponse.json(
94
- { error: "Repository already added", repository: existing[0] },
95
- { status: 409 }
96
- );
97
- }
98
-
99
- // Create new repository
100
- const newRepo = {
101
- id: generateId(),
102
- githubRepoId,
103
- name,
104
- owner,
105
- userId,
106
- createdAt: now(),
107
- };
108
-
109
- await db.insert(repositories).values(newRepo);
110
-
111
- return NextResponse.json(
112
- { message: "Repository added!", repository: newRepo },
113
- { status: 201 }
114
- );
115
- } catch (error) {
116
- console.error("POST /api/repositories error:", error);
117
- return NextResponse.json(
118
- { error: "Failed to add repository" },
119
- { status: 500 }
120
- );
121
- }
122
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/spark/badges/user/[username]/route.ts DELETED
@@ -1,16 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { getUserBadges } from "@/lib/db/queries/gamification";
3
-
4
- export async function GET(
5
- request: NextRequest,
6
- context: { params: Promise<{ username: string }> }
7
- ) {
8
- try {
9
- const { username } = await context.params;
10
- const badges = await getUserBadges(username);
11
- return NextResponse.json(badges);
12
- } catch (error) {
13
- console.error("GET /api/spark/badges/user/:username error:", error);
14
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
15
- }
16
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/spark/gamification/calendar/[username]/route.ts DELETED
@@ -1,16 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { getUserCalendar } from "@/lib/db/queries/gamification";
3
-
4
- export async function GET(
5
- request: NextRequest,
6
- context: { params: Promise<{ username: string }> }
7
- ) {
8
- try {
9
- const { username } = await context.params;
10
- const calendar = await getUserCalendar(username);
11
- return NextResponse.json(calendar);
12
- } catch (error) {
13
- console.error("GET /api/spark/gamification/calendar/:username error:", error);
14
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
15
- }
16
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/spark/gamification/streak/[username]/route.ts DELETED
@@ -1,16 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { getUserStreak } from "@/lib/db/queries/gamification";
3
-
4
- export async function GET(
5
- request: NextRequest,
6
- context: { params: Promise<{ username: string }> }
7
- ) {
8
- try {
9
- const { username } = await context.params;
10
- const streak = await getUserStreak(username);
11
- return NextResponse.json(streak);
12
- } catch (error) {
13
- console.error("GET /api/spark/gamification/streak/:username error:", error);
14
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
15
- }
16
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/api/triage/route.ts DELETED
@@ -1,181 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { db } from "@/db";
3
- import { issues, triageData } from "@/db/schema";
4
- import { requireAuth } from "@/lib/auth";
5
- import { generateId, now } from "@/lib/utils";
6
- import { eq } from "drizzle-orm";
7
-
8
- // OpenRouter API for AI classification
9
- const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY!;
10
-
11
- /**
12
- * Classifications matching the Python backend
13
- */
14
- const CLASSIFICATIONS = [
15
- "CRITICAL_BUG", "BUG", "FEATURE_REQUEST", "QUESTION",
16
- "DOCS", "DUPLICATE", "NEEDS_INFO", "SPAM"
17
- ] as const;
18
-
19
- const SENTIMENTS = ["POSITIVE", "NEUTRAL", "NEGATIVE", "FRUSTRATED"] as const;
20
-
21
- /**
22
- * POST /api/triage
23
- * Classify an issue using AI.
24
- *
25
- * Request body: { issueId: string }
26
- */
27
- export async function POST(request: NextRequest) {
28
- const { user, error } = await requireAuth(request);
29
- if (error) return error;
30
-
31
- try {
32
- const body = await request.json();
33
- const { issueId } = body;
34
-
35
- if (!issueId) {
36
- return NextResponse.json(
37
- { error: "issueId is required" },
38
- { status: 400 }
39
- );
40
- }
41
-
42
- // Fetch the issue
43
- const issueRecords = await db
44
- .select()
45
- .from(issues)
46
- .where(eq(issues.id, issueId))
47
- .limit(1);
48
-
49
- if (issueRecords.length === 0) {
50
- return NextResponse.json(
51
- { error: "Issue not found" },
52
- { status: 404 }
53
- );
54
- }
55
-
56
- const issue = issueRecords[0];
57
-
58
- // Check if already triaged
59
- const existingTriage = await db
60
- .select()
61
- .from(triageData)
62
- .where(eq(triageData.issueId, issueId))
63
- .limit(1);
64
-
65
- if (existingTriage.length > 0) {
66
- return NextResponse.json({
67
- message: "Issue already triaged",
68
- triage: existingTriage[0],
69
- });
70
- }
71
-
72
- // Build AI prompt for classification
73
- const prompt = `Analyze this GitHub issue and provide a classification.
74
-
75
- Title: ${issue.title}
76
- Body: ${issue.body || "(empty)"}
77
-
78
- Respond with valid JSON only:
79
- {
80
- "classification": "BUG" | "CRITICAL_BUG" | "FEATURE_REQUEST" | "QUESTION" | "DOCS" | "DUPLICATE" | "NEEDS_INFO" | "SPAM",
81
- "sentiment": "POSITIVE" | "NEUTRAL" | "NEGATIVE" | "FRUSTRATED",
82
- "summary": "One-line summary of the issue",
83
- "suggestedLabel": "Suggested GitHub label (lowercase, hyphenated)"
84
- }`;
85
-
86
- // Call OpenRouter API
87
- const aiResponse = await fetch(
88
- "https://openrouter.ai/api/v1/chat/completions",
89
- {
90
- method: "POST",
91
- headers: {
92
- Authorization: `Bearer ${OPENROUTER_API_KEY}`,
93
- "Content-Type": "application/json",
94
- },
95
- body: JSON.stringify({
96
- model: "anthropic/claude-3-haiku",
97
- messages: [{ role: "user", content: prompt }],
98
- max_tokens: 500,
99
- }),
100
- }
101
- );
102
-
103
- const aiData = await aiResponse.json();
104
- const aiContent = aiData.choices?.[0]?.message?.content || "{}";
105
-
106
- // Parse AI response
107
- let classification = "NEEDS_INFO";
108
- let sentiment = "NEUTRAL";
109
- let summary = issue.title;
110
- let suggestedLabel = "needs-triage";
111
-
112
- try {
113
- const parsed = JSON.parse(aiContent);
114
- classification = CLASSIFICATIONS.includes(parsed.classification)
115
- ? parsed.classification
116
- : "NEEDS_INFO";
117
- sentiment = SENTIMENTS.includes(parsed.sentiment)
118
- ? parsed.sentiment
119
- : "NEUTRAL";
120
- summary = parsed.summary || issue.title;
121
- suggestedLabel = parsed.suggestedLabel || "needs-triage";
122
- } catch {
123
- console.error("Failed to parse AI response:", aiContent);
124
- }
125
-
126
- // Save triage data
127
- const triage = {
128
- id: generateId(),
129
- issueId,
130
- classification,
131
- summary,
132
- suggestedLabel,
133
- sentiment,
134
- analyzedAt: now(),
135
- };
136
-
137
- await db.insert(triageData).values(triage);
138
-
139
- return NextResponse.json({
140
- message: "Issue triaged successfully",
141
- triage,
142
- });
143
- } catch (error) {
144
- console.error("POST /api/triage error:", error);
145
- return NextResponse.json(
146
- { error: "Failed to triage issue" },
147
- { status: 500 }
148
- );
149
- }
150
- }
151
-
152
- /**
153
- * GET /api/triage?issueId=xxx
154
- * Get triage data for an issue.
155
- */
156
- export async function GET(request: NextRequest) {
157
- const { searchParams } = new URL(request.url);
158
- const issueId = searchParams.get("issueId");
159
-
160
- if (!issueId) {
161
- return NextResponse.json(
162
- { error: "issueId is required" },
163
- { status: 400 }
164
- );
165
- }
166
-
167
- const triageRecords = await db
168
- .select()
169
- .from(triageData)
170
- .where(eq(triageData.issueId, issueId))
171
- .limit(1);
172
-
173
- if (triageRecords.length === 0) {
174
- return NextResponse.json(
175
- { error: "Triage data not found" },
176
- { status: 404 }
177
- );
178
- }
179
-
180
- return NextResponse.json(triageRecords[0]);
181
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/favicon.ico DELETED
Binary file (25.9 kB)
 
src/app/globals.css DELETED
@@ -1,26 +0,0 @@
1
- @import "tailwindcss";
2
-
3
- :root {
4
- --background: #ffffff;
5
- --foreground: #171717;
6
- }
7
-
8
- @theme inline {
9
- --color-background: var(--background);
10
- --color-foreground: var(--foreground);
11
- --font-sans: var(--font-geist-sans);
12
- --font-mono: var(--font-geist-mono);
13
- }
14
-
15
- @media (prefers-color-scheme: dark) {
16
- :root {
17
- --background: #0a0a0a;
18
- --foreground: #ededed;
19
- }
20
- }
21
-
22
- body {
23
- background: var(--background);
24
- color: var(--foreground);
25
- font-family: Arial, Helvetica, sans-serif;
26
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/layout.tsx DELETED
@@ -1,34 +0,0 @@
1
- import type { Metadata } from "next";
2
- import { Geist, Geist_Mono } from "next/font/google";
3
- import "./globals.css";
4
-
5
- const geistSans = Geist({
6
- variable: "--font-geist-sans",
7
- subsets: ["latin"],
8
- });
9
-
10
- const geistMono = Geist_Mono({
11
- variable: "--font-geist-mono",
12
- subsets: ["latin"],
13
- });
14
-
15
- export const metadata: Metadata = {
16
- title: "Create Next App",
17
- description: "Generated by create next app",
18
- };
19
-
20
- export default function RootLayout({
21
- children,
22
- }: Readonly<{
23
- children: React.ReactNode;
24
- }>) {
25
- return (
26
- <html lang="en">
27
- <body
28
- className={`${geistSans.variable} ${geistMono.variable} antialiased`}
29
- >
30
- {children}
31
- </body>
32
- </html>
33
- );
34
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/page.tsx DELETED
@@ -1,65 +0,0 @@
1
- import Image from "next/image";
2
-
3
- export default function Home() {
4
- return (
5
- <div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
6
- <main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
7
- <Image
8
- className="dark:invert"
9
- src="/next.svg"
10
- alt="Next.js logo"
11
- width={100}
12
- height={20}
13
- priority
14
- />
15
- <div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
16
- <h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
17
- To get started, edit the page.tsx file.
18
- </h1>
19
- <p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
20
- Looking for a starting point or more instructions? Head over to{" "}
21
- <a
22
- href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
23
- className="font-medium text-zinc-950 dark:text-zinc-50"
24
- >
25
- Templates
26
- </a>{" "}
27
- or the{" "}
28
- <a
29
- href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
30
- className="font-medium text-zinc-950 dark:text-zinc-50"
31
- >
32
- Learning
33
- </a>{" "}
34
- center.
35
- </p>
36
- </div>
37
- <div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
38
- <a
39
- className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
40
- href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
41
- target="_blank"
42
- rel="noopener noreferrer"
43
- >
44
- <Image
45
- className="dark:invert"
46
- src="/vercel.svg"
47
- alt="Vercel logomark"
48
- width={16}
49
- height={16}
50
- />
51
- Deploy Now
52
- </a>
53
- <a
54
- className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
55
- href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
56
- target="_blank"
57
- rel="noopener noreferrer"
58
- >
59
- Documentation
60
- </a>
61
- </div>
62
- </main>
63
- </div>
64
- );
65
- }