From 6369d5f4627065f5285feecb54adbdde16bfd998 Mon Sep 17 00:00:00 2001 From: Predrag Stojadinovic Date: Sat, 11 May 2024 18:43:55 +0200 Subject: [PATCH 1/8] chore: confluence data connector can now handle custom urls, in addition to default {subdomain}.atlassian.net ones --- .../utils/extensions/Confluence/index.js | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/collector/utils/extensions/Confluence/index.js b/collector/utils/extensions/Confluence/index.js index 5a473f654cc..63094a1f99e 100644 --- a/collector/utils/extensions/Confluence/index.js +++ b/collector/utils/extensions/Confluence/index.js @@ -2,20 +2,30 @@ const fs = require("fs"); const path = require("path"); const { default: slugify } = require("slugify"); const { v4 } = require("uuid"); +const UrlPattern = require("url-pattern"); const { writeToServerDocuments } = require("../../files"); const { tokenizeString } = require("../../tokenizer"); -const { - ConfluencePagesLoader, -} = require("langchain/document_loaders/web/confluence"); +const { ConfluencePagesLoader } = require("langchain/document_loaders/web/confluence"); function validSpaceUrl(spaceUrl = "") { - const UrlPattern = require("url-pattern"); - const pattern = new UrlPattern( - "https\\://(:subdomain).atlassian.net/wiki/spaces/(:spaceKey)*" - ); - const match = pattern.match(spaceUrl); - if (!match) return { valid: false, result: null }; - return { valid: true, result: match }; + // Atlassian default URL match + const atlassianPattern = new UrlPattern("https\\://(:subdomain).atlassian.net/wiki/spaces/(:spaceKey)/*"); + const atlassianMatch = atlassianPattern.match(spaceUrl); + if (atlassianMatch) { + return { valid: true, result: atlassianMatch }; + } + + // Custom Confluence URL match + const customPattern = new UrlPattern("https\\://(:subdomain.):domain.:tld/wiki/spaces/(:spaceKey)/*"); + const customMatch = customPattern.match(spaceUrl); + if (customMatch) { + customMatch.customDomain = (customMatch.subdomain ? `${customMatch.subdomain}.` : "") + // + (`${customMatch.domain}.${customMatch.tld}`); + return { valid: true, result: customMatch, custom: true }; + } + + // No match + return { valid: false, result: null }; } async function loadConfluence({ pageUrl, username, accessToken }) { @@ -31,15 +41,19 @@ async function loadConfluence({ pageUrl, username, accessToken }) { if (!validSpace.result) { return { success: false, - reason: - "Confluence space URL is not in the expected format of https://domain.atlassian.net/wiki/space/~SPACEID/*", + reason: "Confluence space URL is not in the expected format of https://domain.atlassian.net/wiki/space/~SPACEID/* or https://customDomain/wiki/space/~SPACEID/*", }; } - const { subdomain, spaceKey } = validSpace.result; - console.log(`-- Working Confluence ${subdomain}.atlassian.net --`); + const { subdomain, customDomain, spaceKey } = validSpace.result; + let baseUrl = `https://${subdomain}.atlassian.net/wiki`; + if (customDomain) { + baseUrl = `https://${customDomain}/wiki`; + } + + console.log(`-- Working Confluence ${baseUrl} --`); const loader = new ConfluencePagesLoader({ - baseUrl: `https://${subdomain}.atlassian.net/wiki`, + baseUrl, spaceKey, username, accessToken, From c73c5087e2f7a724d00a1186922f46c07622bc57 Mon Sep 17 00:00:00 2001 From: Predrag Stojadinovic Date: Sat, 11 May 2024 19:04:28 +0200 Subject: [PATCH 2/8] chore: formatting as per yarn lint --- .../utils/extensions/Confluence/index.js | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/collector/utils/extensions/Confluence/index.js b/collector/utils/extensions/Confluence/index.js index 63094a1f99e..5bb44deeb75 100644 --- a/collector/utils/extensions/Confluence/index.js +++ b/collector/utils/extensions/Confluence/index.js @@ -5,22 +5,29 @@ const { v4 } = require("uuid"); const UrlPattern = require("url-pattern"); const { writeToServerDocuments } = require("../../files"); const { tokenizeString } = require("../../tokenizer"); -const { ConfluencePagesLoader } = require("langchain/document_loaders/web/confluence"); +const { + ConfluencePagesLoader, +} = require("langchain/document_loaders/web/confluence"); function validSpaceUrl(spaceUrl = "") { // Atlassian default URL match - const atlassianPattern = new UrlPattern("https\\://(:subdomain).atlassian.net/wiki/spaces/(:spaceKey)/*"); + const atlassianPattern = new UrlPattern( + "https\\://(:subdomain).atlassian.net/wiki/spaces/(:spaceKey)/*" + ); const atlassianMatch = atlassianPattern.match(spaceUrl); if (atlassianMatch) { return { valid: true, result: atlassianMatch }; } // Custom Confluence URL match - const customPattern = new UrlPattern("https\\://(:subdomain.):domain.:tld/wiki/spaces/(:spaceKey)/*"); + const customPattern = new UrlPattern( + "https\\://(:subdomain.):domain.:tld/wiki/spaces/(:spaceKey)/*" + ); const customMatch = customPattern.match(spaceUrl); if (customMatch) { - customMatch.customDomain = (customMatch.subdomain ? `${customMatch.subdomain}.` : "") + // - (`${customMatch.domain}.${customMatch.tld}`); + customMatch.customDomain = + (customMatch.subdomain ? `${customMatch.subdomain}.` : "") + // + `${customMatch.domain}.${customMatch.tld}`; return { valid: true, result: customMatch, custom: true }; } @@ -41,7 +48,8 @@ async function loadConfluence({ pageUrl, username, accessToken }) { if (!validSpace.result) { return { success: false, - reason: "Confluence space URL is not in the expected format of https://domain.atlassian.net/wiki/space/~SPACEID/* or https://customDomain/wiki/space/~SPACEID/*", + reason: + "Confluence space URL is not in the expected format of https://domain.atlassian.net/wiki/space/~SPACEID/* or https://customDomain/wiki/space/~SPACEID/*", }; } From 4991aa52c0464bdbb56293a515505b521b49baca Mon Sep 17 00:00:00 2001 From: Predrag Stojadinovic Date: Fri, 17 May 2024 13:23:44 +0200 Subject: [PATCH 3/8] chore: fixing the human readable confluence url fetch baseUrl --- .../utils/extensions/Confluence/index.js | 95 ++++++++----------- 1 file changed, 38 insertions(+), 57 deletions(-) diff --git a/collector/utils/extensions/Confluence/index.js b/collector/utils/extensions/Confluence/index.js index 1732d0726fe..d9690f2ad2d 100644 --- a/collector/utils/extensions/Confluence/index.js +++ b/collector/utils/extensions/Confluence/index.js @@ -9,31 +9,34 @@ const { ConfluencePagesLoader, } = require("langchain/document_loaders/web/confluence"); +function urlMatchesPatter(url, patter) { + const urlPattern = new UrlPattern(patter); + return urlPattern.match(url); +} + +function generateCustomDomain({ subdomain, domain, tld }) { + return (subdomain ? `${subdomain}.` : "") + `${domain}.${tld}`; +} + function validSpaceUrl(spaceUrl = "") { // Atlassian default URL match - const atlassianPattern = new UrlPattern( - "https\\://(:subdomain).atlassian.net/wiki/spaces/(:spaceKey)*" - ); - const atlassianMatch = atlassianPattern.match(spaceUrl); + const atlassianMatch = urlMatchesPatter(spaceUrl, "https\\://(:subdomain).atlassian.net/wiki/spaces/(:spaceKey)*"); if (atlassianMatch) { return { valid: true, result: atlassianMatch }; } - let customMatch = null; - [ - "https\\://(:subdomain.):domain.:tld/wiki/spaces/(:spaceKey)*", // Custom Confluence space - "https\\://(:subdomain.):domain.:tld/display/(:spaceKey)*", // Custom Confluence space + Human-readable space tag. - ].forEach((matchPattern) => { - if (!!customMatch) return; - const pattern = new UrlPattern(matchPattern); - customMatch = pattern.match(spaceUrl); - }); - + // Custom URL match + const customMatch = urlMatchesPatter(spaceUrl, "https\\://(:subdomain.):domain.:tld/wiki/spaces/(:spaceKey)*"); if (customMatch) { - customMatch.customDomain = - (customMatch.subdomain ? `${customMatch.subdomain}.` : "") + // - `${customMatch.domain}.${customMatch.tld}`; - return { valid: true, result: customMatch, custom: true }; + customMatch.customDomain = generateCustomDomain(customMatch); + return { valid: true, result: customMatch, humanReadable: false }; + } + + // Human Readable URL match + const humanReadableMatch = urlMatchesPatter(spaceUrl, "https\\://(:subdomain.):domain.:tld/display/(:spaceKey)*"); + if (humanReadableMatch) { + humanReadableMatch.customDomain = generateCustomDomain(humanReadableMatch); + return { valid: true, result: humanReadableMatch, humanReadable: true }; } // No match @@ -44,32 +47,29 @@ async function loadConfluence({ pageUrl, username, accessToken }) { if (!pageUrl || !username || !accessToken) { return { success: false, - reason: - "You need either a username and access token, or a personal access token (PAT), to use the Confluence connector.", + reason: "You need either a username and access token, or a personal access token (PAT), to use the Confluence connector.", }; } const validSpace = validSpaceUrl(pageUrl); - if (!validSpace.result) { + const { result: validSpaceResult, humanReadable } = validSpace; + if (!validSpaceResult) { return { success: false, - reason: - "Confluence space URL is not in the expected format of https://domain.atlassian.net/wiki/space/~SPACEID/* or https://customDomain/wiki/space/~SPACEID/*", + reason: "Confluence space URL is not in the expected format of one of https://domain.atlassian.net/wiki/space/~SPACEID/* or https://customDomain/wiki/space/~SPACEID/* or https://customDomain/display/~SPACEID/*", }; } - const { subdomain, customDomain, spaceKey } = validSpace.result; - let baseUrl = `https://${subdomain}.atlassian.net/wiki`; + const { subdomain, customDomain, spaceKey } = validSpaceResult; + let subpath = humanReadable ? `` : `/wiki`; + let baseUrl = `https://${subdomain}.atlassian.net${subpath}`; if (customDomain) { - baseUrl = `https://${customDomain}/wiki`; + baseUrl = `https://${customDomain}${subpath}`; } console.log(`-- Working Confluence ${baseUrl} --`); const loader = new ConfluencePagesLoader({ - baseUrl, - spaceKey, - username, - accessToken, + baseUrl, spaceKey, username, accessToken, }); const { docs, error } = await loader @@ -79,8 +79,7 @@ async function loadConfluence({ pageUrl, username, accessToken }) { }) .catch((e) => { return { - docs: [], - error: e.message?.split("Error:")?.[1] || e.message, + docs: [], error: e.message?.split("Error:")?.[1] || e.message, }; }); @@ -90,20 +89,11 @@ async function loadConfluence({ pageUrl, username, accessToken }) { reason: error ?? "No pages found for that Confluence space.", }; } - const outFolder = slugify( - `${subdomain}-confluence-${v4().slice(0, 4)}` - ).toLowerCase(); + const outFolder = slugify(`${subdomain}-confluence-${v4().slice(0, 4)}`).toLowerCase(); - const outFolderPath = - process.env.NODE_ENV === "development" - ? path.resolve( - __dirname, - `../../../../server/storage/documents/${outFolder}` - ) - : path.resolve(process.env.STORAGE_DIR, `documents/${outFolder}`); + const outFolderPath = process.env.NODE_ENV === "development" ? path.resolve(__dirname, `../../../../server/storage/documents/${outFolder}`) : path.resolve(process.env.STORAGE_DIR, `documents/${outFolder}`); - if (!fs.existsSync(outFolderPath)) - fs.mkdirSync(outFolderPath, { recursive: true }); + if (!fs.existsSync(outFolderPath)) fs.mkdirSync(outFolderPath, { recursive: true }); docs.forEach((doc) => { if (!doc.pageContent) return; @@ -122,22 +112,13 @@ async function loadConfluence({ pageUrl, username, accessToken }) { token_count_estimate: tokenizeString(doc.pageContent).length, }; - console.log( - `[Confluence Loader]: Saving ${doc.metadata.title} to ${outFolder}` - ); - writeToServerDocuments( - data, - `${slugify(doc.metadata.title)}-${data.id}`, - outFolderPath - ); + console.log(`[Confluence Loader]: Saving ${doc.metadata.title} to ${outFolder}`); + writeToServerDocuments(data, `${slugify(doc.metadata.title)}-${data.id}`, outFolderPath); }); return { - success: true, - reason: null, - data: { - spaceKey, - destination: outFolder, + success: true, reason: null, data: { + spaceKey, destination: outFolder, }, }; } From 23608642773559d480373ea1397d802dd7efa862 Mon Sep 17 00:00:00 2001 From: Predrag Stojadinovic Date: Fri, 17 May 2024 13:29:09 +0200 Subject: [PATCH 4/8] chore: fixing the human readable confluence url fetch baseUrl --- .../utils/extensions/Confluence/index.js | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/collector/utils/extensions/Confluence/index.js b/collector/utils/extensions/Confluence/index.js index d9690f2ad2d..8c0b9632515 100644 --- a/collector/utils/extensions/Confluence/index.js +++ b/collector/utils/extensions/Confluence/index.js @@ -47,7 +47,8 @@ async function loadConfluence({ pageUrl, username, accessToken }) { if (!pageUrl || !username || !accessToken) { return { success: false, - reason: "You need either a username and access token, or a personal access token (PAT), to use the Confluence connector.", + reason: + "You need either a username and access token, or a personal access token (PAT), to use the Confluence connector.", }; } @@ -56,7 +57,8 @@ async function loadConfluence({ pageUrl, username, accessToken }) { if (!validSpaceResult) { return { success: false, - reason: "Confluence space URL is not in the expected format of one of https://domain.atlassian.net/wiki/space/~SPACEID/* or https://customDomain/wiki/space/~SPACEID/* or https://customDomain/display/~SPACEID/*", + reason: + "Confluence space URL is not in the expected format of one of https://domain.atlassian.net/wiki/space/~SPACEID/* or https://customDomain/wiki/space/~SPACEID/* or https://customDomain/display/~SPACEID/*", }; } @@ -69,7 +71,10 @@ async function loadConfluence({ pageUrl, username, accessToken }) { console.log(`-- Working Confluence ${baseUrl} --`); const loader = new ConfluencePagesLoader({ - baseUrl, spaceKey, username, accessToken, + baseUrl, + spaceKey, + username, + accessToken, }); const { docs, error } = await loader @@ -79,7 +84,8 @@ async function loadConfluence({ pageUrl, username, accessToken }) { }) .catch((e) => { return { - docs: [], error: e.message?.split("Error:")?.[1] || e.message, + docs: [], + error: e.message?.split("Error:")?.[1] || e.message, }; }); @@ -89,11 +95,18 @@ async function loadConfluence({ pageUrl, username, accessToken }) { reason: error ?? "No pages found for that Confluence space.", }; } - const outFolder = slugify(`${subdomain}-confluence-${v4().slice(0, 4)}`).toLowerCase(); + const outFolder = slugify( + `${subdomain}-confluence-${v4().slice(0, 4)}`, + ).toLowerCase(); - const outFolderPath = process.env.NODE_ENV === "development" ? path.resolve(__dirname, `../../../../server/storage/documents/${outFolder}`) : path.resolve(process.env.STORAGE_DIR, `documents/${outFolder}`); + const outFolderPath = + process.env.NODE_ENV === "development" + ? path.resolve(__dirname, `../../../../server/storage/documents/${outFolder}`) + : path.resolve(process.env.STORAGE_DIR, `documents/${outFolder}`); - if (!fs.existsSync(outFolderPath)) fs.mkdirSync(outFolderPath, { recursive: true }); + if (!fs.existsSync(outFolderPath)) { + fs.mkdirSync(outFolderPath, { recursive: true }); + } docs.forEach((doc) => { if (!doc.pageContent) return; @@ -112,13 +125,22 @@ async function loadConfluence({ pageUrl, username, accessToken }) { token_count_estimate: tokenizeString(doc.pageContent).length, }; - console.log(`[Confluence Loader]: Saving ${doc.metadata.title} to ${outFolder}`); - writeToServerDocuments(data, `${slugify(doc.metadata.title)}-${data.id}`, outFolderPath); + console.log( + `[Confluence Loader]: Saving ${doc.metadata.title} to ${outFolder}`, + ); + writeToServerDocuments( + data, + `${slugify(doc.metadata.title)}-${data.id}`, + outFolderPath, + ); }); return { - success: true, reason: null, data: { - spaceKey, destination: outFolder, + success: true, + reason: null, + data: { + spaceKey, + destination: outFolder, }, }; } From b0497d3cafac535ac564df09573df0b90288d676 Mon Sep 17 00:00:00 2001 From: Predrag Stojadinovic Date: Fri, 17 May 2024 13:30:31 +0200 Subject: [PATCH 5/8] chore: fixing the human readable confluence url fetch baseUrl --- collector/utils/extensions/Confluence/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/collector/utils/extensions/Confluence/index.js b/collector/utils/extensions/Confluence/index.js index 8c0b9632515..00428d5bb61 100644 --- a/collector/utils/extensions/Confluence/index.js +++ b/collector/utils/extensions/Confluence/index.js @@ -96,7 +96,7 @@ async function loadConfluence({ pageUrl, username, accessToken }) { }; } const outFolder = slugify( - `${subdomain}-confluence-${v4().slice(0, 4)}`, + `${subdomain}-confluence-${v4().slice(0, 4)}` ).toLowerCase(); const outFolderPath = @@ -126,12 +126,12 @@ async function loadConfluence({ pageUrl, username, accessToken }) { }; console.log( - `[Confluence Loader]: Saving ${doc.metadata.title} to ${outFolder}`, + `[Confluence Loader]: Saving ${doc.metadata.title} to ${outFolder}` ); writeToServerDocuments( data, `${slugify(doc.metadata.title)}-${data.id}`, - outFolderPath, + outFolderPath ); }); From 0fb5fa29ad36d3f1fd0df7f2b13053cad3af9d2f Mon Sep 17 00:00:00 2001 From: Predrag Stojadinovic Date: Fri, 17 May 2024 13:31:59 +0200 Subject: [PATCH 6/8] chore: fixing the human readable confluence url fetch baseUrl --- collector/utils/extensions/Confluence/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/collector/utils/extensions/Confluence/index.js b/collector/utils/extensions/Confluence/index.js index 00428d5bb61..c61e0b3e461 100644 --- a/collector/utils/extensions/Confluence/index.js +++ b/collector/utils/extensions/Confluence/index.js @@ -101,7 +101,10 @@ async function loadConfluence({ pageUrl, username, accessToken }) { const outFolderPath = process.env.NODE_ENV === "development" - ? path.resolve(__dirname, `../../../../server/storage/documents/${outFolder}`) + ? path.resolve( + __dirname, + `../../../../server/storage/documents/${outFolder}` + ) : path.resolve(process.env.STORAGE_DIR, `documents/${outFolder}`); if (!fs.existsSync(outFolderPath)) { From 2da4328c93992251b7b25f67ade2ace3c1ade236 Mon Sep 17 00:00:00 2001 From: Predrag Stojadinovic Date: Fri, 17 May 2024 13:33:09 +0200 Subject: [PATCH 7/8] chore: fixing the human readable confluence url fetch baseUrl --- collector/utils/extensions/Confluence/index.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/collector/utils/extensions/Confluence/index.js b/collector/utils/extensions/Confluence/index.js index c61e0b3e461..0dc845dce56 100644 --- a/collector/utils/extensions/Confluence/index.js +++ b/collector/utils/extensions/Confluence/index.js @@ -102,14 +102,13 @@ async function loadConfluence({ pageUrl, username, accessToken }) { const outFolderPath = process.env.NODE_ENV === "development" ? path.resolve( - __dirname, - `../../../../server/storage/documents/${outFolder}` - ) + __dirname, + `../../../../server/storage/documents/${outFolder}` + ) : path.resolve(process.env.STORAGE_DIR, `documents/${outFolder}`); - if (!fs.existsSync(outFolderPath)) { + if (!fs.existsSync(outFolderPath)) fs.mkdirSync(outFolderPath, { recursive: true }); - } docs.forEach((doc) => { if (!doc.pageContent) return; From 8ef511c7ba423e25c70746dccf22c3e5939eeeb8 Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Mon, 17 Jun 2024 16:02:59 -0700 Subject: [PATCH 8/8] refactor implementation of various types of Confluence URL patterns --- .../utils/extensions/Confluence/index.js | 136 ++++++++++++------ 1 file changed, 92 insertions(+), 44 deletions(-) diff --git a/collector/utils/extensions/Confluence/index.js b/collector/utils/extensions/Confluence/index.js index 0dc845dce56..0bee1561459 100644 --- a/collector/utils/extensions/Confluence/index.js +++ b/collector/utils/extensions/Confluence/index.js @@ -9,40 +9,6 @@ const { ConfluencePagesLoader, } = require("langchain/document_loaders/web/confluence"); -function urlMatchesPatter(url, patter) { - const urlPattern = new UrlPattern(patter); - return urlPattern.match(url); -} - -function generateCustomDomain({ subdomain, domain, tld }) { - return (subdomain ? `${subdomain}.` : "") + `${domain}.${tld}`; -} - -function validSpaceUrl(spaceUrl = "") { - // Atlassian default URL match - const atlassianMatch = urlMatchesPatter(spaceUrl, "https\\://(:subdomain).atlassian.net/wiki/spaces/(:spaceKey)*"); - if (atlassianMatch) { - return { valid: true, result: atlassianMatch }; - } - - // Custom URL match - const customMatch = urlMatchesPatter(spaceUrl, "https\\://(:subdomain.):domain.:tld/wiki/spaces/(:spaceKey)*"); - if (customMatch) { - customMatch.customDomain = generateCustomDomain(customMatch); - return { valid: true, result: customMatch, humanReadable: false }; - } - - // Human Readable URL match - const humanReadableMatch = urlMatchesPatter(spaceUrl, "https\\://(:subdomain.):domain.:tld/display/(:spaceKey)*"); - if (humanReadableMatch) { - humanReadableMatch.customDomain = generateCustomDomain(humanReadableMatch); - return { valid: true, result: humanReadableMatch, humanReadable: true }; - } - - // No match - return { valid: false, result: null }; -} - async function loadConfluence({ pageUrl, username, accessToken }) { if (!pageUrl || !username || !accessToken) { return { @@ -52,9 +18,8 @@ async function loadConfluence({ pageUrl, username, accessToken }) { }; } - const validSpace = validSpaceUrl(pageUrl); - const { result: validSpaceResult, humanReadable } = validSpace; - if (!validSpaceResult) { + const { valid, result } = validSpaceUrl(pageUrl); + if (!valid) { return { success: false, reason: @@ -62,13 +27,7 @@ async function loadConfluence({ pageUrl, username, accessToken }) { }; } - const { subdomain, customDomain, spaceKey } = validSpaceResult; - let subpath = humanReadable ? `` : `/wiki`; - let baseUrl = `https://${subdomain}.atlassian.net${subpath}`; - if (customDomain) { - baseUrl = `https://${customDomain}${subpath}`; - } - + const { apiBase: baseUrl, spaceKey, subdomain } = result; console.log(`-- Working Confluence ${baseUrl} --`); const loader = new ConfluencePagesLoader({ baseUrl, @@ -147,4 +106,93 @@ async function loadConfluence({ pageUrl, username, accessToken }) { }; } +/** + * A match result for a url-pattern of a Confluence URL + * @typedef {Object} ConfluenceMatchResult + * @property {string} subdomain - the subdomain of an organization's Confluence space + * @property {string} spaceKey - the spaceKey of an organization that determines the documents to collect. + * @property {string} apiBase - the correct REST API url to use for loader. + */ + +/** + * Generates the correct API base URL for interfacing with the Confluence REST API + * depending on the URL pattern being used since there are various ways to host/access a + * Confluence space. + * @param {ConfluenceMatchResult} matchResult - result from `url-pattern`.match + * @param {boolean} isCustomDomain - determines if we need to coerce the subpath of the provided URL + * @returns {string} - the resulting REST API URL + */ +function generateAPIBaseUrl(matchResult = {}, isCustomDomain = false) { + const { subdomain } = matchResult; + let subpath = isCustomDomain ? `` : `/wiki`; + if (isCustomDomain) return `https://${customDomain}${subpath}`; + return `https://${subdomain}.atlassian.net${subpath}`; +} + +/** + * Validates and parses the correct information from a given Confluence URL + * @param {string} spaceUrl - The organization's Confluence URL to parse + * @returns {{ + * valid: boolean, + * result: (ConfluenceMatchResult|null), + * }} + */ +function validSpaceUrl(spaceUrl = "") { + let matchResult; + const patterns = { + default: new UrlPattern( + "https\\://(:subdomain).atlassian.net/wiki/spaces/(:spaceKey)*" + ), + subdomain: new UrlPattern( + "https\\://(:subdomain.):domain.:tld/wiki/spaces/(:spaceKey)*" + ), + custom: new UrlPattern( + "https\\://(:subdomain.):domain.:tld/display/(:spaceKey)*" + ), + }; + + // If using the default Atlassian Confluence URL pattern. + // We can proceed because the Library/API can use this base url scheme. + matchResult = patterns.default.match(spaceUrl); + if (matchResult) + return { + valid: matchResult.hasOwnProperty("spaceKey"), + result: { + ...matchResult, + apiBase: generateAPIBaseUrl(matchResult), + }, + }; + + // If using a custom subdomain Confluence URL pattern. + // We need to attach the customDomain as a property to the match result + // so we can form the correct REST API base from the subdomain. + matchResult = patterns.subdomain.match(spaceUrl); + if (matchResult) { + return { + valid: matchResult.hasOwnProperty("spaceKey"), + result: { + ...matchResult, + apiBase: generateAPIBaseUrl(matchResult), + }, + }; + } + + // If using a base FQDN Confluence URL pattern. + // We need to attach the customDomain as a property to the match result + // so we can form the correct REST API base from the root domain since /display/ is basically a URL mask. + matchResult = patterns.custom.match(spaceUrl); + if (matchResult) { + return { + valid: matchResult.hasOwnProperty("spaceKey"), + result: { + ...matchResult, + apiBase: generateAPIBaseUrl(matchResult, true), + }, + }; + } + + // No match + return { valid: false, result: null }; +} + module.exports = loadConfluence;