Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions github-activity-tracker/backend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ 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 app = express();
const PORT = process.env.PORT || 3000;
Expand All @@ -34,6 +36,9 @@ 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 job role to a GitHub user',
'GET /admin/users/:login/role': 'Fetch job role for a GitHub user',
},
});
});
Expand All @@ -43,6 +48,8 @@ app.use(repoSyncRoute);
app.use(commitSyncRoute);
app.use(prSyncRoute);
app.use(reviewSyncRoute);
app.use(userNameSyncRoute);
app.use(userRoleRoute);
Comment thread
kharodejayesh marked this conversation as resolved.
app.use(orgUsersRoute);
app.use(orgSummaryRoute);
app.use(userDetailsRoute);
Expand Down
12 changes: 12 additions & 0 deletions github-activity-tracker/backend/config/syncConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@
/** Delay in ms between processing each repo when syncing commits/PRs/reviews for all repos. */
const DELAY_BETWEEN_REPOS_MS = 300;

/** Default batch size for backfilling missing GitHub user display names per request. */
const NAME_BACKFILL_BATCH_SIZE = 50;

/** Delay in ms between GitHub profile lookups when backfilling user names. */
const NAME_FETCH_DELAY_MS = 120;
Comment thread
kharodejayesh marked this conversation as resolved.

/** Maximum batch size allowed for a single user-name backfill request. */
const NAME_BACKFILL_MAX_BATCH_SIZE = 500;

module.exports = {
DELAY_BETWEEN_REPOS_MS,
NAME_BACKFILL_BATCH_SIZE,
NAME_FETCH_DELAY_MS,
NAME_BACKFILL_MAX_BATCH_SIZE,
};
18 changes: 18 additions & 0 deletions github-activity-tracker/backend/config/userRoles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const USER_ROLES = [
'Developer',
'Tech Lead',
'Architect',
'Product Owner',
'Leadership',
'QA Engineer',
'DevOps Engineer',
];

function isValidUserRole(role) {
return USER_ROLES.includes(role);
}

module.exports = {
USER_ROLES,
isValidUserRole,
};
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);
10 changes: 8 additions & 2 deletions github-activity-tracker/backend/routes/orgActivityRoute.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
const express = require("express");
const router = express.Router();
const { getOrgActivity } = require("../services/orgActivityService");
const { isValidUserRole } = require("../config/userRoles");

router.get("/orgs/:org_id/activity", async (req, res) => {
const { org_id } = req.params;
const { period = "weekly" } = req.query;
const { period = "weekly", role } = req.query;

if (!org_id || typeof org_id !== "string") {
return res.status(400).json({ error: "Invalid org_id" });
Expand All @@ -14,8 +15,13 @@ router.get("/orgs/:org_id/activity", async (req, res) => {
return res.status(400).json({ error: "Invalid period value" });
}

if (role && role !== "all" && !isValidUserRole(role)) {
return res.status(400).json({ error: "Invalid role value" });
}

try {
const data = await getOrgActivity(org_id, period);
const roleFilter = role && role !== "all" ? role : null;
const data = await getOrgActivity(org_id, period, roleFilter);
return res.json(data);
} catch (err) {
console.error("Error fetching org activity:", err);
Expand Down
10 changes: 8 additions & 2 deletions github-activity-tracker/backend/routes/orgSummaryRoute.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
const express = require('express');
const router = express.Router();
const { getOrgSummary } = require('../services/orgSummaryService');
const { isValidUserRole } = require('../config/userRoles');

router.get('/orgs/:org_id/summary', async (req, res) => {
try {
const { org_id } = req.params;
const { period = 'weekly' } = req.query;
const { period = 'weekly', role } = req.query;

if (!org_id) {
return res.status(400).json({ error: 'Invalid org_id' });
Expand All @@ -15,7 +16,12 @@ router.get('/orgs/:org_id/summary', async (req, res) => {
return res.status(400).json({ error: 'Invalid period value' });
}

const summary = await getOrgSummary(org_id, period);
if (role && role !== 'all' && !isValidUserRole(role)) {
return res.status(400).json({ error: 'Invalid role value' });
}

const roleFilter = role && role !== 'all' ? role : null;
const summary = await getOrgSummary(org_id, period, roleFilter);

return res.status(200).json(summary);
} catch (err) {
Expand Down
30 changes: 30 additions & 0 deletions github-activity-tracker/backend/routes/userNameSyncRoute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Route: POST /admin/sync/user-names
* Backfills GitHub profile display names for users missing name in github_users.
*/
const express = require('express');
const { backfillMissingUserNames } = require('../services/githubUserService');
const { HTTP, STATUS } = require('../config/errorCodes');

const router = express.Router();

router.post('/admin/sync/user-names', async (req, res) => {
try {
const result = await backfillMissingUserNames();

return res.json({
status: STATUS.SUCCESS,
...result,
});
} catch (error) {
console.error('Error backfilling GitHub user names:', error);

return res.status(HTTP.INTERNAL_SERVER_ERROR).json({
status: STATUS.ERROR,
message: 'Failed to backfill GitHub user names',
error: process.env.NODE_ENV === 'development' ? error.message : undefined,
});
}
});
Comment thread
kharodejayesh marked this conversation as resolved.

module.exports = router;
100 changes: 100 additions & 0 deletions github-activity-tracker/backend/routes/userRoleRoute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* Routes:
* GET /admin/users/:login/role – fetch role for a GitHub user
* POST /admin/users/role – assign role to a GitHub user
*/
const express = require('express');
const { getUserRole, setUserRole } = require('../services/userRoleService');
const { USER_ROLES } = require('../config/userRoles');
const { HTTP, STATUS } = require('../config/errorCodes');

const router = express.Router();

router.get('/admin/users/:login/role', async (req, res) => {
try {
const { login } = req.params;

if (!login) {
return res.status(HTTP.BAD_REQUEST).json({
status: STATUS.ERROR,
message: 'login is required',
});
}

const user = await getUserRole(login);

if (!user) {
return res.status(HTTP.NOT_FOUND).json({
status: STATUS.ERROR,
message: 'User not found',
});
}

return res.json({
status: STATUS.SUCCESS,
user,
});
} catch (error) {
console.error('Error fetching user role:', error);

return res.status(HTTP.INTERNAL_SERVER_ERROR).json({
status: STATUS.ERROR,
message: 'Failed to fetch user role',
error: process.env.NODE_ENV === 'development' ? error.message : undefined,
});
}
});

router.post('/admin/users/role', async (req, res) => {
try {
const { login, role } = req.body || {};

if (!login) {
return res.status(HTTP.BAD_REQUEST).json({
status: STATUS.ERROR,
message: 'login is required in request body',
});
}

if (!role) {
return res.status(HTTP.BAD_REQUEST).json({
status: STATUS.ERROR,
message: 'role is required in request body',
allowed_roles: USER_ROLES,
});
}

const user = await setUserRole({ login, role });

if (!user) {
return res.status(HTTP.NOT_FOUND).json({
status: STATUS.ERROR,
message: 'User not found',
});
}

return res.json({
status: STATUS.SUCCESS,
message: 'Role assigned successfully',
user,
});
} catch (error) {
if (error.message === 'Invalid role value') {
return res.status(HTTP.BAD_REQUEST).json({
status: STATUS.ERROR,
message: 'Invalid role value',
allowed_roles: USER_ROLES,
});
}

console.error('Error assigning user role:', error);

return res.status(HTTP.INTERNAL_SERVER_ERROR).json({
status: STATUS.ERROR,
message: 'Failed to assign user role',
error: process.env.NODE_ENV === 'development' ? error.message : undefined,
});
}
});

module.exports = router;
25 changes: 8 additions & 17 deletions github-activity-tracker/backend/services/commitSyncService.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const githubClient = require('../utils/githubClient');
const pool = require('../db/dbPool');
const { POSTGRES } = require('../config/errorCodes');
const { isExcludedGitHubLogin } = require('../config/excludedGitHubLogins');
const { upsertGitHubUser } = require('./githubUserService');

/**
* Sync commits for a single repository.
Expand Down Expand Up @@ -120,23 +121,13 @@ async function syncCommits(repoId) {
continue;
}

// Ensure user exists; get our internal id for foreign keys
const userResult = await pool.query(
`
INSERT INTO github_users (github_user_id, login, avatar_url, html_url, type)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (github_user_id)
DO UPDATE SET
login = EXCLUDED.login,
avatar_url = EXCLUDED.avatar_url,
html_url = EXCLUDED.html_url,
type = EXCLUDED.type
RETURNING id
`,
[github_user_id, login, avatar_url || null, html_url || null, type || null]
);

const userId = userResult.rows[0].id;
const userId = await upsertGitHubUser({
github_user_id,
login,
avatar_url,
html_url,
type,
});

// Record event first; only increment commit counters when the event is newly inserted.
const eventInsert = await pool.query(
Expand Down
Loading