Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6743375
Fix sync when retrieving all questions
xGladiate Oct 12, 2025
51b8417
Replace insertOne with updateOne but with $setOnInsert only
xGladiate Oct 12, 2025
e2dd31b
Remove unnecessary unique constrain enforcement
xGladiate Oct 12, 2025
fd3cd12
Remove initial_count from return
xGladiate Oct 12, 2025
36982ed
Remove fully initial_count from fetchNonPaidQuestionList
xGladiate Oct 12, 2025
8e9081e
Remove done attribute for cursor across files
xGladiate Oct 12, 2025
d2a2a45
Remove entirely initial_count
xGladiate Oct 12, 2025
425e37a
Replace magic number total + 1 with total instead
xGladiate Oct 12, 2025
58bbe42
Fix minor naming issues for comments
xGladiate Oct 12, 2025
68280a8
Add question-service health check before question retrieval at seed-b…
xGladiate Oct 14, 2025
f18914a
Modify API in .env.example
xGladiate Oct 16, 2025
98c127a
Reformat mongo db connection for easier logging and debugging
xGladiate Oct 16, 2025
afcdd04
Fix docker run command in question-backend-service README
xGladiate Oct 16, 2025
a213802
Add infrastructure for failedQuestion storing
xGladiate Oct 16, 2025
169d6cd
Refactor fetchNonPaidQuestionInfo to reduce repetition
xGladiate Oct 16, 2025
c6d5e95
Fix comments
xGladiate Oct 16, 2025
7f2a81f
Add debug statement
xGladiate Oct 16, 2025
3d1a492
Replace magic number with constant
xGladiate Oct 16, 2025
bf50547
Remove quesion and dbSchema for failedQuestion
xGladiate Oct 16, 2025
3ed9d26
Fix minor comment and error messages
xGladiate Oct 16, 2025
93259cd
Add error handling for failed leetcode connection
xGladiate Oct 17, 2025
9929a2d
Fix README.md
xGladiate Oct 17, 2025
1259069
Fix naming issues
xGladiate Oct 17, 2025
de94690
Fix README for leetcode-service
xGladiate Oct 17, 2025
f34d832
Fix magic number issue
xGladiate Oct 17, 2025
cd2f45f
Remove redundant params
xGladiate Oct 17, 2025
43bffb3
Fix reties count and comments for setOnInsert
xGladiate Oct 17, 2025
72d9ba5
Add new line for .env.example
xGladiate Oct 17, 2025
1f16df4
Fix retries issue
xGladiate Oct 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/docker-build-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
# Add one entry per microservice
- name: matching-ui
context: ./ui-services/matching-ui-service
- name: questions-ui
- name: question-ui
context: ./ui-services/question-ui-service
- name: collab-ui
context: ./ui-services/collab-ui-service
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/format-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
npx eslint --format json . > eslint-report.json
cat eslint-report.json | reviewdog -f=eslint -name="eslint" -reporter=github-pr-review -level=error
questions-ui-lint:
question-ui-lint:
runs-on: ubuntu-latest
steps:
- name: Set REVIEWDOG_GITHUB_API_TOKEN
Expand Down Expand Up @@ -100,7 +100,7 @@ jobs:
eslint_exit_code=$exit_code
if [ $eslint_exit_code -ne 0 ]; then
echo "ESLint failed for questions-ui"
echo "ESLint failed for question-ui"
exit 1
fi
Expand Down Expand Up @@ -180,7 +180,7 @@ jobs:
eslint_exit_code=$exit_code
if [ $eslint_exit_code -ne 0 ]; then
echo "ESLint failed for questions-ui"
echo "ESLint failed for question-ui"
exit 1
fi
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/leetcode-update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
run: |
echo "Waiting for leetcode-backend to respond..."
for i in {1..60}; do
code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5285/api/v1/leetcode/health || true)
code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5285/api/v1/leetcode-service/health || true)
if [ "$code" = "200" ]; then
echo "Leetcode backend is ready (HTTP 200)"
exit 0
Expand All @@ -73,7 +73,7 @@ jobs:
run: |
echo "Waiting for question-backend to respond..."
for i in {1..60}; do
code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5275/api/v1/questions/health || true)
code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5275/api/v1/question-service/health || true)
if [ "$code" = "200" ]; then
echo "Question backend is ready (HTTP 200)"
exit 0
Expand Down
4 changes: 2 additions & 2 deletions backend-services/leetcode-backend-service/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MONGODB_URI=mongodb+srv://admin:<db-password>@cluster3219.ll0nzkk.mongodb.net/?retryWrites=true&w=majority&appName=Cluster3219
ADMIN_TOKEN=<db-password>
QUESTION_API_URL=http://question-backend:5275/api/v1/questions
QUESTION_API_URL=http://localhost:5275/api/v1/question-service
# Maximum number of concurrent LeetCode detail fetches.
# Optional: defaults to 6 if not set.
LEETCODE_DETAIL_CONCURRENCY=6
LEETCODE_DETAIL_CONCURRENCY=6
10 changes: 5 additions & 5 deletions backend-services/leetcode-backend-service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ Server listening on http://localhost:5285
**Note**: Local development (e.g. `npm run dev`) is possible (though not recommended). To enable it, update the .env file by changing:

```bash
QUESTION_API_URL=http://question-backend:5275/api/v1/questions
QUESTION_API_URL=http://question-backend:5275/api/v1/question-service
```

to:

```bash
QUESTION_API_URL=http://localhost:5275/api/v1/questions
QUESTION_API_URL=http://localhost:5275/api/v1/question-service
```

Do change the `QUESTION_API_URL` back when using docker run.
Expand Down Expand Up @@ -86,19 +86,19 @@ src/

## API

Base URL: `http://localhost:5285/api/v1`
Base URL: `http://localhost:5285/api/v1/leetcode-service`

### Seed 200 problems into Mongo

**POST** `/leetcode/seed-batch`
**POST** `/seed-batch`
Fetches the next 200 problems and **upserts** to Mongo.

Examples:

```bash
# Replace ADMIN_TOKEN with DB password
# MUSt run the question-service before running the follow command
curl.exe --request POST -H "X-Admin-Token: <>" --url "http://localhost:5285/api/v1/leetcode/seed-batch"
curl.exe --request POST -H "X-Admin-Token: <ADMIN_TOKEN>" --url "http://localhost:5285/api/v1/leetcode-service/seed-batch"
```

## Data Model
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,15 @@ export default fp((app: FastifyInstance) => {
const doc = change.fullDocument as QuestionDoc;
if (!doc) continue;
await postDoc(doc);
app.log.info({ doc }, "Got changed document:");
logger.info("Got changed document:", { doc });
} catch (err) {
app.log.error(
{ err },
"[ChangeStream] Error processing change event",
);
logger.error("[ChangeStream] Error processing change event", { err });
}
}
isProcessing = false;
}
changeStream.on("change", (change: ChangeStreamDocument) => {
app.log.info("[ChangeStream] Event");
logger.info("[ChangeStream] Event");
changeQueue.push(change);
void processQueue();
});
Expand Down
21 changes: 13 additions & 8 deletions backend-services/leetcode-backend-service/src/db/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,21 @@ export default fp(async (app: FastifyInstance) => {
const uri = process.env.MONGODB_URI;
if (!uri) throw new Error("MONGODB_URI is missing");

if (
mongoose.connection.readyState === mongoose.ConnectionStates.disconnected
) {
await mongoose.connect(uri, {
dbName: "leetcode-service",
serverSelectionTimeoutMS: 10000,
});
if (mongoose.connection.readyState !== mongoose.ConnectionStates.connected) {
try {
await mongoose.connect(uri, {
dbName: "leetcode-service",
serverSelectionTimeoutMS: 10000,
});
logger.info("[Mongo] Connected");
} catch (err) {
logger.error("[Mongo] Connection failed: ", err);
throw err;
}
} else {
logger.info("[Mongo] Already connected");
}

logger.info("Mongo connected");
app.decorate("mongo", mongoose);

app.addHook("onClose", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const QuestionSchema = new Schema<QuestionDoc>(
{ collection: "questions", timestamps: true },
);

// Compound indexes for faster queries
QuestionSchema.index({
source: 1,
titleSlug: 1,
Expand All @@ -54,7 +55,6 @@ const CursorSchema = new mongoose.Schema(
_id: { type: String, required: true },
nextSkip: { type: Number, default: 0, index: true },
pageSize: { type: Number, default: 200 },
done: { type: Boolean, default: false },
lastRunAt: { type: Date },
total: { type: Number, default: 0 },
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ export interface SeedBatchResponse {
ok: boolean;
message: string;
nextSkip: number;
done: boolean;
// keep room for extra fields
[k: string]: unknown;
}
66 changes: 66 additions & 0 deletions backend-services/leetcode-backend-service/src/health.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { setTimeout as delay } from "node:timers/promises";

type HealthOpts = {
url?: string; // health check URL
timeoutMs?: number; // per-attempt timeout
retries?: number; // number of retries after the first attempt
};

interface HealthResponse {
ok?: boolean;
}

const BATCH_HEALTH_TIMEOUT_MS = 1500;
const BATCH_HEALTH_RETRIES = 2;
const BASE_DELAY_MS = 250;

/** Check the health of the question service.
* Throws an error if the service is unhealthy or unreachable after retries.
* @param opts - Options for health check.
* @returns A promise that resolves to true if healthy, otherwise throws an error.
*/

export async function checkQuestionServiceHealth({
url = `${process.env.QUESTION_API_URL}/health`,
timeoutMs = BATCH_HEALTH_TIMEOUT_MS,
retries = BATCH_HEALTH_RETRIES,
}: HealthOpts = {}) {
if (!process.env.QUESTION_API_URL)
throw new Error("QUESTION_API_URL is not set");

let lastErr: unknown;

// Attempt health check with retries where retries is the number of additional attempts after the first
for (let attempt = 0; attempt < retries + 1; attempt++) {
const controller = new AbortController();
const t = setTimeout(() => controller.abort(), timeoutMs);

try {
const res = await fetch(url, {
method: "GET",
headers: { accept: "application/json" },
signal: controller.signal,
});
clearTimeout(t);

if (!res.ok) {
lastErr = new Error(`Health endpoint returned ${res.status}`);
} else {
const body: HealthResponse = (await res.json()) as HealthResponse;
if (body?.ok !== true) {
lastErr = new Error("Health endpoint did not return ok=true");
} else {
return true;
}
}
} catch (err) {
lastErr = err;
}

if (attempt < retries) {
await delay(BASE_DELAY_MS * 2 ** attempt);
}
}

throw new Error(`Question service health check failed: ${String(lastErr)}`);
}
Loading