diff --git a/gurubu-backend/controllers/storyPointEstimationController.js b/gurubu-backend/controllers/storyPointEstimationController.js index c910843..9154a11 100644 --- a/gurubu-backend/controllers/storyPointEstimationController.js +++ b/gurubu-backend/controllers/storyPointEstimationController.js @@ -4,8 +4,8 @@ exports.estimateStoryPoint = async (req, res) => { try { const { boardName, issueSummary, issueDescription, threadId } = req.body; - if(!boardName) { - return res.status(400).json({ error: "Board name is required" }); + if (!boardName) { + return res.status(400).json({ error: "Board name is required" }); } if (!issueSummary) { @@ -15,9 +15,12 @@ exports.estimateStoryPoint = async (req, res) => { if (!issueDescription) { return res.status(400).json({ error: "Issue Description is required" }); } - + const assistantId = process.env.OPENAI_ASSISTANT_ID; - + if (!assistantId) { + return res.status(500).json({ error: "OpenAI Assistant ID not configured" }); + } + const message = createStoryPointPrompt(issueSummary, issueDescription); const response = await openaiService.askAssistant(assistantId, message, threadId); diff --git a/gurubu-backend/services/jiraInitialStoryPointService.js b/gurubu-backend/services/jiraInitialStoryPointService.js index 660976e..7c30c51 100644 --- a/gurubu-backend/services/jiraInitialStoryPointService.js +++ b/gurubu-backend/services/jiraInitialStoryPointService.js @@ -9,10 +9,38 @@ class JiraInitialStoryPointService { storyPointFieldName; constructor() { - this.baseUrl = process.env.JIRA_BASE_URL || ""; + // Validate required environment variables + const requiredEnvVars = { + JIRA_BASE_URL: process.env.JIRA_BASE_URL, + JIRA_USERNAME: process.env.JIRA_USERNAME, + JIRA_API_TOKEN: process.env.JIRA_API_TOKEN, + JIRA_PROJECT_KEY_FOUR: process.env.JIRA_PROJECT_KEY_FOUR + }; + + const missingVars = []; + for (const [key, value] of Object.entries(requiredEnvVars)) { + if (!value || value.trim() === '') { + missingVars.push(key); + } + } + + if (missingVars.length > 0) { + throw new Error( + `Missing required environment variables for JIRA service: ${missingVars.join(', ')}. ` + + 'Please check your .env file and ensure all JIRA configuration variables are set.' + ); + } + + // Validate JIRA_BASE_URL format + try { + new URL(this.baseUrl = process.env.JIRA_BASE_URL); + } catch (error) { + throw new Error(`Invalid JIRA_BASE_URL format: ${process.env.JIRA_BASE_URL}. Must be a valid URL.`); + } + this.auth = { - username: process.env.JIRA_USERNAME || "", - password: process.env.JIRA_API_TOKEN || "", + username: process.env.JIRA_USERNAME, + password: process.env.JIRA_API_TOKEN, }; this.storyPointFieldName = process.env.JIRA_PROJECT_KEY_FOUR; } diff --git a/gurubu-backend/services/jiraService.js b/gurubu-backend/services/jiraService.js index 2b01d56..8b93b3f 100644 --- a/gurubu-backend/services/jiraService.js +++ b/gurubu-backend/services/jiraService.js @@ -9,11 +9,49 @@ class JiraService { auth; constructor() { - this.baseUrl = process.env.JIRA_BASE_URL || ""; + // Validate required environment variables + const requiredEnvVars = { + JIRA_BASE_URL: process.env.JIRA_BASE_URL, + JIRA_USERNAME: process.env.JIRA_USERNAME, + JIRA_API_TOKEN: process.env.JIRA_API_TOKEN + }; + + const missingVars = []; + for (const [key, value] of Object.entries(requiredEnvVars)) { + if (!value || value.trim() === '') { + missingVars.push(key); + } + } + + if (missingVars.length > 0) { + throw new Error( + `Missing required environment variables for JIRA service: ${missingVars.join(', ')}. ` + + 'Please check your .env file and ensure all JIRA configuration variables are set.' + ); + } + + // Validate JIRA_BASE_URL format + try { + new URL(this.baseUrl = process.env.JIRA_BASE_URL); + } catch (error) { + throw new Error(`Invalid JIRA_BASE_URL format: ${process.env.JIRA_BASE_URL}. Must be a valid URL.`); + } + this.auth = { - username: process.env.JIRA_USERNAME || "", - password: process.env.JIRA_API_TOKEN || "", + username: process.env.JIRA_USERNAME, + password: process.env.JIRA_API_TOKEN, }; + + // Warn about optional JIRA project keys if not set + const optionalKeys = ['JIRA_PROJECT_KEY_ONE', 'JIRA_PROJECT_KEY_TWO', 'JIRA_PROJECT_KEY_THREE', 'JIRA_PROJECT_KEY_FOUR']; + const missingOptionalKeys = optionalKeys.filter(key => !process.env[key]); + + if (missingOptionalKeys.length > 0) { + console.warn( + `Warning: Optional JIRA project keys not configured: ${missingOptionalKeys.join(', ')}. ` + + 'Some features may not work correctly without these configurations.' + ); + } } mapIssueResponse(response) { @@ -47,14 +85,14 @@ class JiraService { key: issue.key, assignee: issue.fields.assignee ? { - self: `${this.baseUrl}/rest/api/2/user?username=${issue.fields.assignee.name}`, - name: issue.fields.assignee.name, - key: `JIRAUSER_${issue.fields.assignee.name}`, - emailAddress: issue.fields.assignee.emailAddress, - displayName: issue.fields.assignee.displayName, - active: true, - timeZone: "Europe/Istanbul", - } + self: `${this.baseUrl}/rest/api/2/user?username=${issue.fields.assignee.name}`, + name: issue.fields.assignee.name, + key: `JIRAUSER_${issue.fields.assignee.name}`, + emailAddress: issue.fields.assignee.emailAddress, + displayName: issue.fields.assignee.displayName, + active: true, + timeZone: "Europe/Istanbul", + } : null, pairAssignee, testAssignee, @@ -298,7 +336,7 @@ class JiraService { baseClauses.push("statusCategory != Done"); } const jql = `${baseClauses.join(" AND ")} ORDER BY updated DESC`; - + const response = await axios.get(`${this.baseUrl}/rest/api/2/search`, { params: { jql, diff --git a/gurubu-backend/services/openaiService.js b/gurubu-backend/services/openaiService.js index 75e4639..7b402fb 100644 --- a/gurubu-backend/services/openaiService.js +++ b/gurubu-backend/services/openaiService.js @@ -5,6 +5,9 @@ dotenv.config(); class OpenAIService { constructor() { + if (!process.env.OPENAI_API_KEY) { + throw new Error('OPENAI_API_KEY environment variable is required'); + } this.openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); @@ -13,7 +16,7 @@ class OpenAIService { async askAssistant(assistantId, message, threadId = null) { try { - + let thread; if (!threadId) { thread = await this.openai.beta.threads.create(); @@ -63,7 +66,7 @@ class OpenAIService { } const latestMessage = assistantMessages[0]; - + const textContent = latestMessage.content .filter((content) => content.type === "text") .map((content) => content.text.value) diff --git a/gurubu-backend/services/pService.js b/gurubu-backend/services/pService.js index 4f2c3f8..758ab28 100644 --- a/gurubu-backend/services/pService.js +++ b/gurubu-backend/services/pService.js @@ -5,7 +5,31 @@ dotenv.config(); class PService { constructor() { - this.baseUrl = process.env.P_GATEWAY_URL; + // Validate required environment variables + const requiredEnvVars = { + P_GATEWAY_URL: process.env.P_GATEWAY_URL + }; + + const missingVars = []; + for (const [key, value] of Object.entries(requiredEnvVars)) { + if (!value || value.trim() === '') { + missingVars.push(key); + } + } + + if (missingVars.length > 0) { + throw new Error( + `Missing required environment variables for P service: ${missingVars.join(', ')}. ` + + 'Please check your .env file and ensure P_GATEWAY_URL is set.' + ); + } + + // Validate P_GATEWAY_URL format + try { + new URL(this.baseUrl = process.env.P_GATEWAY_URL); + } catch (error) { + throw new Error(`Invalid P_GATEWAY_URL format: ${process.env.P_GATEWAY_URL}. Must be a valid URL.`); + } } async getOrganizations() { @@ -77,7 +101,7 @@ class PService { }, } ); - + return response.data; } catch (error) { console.error("Error searching users:", error);