feat: add multi-BU tenancy with per-user team scoping (Option B)
- Add bu_teams column to users table (migration + fresh schema) - Create shared KNOWN_TEAMS constant and validateTeams helper - Expose user teams in auth middleware, login, and /me responses - Add bu_teams CRUD to user management routes with audit logging - Make Ivanti FINDINGS_FILTERS configurable via IVANTI_BU_FILTER env var - Add query-time team filtering to GET /findings and /findings/counts - Update AuthContext with teams helpers and admin scope toggle - Create AdminScopeToggle component (My Teams / All BUs) - Scope ReportingPage findings fetch by user teams - Scope CompliancePage team selector by user teams - Scope ExportsPage findings exports by user teams - Add BU teams multi-select to UserManagement create/edit forms - Display team badges in user list table
This commit is contained in:
@@ -132,69 +132,6 @@ function createJiraTicketsRouter(db) {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/jira/search
|
||||
*
|
||||
* Search Jira issues using a JQL query. Results are capped at 1000 per page.
|
||||
* Charter compliance: JQL must include project+updated, assignee+updated,
|
||||
* or status+updated. Fields are always specified explicitly.
|
||||
*
|
||||
* @body {string} jql - JQL query string (required, max 2000 chars)
|
||||
* @body {number} [startAt] - Pagination offset
|
||||
* @body {number} [maxResults] - Page size (max 1000)
|
||||
* @body {string[]} [fields] - Explicit field list for the Jira response
|
||||
* @returns {object} 200 - { total, startAt, maxResults, issues: [{ key, summary, status, assignee, priority, issuetype, created, updated }] }
|
||||
* @returns {object} 400 - { error } when JQL is missing or too long
|
||||
* @returns {object} 429 - { error } when Jira rate limit exceeded
|
||||
* @returns {object} 502 - { error, details } on Jira search failure
|
||||
* @returns {object} 503 - { error } when Jira API is not configured
|
||||
*/
|
||||
router.post('/search', requireAuth(db), async (req, res) => {
|
||||
if (!jiraApi.isConfigured) {
|
||||
return res.status(503).json({ error: 'Jira API is not configured.' });
|
||||
}
|
||||
|
||||
const { jql, startAt, maxResults, fields } = req.body;
|
||||
if (!jql || typeof jql !== 'string' || jql.trim().length === 0) {
|
||||
return res.status(400).json({ error: 'JQL query is required.' });
|
||||
}
|
||||
if (jql.length > 2000) {
|
||||
return res.status(400).json({ error: 'JQL query too long (max 2000 chars).' });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await jiraApi.searchIssues(jql, {
|
||||
startAt,
|
||||
maxResults: Math.min(maxResults || 1000, 1000),
|
||||
fields: fields || undefined
|
||||
});
|
||||
if (result.ok) {
|
||||
const data = result.data;
|
||||
return res.json({
|
||||
total: data.total,
|
||||
startAt: data.startAt,
|
||||
maxResults: data.maxResults,
|
||||
issues: (data.issues || []).map(issue => ({
|
||||
key: issue.key,
|
||||
summary: issue.fields.summary,
|
||||
status: issue.fields.status ? issue.fields.status.name : null,
|
||||
assignee: issue.fields.assignee ? issue.fields.assignee.displayName : null,
|
||||
priority: issue.fields.priority ? issue.fields.priority.name : null,
|
||||
issuetype: issue.fields.issuetype ? issue.fields.issuetype.name : null,
|
||||
created: issue.fields.created,
|
||||
updated: issue.fields.updated
|
||||
}))
|
||||
});
|
||||
}
|
||||
if (result.rateLimited) {
|
||||
return res.status(429).json({ error: 'Jira rate limit exceeded. Try again later.' });
|
||||
}
|
||||
return res.status(502).json({ error: 'Jira search failed.', details: result.body });
|
||||
} catch (err) {
|
||||
return res.status(502).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/jira/create-in-jira
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user