File size: 4,536 Bytes
14a0342
 
 
 
cd4ab7b
14a0342
 
 
 
78e893f
 
 
 
 
 
 
14a0342
 
 
 
 
 
 
 
 
 
 
 
e9b2cbc
78e893f
 
 
 
 
 
 
 
 
e9b2cbc
78e893f
 
 
2cc8683
e9b2cbc
 
2cc8683
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86c1aaf
 
2cc8683
 
 
 
78e893f
 
 
 
2cc8683
14a0342
 
 
 
 
 
 
 
 
 
 
 
cd4ab7b
 
 
 
 
14a0342
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
 * Sync Trigger API Route
 * 
 * POST /api/sync/run - Manually trigger a full GitHub sync
 * Supports role-based sync with ETag caching for efficient API usage
 */

import { NextRequest, NextResponse } from "next/server";
import { getCurrentUser } from "@/lib/auth";
import { runMaintainerSync, runContributorSync, syncContributorPRsDirect, reconcileOpenTriageIssue1 } from "@/lib/sync/github-sync";

const SYNC_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes

// Layer 2 Defense: In-memory set to prevent race conditions
// This provides instant protection before database queries
const activeSyncs = new Set<string>();

export async function POST(request: NextRequest) {
    try {
        const user = await getCurrentUser(request);
        if (!user) {
            return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
        }

        if (!user.githubAccessToken) {
            return NextResponse.json({ error: "GitHub access token not found" }, { status: 400 });
        }

        // Circuit Breaker: In-memory check (prevents concurrent syncs per user)
        if (activeSyncs.has(user.id)) {
            console.log(`[SyncRun] User ${user.id} sync blocked by in-memory guard`);
            return NextResponse.json({
                error: "Sync already in progress",
                status: "SYNCING",
                message: "Please wait for the current sync to complete"
            }, { status: 429 });
        }

        // Add to in-memory set BEFORE starting sync
        activeSyncs.add(user.id);
        console.log(`[SyncRun] Added user ${user.id} to activeSyncs. Size: ${activeSyncs.size}`);

        try {
            console.log(`[SyncRun] Starting sync for user ${user.id}`);

            // Determine sync type based on user role
            const userRole = user.role?.toUpperCase();
            let stats;
            let directSync = null;

            if (userRole === "MAINTAINER") {
                // Maintainers sync all open issues/PRs
                stats = await runMaintainerSync(user.id, user.githubAccessToken);
            } else {
                // Contributors sync only their authored PRs
                // First: Run search-based sync (may have indexing delay)
                stats = await runContributorSync(user.id, user.username, user.githubAccessToken);

                // Second: Direct fetch from repos where user has existing PRs (bypasses search delay)
                directSync = await syncContributorPRsDirect(user.id, user.username, user.githubAccessToken);
            }

            // Always reconcile the critical openTriage#1 issue to ensure immediate state sync
            const reconcileResult = await reconcileOpenTriageIssue1(user.githubAccessToken);

            console.log(`[SyncRun] Sync completed for user ${user.id}`);

            return NextResponse.json({
                success: true,
                message: "Sync completed",
                role: userRole,
                stats,
                directSync,  // Include direct sync results for contributors
                reconcile: {
                    openTriageIssue1: reconcileResult,
                },
                nextSyncIntervalMs: SYNC_INTERVAL_MS,
            });

        } catch (syncError: any) {
            console.error(`[SyncRun] Error syncing for user ${user.id}:`, syncError);

            return NextResponse.json({
                error: "Sync failed",
                message: syncError.message || "Internal server error"
            }, { status: 500 });
        } finally {
            // CRITICAL: Always remove from in-memory set, even on error
            activeSyncs.delete(user.id);
            console.log(`[SyncRun] Removed user ${user.id} from activeSyncs. Size: ${activeSyncs.size}`);
        }
    } catch (error) {
        console.error("POST /api/sync/run error:", error);
        return NextResponse.json({ error: "Internal server error" }, { status: 500 });
    }
}

export async function GET(request: NextRequest) {
    // Return sync configuration info
    return NextResponse.json({
        syncIntervalMs: SYNC_INTERVAL_MS,
        syncIntervalMinutes: SYNC_INTERVAL_MS / 60000,
        description: "GitHub sync runs every 5 minutes. POST to trigger manually.",
        features: [
            "ETag caching - skips sync if no changes (304 Not Modified)",
            "State reconciliation - marks issues as closed if not in open list",
            "Role-based filtering - Contributors see only their authored PRs",
        ],
    });
}