From f1ebec8563f9450a9f21445f2124cab193d011eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=85=20Daniel=20Danielecki?= Date: Thu, 29 May 2025 13:00:28 +0200 Subject: [PATCH 1/5] feat: Azure CosmosDB as a fallback from repos --- app/api/graphql/route.ts | 55 ++++++++++----- lib/graphql/questionsDataSource.tsx | 103 +++++++++++++++++++++++++++- lib/graphql/resolvers.tsx | 10 +-- 3 files changed, 140 insertions(+), 28 deletions(-) diff --git a/app/api/graphql/route.ts b/app/api/graphql/route.ts index 38bc5b6..d7d0f51 100644 --- a/app/api/graphql/route.ts +++ b/app/api/graphql/route.ts @@ -1,11 +1,13 @@ -// import { CosmosContainer } from "@azure-fundamentals/src/graphql/cosmos-client"; -// import { QuestionsDataSource, LocalQuestionsDataSource } from "@azure-fundamentals/src/graphql/questionsDataSource"; +import { CosmosContainer } from "@azure-fundamentals/lib/graphql/cosmos-client"; +import { + CombinedQuestionsDataSource, + RepoQuestionsDataSource, +} from "@azure-fundamentals/lib/graphql/questionsDataSource"; import { ApolloServer, BaseContext } from "@apollo/server"; import { startServerAndCreateNextHandler } from "@as-integrations/next"; import typeDefs from "@azure-fundamentals/lib/graphql/schemas"; import resolvers from "@azure-fundamentals/lib/graphql/resolvers"; -//import { RepoQuestionsDataSource } from "@azure-fundamentals/lib/graphql/questionsDataSource"; -//import { FetchQuestions } from "@azure-fundamentals/lib/graphql/repoQuestions"; +import { fetchQuestions } from "@azure-fundamentals/lib/graphql/repoQuestions"; interface ContextValue { dataSources: { @@ -19,22 +21,37 @@ const server = new ApolloServer({ introspection: process.env.NODE_ENV !== "production", }); -//const questions = await FetchQuestions(); - -const handler = startServerAndCreateNextHandler( - server, - /*{ +const handler = startServerAndCreateNextHandler(server, { context: async () => { - return { - dataSources: { - // questionsDB: process.env.AZURE_COSMOSDB_ENDPOINT - // ? QuestionsDataSource(CosmosContainer()) - // : LocalQuestionsDataSource(questions), - questionsDB: RepoQuestionsDataSource(questions), - }, - }; + if (process.env.AZURE_COSMOSDB_ENDPOINT) { + return { + dataSources: { + questionsDB: CombinedQuestionsDataSource(CosmosContainer()), + }, + }; + } else { + // Fallback to GitHub-only data source + return { + dataSources: { + questionsDB: { + getQuestion: async (id: string, link: string) => { + const questions = await fetchQuestions(link); + return questions?.find((q: any) => q.id === id); + }, + getQuestions: async (link: string) => { + const questions = await fetchQuestions(link); + return { count: questions?.length || 0 }; + }, + getRandomQuestions: async (range: number, link: string) => { + const questions = await fetchQuestions(link); + const shuffled = questions?.sort(() => 0.5 - Math.random()); + return shuffled?.slice(0, range) || []; + }, + }, + }, + }; + } }, -}*/ -); +}); export { handler as GET, handler as POST }; diff --git a/lib/graphql/questionsDataSource.tsx b/lib/graphql/questionsDataSource.tsx index 5788d2b..9b40fbc 100644 --- a/lib/graphql/questionsDataSource.tsx +++ b/lib/graphql/questionsDataSource.tsx @@ -1,4 +1,5 @@ -/*import { Container } from "@azure/cosmos"; +import { Container } from "@azure/cosmos"; +import { fetchQuestions } from "./repoQuestions"; export const QuestionsDataSource = (container: Container) => { return { @@ -38,7 +39,7 @@ export const QuestionsDataSource = (container: Container) => { }, }; }; -*/ + export const RepoQuestionsDataSource = (container: any) => { return { async getQuestion(id: string) { @@ -65,3 +66,101 @@ export const RepoQuestionsDataSource = (container: any) => { }, }; }; + +export const CombinedQuestionsDataSource = (container: Container) => { + return { + async getQuestion(id: string, link: string) { + try { + // Try GitHub first + const questions = await fetchQuestions(link); + if (questions) { + const question = questions.find((q: any) => q.id === id); + if (question) { + // Upload to Cosmos DB for future use + try { + await container.items.create(question); + } catch (err) { + console.warn("Failed to upload question to Cosmos DB:", err); + } + return question; + } + } + + // Fallback to Cosmos DB + const querySpec = { + query: "SELECT * FROM c WHERE c.id = @id", + parameters: [{ name: "@id", value: id }], + }; + const { resources: items } = await container.items + .query(querySpec) + .fetchAll(); + return items[0]; + } catch (err) { + throw new Error("Error fetching question: " + err); + } + }, + + async getQuestions(link: string) { + try { + // Try GitHub first + const questions = await fetchQuestions(link); + if (questions) { + // Upload all questions to Cosmos DB + try { + for (const question of questions) { + await container.items.create(question); + } + } catch (err) { + console.warn("Failed to upload questions to Cosmos DB:", err); + } + return { count: questions.length }; + } + + // Fallback to Cosmos DB + const querySpec = { + query: "SELECT VALUE COUNT(c.id) FROM c", + }; + const { resources: items } = await container.items + .query(querySpec) + .fetchAll(); + return { count: items[0] }; + } catch (err) { + throw new Error("Error fetching questions: " + err); + } + }, + + async getRandomQuestions(range: number, link: string) { + try { + // Try GitHub first + const questions = await fetchQuestions(link); + if (questions) { + const shuffled = [...questions].sort(() => 0.5 - Math.random()); + const selected = shuffled.slice(0, range); + + // Upload selected questions to Cosmos DB + try { + for (const question of selected) { + await container.items.create(question); + } + } catch (err) { + console.warn("Failed to upload questions to Cosmos DB:", err); + } + + return selected; + } + + // Fallback to Cosmos DB + const querySpec = { + query: "SELECT * FROM c", + }; + const { resources: items } = await container.items + .query(querySpec) + .fetchAll(); + const shuffled = [...items].sort(() => 0.5 - Math.random()); + return shuffled.slice(0, range); + } catch (err) { + throw new Error("Error fetching random questions: " + err); + } + }, + }; +}; diff --git a/lib/graphql/resolvers.tsx b/lib/graphql/resolvers.tsx index 34b15b2..ac9a6fd 100644 --- a/lib/graphql/resolvers.tsx +++ b/lib/graphql/resolvers.tsx @@ -7,25 +7,21 @@ const resolvers = { { link }: { link: string }, { dataSources }: { dataSources: any }, ) => { - const response = await fetchQuestions(link); - return { count: response?.length }; + return dataSources.questionsDB.getQuestions(link); }, questionById: async ( _: unknown, { id, link }: { id: string; link: string }, { dataSources }: { dataSources: any }, ) => { - const response = await fetchQuestions(link); - return response?.filter((el: any) => el.id === id)[0]; + return dataSources.questionsDB.getQuestion(id, link); }, randomQuestions: async ( _: unknown, { range, link }: { range: number; link: string }, { dataSources }: { dataSources: any }, ) => { - const response = await fetchQuestions(link); - const shuffled = response?.sort(() => 0.5 - Math.random()); - return shuffled?.slice(0, range); + return dataSources.questionsDB.getRandomQuestions(range, link); }, }, }; From 4e50430da5bacfbfd68cd369357bc6925fad1d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=85=20Daniel=20Danielecki?= Date: Sat, 31 May 2025 18:02:43 +0200 Subject: [PATCH 2/5] feat: add viewport configuration and clean up metadata in layout --- app/layout.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index 236204a..93a119a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,11 +1,17 @@ import { type ReactNode } from "react"; -import { type Metadata } from "next"; +import { type Metadata, type Viewport } from "next"; import TopNav from "@azure-fundamentals/components/TopNav"; import Footer from "@azure-fundamentals/components/Footer"; import ApolloProvider from "@azure-fundamentals/components/ApolloProvider"; import Cookie from "@azure-fundamentals/components/Cookie"; import "styles/globals.css"; +export const viewport: Viewport = { + themeColor: "#3f51b5", + width: "device-width", + initialScale: 1, +}; + export const metadata: Metadata = { appleWebApp: { capable: true, @@ -71,7 +77,6 @@ export const metadata: Metadata = { follow: true, index: true, }, - themeColor: "#3f51b5", title: { default: "🧪 Practice Exams Platform | Ditectrev", template: "🧪 Practice Exams Platform | Ditectrev", @@ -90,10 +95,6 @@ export const metadata: Metadata = { site: "@ditectrev", title: "🧪 Practice Exams Platform | Ditectrev", }, - viewport: { - initialScale: 1, - width: "device-width", - }, }; type RootLayoutProps = { From 87db618ab9d68f54f5174de09bc3ad0761859f10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=85=20Daniel=20Danielecki?= Date: Tue, 3 Jun 2025 10:07:48 +0200 Subject: [PATCH 3/5] feat: connection to Azure Cosmos DB --- .env.example | 4 ++++ .gitignore | 3 +++ lib/graphql/questionsDataSource.tsx | 6 +++--- 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4f2eb85 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +AZURE_COSMOSDB_CONTAINER= +AZURE_COSMOSDB_DATABASE= +AZURE_COSMOSDB_ENDPOINT= +AZURE_COSMOSDB_KEY= diff --git a/.gitignore b/.gitignore index d545bd7..12ce6ef 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ next-env.d.ts **/public/sw.js **/public/worker-*.js **/public/sw.js.map + +bundles +.env diff --git a/lib/graphql/questionsDataSource.tsx b/lib/graphql/questionsDataSource.tsx index 9b40fbc..f6ad9ad 100644 --- a/lib/graphql/questionsDataSource.tsx +++ b/lib/graphql/questionsDataSource.tsx @@ -78,7 +78,7 @@ export const CombinedQuestionsDataSource = (container: Container) => { if (question) { // Upload to Cosmos DB for future use try { - await container.items.create(question); + await container.items.upsert(question); } catch (err) { console.warn("Failed to upload question to Cosmos DB:", err); } @@ -108,7 +108,7 @@ export const CombinedQuestionsDataSource = (container: Container) => { // Upload all questions to Cosmos DB try { for (const question of questions) { - await container.items.create(question); + await container.items.upsert(question); } } catch (err) { console.warn("Failed to upload questions to Cosmos DB:", err); @@ -140,7 +140,7 @@ export const CombinedQuestionsDataSource = (container: Container) => { // Upload selected questions to Cosmos DB try { for (const question of selected) { - await container.items.create(question); + await container.items.upsert(question); } } catch (err) { console.warn("Failed to upload questions to Cosmos DB:", err); From 269197674792434005b8a76017a2d941c3fb164a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=85=20Daniel=20Danielecki?= Date: Tue, 1 Jul 2025 15:37:46 +0200 Subject: [PATCH 4/5] feat: update Azure Cosmos DB integration and improve error handling in GraphQL API --- .env.example | 1 - .gitignore | 6 +++++ app/api/graphql/route.ts | 25 ++++++++++++++--- lib/graphql/cosmos-client.tsx | 32 ++++++++++++++++++---- lib/graphql/questionsDataSource.tsx | 42 ++++++++++++++++++++++------- package-lock.json | 11 ++------ package.json | 2 +- public/sw.js.map | 2 +- 8 files changed, 91 insertions(+), 30 deletions(-) diff --git a/.env.example b/.env.example index 4f2eb85..cca63b3 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,3 @@ -AZURE_COSMOSDB_CONTAINER= AZURE_COSMOSDB_DATABASE= AZURE_COSMOSDB_ENDPOINT= AZURE_COSMOSDB_KEY= diff --git a/.gitignore b/.gitignore index 12ce6ef..ba3b7b5 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,9 @@ next-env.d.ts bundles .env + +# service worker +sw.js +sw.js.map +workbox-*.js +workbox-*.js.map diff --git a/app/api/graphql/route.ts b/app/api/graphql/route.ts index d7d0f51..411bd78 100644 --- a/app/api/graphql/route.ts +++ b/app/api/graphql/route.ts @@ -1,4 +1,3 @@ -import { CosmosContainer } from "@azure-fundamentals/lib/graphql/cosmos-client"; import { CombinedQuestionsDataSource, RepoQuestionsDataSource, @@ -26,7 +25,7 @@ const handler = startServerAndCreateNextHandler(server, { if (process.env.AZURE_COSMOSDB_ENDPOINT) { return { dataSources: { - questionsDB: CombinedQuestionsDataSource(CosmosContainer()), + questionsDB: CombinedQuestionsDataSource(), }, }; } else { @@ -54,4 +53,24 @@ const handler = startServerAndCreateNextHandler(server, { }, }); -export { handler as GET, handler as POST }; +// Wrap the handler to handle errors +const wrappedHandler = async (req: Request) => { + try { + return await handler(req); + } catch (error) { + console.error("GraphQL Error:", error); + return new Response( + JSON.stringify({ + errors: [{ message: "Internal server error" }], + }), + { + status: 500, + headers: { + "Content-Type": "application/json", + }, + }, + ); + } +}; + +export { wrappedHandler as GET, wrappedHandler as POST }; diff --git a/lib/graphql/cosmos-client.tsx b/lib/graphql/cosmos-client.tsx index 555468e..4172d60 100644 --- a/lib/graphql/cosmos-client.tsx +++ b/lib/graphql/cosmos-client.tsx @@ -1,14 +1,36 @@ import { CosmosClient } from "@azure/cosmos"; -export const CosmosContainer = () => { +export const getDatabase = () => { const client = new CosmosClient({ endpoint: process.env.AZURE_COSMOSDB_ENDPOINT!, key: process.env.AZURE_COSMOSDB_KEY!, }); - const container = client - .database(process.env.AZURE_COSMOSDB_DATABASE!) - .container(process.env.AZURE_COSMOSDB_CONTAINER!); + return client.database(process.env.AZURE_COSMOSDB_DATABASE!); +}; + +export const getContainer = async (containerName: string) => { + const database = getDatabase(); - return container; + // Try to create container if it doesn't exist + try { + const { container } = await database.containers.createIfNotExists({ + id: containerName, + partitionKey: { + paths: ["/id"], + }, + }); + return container; + } catch (error: any) { + // If container creation fails, try to get the existing container + if (error.code === 409) { + console.log( + `Container ${containerName} already exists, using existing container`, + ); + return database.container(containerName); + } else { + console.error("Error creating container:", error); + throw error; + } + } }; diff --git a/lib/graphql/questionsDataSource.tsx b/lib/graphql/questionsDataSource.tsx index f6ad9ad..b58e566 100644 --- a/lib/graphql/questionsDataSource.tsx +++ b/lib/graphql/questionsDataSource.tsx @@ -1,5 +1,6 @@ import { Container } from "@azure/cosmos"; import { fetchQuestions } from "./repoQuestions"; +import { getContainer } from "./cosmos-client"; export const QuestionsDataSource = (container: Container) => { return { @@ -67,18 +68,25 @@ export const RepoQuestionsDataSource = (container: any) => { }; }; -export const CombinedQuestionsDataSource = (container: Container) => { +export const CombinedQuestionsDataSource = () => { return { async getQuestion(id: string, link: string) { try { + // Extract exam name from URL and create a safe container name + const segments = link.split("/"); + const examName = segments[segments.length - 3] + .replace(/-/g, "_") + .toLowerCase(); + const examContainer = await getContainer(examName); + // Try GitHub first const questions = await fetchQuestions(link); if (questions) { const question = questions.find((q: any) => q.id === id); if (question) { - // Upload to Cosmos DB for future use + // Upload to exam-specific container try { - await container.items.upsert(question); + await examContainer.items.upsert(question); } catch (err) { console.warn("Failed to upload question to Cosmos DB:", err); } @@ -91,7 +99,7 @@ export const CombinedQuestionsDataSource = (container: Container) => { query: "SELECT * FROM c WHERE c.id = @id", parameters: [{ name: "@id", value: id }], }; - const { resources: items } = await container.items + const { resources: items } = await examContainer.items .query(querySpec) .fetchAll(); return items[0]; @@ -102,13 +110,20 @@ export const CombinedQuestionsDataSource = (container: Container) => { async getQuestions(link: string) { try { + // Extract exam name from URL and create a safe container name + const segments = link.split("/"); + const examName = segments[segments.length - 3] + .replace(/-/g, "_") + .toLowerCase(); + const examContainer = await getContainer(examName); + // Try GitHub first const questions = await fetchQuestions(link); if (questions) { - // Upload all questions to Cosmos DB + // Upload all questions to exam-specific container try { for (const question of questions) { - await container.items.upsert(question); + await examContainer.items.upsert(question); } } catch (err) { console.warn("Failed to upload questions to Cosmos DB:", err); @@ -120,7 +135,7 @@ export const CombinedQuestionsDataSource = (container: Container) => { const querySpec = { query: "SELECT VALUE COUNT(c.id) FROM c", }; - const { resources: items } = await container.items + const { resources: items } = await examContainer.items .query(querySpec) .fetchAll(); return { count: items[0] }; @@ -131,16 +146,23 @@ export const CombinedQuestionsDataSource = (container: Container) => { async getRandomQuestions(range: number, link: string) { try { + // Extract exam name from URL and create a safe container name + const segments = link.split("/"); + const examName = segments[segments.length - 3] + .replace(/-/g, "_") + .toLowerCase(); + const examContainer = await getContainer(examName); + // Try GitHub first const questions = await fetchQuestions(link); if (questions) { const shuffled = [...questions].sort(() => 0.5 - Math.random()); const selected = shuffled.slice(0, range); - // Upload selected questions to Cosmos DB + // Upload selected questions to exam-specific container try { for (const question of selected) { - await container.items.upsert(question); + await examContainer.items.upsert(question); } } catch (err) { console.warn("Failed to upload questions to Cosmos DB:", err); @@ -153,7 +175,7 @@ export const CombinedQuestionsDataSource = (container: Container) => { const querySpec = { query: "SELECT * FROM c", }; - const { resources: items } = await container.items + const { resources: items } = await examContainer.items .query(querySpec) .fetchAll(); const shuffled = [...items].sort(() => 0.5 - Math.random()); diff --git a/package-lock.json b/package-lock.json index d30589d..0dc5f71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "practice-exams-platform", - "version": "1.2.0", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "practice-exams-platform", - "version": "1.2.0", + "version": "1.3.0", "dependencies": { "@apollo/client": "^3.7.9", "@apollo/server": "^4.11.0", @@ -494,7 +494,6 @@ "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", @@ -821,7 +820,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -831,7 +829,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -862,7 +859,6 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.0", @@ -876,7 +872,6 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.0" @@ -2151,7 +2146,6 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.26.2", @@ -2191,7 +2185,6 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", diff --git a/package.json b/package.json index 94c859a..80ac96f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "practice-exams-platform", - "version": "1.2.0", + "version": "1.3.0", "private": true, "engines": { "node": "20.x" diff --git a/public/sw.js.map b/public/sw.js.map index 3cb498b..0d6f22d 100644 --- a/public/sw.js.map +++ b/public/sw.js.map @@ -1 +1 @@ -{"version":3,"file":"sw.js","sources":["../../../../private/var/folders/46/z6lcqr1s05j43ppcygmcd6n00000gn/T/70173c6dd7fe01f4597ff5e792adc7bf/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/danieldanielecki/Downloads/Practice-Exams-Platform/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/danieldanielecki/Downloads/Practice-Exams-Platform/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/danieldanielecki/Downloads/Practice-Exams-Platform/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/danieldanielecki/Downloads/Practice-Exams-Platform/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({ request, response, event, state }) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) } return response } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sw.js","sources":["../../../../private/var/folders/46/z6lcqr1s05j43ppcygmcd6n00000gn/T/acd0a009b0235feafc4f7112c13d529c/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/danieldanielecki/Downloads/Practice-Exams-Platform/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/danieldanielecki/Downloads/Practice-Exams-Platform/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/danieldanielecki/Downloads/Practice-Exams-Platform/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/danieldanielecki/Downloads/Practice-Exams-Platform/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({ request, response, event, state }) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) } return response } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":"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"} \ No newline at end of file From 79ee9989eee53420396398d76011062a70dc517a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=85=20Daniel=20Danielecki?= Date: Mon, 7 Jul 2025 13:44:20 +0200 Subject: [PATCH 5/5] fix: make a more economic solution with a single container --- lib/graphql/cosmos-client.tsx | 10 +- lib/graphql/questionsDataSource.tsx | 142 +++++++++++++++++----------- lib/graphql/schemas.tsx | 1 + package.json | 3 +- public/sw.js.map | 2 +- 5 files changed, 94 insertions(+), 64 deletions(-) diff --git a/lib/graphql/cosmos-client.tsx b/lib/graphql/cosmos-client.tsx index 4172d60..31a7cb8 100644 --- a/lib/graphql/cosmos-client.tsx +++ b/lib/graphql/cosmos-client.tsx @@ -9,15 +9,15 @@ export const getDatabase = () => { return client.database(process.env.AZURE_COSMOSDB_DATABASE!); }; -export const getContainer = async (containerName: string) => { +export const getQuestionsContainer = async () => { const database = getDatabase(); // Try to create container if it doesn't exist try { const { container } = await database.containers.createIfNotExists({ - id: containerName, + id: "questions", partitionKey: { - paths: ["/id"], + paths: ["/examId"], }, }); return container; @@ -25,9 +25,9 @@ export const getContainer = async (containerName: string) => { // If container creation fails, try to get the existing container if (error.code === 409) { console.log( - `Container ${containerName} already exists, using existing container`, + `Container questions already exists, using existing container`, ); - return database.container(containerName); + return database.container("questions"); } else { console.error("Error creating container:", error); throw error; diff --git a/lib/graphql/questionsDataSource.tsx b/lib/graphql/questionsDataSource.tsx index b58e566..74a2ce6 100644 --- a/lib/graphql/questionsDataSource.tsx +++ b/lib/graphql/questionsDataSource.tsx @@ -1,6 +1,6 @@ import { Container } from "@azure/cosmos"; import { fetchQuestions } from "./repoQuestions"; -import { getContainer } from "./cosmos-client"; +import { getQuestionsContainer } from "./cosmos-client"; export const QuestionsDataSource = (container: Container) => { return { @@ -68,25 +68,48 @@ export const RepoQuestionsDataSource = (container: any) => { }; }; +// Helper function to extract exam ID from URL +const extractExamId = (link: string): string => { + const segments = link.split("/"); + return segments[segments.length - 3].replace(/-/g, "_").toLowerCase(); +}; + export const CombinedQuestionsDataSource = () => { return { async getQuestion(id: string, link: string) { try { - // Extract exam name from URL and create a safe container name - const segments = link.split("/"); - const examName = segments[segments.length - 3] - .replace(/-/g, "_") - .toLowerCase(); - const examContainer = await getContainer(examName); - - // Try GitHub first + const examId = extractExamId(link); + const questionsContainer = await getQuestionsContainer(); + + // Try Cosmos DB first (most efficient) + const querySpec = { + query: "SELECT * FROM c WHERE c.id = @id AND c.examId = @examId", + parameters: [ + { name: "@id", value: id }, + { name: "@examId", value: examId }, + ], + }; + const { resources: items } = await questionsContainer.items + .query(querySpec) + .fetchAll(); + + if (items.length > 0) { + return items[0]; + } + + // Fallback to GitHub if not found in database const questions = await fetchQuestions(link); if (questions) { const question = questions.find((q: any) => q.id === id); if (question) { - // Upload to exam-specific container + // Add examId to the question document and upload to database + const questionWithExamId = { + ...question, + examId: examId, + }; + try { - await examContainer.items.upsert(question); + await questionsContainer.items.upsert(questionWithExamId); } catch (err) { console.warn("Failed to upload question to Cosmos DB:", err); } @@ -94,15 +117,7 @@ export const CombinedQuestionsDataSource = () => { } } - // Fallback to Cosmos DB - const querySpec = { - query: "SELECT * FROM c WHERE c.id = @id", - parameters: [{ name: "@id", value: id }], - }; - const { resources: items } = await examContainer.items - .query(querySpec) - .fetchAll(); - return items[0]; + return null; } catch (err) { throw new Error("Error fetching question: " + err); } @@ -110,20 +125,33 @@ export const CombinedQuestionsDataSource = () => { async getQuestions(link: string) { try { - // Extract exam name from URL and create a safe container name - const segments = link.split("/"); - const examName = segments[segments.length - 3] - .replace(/-/g, "_") - .toLowerCase(); - const examContainer = await getContainer(examName); - - // Try GitHub first + const examId = extractExamId(link); + const questionsContainer = await getQuestionsContainer(); + + // Try Cosmos DB first + const querySpec = { + query: "SELECT VALUE COUNT(c.id) FROM c WHERE c.examId = @examId", + parameters: [{ name: "@examId", value: examId }], + }; + const { resources: items } = await questionsContainer.items + .query(querySpec) + .fetchAll(); + + if (items[0] > 0) { + return { count: items[0] }; + } + + // Fallback to GitHub if no questions found in database const questions = await fetchQuestions(link); if (questions) { - // Upload all questions to exam-specific container + // Upload all questions to database (only if they don't exist) try { for (const question of questions) { - await examContainer.items.upsert(question); + const questionWithExamId = { + ...question, + examId: examId, + }; + await questionsContainer.items.upsert(questionWithExamId); } } catch (err) { console.warn("Failed to upload questions to Cosmos DB:", err); @@ -131,14 +159,7 @@ export const CombinedQuestionsDataSource = () => { return { count: questions.length }; } - // Fallback to Cosmos DB - const querySpec = { - query: "SELECT VALUE COUNT(c.id) FROM c", - }; - const { resources: items } = await examContainer.items - .query(querySpec) - .fetchAll(); - return { count: items[0] }; + return { count: 0 }; } catch (err) { throw new Error("Error fetching questions: " + err); } @@ -146,23 +167,38 @@ export const CombinedQuestionsDataSource = () => { async getRandomQuestions(range: number, link: string) { try { - // Extract exam name from URL and create a safe container name - const segments = link.split("/"); - const examName = segments[segments.length - 3] - .replace(/-/g, "_") - .toLowerCase(); - const examContainer = await getContainer(examName); - - // Try GitHub first + const examId = extractExamId(link); + const questionsContainer = await getQuestionsContainer(); + + // Try Cosmos DB first + const querySpec = { + query: "SELECT * FROM c WHERE c.examId = @examId", + parameters: [{ name: "@examId", value: examId }], + }; + const { resources: items } = await questionsContainer.items + .query(querySpec) + .fetchAll(); + + if (items.length > 0) { + // Questions exist in database, return random selection + const shuffled = [...items].sort(() => 0.5 - Math.random()); + return shuffled.slice(0, range); + } + + // Fallback to GitHub if no questions found in database const questions = await fetchQuestions(link); if (questions) { const shuffled = [...questions].sort(() => 0.5 - Math.random()); const selected = shuffled.slice(0, range); - // Upload selected questions to exam-specific container + // Upload selected questions to database (only if they don't exist) try { for (const question of selected) { - await examContainer.items.upsert(question); + const questionWithExamId = { + ...question, + examId: examId, + }; + await questionsContainer.items.upsert(questionWithExamId); } } catch (err) { console.warn("Failed to upload questions to Cosmos DB:", err); @@ -171,15 +207,7 @@ export const CombinedQuestionsDataSource = () => { return selected; } - // Fallback to Cosmos DB - const querySpec = { - query: "SELECT * FROM c", - }; - const { resources: items } = await examContainer.items - .query(querySpec) - .fetchAll(); - const shuffled = [...items].sort(() => 0.5 - Math.random()); - return shuffled.slice(0, range); + return []; } catch (err) { throw new Error("Error fetching random questions: " + err); } diff --git a/lib/graphql/schemas.tsx b/lib/graphql/schemas.tsx index 6428fc8..e2828a5 100644 --- a/lib/graphql/schemas.tsx +++ b/lib/graphql/schemas.tsx @@ -14,6 +14,7 @@ const typeDefs = gql` question: String options: [Option] images: [Images] + examId: String } type Questions { count: Int diff --git a/package.json b/package.json index 80ac96f..319a9c4 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "postcss": "^8.4.21", "prettier": "^3.0.3", "tailwindcss": "^3.2.7", - "ts-jest": "^29.1.1" + "ts-jest": "^29.1.1", + "tsx": "^4.7.0" } } diff --git a/public/sw.js.map b/public/sw.js.map index 0d6f22d..4064675 100644 --- a/public/sw.js.map +++ b/public/sw.js.map @@ -1 +1 @@ -{"version":3,"file":"sw.js","sources":["../../../../private/var/folders/46/z6lcqr1s05j43ppcygmcd6n00000gn/T/acd0a009b0235feafc4f7112c13d529c/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/danieldanielecki/Downloads/Practice-Exams-Platform/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/danieldanielecki/Downloads/Practice-Exams-Platform/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/danieldanielecki/Downloads/Practice-Exams-Platform/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/danieldanielecki/Downloads/Practice-Exams-Platform/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({ request, response, event, state }) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) } return response } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sw.js","sources":["../../../../private/var/folders/46/z6lcqr1s05j43ppcygmcd6n00000gn/T/d6822973d61f3cff46f33c53d5e6100b/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/danieldanielecki/Downloads/Practice-Exams-Platform/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/danieldanielecki/Downloads/Practice-Exams-Platform/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/danieldanielecki/Downloads/Practice-Exams-Platform/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/danieldanielecki/Downloads/Practice-Exams-Platform/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({ request, response, event, state }) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) } return response } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":"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"} \ No newline at end of file