KrishnaCosmic commited on
Commit
8a2fb1b
·
1 Parent(s): db8667c

apply changes

Browse files
src/app/api/explore/tickets/route.ts ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Explore Tickets API Route (Renamed from Discover Issues)
3
+ *
4
+ * GET /api/explore/tickets
5
+ * Search GitHub for open source issues using authenticated requests
6
+ * to avoid rate limits
7
+ *
8
+ * RENAMED TO AVOID ADBLOCKER DETECTION
9
+ * Old: /api/discover/issues
10
+ * New: /api/explore/tickets
11
+ */
12
+
13
+ import { NextRequest, NextResponse } from "next/server";
14
+ import { getCurrentUser } from "@/lib/auth";
15
+ import { Octokit } from "@octokit/rest";
16
+
17
+ export async function GET(request: NextRequest) {
18
+ try {
19
+ const user = await getCurrentUser(request);
20
+
21
+ const { searchParams } = new URL(request.url);
22
+ const labels = searchParams.get("labels") || "good first issue";
23
+ const language = searchParams.get("language") || "";
24
+ const sort = searchParams.get("sort") || "created";
25
+ const perPage = parseInt(searchParams.get("per_page") || "30");
26
+
27
+ // Use user's GitHub token if available, otherwise use app token
28
+ const token = user?.githubAccessToken || process.env.GITHUB_TOKEN;
29
+
30
+ if (!token) {
31
+ return NextResponse.json({
32
+ error: "No GitHub token available",
33
+ items: [],
34
+ total_count: 0
35
+ }, { status: 200 });
36
+ }
37
+
38
+ const octokit = new Octokit({ auth: token });
39
+
40
+ // Build search query
41
+ let query = "is:issue is:open";
42
+
43
+ // Add labels (support multiple labels)
44
+ const labelList = labels.split(",").filter(l => l.trim());
45
+ for (const label of labelList) {
46
+ query += ` label:"${label.trim()}"`;
47
+ }
48
+
49
+ // Add language filter
50
+ if (language && language !== "all") {
51
+ query += ` language:${language}`;
52
+ }
53
+
54
+ // Map sort options
55
+ const sortMap: Record<string, "created" | "updated" | "comments" | "reactions-+1"> = {
56
+ created: "created",
57
+ updated: "updated",
58
+ comments: "comments",
59
+ reactions: "reactions-+1"
60
+ };
61
+
62
+ const response = await octokit.search.issuesAndPullRequests({
63
+ q: query,
64
+ sort: sortMap[sort] || "created",
65
+ order: "desc",
66
+ per_page: Math.min(perPage, 100),
67
+ });
68
+
69
+ // Add rate limit info to response
70
+ const rateLimit = {
71
+ remaining: response.headers["x-ratelimit-remaining"],
72
+ limit: response.headers["x-ratelimit-limit"],
73
+ reset: response.headers["x-ratelimit-reset"],
74
+ };
75
+
76
+ return NextResponse.json({
77
+ items: response.data.items,
78
+ total_count: response.data.total_count,
79
+ rate_limit: rateLimit,
80
+ });
81
+
82
+ } catch (error: any) {
83
+ console.error("Explore tickets error:", error);
84
+
85
+ // Handle rate limit specifically
86
+ if (error.status === 403) {
87
+ return NextResponse.json({
88
+ error: "Rate limit exceeded. Please try again later.",
89
+ items: [],
90
+ total_count: 0,
91
+ rate_limited: true
92
+ }, { status: 200 }); // Return 200 so frontend handles gracefully
93
+ }
94
+
95
+ return NextResponse.json({
96
+ error: error.message || "Failed to fetch tickets",
97
+ items: [],
98
+ total_count: 0
99
+ }, { status: 200 });
100
+ }
101
+ }
src/app/api/realtime/connect/route.ts ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Real-time Connection API Route (Renamed from WebSocket)
3
+ *
4
+ * GET /api/realtime/connect
5
+ * Establishes a Server-Sent Events (SSE) connection for real-time message delivery
6
+ *
7
+ * RENAMED TO AVOID ADBLOCKER DETECTION
8
+ * Old: /api/messaging/ws
9
+ * New: /api/realtime/connect
10
+ */
11
+
12
+ import { NextRequest } from 'next/server';
13
+ import { getCurrentUser } from '@/lib/auth';
14
+ import { realtimeMessaging } from '@/lib/realtime-messaging';
15
+
16
+ interface WebSocketMessage {
17
+ type: string;
18
+ [key: string]: any;
19
+ }
20
+
21
+ export async function GET(request: NextRequest) {
22
+ try {
23
+ const user = await getCurrentUser(request);
24
+ if (!user) {
25
+ return new Response(JSON.stringify({ error: 'Unauthorized' }), {
26
+ status: 401,
27
+ headers: { 'Content-Type': 'application/json' },
28
+ });
29
+ }
30
+
31
+ // Setup SSE connection
32
+ const encoder = new TextEncoder();
33
+ let isConnected = true;
34
+
35
+ const customReadable = new ReadableStream({
36
+ start(controller) {
37
+ // Send initial connection message
38
+ const message = `data: ${JSON.stringify({
39
+ type: 'connected',
40
+ userId: user.id,
41
+ timestamp: new Date().toISOString(),
42
+ })}\n\n`;
43
+
44
+ controller.enqueue(encoder.encode(message));
45
+
46
+ // Register this connection with the realtime service
47
+ const unsubscribe = realtimeMessaging.registerConnection(user.id, (event) => {
48
+ if (isConnected) {
49
+ const eventMessage = `data: ${JSON.stringify(event)}\n\n`;
50
+ try {
51
+ controller.enqueue(encoder.encode(eventMessage));
52
+ } catch (error) {
53
+ console.error('Error sending SSE event:', error);
54
+ isConnected = false;
55
+ controller.close();
56
+ }
57
+ }
58
+ });
59
+
60
+ // Cleanup on connection close
61
+ request.signal?.addEventListener('abort', () => {
62
+ isConnected = false;
63
+ unsubscribe();
64
+ controller.close();
65
+ });
66
+ },
67
+ });
68
+
69
+ return new Response(customReadable, {
70
+ headers: {
71
+ 'Content-Type': 'text/event-stream',
72
+ 'Cache-Control': 'no-cache',
73
+ 'Connection': 'keep-alive',
74
+ 'X-Accel-Buffering': 'no',
75
+ },
76
+ });
77
+ } catch (error) {
78
+ console.error('Real-time connection error:', error);
79
+ return new Response(JSON.stringify({ error: 'Internal server error' }), {
80
+ status: 500,
81
+ headers: { 'Content-Type': 'application/json' },
82
+ });
83
+ }
84
+ }