KrishnaCosmic commited on
Commit
78e893f
·
1 Parent(s): e19cdf4

apply changes

Browse files
src/app/api/auth/github/callback/route.ts CHANGED
@@ -57,7 +57,6 @@ export async function GET(request: NextRequest) {
57
  const githubUser = await userResponse.json();
58
 
59
  // Check if user exists
60
- // TODO: After Turso migration, add syncStatus, lastSyncAt, syncError to this select
61
  const existingUsers = await db
62
  .select({
63
  id: users.id,
@@ -66,6 +65,9 @@ export async function GET(request: NextRequest) {
66
  avatarUrl: users.avatarUrl,
67
  role: users.role,
68
  githubAccessToken: users.githubAccessToken,
 
 
 
69
  createdAt: users.createdAt,
70
  updatedAt: users.updatedAt,
71
  })
 
57
  const githubUser = await userResponse.json();
58
 
59
  // Check if user exists
 
60
  const existingUsers = await db
61
  .select({
62
  id: users.id,
 
65
  avatarUrl: users.avatarUrl,
66
  role: users.role,
67
  githubAccessToken: users.githubAccessToken,
68
+ syncStatus: users.syncStatus,
69
+ lastSyncAt: users.lastSyncAt,
70
+ syncError: users.syncError,
71
  createdAt: users.createdAt,
72
  updatedAt: users.updatedAt,
73
  })
src/app/api/sync/run/route.ts CHANGED
@@ -7,10 +7,16 @@
7
 
8
  import { NextRequest, NextResponse } from "next/server";
9
  import { getCurrentUser } from "@/lib/auth";
10
- import { runFullSync, runMaintainerSync, runContributorSync, reconcileOpenTriageIssue1, syncContributorPRsDirect, SYNC_INTERVAL_MS } from "@/lib/sync/github-sync";
11
  import { db } from "@/db";
12
  import { users } from "@/db/schema";
13
  import { eq } from "drizzle-orm";
 
 
 
 
 
 
 
14
 
15
  export async function POST(request: NextRequest) {
16
  try {
@@ -23,11 +29,22 @@ export async function POST(request: NextRequest) {
23
  return NextResponse.json({ error: "GitHub access token not found" }, { status: 400 });
24
  }
25
 
26
- // TODO: Uncomment after Turso migration
27
- // Circuit breaker logic commented out until sync_status column exists in production
28
- /*
29
- // Get current user record to check sync status
30
- const userRecord = await db.select()
 
 
 
 
 
 
 
 
 
 
 
31
  .from(users)
32
  .where(eq(users.id, user.id))
33
  .limit(1);
@@ -40,7 +57,7 @@ export async function POST(request: NextRequest) {
40
 
41
  // Circuit breaker: Check if sync already in progress
42
  if (currentSyncStatus === 'SYNCING') {
43
- console.log(`[SyncRun] User ${user.id} sync already in progress`);
44
  return NextResponse.json({
45
  error: "Sync already in progress",
46
  status: "SYNCING",
@@ -57,14 +74,17 @@ export async function POST(request: NextRequest) {
57
  }, { status: 202 });
58
  }
59
 
60
- // Set status to SYNCING before starting
 
 
 
 
61
  await db.update(users)
62
  .set({
63
  syncStatus: 'SYNCING',
64
  syncError: null
65
  })
66
  .where(eq(users.id, user.id));
67
- */
68
 
69
  console.log(`[SyncRun] Starting sync for user ${user.id}`);
70
 
@@ -89,15 +109,12 @@ export async function POST(request: NextRequest) {
89
  // Always reconcile the critical openTriage#1 issue to ensure immediate state sync
90
  const reconcileResult = await reconcileOpenTriageIssue1(user.githubAccessToken);
91
 
92
- // TODO: Uncomment after Turso migration
93
- /*
94
  // Mark sync as COMPLETED
95
  await db.update(users).set({
96
  syncStatus: 'COMPLETED',
97
  lastSyncAt: new Date().toISOString(),
98
  syncError: null
99
  }).where(eq(users.id, user.id));
100
- */
101
 
102
  console.log(`[SyncRun] Sync completed for user ${user.id}`);
103
 
@@ -116,21 +133,20 @@ export async function POST(request: NextRequest) {
116
  } catch (syncError: any) {
117
  console.error(`[SyncRun] Error syncing for user ${user.id}:`, syncError);
118
 
119
- // TODO: Uncomment after Turso migration
120
- /*
121
- // Mark sync as FAILED
122
  await db.update(users).set({
123
  syncStatus: 'FAILED',
124
  syncError: syncError.message || 'Unknown error'
125
  }).where(eq(users.id, user.id));
126
- */
127
-
128
- console.error(`[SyncRun] Sync failed for user ${user.id}:`, syncError);
129
 
130
  return NextResponse.json({
131
  error: "Sync failed",
132
  message: syncError.message || "Internal server error"
133
  }, { status: 500 });
 
 
 
 
134
  }
135
  } catch (error) {
136
  console.error("POST /api/sync/run error:", error);
 
7
 
8
  import { NextRequest, NextResponse } from "next/server";
9
  import { getCurrentUser } from "@/lib/auth";
 
10
  import { db } from "@/db";
11
  import { users } from "@/db/schema";
12
  import { eq } from "drizzle-orm";
13
+ import { runMaintainerSync, runContributorSync, syncContributorPRsDirect, reconcileOpenTriageIssue1 } from "@/lib/sync/github-sync";
14
+
15
+ const SYNC_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
16
+
17
+ // Layer 2 Defense: In-memory set to prevent race conditions
18
+ // This provides instant protection before database queries
19
+ const activeSyncs = new Set<string>();
20
 
21
  export async function POST(request: NextRequest) {
22
  try {
 
29
  return NextResponse.json({ error: "GitHub access token not found" }, { status: 400 });
30
  }
31
 
32
+ // Layer 2 Defense: In-memory check (FAST - prevents race conditions)
33
+ if (activeSyncs.has(user.id)) {
34
+ console.log(`[SyncRun] User ${user.id} sync blocked by in-memory guard`);
35
+ return NextResponse.json({
36
+ error: "Sync already in progress",
37
+ status: "SYNCING",
38
+ message: "Please wait for the current sync to complete"
39
+ }, { status: 429 });
40
+ }
41
+
42
+ // Layer 1 Defense: Database check (PERSISTENT - survives restarts)
43
+ const userRecord = await db.select({
44
+ id: users.id,
45
+ syncStatus: users.syncStatus,
46
+ lastSyncAt: users.lastSyncAt,
47
+ })
48
  .from(users)
49
  .where(eq(users.id, user.id))
50
  .limit(1);
 
57
 
58
  // Circuit breaker: Check if sync already in progress
59
  if (currentSyncStatus === 'SYNCING') {
60
+ console.log(`[SyncRun] User ${user.id} sync already in progress (DB status)`);
61
  return NextResponse.json({
62
  error: "Sync already in progress",
63
  status: "SYNCING",
 
74
  }, { status: 202 });
75
  }
76
 
77
+ // Add to in-memory set FIRST (before DB update)
78
+ activeSyncs.add(user.id);
79
+ console.log(`[SyncRun] Added user ${user.id} to activeSyncs. Size: ${activeSyncs.size}`);
80
+
81
+ // Set database status to SYNCING
82
  await db.update(users)
83
  .set({
84
  syncStatus: 'SYNCING',
85
  syncError: null
86
  })
87
  .where(eq(users.id, user.id));
 
88
 
89
  console.log(`[SyncRun] Starting sync for user ${user.id}`);
90
 
 
109
  // Always reconcile the critical openTriage#1 issue to ensure immediate state sync
110
  const reconcileResult = await reconcileOpenTriageIssue1(user.githubAccessToken);
111
 
 
 
112
  // Mark sync as COMPLETED
113
  await db.update(users).set({
114
  syncStatus: 'COMPLETED',
115
  lastSyncAt: new Date().toISOString(),
116
  syncError: null
117
  }).where(eq(users.id, user.id));
 
118
 
119
  console.log(`[SyncRun] Sync completed for user ${user.id}`);
120
 
 
133
  } catch (syncError: any) {
134
  console.error(`[SyncRun] Error syncing for user ${user.id}:`, syncError);
135
 
136
+ // Mark sync as FAILED with error message
 
 
137
  await db.update(users).set({
138
  syncStatus: 'FAILED',
139
  syncError: syncError.message || 'Unknown error'
140
  }).where(eq(users.id, user.id));
 
 
 
141
 
142
  return NextResponse.json({
143
  error: "Sync failed",
144
  message: syncError.message || "Internal server error"
145
  }, { status: 500 });
146
+ } finally {
147
+ // CRITICAL: Always remove from in-memory set, even on error
148
+ activeSyncs.delete(user.id);
149
+ console.log(`[SyncRun] Removed user ${user.id} from activeSyncs. Size: ${activeSyncs.size}`);
150
  }
151
  } catch (error) {
152
  console.error("POST /api/sync/run error:", error);
src/app/api/sync/status/route.ts CHANGED
@@ -1,25 +1,51 @@
1
  /**
2
- * TODO: Re-enable after Turso migration
3
  *
4
- * This endpoint is temporarily disabled because it queries sync_status columns
5
- * that don't exist in the production Turso database yet.
6
- *
7
- * To re-enable:
8
- * 1. Run migration: add_sync_status.sql on Turso
9
- * 2. Uncomment the code below
10
- * 3. Deploy
11
  */
12
 
13
- // Endpoint disabled until migration
14
- export function GET() {
15
- return new Response(
16
- JSON.stringify({
17
- error: "Endpoint temporarily disabled",
18
- message: "Sync status tracking not yet available"
19
- }),
20
- {
21
- status: 503,
22
- headers: { 'Content-Type': 'application/json' }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  }
24
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  }
 
1
  /**
2
+ * Sync Status API Route
3
  *
4
+ * GET /api/sync/status - Check current sync status for the authenticated user
5
+ * Used by frontend to poll sync progress without triggering new syncs
 
 
 
 
 
6
  */
7
 
8
+ import { NextRequest, NextResponse } from "next/server";
9
+ import { getCurrentUser } from "@/lib/auth";
10
+ import { db } from "@/db";
11
+ import { users } from "@/db/schema";
12
+ import { eq } from "drizzle-orm";
13
+
14
+ export async function GET(request: NextRequest) {
15
+ try {
16
+ const user = await getCurrentUser(request);
17
+ if (!user) {
18
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
19
+ }
20
+
21
+ // Get user's sync status
22
+ const userRecord = await db.select({
23
+ syncStatus: users.syncStatus,
24
+ lastSyncAt: users.lastSyncAt,
25
+ syncError: users.syncError,
26
+ })
27
+ .from(users)
28
+ .where(eq(users.id, user.id))
29
+ .limit(1);
30
+
31
+ if (!userRecord || userRecord.length === 0) {
32
+ return NextResponse.json({ error: "User not found" }, { status: 404 });
33
  }
34
+
35
+ const { syncStatus, lastSyncAt, syncError } = userRecord[0];
36
+
37
+ return NextResponse.json({
38
+ status: syncStatus || 'IDLE',
39
+ lastSyncAt: lastSyncAt || null,
40
+ error: syncError || null,
41
+ isIdle: syncStatus === 'IDLE' || syncStatus === 'COMPLETED',
42
+ isSyncing: syncStatus === 'SYNCING',
43
+ isPending: syncStatus === 'PENDING',
44
+ hasFailed: syncStatus === 'FAILED',
45
+ });
46
+
47
+ } catch (error) {
48
+ console.error("GET /api/sync/status error:", error);
49
+ return NextResponse.json({ error: "Internal server error" }, { status: 500 });
50
+ }
51
  }
src/db/schema.ts CHANGED
@@ -39,11 +39,10 @@ export const users = sqliteTable("users", {
39
  role: text("role"), // email: text("email"),
40
  githubAccessToken: text("github_access_token"),
41
 
42
- // TODO: Uncomment after running Turso migration (add_sync_status.sql)
43
- // Sync status tracking (Phase 2)
44
- // syncStatus: text("sync_status").default("IDLE"), // IDLE | PENDING | SYNCING | COMPLETED | FAILED
45
- // lastSyncAt: text("last_sync_at"), // ISO timestamp
46
- // syncError: text("sync_error"), // Error message if failed
47
 
48
  createdAt: text("created_at").notNull(),
49
  updatedAt: text("updated_at").notNull(),
 
39
  role: text("role"), // email: text("email"),
40
  githubAccessToken: text("github_access_token"),
41
 
42
+ // Sync status tracking (Phase 2) - Re-enabled after Turso migration
43
+ syncStatus: text("sync_status").default("IDLE"), // IDLE | PENDING | SYNCING | COMPLETED | FAILED
44
+ lastSyncAt: text("last_sync_at"), // ISO timestamp
45
+ syncError: text("sync_error"), // Error message if failed
 
46
 
47
  createdAt: text("created_at").notNull(),
48
  updatedAt: text("updated_at").notNull(),
src/lib/auth.ts CHANGED
@@ -66,7 +66,6 @@ export async function getCurrentUser(request: NextRequest) {
66
  console.log("[getCurrentUser] Token verified, user_id:", payload.user_id);
67
 
68
  // Fetch full user from database
69
- // TODO: After Turso migration, re-add syncStatus, lastSyncAt, syncError to this select
70
  const userRecords = await db
71
  .select({
72
  id: users.id,
@@ -75,6 +74,9 @@ export async function getCurrentUser(request: NextRequest) {
75
  avatarUrl: users.avatarUrl,
76
  role: users.role,
77
  githubAccessToken: users.githubAccessToken,
 
 
 
78
  createdAt: users.createdAt,
79
  updatedAt: users.updatedAt,
80
  })
 
66
  console.log("[getCurrentUser] Token verified, user_id:", payload.user_id);
67
 
68
  // Fetch full user from database
 
69
  const userRecords = await db
70
  .select({
71
  id: users.id,
 
74
  avatarUrl: users.avatarUrl,
75
  role: users.role,
76
  githubAccessToken: users.githubAccessToken,
77
+ syncStatus: users.syncStatus,
78
+ lastSyncAt: users.lastSyncAt,
79
+ syncError: users.syncError,
80
  createdAt: users.createdAt,
81
  updatedAt: users.updatedAt,
82
  })