Skip to content

Commit 737f734

Browse files
Merge pull request #103 from Shashank0701-byte/coderabbitai/autofix/30bfc16
fix: CodeRabbit auto-fixes for PR #98
2 parents 30bfc16 + 4348409 commit 737f734

File tree

4 files changed

+98
-33
lines changed

4 files changed

+98
-33
lines changed

app/api/interview/[id]/chaos-timeout/route.ts

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,51 @@
11
import { NextRequest, NextResponse } from 'next/server';
22
import dbConnect from '@/src/lib/db/mongoose';
33
import InterviewSession, { IConstraintChange } from '@/src/lib/db/models/InterviewSession';
4+
import User from '@/src/lib/db/models/User';
5+
import { getAuthenticatedUser } from '@/src/lib/firebase/firebaseAdmin';
46

57
export async function POST(
68
req: NextRequest,
79
context: { params: Promise<{ id: string }> }
810
) {
911
try {
10-
await dbConnect();
1112
const { id } = await context.params;
12-
const { type, constraintId } = await req.json();
13+
const body = await req.json();
14+
15+
// Validate request body
16+
const { type, constraintId } = body;
17+
if (!type || (type !== 'warning' && type !== 'penalty')) {
18+
return new Response(JSON.stringify({ error: 'Invalid type. Must be "warning" or "penalty".' }), {
19+
status: 400,
20+
headers: { 'Content-Type': 'application/json' }
21+
});
22+
}
23+
if (!constraintId || (typeof constraintId !== 'string' && typeof constraintId !== 'number') || constraintId === '') {
24+
return new Response(JSON.stringify({ error: 'Invalid constraintId. Must be a non-empty string or number.' }), {
25+
status: 400,
26+
headers: { 'Content-Type': 'application/json' }
27+
});
28+
}
29+
30+
// Authenticate user
31+
const authHeader = req.headers.get('Authorization');
32+
const authenticatedUser = await getAuthenticatedUser(authHeader);
33+
34+
if (!authenticatedUser) {
35+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
36+
}
37+
38+
await dbConnect();
39+
40+
const user = await User.findOne({ firebaseUid: authenticatedUser.uid });
41+
if (!user) {
42+
return NextResponse.json({ error: 'User not found' }, { status: 404 });
43+
}
1344

14-
// 1. Fetch Session
15-
const session = await InterviewSession.findOne({ id });
45+
// 1. Fetch Session with ownership check
46+
const session = await InterviewSession.findOne({ id, userId: user._id });
1647
if (!session) {
17-
return NextResponse.json({ error: 'Session not found' }, { status: 404 });
48+
return NextResponse.json({ error: 'Session not found or access denied' }, { status: 403 });
1849
}
1950
if (session.status !== 'in_progress') {
2051
return NextResponse.json({ success: true, warning: 'Session is no longer in progress' });
@@ -66,4 +97,4 @@ export async function POST(
6697
console.error('Chaos Timeout Error:', error);
6798
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
6899
}
69-
}
100+
}

components/canvas/DesignCanvas.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,13 @@ export function DesignCanvas({
187187
const [targetRps, setTargetRps] = useState(10000);
188188
const simulationMetrics = useSimulationEngine(nodes, connections, targetRps, isSimulationRunning);
189189

190+
// Stop simulation when canvas becomes read-only
191+
useEffect(() => {
192+
if (readOnly && isSimulationRunning) {
193+
setIsSimulationRunning(false);
194+
}
195+
}, [readOnly, isSimulationRunning]);
196+
190197
// Selection state
191198
const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
192199
const [selectedConnectionId, setSelectedConnectionId] = useState<string | null>(null);
@@ -1190,4 +1197,4 @@ export function DesignCanvas({
11901197
</div>
11911198
</main>
11921199
);
1193-
}
1200+
}

components/interview/ChaosTimer.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ export function ChaosTimer({ constraint, onTimeout }: ChaosTimerProps) {
4545
const hasPendingPenaltyRequest = useRef(false);
4646

4747
useEffect(() => {
48-
// Reset timers on DB fields updates have been removed (unnecessary toggles)
49-
48+
// Reset pending request flags for each new active constraint
49+
hasPendingWarningRequest.current = false;
50+
hasPendingPenaltyRequest.current = false;
51+
5052
const interval = setInterval(() => {
5153
const current = calculateRemaining();
5254
setState(current);
@@ -93,4 +95,4 @@ export function ChaosTimer({ constraint, onTimeout }: ChaosTimerProps) {
9395
</div>
9496
</div>
9597
);
96-
}
98+
}

src/lib/simulation/engine.ts

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -55,41 +55,50 @@ export function runSimulation(nodes: ICanvasNode[], edges: IConnection[], target
5555
if (inDegree[e.to] !== undefined) inDegree[e.to]++;
5656
});
5757

58-
// We will do a modified iterative propagation to handle cycles softly.
59-
// Instead of strict topological sort, we do a fixed number of iterations (e.g. 5 passes)
60-
// For pure DAGs, it propagates cleanly. For cycles, it stabilizes.
61-
58+
// Iterative propagation with convergence detection to handle cycles properly
59+
// We iterate until traffic distribution stabilizes or we hit maxIterations
60+
6261
// Initial Load
6362
sources.forEach(s => {
6463
if (nodeMetrics[s.id]) {
6564
nodeMetrics[s.id].trafficIn += targetRps / sources.length;
6665
}
6766
});
6867

69-
// 10 passes is enough for most UI architectures
70-
for (let pass = 0; pass < 10; pass++) {
71-
// Reset edge flows before recalculating this pass's spread
68+
const maxIterations = 100;
69+
const epsilon = 0.01; // Convergence threshold: stop when total change < epsilon
70+
const sourceIds = new Set(sources.map(s => s.id));
71+
72+
for (let iteration = 0; iteration < maxIterations; iteration++) {
73+
// Store previous trafficIn values to measure convergence
74+
const prevTrafficIn: Record<string, number> = {};
75+
Object.keys(nodeMetrics).forEach(id => {
76+
prevTrafficIn[id] = nodeMetrics[id].trafficIn;
77+
});
78+
79+
// Reset edge flows before recalculating this iteration's spread
7280
edges.forEach(e => { edgeMetrics[e.id].trafficFlow = 0; });
73-
74-
// We need a snapshot of trafficIn for the current frame to distribute it properly
75-
const sourceIds = new Set(sources.map(s => s.id));
76-
const currentTrafficIn = Object.keys(nodeMetrics).reduce((acc, id) => {
77-
acc[id] = nodeMetrics[id].trafficIn;
78-
// Clear trafficIn for non-sources so they can receive the fresh wave
79-
if (!sourceIds.has(id)) {
80-
nodeMetrics[id].trafficIn = 0;
81+
82+
// Create next iteration's trafficIn map, starting with sources
83+
const nextTrafficIn: Record<string, number> = {};
84+
sourceIds.forEach(id => {
85+
nextTrafficIn[id] = targetRps / sources.length;
86+
});
87+
nodes.forEach(n => {
88+
if (!sourceIds.has(n.id)) {
89+
nextTrafficIn[n.id] = 0;
8190
}
82-
return acc;
83-
}, {} as Record<string, number>);
91+
});
8492

93+
// Process each node: compute outFlow and distribute to children
8594
nodes.forEach(node => {
8695
const metrics = nodeMetrics[node.id];
87-
const processingTraffic = currentTrafficIn[node.id];
88-
96+
const processingTraffic = prevTrafficIn[node.id];
97+
8998
// Cap out at capacity
9099
const outFlow = Math.min(processingTraffic, metrics.capacity);
91100
metrics.trafficOut = outFlow;
92-
101+
93102
// Update status
94103
if (processingTraffic > metrics.capacity) {
95104
metrics.status = 'bottlenecked';
@@ -105,12 +114,28 @@ export function runSimulation(nodes: ICanvasNode[], edges: IConnection[], target
105114
const flowPerEdge = outFlow / outgoingEdges.length;
106115
outgoingEdges.forEach(edge => {
107116
edgeMetrics[edge.id].trafficFlow += flowPerEdge;
108-
if (nodeMetrics[edge.to]) {
109-
nodeMetrics[edge.to].trafficIn += flowPerEdge;
117+
if (nextTrafficIn[edge.to] !== undefined) {
118+
nextTrafficIn[edge.to] += flowPerEdge;
110119
}
111120
});
112121
}
113122
});
123+
124+
// Update nodeMetrics with nextTrafficIn
125+
Object.keys(nextTrafficIn).forEach(id => {
126+
nodeMetrics[id].trafficIn = nextTrafficIn[id];
127+
});
128+
129+
// Measure total delta to check for convergence
130+
let totalDelta = 0;
131+
Object.keys(nodeMetrics).forEach(id => {
132+
totalDelta += Math.abs(nodeMetrics[id].trafficIn - prevTrafficIn[id]);
133+
});
134+
135+
// Stop if converged
136+
if (totalDelta < epsilon) {
137+
break;
138+
}
114139
}
115140

116141
// Evaluate Global Status
@@ -120,4 +145,4 @@ export function runSimulation(nodes: ICanvasNode[], edges: IConnection[], target
120145
else if (bottleneckCount > 0) globalStatus = 'degraded';
121146

122147
return { nodeMetrics, edgeMetrics, globalStatus };
123-
}
148+
}

0 commit comments

Comments
 (0)