Skip to content

Commit 2691976

Browse files
feat: update Azure Cosmos DB integration and improve error handling in GraphQL API
1 parent 87db618 commit 2691976

File tree

8 files changed

+91
-30
lines changed

8 files changed

+91
-30
lines changed

.env.example

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
AZURE_COSMOSDB_CONTAINER=
21
AZURE_COSMOSDB_DATABASE=
32
AZURE_COSMOSDB_ENDPOINT=
43
AZURE_COSMOSDB_KEY=

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,9 @@ next-env.d.ts
4949

5050
bundles
5151
.env
52+
53+
# service worker
54+
sw.js
55+
sw.js.map
56+
workbox-*.js
57+
workbox-*.js.map

app/api/graphql/route.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { CosmosContainer } from "@azure-fundamentals/lib/graphql/cosmos-client";
21
import {
32
CombinedQuestionsDataSource,
43
RepoQuestionsDataSource,
@@ -26,7 +25,7 @@ const handler = startServerAndCreateNextHandler(server, {
2625
if (process.env.AZURE_COSMOSDB_ENDPOINT) {
2726
return {
2827
dataSources: {
29-
questionsDB: CombinedQuestionsDataSource(CosmosContainer()),
28+
questionsDB: CombinedQuestionsDataSource(),
3029
},
3130
};
3231
} else {
@@ -54,4 +53,24 @@ const handler = startServerAndCreateNextHandler(server, {
5453
},
5554
});
5655

57-
export { handler as GET, handler as POST };
56+
// Wrap the handler to handle errors
57+
const wrappedHandler = async (req: Request) => {
58+
try {
59+
return await handler(req);
60+
} catch (error) {
61+
console.error("GraphQL Error:", error);
62+
return new Response(
63+
JSON.stringify({
64+
errors: [{ message: "Internal server error" }],
65+
}),
66+
{
67+
status: 500,
68+
headers: {
69+
"Content-Type": "application/json",
70+
},
71+
},
72+
);
73+
}
74+
};
75+
76+
export { wrappedHandler as GET, wrappedHandler as POST };

lib/graphql/cosmos-client.tsx

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,36 @@
11
import { CosmosClient } from "@azure/cosmos";
22

3-
export const CosmosContainer = () => {
3+
export const getDatabase = () => {
44
const client = new CosmosClient({
55
endpoint: process.env.AZURE_COSMOSDB_ENDPOINT!,
66
key: process.env.AZURE_COSMOSDB_KEY!,
77
});
88

9-
const container = client
10-
.database(process.env.AZURE_COSMOSDB_DATABASE!)
11-
.container(process.env.AZURE_COSMOSDB_CONTAINER!);
9+
return client.database(process.env.AZURE_COSMOSDB_DATABASE!);
10+
};
11+
12+
export const getContainer = async (containerName: string) => {
13+
const database = getDatabase();
1214

13-
return container;
15+
// Try to create container if it doesn't exist
16+
try {
17+
const { container } = await database.containers.createIfNotExists({
18+
id: containerName,
19+
partitionKey: {
20+
paths: ["/id"],
21+
},
22+
});
23+
return container;
24+
} catch (error: any) {
25+
// If container creation fails, try to get the existing container
26+
if (error.code === 409) {
27+
console.log(
28+
`Container ${containerName} already exists, using existing container`,
29+
);
30+
return database.container(containerName);
31+
} else {
32+
console.error("Error creating container:", error);
33+
throw error;
34+
}
35+
}
1436
};

lib/graphql/questionsDataSource.tsx

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Container } from "@azure/cosmos";
22
import { fetchQuestions } from "./repoQuestions";
3+
import { getContainer } from "./cosmos-client";
34

45
export const QuestionsDataSource = (container: Container) => {
56
return {
@@ -67,18 +68,25 @@ export const RepoQuestionsDataSource = (container: any) => {
6768
};
6869
};
6970

70-
export const CombinedQuestionsDataSource = (container: Container) => {
71+
export const CombinedQuestionsDataSource = () => {
7172
return {
7273
async getQuestion(id: string, link: string) {
7374
try {
75+
// Extract exam name from URL and create a safe container name
76+
const segments = link.split("/");
77+
const examName = segments[segments.length - 3]
78+
.replace(/-/g, "_")
79+
.toLowerCase();
80+
const examContainer = await getContainer(examName);
81+
7482
// Try GitHub first
7583
const questions = await fetchQuestions(link);
7684
if (questions) {
7785
const question = questions.find((q: any) => q.id === id);
7886
if (question) {
79-
// Upload to Cosmos DB for future use
87+
// Upload to exam-specific container
8088
try {
81-
await container.items.upsert(question);
89+
await examContainer.items.upsert(question);
8290
} catch (err) {
8391
console.warn("Failed to upload question to Cosmos DB:", err);
8492
}
@@ -91,7 +99,7 @@ export const CombinedQuestionsDataSource = (container: Container) => {
9199
query: "SELECT * FROM c WHERE c.id = @id",
92100
parameters: [{ name: "@id", value: id }],
93101
};
94-
const { resources: items } = await container.items
102+
const { resources: items } = await examContainer.items
95103
.query(querySpec)
96104
.fetchAll();
97105
return items[0];
@@ -102,13 +110,20 @@ export const CombinedQuestionsDataSource = (container: Container) => {
102110

103111
async getQuestions(link: string) {
104112
try {
113+
// Extract exam name from URL and create a safe container name
114+
const segments = link.split("/");
115+
const examName = segments[segments.length - 3]
116+
.replace(/-/g, "_")
117+
.toLowerCase();
118+
const examContainer = await getContainer(examName);
119+
105120
// Try GitHub first
106121
const questions = await fetchQuestions(link);
107122
if (questions) {
108-
// Upload all questions to Cosmos DB
123+
// Upload all questions to exam-specific container
109124
try {
110125
for (const question of questions) {
111-
await container.items.upsert(question);
126+
await examContainer.items.upsert(question);
112127
}
113128
} catch (err) {
114129
console.warn("Failed to upload questions to Cosmos DB:", err);
@@ -120,7 +135,7 @@ export const CombinedQuestionsDataSource = (container: Container) => {
120135
const querySpec = {
121136
query: "SELECT VALUE COUNT(c.id) FROM c",
122137
};
123-
const { resources: items } = await container.items
138+
const { resources: items } = await examContainer.items
124139
.query(querySpec)
125140
.fetchAll();
126141
return { count: items[0] };
@@ -131,16 +146,23 @@ export const CombinedQuestionsDataSource = (container: Container) => {
131146

132147
async getRandomQuestions(range: number, link: string) {
133148
try {
149+
// Extract exam name from URL and create a safe container name
150+
const segments = link.split("/");
151+
const examName = segments[segments.length - 3]
152+
.replace(/-/g, "_")
153+
.toLowerCase();
154+
const examContainer = await getContainer(examName);
155+
134156
// Try GitHub first
135157
const questions = await fetchQuestions(link);
136158
if (questions) {
137159
const shuffled = [...questions].sort(() => 0.5 - Math.random());
138160
const selected = shuffled.slice(0, range);
139161

140-
// Upload selected questions to Cosmos DB
162+
// Upload selected questions to exam-specific container
141163
try {
142164
for (const question of selected) {
143-
await container.items.upsert(question);
165+
await examContainer.items.upsert(question);
144166
}
145167
} catch (err) {
146168
console.warn("Failed to upload questions to Cosmos DB:", err);
@@ -153,7 +175,7 @@ export const CombinedQuestionsDataSource = (container: Container) => {
153175
const querySpec = {
154176
query: "SELECT * FROM c",
155177
};
156-
const { resources: items } = await container.items
178+
const { resources: items } = await examContainer.items
157179
.query(querySpec)
158180
.fetchAll();
159181
const shuffled = [...items].sort(() => 0.5 - Math.random());

package-lock.json

Lines changed: 2 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "practice-exams-platform",
3-
"version": "1.2.0",
3+
"version": "1.3.0",
44
"private": true,
55
"engines": {
66
"node": "20.x"

0 commit comments

Comments
 (0)