Type updates
This commit is contained in:
Родитель
7ecc386125
Коммит
4ca3b70ae1
|
@ -840,9 +840,6 @@
|
|||
"Whois",
|
||||
"withmaintainers",
|
||||
"withservicetree",
|
||||
"workboard",
|
||||
"Workboard",
|
||||
"workboarding",
|
||||
"xamarinhq",
|
||||
"Xcache",
|
||||
"xlink",
|
||||
|
|
|
@ -4,34 +4,7 @@
|
|||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch site 3000",
|
||||
"program": "${workspaceFolder}/dist/bin/www.js",
|
||||
"cwd": "${workspaceFolder}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
"sourceMaps": true,
|
||||
"console": "integratedTerminal",
|
||||
"env": {
|
||||
"NODE_ENV": "development",
|
||||
"DEBUG": "startup,g:server,context,github:tokens",
|
||||
"MORE_DEBUG": "appinsights,cache,restapi,pg,querycache,user,redis-cross-org,health"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Jest Tests",
|
||||
"program": "${workspaceRoot}/node_modules/jest/bin/jest.js",
|
||||
"args": ["-i"],
|
||||
"preLaunchTask": "tsbuild",
|
||||
"sourceMaps": true,
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"console": "externalTerminal",
|
||||
"outFiles": ["${workspaceRoot}/dist/**/*"]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch site 4000 sudo",
|
||||
"name": "Launch site :4000 sudo",
|
||||
"program": "${workspaceFolder}/dist/bin/www.js",
|
||||
"cwd": "${workspaceFolder}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
|
@ -48,7 +21,7 @@
|
|||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch site 4000 sudo OFF",
|
||||
"name": "Launch site :4000 sudo OFF",
|
||||
"program": "${workspaceFolder}/dist/bin/www.js",
|
||||
"cwd": "${workspaceFolder}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
|
@ -66,8 +39,23 @@
|
|||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Background Job: Firehose",
|
||||
"program": "${workspaceRoot}/dist/jobs/firehose/index.js",
|
||||
"name": "Launch server-rendered legacy site :3000",
|
||||
"program": "${workspaceFolder}/dist/bin/www.js",
|
||||
"cwd": "${workspaceFolder}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
"sourceMaps": true,
|
||||
"console": "integratedTerminal",
|
||||
"env": {
|
||||
"NODE_ENV": "development",
|
||||
"DEBUG": "startup,g:server,context,github:tokens",
|
||||
"MORE_DEBUG": "appinsights,cache,restapi,pg,querycache,user,redis-cross-org,health"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Background Job: Event firehose",
|
||||
"program": "${workspaceRoot}/dist/jobs/firehose.js",
|
||||
"preLaunchTask": "tsbuild",
|
||||
"sourceMaps": true,
|
||||
"console": "integratedTerminal",
|
||||
|
@ -77,6 +65,18 @@
|
|||
"DEBUG": "startup,querycache"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Jest Tests",
|
||||
"program": "${workspaceRoot}/node_modules/jest/bin/jest.js",
|
||||
"args": ["-i"],
|
||||
"preLaunchTask": "tsbuild",
|
||||
"sourceMaps": true,
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"console": "externalTerminal",
|
||||
"outFiles": ["${workspaceRoot}/dist/**/*"]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
|
@ -118,7 +118,7 @@
|
|||
"sourceMaps": true,
|
||||
"env": {
|
||||
"NODE_ENV": "development",
|
||||
"DEBUG": "appinsights,restapi,startup,redis-cross-org,appinsights",
|
||||
"DEBUG": "appinsights,startup",
|
||||
"DEBUG_GITHUB_PORTAL_SUDO_OFF": "1",
|
||||
"DEBUG_GITHUB_ORG_SUDO_OFF": "1"
|
||||
}
|
||||
|
@ -137,39 +137,11 @@
|
|||
"DEBUG": "startup"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Job: Cleanup invitations",
|
||||
"program": "${workspaceRoot}/dist/jobs/cleanupInvites/index.js",
|
||||
"cwd": "${workspaceRoot}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
"sourceMaps": true,
|
||||
"console": "integratedTerminal",
|
||||
"env": {
|
||||
"NODE_ENV": "development",
|
||||
"DEBUG": "redis,restapi,startup,appinsights"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Job: Cleanup blob cache",
|
||||
"program": "${workspaceRoot}/dist/jobs/cleanupBlobCache/index.js",
|
||||
"cwd": "${workspaceRoot}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
"sourceMaps": true,
|
||||
"console": "integratedTerminal",
|
||||
"env": {
|
||||
"NODE_ENV": "development",
|
||||
"DEBUG": "startup"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Job: Backfill aliases (3)",
|
||||
"program": "${workspaceRoot}/dist/jobs/refreshUsernames/index.js",
|
||||
"program": "${workspaceRoot}/dist/jobs/refreshUsernames.js",
|
||||
"cwd": "${workspaceRoot}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
"sourceMaps": true,
|
||||
|
@ -184,21 +156,21 @@
|
|||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Job: User attributes hygiene (4)",
|
||||
"program": "${workspaceRoot}/dist/jobs/refreshUsernames/index.js",
|
||||
"program": "${workspaceRoot}/dist/jobs/refreshUsernames.js",
|
||||
"cwd": "${workspaceRoot}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
"sourceMaps": true,
|
||||
"console": "integratedTerminal",
|
||||
"env": {
|
||||
"NODE_ENV": "development",
|
||||
"DEBUG": "redis,restapi,startup,appinsights,cache"
|
||||
"DEBUG": "startup,appinsights"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Job: Consistency: All (6)",
|
||||
"program": "${workspaceRoot}/dist/jobs/refreshQueryCache/index.js",
|
||||
"program": "${workspaceRoot}/dist/jobs/refreshQueryCache.js",
|
||||
"args": ["all"],
|
||||
"cwd": "${workspaceRoot}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
|
@ -213,7 +185,7 @@
|
|||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Job: Consistency: Deleted repos (7)",
|
||||
"program": "${workspaceRoot}/dist/jobs/refreshQueryCache/deletedRepositories.js",
|
||||
"program": "${workspaceRoot}/dist/jobs/deletedRepositoriesCache.js",
|
||||
"args": [],
|
||||
"cwd": "${workspaceRoot}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
|
@ -228,7 +200,7 @@
|
|||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Job: Consistency: Teams (8)",
|
||||
"program": "${workspaceRoot}/dist/jobs/refreshQueryCache/index.js",
|
||||
"program": "${workspaceRoot}/dist/jobs/refreshQueryCache.js",
|
||||
"args": ["teams"],
|
||||
"cwd": "${workspaceRoot}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
|
@ -243,7 +215,7 @@
|
|||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Job: Consistency: Org members (9)",
|
||||
"program": "${workspaceRoot}/dist/jobs/refreshQueryCache/index.js",
|
||||
"program": "${workspaceRoot}/dist/jobs/refreshQueryCache.js",
|
||||
"args": ["organizations"],
|
||||
"cwd": "${workspaceRoot}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
|
@ -258,7 +230,7 @@
|
|||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Job: Consistency: Repo collaborators (10)",
|
||||
"program": "${workspaceRoot}/dist/jobs/refreshQueryCache/index.js",
|
||||
"program": "${workspaceRoot}/dist/jobs/refreshQueryCache.js",
|
||||
"args": ["collaborators"],
|
||||
"cwd": "${workspaceRoot}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
|
@ -273,7 +245,7 @@
|
|||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Job: Consistency: Team permissions (11)",
|
||||
"program": "${workspaceRoot}/dist/jobs/refreshQueryCache/index.js",
|
||||
"program": "${workspaceRoot}/dist/jobs/refreshQueryCache.js",
|
||||
"args": ["permissions"],
|
||||
"cwd": "${workspaceRoot}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
|
@ -301,8 +273,8 @@
|
|||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Job: Migrate links",
|
||||
"program": "${workspaceRoot}/dist/jobs/migrateLinks/index.js",
|
||||
"name": "Job: Migrate links (14)",
|
||||
"program": "${workspaceRoot}/dist/jobs/migrateLinks.js",
|
||||
"preLaunchTask": "tsbuild",
|
||||
"sourceMaps": true,
|
||||
"console": "integratedTerminal",
|
||||
|
@ -315,8 +287,8 @@
|
|||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Job: System Team Permissions",
|
||||
"program": "${workspaceRoot}/dist/jobs/permissions/index.js",
|
||||
"name": "Job: System Team Permissions (15)",
|
||||
"program": "${workspaceRoot}/dist/jobs/permissions.js",
|
||||
"cwd": "${workspaceRoot}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
"sourceMaps": true,
|
||||
|
@ -326,6 +298,34 @@
|
|||
"DEBUG": "startup,appinsights"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Job: Cleanup invitations (16)",
|
||||
"program": "${workspaceRoot}/dist/jobs/cleanupInvites.js",
|
||||
"cwd": "${workspaceRoot}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
"sourceMaps": true,
|
||||
"console": "integratedTerminal",
|
||||
"env": {
|
||||
"NODE_ENV": "development",
|
||||
"DEBUG": "redis,restapi,startup,appinsights"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Job: Cleanup blob cache (17)",
|
||||
"program": "${workspaceRoot}/dist/jobs/cleanupBlobCache.js",
|
||||
"cwd": "${workspaceRoot}/dist",
|
||||
"preLaunchTask": "tsbuild",
|
||||
"sourceMaps": true,
|
||||
"console": "integratedTerminal",
|
||||
"env": {
|
||||
"NODE_ENV": "development",
|
||||
"DEBUG": "startup"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
|
@ -345,7 +345,7 @@
|
|||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Script: Import audit CSV",
|
||||
"program": "${workspaceRoot}/dist/jobs/importAuditLog/index.js",
|
||||
"program": "${workspaceRoot}/dist/jobs/importAuditLog.js",
|
||||
"preLaunchTask": "tsbuild",
|
||||
"sourceMaps": true,
|
||||
"console": "integratedTerminal",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
#
|
||||
|
||||
ARG IMAGE_NAME=mcr.microsoft.com/cbl-mariner/base/nodejs:16
|
||||
ARG IMAGE_NAME=mcr.microsoft.com/cbl-mariner/base/nodejs:18
|
||||
|
||||
FROM $IMAGE_NAME AS build
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import { ReposAppRequest } from '../../interfaces';
|
||||
|
||||
import { jsonError } from '../../middleware';
|
||||
|
@ -13,7 +13,7 @@ const router: Router = Router();
|
|||
|
||||
// TODO: move to modern w/administration experience, optionally
|
||||
|
||||
router.get('/', (req: ReposAppRequest, res, next) => {
|
||||
router.get('/', (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { config } = getProviders(req);
|
||||
const text = config?.serviceMessage?.banner || null;
|
||||
const link = config.serviceMessage?.link;
|
||||
|
@ -22,7 +22,7 @@ router.get('/', (req: ReposAppRequest, res, next) => {
|
|||
return res.json({ banner });
|
||||
});
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available within this banner route', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { Organization } from '../../../../business/organization';
|
||||
import { ReposAppRequest } from '../../../../interfaces';
|
||||
import { getIsCorporateAdministrator, jsonError } from '../../../../middleware';
|
||||
|
@ -20,7 +21,7 @@ interface IRequestWithAdministration extends ReposAppRequest {
|
|||
}
|
||||
|
||||
router.use(
|
||||
asyncHandler(async (req: IRequestWithAdministration, res, next) => {
|
||||
asyncHandler(async (req: IRequestWithAdministration, res: Response, next: NextFunction) => {
|
||||
req.isSystemAdministrator = await getIsCorporateAdministrator(req);
|
||||
return next();
|
||||
})
|
||||
|
@ -28,7 +29,7 @@ router.use(
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: IRequestWithAdministration, res, next) => {
|
||||
asyncHandler(async (req: IRequestWithAdministration, res: Response, next: NextFunction) => {
|
||||
const { operations } = getProviders(req);
|
||||
const isAdministrator = req.isSystemAdministrator;
|
||||
if (!isAdministrator) {
|
||||
|
@ -44,13 +45,13 @@ router.get(
|
|||
})
|
||||
);
|
||||
|
||||
router.use((req: IRequestWithAdministration, res, next) => {
|
||||
router.use((req: IRequestWithAdministration, res: Response, next: NextFunction) => {
|
||||
return req.isSystemAdministrator ? next() : next(jsonError('Not authorized', 403));
|
||||
});
|
||||
|
||||
router.use(
|
||||
'/organization/:orgName',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { orgName } = req.params;
|
||||
const { operations } = getProviders(req);
|
||||
let organization: Organization = null;
|
||||
|
@ -74,7 +75,7 @@ const deployment = getCompanySpecificDeployment();
|
|||
deployment?.routes?.api?.context?.administration?.index &&
|
||||
deployment?.routes?.api?.context.administration.index(router);
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available: context/administration', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { ReposAppRequest } from '../../../../../interfaces';
|
||||
import { jsonError } from '../../../../../middleware';
|
||||
|
||||
|
@ -16,7 +17,7 @@ router.use('/settings', routeSettings);
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { organization } = req;
|
||||
return res.json({
|
||||
organization: organization.asClientJson(),
|
||||
|
@ -24,7 +25,7 @@ router.get(
|
|||
})
|
||||
);
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available in administration - organization', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { OrganizationSetting } from '../../../../../entities/organizationSettings/organizationSetting';
|
||||
import { ReposAppRequest } from '../../../../../interfaces';
|
||||
import { jsonError } from '../../../../../middleware';
|
||||
|
@ -17,7 +18,7 @@ interface IOrganizationSettings extends ReposAppRequest {
|
|||
}
|
||||
|
||||
router.use(
|
||||
asyncHandler(async (req: IOrganizationSettings, res, next) => {
|
||||
asyncHandler(async (req: IOrganizationSettings, res: Response, next: NextFunction) => {
|
||||
const { organization } = req;
|
||||
const { organizationSettingsProvider } = getProviders(req);
|
||||
try {
|
||||
|
@ -34,7 +35,7 @@ router.use(
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: IOrganizationSettings, res, next) => {
|
||||
asyncHandler(async (req: IOrganizationSettings, res: Response, next: NextFunction) => {
|
||||
const { dynamicSettings } = req;
|
||||
return res.json({
|
||||
dynamicSettings,
|
||||
|
@ -46,7 +47,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/features',
|
||||
asyncHandler(async (req: IOrganizationSettings, res, next) => {
|
||||
asyncHandler(async (req: IOrganizationSettings, res: Response, next: NextFunction) => {
|
||||
const { dynamicSettings, organization } = req;
|
||||
const { features } = dynamicSettings;
|
||||
return res.json({
|
||||
|
@ -58,7 +59,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/feature/:flag',
|
||||
asyncHandler(async (req: IOrganizationSettings, res, next) => {
|
||||
asyncHandler(async (req: IOrganizationSettings, res: Response, next: NextFunction) => {
|
||||
const { dynamicSettings, organization } = req;
|
||||
const flag = req.params.flag as string;
|
||||
return res.json({
|
||||
|
@ -71,7 +72,7 @@ router.get(
|
|||
|
||||
router.put(
|
||||
'/feature/:flag',
|
||||
asyncHandler(async (req: IOrganizationSettings, res, next) => {
|
||||
asyncHandler(async (req: IOrganizationSettings, res: Response, next: NextFunction) => {
|
||||
const { dynamicSettings, organization } = req;
|
||||
const { insights, organizationSettingsProvider } = getProviders(req);
|
||||
const { features } = dynamicSettings;
|
||||
|
@ -103,7 +104,7 @@ router.put(
|
|||
|
||||
router.delete(
|
||||
'/feature/:flag',
|
||||
asyncHandler(async (req: IOrganizationSettings, res, next) => {
|
||||
asyncHandler(async (req: IOrganizationSettings, res: Response, next: NextFunction) => {
|
||||
const { organization, dynamicSettings } = req;
|
||||
const { organizationSettingsProvider, insights } = getProviders(req);
|
||||
const { features } = dynamicSettings;
|
||||
|
@ -137,7 +138,7 @@ router.delete(
|
|||
|
||||
router.get(
|
||||
'/properties',
|
||||
asyncHandler(async (req: IOrganizationSettings, res, next) => {
|
||||
asyncHandler(async (req: IOrganizationSettings, res: Response, next: NextFunction) => {
|
||||
const { dynamicSettings, organization } = req;
|
||||
const { properties } = dynamicSettings;
|
||||
return res.json({
|
||||
|
@ -149,7 +150,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/property/:flag',
|
||||
asyncHandler(async (req: IOrganizationSettings, res, next) => {
|
||||
asyncHandler(async (req: IOrganizationSettings, res: Response, next: NextFunction) => {
|
||||
const { dynamicSettings, organization } = req;
|
||||
const propertyName = req.params.propertyName as string;
|
||||
const { properties } = dynamicSettings;
|
||||
|
@ -163,7 +164,7 @@ router.get(
|
|||
|
||||
router.put(
|
||||
'/property/:propertyName',
|
||||
asyncHandler(async (req: IOrganizationSettings, res, next) => {
|
||||
asyncHandler(async (req: IOrganizationSettings, res: Response, next: NextFunction) => {
|
||||
const { organization, dynamicSettings } = req;
|
||||
const { insights, organizationSettingsProvider } = getProviders(req);
|
||||
const { properties } = dynamicSettings;
|
||||
|
@ -209,7 +210,7 @@ router.put(
|
|||
|
||||
router.delete(
|
||||
'/property/:propertyName',
|
||||
asyncHandler(async (req: IOrganizationSettings, res, next) => {
|
||||
asyncHandler(async (req: IOrganizationSettings, res: Response, next: NextFunction) => {
|
||||
const { organization, dynamicSettings } = req;
|
||||
const { organizationSettingsProvider, insights } = getProviders(req);
|
||||
const { properties } = dynamicSettings;
|
||||
|
@ -245,7 +246,7 @@ router.delete(
|
|||
|
||||
//
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available in administration - organization', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { Team, Organization } from '../../../business';
|
||||
|
@ -30,7 +30,7 @@ const approvalPairToJson = (pair: ApprovalPair) => {
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { approvalProvider, operations } = getProviders(req);
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
if (!activeContext.link) {
|
||||
|
@ -65,7 +65,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/:approvalId',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const approvalId = req.params.approvalId;
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
if (!activeContext.link) {
|
||||
|
@ -112,7 +112,7 @@ router.get(
|
|||
})
|
||||
);
|
||||
|
||||
router.use('*', (req: ReposAppRequest, res, next) => {
|
||||
router.use('*', (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('Contextual API or route not found within approvals', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { Organization } from '../../../business';
|
||||
|
@ -48,7 +48,7 @@ router.get('/', (req: ReposAppRequest, res) => {
|
|||
|
||||
router.get(
|
||||
'/specialized/multipleLinkGitHubIdentities',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { operations } = getProviders(req);
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
const links = (activeContext?.link ? [activeContext.link, ...activeContext.additionalLinks] : []).map(
|
||||
|
@ -79,7 +79,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/accountDetails',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { operations } = getProviders(req);
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
try {
|
||||
|
@ -107,7 +107,7 @@ router.use('/sample', routeSample);
|
|||
|
||||
router.use(
|
||||
'/orgs/:orgName',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { orgName } = req.params;
|
||||
const providers = getProviders(req);
|
||||
const { operations } = providers;
|
||||
|
@ -151,7 +151,7 @@ async function isUnmanagedOrganization(providers: IProviders, orgName: string):
|
|||
|
||||
router.use('/orgs/:orgName', routeIndividualContextualOrganization);
|
||||
|
||||
router.use('*', (req: ReposAppRequest, res, next) => {
|
||||
router.use('*', (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('Contextual API or route not found', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { Organization, Team } from '../../../../business';
|
||||
import {
|
||||
ReposAppRequest,
|
||||
|
@ -23,7 +24,7 @@ const router: Router = Router();
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { organization } = req;
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
if (!activeContext.link) {
|
||||
|
@ -46,7 +47,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/sudo',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { organization } = req;
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
if (!activeContext.link) {
|
||||
|
@ -60,7 +61,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/isOwner',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { organization } = req;
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
if (!activeContext.link) {
|
||||
|
@ -81,7 +82,7 @@ router.get(
|
|||
|
||||
router.delete(
|
||||
'/',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
// "Leave" / remove my context
|
||||
const { organization } = req;
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
|
@ -104,7 +105,7 @@ router.delete(
|
|||
|
||||
router.get(
|
||||
'/personalizedTeams',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const organization = req.organization as Organization;
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
|
@ -145,7 +146,7 @@ const deployment = getCompanySpecificDeployment();
|
|||
deployment?.routes?.api?.context?.organization?.index &&
|
||||
deployment?.routes?.api?.context?.organization?.index(router);
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available: client>organization', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import {
|
||||
|
@ -21,7 +21,7 @@ const router: Router = Router();
|
|||
router.get(
|
||||
'/permissions',
|
||||
AddRepositoryPermissionsToRequest,
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const permissions = getContextualRepositoryPermissions(req);
|
||||
return res.json(permissions);
|
||||
})
|
||||
|
@ -33,7 +33,7 @@ const deployment = getCompanySpecificDeployment();
|
|||
deployment?.routes?.api?.context?.organization?.repo &&
|
||||
deployment?.routes?.api?.context?.organization?.repo(router);
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError(`no API or ${req.method} function available for repo`, 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { jsonError } from '../../../../middleware';
|
||||
|
@ -20,7 +20,7 @@ import NewRepositoryLockdownSystem from '../../../../features/newRepositories/ne
|
|||
const router: Router = Router();
|
||||
|
||||
router.use(
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const organization = req.organization as Organization;
|
||||
if (!organization.isNewRepositoryLockdownSystemEnabled()) {
|
||||
return next(jsonError('This endpoint is not available as configured for the organization', 400));
|
||||
|
@ -42,7 +42,7 @@ router.use(
|
|||
|
||||
router.post(
|
||||
'/approve',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { operations } = getProviders(req);
|
||||
const repository = getContextualRepository(req);
|
||||
const repositoryMetadataProvider = getRepositoryMetadataProvider(operations);
|
||||
|
@ -70,7 +70,7 @@ router.post(
|
|||
})
|
||||
);
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError(`no API or ${req.method} function available for repo fork unlock`, 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { Repository } from '../../../../business';
|
||||
import { jsonError } from '../../../../middleware';
|
||||
import { setContextualRepository } from '../../../../middleware/github/repoPermissions';
|
||||
|
@ -17,7 +18,7 @@ import RouteContextualRepo from './repo';
|
|||
|
||||
const router: Router = Router();
|
||||
|
||||
async function validateActiveMembership(req: ReposAppRequest, res, next) {
|
||||
async function validateActiveMembership(req: ReposAppRequest, res: Response, next: NextFunction) {
|
||||
const { organization } = req;
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
if (!activeContext.link) {
|
||||
|
@ -37,7 +38,7 @@ router.post('/', asyncHandler(validateActiveMembership), asyncHandler(createRepo
|
|||
|
||||
router.use(
|
||||
'/:repoName',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { organization } = req;
|
||||
const { repoName } = req.params;
|
||||
let repository: Repository = null;
|
||||
|
@ -49,7 +50,7 @@ router.use(
|
|||
|
||||
router.use('/:repoName', RouteContextualRepo);
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available for repos', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { TeamJoinApprovalEntity } from '../../../../entities/teamJoinApproval/teamJoinApproval';
|
||||
|
@ -42,7 +42,7 @@ router.get(
|
|||
'/permissions',
|
||||
asyncHandler(AddTeamPermissionsToRequest),
|
||||
asyncHandler(AddTeamMembershipToRequest),
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const membership = getTeamMembershipFromRequest(req);
|
||||
const permissions = getTeamPermissionsFromRequest(req);
|
||||
return res.json({ permissions, membership });
|
||||
|
@ -51,7 +51,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/join/request',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { approvalProvider } = getProviders(req);
|
||||
const team = getContextualTeam(req);
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
|
@ -70,7 +70,7 @@ router.get(
|
|||
router.post(
|
||||
'/join',
|
||||
asyncHandler(AddTeamMembershipToRequest),
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const providers = getProviders(req);
|
||||
const { approvalProvider } = providers;
|
||||
|
@ -112,7 +112,7 @@ router.post(
|
|||
router.post(
|
||||
'/join/approvals/:approvalId',
|
||||
asyncHandler(AddTeamPermissionsToRequest),
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { approvalId: id } = req.params;
|
||||
if (!id) {
|
||||
return next(jsonError('invalid approval', 400));
|
||||
|
@ -164,7 +164,7 @@ router.post(
|
|||
router.get(
|
||||
'/join/approvals/:approvalId',
|
||||
asyncHandler(AddTeamPermissionsToRequest),
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { approvalId: id } = req.params;
|
||||
if (!id) {
|
||||
return next(jsonError('invalid approval', 400));
|
||||
|
@ -195,7 +195,7 @@ router.get(
|
|||
router.get(
|
||||
'/join/approvals',
|
||||
asyncHandler(AddTeamPermissionsToRequest),
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { approvalProvider } = getProviders(req);
|
||||
const team = getContextualTeam(req);
|
||||
const permissions = getTeamPermissionsFromRequest(req);
|
||||
|
@ -213,7 +213,7 @@ router.get(
|
|||
router.post(
|
||||
'/role/:login',
|
||||
asyncHandler(AddTeamPermissionsToRequest),
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { role } = req.body;
|
||||
const { login } = req.params;
|
||||
if (!login) {
|
||||
|
@ -240,7 +240,7 @@ router.post(
|
|||
})
|
||||
);
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available for contextual team', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { Team } from '../../../../business';
|
||||
import { jsonError } from '../../../../middleware';
|
||||
import { setContextualTeam } from '../../../../middleware/github/teamPermissions';
|
||||
|
@ -18,7 +19,7 @@ const router: Router = Router();
|
|||
|
||||
router.use(
|
||||
'/:teamSlug',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { organization } = req;
|
||||
const { teamSlug } = req.params;
|
||||
let team: Team = null;
|
||||
|
@ -35,7 +36,7 @@ router.use(
|
|||
|
||||
router.use('/:teamSlug', RouteTeam);
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available for repos', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { NextFunction, Response } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { GitHubRepositoryPermission, ReposAppRequest } from '../../../interfaces';
|
||||
import { IndividualContext } from '../../../business/user';
|
||||
|
||||
export default asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
export default asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
if (!activeContext.link) {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { sendLinkedAccountMail } from '../../../business/operations/link';
|
||||
|
@ -16,7 +16,7 @@ const router: Router = Router();
|
|||
|
||||
router.get(
|
||||
'/:templateName',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { operations } = getProviders(req);
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
const templateName = req.params.templateName as string;
|
||||
|
@ -43,7 +43,7 @@ router.get(
|
|||
})
|
||||
);
|
||||
|
||||
router.use('*', (req: ReposAppRequest, res, next) => {
|
||||
router.use('*', (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('Contextual API or route not found within samples', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
//
|
||||
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import { NextFunction, Response } from 'express';
|
||||
|
||||
import { ReposAppRequest, TeamJsonFormat } from '../../../interfaces';
|
||||
import { IndividualContext } from '../../../business/user';
|
||||
|
||||
export default asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
export default asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
if (!activeContext.link) {
|
||||
return res.json({
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import {
|
||||
|
@ -34,7 +34,7 @@ import routeCrossOrganizationTeams from './teams';
|
|||
|
||||
const router: Router = Router();
|
||||
|
||||
router.use((req: ReposAppRequest, res, next) => {
|
||||
router.use((req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { config } = getProviders(req);
|
||||
if (config?.features?.allowApiClient) {
|
||||
if (req.isAuthenticated()) {
|
||||
|
@ -126,7 +126,7 @@ router.get('/', (req: ReposAppRequest, res) => {
|
|||
return res.send(JSON.stringify(data, null, 2));
|
||||
});
|
||||
|
||||
router.use((req, res, next) => {
|
||||
router.use((req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('The resource or endpoint you are looking for is not there', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { IndividualContext } from '../../business/user';
|
||||
|
@ -15,7 +15,7 @@ import { ReposAppRequest } from '../../interfaces';
|
|||
|
||||
const router: Router = Router();
|
||||
|
||||
async function validateLinkOk(req: ReposAppRequest, res, next) {
|
||||
async function validateLinkOk(req: ReposAppRequest, res: Response, next: NextFunction) {
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
const providers = getProviders(req);
|
||||
const insights = providers.insights;
|
||||
|
@ -91,7 +91,7 @@ async function validateLinkOk(req: ReposAppRequest, res, next) {
|
|||
}
|
||||
}
|
||||
|
||||
router.get('/banner', (req: ReposAppRequest, res, next) => {
|
||||
router.get('/banner', (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { config } = getProviders(req);
|
||||
const offline = config?.github?.links?.provider?.linkingOfflineMessage;
|
||||
return res.json({ offline });
|
||||
|
@ -99,7 +99,7 @@ router.get('/banner', (req: ReposAppRequest, res, next) => {
|
|||
|
||||
router.delete(
|
||||
'/',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
return unlinkInteractive(true, activeContext, req, res, next);
|
||||
})
|
||||
|
@ -108,13 +108,13 @@ router.delete(
|
|||
router.post(
|
||||
'/',
|
||||
validateLinkOk,
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
return interactiveLinkUser(true, activeContext, req, res, next);
|
||||
})
|
||||
);
|
||||
|
||||
router.use('*', (req: ReposAppRequest, res, next) => {
|
||||
router.use('*', (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('API or route not found', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
const router: Router = Router();
|
||||
|
||||
|
@ -25,7 +25,7 @@ interface ILocalApiRequest extends ReposAppRequest {
|
|||
knownRequesterMailAddress?: string;
|
||||
}
|
||||
|
||||
router.get('/metadata', (req: ILocalApiRequest, res, next) => {
|
||||
router.get('/metadata', (req: ILocalApiRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const options = {
|
||||
projectType: req.query.projectType,
|
||||
|
@ -40,7 +40,7 @@ router.get('/metadata', (req: ILocalApiRequest, res, next) => {
|
|||
|
||||
router.get(
|
||||
'/personalizedTeams',
|
||||
asyncHandler(async (req: ILocalApiRequest, res, next) => {
|
||||
asyncHandler(async (req: ILocalApiRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const organization = req.organization as Organization;
|
||||
const userAggregateContext = req.apiContext.aggregations;
|
||||
|
@ -76,7 +76,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/teams',
|
||||
asyncHandler(async (req: ILocalApiRequest, res, next) => {
|
||||
asyncHandler(async (req: ILocalApiRequest, res: Response, next: NextFunction) => {
|
||||
const providers = getProviders(req);
|
||||
const queryCache = providers.queryCache;
|
||||
const organization = req.organization as Organization;
|
||||
|
@ -154,7 +154,7 @@ router.get(
|
|||
})
|
||||
);
|
||||
|
||||
export async function discoverUserIdentities(req: ReposAppRequest, res, next) {
|
||||
export async function discoverUserIdentities(req: ReposAppRequest, res: Response, next: NextFunction) {
|
||||
const apiContext = req.apiContext as IndividualContext;
|
||||
const providers = getProviders(req);
|
||||
const mailAddressProvider = providers.mailAddressProvider;
|
||||
|
@ -177,7 +177,7 @@ export async function discoverUserIdentities(req: ReposAppRequest, res, next) {
|
|||
|
||||
router.post('/repo/:repo', asyncHandler(discoverUserIdentities), asyncHandler(createRepositoryFromClient));
|
||||
|
||||
export async function createRepositoryFromClient(req: ILocalApiRequest, res, next) {
|
||||
export async function createRepositoryFromClient(req: ILocalApiRequest, res: Response, next: NextFunction) {
|
||||
const providers = getProviders(req);
|
||||
const { insights, diagnosticsDrop, customizedNewRepositoryLogic, graphProvider } = providers;
|
||||
const individualContext = req.watchdogContextOverride || req.individualContext || req.apiContext;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
const router: Router = Router();
|
||||
|
||||
import { getProviders } from '../../transitional';
|
||||
|
@ -12,7 +12,7 @@ import { jsonError } from '../../middleware/jsonError';
|
|||
import newOrgRepo from './newOrgRepo';
|
||||
import { ReposAppRequest } from '../../interfaces';
|
||||
|
||||
router.use('/org/:org', (req: ReposAppRequest, res, next) => {
|
||||
router.use('/org/:org', (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const orgName = req.params.org;
|
||||
const { operations } = getProviders(req);
|
||||
try {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { jsonError } from '../../middleware';
|
||||
|
@ -20,7 +20,7 @@ router.get(
|
|||
})
|
||||
);
|
||||
|
||||
router.use('*', (req: ReposAppRequest, res, next) => {
|
||||
router.use('*', (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('API or route not found within news', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { jsonError } from '../../../middleware/jsonError';
|
||||
|
@ -35,7 +35,7 @@ type IRequestWithOrganizationAnnotations = IReposAppRequestWithOrganizationManag
|
|||
router.use(
|
||||
'/',
|
||||
checkIsCorporateAdministrator,
|
||||
asyncHandler(async (req: IRequestWithOrganizationAnnotations, res, next) => {
|
||||
asyncHandler(async (req: IRequestWithOrganizationAnnotations, res: Response, next: NextFunction) => {
|
||||
const { organizationAnnotationsProvider } = getProviders(req);
|
||||
const { organization, organizationManagementType, organizationProfile } = req;
|
||||
const organizationId =
|
||||
|
@ -55,7 +55,7 @@ router.use(
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: IRequestWithOrganizationAnnotations, res, next) => {
|
||||
asyncHandler(async (req: IRequestWithOrganizationAnnotations, res: Response, next: NextFunction) => {
|
||||
const { annotations } = req;
|
||||
// Limited redaction
|
||||
const isSystemAdministrator = await getIsCorporateAdministrator(req);
|
||||
|
@ -68,7 +68,11 @@ router.get(
|
|||
|
||||
router.use(ensureOrganizationProfileMiddleware);
|
||||
|
||||
async function ensureAnnotations(req: IRequestWithOrganizationAnnotations, res, next) {
|
||||
async function ensureAnnotations(
|
||||
req: IRequestWithOrganizationAnnotations,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
if (!req.annotations) {
|
||||
const { organizationAnnotationsProvider } = getProviders(req);
|
||||
try {
|
||||
|
@ -89,7 +93,7 @@ router.put('*', AuthorizeOnlyCorporateAdministrators, ensureAnnotations);
|
|||
|
||||
router.put(
|
||||
'/',
|
||||
asyncHandler(async (req: IRequestWithOrganizationAnnotations, res, next) => {
|
||||
asyncHandler(async (req: IRequestWithOrganizationAnnotations, res: Response, next: NextFunction) => {
|
||||
// No-op mostly, since ensureAnnotations precedes
|
||||
return res.json({
|
||||
annotations: req.annotations,
|
||||
|
@ -125,7 +129,7 @@ function addChangeNote(
|
|||
|
||||
router.put(
|
||||
'/property/:propertyName',
|
||||
asyncHandler(async (req: IRequestWithOrganizationAnnotations, res, next) => {
|
||||
asyncHandler(async (req: IRequestWithOrganizationAnnotations, res: Response, next: NextFunction) => {
|
||||
const { annotations } = req;
|
||||
const providers = getProviders(req);
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
|
@ -152,7 +156,7 @@ router.put(
|
|||
|
||||
router.delete(
|
||||
'/property/:propertyName',
|
||||
asyncHandler(async (req: IRequestWithOrganizationAnnotations, res, next) => {
|
||||
asyncHandler(async (req: IRequestWithOrganizationAnnotations, res: Response, next: NextFunction) => {
|
||||
const { annotations } = req;
|
||||
const providers = getProviders(req);
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
|
@ -183,7 +187,7 @@ router.delete(
|
|||
|
||||
router.put(
|
||||
'/feature/:flag',
|
||||
asyncHandler(async (req: IRequestWithOrganizationAnnotations, res, next) => {
|
||||
asyncHandler(async (req: IRequestWithOrganizationAnnotations, res: Response, next: NextFunction) => {
|
||||
const { annotations } = req;
|
||||
const providers = getProviders(req);
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
|
@ -211,7 +215,7 @@ router.put(
|
|||
|
||||
router.delete(
|
||||
'/feature/:flag',
|
||||
asyncHandler(async (req: IRequestWithOrganizationAnnotations, res, next) => {
|
||||
asyncHandler(async (req: IRequestWithOrganizationAnnotations, res: Response, next: NextFunction) => {
|
||||
const { annotations } = req;
|
||||
const providers = getProviders(req);
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
|
@ -241,7 +245,7 @@ router.delete(
|
|||
|
||||
router.patch(
|
||||
'/',
|
||||
asyncHandler(async (req: IRequestWithOrganizationAnnotations, res, next) => {
|
||||
asyncHandler(async (req: IRequestWithOrganizationAnnotations, res: Response, next: NextFunction) => {
|
||||
const { annotations } = req;
|
||||
const providers = getProviders(req);
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
|
@ -288,7 +292,7 @@ async function applyPatch(
|
|||
// features, properties
|
||||
// flag
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available within the organization annotations route', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { ReposAppRequest } from '../../../interfaces';
|
||||
|
@ -63,19 +63,21 @@ asClientJson() {
|
|||
*/
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: IReposAppRequestWithOrganizationManagementType, res, next) => {
|
||||
const { organization, organizationProfile, organizationManagementType } = req;
|
||||
if (organizationManagementType === OrganizationManagementType.Unmanaged) {
|
||||
asyncHandler(
|
||||
async (req: IReposAppRequestWithOrganizationManagementType, res: Response, next: NextFunction) => {
|
||||
const { organization, organizationProfile, organizationManagementType } = req;
|
||||
if (organizationManagementType === OrganizationManagementType.Unmanaged) {
|
||||
return res.json({
|
||||
managementType: req.organizationManagementType,
|
||||
id: organizationProfile.id,
|
||||
});
|
||||
}
|
||||
return res.json({
|
||||
managementType: req.organizationManagementType,
|
||||
id: organizationProfile.id,
|
||||
...organization.asClientJson(),
|
||||
});
|
||||
}
|
||||
return res.json({
|
||||
managementType: req.organizationManagementType,
|
||||
...organization.asClientJson(),
|
||||
});
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
router.use('/annotations', routeAnnotations);
|
||||
|
@ -93,7 +95,7 @@ router.get('/newRepoBanner', (req: ReposAppRequest, res) => {
|
|||
return res.json({ newRepositoriesOffline });
|
||||
});
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { jsonError } from '../../../middleware/jsonError';
|
||||
|
@ -13,7 +13,7 @@ const router: Router = Router();
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { organization } = req;
|
||||
const metadata = organization.getRepositoryCreateMetadata();
|
||||
res.json(metadata);
|
||||
|
@ -22,7 +22,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/byProjectReleaseType',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { organization } = req;
|
||||
const options = {
|
||||
projectType: req.query.projectType,
|
||||
|
@ -32,7 +32,7 @@ router.get(
|
|||
})
|
||||
);
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available within this path', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { jsonError } from '../../../middleware';
|
||||
|
@ -118,7 +118,7 @@ export async function equivalentLegacyPeopleSearch(req: ReposAppRequest, options
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const pager = new JsonPager<OrganizationMember>(req, res);
|
||||
try {
|
||||
const searcher = await equivalentLegacyPeopleSearch(req);
|
||||
|
@ -142,7 +142,7 @@ router.get(
|
|||
})
|
||||
);
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available within this people list', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { jsonError } from '../../../middleware';
|
||||
|
@ -44,7 +44,7 @@ router.use('/permissions', RouteRepoPermissions);
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: RequestWithRepo, res, next) => {
|
||||
asyncHandler(async (req: RequestWithRepo, res: Response, next: NextFunction) => {
|
||||
const { repository } = req;
|
||||
try {
|
||||
await repository.getDetails({ backgroundRefresh: false });
|
||||
|
@ -64,7 +64,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/exists',
|
||||
asyncHandler(async (req: RequestWithRepo, res, next) => {
|
||||
asyncHandler(async (req: RequestWithRepo, res: Response, next: NextFunction) => {
|
||||
let exists = false;
|
||||
let name: string = undefined;
|
||||
const { repository } = req;
|
||||
|
@ -88,7 +88,7 @@ router.get(
|
|||
router.patch(
|
||||
'/renameDefaultBranch',
|
||||
asyncHandler(AddRepositoryPermissionsToRequest),
|
||||
asyncHandler(async function (req: RequestWithRepo, res, next) {
|
||||
asyncHandler(async function (req: RequestWithRepo, res: Response, next: NextFunction) {
|
||||
const providers = getProviders(req);
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
const repoPermissions = getContextualRepositoryPermissions(req);
|
||||
|
@ -122,7 +122,12 @@ router.post(
|
|||
asyncHandler(archiveUnArchiveRepositoryHandler.bind(null, ArchivalAction.UnArchive))
|
||||
);
|
||||
|
||||
async function archiveUnArchiveRepositoryHandler(action: ArchivalAction, req: RequestWithRepo, res, next) {
|
||||
async function archiveUnArchiveRepositoryHandler(
|
||||
action: ArchivalAction,
|
||||
req: RequestWithRepo,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
const providers = getProviders(req);
|
||||
const { insights } = providers;
|
||||
|
@ -200,7 +205,7 @@ async function archiveUnArchiveRepositoryHandler(action: ArchivalAction, req: Re
|
|||
router.delete(
|
||||
'/',
|
||||
asyncHandler(AddRepositoryPermissionsToRequest),
|
||||
asyncHandler(async function (req: RequestWithRepo, res, next) {
|
||||
asyncHandler(async function (req: RequestWithRepo, res: Response, next: NextFunction) {
|
||||
// NOTE: duplicated code from /routes/org/repos.ts
|
||||
const providers = getProviders(req);
|
||||
const { insights } = providers;
|
||||
|
@ -310,7 +315,7 @@ router.delete(
|
|||
})
|
||||
);
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
console.warn(req.baseUrl);
|
||||
return next(jsonError('no API or function available within this specific repo', 404));
|
||||
});
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { jsonError } from '../../../middleware/jsonError';
|
||||
|
@ -19,7 +19,7 @@ const router: Router = Router();
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: RequestWithRepo, res, next) => {
|
||||
asyncHandler(async (req: RequestWithRepo, res: Response, next: NextFunction) => {
|
||||
const { repository, organization } = req;
|
||||
try {
|
||||
const teamPermissions = await repository.getTeamPermissions();
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { jsonError } from '../../../middleware';
|
||||
|
@ -19,7 +19,7 @@ const router: Router = Router();
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { organization } = req;
|
||||
const providers = getProviders(req);
|
||||
const pager = new JsonPager<Repository>(req, res);
|
||||
|
@ -238,7 +238,7 @@ export async function searchRepos(
|
|||
|
||||
router.use(
|
||||
'/:repoName',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { organization } = req;
|
||||
const { repoName } = req.params;
|
||||
// does not confirm the name
|
||||
|
@ -249,7 +249,7 @@ router.use(
|
|||
|
||||
router.use('/:repoName', RouteRepo);
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available within this repos endpoint', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { getContextualTeam } from '../../../middleware/github/teamPermissions';
|
||||
|
@ -21,7 +21,7 @@ const router: Router = Router();
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const team = getContextualTeam(req);
|
||||
return res.json(team.asJson(TeamJsonFormat.Augmented /* includes corporateMetadata */));
|
||||
})
|
||||
|
@ -29,7 +29,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/repos',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const forceRefresh = !!req.query.refresh;
|
||||
const pager = new JsonPager<TeamRepositoryPermission>(req, res);
|
||||
|
@ -56,7 +56,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/members',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const forceRefresh = !!req.query.refresh;
|
||||
const team = getContextualTeam(req);
|
||||
|
@ -84,7 +84,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/maintainers',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { operations } = getProviders(req);
|
||||
try {
|
||||
const forceRefresh = !!req.query.refresh;
|
||||
|
@ -115,7 +115,7 @@ router.get(
|
|||
})
|
||||
);
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available for this specific team', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { Organization } from '../../../business/organization';
|
||||
|
@ -24,7 +24,7 @@ const leakyLocalCache = new LeakyLocalCache<number, Team[]>();
|
|||
|
||||
router.use(
|
||||
'/:teamSlug',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { organization } = req;
|
||||
const { teamSlug } = req.params;
|
||||
let team: Team = null;
|
||||
|
@ -61,15 +61,19 @@ async function getTeamsForOrganization(
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
return await getClientApiOrganizationTeamsResponse(req, res, next);
|
||||
})
|
||||
);
|
||||
|
||||
export async function getClientApiOrganizationTeamsResponse(req: ReposAppRequest, res, next) {
|
||||
export async function getClientApiOrganizationTeamsResponse(
|
||||
req: ReposAppRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const organization = (req.organization || (req as any).aeOrganization) as Organization;
|
||||
if (!organization) {
|
||||
return next(jsonError('No available organization'), 400);
|
||||
return next(jsonError('No available organization', 400));
|
||||
}
|
||||
const pager = new JsonPager<Team>(req, res);
|
||||
const q: string = (req.query.q ? (req.query.q as string) : null) || '';
|
||||
|
@ -101,7 +105,7 @@ export async function getClientApiOrganizationTeamsResponse(req: ReposAppRequest
|
|||
}
|
||||
}
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available within this team', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { jsonError } from '../../middleware';
|
||||
|
@ -36,7 +36,7 @@ type HighlightedOrganization = {
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { operations } = getProviders(req);
|
||||
try {
|
||||
const orgs = operations.getOrganizations();
|
||||
|
@ -52,7 +52,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/annotations',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const providers = getProviders(req);
|
||||
const { organizationAnnotationsProvider } = providers;
|
||||
const projection = typeof req.query.projection === 'string' ? req.query.projection : undefined;
|
||||
|
@ -113,7 +113,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/list.txt',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { operations } = getProviders(req);
|
||||
try {
|
||||
const orgs = operations.getOrganizations();
|
||||
|
@ -130,41 +130,43 @@ router.get(
|
|||
|
||||
router.use(
|
||||
'/:orgName',
|
||||
asyncHandler(async (req: IReposAppRequestWithOrganizationManagementType, res, next) => {
|
||||
const { operations } = getProviders(req);
|
||||
const { orgName } = req.params;
|
||||
req.organizationName = orgName;
|
||||
try {
|
||||
const org = operations.getOrganization(orgName);
|
||||
if (org) {
|
||||
req.organizationManagementType = OrganizationManagementType.Managed;
|
||||
asyncHandler(
|
||||
async (req: IReposAppRequestWithOrganizationManagementType, res: Response, next: NextFunction) => {
|
||||
const { operations } = getProviders(req);
|
||||
const { orgName } = req.params;
|
||||
req.organizationName = orgName;
|
||||
try {
|
||||
const org = operations.getOrganization(orgName);
|
||||
if (org) {
|
||||
req.organizationManagementType = OrganizationManagementType.Managed;
|
||||
req.organization = org;
|
||||
return next();
|
||||
}
|
||||
} catch (orgNotFoundError) {
|
||||
if (!ErrorHelper.IsNotFound(orgNotFoundError)) {
|
||||
return next(orgNotFoundError);
|
||||
}
|
||||
}
|
||||
try {
|
||||
const org = operations.getUncontrolledOrganization(orgName);
|
||||
req.organizationManagementType = OrganizationManagementType.Unmanaged;
|
||||
req.organization = org;
|
||||
return next();
|
||||
}
|
||||
} catch (orgNotFoundError) {
|
||||
if (!ErrorHelper.IsNotFound(orgNotFoundError)) {
|
||||
return next(orgNotFoundError);
|
||||
await setOrganizationProfileForRequest(req);
|
||||
} catch (orgProfileError) {
|
||||
if (ErrorHelper.IsNotFound(orgProfileError)) {
|
||||
return next(CreateError.NotFound(`The organization ${orgName} does not exist`));
|
||||
} else {
|
||||
return next(orgProfileError);
|
||||
}
|
||||
}
|
||||
return next();
|
||||
}
|
||||
try {
|
||||
const org = operations.getUncontrolledOrganization(orgName);
|
||||
req.organizationManagementType = OrganizationManagementType.Unmanaged;
|
||||
req.organization = org;
|
||||
await setOrganizationProfileForRequest(req);
|
||||
} catch (orgProfileError) {
|
||||
if (ErrorHelper.IsNotFound(orgProfileError)) {
|
||||
return next(CreateError.NotFound(`The organization ${orgName} does not exist`));
|
||||
} else {
|
||||
return next(orgProfileError);
|
||||
}
|
||||
}
|
||||
return next();
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
router.use('/:orgName', RouteOrganization);
|
||||
|
||||
router.use('*', (req: ReposAppRequest, res, next) => {
|
||||
router.use('*', (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('orgs API not found', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { corporateLinkToJson } from '../../business';
|
||||
import { jsonError } from '../../middleware';
|
||||
import { ICorporateLink, ReposAppRequest } from '../../interfaces';
|
||||
import { type GitHubSimpleAccount, type ICorporateLink, ReposAppRequest } from '../../interfaces';
|
||||
import JsonPager from './jsonPager';
|
||||
import getCompanySpecificDeployment from '../../middleware/companySpecificDeployment';
|
||||
|
||||
|
@ -20,34 +20,28 @@ const router: Router = Router();
|
|||
const deployment = getCompanySpecificDeployment();
|
||||
deployment?.routes?.api?.people && deployment.routes.api.people(router);
|
||||
|
||||
interface ISimpleAccount {
|
||||
login: string;
|
||||
avatar_url: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface ICrossOrganizationMemberResponse {
|
||||
account: ISimpleAccount;
|
||||
account: GitHubSimpleAccount;
|
||||
link?: ICorporateLink;
|
||||
organizations: string[];
|
||||
}
|
||||
|
||||
export interface ICrossOrganizationSearchedMember {
|
||||
id: number;
|
||||
account: ISimpleAccount;
|
||||
account: GitHubSimpleAccount;
|
||||
link?: ICorporateLink;
|
||||
orgs: IOrganizationMembershipAccount;
|
||||
}
|
||||
|
||||
interface IOrganizationMembershipAccount {
|
||||
[id: string]: ISimpleAccount;
|
||||
[id: string]: GitHubSimpleAccount;
|
||||
}
|
||||
|
||||
router.get('/:login', RouteGetPerson);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const pager = new JsonPager<ICrossOrganizationSearchedMember>(req, res);
|
||||
try {
|
||||
const searcher = await equivalentLegacyPeopleSearch(req);
|
||||
|
@ -73,7 +67,7 @@ router.get(
|
|||
})
|
||||
);
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available within this cross-organization people list', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -4,13 +4,15 @@
|
|||
//
|
||||
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import { NextFunction, Response } from 'express';
|
||||
|
||||
import { ReposAppRequest, AccountJsonFormat } from '../../interfaces';
|
||||
import { IGraphEntry } from '../../lib/graphProvider';
|
||||
|
||||
import { jsonError } from '../../middleware';
|
||||
import { getProviders } from '../../transitional';
|
||||
|
||||
export default asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
export default asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const providers = getProviders(req);
|
||||
const { operations, queryCache, graphProvider } = providers;
|
||||
const login = req.params.login as string;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { Repository } from '../../business';
|
||||
|
@ -17,7 +17,7 @@ const router: Router = Router();
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const providers = getProviders(req);
|
||||
const pager = new JsonPager<Repository>(req, res);
|
||||
const searchOptions = {
|
||||
|
@ -39,7 +39,7 @@ router.get(
|
|||
})
|
||||
);
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available within this cross-organization repps list', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
|
||||
import { jsonError } from '../../middleware/jsonError';
|
||||
import { IAppSession, ReposAppRequest } from '../../interfaces';
|
||||
|
@ -42,7 +42,7 @@ router.post('/github', (req: ReposAppRequest, res) => {
|
|||
res.end();
|
||||
});
|
||||
|
||||
router.use('*', (req: ReposAppRequest, res, next) => {
|
||||
router.use('*', (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('API or route not found', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { Operations, Team } from '../../business';
|
||||
|
@ -40,7 +40,7 @@ async function getCrossOrganizationTeams(operations: Operations): Promise<Team[]
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: ReposAppRequest, res, next) => {
|
||||
asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
const { operations } = getProviders(req);
|
||||
const pager = new JsonPager<Team>(req, res);
|
||||
const q: string = (req.query.q ? (req.query.q as string) : null) || '';
|
||||
|
@ -71,7 +71,7 @@ router.get(
|
|||
})
|
||||
);
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('no API or function available within this cross-organization teams list', 404));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Response, Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
const router: Router = Router();
|
||||
|
||||
|
@ -19,9 +19,9 @@ import { PersonalAccessToken } from '../entities/token/token';
|
|||
|
||||
const thisApiScopeName = 'extension';
|
||||
|
||||
interface IExtensionResponse extends Response {
|
||||
type ExtensionResponse = Response & {
|
||||
localKey?: any;
|
||||
}
|
||||
};
|
||||
|
||||
interface IConnectionInformation {
|
||||
link?: any;
|
||||
|
@ -29,7 +29,7 @@ interface IConnectionInformation {
|
|||
auth?: any;
|
||||
}
|
||||
|
||||
router.use(function (req: IApiRequest, res, next) {
|
||||
router.use(function (req: IApiRequest, res: Response, next: NextFunction) {
|
||||
const token = req.apiKeyToken;
|
||||
if (!token.scopes) {
|
||||
return next(jsonError('The key is not authorized for specific APIs', 403));
|
||||
|
@ -40,7 +40,7 @@ router.use(function (req: IApiRequest, res, next) {
|
|||
return next();
|
||||
});
|
||||
|
||||
function overwriteUserContext(req: IApiRequest, res, next) {
|
||||
function overwriteUserContext(req: IApiRequest, res: Response, next: NextFunction) {
|
||||
const token = req.apiKeyToken;
|
||||
const corporateId = token.corporateId;
|
||||
if (!corporateId) {
|
||||
|
@ -121,7 +121,7 @@ router.get('/', (req: IApiRequest, res) => {
|
|||
router.get(
|
||||
'/metadata',
|
||||
asyncHandler(getLocalEncryptionKeyMiddleware),
|
||||
(req: IApiRequest, res: IExtensionResponse) => {
|
||||
(req: IApiRequest, res: ExtensionResponse) => {
|
||||
const apiContext = req.apiContext;
|
||||
|
||||
const localKey = res.localKey;
|
||||
|
@ -186,7 +186,11 @@ function getSanitizedOrganizations(operations) {
|
|||
return value;
|
||||
}
|
||||
|
||||
async function getLocalEncryptionKeyMiddleware(req: IApiRequest, res, next): Promise<void> {
|
||||
async function getLocalEncryptionKeyMiddleware(
|
||||
req: IApiRequest,
|
||||
res: ExtensionResponse,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
const providers = getProviders(req);
|
||||
const localExtensionKeyProvider = providers.localExtensionKeyProvider;
|
||||
const apiKeyToken = req.apiKeyToken;
|
||||
|
@ -250,7 +254,7 @@ async function getOrCreateLocalEncryptionKey(
|
|||
return await createLocalEncryptionKey(insights, localExtensionKeyProvider, corporateId);
|
||||
}
|
||||
|
||||
router.use('*', (req, res, next) => {
|
||||
router.use('*', (req, res: Response, next: NextFunction) => {
|
||||
return next(jsonError('API not found', 404));
|
||||
});
|
||||
|
||||
|
|
10
api/index.ts
10
api/index.ts
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
const router: Router = Router();
|
||||
|
||||
|
@ -36,7 +36,7 @@ function isClientRoute(req: ReposAppRequest) {
|
|||
|
||||
router.use('/webhook', apiWebhook);
|
||||
|
||||
router.use((req: IApiRequest, res, next) => {
|
||||
router.use((req: IApiRequest, res: Response, next: NextFunction) => {
|
||||
if (isClientRoute(req)) {
|
||||
// The frontend client routes are hooked into Express after
|
||||
// the session middleware. The client route does not require
|
||||
|
@ -91,7 +91,7 @@ router.post('/:org/repos', aadAndCustomProviders);
|
|||
router.post(
|
||||
'/:org/repos',
|
||||
requireAadApiAuthorizedScope(['repo/create', 'createRepo']),
|
||||
function (req: IApiRequest, res, next) {
|
||||
function (req: IApiRequest, res: Response, next: NextFunction) {
|
||||
const orgName = req.params.org;
|
||||
if (!req.apiKeyToken.organizationScopes) {
|
||||
return next(jsonError('There is a problem with the key configuration (no organization scopes)', 412));
|
||||
|
@ -116,7 +116,7 @@ router.post(
|
|||
|
||||
router.post(
|
||||
'/:org/repos',
|
||||
asyncHandler(async function (req: ReposAppRequest, res, next) {
|
||||
asyncHandler(async function (req: ReposAppRequest, res: Response, next: NextFunction) {
|
||||
const providers = getProviders(req);
|
||||
const organization = req.organization;
|
||||
const convergedObject = Object.assign({}, req.headers);
|
||||
|
@ -185,7 +185,7 @@ router.post(
|
|||
})
|
||||
);
|
||||
|
||||
router.use((req: IApiRequest, res, next) => {
|
||||
router.use((req: IApiRequest, res: Response, next: NextFunction) => {
|
||||
if (isClientRoute(req)) {
|
||||
// The frontend client routes are hooked into Express after
|
||||
// the session middleware. The client route does not require
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { NextFunction, Response } from 'express';
|
||||
import { getProviders } from '../transitional';
|
||||
|
||||
export default function JsonErrorHandler(err, req, res, next) {
|
||||
export default function JsonErrorHandler(err, req, res: Response, next: NextFunction) {
|
||||
if (err && err['json']) {
|
||||
// jsonError objects should bubble up like before
|
||||
return next(err);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
|
||||
import { json404 } from '../../middleware/jsonError';
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { NextFunction, Response } from 'express';
|
||||
|
||||
import { getProviders } from '../../transitional';
|
||||
import { jsonError } from '../../middleware';
|
||||
import { IApiRequest } from '../../middleware/apiReposAuth';
|
||||
|
@ -15,7 +17,7 @@ const supportedApiVersions = new Set([
|
|||
'2019-10-01',
|
||||
]);
|
||||
|
||||
export default async function postLinkApi(req: IApiRequest, res, next) {
|
||||
export default async function postLinkApi(req: IApiRequest, res: Response, next: NextFunction) {
|
||||
const providers = getProviders(req);
|
||||
const { operations } = providers;
|
||||
const token = req.apiKeyToken;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { jsonError } from '../../middleware';
|
||||
|
@ -26,7 +26,7 @@ const extendedLinkApiVersions = [
|
|||
'2019-02-01',
|
||||
];
|
||||
|
||||
router.use(function (req: IApiRequest, res, next) {
|
||||
router.use(function (req: IApiRequest, res: Response, next: NextFunction) {
|
||||
const token = req.apiKeyToken;
|
||||
if (!token.scopes) {
|
||||
return next(jsonError('The key is not authorized for specific APIs', 401));
|
||||
|
@ -41,7 +41,7 @@ router.post('/', asyncHandler(postLinkApi));
|
|||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req: IApiRequest, res, next) => {
|
||||
asyncHandler(async (req: IApiRequest, res: Response, next: NextFunction) => {
|
||||
const { operations } = getProviders(req);
|
||||
const skipOrganizations = req.query.showOrganizations !== undefined && !!req.query.showOrganizations;
|
||||
const showTimestamps = req.query.showTimestamps !== undefined && req.query.showTimestamps === 'true';
|
||||
|
@ -54,7 +54,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/:linkid',
|
||||
asyncHandler(async (req: IApiRequest, res, next) => {
|
||||
asyncHandler(async (req: IApiRequest, res: Response, next: NextFunction) => {
|
||||
if (unsupportedApiVersions.includes(req.apiVersion)) {
|
||||
return next(jsonError('This API is not supported by the API version you are using.', 400));
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/github/:username',
|
||||
asyncHandler(async (req: IApiRequest, res, next) => {
|
||||
asyncHandler(async (req: IApiRequest, res: Response, next: NextFunction) => {
|
||||
if (unsupportedApiVersions.includes(req.apiVersion)) {
|
||||
return next(jsonError('This API is not supported by the API version you are using.', 400));
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/aad/userPrincipalName/:upn',
|
||||
asyncHandler(async (req: IApiRequest, res, next) => {
|
||||
asyncHandler(async (req: IApiRequest, res: Response, next: NextFunction) => {
|
||||
const upn = req.params.upn;
|
||||
const { operations } = getProviders(req);
|
||||
const skipOrganizations = req.query.showOrganizations !== undefined && !!req.query.showOrganizations;
|
||||
|
@ -212,7 +212,7 @@ router.get(
|
|||
|
||||
router.get(
|
||||
'/aad/:id',
|
||||
asyncHandler(async (req: IApiRequest, res, next) => {
|
||||
asyncHandler(async (req: IApiRequest, res: Response, next: NextFunction) => {
|
||||
if (req.apiVersion == '2016-12-01') {
|
||||
return next(jsonError('This API is not supported by the API version you are using.', 400));
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import { ICorporateLink, UnlinkPurpose } from '../../interfaces';
|
||||
|
@ -17,7 +17,7 @@ interface ILinksApiRequestWithUnlink extends IApiRequest {
|
|||
unlink?: ICorporateLink;
|
||||
}
|
||||
|
||||
router.use(function (req: ILinksApiRequestWithUnlink, res, next) {
|
||||
router.use(function (req: ILinksApiRequestWithUnlink, res: Response, next: NextFunction) {
|
||||
const token = req.apiKeyToken;
|
||||
if (!token.scopes) {
|
||||
return next(jsonError('The key is not authorized for specific APIs', 401));
|
||||
|
@ -30,7 +30,7 @@ router.use(function (req: ILinksApiRequestWithUnlink, res, next) {
|
|||
|
||||
router.use(
|
||||
'/github/id/:id',
|
||||
asyncHandler(async (req: ILinksApiRequestWithUnlink, res, next) => {
|
||||
asyncHandler(async (req: ILinksApiRequestWithUnlink, res: Response, next: NextFunction) => {
|
||||
const { linkProvider } = getProviders(req);
|
||||
const id = req.params.id;
|
||||
try {
|
||||
|
@ -46,11 +46,11 @@ router.use(
|
|||
})
|
||||
);
|
||||
|
||||
router.use('*', (req: ILinksApiRequestWithUnlink, res, next) => {
|
||||
router.use('*', (req: ILinksApiRequestWithUnlink, res: Response, next: NextFunction) => {
|
||||
return next(req.unlink ? undefined : jsonError('No link available for operation', 404));
|
||||
});
|
||||
|
||||
router.delete('*', (req: ILinksApiRequestWithUnlink, res, next) => {
|
||||
router.delete('*', (req: ILinksApiRequestWithUnlink, res: Response, next: NextFunction) => {
|
||||
const { config, operations } = getProviders(req);
|
||||
const link = req.unlink;
|
||||
let purpose: UnlinkPurpose = null;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
|
||||
import moment from 'moment';
|
||||
|
@ -21,7 +21,7 @@ interface IRequestWithRaw extends ReposAppRequest {
|
|||
}
|
||||
|
||||
router.use(
|
||||
asyncHandler(async (req: IRequestWithRaw, res, next) => {
|
||||
asyncHandler(async (req: IRequestWithRaw, res: Response, next: NextFunction) => {
|
||||
if (!isWebhookIngestionEndpointEnabled(req)) {
|
||||
return next(
|
||||
jsonError(
|
||||
|
|
|
@ -9,13 +9,15 @@ import Debug from 'debug';
|
|||
const debug = Debug.debug('g:server');
|
||||
const debugInitialization = Debug.debug('startup');
|
||||
|
||||
import app from '../app';
|
||||
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { createExpressApplication } from '..';
|
||||
|
||||
const app = createExpressApplication();
|
||||
|
||||
function normalizePort(val) {
|
||||
const port = parseInt(val, 10);
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { IReposApplication } from '../../interfaces';
|
||||
import { ExecutionEnvironment } from '../../interfaces';
|
||||
import { CreateError } from '../../transitional';
|
||||
|
||||
import Debug from 'debug';
|
||||
|
@ -146,6 +146,7 @@ export interface IGitHubAppConfiguration {
|
|||
}
|
||||
|
||||
export interface IGitHubAppsOptions {
|
||||
app: IReposApplication;
|
||||
// app: IReposApplication;
|
||||
configurations: Map<AppPurposeTypes, IGitHubAppConfiguration>;
|
||||
executionEnvironment: ExecutionEnvironment;
|
||||
}
|
||||
|
|
|
@ -65,9 +65,10 @@ export class GitHubTokenManager {
|
|||
if (!options) {
|
||||
throw new Error('options required');
|
||||
}
|
||||
const executionEnvironment = options.executionEnvironment;
|
||||
this.#options = options;
|
||||
GitHubTokenManager._forceBackgroundTokens =
|
||||
options.app.isBackgroundJob && !options.app.enableAllGitHubApps;
|
||||
executionEnvironment.isJob && !executionEnvironment.enableAllGitHubApps;
|
||||
}
|
||||
|
||||
private getFallbackList(input: AppPurposeTypes[]) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
IOperationsCentralOperationsToken,
|
||||
IAuthorizationHeaderValue,
|
||||
SiteConfiguration,
|
||||
ExecutionEnvironment,
|
||||
} from '../../interfaces';
|
||||
import { RestLibrary } from '../../lib/github';
|
||||
import { CreateError } from '../../transitional';
|
||||
|
@ -35,6 +36,7 @@ export interface IOperationsCoreOptions {
|
|||
github: RestLibrary;
|
||||
providers: IProviders;
|
||||
baseUrl?: string;
|
||||
executionEnvironment: ExecutionEnvironment;
|
||||
}
|
||||
|
||||
export enum CacheDefault {
|
||||
|
|
|
@ -86,13 +86,7 @@ export interface ICrossOrganizationMembersResult
|
|||
extends Map<number, ICrossOrganizationMembershipByOrganization> {}
|
||||
|
||||
export interface IOperationsOptions extends IOperationsCoreOptions {
|
||||
// cacheProvider: ICacheHelper;
|
||||
// config: any;
|
||||
github: RestLibrary;
|
||||
// insights: TelemetryClient;
|
||||
// linkProvider: ILinkProvider;
|
||||
// mailAddressProvider: IMailAddressProvider;
|
||||
// mailProvider: IMailProvider;
|
||||
repositoryMetadataProvider: IRepositoryMetadataProvider;
|
||||
}
|
||||
|
||||
|
@ -179,7 +173,7 @@ export class Operations
|
|||
}
|
||||
this._tokenManager = new GitHubTokenManager({
|
||||
configurations: purposesToConfigurations,
|
||||
app: this.providers.app,
|
||||
executionEnvironment: options.executionEnvironment,
|
||||
});
|
||||
GitHubTokenManager.RegisterManagerForOperations(this, this._tokenManager);
|
||||
this._dynamicOrganizationIds = new Set();
|
||||
|
|
|
@ -24,6 +24,7 @@ import { CacheDefault, getMaxAgeSeconds, getPageSize, OperationsCore } from './o
|
|||
import {
|
||||
CoreCapability,
|
||||
GitHubAuditLogEntry,
|
||||
GitHubOrganizationInvite,
|
||||
GitHubRepositoryVisibility,
|
||||
IAccountBasics,
|
||||
IAddOrganizationMembershipOptions,
|
||||
|
@ -512,10 +513,6 @@ export class Organization {
|
|||
return this._settings.hasFeature(OrganizationFeature.Hidden) || false;
|
||||
}
|
||||
|
||||
get pilot_program() {
|
||||
return this._settings.properties['1es'];
|
||||
}
|
||||
|
||||
get createRepositoriesOnGitHub(): boolean {
|
||||
return this._settings.hasFeature(OrganizationFeature.CreateNativeRepositories) || false;
|
||||
}
|
||||
|
@ -1331,7 +1328,7 @@ export class Organization {
|
|||
}
|
||||
}
|
||||
|
||||
async getMembershipInvitations(): Promise<any> {
|
||||
async getMembershipInvitations(): Promise<GitHubOrganizationInvite[]> {
|
||||
const operations = throwIfNotGitHubCapable(this._operations);
|
||||
const parameters = {
|
||||
org: this.name,
|
||||
|
@ -1342,7 +1339,7 @@ export class Organization {
|
|||
'orgs.listPendingInvitations',
|
||||
parameters
|
||||
);
|
||||
return invitations;
|
||||
return invitations as GitHubOrganizationInvite[];
|
||||
} catch (error) {
|
||||
if (error.status == /* loose */ 404) {
|
||||
return null;
|
||||
|
|
|
@ -47,6 +47,7 @@ import {
|
|||
IOperationsRepositoryMetadataProvider,
|
||||
IOperationsUrls,
|
||||
GitHubRepositoryPermission,
|
||||
GitHubRepositoryVisibility,
|
||||
} from '../interfaces';
|
||||
import { IListPullsParameters, GitHubPullRequestState } from '../lib/github/collections';
|
||||
|
||||
|
@ -256,6 +257,9 @@ export class Repository {
|
|||
get private(): boolean {
|
||||
return this._entity ? this._entity.private : false;
|
||||
}
|
||||
get visibility(): GitHubRepositoryVisibility {
|
||||
return this._entity ? this._entity.visibility : null;
|
||||
}
|
||||
get html_url(): string {
|
||||
return this._entity ? this._entity.html_url : null;
|
||||
}
|
||||
|
@ -403,6 +407,17 @@ export class Repository {
|
|||
};
|
||||
}
|
||||
|
||||
async getId(options?: ICacheOptions): Promise<number> {
|
||||
// Repositories by name may not actually have the ID; this ensures it's available
|
||||
// and a number. Similar to previously checking "isDeleted" or "getDetails" first.
|
||||
if (!this.id) {
|
||||
await this.getDetails(options);
|
||||
}
|
||||
if (this.id) {
|
||||
return typeof this.id === 'number' ? this.id : parseInt(this.id, 10);
|
||||
}
|
||||
}
|
||||
|
||||
async isDeleted(options?: ICacheOptions): Promise<boolean> {
|
||||
try {
|
||||
await this.getDetails(options);
|
||||
|
|
|
@ -9,10 +9,7 @@ import { AppPurpose } from './githubApps';
|
|||
import {
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
IOperationsInstance,
|
||||
IGetBranchesOptions,
|
||||
IGitHubBranch,
|
||||
throwIfNotGitHubCapable,
|
||||
IGetPullsOptions,
|
||||
ICacheOptions,
|
||||
IGetAuthorizationHeader,
|
||||
} from '../interfaces';
|
||||
|
|
39
docs/jobs.md
39
docs/jobs.md
|
@ -6,15 +6,34 @@ if you choose to use them.
|
|||
Jobs are an alternate entrypoint into the application, and have full use of
|
||||
the same set of [providers](./providers.md).
|
||||
|
||||
## list of cronjobs
|
||||
## Webhook event firehose
|
||||
|
||||
Several jobs are available in the container or the `jobs/` folder. These can
|
||||
optionally provide useful operational and services support. Often a Kubernetes
|
||||
CronJob can help.
|
||||
> The primary consistency and event processing loop for the entire app. [firehose](../jobs/firehose.ts)
|
||||
|
||||
- `cleanupInvites`: if configured for an org, cleanup old unaccepted invites
|
||||
- `firehose`: ongoing processing of GitHub events for keeping cache up-to-date
|
||||
- `managers`: cache the last-known manager for links, to use in notifications after a departure may remove someone from the graph
|
||||
- `permissions`: updating permissions for all-write/all-read/all-admin teams when configured
|
||||
- `refreshUsernames`: keeping link data fresh with GitHub username renames, corporate username and display name updates, and removing links for deleted GitHub users who remove their accounts permanently from GitHub.com
|
||||
- `reports`: processing the building of report data about use, abandoned repos, etc. **this job is broken**
|
||||
Ongoing processing of GitHub events for keeping cache up-to-date, locking down new repos, etc.
|
||||
|
||||
## Cleanup organization invitations
|
||||
|
||||
> [cleanupInvites](../jobs/cleanupInvites.ts)
|
||||
|
||||
If configured for an org, cleanup old unaccepted invites. This predates
|
||||
GitHub-native expiration of invites.
|
||||
|
||||
## System Team permissions
|
||||
|
||||
> [permissions](../jobs/permissions.ts)
|
||||
|
||||
Updating permissions for all-write/all-read/all-admin teams when configured
|
||||
|
||||
## Refresh usernames and other link attributes
|
||||
|
||||
> [refreshUsernames](../jobs/refreshUsernames.ts)
|
||||
|
||||
Keeps link data fresh with GitHub username renames, corporate username and display name updates,
|
||||
and removing links for deleted GitHub users who remove their accounts permanently from GitHub.com.
|
||||
|
||||
## Cleanup blob cache
|
||||
|
||||
Removes expired cache entities from the blob cache.
|
||||
|
||||
> [cleanupBlobCache](../jobs/cleanupBlobCache.ts)
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
QueryBase,
|
||||
} from '../lib/entityMetadataProvider';
|
||||
import { PostgresConfiguration, PostgresSettings } from '../lib/entityMetadataProvider/postgres';
|
||||
import { ErrorHelper } from '../transitional';
|
||||
|
||||
const type = new EntityMetadataType('RepositoryDetails');
|
||||
const typeColumnValue = 'repositorydetails';
|
||||
|
@ -312,6 +313,21 @@ for (let i = 0; i < fieldNames.length; i++) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function tryGetRepositoryEntity(
|
||||
repositoryProvider: IRepositoryProvider,
|
||||
repositoryId: number
|
||||
): Promise<RepositoryEntity> {
|
||||
try {
|
||||
const repositoryEntity = await repositoryProvider.get(repositoryId);
|
||||
return repositoryEntity;
|
||||
} catch (error) {
|
||||
if (ErrorHelper.IsNotFound(error)) {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export const EntityImplementation = {
|
||||
Type: type,
|
||||
EnsureDefinitions: () => {},
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
//
|
||||
|
||||
import { EntityField } from '../../lib/entityMetadataProvider/entityMetadataProvider';
|
||||
import { IEntityMetadata } from '../../lib/entityMetadataProvider/entityMetadata';
|
||||
import { IEntityMetadataFixedQuery, FixedQueryType } from '../../lib/entityMetadataProvider/query';
|
||||
import type { IEntityMetadata } from '../../lib/entityMetadataProvider/entityMetadata';
|
||||
import { type IEntityMetadataFixedQuery, FixedQueryType } from '../../lib/entityMetadataProvider/query';
|
||||
import {
|
||||
EntityMetadataMappings,
|
||||
MetadataMappingDefinition,
|
||||
|
@ -17,7 +17,7 @@ import {
|
|||
PostgresSettings,
|
||||
PostgresConfiguration,
|
||||
} from '../../lib/entityMetadataProvider/postgres';
|
||||
import { TableConfiguration, TableSettings } from '../../lib/entityMetadataProvider/table';
|
||||
import { TableSettings } from '../../lib/entityMetadataProvider/table';
|
||||
import { MemoryConfiguration, MemorySettings } from '../../lib/entityMetadataProvider/memory';
|
||||
import { odata, TableEntityQueryOptions } from '@azure/data-tables';
|
||||
import {
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
//
|
||||
// Copyright (c) Microsoft.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import express from 'express';
|
||||
import Debug from 'debug';
|
||||
|
||||
import type { ExecutionEnvironment, IReposApplication, SiteConfiguration } from './interfaces';
|
||||
import configResolver from './lib/config';
|
||||
import initialize from './middleware/initialize';
|
||||
|
||||
// Library framework
|
||||
export * from './interfaces';
|
||||
|
||||
// Application framework
|
||||
type InitializeCall = (
|
||||
executionEnvironment: ExecutionEnvironment,
|
||||
config: SiteConfiguration,
|
||||
configurationError: Error
|
||||
) => Promise<ExecutionEnvironment>;
|
||||
|
||||
export function createExpressApplication(): IReposApplication {
|
||||
Debug.debug('startup')('starting web framework...');
|
||||
const app = express() as any as IReposApplication;
|
||||
|
||||
app.initializeApplication = initializeApp.bind(undefined, app, express, __dirname);
|
||||
app.startupApplication = commonStartup.bind(
|
||||
undefined,
|
||||
app.initializeApplication,
|
||||
false /* not a job */,
|
||||
app
|
||||
);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
function initializeApp(
|
||||
app: IReposApplication,
|
||||
express: any,
|
||||
dirname: string,
|
||||
executionEnvironment: ExecutionEnvironment,
|
||||
config: SiteConfiguration,
|
||||
configurationError: Error
|
||||
) {
|
||||
return initialize(executionEnvironment, app, express, dirname, config, configurationError);
|
||||
}
|
||||
|
||||
export async function commonStartup(call: InitializeCall, isJob: boolean, app?: IReposApplication) {
|
||||
const executionEnvironment: ExecutionEnvironment = {
|
||||
isJob,
|
||||
enableAllGitHubApps: undefined,
|
||||
//
|
||||
expressApplication: app,
|
||||
//
|
||||
providers: undefined,
|
||||
skipModules: new Set(),
|
||||
//
|
||||
started: new Date(),
|
||||
};
|
||||
|
||||
let painlessConfigResolver = null;
|
||||
try {
|
||||
painlessConfigResolver = configResolver();
|
||||
} catch (error) {
|
||||
console.warn('Painless config resolver initialization error:');
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
let config: any = null;
|
||||
let configurationError: Error = null;
|
||||
try {
|
||||
config = await painlessConfigResolver.resolve();
|
||||
} catch (error) {
|
||||
configurationError = error;
|
||||
}
|
||||
if (isJob) {
|
||||
executionEnvironment.skipModules.add('web');
|
||||
}
|
||||
try {
|
||||
await call(executionEnvironment, config, configurationError);
|
||||
} catch (startupError) {
|
||||
console.error(`Startup error: ${startupError}`);
|
||||
if (startupError.stack) {
|
||||
console.error(startupError.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return executionEnvironment;
|
||||
}
|
|
@ -3,14 +3,21 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Application } from 'express';
|
||||
import { Application, Response, NextFunction } from 'express';
|
||||
import { IProviders } from './providers';
|
||||
|
||||
import type { RuntimeConfiguration } from './config';
|
||||
import { ReposAppRequest } from './web';
|
||||
|
||||
export interface IApplicationProfile {
|
||||
applicationName: string;
|
||||
customErrorHandlerRender?: (errorView: any, err: Error, req: any, res: any, next: any) => Promise<void>;
|
||||
customErrorHandlerRender?: (
|
||||
errorView: unknown,
|
||||
err: Error,
|
||||
req: ReposAppRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => Promise<void | unknown>;
|
||||
customRoutes?: () => Promise<void>;
|
||||
logDependencies: boolean;
|
||||
serveClientAssets: boolean;
|
||||
|
@ -32,24 +39,43 @@ export interface IReposApplication extends Application {
|
|||
enableAllGitHubApps: boolean;
|
||||
runtimeConfiguration: RuntimeConfiguration;
|
||||
|
||||
executionEnvironment: ExecutionEnvironment;
|
||||
|
||||
startServer: () => Promise<void>;
|
||||
|
||||
initializeApplication: (config: any, configurationError: Error) => Promise<IReposApplication>;
|
||||
initializeJob: (config: any, configurationError: Error) => Promise<IReposApplication>;
|
||||
initializeApplication: (
|
||||
executionEnvironment: ExecutionEnvironment,
|
||||
config: any,
|
||||
configurationError: Error
|
||||
) => Promise<IReposApplication>;
|
||||
|
||||
startupApplication: () => Promise<IReposApplication>;
|
||||
startupJob: () => Promise<IReposApplication>;
|
||||
runJob: (
|
||||
job: (job: IReposJob) => Promise<IReposJobResult | void>,
|
||||
options?: IReposJobOptions
|
||||
) => Promise<IReposApplication>;
|
||||
) => Promise<IReposJobResult | void>;
|
||||
}
|
||||
|
||||
export type ExecutionEnvironment = {
|
||||
isJob: boolean;
|
||||
enableAllGitHubApps: boolean;
|
||||
|
||||
expressApplication: IReposApplication | null;
|
||||
|
||||
providers: IProviders;
|
||||
skipModules: Set<string>;
|
||||
|
||||
started: Date;
|
||||
};
|
||||
|
||||
export interface IReposJob {
|
||||
app: IReposApplication;
|
||||
started: Date;
|
||||
providers: IProviders;
|
||||
parameters: any;
|
||||
args: string[];
|
||||
|
||||
executionEnvironment: ExecutionEnvironment;
|
||||
}
|
||||
|
||||
export interface IReposJobResult {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
|
||||
import { IDictionary } from '../../interfaces';
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
//
|
||||
|
||||
import { PassportStatic } from 'passport';
|
||||
import { IAuthenticationHelperMethods } from '../../middleware/passport-routes';
|
||||
import type { IAuthenticationHelperMethods } from '../../middleware/passport-routes';
|
||||
|
||||
export interface ICompanySpecificPassportMiddleware {
|
||||
configure?: (app: any, config: any, passport: PassportStatic) => void;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
import { IAttachCompanySpecificRoutesApi } from './api';
|
||||
|
||||
export * from './api';
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { ICacheOptions, IPagedCacheOptions } from '.';
|
||||
import { ICorporateLink } from '..';
|
||||
import type { ICacheOptions, IPagedCacheOptions } from '.';
|
||||
import type { ICorporateLink } from '..';
|
||||
import { OrganizationMember } from '../../business';
|
||||
import { Repository } from '../../business/repository';
|
||||
|
||||
|
@ -97,3 +97,24 @@ export interface IOrganizationMembership {
|
|||
organization: any;
|
||||
user: any;
|
||||
}
|
||||
|
||||
export type GitHubSimpleAccount = {
|
||||
login: string;
|
||||
avatar_url: string;
|
||||
id: number;
|
||||
};
|
||||
|
||||
export type GitHubOrganizationInvite = {
|
||||
created_at: string;
|
||||
email: string;
|
||||
failed_at: string;
|
||||
failed_reason: string;
|
||||
id: number;
|
||||
invitation_source: string; // 'member'
|
||||
invitation_teams_url: string;
|
||||
inviter: GitHubSimpleAccount;
|
||||
login: string;
|
||||
node_id: string;
|
||||
role: string; // 'direct_member'
|
||||
team_count: number;
|
||||
};
|
||||
|
|
|
@ -16,7 +16,8 @@ export * from './providers';
|
|||
export * from './web';
|
||||
export * from './config';
|
||||
|
||||
import {
|
||||
import type { ExecutionEnvironment } from './app';
|
||||
import type {
|
||||
IAttachCompanySpecificRoutes,
|
||||
IAttachCompanySpecificMiddleware,
|
||||
ICorporationAdministrationSection,
|
||||
|
@ -25,8 +26,9 @@ import {
|
|||
IAttachCompanySpecificViews,
|
||||
IAttachCompanySpecificUrls,
|
||||
} from './companySpecific';
|
||||
import { ICompanySpecificPassportMiddleware } from './companySpecific/passport';
|
||||
import { IProviders } from './providers';
|
||||
import type { ICompanySpecificPassportMiddleware } from './companySpecific/passport';
|
||||
import type { SiteConfiguration } from './config';
|
||||
import type { IProviders } from './providers';
|
||||
|
||||
// We're great at long variable names!
|
||||
|
||||
|
@ -42,6 +44,11 @@ export interface ICompanySpecificStartupProperties {
|
|||
urls?: IAttachCompanySpecificUrls;
|
||||
}
|
||||
|
||||
export type ICompanySpecificStartupFunction = (config: any, p: IProviders, rootdir: string) => Promise<void>;
|
||||
export type ICompanySpecificStartupFunction = (
|
||||
executionEnvironment: ExecutionEnvironment,
|
||||
config: SiteConfiguration,
|
||||
p: IProviders,
|
||||
rootdir: string
|
||||
) => Promise<void>;
|
||||
|
||||
export type ICompanySpecificStartup = ICompanySpecificStartupFunction & ICompanySpecificStartupProperties;
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
//
|
||||
// Copyright (c) Microsoft.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
// eslint-disable-next-line n/no-unpublished-import
|
||||
import type { Config } from 'jest';
|
||||
|
||||
|
|
150
app.ts → job.ts
150
app.ts → job.ts
|
@ -3,69 +3,27 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import express from 'express';
|
||||
import { hostname } from 'os';
|
||||
|
||||
import { IReposApplication, IReposJob, IReposJobOptions, IReposJobResult } from './interfaces';
|
||||
|
||||
import configResolver from './lib/config';
|
||||
import initialize from './middleware/initialize';
|
||||
import { quitInTenSeconds } from './utils';
|
||||
|
||||
const app = express() as any as IReposApplication;
|
||||
|
||||
import Debug from 'debug';
|
||||
Debug.debug('startup')('starting...');
|
||||
|
||||
app.initializeApplication = initialize.bind(undefined, app, express, __dirname);
|
||||
import type {
|
||||
ExecutionEnvironment,
|
||||
IProviders,
|
||||
IReposJob,
|
||||
IReposJobOptions,
|
||||
IReposJobResult,
|
||||
SiteConfiguration,
|
||||
} from './interfaces';
|
||||
import { commonStartup } from '.';
|
||||
import { quitInTenSeconds } from './utils';
|
||||
import initialize from './middleware/initialize';
|
||||
|
||||
app.initializeJob = function initializeJob(config, configurationError) {
|
||||
if (config) {
|
||||
config.isJobInternal = true;
|
||||
config.skipModules = new Set(['web']);
|
||||
} else {
|
||||
console.warn(`Configuration did not resolve successfully`, configurationError);
|
||||
}
|
||||
return initialize(app, express, __dirname, config, configurationError);
|
||||
};
|
||||
|
||||
async function startup(startupApplication: boolean) {
|
||||
let painlessConfigResolver = null;
|
||||
try {
|
||||
painlessConfigResolver = configResolver();
|
||||
} catch (error) {
|
||||
console.warn('Painless config resolver initialization error:');
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
let config: any = null;
|
||||
let configurationError: Error = null;
|
||||
try {
|
||||
config = await painlessConfigResolver.resolve();
|
||||
} catch (error) {
|
||||
configurationError = error;
|
||||
}
|
||||
|
||||
try {
|
||||
if (startupApplication) {
|
||||
await app.initializeApplication(config, configurationError);
|
||||
} else {
|
||||
await app.initializeJob(config, configurationError);
|
||||
}
|
||||
} catch (startupError) {
|
||||
console.error(`Startup error: ${startupError}`);
|
||||
process.exit(1); // throw startupError;
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
app.startupApplication = startup.bind(null, true);
|
||||
app.startupJob = startup.bind(null, false);
|
||||
app.runJob = async function (
|
||||
export async function runJob(
|
||||
job: (job: IReposJob) => Promise<IReposJobResult | void>,
|
||||
options?: IReposJobOptions
|
||||
): Promise<IReposApplication> {
|
||||
): Promise<IReposJobResult | void> {
|
||||
Debug.debug('startup')('starting job...');
|
||||
|
||||
options = options || {};
|
||||
// TODO: automatically track elapsed job time
|
||||
const started = new Date();
|
||||
|
@ -79,20 +37,19 @@ app.runJob = async function (
|
|||
if (options.defaultDebugOutput && !process.env.DEBUG) {
|
||||
process.env.DEBUG = options.defaultDebugOutput;
|
||||
}
|
||||
app.isBackgroundJob = true;
|
||||
if (options.enableAllGitHubApps) {
|
||||
app.enableAllGitHubApps = true;
|
||||
}
|
||||
|
||||
let executionEnvironment: ExecutionEnvironment = null;
|
||||
try {
|
||||
await app.startupJob();
|
||||
executionEnvironment = await commonStartup(initializeJob, true /* job */, null /* app */);
|
||||
} catch (startupError) {
|
||||
console.error(`Job startup error before runJob: ${startupError}`);
|
||||
quitInTenSeconds(false);
|
||||
return app;
|
||||
return;
|
||||
}
|
||||
if (options.insightsPrefix && app.providers.insights) {
|
||||
const providers = executionEnvironment?.providers;
|
||||
if (options.insightsPrefix && providers?.insights) {
|
||||
try {
|
||||
app.providers.insights.trackEvent({
|
||||
providers?.insights?.trackEvent({
|
||||
name: `${options.insightsPrefix}Started`,
|
||||
properties: {
|
||||
hostname: hostname(),
|
||||
|
@ -103,17 +60,19 @@ app.runJob = async function (
|
|||
}
|
||||
}
|
||||
const jobObject = {
|
||||
app,
|
||||
providers: app.providers,
|
||||
app: providers?.app,
|
||||
executionEnvironment,
|
||||
providers,
|
||||
started,
|
||||
parameters: options && options.parameters ? options.parameters : {},
|
||||
args: process.argv.length > 2 ? process.argv.slice(2) : [],
|
||||
};
|
||||
let result: IReposJobResult = null;
|
||||
try {
|
||||
const result = await job.call(null, jobObject);
|
||||
if (result && result.successProperties && app.providers.insights && options.insightsPrefix) {
|
||||
result = (await job.call(null, jobObject)) as IReposJobResult;
|
||||
if (result?.successProperties && providers?.insights && options.insightsPrefix) {
|
||||
try {
|
||||
app.providers.insights.trackEvent({
|
||||
providers?.insights?.trackEvent({
|
||||
name: `${options.insightsPrefix}Success`,
|
||||
properties: Object.assign(
|
||||
{
|
||||
|
@ -128,14 +87,17 @@ app.runJob = async function (
|
|||
}
|
||||
} catch (jobError) {
|
||||
console.error(`The job failed: ${jobError}`);
|
||||
if (jobError.stack) {
|
||||
console.error(jobError.stack);
|
||||
}
|
||||
// by default, let's not show the whole inner error
|
||||
const simpleError = { ...jobError };
|
||||
simpleError?.cause && delete simpleError.cause;
|
||||
console.dir(simpleError);
|
||||
quitInTenSeconds(false);
|
||||
if (options.insightsPrefix && app.providers.insights) {
|
||||
if (options.insightsPrefix) {
|
||||
try {
|
||||
app.providers.insights.trackException({
|
||||
providers?.insights?.trackException({
|
||||
exception: jobError,
|
||||
properties: {
|
||||
name: `${options.insightsPrefix}Failure`,
|
||||
|
@ -145,12 +107,50 @@ app.runJob = async function (
|
|||
console.error(`insights error: ${ignoreInsightsError}`);
|
||||
}
|
||||
}
|
||||
return app;
|
||||
return result;
|
||||
}
|
||||
// CONSIDER: insights metric for job time
|
||||
console.log();
|
||||
console.log('The job was successful.');
|
||||
quitInTenSeconds(true);
|
||||
return app;
|
||||
return result;
|
||||
}
|
||||
|
||||
function initializeJob(
|
||||
executionEnvironment: ExecutionEnvironment,
|
||||
config: SiteConfiguration,
|
||||
configurationError: Error
|
||||
) {
|
||||
if (!config || configurationError) {
|
||||
console.warn(`Configuration did not resolve successfully`, configurationError);
|
||||
}
|
||||
return initialize(
|
||||
executionEnvironment,
|
||||
null /* app */,
|
||||
null /* express */,
|
||||
__dirname,
|
||||
config,
|
||||
configurationError
|
||||
);
|
||||
}
|
||||
|
||||
const job = {
|
||||
runBackgroundJob: async (
|
||||
script: (providers: IProviders, jobParameters?: IReposJob) => Promise<IReposJobResult | void>,
|
||||
options?: IReposJobOptions
|
||||
) => {
|
||||
return runJob(async function (jobParameters: IReposJob) {
|
||||
return (await script(jobParameters.providers, jobParameters)) || {};
|
||||
}, Object.assign({ enableAllGitHubApps: false }, options || {}));
|
||||
},
|
||||
run: async (
|
||||
script: (providers: IProviders, jobParameters?: IReposJob) => Promise<IReposJobResult | void>,
|
||||
options?: IReposJobOptions
|
||||
) => {
|
||||
return runJob(async function (jobParameters: IReposJob) {
|
||||
return (await script(jobParameters.providers, jobParameters)) || {};
|
||||
}, Object.assign({ enableAllGitHubApps: true }, options || {}));
|
||||
},
|
||||
};
|
||||
|
||||
export default app;
|
||||
export default job;
|
|
@ -3,10 +3,15 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import BlobCache from '../../lib/caching/blob';
|
||||
import { IReposJob } from '../../interfaces';
|
||||
// Job 17: Cleanup blob cache
|
||||
|
||||
export default async function go({ providers }: IReposJob): Promise<void> {
|
||||
import BlobCache from '../lib/caching/blob';
|
||||
import job from '../job';
|
||||
import { IProviders } from '../interfaces';
|
||||
|
||||
job.runBackgroundJob(cleanup);
|
||||
|
||||
async function cleanup(providers: IProviders): Promise<void> {
|
||||
for (const providerName in providers) {
|
||||
const provider = providers[providerName];
|
||||
if (provider && provider['expiringBlobCache']) {
|
|
@ -1,9 +0,0 @@
|
|||
//
|
||||
// Copyright (c) Microsoft.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import Job from './task';
|
||||
import app from '../../app';
|
||||
|
||||
app.runJob(Job);
|
|
@ -3,48 +3,50 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import moment from 'moment';
|
||||
|
||||
import { IReposJob } from '../../interfaces';
|
||||
// Job 16: cleanup invites
|
||||
|
||||
// Organization invitations cleanup: remove any invitations that are older than a
|
||||
// set period of time from the organization.
|
||||
|
||||
import { GitHubOrganizationInvite, IProviders } from '../interfaces';
|
||||
import job from '../job';
|
||||
import { daysInMilliseconds } from '../utils';
|
||||
|
||||
const defaultMaximumInvitationAgeDays = 4;
|
||||
|
||||
export default async function cleanup({ providers }: IReposJob): Promise<void> {
|
||||
job.runBackgroundJob(cleanup, {
|
||||
timeoutMinutes: 90,
|
||||
insightsPrefix: 'JobOrganizationInvitationsCleanup',
|
||||
});
|
||||
|
||||
async function cleanup(providers: IProviders) {
|
||||
const insights = providers.insights;
|
||||
let maximumInvitationAgeDays = defaultMaximumInvitationAgeDays;
|
||||
const { config, operations } = providers;
|
||||
if (
|
||||
config.github &&
|
||||
config.github.jobs &&
|
||||
config.github.jobs.cleanup &&
|
||||
config.github.jobs.cleanup.maximumInvitationAgeDays
|
||||
) {
|
||||
if (config?.github?.jobs?.cleanup?.maximumInvitationAgeDays) {
|
||||
maximumInvitationAgeDays = config.github.jobs.cleanup.maximumInvitationAgeDays;
|
||||
}
|
||||
const maximumAgeMoment = moment().subtract(maximumInvitationAgeDays, 'days');
|
||||
const maximumAgeDate = new Date(new Date().getTime() - daysInMilliseconds(maximumInvitationAgeDays));
|
||||
const organizations = operations.getOrganizations();
|
||||
const removedInvitations = 0;
|
||||
for (const organization of organizations) {
|
||||
let invitations: any[];
|
||||
let invitations: GitHubOrganizationInvite[];
|
||||
try {
|
||||
invitations = await organization.getMembershipInvitations();
|
||||
} catch (getInvitationsError) {
|
||||
insights.trackException({ exception: getInvitationsError });
|
||||
insights?.trackException({ exception: getInvitationsError });
|
||||
console.dir(getInvitationsError);
|
||||
continue;
|
||||
}
|
||||
if (!invitations || invitations.length === 0) {
|
||||
continue;
|
||||
}
|
||||
const invitationsToRemove = [];
|
||||
const invitationsToRemove: string[] = [];
|
||||
let emailInvitations = 0;
|
||||
for (let i = 0; i < invitations.length; i++) {
|
||||
const invite = invitations[i];
|
||||
const createdAt = moment(invite.created_at);
|
||||
if (createdAt.isBefore(maximumAgeMoment)) {
|
||||
const createdAt = new Date(invite.created_at);
|
||||
if (createdAt < maximumAgeDate) {
|
||||
if (invite.login) {
|
||||
invitationsToRemove.push(invite.login);
|
||||
} else {
|
||||
|
@ -52,8 +54,7 @@ export default async function cleanup({ providers }: IReposJob): Promise<void> {
|
|||
console.warn(`An e-mail based invitation to ${invite.email} cannot be automatically canceled`);
|
||||
}
|
||||
const data = {
|
||||
createdAt: createdAt.format(),
|
||||
invitedAgo: createdAt.fromNow(),
|
||||
createdAt: createdAt.toISOString(),
|
||||
login: invite.login,
|
||||
inviter: invite && invite.inviter && invite.inviter.login ? invite.inviter.login : undefined,
|
||||
role: invite.role,
|
||||
|
@ -62,7 +63,7 @@ export default async function cleanup({ providers }: IReposJob): Promise<void> {
|
|||
const eventName = invite.login
|
||||
? 'JobOrganizationInviteCleanupInvitationNeeded'
|
||||
: 'JobOrganizationInviteCleanupInvitationNotUser';
|
||||
insights.trackEvent({
|
||||
insights?.trackEvent({
|
||||
name: eventName,
|
||||
properties: data,
|
||||
});
|
||||
|
@ -80,8 +81,8 @@ export default async function cleanup({ providers }: IReposJob): Promise<void> {
|
|||
try {
|
||||
await organization.removeMember(login);
|
||||
} catch (removeError) {
|
||||
insights.trackException({ exception: removeError });
|
||||
insights.trackEvent({
|
||||
insights?.trackException({ exception: removeError });
|
||||
insights?.trackEvent({
|
||||
name: 'JobOrganizationInvitationsCleanupInvitationFailed',
|
||||
properties: {
|
||||
login: login,
|
||||
|
@ -92,5 +93,5 @@ export default async function cleanup({ providers }: IReposJob): Promise<void> {
|
|||
}
|
||||
}
|
||||
console.log(`Job finishing. Removed ${removedInvitations} expired invitations.`);
|
||||
insights.trackMetric({ name: 'JobOrganizationInvitationsExpired', value: removedInvitations });
|
||||
insights?.trackMetric({ name: 'JobOrganizationInvitationsExpired', value: removedInvitations });
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
//
|
||||
// Copyright (c) Microsoft.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import Job from './task';
|
||||
import app from '../../app';
|
||||
|
||||
app.runJob(Job, {
|
||||
timeoutMinutes: 90,
|
||||
defaultDebugOutput: 'restapi',
|
||||
insightsPrefix: 'JobOrganizationInvitationsCleanup',
|
||||
});
|
|
@ -1,9 +0,0 @@
|
|||
//
|
||||
// Copyright (c) Microsoft.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import Job from './task';
|
||||
import app from '../../app';
|
||||
|
||||
app.runJob(Job, { timeoutMinutes: 90, defaultDebugOutput: 'restapi', insightsPrefix: 'JobCleanupKeys' });
|
|
@ -1,98 +0,0 @@
|
|||
//
|
||||
// Copyright (c) Microsoft.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import throat from 'throat';
|
||||
|
||||
import { IReposJob, IReposJobResult } from '../../interfaces';
|
||||
import { sleep } from '../../utils';
|
||||
import { IGraphProvider } from '../../lib/graphProvider';
|
||||
import { LocalExtensionKey } from '../../entities/localExtensionKey/localExtensionKey';
|
||||
|
||||
async function lookupCorporateId(
|
||||
graphProvider: IGraphProvider,
|
||||
knownUsers: Map<string, any>,
|
||||
corporateId: string
|
||||
): Promise<any> {
|
||||
const entry = knownUsers.get(corporateId);
|
||||
if (entry === false) {
|
||||
return false;
|
||||
} else if (entry) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
const userDetails = await graphProvider.getUserById(corporateId);
|
||||
if (!userDetails || !userDetails.userPrincipalName) {
|
||||
knownUsers.set(corporateId, false);
|
||||
return false;
|
||||
}
|
||||
knownUsers.set(corporateId, userDetails);
|
||||
return true;
|
||||
} catch (otherUserError) {
|
||||
console.dir(otherUserError);
|
||||
throw otherUserError;
|
||||
}
|
||||
}
|
||||
|
||||
export default async function cleanup({ providers }: IReposJob): Promise<IReposJobResult> {
|
||||
const graphProvider = providers.graphProvider;
|
||||
const localExtensionKeyProvider = providers.localExtensionKeyProvider;
|
||||
const insights = providers.insights;
|
||||
|
||||
console.log('reading all keys');
|
||||
const allKeys = await localExtensionKeyProvider.getAllKeys();
|
||||
console.log(`read ${allKeys.length}`);
|
||||
|
||||
insights.trackEvent({ name: 'JobCleanupTokensKeysTokens', properties: { tokens: String(allKeys.length) } });
|
||||
|
||||
let errors = 0;
|
||||
|
||||
let deleted = 0;
|
||||
let okUserTokens = 0;
|
||||
|
||||
const parallelUsers = 2;
|
||||
const secondsDelayAfterSuccess = 0.25;
|
||||
|
||||
const knownUsers = new Map<string, any>();
|
||||
|
||||
const throttle = throat(parallelUsers);
|
||||
await Promise.all(
|
||||
allKeys.map((key: LocalExtensionKey) =>
|
||||
throttle(async () => {
|
||||
const corporateId = key.corporateId;
|
||||
const userStatus = await lookupCorporateId(graphProvider, knownUsers, corporateId);
|
||||
if (!userStatus) {
|
||||
try {
|
||||
++deleted;
|
||||
console.log(`${deleted}: Deleting key for ${corporateId} that could not be found`);
|
||||
await localExtensionKeyProvider.delete(key);
|
||||
} catch (tokenDeleteError) {
|
||||
--deleted;
|
||||
console.dir(tokenDeleteError);
|
||||
++errors;
|
||||
insights.trackException({ exception: tokenDeleteError });
|
||||
}
|
||||
} else {
|
||||
++okUserTokens;
|
||||
console.log(`${okUserTokens}: valid`);
|
||||
}
|
||||
|
||||
await sleep(secondsDelayAfterSuccess * 1000);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
console.log(`deleted: ${deleted}`);
|
||||
console.log(`okUserTokens: ${okUserTokens}`);
|
||||
console.log();
|
||||
|
||||
return {
|
||||
successProperties: {
|
||||
deleted,
|
||||
okUserTokens,
|
||||
errors,
|
||||
},
|
||||
};
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
//
|
||||
// Copyright (c) Microsoft.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import Job from './task';
|
||||
import app from '../../app';
|
||||
|
||||
app.runJob(Job, { timeoutMinutes: 90, defaultDebugOutput: 'restapi', insightsPrefix: 'JobCleanupTokens' });
|
|
@ -1,142 +0,0 @@
|
|||
//
|
||||
// Copyright (c) Microsoft.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import throat from 'throat';
|
||||
import { IReposJob, IReposJobResult } from '../../interfaces';
|
||||
|
||||
// Revoke tokens of users that no longer resolve in the corporate graph and
|
||||
// delete tokens that have been expired 30 days.
|
||||
|
||||
const expiredTokenDeleteThresholdDays = 30;
|
||||
|
||||
import { PersonalAccessToken } from '../../entities/token/token';
|
||||
import { sleep } from '../../utils';
|
||||
import { IGraphProvider } from '../../lib/graphProvider';
|
||||
|
||||
async function lookupCorporateId(
|
||||
graphProvider: IGraphProvider,
|
||||
knownUsers: Map<string, any>,
|
||||
corporateId: string
|
||||
): Promise<any> {
|
||||
const entry = knownUsers.get(corporateId);
|
||||
if (entry === false) {
|
||||
return false;
|
||||
} else if (entry) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
const userDetails = await graphProvider.getUserById(corporateId);
|
||||
if (!userDetails || !userDetails.userPrincipalName) {
|
||||
knownUsers.set(corporateId, false);
|
||||
return false;
|
||||
}
|
||||
knownUsers.set(corporateId, userDetails);
|
||||
return true;
|
||||
} catch (otherUserError) {
|
||||
console.dir(otherUserError);
|
||||
throw otherUserError;
|
||||
}
|
||||
}
|
||||
|
||||
export default async function cleanup({ providers }: IReposJob): Promise<IReposJobResult> {
|
||||
const insights = providers.insights;
|
||||
const graphProvider = providers.graphProvider;
|
||||
const tokenProvider = providers.tokenProvider;
|
||||
|
||||
console.log('reading all tokens');
|
||||
const allTokens = await tokenProvider.getAllTokens();
|
||||
console.log(`read ${allTokens.length}`);
|
||||
|
||||
insights.trackEvent({
|
||||
name: 'JobCleanupTokensReadTokens',
|
||||
properties: { tokens: String(allTokens.length) },
|
||||
});
|
||||
|
||||
let errors = 0;
|
||||
|
||||
let revokedUnresolved = 0;
|
||||
let deleted = 0;
|
||||
let serviceTokens = 0;
|
||||
let okUserTokens = 0;
|
||||
|
||||
const parallelUsers = 1;
|
||||
const secondsDelayAfterSuccess = 0.25;
|
||||
|
||||
const now = new Date();
|
||||
const monthAgo = new Date(now.getTime() - 1000 * 60 * 60 * 24 * expiredTokenDeleteThresholdDays);
|
||||
|
||||
const knownUsers = new Map<string, any>();
|
||||
|
||||
const throttle = throat(parallelUsers);
|
||||
await Promise.all(
|
||||
allTokens.map((pat: PersonalAccessToken) =>
|
||||
throttle(async () => {
|
||||
const isGuidMeansADash = pat.corporateId && pat.corporateId.includes('-');
|
||||
let wasUser = false;
|
||||
if (isGuidMeansADash) {
|
||||
wasUser = true;
|
||||
|
||||
const userStatus = await lookupCorporateId(graphProvider, knownUsers, pat.corporateId);
|
||||
if (!userStatus && pat.active !== false) {
|
||||
pat.active = false;
|
||||
console.log(
|
||||
`Revoking key for ${pat.getIdentifier()} - employee ${pat.corporateId} could not be found`
|
||||
);
|
||||
try {
|
||||
await tokenProvider.updateToken(pat);
|
||||
++revokedUnresolved;
|
||||
} catch (tokenUpdateError) {
|
||||
console.dir(tokenUpdateError);
|
||||
++errors;
|
||||
insights.trackException({ exception: tokenUpdateError });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
++serviceTokens;
|
||||
}
|
||||
|
||||
if (pat.isExpired()) {
|
||||
const dateExpired = pat.expires;
|
||||
if (dateExpired < monthAgo) {
|
||||
console.log(`Deleting key for ${pat.getIdentifier()} that expired ${dateExpired}`);
|
||||
try {
|
||||
await tokenProvider.deleteToken(pat);
|
||||
++deleted;
|
||||
} catch (tokenDeleteError) {
|
||||
console.dir(tokenDeleteError);
|
||||
++errors;
|
||||
insights.trackException({ exception: tokenDeleteError });
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
`Expired key, keeping around ${pat.getIdentifier()} that expired ${dateExpired} for user notification purposes`
|
||||
);
|
||||
}
|
||||
} else if (wasUser) {
|
||||
++okUserTokens;
|
||||
}
|
||||
|
||||
await sleep(secondsDelayAfterSuccess * 1000);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
console.log(`deleted: ${deleted}`);
|
||||
console.log(`revokedUnresolved: ${revokedUnresolved}`);
|
||||
console.log(`okUserTokens: ${okUserTokens}`);
|
||||
console.log(`serviceTokens: ${serviceTokens}`);
|
||||
console.log();
|
||||
|
||||
return {
|
||||
successProperties: {
|
||||
deleted,
|
||||
revokedUnresolved,
|
||||
okUserTokens,
|
||||
serviceTokens,
|
||||
errors,
|
||||
},
|
||||
};
|
||||
}
|
|
@ -1,14 +1,21 @@
|
|||
import app from '../../app';
|
||||
import { Organization } from '../../business/organization';
|
||||
import { RepositoryCollaboratorCacheEntity } from '../../entities/repositoryCollaboratorCache/repositoryCollaboratorCache';
|
||||
import { RepositoryTeamCacheEntity } from '../../entities/repositoryTeamCache/repositoryTeamCache';
|
||||
import { IProviders, IReposJob, IReposJobResult } from '../../interfaces';
|
||||
import { ErrorHelper } from '../../transitional';
|
||||
import { sleep } from '../../utils';
|
||||
//
|
||||
// Copyright (c) Microsoft.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
// Job: Consistency: Deleted repos (7)
|
||||
|
||||
import job from '../job';
|
||||
import { Organization } from '../business/organization';
|
||||
import { RepositoryCollaboratorCacheEntity } from '../entities/repositoryCollaboratorCache/repositoryCollaboratorCache';
|
||||
import { RepositoryTeamCacheEntity } from '../entities/repositoryTeamCache/repositoryTeamCache';
|
||||
import { IProviders, IReposJobResult } from '../interfaces';
|
||||
import { ErrorHelper } from '../transitional';
|
||||
import { sleep } from '../utils';
|
||||
|
||||
const killBitHours = 8;
|
||||
|
||||
app.runJob(byUserJob, {
|
||||
job.runBackgroundJob(byUserJob, {
|
||||
defaultDebugOutput: 'qcuser',
|
||||
timeoutMinutes: 60 * killBitHours,
|
||||
insightsPrefix: 'JobRefreshUserQC',
|
||||
|
@ -202,7 +209,7 @@ async function processDeletedRepositories(providers: IProviders): Promise<void>
|
|||
console.log(`removed collaborator repos: ${removedCollaboratorRepositories}`);
|
||||
}
|
||||
|
||||
export default async function byUserJob({ providers, args }: IReposJob): Promise<IReposJobResult> {
|
||||
async function byUserJob(providers: IProviders): Promise<IReposJobResult> {
|
||||
const { config } = providers;
|
||||
if (config?.jobs?.refreshWrites !== true) {
|
||||
console.log('job is currently disabled to avoid metadata refresh/rewrites');
|
|
@ -7,24 +7,29 @@
|
|||
|
||||
import os from 'os';
|
||||
import { DateTime } from 'luxon';
|
||||
import App from '../../app';
|
||||
import ProcessOrganizationWebhook, { IGitHubWebhookProperties } from '../../webhooks/organizationProcessor';
|
||||
|
||||
import ProcessOrganizationWebhook, { IGitHubWebhookProperties } from '../webhooks/organizationProcessor';
|
||||
import {
|
||||
IGitHubAppInstallation,
|
||||
IGitHubWebhookEnterprise,
|
||||
IProviders,
|
||||
IReposJob,
|
||||
IReposJobResult,
|
||||
} from '../../interfaces';
|
||||
import { sleep } from '../../utils';
|
||||
import { IQueueMessage } from '../../lib/queues';
|
||||
import getCompanySpecificDeployment from '../../middleware/companySpecificDeployment';
|
||||
} from '../interfaces';
|
||||
import { sleep } from '../utils';
|
||||
import { IQueueMessage } from '../lib/queues';
|
||||
import getCompanySpecificDeployment from '../middleware/companySpecificDeployment';
|
||||
import job from '../job';
|
||||
|
||||
const runningAsOngoingDeployment = true;
|
||||
|
||||
const hardAbortMs = 1000 * 60 * 5; // 5 minutes
|
||||
|
||||
export default async function firehose({ providers, started }: IReposJob): Promise<IReposJobResult> {
|
||||
job.run(firehose, {
|
||||
insightsPrefix: 'JobFirehose',
|
||||
});
|
||||
|
||||
async function firehose(providers: IProviders, { started }: IReposJob): Promise<IReposJobResult> {
|
||||
const processedEventTypes = {};
|
||||
const interestingEvents = 0;
|
||||
let processedEvents = 0;
|
||||
|
@ -89,28 +94,24 @@ export default async function firehose({ providers, started }: IReposJob): Promi
|
|||
console.log(
|
||||
`Parallelism for this run will be ${parallelism} logical threads, offset by ${sliceDelayPerThread}s`
|
||||
);
|
||||
// const insights = app.settings.appInsightsClient;
|
||||
if (insights) {
|
||||
insights.trackEvent({
|
||||
name: 'JobFirehoseStarted',
|
||||
properties: {
|
||||
hostname: os.hostname(),
|
||||
// queue: serviceBusConfig.queue,
|
||||
// subscription: serviceBusConfig.subscriptionName,
|
||||
// messagesInQueue: messagesInQueue.toString(),
|
||||
// deadLetters: deadLetters.toString(),
|
||||
},
|
||||
});
|
||||
// insights.trackMetric({ name: 'FirehoseMessagesInQueue', value: messagesInQueue });
|
||||
// insights.trackMetric({ name: 'FirehoseDeadLetters', value: deadLetters });
|
||||
}
|
||||
insights?.trackEvent({
|
||||
name: 'JobFirehoseStarted',
|
||||
properties: {
|
||||
hostname: os.hostname(),
|
||||
// queue: serviceBusConfig.queue,
|
||||
// subscription: serviceBusConfig.subscriptionName,
|
||||
// messagesInQueue: messagesInQueue.toString(),
|
||||
// deadLetters: deadLetters.toString(),
|
||||
},
|
||||
});
|
||||
// insights.trackMetric({ name: 'FirehoseMessagesInQueue', value: messagesInQueue });
|
||||
// insights.trackMetric({ name: 'FirehoseDeadLetters', value: deadLetters });
|
||||
const threads: Promise<void>[] = [];
|
||||
let delay = 0;
|
||||
for (let i = 0; i < parallelism; i++) {
|
||||
threads.push(createThread(App, providers, i, delay));
|
||||
threads.push(createThread(providers, i, delay));
|
||||
delay += sliceDelayPerThread;
|
||||
}
|
||||
const ok = true;
|
||||
await Promise.all(threads);
|
||||
|
||||
console.warn('Forever execution thread has completed.');
|
||||
|
@ -119,7 +120,6 @@ export default async function firehose({ providers, started }: IReposJob): Promi
|
|||
// -- end of job startup --
|
||||
|
||||
async function createThread(
|
||||
app,
|
||||
providers: IProviders,
|
||||
threadNumber: number,
|
||||
startupDelay: number
|
||||
|
@ -136,7 +136,7 @@ export default async function firehose({ providers, started }: IReposJob): Promi
|
|||
await iterate(providers, threadNumber);
|
||||
}
|
||||
} catch (error) {
|
||||
const insights = app.settings.appInsightsClient;
|
||||
const insights = providers.insights;
|
||||
insights.trackException({ exception: error });
|
||||
insights.trackEvent({
|
||||
name: 'JobFirehoseFatalError',
|
|
@ -1,13 +0,0 @@
|
|||
//
|
||||
// Copyright (c) Microsoft.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import Job from './task';
|
||||
import app from '../../app';
|
||||
|
||||
app.runJob(Job, {
|
||||
defaultDebugOutput: 'redis,restapi,querycache',
|
||||
insightsPrefix: 'JobFirehose',
|
||||
enableAllGitHubApps: true,
|
||||
});
|
|
@ -0,0 +1,214 @@
|
|||
//
|
||||
// Copyright (c) Microsoft.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
// Job 15: System Team Permissions
|
||||
|
||||
import { shuffle } from 'lodash';
|
||||
import throat from 'throat';
|
||||
|
||||
import job from '../job';
|
||||
import { TeamPermission } from '../business/teamPermission';
|
||||
import { GitHubRepositoryPermission, IProviders, IReposJobResult } from '../interfaces';
|
||||
import AutomaticTeamsWebhookProcessor from '../webhooks/tasks/automaticTeams';
|
||||
import { sleep } from '../utils';
|
||||
import { ErrorHelper } from '../transitional';
|
||||
import { Organization } from '../business';
|
||||
|
||||
// Permissions processing: visit all repos and make sure that any designated read, write, admin
|
||||
// teams for the organization are present on every repo. This job is designed to be run relatively
|
||||
// regularly but is not looking to answer "the truth" - it will use the cache of repos and other
|
||||
// assets to not abuse GitHub and its API exhaustively. Over time repos will converge to having
|
||||
// the right permissions.
|
||||
//
|
||||
// If a repository is "compliance locked", the system teams are not enforced until the lock is removed.
|
||||
|
||||
const maxParallelism = 3;
|
||||
const delayBetweenSeconds = 1;
|
||||
|
||||
let updatedPermissions = 0;
|
||||
let updatedRepos = 0;
|
||||
|
||||
const missingTeams = new Set<number>();
|
||||
|
||||
job.runBackgroundJob(permissionsRun, {
|
||||
insightsPrefix: 'JobPermissions',
|
||||
});
|
||||
|
||||
async function permissionsRun(providers: IProviders): Promise<IReposJobResult> {
|
||||
const { config, insights, operations } = providers;
|
||||
if (config?.jobs?.refreshWrites !== true) {
|
||||
console.log('job is currently disabled to avoid metadata refresh/rewrites');
|
||||
return;
|
||||
}
|
||||
|
||||
const throttle = throat(maxParallelism);
|
||||
|
||||
const organizations = shuffle(Array.from(operations.organizations.values()));
|
||||
|
||||
await Promise.allSettled(
|
||||
organizations.map((organization, index) =>
|
||||
throttle(async () => {
|
||||
return reviewOrganizationSystemTeams(providers, organization, index, organizations.length);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
console.log(`Updated ${updatedPermissions} permissions across ${organizations.length} organizations`);
|
||||
insights?.trackMetric({ name: 'JobSystemTeamsUpdatedPermissions', value: updatedPermissions });
|
||||
|
||||
console.log(`Updated ${updatedRepos} repos across ${organizations.length} organizations`);
|
||||
insights?.trackMetric({ name: 'JobSystemTeamsUpdatedRepos', value: updatedRepos });
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
async function reviewOrganizationSystemTeams(
|
||||
providers: IProviders,
|
||||
organization: Organization,
|
||||
index: number,
|
||||
count: number
|
||||
) {
|
||||
const { insights } = providers;
|
||||
const prefix = `${index}/${count}: ${organization.name}: `;
|
||||
|
||||
console.log(`${prefix} Reviewing permissions for all repos in ${organization.name}...`);
|
||||
try {
|
||||
const repos = await organization.getRepositories();
|
||||
console.log(`Repos in the ${organization.name} org: ${repos.length}`);
|
||||
const automaticTeams = new AutomaticTeamsWebhookProcessor();
|
||||
for (const repo of repos) {
|
||||
let thisRepoUpdated = false;
|
||||
console.log(`${organization.name}/${repo.name}`);
|
||||
sleep(1000 * delayBetweenSeconds);
|
||||
const cacheOptions = {
|
||||
maxAgeSeconds: 10 * 60 /* 10m */,
|
||||
backgroundRefresh: false,
|
||||
};
|
||||
const { specialTeamIds, specialTeamLevels } = automaticTeams.processOrgSpecialTeams(repo.organization);
|
||||
let permissions: TeamPermission[] = null;
|
||||
try {
|
||||
permissions = await repo.getTeamPermissions(cacheOptions);
|
||||
} catch (getError) {
|
||||
if (ErrorHelper.IsNotFound(getError)) {
|
||||
console.log(`Repo gone: ${repo.organization.name}/${repo.name}`);
|
||||
} else {
|
||||
console.log(
|
||||
`There was a problem getting the permissions for the repo ${repo.name} from ${repo.organization.name}`
|
||||
);
|
||||
console.dir(getError);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let shouldSkipEnforcement = false;
|
||||
const { customizedTeamPermissionsWebhookLogic } = providers;
|
||||
if (customizedTeamPermissionsWebhookLogic) {
|
||||
shouldSkipEnforcement = await customizedTeamPermissionsWebhookLogic.shouldSkipEnforcement(repo);
|
||||
}
|
||||
const currentPermissions = new Map<number, GitHubRepositoryPermission>();
|
||||
permissions.forEach((entry) => {
|
||||
currentPermissions.set(Number(entry.team.id), entry.permission);
|
||||
});
|
||||
const teamsToSet = new Set<number>();
|
||||
specialTeamIds.forEach((specialTeamId) => {
|
||||
if (!currentPermissions.has(specialTeamId)) {
|
||||
teamsToSet.add(specialTeamId);
|
||||
} else if (
|
||||
isAtLeastPermissionLevel(
|
||||
currentPermissions.get(specialTeamId),
|
||||
specialTeamLevels.get(specialTeamId)
|
||||
)
|
||||
) {
|
||||
// The team permission is already acceptable
|
||||
} else {
|
||||
console.log(
|
||||
`Permission level for ${specialTeamId} is not good enough, expected ${specialTeamLevels.get(
|
||||
specialTeamId
|
||||
)} but currently ${currentPermissions.get(specialTeamId)}`
|
||||
);
|
||||
teamsToSet.add(specialTeamId);
|
||||
}
|
||||
});
|
||||
const setArray = Array.from(teamsToSet.values());
|
||||
for (const teamId of setArray) {
|
||||
const newPermission = specialTeamLevels.get(teamId);
|
||||
if (
|
||||
shouldSkipEnforcement &&
|
||||
(newPermission as GitHubRepositoryPermission) !== GitHubRepositoryPermission.Pull
|
||||
) {
|
||||
console.log(
|
||||
`should add ${teamId} team with permission ${newPermission} to the repo ${repo.name}, but compliance lock prevents non-read system teams`
|
||||
);
|
||||
insights?.trackEvent({
|
||||
name: 'JobSystemTeamsSkipped',
|
||||
properties: {
|
||||
org: organization.name,
|
||||
repo: repo.name,
|
||||
teamId,
|
||||
reason: 'compliance lock',
|
||||
newPermission,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
if (!missingTeams.has(teamId)) {
|
||||
await repo.setTeamPermission(teamId, newPermission as GitHubRepositoryPermission);
|
||||
++updatedPermissions;
|
||||
thisRepoUpdated = true;
|
||||
insights?.trackEvent({
|
||||
name: 'JobSystemTeamsUpdated',
|
||||
properties: {
|
||||
org: organization.name,
|
||||
repo: repo.name,
|
||||
teamId,
|
||||
newPermission,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (ErrorHelper.IsNotFound(error)) {
|
||||
missingTeams.add(teamId);
|
||||
console.log(
|
||||
`the team ID ${teamId} could not be found when setting to repo ${repo.name} in org ${organization.name} and should likely be removed from config...`
|
||||
);
|
||||
} else {
|
||||
console.log(`${repo.name}`);
|
||||
console.dir(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (thisRepoUpdated) {
|
||||
++updatedRepos;
|
||||
}
|
||||
}
|
||||
console.log(`Finished with repos in ${organization.name} organization`);
|
||||
} catch (processOrganizationError) {
|
||||
console.dir(processOrganizationError);
|
||||
console.log(`moving past ${organization.name} processing due to error...`);
|
||||
}
|
||||
}
|
||||
|
||||
function isAtLeastPermissionLevel(value, expected) {
|
||||
if (value !== 'admin' && value !== 'push' && value !== 'pull') {
|
||||
throw new Error(`The permission type ${value} is not understood by isAtLeastPermissionLevel`);
|
||||
}
|
||||
if (value === expected) {
|
||||
return true;
|
||||
}
|
||||
// Admin always wins
|
||||
if (value === 'admin') {
|
||||
return true;
|
||||
} else if (expected === 'admin') {
|
||||
return false;
|
||||
}
|
||||
if (expected === 'write' && value === expected) {
|
||||
return true;
|
||||
}
|
||||
if (expected === 'read') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
//
|
||||
// Copyright (c) Microsoft.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
// Job: System Team Permissions
|
||||
|
||||
import Job from './task';
|
||||
import app from '../../app';
|
||||
|
||||
app.runJob(Job, {
|
||||
insightsPrefix: 'JobPermissions',
|
||||
defaultDebugOutput: 'cache,restapi',
|
||||
});
|
|
@ -1,155 +0,0 @@
|
|||
//
|
||||
// Copyright (c) Microsoft.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
// Job: System Team Permissions
|
||||
|
||||
import { shuffle } from 'lodash';
|
||||
|
||||
import { TeamPermission } from '../../business/teamPermission';
|
||||
import { GitHubRepositoryPermission, IReposJob, IReposJobResult } from '../../interfaces';
|
||||
import AutomaticTeamsWebhookProcessor from '../../webhooks/tasks/automaticTeams';
|
||||
import { sleep } from '../../utils';
|
||||
import { ErrorHelper } from '../../transitional';
|
||||
|
||||
// Permissions processing: visit all repos and make sure that any designated read, write, admin
|
||||
// teams for the organization are present on every repo. This job is designed to be run relatively
|
||||
// regularly but is not looking to answer "the truth" - it will use the cache of repos and other
|
||||
// assets to not abuse GitHub and its API exhaustively. Over time repos will converge to having
|
||||
// the right permissions.
|
||||
//
|
||||
// If a repository is "compliance locked", the system teams are not enforced until the lock is removed.
|
||||
|
||||
const maxParallelism = 1;
|
||||
|
||||
const delayBetweenSeconds = 1;
|
||||
|
||||
export default async function permissionsRun({ providers }: IReposJob): Promise<IReposJobResult> {
|
||||
const { config, operations } = providers;
|
||||
if (config?.jobs?.refreshWrites !== true) {
|
||||
console.log('job is currently disabled to avoid metadata refresh/rewrites');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const organization of shuffle(Array.from(operations.organizations.values()))) {
|
||||
console.log(`Reviewing permissions for all repos in ${organization.name}...`);
|
||||
try {
|
||||
const repos = await organization.getRepositories();
|
||||
console.log(`Repos in the ${organization.name} org: ${repos.length}`);
|
||||
let z = 0;
|
||||
const automaticTeams = new AutomaticTeamsWebhookProcessor();
|
||||
for (const repo of repos) {
|
||||
console.log(`${repo.organization.name}/${repo.name}`);
|
||||
sleep(1000 * delayBetweenSeconds);
|
||||
const cacheOptions = {
|
||||
maxAgeSeconds: 10 * 60 /* 10m */,
|
||||
backgroundRefresh: false,
|
||||
};
|
||||
++z;
|
||||
if (z % 250 === 1) {
|
||||
console.log('. ' + z);
|
||||
}
|
||||
const { specialTeamIds, specialTeamLevels } = automaticTeams.processOrgSpecialTeams(
|
||||
repo.organization
|
||||
);
|
||||
let permissions: TeamPermission[] = null;
|
||||
try {
|
||||
permissions = await repo.getTeamPermissions(cacheOptions);
|
||||
} catch (getError) {
|
||||
if (getError.status == /* loose */ 404) {
|
||||
console.log(`Repo gone: ${repo.organization.name}/${repo.name}`);
|
||||
} else {
|
||||
console.log(
|
||||
`There was a problem getting the permissions for the repo ${repo.name} from ${repo.organization.name}`
|
||||
);
|
||||
console.dir(getError);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let shouldSkipEnforcement = false;
|
||||
const { customizedTeamPermissionsWebhookLogic } = providers;
|
||||
if (customizedTeamPermissionsWebhookLogic) {
|
||||
shouldSkipEnforcement = await customizedTeamPermissionsWebhookLogic.shouldSkipEnforcement(repo);
|
||||
}
|
||||
const currentPermissions = new Map<number, GitHubRepositoryPermission>();
|
||||
permissions.forEach((entry) => {
|
||||
currentPermissions.set(Number(entry.team.id), entry.permission);
|
||||
});
|
||||
const teamsToSet = new Set<number>();
|
||||
specialTeamIds.forEach((specialTeamId) => {
|
||||
if (!currentPermissions.has(specialTeamId)) {
|
||||
teamsToSet.add(specialTeamId);
|
||||
} else if (
|
||||
isAtLeastPermissionLevel(
|
||||
currentPermissions.get(specialTeamId),
|
||||
specialTeamLevels.get(specialTeamId)
|
||||
)
|
||||
) {
|
||||
// The team permission is already acceptable
|
||||
} else {
|
||||
console.log(
|
||||
`Permission level for ${specialTeamId} is not good enough, expected ${specialTeamLevels.get(
|
||||
specialTeamId
|
||||
)} but currently ${currentPermissions.get(specialTeamId)}`
|
||||
);
|
||||
teamsToSet.add(specialTeamId);
|
||||
}
|
||||
});
|
||||
const setArray = Array.from(teamsToSet.values());
|
||||
for (const teamId of setArray) {
|
||||
const newPermission = specialTeamLevels.get(teamId);
|
||||
if (
|
||||
shouldSkipEnforcement &&
|
||||
(newPermission as GitHubRepositoryPermission) !== GitHubRepositoryPermission.Pull
|
||||
) {
|
||||
console.log(
|
||||
`should add ${teamId} team with permission ${newPermission} to the repo ${repo.name}, but compliance lock prevents non-read system teams`
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
await repo.setTeamPermission(teamId, newPermission as GitHubRepositoryPermission);
|
||||
} catch (error) {
|
||||
if (ErrorHelper.IsNotFound(error)) {
|
||||
console.log(
|
||||
`the team ID ${teamId} could not be found when setting to repo ${repo.name} in org ${organization.name} and should likely be removed from config...`
|
||||
);
|
||||
} else {
|
||||
console.log(`${repo.name}`);
|
||||
console.dir(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(`Finished with repos in ${organization.name} organization`);
|
||||
} catch (processOrganizationError) {
|
||||
console.dir(processOrganizationError);
|
||||
console.log(`moving past ${organization.name} processing due to error...`);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function isAtLeastPermissionLevel(value, expected) {
|
||||
if (value !== 'admin' && value !== 'push' && value !== 'pull') {
|
||||
throw new Error(`The permission type ${value} is not understood by isAtLeastPermissionLevel`);
|
||||
}
|
||||
if (value === expected) {
|
||||
return true;
|
||||
}
|
||||
// Admin always wins
|
||||
if (value === 'admin') {
|
||||
return true;
|
||||
} else if (expected === 'admin') {
|
||||
return false;
|
||||
}
|
||||
if (expected === 'write' && value === expected) {
|
||||
return true;
|
||||
}
|
||||
if (expected === 'read') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -6,7 +6,17 @@
|
|||
import throat from 'throat';
|
||||
import { shuffle } from 'lodash';
|
||||
|
||||
import { permissionsObjectToValue } from '../../transitional';
|
||||
const killBitHours = 48;
|
||||
|
||||
import job from '../job';
|
||||
|
||||
job.runBackgroundJob(refreshQueryCache, {
|
||||
defaultDebugOutput: 'querycache',
|
||||
timeoutMinutes: 60 * killBitHours,
|
||||
insightsPrefix: 'JobRefreshQueryCache',
|
||||
});
|
||||
|
||||
import { permissionsObjectToValue } from '../transitional';
|
||||
import {
|
||||
Collaborator,
|
||||
Operations,
|
||||
|
@ -16,9 +26,9 @@ import {
|
|||
Team,
|
||||
TeamMember,
|
||||
TeamPermission,
|
||||
} from '../../business';
|
||||
import { sleep, addArrayToSet } from '../../utils';
|
||||
import QueryCache from '../../business/queryCache';
|
||||
} from '../business';
|
||||
import { sleep, addArrayToSet } from '../utils';
|
||||
import QueryCache from '../business/queryCache';
|
||||
import {
|
||||
IPagedCacheOptions,
|
||||
ICacheOptions,
|
||||
|
@ -37,7 +47,8 @@ import {
|
|||
QueryCacheOperation,
|
||||
IReposJob,
|
||||
IReposJobResult,
|
||||
} from '../../interfaces';
|
||||
IProviders,
|
||||
} from '../interfaces';
|
||||
|
||||
interface IConsistencyStats {
|
||||
new: number;
|
||||
|
@ -628,7 +639,7 @@ async function cacheRepositoryCollaborators(
|
|||
return operations.filter((real) => real);
|
||||
}
|
||||
|
||||
export default async function refresh({ providers, args }: IReposJob): Promise<IReposJobResult> {
|
||||
async function refreshQueryCache(providers: IProviders, { args }: IReposJob): Promise<IReposJobResult> {
|
||||
const { config } = providers;
|
||||
if (config?.jobs?.refreshWrites !== true) {
|
||||
console.log('job is currently disabled to avoid metadata refresh/rewrites');
|
|
@ -1,15 +0,0 @@
|
|||
//
|
||||
// Copyright (c) Microsoft.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import Job from './task';
|
||||
import app from '../../app';
|
||||
|
||||
const killBitHours = 48;
|
||||
|
||||
app.runJob(Job, {
|
||||
defaultDebugOutput: 'querycache',
|
||||
timeoutMinutes: 60 * killBitHours,
|
||||
insightsPrefix: 'JobRefreshQueryCache',
|
||||
});
|
|
@ -6,28 +6,29 @@
|
|||
// Job: Backfill aliases (3)
|
||||
// Job: User attributes hygiene - alias backfills (4)
|
||||
|
||||
import app from '../../app';
|
||||
import job from '../job';
|
||||
|
||||
import throat from 'throat';
|
||||
import { shuffle } from 'lodash';
|
||||
|
||||
import { sleep } from '../../utils';
|
||||
import { IReposJob, IReposJobResult, UnlinkPurpose } from '../../interfaces';
|
||||
import { sleep } from '../utils';
|
||||
import { IProviders, IReposJobResult, UnlinkPurpose } from '../interfaces';
|
||||
import { ErrorHelper } from '../transitional';
|
||||
|
||||
const backfillAliasesOnly = process.env.BACKFILL_ALIASES === '1';
|
||||
|
||||
app.runJob(refresh, {
|
||||
defaultDebugOutput: 'cache,restapi',
|
||||
job.runBackgroundJob(refresh, {
|
||||
insightsPrefix: 'JobRefreshUsernames',
|
||||
});
|
||||
|
||||
async function refresh({ providers }: IReposJob): Promise<IReposJobResult> {
|
||||
async function refresh(providers: IProviders): Promise<IReposJobResult> {
|
||||
const { config, operations, insights, linkProvider, graphProvider } = providers;
|
||||
if (config?.jobs?.refreshWrites !== true) {
|
||||
console.log('job is currently disabled to avoid metadata refresh/rewrites');
|
||||
return;
|
||||
}
|
||||
|
||||
const backfillAliasesOnly = config.process.get('BACKFILL_ALIASES') === '1';
|
||||
const terminateLinksAndMemberships = config.process.get('REFRESH_USERNAMES_TERMINATE_ACCOUNTS') === '1';
|
||||
|
||||
console.log('reading all links');
|
||||
let allLinks = shuffle(await linkProvider.getAll());
|
||||
console.log(`READ: ${allLinks.length} links`);
|
||||
|
@ -67,6 +68,9 @@ async function refresh({ providers }: IReposJob): Promise<IReposJobResult> {
|
|||
allLinks.map((link) =>
|
||||
throttle(async () => {
|
||||
++i;
|
||||
if (i % 100 === 0) {
|
||||
console.log(`${i}/${allLinks.length}; total updates=${updates}, errors=${errors}`);
|
||||
}
|
||||
|
||||
// Refresh GitHub username for the ID
|
||||
const id = link.thirdPartyId;
|
||||
|
@ -96,7 +100,19 @@ async function refresh({ providers }: IReposJob): Promise<IReposJobResult> {
|
|||
++updatedAvatars;
|
||||
}
|
||||
} catch (githubError) {
|
||||
console.dir(githubError);
|
||||
if (ErrorHelper.IsNotFound(githubError)) {
|
||||
console.warn(
|
||||
`Deleted GitHub account, id=${id}, username_was=${link.thirdPartyUsername}; https://api.github.com/users/${link.thirdPartyUsername}`
|
||||
);
|
||||
insights.trackMetric({ name: 'JobRefreshUsernamesMissingGitHubAccounts', value: 1 });
|
||||
insights.trackEvent({
|
||||
name: 'JobRefreshUsernamesGitHubAccountNotFound',
|
||||
properties: { githubid: id, error: githubError.message },
|
||||
});
|
||||
} else {
|
||||
console.dir(githubError);
|
||||
}
|
||||
throw githubError;
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -129,7 +145,17 @@ async function refresh({ providers }: IReposJob): Promise<IReposJobResult> {
|
|||
}
|
||||
} catch (graphLookupError) {
|
||||
// Ignore graph lookup issues, other jobs handle terminated employees
|
||||
console.dir(graphLookupError);
|
||||
if (ErrorHelper.IsNotFound(graphLookupError)) {
|
||||
console.warn(`Deleted AAD account, id=${id}, username_was=${link.corporateUsername}`);
|
||||
insights.trackMetric({ name: 'JobRefreshUsernamesMissingCorporateAccounts', value: 1 });
|
||||
insights.trackEvent({
|
||||
name: 'JobRefreshUsernamesCorporateAccountNotFound',
|
||||
properties: { githubid: id, error: graphLookupError.message },
|
||||
});
|
||||
} else {
|
||||
console.dir(graphLookupError);
|
||||
}
|
||||
throw graphLookupError;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
|
@ -138,28 +164,31 @@ async function refresh({ providers }: IReposJob): Promise<IReposJobResult> {
|
|||
++updates;
|
||||
}
|
||||
} catch (getDetailsError) {
|
||||
if (getDetailsError.status == /* loose compare */ '404') {
|
||||
if (ErrorHelper.IsNotFound(getDetailsError)) {
|
||||
++notFoundErrors;
|
||||
insights.trackEvent({
|
||||
name: 'JobRefreshUsernamesNotFound',
|
||||
properties: { githubid: id, error: getDetailsError.message },
|
||||
properties: { githubid: id, aadId: link.corporateId, error: getDetailsError.message },
|
||||
});
|
||||
try {
|
||||
await operations.terminateLinkAndMemberships(id, { purpose: UnlinkPurpose.Deleted });
|
||||
insights.trackEvent({
|
||||
name: 'JobRefreshUsernamesUnlinkDelete',
|
||||
properties: { githubid: id, error: getDetailsError.message },
|
||||
});
|
||||
} catch (unlinkDeletedAccountError) {
|
||||
console.dir(unlinkDeletedAccountError);
|
||||
insights.trackException({
|
||||
exception: unlinkDeletedAccountError,
|
||||
properties: { githubid: id, event: 'JobRefreshUsernamesDeleteError' },
|
||||
});
|
||||
if (terminateLinksAndMemberships) {
|
||||
try {
|
||||
await operations.terminateLinkAndMemberships(id, { purpose: UnlinkPurpose.Deleted });
|
||||
insights.trackEvent({
|
||||
name: 'JobRefreshUsernamesUnlinkDelete',
|
||||
properties: { githubid: id, error: getDetailsError.message },
|
||||
});
|
||||
} catch (unlinkDeletedAccountError) {
|
||||
console.dir(unlinkDeletedAccountError);
|
||||
insights.trackException({
|
||||
exception: unlinkDeletedAccountError,
|
||||
properties: { githubid: id, event: 'JobRefreshUsernamesDeleteError' },
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.dir(getDetailsError);
|
||||
++errors;
|
||||
insights.trackMetric({ name: 'JobRefreshUsernamesErrors', value: 1 });
|
||||
insights.trackException({
|
||||
exception: getDetailsError,
|
||||
properties: { name: 'JobRefreshUsernamesError' },
|
||||
|
@ -188,8 +217,8 @@ async function refresh({ providers }: IReposJob): Promise<IReposJobResult> {
|
|||
console.log(`Updates: ${updates}`);
|
||||
console.log(`GitHub username changes: ${updatedUsernames}`);
|
||||
console.log(`GitHub avatar changes: ${updatedAvatars}`);
|
||||
console.log(`AAD name changes: ${updatedAadNames}`);
|
||||
console.log(`AAD username changes: ${updatedAadUpns}`);
|
||||
console.log(`Corporate name changes: ${updatedAadNames}`);
|
||||
console.log(`Corporate username changes: ${updatedAadUpns}`);
|
||||
console.log(`Updated corporate mails: ${updatedCorporateMails}`);
|
||||
|
||||
return {
|
|
@ -3,20 +3,19 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
// JOB 13: refresh repository data
|
||||
// This is very similar to the query cache, but using proper Postgres type entities, and
|
||||
// not being used by the app today.
|
||||
// Job 13: refresh repository data
|
||||
|
||||
// Implementation is initial and not as robust (will refresh everything, even things not touched
|
||||
// since the last update; limited telemetry.)
|
||||
// This is very similar to the query cache, but using proper Postgres type entities, and
|
||||
// not being used by the app today at runtime. Possible optimizations include only
|
||||
// targeting refreshes based on last-cached times. The act of refreshing these entities
|
||||
// also helps keep the standard GitHub repository cache up to date.
|
||||
|
||||
import throat from 'throat';
|
||||
|
||||
import app from '../app';
|
||||
import job from '../job';
|
||||
import { Organization, sortByRepositoryDate } from '../business';
|
||||
import { IRepositoryProvider, RepositoryEntity } from '../entities/repository';
|
||||
import { IProviders, IReposJob, IReposJobResult } from '../interfaces';
|
||||
import { ErrorHelper } from '../transitional';
|
||||
import { RepositoryEntity, tryGetRepositoryEntity } from '../entities/repository';
|
||||
import { IProviders, IReposJobResult } from '../interfaces';
|
||||
import { sleep } from '../utils';
|
||||
|
||||
const sleepBetweenReposMs = 125;
|
||||
|
@ -24,7 +23,7 @@ const maxParallel = 6;
|
|||
|
||||
const shouldUpdateCached = true;
|
||||
|
||||
async function refreshRepositories({ providers }: IReposJob): Promise<IReposJobResult> {
|
||||
async function refreshRepositories(providers: IProviders): Promise<IReposJobResult> {
|
||||
const { config, operations } = providers;
|
||||
if (config?.jobs?.refreshWrites !== true) {
|
||||
console.log('job is currently disabled to avoid metadata refresh/rewrites');
|
||||
|
@ -62,7 +61,11 @@ async function processOrganization(
|
|||
repos = repos.sort(sortByRepositoryDate);
|
||||
for (let i = 0; i < repos.length; i++) {
|
||||
const repo = repos[i];
|
||||
const prefix = `org ${orgIndex}/${orgsLength}: repo ${i}/${repos.length}: `;
|
||||
const prefix =
|
||||
'org ' + `${orgIndex + 1}/${orgsLength}:`.padEnd(6) + ` repo ${i + 1}/${repos.length}: `.padEnd(17);
|
||||
if (i % 100 === 0) {
|
||||
console.log(`${prefix}(Processing ${organization.name}${i > 0 ? ' continues' : ''})`);
|
||||
}
|
||||
try {
|
||||
let repositoryEntity = await tryGetRepositoryEntity(repositoryProvider, repo.id);
|
||||
if (await repo.isDeleted()) {
|
||||
|
@ -73,28 +76,33 @@ async function processOrganization(
|
|||
continue;
|
||||
}
|
||||
const entity = repo.getEntity();
|
||||
let update = false;
|
||||
let updatedFields: string[] = null;
|
||||
let replace = false;
|
||||
if (!repositoryEntity) {
|
||||
repositoryEntity = new RepositoryEntity();
|
||||
setFields(repositoryProvider, repositoryEntity, entity);
|
||||
setFields(repositoryEntity, entity, true);
|
||||
await repositoryProvider.insert(repositoryEntity);
|
||||
console.log(`${prefix}inserted ${organization.name}/${repositoryEntity.name}`);
|
||||
continue;
|
||||
} else {
|
||||
setFields(repositoryProvider, repositoryEntity, entity);
|
||||
// not detecting changes now
|
||||
update = true;
|
||||
updatedFields = setFields(repositoryEntity, entity, false /* not new */);
|
||||
replace = !!updatedFields;
|
||||
}
|
||||
if (!update && shouldUpdateCached) {
|
||||
update = true;
|
||||
if (updatedFields.length === 0 && shouldUpdateCached) {
|
||||
replace = true;
|
||||
repositoryEntity.cached = new Date();
|
||||
}
|
||||
if (update) {
|
||||
if (replace) {
|
||||
await repositoryProvider.replace(repositoryEntity);
|
||||
console.log(`${prefix}Updated all fields for ${organization.name}/${repo.name}`);
|
||||
updatedFields.length > 0 &&
|
||||
console.log(
|
||||
`${prefix}Updated ${updatedFields.length} field${updatedFields.length === 1 ? '' : 's'} for ${
|
||||
organization.name
|
||||
}/${repo.name} [${updatedFields.join(', ')}]`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`${prefix}repo error: ${repo.name} in organization ${organization.name}`);
|
||||
console.warn(`${prefix}repo error: ${repo.name} in organization ${organization.name}: ${error}`);
|
||||
}
|
||||
|
||||
await sleep(sleepBetweenReposMs);
|
||||
|
@ -106,70 +114,237 @@ async function processOrganization(
|
|||
return {};
|
||||
}
|
||||
|
||||
function setFields(repositoryProvider: IRepositoryProvider, repositoryEntity: RepositoryEntity, entity: any) {
|
||||
repositoryEntity.repositoryId = entity.id;
|
||||
repositoryEntity.archived = entity.archived;
|
||||
repositoryEntity.cached = new Date();
|
||||
function setFields(repositoryEntity: RepositoryEntity, entity: any, isNew: boolean) {
|
||||
const changed: string[] = [];
|
||||
if (
|
||||
(repositoryEntity.repositoryId || entity.id) &&
|
||||
String(repositoryEntity.repositoryId) !== String(entity.id)
|
||||
) {
|
||||
repositoryEntity.repositoryId = parseInt(entity.id, 10);
|
||||
changed.push('id');
|
||||
}
|
||||
if ((entity.archived || repositoryEntity.archived) && repositoryEntity.archived !== entity.archived) {
|
||||
repositoryEntity.archived = entity.archived;
|
||||
changed.push('archived');
|
||||
}
|
||||
if (entity.created_at) {
|
||||
repositoryEntity.createdAt = new Date(entity.created_at);
|
||||
}
|
||||
repositoryEntity.defaultBranch = entity.default_branch;
|
||||
repositoryEntity.description = entity.description;
|
||||
repositoryEntity.disabled = entity.disabled;
|
||||
repositoryEntity.fork = entity.fork;
|
||||
repositoryEntity.forksCount = entity.forks_count;
|
||||
repositoryEntity.hasDownloads = entity.has_downloads;
|
||||
repositoryEntity.hasIssues = entity.has_issues;
|
||||
repositoryEntity.hasPages = entity.has_pages;
|
||||
repositoryEntity.hasProjects = entity.has_projects;
|
||||
repositoryEntity.hasWiki = entity.has_wiki;
|
||||
repositoryEntity.homepage = entity.homepage;
|
||||
repositoryEntity.language = entity.language;
|
||||
repositoryEntity.license = entity.license?.spdx_id;
|
||||
repositoryEntity.fullName = entity.full_name;
|
||||
repositoryEntity.organizationId = entity.organization?.id;
|
||||
repositoryEntity.organizationLogin = entity.organization?.login;
|
||||
repositoryEntity.name = entity.name;
|
||||
repositoryEntity.networkCount = entity.network_count;
|
||||
repositoryEntity.openIssuesCount = entity.open_issues_count;
|
||||
repositoryEntity.organizationId = entity.organization?.id;
|
||||
repositoryEntity.parentId = entity.parent?.id;
|
||||
repositoryEntity.parentName = entity.parent?.login;
|
||||
repositoryEntity.parentOrganizationId = entity.parent?.organization?.id;
|
||||
repositoryEntity.parentOrganizationName = entity.parent?.organization?.login;
|
||||
repositoryEntity.private = entity.private;
|
||||
if (entity.pushed_at) {
|
||||
repositoryEntity.pushedAt = new Date(entity.pushed_at);
|
||||
}
|
||||
repositoryEntity.size = entity.size;
|
||||
repositoryEntity.stargazersCount = entity.stargazers_count;
|
||||
repositoryEntity.subscribersCount = entity.subscribers_count;
|
||||
repositoryEntity.topics = entity.topics;
|
||||
if (entity.updated_at) {
|
||||
repositoryEntity.updatedAt = new Date(entity.updated_at);
|
||||
}
|
||||
repositoryEntity.visibility = entity.visibility;
|
||||
repositoryEntity.watchersCount = entity.watchers_count;
|
||||
return repositoryEntity;
|
||||
}
|
||||
|
||||
async function tryGetRepositoryEntity(
|
||||
repositoryProvider: IRepositoryProvider,
|
||||
repositoryId: number
|
||||
): Promise<RepositoryEntity> {
|
||||
try {
|
||||
const repositoryEntity = await repositoryProvider.get(repositoryId);
|
||||
return repositoryEntity;
|
||||
} catch (error) {
|
||||
if (ErrorHelper.IsNotFound(error)) {
|
||||
return null;
|
||||
const createdAt = new Date(entity.created_at);
|
||||
const currentCreatedAt = repositoryEntity.createdAt ? new Date(repositoryEntity.createdAt) : null;
|
||||
if (currentCreatedAt && createdAt && currentCreatedAt.toISOString() !== createdAt.toISOString()) {
|
||||
repositoryEntity.pushedAt = createdAt;
|
||||
changed.push('created_at');
|
||||
} else if (!currentCreatedAt && createdAt) {
|
||||
repositoryEntity.createdAt = createdAt;
|
||||
changed.push('created_at');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
if (
|
||||
(entity.default_branch || repositoryEntity.defaultBranch) &&
|
||||
entity.default_branch !== repositoryEntity.defaultBranch
|
||||
) {
|
||||
repositoryEntity.defaultBranch = entity.default_branch;
|
||||
changed.push('default_branch');
|
||||
}
|
||||
if (
|
||||
(entity.description || repositoryEntity.description) &&
|
||||
entity.description !== repositoryEntity.description
|
||||
) {
|
||||
repositoryEntity.description = entity.description;
|
||||
changed.push('description');
|
||||
}
|
||||
if ((entity.disabled || repositoryEntity.disabled) && entity.disabled !== repositoryEntity.disabled) {
|
||||
repositoryEntity.disabled = entity.disabled;
|
||||
changed.push('disabled');
|
||||
}
|
||||
if ((entity.fork || repositoryEntity.fork) && entity.fork !== repositoryEntity.fork) {
|
||||
repositoryEntity.fork = entity.fork;
|
||||
changed.push('fork');
|
||||
}
|
||||
if (
|
||||
(entity.forks_count || repositoryEntity.forksCount) &&
|
||||
String(entity.forks_count) !== String(repositoryEntity.forksCount)
|
||||
) {
|
||||
repositoryEntity.forksCount = parseInt(entity.forks_count, 10);
|
||||
changed.push('forks_count');
|
||||
}
|
||||
if (
|
||||
(entity.has_downloads || repositoryEntity.hasDownloads) &&
|
||||
entity.has_downloads !== repositoryEntity.hasDownloads
|
||||
) {
|
||||
repositoryEntity.hasDownloads = entity.has_downloads;
|
||||
changed.push('has_downloads');
|
||||
}
|
||||
if ((entity.has_issues || repositoryEntity.hasIssues) && entity.has_issues !== repositoryEntity.hasIssues) {
|
||||
repositoryEntity.hasIssues = entity.has_issues;
|
||||
changed.push('has_issues');
|
||||
}
|
||||
if ((entity.has_pages || repositoryEntity.hasPages) && entity.has_pages !== repositoryEntity.hasPages) {
|
||||
repositoryEntity.hasPages = entity.has_pages;
|
||||
changed.push('has_pages');
|
||||
}
|
||||
if (
|
||||
(entity.has_projects || repositoryEntity.hasProjects) &&
|
||||
entity.has_projects !== repositoryEntity.hasProjects
|
||||
) {
|
||||
repositoryEntity.hasProjects = entity.has_projects;
|
||||
changed.push('has_projects');
|
||||
}
|
||||
if ((entity.has_wiki || repositoryEntity.hasWiki) && entity.has_wiki !== repositoryEntity.hasWiki) {
|
||||
repositoryEntity.hasWiki = entity.has_wiki;
|
||||
changed.push('has_wiki');
|
||||
}
|
||||
if ((entity.homepage || repositoryEntity.homepage) && entity.homepage !== repositoryEntity.homepage) {
|
||||
repositoryEntity.homepage = entity.homepage;
|
||||
changed.push('homepage');
|
||||
}
|
||||
if ((entity.language || repositoryEntity.language) && entity.language !== repositoryEntity.language) {
|
||||
repositoryEntity.language = entity.language;
|
||||
changed.push('language');
|
||||
}
|
||||
if (entity.license?.spdx_id !== repositoryEntity.license) {
|
||||
repositoryEntity.license = entity.license?.spdx_id;
|
||||
changed.push('license.spdx_id');
|
||||
}
|
||||
if ((entity.full_name || repositoryEntity.fullName) && entity.full_name !== repositoryEntity.fullName) {
|
||||
repositoryEntity.fullName = entity.full_name;
|
||||
changed.push('full_name');
|
||||
}
|
||||
if (
|
||||
(entity.organization?.id || repositoryEntity.organizationId) &&
|
||||
String(entity.organization?.id) !== String(repositoryEntity.organizationId)
|
||||
) {
|
||||
repositoryEntity.organizationId = parseInt(entity.organization?.id, 10);
|
||||
changed.push('organization.id');
|
||||
}
|
||||
if (entity.organization?.login !== repositoryEntity.organizationLogin) {
|
||||
repositoryEntity.organizationLogin = entity.organization?.login;
|
||||
changed.push('organization.login');
|
||||
}
|
||||
if ((entity.name || repositoryEntity.name) && entity.name !== repositoryEntity.name) {
|
||||
repositoryEntity.name = entity.name;
|
||||
changed.push('name');
|
||||
}
|
||||
if (
|
||||
(entity.network_count || repositoryEntity.networkCount) &&
|
||||
String(entity.network_count) !== String(repositoryEntity.networkCount)
|
||||
) {
|
||||
repositoryEntity.networkCount = parseInt(entity.network_count, 10);
|
||||
changed.push('network_count');
|
||||
}
|
||||
if (
|
||||
(entity.open_issues_count || repositoryEntity.openIssuesCount) &&
|
||||
String(entity.open_issues_count) !== String(repositoryEntity.openIssuesCount)
|
||||
) {
|
||||
repositoryEntity.openIssuesCount = parseInt(entity.open_issues_count, 10);
|
||||
changed.push('open_issues_count');
|
||||
}
|
||||
if (
|
||||
(entity.parent?.id || repositoryEntity.parentId) &&
|
||||
String(entity.parent?.id) !== String(repositoryEntity.parentId)
|
||||
) {
|
||||
repositoryEntity.parentId = parseInt(entity.parent?.id, 10);
|
||||
changed.push('parent.id');
|
||||
}
|
||||
if (
|
||||
(entity.parent?.login || repositoryEntity.parentName) &&
|
||||
entity.parent?.login !== repositoryEntity.parentName
|
||||
) {
|
||||
repositoryEntity.parentName = entity.parent?.login;
|
||||
changed.push('parent.login');
|
||||
}
|
||||
if (
|
||||
(entity?.parent?.organization?.id || repositoryEntity.parentOrganizationId) &&
|
||||
String(entity?.parent?.organization?.id) !== String(repositoryEntity.parentOrganizationId)
|
||||
) {
|
||||
repositoryEntity.parentOrganizationId = parseInt(entity.parent?.organization?.id, 10);
|
||||
changed.push('parent.organization.id');
|
||||
}
|
||||
if (
|
||||
(entity?.parent?.organization?.login || repositoryEntity.parentOrganizationName) &&
|
||||
entity?.parent?.organization?.login !== repositoryEntity.parentOrganizationName
|
||||
) {
|
||||
repositoryEntity.parentOrganizationName = entity.parent?.organization?.login;
|
||||
changed.push('parent.organization.login');
|
||||
}
|
||||
if ((entity.private || repositoryEntity.private) && entity.private !== repositoryEntity.private) {
|
||||
repositoryEntity.private = entity.private;
|
||||
changed.push('private');
|
||||
}
|
||||
if (entity.pushed_at) {
|
||||
const pushedAt = new Date(entity.pushed_at);
|
||||
const currentPushedAt = repositoryEntity.pushedAt ? new Date(repositoryEntity.pushedAt) : null;
|
||||
if (currentPushedAt && pushedAt && currentPushedAt.toISOString() !== pushedAt.toISOString()) {
|
||||
repositoryEntity.pushedAt = pushedAt;
|
||||
changed.push('pushed_at');
|
||||
} else if (!currentPushedAt && pushedAt) {
|
||||
repositoryEntity.pushedAt = pushedAt;
|
||||
changed.push('pushed_at');
|
||||
}
|
||||
}
|
||||
if ((entity.size || repositoryEntity.size) && String(entity.size) !== String(repositoryEntity.size)) {
|
||||
repositoryEntity.size = parseInt(entity.size, 10);
|
||||
changed.push('size');
|
||||
}
|
||||
if (
|
||||
(entity.stargazers_count || repositoryEntity.stargazersCount) &&
|
||||
String(entity.stargazers_count) !== String(repositoryEntity.stargazersCount)
|
||||
) {
|
||||
repositoryEntity.stargazersCount = parseInt(entity.stargazers_count, 10);
|
||||
changed.push('stargazers_count');
|
||||
}
|
||||
if (
|
||||
(entity.subscribers_count || repositoryEntity.subscribersCount) &&
|
||||
String(entity.subscribers_count) !== String(repositoryEntity.subscribersCount)
|
||||
) {
|
||||
repositoryEntity.subscribersCount = parseInt(entity.subscribers_count, 10);
|
||||
changed.push('subscribers_count');
|
||||
}
|
||||
if (entity.topics && !repositoryEntity.topics) {
|
||||
repositoryEntity.topics = entity.topics;
|
||||
changed.push('topics');
|
||||
} else if (!entity.topics && repositoryEntity.topics) {
|
||||
repositoryEntity.topics = null;
|
||||
changed.push('topics');
|
||||
} else {
|
||||
const storedTopics = [...(repositoryEntity.topics || [])].sort();
|
||||
const entityTopics = [...(entity.topics || [])].sort();
|
||||
if (storedTopics.join(',') !== entityTopics.join(',')) {
|
||||
repositoryEntity.topics = entity.topics;
|
||||
changed.push('topics');
|
||||
}
|
||||
}
|
||||
if (entity.updated_at) {
|
||||
const updatedAt = new Date(entity.updated_at);
|
||||
const currentUpdatedAt = repositoryEntity.updatedAt ? new Date(repositoryEntity.updatedAt) : null;
|
||||
if (currentUpdatedAt && updatedAt && currentUpdatedAt.toISOString() !== updatedAt.toISOString()) {
|
||||
repositoryEntity.pushedAt = updatedAt;
|
||||
changed.push('updated_at');
|
||||
} else if (!currentUpdatedAt && updatedAt) {
|
||||
repositoryEntity.updatedAt = updatedAt;
|
||||
changed.push('updated_at');
|
||||
}
|
||||
}
|
||||
if (
|
||||
(entity.visibility || repositoryEntity.visibility) &&
|
||||
entity.visibility !== repositoryEntity.visibility
|
||||
) {
|
||||
repositoryEntity.visibility = entity.visibility;
|
||||
changed.push('visibility');
|
||||
}
|
||||
if (
|
||||
(entity.watchers_count || repositoryEntity.watchersCount) &&
|
||||
String(entity.watchers_count) !== String(repositoryEntity.watchersCount)
|
||||
) {
|
||||
repositoryEntity.watchersCount = parseInt(entity.watchers_count, 10);
|
||||
changed.push('watchers_count');
|
||||
}
|
||||
if (changed.length > 0 || isNew) {
|
||||
repositoryEntity.cached = new Date();
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
app.runJob(refreshRepositories, {
|
||||
job.run(refreshRepositories, {
|
||||
timeoutMinutes: 320,
|
||||
defaultDebugOutput: 'restapi',
|
||||
insightsPrefix: 'JobRefreshRepositories',
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ import Debug from 'debug';
|
|||
const debug = Debug.debug('redis');
|
||||
const debugCrossOrganization = Debug.debug('redis-cross-org');
|
||||
|
||||
import { ICacheHelper } from '.';
|
||||
import type { ICacheHelper } from '.';
|
||||
import { gunzipBuffer, gzipString } from '../../utils';
|
||||
|
||||
export interface ISetCompressedOptions {
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { IEntityMetadata, EntityMetadataType } from './entityMetadata';
|
||||
import { IEntityMetadataFixedQuery } from './query';
|
||||
import { type IEntityMetadata, EntityMetadataType } from './entityMetadata';
|
||||
import type { IEntityMetadataFixedQuery } from './query';
|
||||
import { swapMap } from '../../utils';
|
||||
|
||||
export enum EntityField {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { IEntityMetadataProvider } from './entityMetadataProvider';
|
||||
import type { IEntityMetadataProvider } from './entityMetadataProvider';
|
||||
import { EntityMetadataType, EntityMetadataBase } from './entityMetadata';
|
||||
|
||||
// Newer "entity" implementations have fully decoupled and no longer use this single query type.
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { NextFunction, Response } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import jwksClient from 'jwks-rsa';
|
||||
|
||||
|
@ -15,7 +16,7 @@ import getCompanySpecificDeployment from './companySpecificDeployment';
|
|||
// CONSIDER: Caching of signing keys
|
||||
|
||||
export function requireAadApiAuthorizedScope(scope: string | string[]) {
|
||||
return (req: IApiRequest, res, next) => {
|
||||
return (req: IApiRequest, res: Response, next: NextFunction) => {
|
||||
const { apiKeyToken } = req;
|
||||
const scopes = typeof scope === 'string' ? [scope] : scope;
|
||||
if (!apiKeyToken.hasAnyScope(scopes)) {
|
||||
|
@ -25,7 +26,7 @@ export function requireAadApiAuthorizedScope(scope: string | string[]) {
|
|||
};
|
||||
}
|
||||
|
||||
export default function aadApiMiddleware(req: IApiRequest, res, next) {
|
||||
export default function aadApiMiddleware(req: IApiRequest, res: Response, next: NextFunction) {
|
||||
return validateAadAuthorization(req)
|
||||
.then((ok) => {
|
||||
return next();
|
||||
|
@ -34,7 +35,7 @@ export default function aadApiMiddleware(req: IApiRequest, res, next) {
|
|||
if ((err as any).immediate === true) {
|
||||
console.warn(`AAD API authorization failed: ${err}`);
|
||||
}
|
||||
return isJsonError(err) ? next(err) : jsonError(err, 500);
|
||||
return isJsonError(err) ? next(err) : (jsonError(err, 500) as unknown);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
import basicAuth from 'basic-auth';
|
||||
import crypto from 'crypto';
|
||||
import { NextFunction, Response } from 'express';
|
||||
|
||||
import { jsonError } from './jsonError';
|
||||
import { getProviders } from '../transitional';
|
||||
|
@ -30,7 +31,7 @@ export interface IApiRequest extends ReposAppRequest {
|
|||
userContextOverwriteRequest?: any; // refactor?
|
||||
}
|
||||
|
||||
export default function ReposApiAuthentication(req: IApiRequest, res, next) {
|
||||
export default function ReposApiAuthentication(req: IApiRequest, res: Response, next: NextFunction) {
|
||||
const user = basicAuth(req);
|
||||
const key = user ? user.pass || user.name : null;
|
||||
if (!key) {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import axios from 'axios';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import { NextFunction, Response } from 'express';
|
||||
|
||||
import { jsonError } from './jsonError';
|
||||
import { IApiRequest } from './apiReposAuth';
|
||||
|
@ -14,7 +15,7 @@ import { getProviders } from '../transitional';
|
|||
// TODO: consider better caching
|
||||
const localMemoryCacheVstsToAadId = new Map();
|
||||
|
||||
const vstsAuth = asyncHandler(async (req: IApiRequest, res, next) => {
|
||||
const vstsAuth = asyncHandler(async (req: IApiRequest, res: Response, next: NextFunction) => {
|
||||
const config = getProviders(req).config;
|
||||
if (!config) {
|
||||
return next(new Error('Missing configuration for the application'));
|
||||
|
|
|
@ -3,13 +3,21 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { NextFunction, Response } from 'express';
|
||||
|
||||
import wrapOrCreateInsightsConsoleClient from '../lib/insights';
|
||||
|
||||
import Debug from 'debug';
|
||||
const debug = Debug.debug('startup');
|
||||
|
||||
import { setup as appInsightsSetup, defaultClient } from 'applicationinsights';
|
||||
import { IReposApplication, IProviders, ReposAppRequest } from '../interfaces';
|
||||
import type {
|
||||
IReposApplication,
|
||||
IProviders,
|
||||
ReposAppRequest,
|
||||
SiteConfiguration,
|
||||
ExecutionEnvironment,
|
||||
} from '../interfaces';
|
||||
|
||||
function ignoreKubernetesProbes(envelope /* , context */) {
|
||||
if ('RequestData' === envelope.data.baseType) {
|
||||
|
@ -42,20 +50,22 @@ function filterTelemetry(envelope, context): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
export default function initializeAppInsights(app: IReposApplication, config) {
|
||||
export default function initializeAppInsights(
|
||||
providers: IProviders,
|
||||
executionEnvironment: ExecutionEnvironment,
|
||||
app: IReposApplication,
|
||||
config: SiteConfiguration
|
||||
) {
|
||||
let client = undefined;
|
||||
if (!config) {
|
||||
// Configuration failure happened ahead of this module
|
||||
return;
|
||||
}
|
||||
const providers = app.settings.providers as IProviders;
|
||||
let cs: string =
|
||||
config?.telemetry?.applicationInsightsConnectionString || config?.telemetry?.applicationInsightsKey;
|
||||
// Override the key with a job-specific one if this is a job execution instead
|
||||
const jobCs: string =
|
||||
config?.telemetry?.jobsApplicationInsightsConnectionString ||
|
||||
config?.telemetry?.jobsApplicationInsightsKey;
|
||||
if (jobCs && config.isJobInternal === true) {
|
||||
const jobCs: string = config?.telemetry?.jobsApplicationInsightsConnectionString;
|
||||
if (jobCs && executionEnvironment.isJob === true) {
|
||||
cs = jobCs;
|
||||
}
|
||||
if (cs) {
|
||||
|
@ -78,7 +88,7 @@ export default function initializeAppInsights(app: IReposApplication, config) {
|
|||
debug('insights telemetry is not configured with a key or connection string');
|
||||
}
|
||||
|
||||
app.use((req: ReposAppRequest, res, next) => {
|
||||
app?.use((req: ReposAppRequest, res: Response, next: NextFunction) => {
|
||||
// Acknowledge synthetic tests immediately without spending time in more middleware
|
||||
if (
|
||||
req.headers &&
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Response, Router } from 'express';
|
||||
const router: Router = Router();
|
||||
|
||||
import { ReposAppRequest } from '../../interfaces';
|
||||
|
@ -20,7 +20,11 @@ function denyRoute(next) {
|
|||
);
|
||||
}
|
||||
|
||||
export function requirePortalAdministrationPermission(req: ReposAppRequest, res, next) {
|
||||
export function requirePortalAdministrationPermission(
|
||||
req: ReposAppRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
req.individualContext
|
||||
.isPortalAdministrator()
|
||||
.then((isAdmin) => {
|
||||
|
|
|
@ -3,13 +3,15 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { NextFunction, Response } from 'express';
|
||||
|
||||
import { ReposAppRequest } from '../../interfaces';
|
||||
import { getProviders } from '../../transitional';
|
||||
import { wrapError } from '../../utils';
|
||||
|
||||
const cachedLinksRequestKeyName = 'cachedLinks';
|
||||
|
||||
export async function ensureAllLinksInMemory(req: ReposAppRequest, res, next) {
|
||||
export async function ensureAllLinksInMemory(req: ReposAppRequest, res: Response, next: NextFunction) {
|
||||
if (req[cachedLinksRequestKeyName]) {
|
||||
return next();
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
} from '../../business/user';
|
||||
import { storeOriginalUrlAsReferrer } from '../../utils';
|
||||
import getCompanySpecificDeployment from '../companySpecificDeployment';
|
||||
import { Response, NextFunction } from 'express';
|
||||
|
||||
export async function requireAuthenticatedUserOrSignInExcluding(
|
||||
exclusionPaths: string[],
|
||||
|
@ -34,7 +35,7 @@ export async function requireAuthenticatedUserOrSignInExcluding(
|
|||
return await requireAuthenticatedUserOrSignIn(req, res, next);
|
||||
}
|
||||
|
||||
export async function requireAccessTokenClient(req: ReposAppRequest, res, next) {
|
||||
export async function requireAccessTokenClient(req: ReposAppRequest, res: Response, next: NextFunction) {
|
||||
if (req.oauthAccessToken) {
|
||||
return next();
|
||||
}
|
||||
|
@ -83,7 +84,11 @@ function redirectToSignIn(req, res) {
|
|||
);
|
||||
}
|
||||
|
||||
export async function requireAuthenticatedUserOrSignIn(req: ReposAppRequest, res, next) {
|
||||
export async function requireAuthenticatedUserOrSignIn(
|
||||
req: ReposAppRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const companySpecific = getCompanySpecificDeployment();
|
||||
const providers = getProviders(req);
|
||||
const { config } = providers;
|
||||
|
@ -111,7 +116,7 @@ export async function requireAuthenticatedUserOrSignIn(req: ReposAppRequest, res
|
|||
return shouldRedirectToSignIn ? redirectToSignIn(req, res) : next();
|
||||
}
|
||||
|
||||
export function setIdentity(req: ReposAppRequest, res, next) {
|
||||
export function setIdentity(req: ReposAppRequest, res: Response, next: NextFunction) {
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
if (!activeContext) {
|
||||
return next(new Error('No context available'));
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
// This route does not use GitHub as a source of truth but instead falls back to
|
||||
// corporate assigned usernames or security group membership.
|
||||
|
||||
import { NextFunction, Response } from 'express';
|
||||
|
||||
import { ReposAppRequest } from '../../interfaces';
|
||||
import { getProviders } from '../../transitional';
|
||||
import { IndividualContext } from '../../business/user';
|
||||
|
@ -29,7 +31,11 @@ function denyRoute(next, isApi: boolean) {
|
|||
);
|
||||
}
|
||||
|
||||
export async function AuthorizeOnlyCorporateAdministrators(req: ReposAppRequest, res, next) {
|
||||
export async function AuthorizeOnlyCorporateAdministrators(
|
||||
req: ReposAppRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { operations } = getProviders(req);
|
||||
const activeContext = (req.individualContext || req.apiContext) as IndividualContext;
|
||||
const corporateId = activeContext.corporateIdentity?.id;
|
||||
|
|
|
@ -5,9 +5,15 @@
|
|||
|
||||
// This is a Microsoft-specific piece of middleware.
|
||||
|
||||
import { NextFunction, Response } from 'express';
|
||||
|
||||
import { ReposAppRequest } from '../../interfaces';
|
||||
|
||||
export function AuthorizeOnlyFullTimeEmployeesAndInterns(req: ReposAppRequest, res, next) {
|
||||
export function AuthorizeOnlyFullTimeEmployeesAndInterns(
|
||||
req: ReposAppRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const individualContext = req.individualContext;
|
||||
if (!individualContext.corporateIdentity || !individualContext.corporateIdentity.username) {
|
||||
return next(new Error('This resource is only available to authenticated users.'));
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { NextFunction, Response } from 'express';
|
||||
import { ReposAppRequest } from '../../interfaces/web';
|
||||
import { jsonError } from '../jsonError';
|
||||
|
||||
|
@ -21,7 +22,11 @@ export function getOrganizationManagementType(req: IReposAppRequestWithOrganizat
|
|||
return req.organizationManagementType;
|
||||
}
|
||||
|
||||
export function blockIfUnmanagedOrganization(req: IReposAppRequestWithOrganizationManagementType, res, next) {
|
||||
export function blockIfUnmanagedOrganization(
|
||||
req: IReposAppRequestWithOrganizationManagementType,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const managementType = getOrganizationManagementType(req);
|
||||
switch (managementType) {
|
||||
case OrganizationManagementType.Unmanaged:
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { NextFunction, Response } from 'express';
|
||||
import {
|
||||
IndividualContext,
|
||||
IIndividualContextOptions,
|
||||
|
@ -11,9 +12,10 @@ import {
|
|||
SessionUserProperties,
|
||||
WebApiContext,
|
||||
} from '../../business/user';
|
||||
import { ReposAppRequest } from '../../interfaces';
|
||||
import { getProviders } from '../../transitional';
|
||||
|
||||
export function webContextMiddleware(req, res, next) {
|
||||
export function webContextMiddleware(req: ReposAppRequest, res: Response, next: NextFunction) {
|
||||
const { operations, insights } = getProviders(req);
|
||||
if (req.apiContext) {
|
||||
const msg = 'INVALID: API and web contexts should not be mixed';
|
||||
|
@ -46,7 +48,7 @@ export function webContextMiddleware(req, res, next) {
|
|||
return next();
|
||||
}
|
||||
|
||||
export function apiContextMiddleware(req, res, next) {
|
||||
export function apiContextMiddleware(req, res: Response, next: NextFunction) {
|
||||
const { operations, insights } = getProviders(req);
|
||||
if (req.individualContext) {
|
||||
const msg = 'INVALID: API and web contexts should not be mixed';
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { NextFunction, Response } from 'express';
|
||||
|
||||
import { getProviders } from '../transitional';
|
||||
|
||||
interface ICampaignData {
|
||||
|
@ -23,7 +25,7 @@ export default function initializeCampaigns(app) {
|
|||
// come through the app.
|
||||
app.use('*', campaignMiddleware);
|
||||
|
||||
function campaignMiddleware(req, res, next) {
|
||||
function campaignMiddleware(req, res: Response, next: NextFunction) {
|
||||
process.nextTick(processCampaignTelemetry.bind(null, req));
|
||||
|
||||
// Immediate return to keep middleware going
|
||||
|
|
|
@ -3,13 +3,15 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { NextFunction, Response } from 'express';
|
||||
|
||||
import { ReposAppRequest } from '../interfaces';
|
||||
|
||||
// Assistant for when using Visual Studio Code to connect to a Codespace
|
||||
// locally instead of the web. The default port forwarding experience is
|
||||
// to toast the user to browse to 127.0.0.1:3000, but since AAD does not
|
||||
// allow for IP-based callback URLs, the user must use localhost.
|
||||
export function codespacesDevAssistant(req: ReposAppRequest, res, next) {
|
||||
export function codespacesDevAssistant(req: ReposAppRequest, res: Response, next: NextFunction) {
|
||||
if (req.hostname === '127.0.0.1') {
|
||||
console.warn(
|
||||
`${req.method} ${req.url}: WARNING: You're trying to connect to ${req.hostname} from your codespace.`
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче