-
Notifications
You must be signed in to change notification settings - Fork 14
MOSIP-45245-Github Trakker add Apis to fetch user details in backend along with roles #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,4 +3,12 @@ RDS_PORT=5432 | |
| RDS_DATABASE=github_data | ||
| RDS_USER=your-rds-user | ||
| RDS_PASSWORD=your-rds-password | ||
| GITHUB_TOKEN=your-github-token | ||
| GITHUB_TOKEN=your-github-token | ||
|
|
||
|
Comment on lines
+6
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Keep the dotenv keys in the expected order. Move 🧰 Tools🪛 dotenv-linter (4.0.0)[warning] 6-6: [UnorderedKey] The GITHUB_TOKEN key should go before the RDS_DATABASE key (UnorderedKey) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| # Read from .env on backend start and stored in user_roles / organizations tables. | ||
| GITHUB_ORG=mosip,inji | ||
| USER_ROLES=Developer,Tech Lead,Architect,Product Owner,Leadership,QA Engineer,DevOps Engineer | ||
|
|
||
| # Bearer token required to call /admin/* endpoints (sync + role management). | ||
| # Generate a strong random value, e.g. `openssl rand -hex 32`. | ||
| ADMIN_API_TOKEN=your-admin-api-token | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ | |
| * GitHub Activity Tracker – Backend API | ||
| * | ||
| * Express server that exposes admin sync endpoints to pull repository, commit, | ||
| * PR, and review data from GitHub into PostgreSQL. Run migrations first (npm run migrate). | ||
| * PR, and review data from GitHub into PostgreSQL. | ||
| */ | ||
| require('dotenv').config(); | ||
| const express = require('express'); | ||
|
|
@@ -16,6 +16,11 @@ const orgActivityRoute = require('./routes/orgActivityRoute'); | |
| const orgSummaryRoute = require('./routes/orgSummaryRoute'); | ||
| const orgUsersRoute = require('./routes/orgUsersRoute'); | ||
| const userDetailsRoute = require('./routes/userDetailsRoute'); | ||
| const userNameSyncRoute = require('./routes/userNameSyncRoute'); | ||
| const userRoleRoute = require('./routes/userRoleRoute'); | ||
| const userRolesRoute = require('./routes/userRolesRoute'); | ||
| const organizationsRoute = require('./routes/organizationsRoute'); | ||
| const { ensureLookupTables } = require('./db/initLookupTables'); | ||
|
|
||
| const app = express(); | ||
| const PORT = process.env.PORT || 3000; | ||
|
|
@@ -34,6 +39,11 @@ app.get('/', (req, res) => { | |
| 'POST /admin/sync/commits': 'Sync commits for all repositories in DB', | ||
| 'POST /admin/sync/prs': 'Sync PRs for all repositories in DB', | ||
| 'POST /admin/sync/reviews': 'Sync PR reviews for all repositories in DB', | ||
| 'POST /admin/sync/user-names': 'Backfill GitHub profile names for users in DB', | ||
| 'POST /admin/users/role': 'Assign or change job role for a GitHub user', | ||
| 'GET /admin/users/:login/role': 'Fetch job role for a GitHub user', | ||
| 'GET /user-roles': 'List assignable user job roles', | ||
| 'GET /organizations': 'List tracked GitHub organizations', | ||
| }, | ||
| }); | ||
| }); | ||
|
|
@@ -43,12 +53,33 @@ app.use(repoSyncRoute); | |
| app.use(commitSyncRoute); | ||
| app.use(prSyncRoute); | ||
| app.use(reviewSyncRoute); | ||
| app.use(userNameSyncRoute); | ||
| app.use(userRoleRoute); | ||
|
kharodejayesh marked this conversation as resolved.
|
||
| app.use(userRolesRoute); | ||
| app.use(organizationsRoute); | ||
| app.use(orgUsersRoute); | ||
| app.use(orgSummaryRoute); | ||
| app.use(userDetailsRoute); | ||
| app.use(orgActivityRoute); | ||
| app.use(leaderboardRoute); | ||
|
Comment on lines
+56
to
64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check how admin routes are mounted in app.js
cat -n github-activity-tracker/backend/app.js | sed -n '40,65p'
# Search for adminAuth middleware usage
rg -n 'adminAuth' github-activity-tracker/backend/app.js
# Check route files for any built-in auth guards
rg -n 'adminAuth|requireAuth|middleware' github-activity-tracker/backend/routes/userRoleRoute.js github-activity-tracker/backend/routes/userNameSyncRoute.jsRepository: mosip/mosip-labs Length of output: 1282 🏁 Script executed: #!/bin/bash
set -e
cat -n github-activity-tracker/backend/routes/userNameSyncRoute.js | sed -n '1,220p'
printf '\n---\n'
cat -n github-activity-tracker/backend/routes/userRoleRoute.js | sed -n '1,220p'
printf '\n---\n'
cat -n github-activity-tracker/backend/app.js | sed -n '1,140p'Repository: mosip/mosip-labs Length of output: 9516 Mount the admin routes behind 🤖 Prompt for AI Agents |
||
|
|
||
| app.listen(PORT, () => { | ||
| app.listen(PORT, async () => { | ||
| try { | ||
| const result = await ensureLookupTables(); | ||
|
|
||
| if (result.createdTables.length > 0) { | ||
| console.log(`Created tables: ${result.createdTables.join(', ')}`); | ||
| } | ||
|
|
||
| if (result.rolesAdded > 0 || result.orgsAdded > 0) { | ||
| console.log(`Added from .env: ${result.rolesAdded} role(s), ${result.orgsAdded} organization(s)`); | ||
| } else if (result.createdTables.length === 0) { | ||
| console.log('Lookup tables already exist; no new roles or organizations to add.'); | ||
| } | ||
| } catch (error) { | ||
| console.error('Failed to initialize lookup tables:', error.message); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| console.log(`Server running on http://localhost:${PORT}`); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| function parseUserRolesFromEnv() { | ||
| const fromEnv = process.env.USER_ROLES; | ||
|
|
||
| if (!fromEnv || !fromEnv.trim()) { | ||
| return []; | ||
| } | ||
|
|
||
| return fromEnv | ||
| .split(',') | ||
| .map((role) => role.trim()) | ||
| .filter(Boolean); | ||
| } | ||
|
|
||
| module.exports = { | ||
| parseUserRolesFromEnv, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| const { | ||
| getAllOrganizations, | ||
| getOrganizationSlugs, | ||
| getOrganizationNames, | ||
| normalizeOrganization, | ||
| isValidOrganization, | ||
| } = require('../services/organizationsService'); | ||
|
|
||
| module.exports = { | ||
| getAllOrganizations, | ||
| getOrganizationSlugs, | ||
| getOrganizationNames, | ||
| normalizeOrganization, | ||
| isValidOrganization, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| const { | ||
| getAllUserRoles, | ||
| getUserRoleNames, | ||
| isValidUserRole, | ||
| } = require('../services/userRolesService'); | ||
|
|
||
| module.exports = { | ||
| getAllUserRoles, | ||
| getUserRoleNames, | ||
| isValidUserRole, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| const pool = require('./dbPool'); | ||
| const { parseUserRolesFromEnv } = require('../config/defaultUserRoles'); | ||
|
|
||
| function parseOrganizationsFromEnv(value = process.env.GITHUB_ORG) { | ||
| return (value || '') | ||
| .split(',') | ||
| .map((slug) => slug.trim().toLowerCase()) | ||
| .filter(Boolean); | ||
| } | ||
|
|
||
| async function tableExists(tableName) { | ||
| const result = await pool.query( | ||
| ` | ||
| SELECT 1 | ||
| FROM information_schema.tables | ||
| WHERE table_schema = 'public' AND table_name = $1 | ||
| LIMIT 1 | ||
| `, | ||
| [tableName] | ||
| ); | ||
| return result.rowCount > 0; | ||
| } | ||
|
|
||
| async function ensureLookupTables() { | ||
| const roles = parseUserRolesFromEnv(); | ||
| const orgs = parseOrganizationsFromEnv(); | ||
| const createdTables = []; | ||
|
|
||
| if (!(await tableExists('user_roles'))) { | ||
| await pool.query(` | ||
| CREATE TABLE user_roles ( | ||
| id SERIAL PRIMARY KEY, | ||
| name VARCHAR(50) NOT NULL UNIQUE, | ||
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | ||
| ) | ||
| `); | ||
| createdTables.push('user_roles'); | ||
| } | ||
|
|
||
| if (!(await tableExists('organizations'))) { | ||
| await pool.query(` | ||
| CREATE TABLE organizations ( | ||
| id SERIAL PRIMARY KEY, | ||
| slug VARCHAR(255) NOT NULL UNIQUE, | ||
| name VARCHAR(255) NOT NULL, | ||
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | ||
| ) | ||
| `); | ||
| createdTables.push('organizations'); | ||
| } | ||
|
|
||
| let rolesAdded = 0; | ||
| for (const name of roles) { | ||
| const result = await pool.query( | ||
| ` | ||
| INSERT INTO user_roles (name) | ||
| VALUES ($1) | ||
| ON CONFLICT (name) DO NOTHING | ||
| RETURNING id | ||
| `, | ||
| [name] | ||
| ); | ||
| if (result.rowCount > 0) { | ||
| rolesAdded += 1; | ||
| } | ||
| } | ||
|
|
||
| let orgsAdded = 0; | ||
| for (const slug of orgs) { | ||
| const result = await pool.query( | ||
| ` | ||
| INSERT INTO organizations (slug, name) | ||
| VALUES ($1, $2) | ||
| ON CONFLICT (slug) DO NOTHING | ||
| RETURNING id | ||
| `, | ||
| [slug, slug.toUpperCase()] | ||
| ); | ||
| if (result.rowCount > 0) { | ||
| orgsAdded += 1; | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| roles, | ||
| orgs, | ||
| createdTables, | ||
| rolesAdded, | ||
| orgsAdded, | ||
| }; | ||
| } | ||
|
|
||
| module.exports = { | ||
| ensureLookupTables, | ||
| parseOrganizationsFromEnv, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| -- GitHub profile display name (from GET /users/{login} or GraphQL User.name). | ||
| ALTER TABLE github_users | ||
| ADD COLUMN IF NOT EXISTS name VARCHAR(255); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| -- Job role for team members (set manually until admin update API exists). | ||
| -- Allowed values: Developer, Tech Lead, Architect, Product Owner, Leadership, QA Engineer, DevOps Engineer | ||
| ALTER TABLE github_users | ||
| ADD COLUMN IF NOT EXISTS role VARCHAR(50); | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_github_users_role ON github_users(role); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,87 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -- User role/organization assignments with history. One active row per user at a time. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -- Lookup tables (user_roles, organizations) are created at app startup from .env. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CREATE TABLE IF NOT EXISTS user_details ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id SERIAL PRIMARY KEY, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_id INTEGER NOT NULL REFERENCES github_users(id) ON DELETE CASCADE, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| role_id INTEGER REFERENCES user_roles(id), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| organization_id INTEGER REFERENCES organizations(id), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| active BOOLEAN NOT NULL DEFAULT true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| active_from TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| active_to TIMESTAMP, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ALTER TABLE user_details | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ADD COLUMN IF NOT EXISTS role_id INTEGER REFERENCES user_roles(id), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ADD COLUMN IF NOT EXISTS organization_id INTEGER REFERENCES organizations(id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ALTER TABLE user_details | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DROP COLUMN IF EXISTS login, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DROP COLUMN IF EXISTS name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DROP COLUMN IF EXISTS github_user_id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CREATE INDEX IF NOT EXISTS idx_user_details_user_id ON user_details(user_id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CREATE INDEX IF NOT EXISTS idx_user_details_role_id ON user_details(role_id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CREATE INDEX IF NOT EXISTS idx_user_details_organization_id ON user_details(organization_id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CREATE INDEX IF NOT EXISTS idx_user_details_active_from ON user_details(active_from); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CREATE INDEX IF NOT EXISTS idx_user_details_active_to ON user_details(active_to); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CREATE UNIQUE INDEX IF NOT EXISTS idx_user_details_one_active_per_user | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ON user_details(user_id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WHERE active = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -- GitHub profile name lives on github_users; assignments live in user_details. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ALTER TABLE github_users | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ADD COLUMN IF NOT EXISTS name VARCHAR(255); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DO $$ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BEGIN | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| IF EXISTS ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FROM information_schema.columns | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WHERE table_schema = 'public' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AND table_name = 'user_details' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AND column_name = 'name' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) THEN | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UPDATE github_users u | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SET | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name = ud.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updated_at = CURRENT_TIMESTAMP | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FROM user_details ud | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WHERE ud.user_id = u.id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AND ud.active = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AND ud.name IS NOT NULL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AND (u.name IS NULL OR u.name = ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| END IF; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| END $$; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+20
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win Critical: Lines 20-23 execute For fresh installs this is harmless (no data to migrate), but for upgrades from a schema where Note: the 🐛 Proposed fix: move DROP COLUMN after the data migration ALTER TABLE user_details
- DROP COLUMN IF EXISTS login,
- DROP COLUMN IF EXISTS name,
- DROP COLUMN IF EXISTS github_user_id;
+ DROP COLUMN IF EXISTS login,
+ DROP COLUMN IF EXISTS github_user_id;
CREATE INDEX IF NOT EXISTS idx_user_details_user_id ON user_details(user_id);
CREATE INDEX IF NOT EXISTS idx_user_details_role_id ON user_details(role_id);
CREATE INDEX IF NOT EXISTS idx_user_details_organization_id ON user_details(organization_id);
CREATE INDEX IF NOT EXISTS idx_user_details_active_from ON user_details(active_from);
CREATE INDEX IF NOT EXISTS idx_user_details_active_to ON user_details(active_to);
CREATE UNIQUE INDEX IF NOT EXISTS idx_user_details_one_active_per_user
ON user_details(user_id)
WHERE active = true;
-- GitHub profile name lives on github_users; assignments live in user_details.
ALTER TABLE github_users
ADD COLUMN IF NOT EXISTS name VARCHAR(255);
DO $$
BEGIN
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'user_details'
AND column_name = 'name'
) THEN
UPDATE github_users u
SET
name = ud.name,
updated_at = CURRENT_TIMESTAMP
FROM user_details ud
WHERE ud.user_id = u.id
AND ud.active = true
AND ud.name IS NOT NULL
AND (u.name IS NULL OR u.name = '');
END IF;
END $$;
+-- Now safe to drop the name column after migration
+ALTER TABLE user_details
+ DROP COLUMN IF EXISTS name;
+
DO $$📝 Committable suggestion
Suggested change
🧰 Tools🪛 Squawk (2.59.0)[warning] 21-21: Dropping a column may break existing clients. (ban-drop-column) [warning] 22-22: Dropping a column may break existing clients. (ban-drop-column) [warning] 23-23: Dropping a column may break existing clients. (ban-drop-column) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DO $$ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BEGIN | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| IF EXISTS ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FROM information_schema.columns | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WHERE table_schema = 'public' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AND table_name = 'github_users' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AND column_name = 'role' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) THEN | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| INSERT INTO user_details (user_id, role_id, active, active_from, active_to) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| u.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ur.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| COALESCE(u.updated_at, u.inserted_at, CURRENT_TIMESTAMP), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| NULL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FROM github_users u | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JOIN user_roles ur ON ur.name = u.role | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WHERE u.role IS NOT NULL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AND NOT EXISTS ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT 1 FROM user_details ud WHERE ud.user_id = u.id AND ud.active = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| END IF; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| END $$; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DROP INDEX IF EXISTS idx_github_users_role; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ALTER TABLE github_users | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DROP COLUMN IF EXISTS role; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| -- Backdate user_details.active_from so historical activity before user sync | ||
| -- is included when a role is first assigned. | ||
| UPDATE user_details ud | ||
| SET | ||
| active_from = LEAST( | ||
| ud.active_from, | ||
| COALESCE( | ||
| (SELECT MIN(e.created_at) FROM activity_events e WHERE e.user_id = ud.user_id), | ||
| ud.active_from | ||
| ) | ||
| ), | ||
| updated_at = CURRENT_TIMESTAMP | ||
| WHERE EXISTS ( | ||
| SELECT 1 FROM activity_events e WHERE e.user_id = ud.user_id | ||
| ); | ||
|
|
||
| -- First role assignment rows should cover all prior activity, not only from assignment time. | ||
| UPDATE user_details current_ud | ||
| SET | ||
| active_from = LEAST( | ||
| current_ud.active_from, | ||
| COALESCE( | ||
| (SELECT MIN(e.created_at) FROM activity_events e WHERE e.user_id = current_ud.user_id), | ||
| current_ud.active_from | ||
| ), | ||
| COALESCE( | ||
| (SELECT MIN(h.active_from) FROM user_details h WHERE h.user_id = current_ud.user_id), | ||
| current_ud.active_from | ||
| ) | ||
| ), | ||
| updated_at = CURRENT_TIMESTAMP | ||
| WHERE current_ud.role_id IS NOT NULL | ||
| AND NOT EXISTS ( | ||
| SELECT 1 | ||
| FROM user_details prior_role | ||
| WHERE prior_role.user_id = current_ud.user_id | ||
| AND prior_role.role_id IS NOT NULL | ||
| AND prior_role.id <> current_ud.id | ||
| AND prior_role.active_to IS NOT NULL | ||
| AND prior_role.active_to <= current_ud.active_from | ||
| ); | ||
|
Comment on lines
+3
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift Only backdate the first role assignment per user. Line 3 backdates every 🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Quote the
USER_ROLESvalue to prevent potential parsing issues.The value contains spaces (e.g., "Tech Lead", "QA Engineer"). Without quotes, strict dotenv parsers may truncate at the first space, yielding
Developer,Techinstead of the full list. The downstreamdefaultUserRoles.jsreadsprocess.env.USER_ROLESdirectly, so a truncation would silently produce an incomplete role list.🔧 Proposed fix
📝 Committable suggestion
🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 15-15: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
🤖 Prompt for AI Agents
Source: Linters/SAST tools