diff --git a/.github/workflows/meshmap.yml b/.github/workflows/meshmap.yml deleted file mode 100644 index e8baafe..0000000 --- a/.github/workflows/meshmap.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: MeshMap Screenshot Service -'on': - pull_request_target: - types: - - opened - - synchronize - - reopened - workflow_call: - inputs: - fileName: - description: Relative file path from the root directory - required: true - type: string - outputs: - resource_url: - description: The URL of the generated resource. - value: ${{ jobs.MeshMapScreenshot.outputs.resource_url }} -permissions: - actions: read - contents: write - security-events: write - statuses: write - pull-requests: write - id-token: write -jobs: - MeshMapScreenshot: - runs-on: ubuntu-latest - outputs: - resource_url: ${{ steps.test_result.outputs.resource_url }} - steps: - - name: Set PR number - run: | - export pull_number=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") - echo "PULL_NO=$pull_number" >> $GITHUB_ENV - - uses: actions/checkout@v4 - - uses: actions/checkout@v4 - with: - path: action - repository: layer5labs/meshmap-snapshot - - id: test_result - uses: ${GITHUB_REF/refs/tags//} - with: - githubToken: ${{ secrets.GITHUB_TOKEN }} - mesheryToken: ${{ secrets.MESHERY_TOKEN }} - prNumber: ${{ env.PULL_NO }} - application_type: Kubernetes Manifest - filePath: ${{ inputs.fileName == '' && '.github/workflows/deploy-web.yml' || inputs.fileName }} diff --git a/apps/boilerplate-generator/test/directory.test.ts b/apps/boilerplate-generator/test/directory.test.ts index 9b8e6e2..be66d24 100644 --- a/apps/boilerplate-generator/test/directory.test.ts +++ b/apps/boilerplate-generator/test/directory.test.ts @@ -6,6 +6,80 @@ dotenv.config(); const problemsPath: string = process.env.PROBLEMS_DIR_PATH || ''; +const tags = [ + 'ARRAY', + 'STRING', + 'HASH_TABLE', + 'DYNAMIC_PROGRAMMING', + 'MATH', + 'SORTING', + 'GREEDY', + 'DEPTH_FIRST_SEARCH', + 'DATABASE', + 'BINARY_SEARCH', + 'BREADTH_FIRST_SEARCH', + 'TREE', + 'MATRIX', + 'BIT_MANIPULATION', + 'TWO_POINTERS', + 'BINARY_TREE', + 'HEAP', + 'PREFIX_SUM', + 'STACK', + 'SIMULATION', + 'GRAPH', + 'COUNTING', + 'DESIGN', + 'SLIDING_WINDOW', + 'BACKTRACKING', + 'ENUMERATION', + 'UNION_FIND', + 'LINKED_LIST', + 'ORDERED_SET', + 'MONOTONIC_STACK', + 'NUMBER_THEORY', + 'TRIE', + 'SEGMENT_TREE', + 'DIVIDE_AND_CONQUER', + 'QUEUE', + 'RECURSION', + 'BITMASK', + 'BINARY_SEARCH_TREE', + 'GEOMETRY', + 'MEMOIZATION', + 'BINARY_INDEXED_TREE', + 'HASH_FUNCTION', + 'COMBINATORICS', + 'TOPOLOGICAL_SORT', + 'STRING_MATCHING', + 'SHORTEST_PATH', + 'ROLLING_HASH', + 'GAME_THEORY', + 'INTERACTIVE', + 'DATA_STREAM', + 'BRAINTEASER', + 'MONOTONIC_QUEUE', + 'RANDOMIZED', + 'MERGE_SORT', + 'ITERATOR', + 'DOUBLY_LINKED_LIST', + 'CONCURRENCY', + 'PROBABILITY_AND_STATISTICS', + 'QUICKSELECT', + 'SUFFIX_ARRAY', + 'COUNTING_SORT', + 'BUCKET_SORT', + 'MINIMUM_SPANNING_TREE', + 'SHELL', + 'LINE_SWEEP', + 'RESERVOIR_SAMPLING', + 'STRONGLY_CONNECTED_COMPONENT', + 'EULERIAN_CIRCUIT', + 'RADIX_SORT', + 'REJECTION_SAMPLING', + 'INCONSISTENT_COMPARISON', +]; + // check if problem directory exist or not describe('Problem folder exist', () => { it('should have problems folder', () => { @@ -61,6 +135,37 @@ describe('Problem folder exist', () => { }); }) }); + + fs.readdirSync(problemsPath).filter(file => { + it(`${file} should have tags.md file`, () => { + const problemFolderPath = path.join(problemsPath, file); + const tagsPath = path.join(problemFolderPath, 'tags.md'); + expect(fs.existsSync(tagsPath)).toBe(true); + }); + }); + + fs.readdirSync(problemsPath).filter(file => { + it(`${file} tags should belong to predefined tags`, () => { + const problemFolderPath = path.join(problemsPath, file); + const tagsPath = path.join(problemFolderPath, 'tags.md'); + const tagsContent = fs.readFileSync(tagsPath, 'utf8'); + const fetchedTags = tagsContent.split('\n').map(tag => tag.trim()); + fetchedTags.forEach(tag => { + expect(tags.includes(tag)).toBe(true); + }); + }); + //there should be no empty spaces && one line should have only one word which is predefined + it(`${file} tags should not have empty spaces`, () => { + const problemFolderPath = path.join(problemsPath, file); + const tagsPath = path.join(problemFolderPath, 'tags.md'); + const tagsContent = fs.readFileSync(tagsPath, 'utf8'); + const fetchedTags = tagsContent.split('\n').map(tag => tag.trim()); + fetchedTags.forEach(tag => { + expect(tag).not.toBe(''); + }); + }); + }); + }); diff --git a/apps/problems/Broken-SubArray/tags.md b/apps/problems/Broken-SubArray/tags.md new file mode 100644 index 0000000..1e65c55 --- /dev/null +++ b/apps/problems/Broken-SubArray/tags.md @@ -0,0 +1,3 @@ +ARRAY +STRING +HASH_TABLE \ No newline at end of file diff --git a/apps/problems/Calculate-Fibonacci/tags.md b/apps/problems/Calculate-Fibonacci/tags.md new file mode 100644 index 0000000..397a358 --- /dev/null +++ b/apps/problems/Calculate-Fibonacci/tags.md @@ -0,0 +1,2 @@ + ARRAY + GREEDY \ No newline at end of file diff --git a/apps/problems/Check-Palindrome/tags.md b/apps/problems/Check-Palindrome/tags.md new file mode 100644 index 0000000..9e0f090 --- /dev/null +++ b/apps/problems/Check-Palindrome/tags.md @@ -0,0 +1,5 @@ + ARRAY + STRING + MATH + SORTING + GREEDY \ No newline at end of file diff --git a/apps/problems/Find-Median/tags.md b/apps/problems/Find-Median/tags.md new file mode 100644 index 0000000..e687646 --- /dev/null +++ b/apps/problems/Find-Median/tags.md @@ -0,0 +1,3 @@ + ARRAY + STRING + GREEDY \ No newline at end of file diff --git a/apps/problems/Find-Prime-Numbers/tags.md b/apps/problems/Find-Prime-Numbers/tags.md new file mode 100644 index 0000000..420fd23 --- /dev/null +++ b/apps/problems/Find-Prime-Numbers/tags.md @@ -0,0 +1,7 @@ + ARRAY + STRING + HASH_TABLE + DYNAMIC_PROGRAMMING + MATH + SORTING + GREEDY \ No newline at end of file diff --git a/apps/problems/Intersting-Arrays/tags.md b/apps/problems/Intersting-Arrays/tags.md new file mode 100644 index 0000000..ccdc9d1 --- /dev/null +++ b/apps/problems/Intersting-Arrays/tags.md @@ -0,0 +1,5 @@ + ARRAY + STRING + HASH_TABLE + SORTING + GREEDY \ No newline at end of file diff --git a/apps/problems/Kth-Smallest/tags.md b/apps/problems/Kth-Smallest/tags.md new file mode 100644 index 0000000..9e0f090 --- /dev/null +++ b/apps/problems/Kth-Smallest/tags.md @@ -0,0 +1,5 @@ + ARRAY + STRING + MATH + SORTING + GREEDY \ No newline at end of file diff --git a/apps/problems/Matrix-Paths/tags.md b/apps/problems/Matrix-Paths/tags.md new file mode 100644 index 0000000..276fd82 --- /dev/null +++ b/apps/problems/Matrix-Paths/tags.md @@ -0,0 +1,5 @@ + ARRAY + STRING + HASH_TABLE + DYNAMIC_PROGRAMMING + MATH \ No newline at end of file diff --git a/apps/problems/Max-Element/tags.md b/apps/problems/Max-Element/tags.md new file mode 100644 index 0000000..2909fa1 --- /dev/null +++ b/apps/problems/Max-Element/tags.md @@ -0,0 +1,5 @@ + ARRAY + DYNAMIC_PROGRAMMING + MATH + SORTING + GREEDY \ No newline at end of file diff --git a/apps/problems/Merge-Sorted-Arrays/tags.md b/apps/problems/Merge-Sorted-Arrays/tags.md new file mode 100644 index 0000000..420fd23 --- /dev/null +++ b/apps/problems/Merge-Sorted-Arrays/tags.md @@ -0,0 +1,7 @@ + ARRAY + STRING + HASH_TABLE + DYNAMIC_PROGRAMMING + MATH + SORTING + GREEDY \ No newline at end of file diff --git a/apps/problems/Reverse-String/tags.md b/apps/problems/Reverse-String/tags.md new file mode 100644 index 0000000..420fd23 --- /dev/null +++ b/apps/problems/Reverse-String/tags.md @@ -0,0 +1,7 @@ + ARRAY + STRING + HASH_TABLE + DYNAMIC_PROGRAMMING + MATH + SORTING + GREEDY \ No newline at end of file diff --git a/apps/problems/Software-Dev/tags.md b/apps/problems/Software-Dev/tags.md new file mode 100644 index 0000000..420fd23 --- /dev/null +++ b/apps/problems/Software-Dev/tags.md @@ -0,0 +1,7 @@ + ARRAY + STRING + HASH_TABLE + DYNAMIC_PROGRAMMING + MATH + SORTING + GREEDY \ No newline at end of file diff --git a/apps/problems/Sort-Array/tags.md b/apps/problems/Sort-Array/tags.md new file mode 100644 index 0000000..420fd23 --- /dev/null +++ b/apps/problems/Sort-Array/tags.md @@ -0,0 +1,7 @@ + ARRAY + STRING + HASH_TABLE + DYNAMIC_PROGRAMMING + MATH + SORTING + GREEDY \ No newline at end of file diff --git a/apps/problems/Two-Sum/tags.md b/apps/problems/Two-Sum/tags.md new file mode 100644 index 0000000..042637b --- /dev/null +++ b/apps/problems/Two-Sum/tags.md @@ -0,0 +1,4 @@ + ARRAY + MATH + SORTING + GREEDY \ No newline at end of file diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 97f4ae9..074dc9a 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -64,6 +64,13 @@ model Problem { contests ContestProblem[] submissions Submission[] defaultCode DefaultCode[] + tags Tag[] +} + +model Tag { + id String @id @default(cuid()) + name String @unique + problems Problem[] } model DefaultCode { diff --git a/packages/db/prisma/updateQuestion.ts b/packages/db/prisma/updateQuestion.ts index 24eeabf..79cbcc1 100644 --- a/packages/db/prisma/updateQuestion.ts +++ b/packages/db/prisma/updateQuestion.ts @@ -1,74 +1,153 @@ import { LANGUAGE_MAPPING } from "@repo/common/language"; import fs from "fs"; import prismaClient from "../src/index"; - const MOUNT_PATH = process.env.MOUNT_PATH ?? "../../../apps/problems"; +/* + +DETAILS ABOUT RETRY LOGIC : + +we implemented retry logic because when we are trying to upsert a problem or default code, +it may happen that the prisma client is not able to connect to the database and it throws an error. +In such cases, we are retrying the operation for a maximum of 3 times with a delay of 1 second between each retry. +If the operation is successful within 3 retries, then we return the result. +If the operation fails even after 3 retries, then we throw an error. + +*/ +const MAX_RETRIES = 3; +const RETRY_DELAY = 1000; // 1 second + + function promisifedReadFile(path: string): Promise { - return new Promise((resolve, reject) => { - fs.readFile(path, "utf8", (err, data) => { - if (err) { - reject(err); - } - resolve(data); + return new Promise((resolve, reject) => { + fs.readFile(path, "utf8", (err, data) => { + if (err) { + reject(err); + } + resolve(data); + }); }); - }); } -async function main(problemSlug: string, problemTitle: string) { - const problemStatement = await promisifedReadFile( - `${MOUNT_PATH}/${problemSlug}/Problem.md` - ); - - const problem = await prismaClient.problem.upsert({ - where: { - slug: problemSlug, - }, - create: { - title: problemSlug, - slug: problemSlug, - description: problemStatement, - hidden: false, - }, - update: { - description: problemStatement, - }, - }); - - await Promise.all( - Object.keys(LANGUAGE_MAPPING).map(async (language) => { - const code = await promisifedReadFile( - `${MOUNT_PATH}/${problemSlug}/boilerplate/function.${language}` - ); - - await prismaClient.defaultCode.upsert({ - where: { - problemId_languageId: { - problemId: problem.id, - languageId: LANGUAGE_MAPPING[language].internal, - }, - }, - create: { - problemId: problem.id, - languageId: LANGUAGE_MAPPING[language].internal, - code, - }, - update: { - code, - }, - }); - }) - ); +async function upsertProblemWithRetry(problemSlug: string, problemStatement: string, tagsArray: string[]) { + let retries = 0; + + while (retries < MAX_RETRIES) { + try { + const connectOrCreateTags = tagsArray.map(tag => ({ + where: { name: tag }, + create: { name: tag } + })); + + const problem = await prismaClient.problem.upsert({ + where: { + slug: problemSlug, + }, + create: { + title: problemSlug.replace(/-/g, ' '), + slug: problemSlug, + description: problemStatement, + hidden: false, + tags: { + connectOrCreate: connectOrCreateTags + } + }, + update: { + title: problemSlug.replace(/-/g, ' '), + slug: problemSlug, + description: problemStatement, + tags: { + connectOrCreate: connectOrCreateTags + } + }, + }); + + return problem; + } catch (error: any) { + if (error.code === 'P2024') { + retries++; + console.log(`Retrying upsert operation for problem (${retries}/${MAX_RETRIES})...`); + await new Promise(resolve => setTimeout(resolve, RETRY_DELAY)); + } else { + throw error; + } + } + } + + throw new Error('Failed to upsert problem after multiple retries.'); } -export function addProblemsInDB() { - fs.readdir(MOUNT_PATH, (err, dirs) => { - if (err) { - console.error("Error reading directory:", err); - return; +async function upsertDefaultCodeWithRetry(problemId: string, language: string, code: string) { + let retries = 0; + + while (retries < MAX_RETRIES) { + try { + await prismaClient.defaultCode.upsert({ + where: { + problemId_languageId: { + problemId, + languageId: LANGUAGE_MAPPING[language].internal, + }, + }, + create: { + problemId, + languageId: LANGUAGE_MAPPING[language].internal, + code, + }, + update: { + code, + }, + }); + + return; + } catch (error: any) { + if (error.code === 'P2024') { + retries++; + console.log(`Retrying upsert operation for default code (${retries}/${MAX_RETRIES})...`); + await new Promise(resolve => setTimeout(resolve, RETRY_DELAY)); + } else { + throw error; + } + } + } + + throw new Error('Failed to upsert default code after multiple retries.'); +} + +async function main(problemSlug: string) { + try { + const problemStatement = await promisifedReadFile( + `${MOUNT_PATH}/${problemSlug}/Problem.md` + ); + const tags = await promisifedReadFile( + `${MOUNT_PATH}/${problemSlug}/tags.md` + ); + + const tagsArray = tags.split("\n").map(tag => tag.trim()); + + const problem = await upsertProblemWithRetry(problemSlug, problemStatement, tagsArray); + + await Promise.all( + Object.keys(LANGUAGE_MAPPING).map(async (language) => { + const code = await promisifedReadFile( + `${MOUNT_PATH}/${problemSlug}/boilerplate/function.${language}` + ); + await upsertDefaultCodeWithRetry(problem.id, language, code); + }) + ); + } catch (error: any) { + console.error('Error in main function:', error); } - dirs.forEach(async (dir) => { - await main(dir, dir); - }); - }); } + +export function addProblemsInDB() { + fs.readdir(MOUNT_PATH, (err, dirs) => { + if (err) { + console.error("Error reading directory:", err); + return; + } + dirs.forEach(async (dir) => { + await main(dir); + }); + }); +} \ No newline at end of file