зеркало из
1
0
Форкнуть 0

Integrating recent internal changes

- Changes JSON paging APIs to be zero-based indexes
- Additional optional security app configuration
- Fixes a minor configuration issue
- Adds a more robust "RepositoryEntity" that is not used in the site, but by jobs or data systems
- Updated deps
- Graph provider adds a "get direct reports" method
This commit is contained in:
Jeff Wilcox 2021-05-05 20:07:09 -07:00
Родитель d0c834edac
Коммит 7f3c6f7b43
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 179C982476A906AC
25 изменённых файлов: 957 добавлений и 220 удалений

14
.vscode/launch.json поставляемый
Просмотреть файл

@ -328,6 +328,20 @@
"DEBUG": "startup,appinsights"
}
},
{
"type": "node",
"request": "launch",
"name": "Job: Repositories Table (13)",
"program": "${workspaceRoot}/dist/jobs/repositories.js",
"cwd": "${workspaceRoot}/dist",
"preLaunchTask": "tsbuild",
"sourceMaps": true,
"console": "integratedTerminal",
"env": {
"NODE_ENV": "development",
"DEBUG": "startup,appinsights"
}
},
{
"type": "node",
"request": "launch",

Просмотреть файл

@ -35,7 +35,7 @@ export default class JsonPager<T> {
const requestedPageSize = query.pageSize ? Number(query.pageSize) : defaultPageSize;
const requestedPage = query.page ? Number(query.page) : 0;
this.pageSize = Math.min(requestedPageSize, maxPageSize);
const page = requestedPage || 1;
const page = requestedPage || 0;
if (page < 0 || isNaN(page)) {
throw jsonError('Invalid page', 400);
}
@ -43,10 +43,11 @@ export default class JsonPager<T> {
}
slice(array: T[]) {
// now this is zero-based indexing
this.total = array.length;
this.lastPage = Math.ceil(this.total / this.pageSize);
// TODO: this can go past the end, i.e. search while on page 7, it will not return page 1 results
this.begin = ((this.page - 1) * this.pageSize);
this.begin = this.page * this.pageSize;
this.end = this.begin + this.pageSize;
const subset = array.slice(this.begin, this.end);
this.subsetReturnSize = subset.length;
@ -57,11 +58,12 @@ export default class JsonPager<T> {
if (mappedValues && mappedValues.length !== this.subsetReturnSize) {
console.warn(`The mapped values length ${mappedValues.length} !== ${this.subsetReturnSize} that was computed`);
}
const pageCount = this.lastPage;
return this.res.json({
values: mappedValues,
total: this.total,
lastPage: this.lastPage,
nextPage: this.page + 1, // TODO: should not return if it's the end of the road
lastPage: pageCount - 1,
nextPage: this.page + 1 >= pageCount ? this.page : this.page + 1,
page: this.page,
pageSize: this.pageSize,
});
@ -71,4 +73,16 @@ export default class JsonPager<T> {
const subset = this.slice(array);
return this.sendJson(subset);
}
static FromSqlParameters(pageSize: number, page: number, total: number) {
// let's keep this math in a single place
const pageCount = Math.ceil(total / pageSize);
return {
total,
page,
pageSize,
lastPage: pageCount - 1,
nextPage: page + 1 >= pageCount ? page : page + 1,
};
}
}

Просмотреть файл

@ -5,19 +5,28 @@
import asyncHandler from 'express-async-handler';
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) => {
const providers = getProviders(req);
const { operations, queryCache } = providers;
const { operations, queryCache, graphProvider } = providers;
const login = req.params.login as string;
let corporateEntry: IGraphEntry = null;
try {
const account = await operations.getAccountByUsername(login);
const idAsString = String(account.id);
await account.tryGetLink();
const json = account.asJson(AccountJsonFormat.UplevelWithLink);
try {
if (graphProvider && account.link?.corporateId) {
corporateEntry = await graphProvider.getUserById(account.link.corporateId);
}
} catch (ignoreError) {
//
}
const json = account.asJson(AccountJsonFormat.GitHubDetailedWithLink);
const orgs = await queryCache.userOrganizations(idAsString);
const teams = await queryCache.userTeams(idAsString);
for (let team of teams) {
@ -56,7 +65,7 @@ export default asyncHandler(async (req: ReposAppRequest, res, next) => {
private: c.repository.private,
};
}),
}, json);
}, json, { corporateEntry });
return res.json(combined);
} catch (error) {
return next(jsonError(`login ${login} error: ${error}`, 500));

Просмотреть файл

@ -56,6 +56,17 @@ export class Account {
case AccountJsonFormat.GitHub: {
return basic;
}
case AccountJsonFormat.GitHubDetailedWithLink: {
const cloneEntity = Object.assign({}, this._originalEntity || {});
delete cloneEntity.cost;
delete cloneEntity.headers;
const link = this._link ? corporateLinkToJson(this._link) : undefined;
return {
account: cloneEntity,
isLinked: !!link,
link,
};
}
case AccountJsonFormat.UplevelWithLink: {
const link = this._link ? corporateLinkToJson(this._link) : undefined;
return {

Просмотреть файл

@ -125,6 +125,7 @@ export class Operations
dataApp: hasModernGitHubApps ? config.github.app.data : null,
backgroundJobs: hasModernGitHubApps ? config.github.app.jobs : null,
updatesApp: hasModernGitHubApps ? config.github.app.updates : null,
securityApp: hasModernGitHubApps ? config.github.app.security : null,
app: this.providers.app,
});
this._dynamicOrganizationIds = new Set();
@ -163,6 +164,16 @@ export class Operations
return this;
}
get previewMediaTypes() {
return {
repository: {
// this will allow GitHub Enterprise Cloud "visibility" fields to appear
getDetails: 'nebula-preview',
list: 'nebula-preview',
},
};
}
get organizationNames(): string[] {
if (!this._organizationNames) {
const names = [];

Просмотреть файл

@ -266,10 +266,13 @@ export class Organization {
options = options || {};
const operations = throwIfNotGitHubCapable(this._operations);
const github = operations.github;
const previewMediaTypes = operations['previewMediaTypes'] || {}; // TEMPORARY MEDIA TYPE HACK
const mediaType = previewMediaTypes?.repository?.getDetails ? { previews: [previewMediaTypes.repository.list]} : undefined;
const parameters = {
org: this.name,
type: 'all',
per_page: getPageSize(operations),
mediaType,
};
const caching = {
maxAgeSeconds: getMaxAgeSeconds(operations, CacheDefault.orgReposStaleSeconds, options),

Просмотреть файл

@ -13,6 +13,7 @@ import { IPurposefulGetAuthorizationHeader, IOperationsInstance, ICacheOptions,
import { IListPullsParameters, GitHubPullRequestState } from '../lib/github/collections';
import { wrapError } from '../utils';
import { RepositoryActions } from './repositoryActions';
interface IRepositoryMoments {
created?: moment.Moment;
@ -250,6 +251,10 @@ export class Repository {
return false;
}
get actions() {
return new RepositoryActions(this, this._getAuthorizationHeader, this._getSpecificAuthorizationHeader, this._operations);
}
async getDetails(options?: ICacheOptions): Promise<any> {
options = options || {};
const operations = throwIfNotGitHubCapable(this._operations);

Просмотреть файл

@ -0,0 +1,78 @@
//
// Copyright (c) Microsoft.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
import { Repository } from './repository';
import { getPageSize, getMaxAgeSeconds, CacheDefault } from '.';
import { AppPurpose } from '../github';
import { IPurposefulGetAuthorizationHeader, IOperationsInstance, IGetBranchesOptions, IGitHubBranch, throwIfNotGitHubCapable, IGetPullsOptions, ICacheOptions, IGetAuthorizationHeader } from '../interfaces';
export class RepositoryActions {
private _getAuthorizationHeader: IPurposefulGetAuthorizationHeader;
private _getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader;
private _operations: IOperationsInstance;
private _repository: Repository;
constructor(repository: Repository, getAuthorizationHeader: IPurposefulGetAuthorizationHeader, getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader, operations: IOperationsInstance) {
this._repository = repository;
this._getAuthorizationHeader = getAuthorizationHeader;
this._getSpecificAuthorizationHeader = getSpecificAuthorizationHeader;
this._operations = operations;
}
async getWorkflow(workflowId: number, cacheOptions?: ICacheOptions): Promise<any> {
cacheOptions = cacheOptions || {};
const operations = throwIfNotGitHubCapable(this._operations);
const github = operations.github;
const parameters = {
owner: this._repository.organization.name,
repo: this._repository.name,
workflow_id: workflowId,
};
if (!cacheOptions.maxAgeSeconds) {
cacheOptions.maxAgeSeconds = getMaxAgeSeconds(operations, CacheDefault.repoBranchesStaleSeconds /* not specific */);
}
if (cacheOptions.backgroundRefresh === undefined) {
cacheOptions.backgroundRefresh = true;
}
const entity = await github.call(this.authorize(AppPurpose.Security), 'actions.getWorkflow', parameters, cacheOptions);
return entity;
}
async getRepositorySecrets(): Promise<any> {
const operations = throwIfNotGitHubCapable(this._operations);
const github = operations.github;
const parameters = {
owner: this._repository.organization.name,
repo: this._repository.name,
};
const entity = await github.post(this.authorize(AppPurpose.Security), 'actions.listRepoSecrets', parameters);
return entity;
}
async getWorkflows(cacheOptions?: ICacheOptions): Promise<any> {
cacheOptions = cacheOptions || {};
const operations = throwIfNotGitHubCapable(this._operations);
const github = operations.github;
const parameters = {
owner: this._repository.organization.name,
repo: this._repository.name,
per_page: getPageSize(operations),
};
if (!cacheOptions.maxAgeSeconds) {
cacheOptions.maxAgeSeconds = getMaxAgeSeconds(operations, CacheDefault.repoBranchesStaleSeconds /* not specific */);
}
if (cacheOptions.backgroundRefresh === undefined) {
cacheOptions.backgroundRefresh = true;
}
const entity = await github.call(this.authorize(AppPurpose.Security), 'actions.listRepoWorkflows', parameters, cacheOptions);
return entity;
}
private authorize(purpose: AppPurpose): IGetAuthorizationHeader | string {
const getAuthorizationHeader = this._getSpecificAuthorizationHeader.bind(this, purpose) as IGetAuthorizationHeader;
return getAuthorizationHeader;
}
}

Просмотреть файл

@ -2,20 +2,21 @@
"auditlogrecord": "env://ENTITY_PROVIDER_AUDITLOG?default=firstconfigured",
"eventrecord": "env://ENTITY_PROVIDER_EVENT?default=firstconfigured",
"teamjoin": "env://ENTITY_PROVIDER_TEAMJOIN?default=firstconfigured",
"repositorymetadata": "env://ENTITY_PROVIDER_TEAMJOIN?default=firstconfigured",
"teamjoin": "env://ENTITY_PROVIDER_TEAM_JOIN?default=firstconfigured",
"repositorymetadata": "env://ENTITY_PROVIDER_REPOSITORY_METADATA?default=firstconfigured",
"organizationsettings": "env://ENTITY_PROVIDER_TEAMJOIN?default=firstconfigured",
"organizationsettings": "env://ENTITY_PROVIDER_ORGANIZATION_SETTINGS?default=firstconfigured",
"organizationmembercache": "env://ENTITY_PROVIDER_TEAMJOIN?default=firstconfigured",
"repositorycache": "env://ENTITY_PROVIDER_TEAMJOIN?default=firstconfigured",
"repositorycollaboratorcache": "env://ENTITY_PROVIDER_TEAMJOIN?default=firstconfigured",
"repositoryteamcache": "env://ENTITY_PROVIDER_TEAMJOIN?default=firstconfigured",
"teamcache": "env://ENTITY_PROVIDER_TEAMJOIN?default=firstconfigured",
"teammembercache": "env://ENTITY_PROVIDER_TEAMJOIN?default=firstconfigured",
"organizationmembercache": "env://ENTITY_PROVIDER_ORGANIZATION_MEMBER_CACHE?default=firstconfigured",
"repository": "env://ENTITY_PROVIDER_REPOSITORY?default=firstconfigured",
"repositorycache": "env://ENTITY_PROVIDER_REPOSITORY_CACE?default=firstconfigured",
"repositorycollaboratorcache": "env://ENTITY_PROVIDER_REPOSITORY_COLLABORATOR_CACHE?default=firstconfigured",
"repositoryteamcache": "env://ENTITY_PROVIDER_REPOSITORY_TEAM_CACHE?default=firstconfigured",
"teamcache": "env://ENTITY_PROVIDER_TEAM_CACHE?default=firstconfigured",
"teammembercache": "env://ENTITY_PROVIDER_TEAM_MEMBER_CACHE?default=firstconfigured",
"usersettings": "env://ENTITY_PROVIDER_USERSETTINGS?default=firstconfigured",
"tokens": "env://ENTITY_PROVIDER_TEAMJOIN?default=table",
"localextensionkey": "env://ENTITY_PROVIDER_TEAMJOIN?default=table"
"tokens": "env://ENTITY_PROVIDER_TOKENS?default=table",
"localextensionkey": "env://ENTITY_PROVIDER_LOCALEXTENSIONKEY?default=table"
}

Просмотреть файл

@ -0,0 +1,8 @@
{
"appId": "env://GITHUB_APP_SECURITY_APP_ID",
"appKey": "env://GITHUB_APP_SECURITY_KEY",
"appKeyFile": "env://GITHUB_APP_SECURITY_KEY_FILE",
"webhookSecret": "",
"slug": "env://GITHUB_APP_SECURITY_SLUG",
"description": "Security Response"
}

295
entities/repository.ts Normal file
Просмотреть файл

@ -0,0 +1,295 @@
//
// Copyright (c) Microsoft.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
import { IDictionary } from '../interfaces';
import { EntityMetadataBase, EntityMetadataMappings, EntityMetadataType, IEntityMetadata, IEntityMetadataBaseOptions, IEntityMetadataFixedQuery, keyValueMetadataField, MetadataMappingDefinition, QueryBase } from '../lib/entityMetadataProvider';
import { PostgresConfiguration, PostgresSettings } from '../lib/entityMetadataProvider/postgres';
import { GitHubRepositoryVisibility } from './repositoryMetadata/repositoryMetadata';
const type = new EntityMetadataType('RepositoryDetails');
const typeColumnValue = 'repositorydetails';
const defaultTableName = 'repositories';
const thisProviderType = type;
type InterfaceProviderType = IRepositoryProvider;
type ClassType = RepositoryEntity;
EntityMetadataMappings.Register(type, MetadataMappingDefinition.EntityInstantiate, () => { return new RepositoryEntity(); });
class ThisQueryBase extends QueryBase<ClassType> {
constructor(public query: Query) {
super();
}
}
class ThisQuery<T> extends ThisQueryBase {
constructor(query: Query, public parameters: T) {
super(query);
if (!this.parameters) {
this.parameters = {} as T;
}
}
}
const repositoryId = 'repositoryId';
const primaryFieldId = repositoryId;
interface IProperties {
repositoryId: any;
organizationId: any;
cached: any;
name: any;
organizationLogin: any;
fullName: any;
private: any;
visibility: any;
fork: any;
archived: any;
disabled: any;
pushedAt: any;
createdAt: any;
updatedAt: any;
description: any;
homepage: any;
language: any;
forksCount: any;
stargazersCount: any;
watchersCount: any;
size: any;
defaultBranch: any;
openIssuesCount: any;
topics: any; // array of strings
hasIssues: any;
hasProjects: any;
hasWiki: any;
hasPages: any;
hasDownloads: any;
subscribersCount: any;
networkCount: any;
license: any; // ?
parentId: any;
parentName: any;
parentOrganizationName: any;
parentOrganizationId: any;
additionalData: any;
}
const Field: IProperties = {
[keyValueMetadataField]: keyValueMetadataField,
repositoryId: 'repositoryId',
organizationId: 'organizationId',
cached: 'cached',
name: 'name',
organizationLogin: 'organizationLogin',
fullName: 'fullName',
private: 'private',
visibility: 'visibility',
fork: 'fork',
archived: 'archived',
disabled: 'disabled',
pushedAt: 'pushedAt',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
description: 'description',
homepage: 'homepage',
language: 'language',
forksCount: 'forksCount',
stargazersCount: 'stargazersCount',
watchersCount: 'watchersCount',
size: 'size',
defaultBranch: 'defaultBranch',
openIssuesCount: 'openIssuesCount',
topics: 'topics',
hasIssues: 'hasIssues',
hasProjects: 'hasProjects',
hasWiki: 'hasWiki',
hasPages: 'hasPages',
hasDownloads: 'hasDownloads',
subscribersCount: 'subscribersCount',
networkCount: 'networkCount',
license: 'license',
parentId: 'parentId',
parentName: 'parentName',
parentOrganizationName: 'parentOrganizationName',
parentOrganizationId: 'parentOrganizationId',
}
const dateColumns = [
Field.cached,
Field.pushedAt,
Field.createdAt,
Field.updatedAt,
];
enum Query {
}
export class RepositoryEntity implements IProperties {
repositoryId: number;
organizationId: number;
[keyValueMetadataField]: IDictionary<any>;
name: string;
organizationLogin: string;
fullName: string;
cached: Date;
private: boolean;
visibility: GitHubRepositoryVisibility;
fork: boolean;
archived: boolean;
disabled: boolean; // ?
pushedAt: Date;
createdAt: Date;
updatedAt: Date;
description: string;
homepage: string;
language: string; // ?
forksCount: number;
stargazersCount: number;
watchersCount: number;
size: number;
defaultBranch: string;
openIssuesCount: number;
topics: string[]; // ?
hasIssues: boolean;
hasProjects: boolean;
hasWiki: boolean;
hasPages: boolean;
hasDownloads: boolean;
subscribersCount: number;
networkCount: number;
license: string; // ??
parentId: number;
parentName: string;
parentOrganizationName: string;
parentOrganizationId: number;
asJson() {
// Based on the hard-coded names, nothing intelligent.
return {
id: this.repositoryId,
name: this.name,
organization: this.organizationLogin && this.organizationId ? {
id: this.organizationId,
login: this.organizationLogin,
} : null,
full_name: this.fullName || `${this.organizationLogin}/${this.name}`,
private: this.private,
visibility: this.visibility,
fork: this.fork,
archived: this.archived,
disabled: this.disabled,
pushed_at: this.pushedAt ? (new Date(this.pushedAt)).toISOString() : undefined,
created_at: this.createdAt ? (new Date(this.createdAt)).toISOString() : undefined,
updated_at: this.updatedAt ? (new Date(this.updatedAt)).toISOString() : undefined,
description: this.description,
homepage: this.homepage,
language: this.language,
forks_count: this.forksCount,
stargazers_count: this.stargazersCount,
watchers_count: this.watchersCount,
size: this.size,
default_branch: this.defaultBranch,
open_issues_count: this.openIssuesCount,
topics: this.topics,
has_issues: this.hasIssues,
has_projects: this.hasProjects,
has_wiki: this.hasWiki,
has_downloads: this.hasDownloads,
subscribers_count: this.subscribersCount,
network_count: this.networkCount,
license: this.license ? { spdx_id: this.license } : null,
parent: this.parentId && this.parentName && this.parentOrganizationId && this.parentOrganizationName ? {
id: this.parentId,
name: this.parentName,
organization: {
id: this.parentOrganizationId,
login: this.parentOrganizationName,
},
} : null,
};
}
}
EntityMetadataMappings.Register(type, PostgresSettings.PostgresQueries, (query: IEntityMetadataFixedQuery, mapMetadataPropertiesToFields: string[], metadataColumnName: string, tableName: string, getEntityTypeColumnValue) => {
const base = query as ThisQueryBase;
switch (base.query) {
default:
throw new Error(`The query ${base.query} is not implemented by this provider for the type ${type}`);
}
});
export interface IRepositoryProvider {
initialize(): Promise<void>;
get(repositoryId: number): Promise<ClassType>;
insert(entity: ClassType): Promise<string>;
replace(entity: ClassType): Promise<void>;
delete(entity: ClassType): Promise<void>;
}
export class RepositoryProvider extends EntityMetadataBase implements IRepositoryProvider {
constructor(options: IEntityMetadataBaseOptions) {
super(thisProviderType, options);
EntityImplementation.EnsureDefinitions();
}
async replace(metadata: ClassType): Promise<void> {
const entity = this.serialize(thisProviderType, metadata);
await this._entities.updateMetadata(entity);
}
async delete(metadata: ClassType): Promise<void> {
const entity = this.serialize(thisProviderType, metadata);
await this._entities.deleteMetadata(entity);
}
async get(repositoryId: number): Promise<ClassType> {
this.ensureHelpers(thisProviderType);
let metadata: IEntityMetadata = null;
metadata = await this._entities.getMetadata(thisProviderType, String(repositoryId));
return this.deserialize<ClassType>(thisProviderType, metadata);
}
async insert(metadata: ClassType): Promise<string> {
const entity = this.serialize(thisProviderType, metadata);
await this._entities.setMetadata(entity);
return entity.entityId;
}
rowToEntity(row: unknown) {
const metadata = PostgresConfiguration.RowToMetadataObject(type, row);
return this.deserialize<RepositoryEntity>(thisProviderType, metadata);
}
}
export default async function initializeRepositoryProvider(options?: IEntityMetadataBaseOptions): Promise<InterfaceProviderType> {
const provider = new RepositoryProvider(options);
await provider.initialize();
return provider;
}
const fieldNames = Object.getOwnPropertyNames(Field);
const nativeFieldNames = fieldNames.filter(x => x !== Field[keyValueMetadataField]);
EntityMetadataMappings.Register(type, MetadataMappingDefinition.EntityIdColumnName, primaryFieldId);
EntityMetadataMappings.Register(type, PostgresSettings.PostgresDefaultTypeColumnName, typeColumnValue);
EntityMetadataMappings.Register(type, PostgresSettings.PostgresDateColumns, dateColumns);
PostgresConfiguration.SetDefaultTableName(type, defaultTableName);
PostgresConfiguration.MapFieldsToColumnNamesFromListLowercased(type, fieldNames);
PostgresConfiguration.IdentifyNativeFields(type, nativeFieldNames);
PostgresConfiguration.ValidateMappings(type, fieldNames, [primaryFieldId]);
// Runtime validation of FieldNames
for (let i = 0; i < fieldNames.length; i++) {
const fn = fieldNames[i];
if (Field[fn] !== fn) {
throw new Error(`Field name ${fn} and value do not match in ${__filename}`);
}
}
export const EntityImplementation = {
Type: type,
EnsureDefinitions: () => {},
};

Просмотреть файл

@ -11,6 +11,7 @@ export enum AppPurpose {
Operations = 'Operations',
BackgroundJobs = 'BackgroundJobs', // "secondary" / "default" fallback
Updates = 'Updates',
Security = 'Security',
}
export enum GitHubAppAuthenticationType {
@ -36,5 +37,6 @@ export interface IGitHubAppsOptions {
customerFacingApp?: IGitHubAppConfiguration;
operationsApp?: IGitHubAppConfiguration;
updatesApp?: IGitHubAppConfiguration;
securityApp?: IGitHubAppConfiguration;
app: IReposApplication;
}

Просмотреть файл

@ -58,6 +58,7 @@ export class GitHubTokenManager {
await this.initializeApp(AppPurpose.Data, this.#options.dataApp);
await this.initializeApp(AppPurpose.BackgroundJobs, this.#options.backgroundJobs);
await this.initializeApp(AppPurpose.Updates, this.#options.updatesApp);
await this.initializeApp(AppPurpose.Security, this.#options.securityApp);
}
organizationSupportsAnyPurpose(organizationName: string, organizationSettings?: OrganizationSetting) {

Просмотреть файл

@ -6,6 +6,7 @@
export enum AccountJsonFormat {
GitHub = 'github',
UplevelWithLink = 'github+link',
GitHubDetailedWithLink = 'detailed+link',
}
export interface IAccountBasics {

Просмотреть файл

@ -36,6 +36,7 @@ import { IMailProvider } from '../lib/mailProvider';
import { IQueueProcessor } from '../lib/queues';
import { ICustomizedNewRepositoryLogic, ICustomizedTeamPermissionsWebhookLogic } from '../transitional';
import { IEntityMetadataProvider } from '../lib/entityMetadataProvider';
import { IRepositoryProvider } from '../entities/repository';
export interface IProviders {
app: IReposApplication;
@ -71,6 +72,7 @@ export interface IProviders {
webhookQueueProcessor?: IQueueProcessor;
sessionRedisClient?: redis.RedisClient;
cacheProvider?: ICacheHelper;
repositoryProvider?: IRepositoryProvider;
repositoryCacheProvider?: IRepositoryCacheProvider;
repositoryCollaboratorCacheProvider?: IRepositoryCollaboratorCacheProvider;
// repositoryMetadataProvider?: IRepositoryMetadataProvider;

181
jobs/repositories.ts Normal file
Просмотреть файл

@ -0,0 +1,181 @@
//
// Copyright (c) Microsoft.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
// JOB: 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.
// Implementation is initial and not as robust (will refresh everything, even things not touched
// since the last update; limited telemetry.)
import throat from 'throat';
import app from '../app';
import { Organization, Repository } from '../business';
import { IRepositoryProvider, RepositoryEntity } from '../entities/repository';
import { IProviders, IReposJob, IReposJobResult } from '../interfaces';
import { ErrorHelper } from '../transitional';
import { sleep } from '../utils';
const sleepBetweenReposMs = 125;
const maxParallel = 6;
const shouldUpdateCached = true;
async function refreshRepositories({ providers }: IReposJob): Promise<IReposJobResult> {
const { operations } = providers;
const started = new Date();
console.log(`Starting at ${started}`);
const orgs = operations.getOrganizations();
const throttle = throat(maxParallel);
await Promise.allSettled(orgs.map((organization, index) => throttle(async () => {
return processOrganization(providers, organization, index, orgs.length);
})));
// TODO: query all, remove any not processed [recently]
console.log(`Finished at ${new Date()}, started at ${started}`);
return {};
}
async function processOrganization(providers: IProviders, organization: Organization, orgIndex: number, orgsLength: number): Promise<unknown> {
const { repositoryProvider } = providers;
try {
let repos = await organization.getRepositories();
repos = repos.sort(sortDates);
for (let i = 0; i < repos.length; i++) {
const repo = repos[i];
const prefix = `org ${orgIndex}/${orgsLength}: repo ${i}/${repos.length}: `;
try {
let repositoryEntity = await tryGetRepositoryEntity(repositoryProvider, repo.id);
if (await repo.isDeleted()) {
if (repositoryEntity) {
await repositoryProvider.delete(repositoryEntity);
console.log(`${prefix}Deleted repository ${organization.name}/${repo.name}`);
}
continue;
}
const entity = repo.getEntity();
let update = false;
if (!repositoryEntity) {
repositoryEntity = new RepositoryEntity();
setFields(repositoryProvider, repositoryEntity, entity);
await repositoryProvider.insert(repositoryEntity);
console.log(`${prefix}inserted ${organization.name}/${repositoryEntity.name}`);
continue;
} else {
setFields(repositoryProvider, repositoryEntity, entity);
// not detecting changes now
update = true;
}
if (!update && shouldUpdateCached) {
update = true;
repositoryEntity.cached = new Date();
}
if (update) {
await repositoryProvider.replace(repositoryEntity);
console.log(`${prefix}Updated all fields for ${organization.name}/${repo.name}`);
}
} catch (error) {
console.warn(`${prefix}repo error: ${repo.name} in organization ${organization.name}`);
}
await sleep(sleepBetweenReposMs);
}
} catch (organizationError) {
console.warn(`error processing ${organization.name}: ${organizationError}`);
}
return {};
}
function setFields(repositoryProvider: IRepositoryProvider, repositoryEntity: RepositoryEntity, entity: any) {
repositoryEntity.repositoryId = entity.id;
repositoryEntity.archived = entity.archived;
repositoryEntity.cached = new Date();
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.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;
}
throw error;
}
}
function sortDates(a: Repository, b: Repository): number { // Inverted sort (newest first)
const aa = getRecentDate(a);
const bb = getRecentDate(b);
return aa == bb ? 0 : (aa < bb) ? 1 : -1;
}
function getRecentDate(repo: Repository) {
const dates: Date[] = [
getAsDate(repo, 'created_at'),
getAsDate(repo, 'pushed_at'),
getAsDate(repo, 'updated_at'),
].sort();
return dates[dates.length - 1];
}
function getAsDate(repo: Repository, fieldName: string) {
if (repo[fieldName]) {
const val = repo[fieldName];
if (typeof(val) === 'string') {
return new Date(val);
}
return val;
}
return new Date(0);
}
app.runJob(
refreshRepositories, { timeoutMinutes: 320, defaultDebugOutput: 'restapi', insightsPrefix: 'JobRefreshRepositories' });

Просмотреть файл

@ -184,6 +184,14 @@ export class PostgresConfiguration {
}
}
static StripRowInternals(type: EntityMetadataType, rowWithEntityValues: unknown) {
return stripEntityIdentities(type, rowWithEntityValues);
}
static RowToMetadataObject(type: EntityMetadataType, row: unknown) {
return rowToMetadataObject(type, row);
}
static MapFieldsToColumnNamesFromListLowercased(type: EntityMetadataType, fieldNames: string[]) {
PostgresConfiguration.MapFieldsToColumnNames(type, new Map(fieldNames.map(fieldName => {
return [fieldName, fieldName];
@ -207,6 +215,41 @@ export interface IPostgresQuery {
values: any;
}
function stripEntityIdentities(type: EntityMetadataType, entity: any) {
let entityTypeString = null;
let entityCreated = null;
let entityId = null;
if (MapMetadataPropertiesToFields.entityType) {
entityTypeString = entity[MapMetadataPropertiesToFields.entityType];
delete entity[MapMetadataPropertiesToFields.entityType];
}
if (MapMetadataPropertiesToFields.entityId) {
entityId = entity[MapMetadataPropertiesToFields.entityId];
delete entity[MapMetadataPropertiesToFields.entityId];
}
if (MapMetadataPropertiesToFields.entityCreated) {
entityCreated = entity[MapMetadataPropertiesToFields.entityCreated];
delete entity[MapMetadataPropertiesToFields.entityCreated];
}
const internals = PostgresInternals.instance(type);
const { metadata, native, leftovers } = internals.extractRowToFieldObjects(entity, MetadataColumnName, false);
const combined = {...metadata, ...native};
const entityFieldNames = Object.getOwnPropertyNames(combined);
return { entity: combined, entityTypeString, entityId, entityCreated, entityFieldNames, leftovers };
}
function rowToMetadataObject(type: EntityMetadataType, row: any): IEntityMetadata {
const { entity, entityId, entityCreated, entityFieldNames } = stripEntityIdentities(type, row);
const entityIdentity: IEntityMetadata = {
entityType: type,
entityId,
entityFieldNames,
entityCreated,
};
const newMetadataObject: IEntityMetadata = Object.assign(entity, entityIdentity);
return newMetadataObject;
}
export function PostgresGetAllEntities(tableName: string, entityTypeColumn: string, entityTypeValue: string): IPostgresQuery {
const sql = `
SELECT * FROM ${tableName} WHERE
@ -507,38 +550,11 @@ export class PostgresEntityMetadataProvider implements IEntityMetadataProvider {
}
private stripEntityIdentities(type: EntityMetadataType, entity: any) {
let entityTypeString = null;
let entityCreated = null;
let entityId = null;
if (MapMetadataPropertiesToFields.entityType) {
entityTypeString = entity[MapMetadataPropertiesToFields.entityType];
delete entity[MapMetadataPropertiesToFields.entityType];
}
if (MapMetadataPropertiesToFields.entityId) {
entityId = entity[MapMetadataPropertiesToFields.entityId];
delete entity[MapMetadataPropertiesToFields.entityId];
}
if (MapMetadataPropertiesToFields.entityCreated) {
entityCreated = entity[MapMetadataPropertiesToFields.entityCreated];
delete entity[MapMetadataPropertiesToFields.entityCreated];
}
const internals = PostgresInternals.instance(type);
const { metadata, native, leftovers } = internals.extractRowToFieldObjects(entity, MetadataColumnName, false);
const combined = {...metadata, ...native};
const entityFieldNames = Object.getOwnPropertyNames(combined);
return { entity: combined, entityTypeString, entityId, entityCreated, entityFieldNames, leftovers };
return stripEntityIdentities(type, entity);
}
private rowToMetadataObject(type: EntityMetadataType, row: any): IEntityMetadata {
const { entity, entityId, entityCreated, entityFieldNames } = this.stripEntityIdentities(type, row);
const entityIdentity: IEntityMetadata = {
entityType: type,
entityId,
entityFieldNames,
entityCreated,
};
const newMetadataObject: IEntityMetadata = Object.assign(entity, entityIdentity);
return newMetadataObject;
return rowToMetadataObject(type, row);
}
private async sqlQueryToMetadataArray(type, sql, values, skipEntityMapping): Promise<IEntityMetadata[]> {

Просмотреть файл

@ -52,6 +52,8 @@ export interface IGraphProvider {
getManagerById(corporateId: string): Promise<IGraphEntry>;
getManagementChain(corporateId: string): Promise<IGraphEntry[]>;
getDirectReports(corporateIdOrUpn: string): Promise<IGraphEntry[]>;
getMailAddressByUsername(corporateUsername: string): Promise<string>;
getUserIdByUsername(corporateUsername: string): Promise<string>;

Просмотреть файл

@ -216,6 +216,31 @@ export class MicrosoftGraphProvider implements IGraphProvider {
});
}
async getDirectReports(corporateIdOrUpn: string): Promise<IGraphEntry[]> {
let response = await this.lookupInGraph([
'users',
corporateIdOrUpn,
'directReports',
], {
selectValues: 'id,displayName,mailNickname,mail,userPrincipalName,userType,jobTitle',
}) as any[];
if (!response.filter && (response as any).value?.filter) {
response = (response as any).value;
}
return response.filter(e => e.userType !== GraphUserType.Guest).map(entry => {
return {
id: entry.id,
mailNickname: entry.mailNickname,
displayName: entry.displayName,
mail: entry.mail,
givenName: entry.givenName,
userPrincipalName: entry.userPrincipalName,
jobTitle: entry.jobTitle,
}
});
}
async getUsersByMailNicknames(mailNicknames: string[]): Promise<IGraphEntry[]> {
let response = await this.lookupInGraph([
'users',
@ -328,7 +353,7 @@ export class MicrosoftGraphProvider implements IGraphProvider {
private async getUserByIdLookup(aadId: string, token: string, subResource: string): Promise<any> {
const extraPath = subResource ? `/${subResource}` : '';
const url = `https://graph.microsoft.com/v1.0/users/${aadId}${extraPath}?$select=id,mailNickname,userType,displayName,givenName,mail,userPrincipalName`;
const url = `https://graph.microsoft.com/v1.0/users/${aadId}${extraPath}?$select=id,mailNickname,userType,displayName,givenName,mail,userPrincipalName,jobTitle`;
if (this.#_cache) {
try {
const cached = await this.#_cache.getObject(url);

Просмотреть файл

@ -74,6 +74,7 @@ import RouteSslify from './sslify';
import MiddlewareIndex from '.';
import { ICacheHelper } from '../lib/caching';
import { IApplicationProfile, IProviders, IReposApplication, InnerError } from '../interfaces';
import initializeRepositoryProvider from '../entities/repository';
const DefaultApplicationProfile: IApplicationProfile = {
applicationName: 'GitHub Management Portal',
@ -176,6 +177,7 @@ async function initializeAsync(app: IReposApplication, express, rootdir: string,
providers.teamMemberCacheProvider = await CreateTeamMemberCacheProviderInstance({ entityMetadataProvider: providerNameToInstance(config.entityProviders.teammembercache) });
providers.auditLogRecordProvider = await createAndInitializeAuditLogRecordProviderInstance({ entityMetadataProvider: providerNameToInstance(config.entityProviders.auditlogrecord) });
providers.userSettingsProvider = new UserSettingsProvider({ entityMetadataProvider: providerNameToInstance(config.entityProviders.usersettings) });
providers.repositoryProvider = await initializeRepositoryProvider({ entityMetadataProvider: providerNameToInstance(config.entityProviders.repository) });
await providers.userSettingsProvider.initialize();
providers.queryCache = new QueryCache(providers);
if (config.campaigns && config.campaigns.provider === 'cosmosdb') {

317
package-lock.json сгенерированный
Просмотреть файл

@ -5,15 +5,16 @@
"requires": true,
"packages": {
"": {
"name": "opensource-portal",
"version": "7.0.0",
"license": "MIT",
"dependencies": {
"@azure/cosmos": "3.10.5",
"@azure/cosmos": "3.11.0",
"@azure/data-tables": "12.0.0-beta.2",
"@azure/storage-blob": "12.5.0",
"@azure/storage-queue": "12.4.0",
"@octokit/auth-app": "3.3.0",
"@octokit/rest": "18.5.2",
"@octokit/auth-app": "3.4.0",
"@octokit/rest": "18.5.3",
"adal-node": "0.2.2",
"applicationinsights": "1.8.10",
"async-prompt": "1.0.1",
@ -28,7 +29,7 @@
"child-process-promise": "2.2.1",
"color-contrast-checker": "1.5.0",
"compression": "1.7.4",
"connect-redis": "5.1.0",
"connect-redis": "5.2.0",
"cors": "2.8.5",
"csv-parser": "3.0.0",
"debug": "4.3.1",
@ -41,7 +42,7 @@
"github-username-regex": "^1.0.0",
"hsts": "2.2.0",
"jsonwebtoken": "8.5.1",
"jwks-rsa": "2.0.2",
"jwks-rsa": "2.0.3",
"language-map": "1.4.0",
"lodash": "4.17.21",
"luxon": "1.26.0",
@ -51,7 +52,7 @@
"morgan": "1.10.0",
"mz": "2.7.0",
"node-jose": "2.0.0",
"nodemailer": "6.5.0",
"nodemailer": "6.6.0",
"oauth": "0.9.x",
"object-path": "0.11.5",
"octicons": "5.0.1",
@ -62,11 +63,11 @@
"passport-azure-ad": "4.3.0",
"passport-github": "1.1.0",
"passport-strategy": "1.x.x",
"pg": "8.5.1",
"pg": "8.6.0",
"pg-escape": "0.2.0",
"pug": "3.0.2",
"recursive-readdir": "2.2.2",
"redis": "3.1.1",
"redis": "3.1.2",
"redis-mock": "0.56.3",
"secure-compare": "3.0.1",
"semver": "7.3.5",
@ -77,7 +78,7 @@
"tmp-promise": "3.0.2",
"unzipper": "^0.10.11",
"uuid": "8.3.2",
"validator": "13.5.2"
"validator": "13.6.0"
},
"devDependencies": {
"@types/azure-sb": "0.0.38",
@ -87,12 +88,12 @@
"@types/debug": "4.1.5",
"@types/express": "4.17.11",
"@types/express-session": "1.17.3",
"@types/jest": "26.0.22",
"@types/jest": "26.0.23",
"@types/lodash": "4.14.168",
"@types/luxon": "1.26.3",
"@types/luxon": "1.26.5",
"@types/memory-cache": "0.2.1",
"@types/morgan": "1.9.2",
"@types/node": "14.14.37",
"@types/node": "15.0.2",
"@types/node-jose": "1.1.5",
"@types/passport": "1.0.6",
"@types/passport-azure-ad": "4.0.8",
@ -102,14 +103,14 @@
"@types/recursive-readdir": "2.2.0",
"@types/redis": "2.8.28",
"@types/request": "2.48.5",
"@types/semver": "7.3.4",
"@types/semver": "7.3.5",
"@types/simple-oauth2": "4.1.0",
"@types/unzipper": "^0.10.3",
"@types/uuid": "8.3.0",
"@types/validator": "13.1.3",
"jest": "26.6.3",
"jest-junit": "12.0.0",
"ts-jest": "26.5.4",
"ts-jest": "26.5.6",
"ts-node": "9.1.1",
"typescript": "4.2.4"
}
@ -308,16 +309,16 @@
}
},
"node_modules/@azure/cosmos": {
"version": "3.10.5",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.10.5.tgz",
"integrity": "sha512-if1uApYNjNXzB+reNFvzEBHvinxdQOzU8fni9e9Fs9jcPv9m76t2pzmYJNrxxCiFLP0vbNr/QCfQzIPQVw6v/A==",
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.11.0.tgz",
"integrity": "sha512-E4I1P7qV9PBr6D1Ea5UW/QQ6VS6bbBCmf2y0cWVmR6/0B8IVTsHQEjTzMqTKvu3Cuxgl6/FUsBRw9m684RLBzA==",
"dependencies": {
"@azure/core-auth": "^1.2.0",
"@azure/core-auth": "^1.3.0",
"@azure/core-rest-pipeline": "^1.0.0",
"debug": "^4.1.1",
"fast-json-stable-stringify": "^2.0.0",
"jsbi": "^3.1.3",
"node-abort-controller": "^1.2.0",
"node-fetch": "^2.6.0",
"priorityqueuejs": "^1.0.0",
"semaphore": "^1.0.5",
"tslib": "^2.0.0",
@ -1346,9 +1347,9 @@
}
},
"node_modules/@octokit/auth-app": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-3.3.0.tgz",
"integrity": "sha512-89jfgpEc0P+7V3SjMiNfTMrAOYQcmZ5g0HbBql0Vd7U47QWxUdrORv003Dtcjpc96GMo7pGaGbVlEEkG+pPwEA==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-3.4.0.tgz",
"integrity": "sha512-zBVgTnLJb0uoNMGCpcDkkAbPeavHX7oAjJkaDv2nqMmsXSsCw4AbUhjl99EtJQG/JqFY/kLFHM9330Wn0k70+g==",
"dependencies": {
"@octokit/auth-oauth-app": "^4.1.0",
"@octokit/auth-oauth-user": "^1.2.3",
@ -1462,9 +1463,9 @@
}
},
"node_modules/@octokit/openapi-types": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-6.0.0.tgz",
"integrity": "sha512-CnDdK7ivHkBtJYzWzZm7gEkanA7gKH6a09Eguz7flHw//GacPJLmkHA3f3N++MJmlxD1Fl+mB7B32EEpSCwztQ=="
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-6.1.1.tgz",
"integrity": "sha512-ICBhnEb+ahi/TTdNuYb/kTyKVBgAM0VD4k6JPzlhJyzt3Z+Tq/bynwCD+gpkJP7AEcNnzC8YO5R39trmzEo2UA=="
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "2.13.2",
@ -1486,11 +1487,11 @@
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.0.tgz",
"integrity": "sha512-Jc7CLNUueIshXT+HWt6T+M0sySPjF32mSFQAK7UfAg8qGeRI6OM1GSBxDLwbXjkqy2NVdnqCedJcP1nC785JYg==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.1.tgz",
"integrity": "sha512-vvWbPtPqLyIzJ7A4IPdTl+8IeuKAwMJ4LjvmqWOOdfSuqWQYZXq2CEd0hsnkidff2YfKlguzujHs/reBdAx8Sg==",
"dependencies": {
"@octokit/types": "^6.13.0",
"@octokit/types": "^6.13.1",
"deprecation": "^2.3.1"
},
"peerDependencies": {
@ -1523,22 +1524,22 @@
}
},
"node_modules/@octokit/rest": {
"version": "18.5.2",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.5.2.tgz",
"integrity": "sha512-Kz03XYfKS0yYdi61BkL9/aJ0pP2A/WK5vF/syhu9/kY30J8He3P68hv9GRpn8bULFx2K0A9MEErn4v3QEdbZcw==",
"version": "18.5.3",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.5.3.tgz",
"integrity": "sha512-KPAsUCr1DOdLVbZJgGNuE/QVLWEaVBpFQwDAz/2Cnya6uW2wJ/P5RVGk0itx7yyN1aGa8uXm2pri4umEqG1JBA==",
"dependencies": {
"@octokit/core": "^3.2.3",
"@octokit/plugin-paginate-rest": "^2.6.2",
"@octokit/plugin-request-log": "^1.0.2",
"@octokit/plugin-rest-endpoint-methods": "5.0.0"
"@octokit/plugin-rest-endpoint-methods": "5.0.1"
}
},
"node_modules/@octokit/types": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.13.0.tgz",
"integrity": "sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA==",
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.13.2.tgz",
"integrity": "sha512-jN5LImYHvv7W6SZargq1UMJ3EiaqIz5qkpfsv4GAb4b16SGqctxtOU2TQAZxvsKHkOw2A4zxdsi5wR9en1/ezQ==",
"dependencies": {
"@octokit/openapi-types": "^6.0.0"
"@octokit/openapi-types": "^6.1.1"
}
},
"node_modules/@opencensus/web-types": {
@ -1821,9 +1822,9 @@
}
},
"node_modules/@types/jest": {
"version": "26.0.22",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.22.tgz",
"integrity": "sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw==",
"version": "26.0.23",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.23.tgz",
"integrity": "sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA==",
"dev": true,
"dependencies": {
"jest-diff": "^26.0.0",
@ -1850,9 +1851,9 @@
"integrity": "sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w=="
},
"node_modules/@types/luxon": {
"version": "1.26.3",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-1.26.3.tgz",
"integrity": "sha512-2TELN+Pd3Ocde87sKJMSQ9Wdj0zc/okHK3/+fOQHr3CaWv4jtVtcMzmt1Foww1+5YvPd9B5vL3XR6u5KF0daEA==",
"version": "1.26.5",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-1.26.5.tgz",
"integrity": "sha512-XeQxxRMyJi1znfzHw4CGDLyup/raj84SnjjkI2fDootZPGlB0yqtvlvEIAmzHDa5wiEI5JJevZOWxpcofsaV+A==",
"dev": true
},
"node_modules/@types/memory-cache": {
@ -1876,9 +1877,9 @@
}
},
"node_modules/@types/node": {
"version": "14.14.37",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz",
"integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw=="
"version": "15.0.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz",
"integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA=="
},
"node_modules/@types/node-fetch": {
"version": "2.5.8",
@ -2036,9 +2037,9 @@
}
},
"node_modules/@types/semver": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz",
"integrity": "sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==",
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-iotVxtCCsPLRAvxMFFgxL8HD2l4mAZ2Oin7/VJ2ooWO0VOK4EGOGmZWZn1uCq7RofR3I/1IOSjCHlFT71eVK0Q==",
"dev": true
},
"node_modules/@types/serve-static": {
@ -3505,9 +3506,9 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"node_modules/connect-redis": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-5.1.0.tgz",
"integrity": "sha512-Cosy8gGUdkBPEYG84svgkzIGzspKBb98NUX4Nfc5eTJOF0A++41ZV15rPwWW0n5nU/FusNgNzuMeXhLVEQF80Q==",
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-5.2.0.tgz",
"integrity": "sha512-wcv1lZWa2K7RbsdSlrvwApBQFLQx+cia+oirLIeim0axR3D/9ZJbHdeTM/j8tJYYKk34dVs2QPAuAqcIklWD+Q==",
"engines": {
"node": ">=10.0.0"
}
@ -6634,9 +6635,9 @@
}
},
"node_modules/jose": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/jose/-/jose-2.0.4.tgz",
"integrity": "sha512-EArN9f6aq1LT/fIGGsfghOnNXn4noD+3dG5lL/ljY3LcRjw1u9w+4ahu/4ahsN6N0kRLyyW6zqdoYk7LNx3+YQ==",
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz",
"integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==",
"dependencies": {
"@panva/asn1.js": "^1.0.0"
},
@ -6867,13 +6868,13 @@
}
},
"node_modules/jwks-rsa": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.2.tgz",
"integrity": "sha512-oRnlZvmP21LxqEgEFiPycLn3jyw/QuynyaERe7GMxR4TlTg7BRGBgEyEN+rRN4xGHMekXur1RY/MSt8UJBiSgA==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.3.tgz",
"integrity": "sha512-/rkjXRWAp0cS00tunsHResw68P5iTQru8+jHufLNv3JHc4nObFEndfEUSuPugh09N+V9XYxKUqi7QrkmCHSSSg==",
"dependencies": {
"@types/express-jwt": "0.0.42",
"debug": "^4.1.0",
"jose": "^2.0.2",
"jose": "^2.0.5",
"limiter": "^1.1.5",
"lru-memoizer": "^2.1.2"
},
@ -7621,9 +7622,9 @@
}
},
"node_modules/nodemailer": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.5.0.tgz",
"integrity": "sha512-Tm4RPrrIZbnqDKAvX+/4M+zovEReiKlEXWDzG4iwtpL9X34MJY+D5LnQPH/+eghe8DLlAVshHAJZAZWBGhkguw==",
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.0.tgz",
"integrity": "sha512-ikSMDU1nZqpo2WUPE0wTTw/NGGImTkwpJKDIFPZT+YvvR9Sj+ze5wzu95JHkBMglQLoG2ITxU21WukCC/XsFkg==",
"engines": {
"node": ">=6.0.0"
}
@ -8230,15 +8231,15 @@
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"node_modules/pg": {
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.5.1.tgz",
"integrity": "sha512-9wm3yX9lCfjvA98ybCyw2pADUivyNWT/yIP4ZcDVpMN0og70BUWYEGXPCTAQdGTAqnytfRADb7NERrY1qxhIqw==",
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.6.0.tgz",
"integrity": "sha512-qNS9u61lqljTDFvmk/N66EeGq3n6Ujzj0FFyNMGQr6XuEv4tgNTXvJQTfJdcvGit5p5/DWPu+wj920hAJFI+QQ==",
"dependencies": {
"buffer-writer": "2.0.0",
"packet-reader": "1.0.0",
"pg-connection-string": "^2.4.0",
"pg-pool": "^3.2.2",
"pg-protocol": "^1.4.0",
"pg-connection-string": "^2.5.0",
"pg-pool": "^3.3.0",
"pg-protocol": "^1.5.0",
"pg-types": "^2.1.0",
"pgpass": "1.x"
},
@ -8255,9 +8256,9 @@
}
},
"node_modules/pg-connection-string": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.4.0.tgz",
"integrity": "sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ=="
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
"integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
},
"node_modules/pg-escape": {
"version": "0.2.0",
@ -8273,17 +8274,17 @@
}
},
"node_modules/pg-pool": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.2.tgz",
"integrity": "sha512-ORJoFxAlmmros8igi608iVEbQNNZlp89diFVx6yV5v+ehmpMY9sK6QgpmgoXbmkNaBAx8cOOZh9g80kJv1ooyA==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.3.0.tgz",
"integrity": "sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg==",
"peerDependencies": {
"pg": ">=8.0"
}
},
"node_modules/pg-protocol": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.4.0.tgz",
"integrity": "sha512-El+aXWcwG/8wuFICMQjM5ZSAm6OWiJicFdNYo+VY3QP+8vI4SvLIWVe51PppTzMhikUJR+PsyIFKqfdXPz/yxA=="
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
"integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
},
"node_modules/pg-types": {
"version": "2.2.0",
@ -8848,9 +8849,9 @@
}
},
"node_modules/redis": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/redis/-/redis-3.1.1.tgz",
"integrity": "sha512-QhkKhOuzhogR1NDJfBD34TQJz2ZJwDhhIC6ZmvpftlmfYShHHQXjjNspAJ+Z2HH5NwSBVYBVganbiZ8bgFMHjg==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz",
"integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==",
"dependencies": {
"denque": "^1.5.0",
"redis-commands": "^1.7.0",
@ -10510,9 +10511,9 @@
}
},
"node_modules/ts-jest": {
"version": "26.5.4",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.4.tgz",
"integrity": "sha512-I5Qsddo+VTm94SukBJ4cPimOoFZsYTeElR2xy6H2TOVs+NsvgYglW8KuQgKoApOKuaU/Ix/vrF9ebFZlb5D2Pg==",
"version": "26.5.6",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.6.tgz",
"integrity": "sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA==",
"dev": true,
"dependencies": {
"bs-logger": "0.x",
@ -10927,9 +10928,9 @@
}
},
"node_modules/validator": {
"version": "13.5.2",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.5.2.tgz",
"integrity": "sha512-mD45p0rvHVBlY2Zuy3F3ESIe1h5X58GPfAtslBjY7EtTqGquZTj+VX/J4RnHWN8FKq0C9WRVt1oWAcytWRuYLQ==",
"version": "13.6.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz",
"integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==",
"engines": {
"node": ">= 0.10"
}
@ -11378,16 +11379,16 @@
}
},
"@azure/cosmos": {
"version": "3.10.5",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.10.5.tgz",
"integrity": "sha512-if1uApYNjNXzB+reNFvzEBHvinxdQOzU8fni9e9Fs9jcPv9m76t2pzmYJNrxxCiFLP0vbNr/QCfQzIPQVw6v/A==",
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.11.0.tgz",
"integrity": "sha512-E4I1P7qV9PBr6D1Ea5UW/QQ6VS6bbBCmf2y0cWVmR6/0B8IVTsHQEjTzMqTKvu3Cuxgl6/FUsBRw9m684RLBzA==",
"requires": {
"@azure/core-auth": "^1.2.0",
"@azure/core-auth": "^1.3.0",
"@azure/core-rest-pipeline": "^1.0.0",
"debug": "^4.1.1",
"fast-json-stable-stringify": "^2.0.0",
"jsbi": "^3.1.3",
"node-abort-controller": "^1.2.0",
"node-fetch": "^2.6.0",
"priorityqueuejs": "^1.0.0",
"semaphore": "^1.0.5",
"tslib": "^2.0.0",
@ -12250,9 +12251,9 @@
}
},
"@octokit/auth-app": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-3.3.0.tgz",
"integrity": "sha512-89jfgpEc0P+7V3SjMiNfTMrAOYQcmZ5g0HbBql0Vd7U47QWxUdrORv003Dtcjpc96GMo7pGaGbVlEEkG+pPwEA==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-3.4.0.tgz",
"integrity": "sha512-zBVgTnLJb0uoNMGCpcDkkAbPeavHX7oAjJkaDv2nqMmsXSsCw4AbUhjl99EtJQG/JqFY/kLFHM9330Wn0k70+g==",
"requires": {
"@octokit/auth-oauth-app": "^4.1.0",
"@octokit/auth-oauth-user": "^1.2.3",
@ -12366,9 +12367,9 @@
}
},
"@octokit/openapi-types": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-6.0.0.tgz",
"integrity": "sha512-CnDdK7ivHkBtJYzWzZm7gEkanA7gKH6a09Eguz7flHw//GacPJLmkHA3f3N++MJmlxD1Fl+mB7B32EEpSCwztQ=="
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-6.1.1.tgz",
"integrity": "sha512-ICBhnEb+ahi/TTdNuYb/kTyKVBgAM0VD4k6JPzlhJyzt3Z+Tq/bynwCD+gpkJP7AEcNnzC8YO5R39trmzEo2UA=="
},
"@octokit/plugin-paginate-rest": {
"version": "2.13.2",
@ -12385,11 +12386,11 @@
"requires": {}
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.0.tgz",
"integrity": "sha512-Jc7CLNUueIshXT+HWt6T+M0sySPjF32mSFQAK7UfAg8qGeRI6OM1GSBxDLwbXjkqy2NVdnqCedJcP1nC785JYg==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.1.tgz",
"integrity": "sha512-vvWbPtPqLyIzJ7A4IPdTl+8IeuKAwMJ4LjvmqWOOdfSuqWQYZXq2CEd0hsnkidff2YfKlguzujHs/reBdAx8Sg==",
"requires": {
"@octokit/types": "^6.13.0",
"@octokit/types": "^6.13.1",
"deprecation": "^2.3.1"
}
},
@ -12419,22 +12420,22 @@
}
},
"@octokit/rest": {
"version": "18.5.2",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.5.2.tgz",
"integrity": "sha512-Kz03XYfKS0yYdi61BkL9/aJ0pP2A/WK5vF/syhu9/kY30J8He3P68hv9GRpn8bULFx2K0A9MEErn4v3QEdbZcw==",
"version": "18.5.3",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.5.3.tgz",
"integrity": "sha512-KPAsUCr1DOdLVbZJgGNuE/QVLWEaVBpFQwDAz/2Cnya6uW2wJ/P5RVGk0itx7yyN1aGa8uXm2pri4umEqG1JBA==",
"requires": {
"@octokit/core": "^3.2.3",
"@octokit/plugin-paginate-rest": "^2.6.2",
"@octokit/plugin-request-log": "^1.0.2",
"@octokit/plugin-rest-endpoint-methods": "5.0.0"
"@octokit/plugin-rest-endpoint-methods": "5.0.1"
}
},
"@octokit/types": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.13.0.tgz",
"integrity": "sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA==",
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.13.2.tgz",
"integrity": "sha512-jN5LImYHvv7W6SZargq1UMJ3EiaqIz5qkpfsv4GAb4b16SGqctxtOU2TQAZxvsKHkOw2A4zxdsi5wR9en1/ezQ==",
"requires": {
"@octokit/openapi-types": "^6.0.0"
"@octokit/openapi-types": "^6.1.1"
}
},
"@opencensus/web-types": {
@ -12702,9 +12703,9 @@
}
},
"@types/jest": {
"version": "26.0.22",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.22.tgz",
"integrity": "sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw==",
"version": "26.0.23",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.23.tgz",
"integrity": "sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA==",
"dev": true,
"requires": {
"jest-diff": "^26.0.0",
@ -12731,9 +12732,9 @@
"integrity": "sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w=="
},
"@types/luxon": {
"version": "1.26.3",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-1.26.3.tgz",
"integrity": "sha512-2TELN+Pd3Ocde87sKJMSQ9Wdj0zc/okHK3/+fOQHr3CaWv4jtVtcMzmt1Foww1+5YvPd9B5vL3XR6u5KF0daEA==",
"version": "1.26.5",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-1.26.5.tgz",
"integrity": "sha512-XeQxxRMyJi1znfzHw4CGDLyup/raj84SnjjkI2fDootZPGlB0yqtvlvEIAmzHDa5wiEI5JJevZOWxpcofsaV+A==",
"dev": true
},
"@types/memory-cache": {
@ -12757,9 +12758,9 @@
}
},
"@types/node": {
"version": "14.14.37",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz",
"integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw=="
"version": "15.0.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz",
"integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA=="
},
"@types/node-fetch": {
"version": "2.5.8",
@ -12916,9 +12917,9 @@
}
},
"@types/semver": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz",
"integrity": "sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==",
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-iotVxtCCsPLRAvxMFFgxL8HD2l4mAZ2Oin7/VJ2ooWO0VOK4EGOGmZWZn1uCq7RofR3I/1IOSjCHlFT71eVK0Q==",
"dev": true
},
"@types/serve-static": {
@ -14106,9 +14107,9 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"connect-redis": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-5.1.0.tgz",
"integrity": "sha512-Cosy8gGUdkBPEYG84svgkzIGzspKBb98NUX4Nfc5eTJOF0A++41ZV15rPwWW0n5nU/FusNgNzuMeXhLVEQF80Q=="
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-5.2.0.tgz",
"integrity": "sha512-wcv1lZWa2K7RbsdSlrvwApBQFLQx+cia+oirLIeim0axR3D/9ZJbHdeTM/j8tJYYKk34dVs2QPAuAqcIklWD+Q=="
},
"constantinople": {
"version": "4.0.1",
@ -16566,9 +16567,9 @@
}
},
"jose": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/jose/-/jose-2.0.4.tgz",
"integrity": "sha512-EArN9f6aq1LT/fIGGsfghOnNXn4noD+3dG5lL/ljY3LcRjw1u9w+4ahu/4ahsN6N0kRLyyW6zqdoYk7LNx3+YQ==",
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz",
"integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==",
"requires": {
"@panva/asn1.js": "^1.0.0"
}
@ -16755,13 +16756,13 @@
}
},
"jwks-rsa": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.2.tgz",
"integrity": "sha512-oRnlZvmP21LxqEgEFiPycLn3jyw/QuynyaERe7GMxR4TlTg7BRGBgEyEN+rRN4xGHMekXur1RY/MSt8UJBiSgA==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.3.tgz",
"integrity": "sha512-/rkjXRWAp0cS00tunsHResw68P5iTQru8+jHufLNv3JHc4nObFEndfEUSuPugh09N+V9XYxKUqi7QrkmCHSSSg==",
"requires": {
"@types/express-jwt": "0.0.42",
"debug": "^4.1.0",
"jose": "^2.0.2",
"jose": "^2.0.5",
"limiter": "^1.1.5",
"lru-memoizer": "^2.1.2"
}
@ -17391,9 +17392,9 @@
"integrity": "sha512-ma6oU4Sk0qOoKEAymVoTvk8EdXEobdS7m/mAGhDJ8Rouugho48crHBORAmy5BoOcv8wraPM6xumapQp5hl4iIQ=="
},
"nodemailer": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.5.0.tgz",
"integrity": "sha512-Tm4RPrrIZbnqDKAvX+/4M+zovEReiKlEXWDzG4iwtpL9X34MJY+D5LnQPH/+eghe8DLlAVshHAJZAZWBGhkguw=="
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.0.tgz",
"integrity": "sha512-ikSMDU1nZqpo2WUPE0wTTw/NGGImTkwpJKDIFPZT+YvvR9Sj+ze5wzu95JHkBMglQLoG2ITxU21WukCC/XsFkg=="
},
"normalize-package-data": {
"version": "2.5.0",
@ -17877,23 +17878,23 @@
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"pg": {
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.5.1.tgz",
"integrity": "sha512-9wm3yX9lCfjvA98ybCyw2pADUivyNWT/yIP4ZcDVpMN0og70BUWYEGXPCTAQdGTAqnytfRADb7NERrY1qxhIqw==",
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.6.0.tgz",
"integrity": "sha512-qNS9u61lqljTDFvmk/N66EeGq3n6Ujzj0FFyNMGQr6XuEv4tgNTXvJQTfJdcvGit5p5/DWPu+wj920hAJFI+QQ==",
"requires": {
"buffer-writer": "2.0.0",
"packet-reader": "1.0.0",
"pg-connection-string": "^2.4.0",
"pg-pool": "^3.2.2",
"pg-protocol": "^1.4.0",
"pg-connection-string": "^2.5.0",
"pg-pool": "^3.3.0",
"pg-protocol": "^1.5.0",
"pg-types": "^2.1.0",
"pgpass": "1.x"
}
},
"pg-connection-string": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.4.0.tgz",
"integrity": "sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ=="
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
"integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
},
"pg-escape": {
"version": "0.2.0",
@ -17906,15 +17907,15 @@
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
},
"pg-pool": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.2.tgz",
"integrity": "sha512-ORJoFxAlmmros8igi608iVEbQNNZlp89diFVx6yV5v+ehmpMY9sK6QgpmgoXbmkNaBAx8cOOZh9g80kJv1ooyA==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.3.0.tgz",
"integrity": "sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg==",
"requires": {}
},
"pg-protocol": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.4.0.tgz",
"integrity": "sha512-El+aXWcwG/8wuFICMQjM5ZSAm6OWiJicFdNYo+VY3QP+8vI4SvLIWVe51PppTzMhikUJR+PsyIFKqfdXPz/yxA=="
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
"integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
},
"pg-types": {
"version": "2.2.0",
@ -18373,9 +18374,9 @@
}
},
"redis": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/redis/-/redis-3.1.1.tgz",
"integrity": "sha512-QhkKhOuzhogR1NDJfBD34TQJz2ZJwDhhIC6ZmvpftlmfYShHHQXjjNspAJ+Z2HH5NwSBVYBVganbiZ8bgFMHjg==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz",
"integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==",
"requires": {
"denque": "^1.5.0",
"redis-commands": "^1.7.0",
@ -19714,9 +19715,9 @@
"integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk="
},
"ts-jest": {
"version": "26.5.4",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.4.tgz",
"integrity": "sha512-I5Qsddo+VTm94SukBJ4cPimOoFZsYTeElR2xy6H2TOVs+NsvgYglW8KuQgKoApOKuaU/Ix/vrF9ebFZlb5D2Pg==",
"version": "26.5.6",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.6.tgz",
"integrity": "sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA==",
"dev": true,
"requires": {
"bs-logger": "0.x",
@ -20044,9 +20045,9 @@
}
},
"validator": {
"version": "13.5.2",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.5.2.tgz",
"integrity": "sha512-mD45p0rvHVBlY2Zuy3F3ESIe1h5X58GPfAtslBjY7EtTqGquZTj+VX/J4RnHWN8FKq0C9WRVt1oWAcytWRuYLQ=="
"version": "13.6.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz",
"integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg=="
},
"vary": {
"version": "1.1.2",

Просмотреть файл

@ -47,12 +47,12 @@
"_end_microsoft_internal_": 0,
"//...": "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ",
"dependencies": {
"@azure/cosmos": "3.10.5",
"@azure/cosmos": "3.11.0",
"@azure/data-tables": "12.0.0-beta.2",
"@azure/storage-blob": "12.5.0",
"@azure/storage-queue": "12.4.0",
"@octokit/auth-app": "3.3.0",
"@octokit/rest": "18.5.2",
"@octokit/auth-app": "3.4.0",
"@octokit/rest": "18.5.3",
"adal-node": "0.2.2",
"applicationinsights": "1.8.10",
"async-prompt": "1.0.1",
@ -67,7 +67,7 @@
"child-process-promise": "2.2.1",
"color-contrast-checker": "1.5.0",
"compression": "1.7.4",
"connect-redis": "5.1.0",
"connect-redis": "5.2.0",
"cors": "2.8.5",
"csv-parser": "3.0.0",
"debug": "4.3.1",
@ -80,7 +80,7 @@
"github-username-regex": "^1.0.0",
"hsts": "2.2.0",
"jsonwebtoken": "8.5.1",
"jwks-rsa": "2.0.2",
"jwks-rsa": "2.0.3",
"language-map": "1.4.0",
"lodash": "4.17.21",
"luxon": "1.26.0",
@ -90,7 +90,7 @@
"morgan": "1.10.0",
"mz": "2.7.0",
"node-jose": "2.0.0",
"nodemailer": "6.5.0",
"nodemailer": "6.6.0",
"oauth": "0.9.x",
"object-path": "0.11.5",
"octicons": "5.0.1",
@ -101,11 +101,11 @@
"passport-azure-ad": "4.3.0",
"passport-github": "1.1.0",
"passport-strategy": "1.x.x",
"pg": "8.5.1",
"pg": "8.6.0",
"pg-escape": "0.2.0",
"pug": "3.0.2",
"recursive-readdir": "2.2.2",
"redis": "3.1.1",
"redis": "3.1.2",
"redis-mock": "0.56.3",
"secure-compare": "3.0.1",
"semver": "7.3.5",
@ -116,7 +116,7 @@
"tmp-promise": "3.0.2",
"unzipper": "^0.10.11",
"uuid": "8.3.2",
"validator": "13.5.2"
"validator": "13.6.0"
},
"devDependencies": {
"@types/azure-sb": "0.0.38",
@ -126,12 +126,12 @@
"@types/debug": "4.1.5",
"@types/express": "4.17.11",
"@types/express-session": "1.17.3",
"@types/jest": "26.0.22",
"@types/jest": "26.0.23",
"@types/lodash": "4.14.168",
"@types/luxon": "1.26.3",
"@types/luxon": "1.26.5",
"@types/memory-cache": "0.2.1",
"@types/morgan": "1.9.2",
"@types/node": "14.14.37",
"@types/node": "15.0.2",
"@types/node-jose": "1.1.5",
"@types/passport": "1.0.6",
"@types/passport-azure-ad": "4.0.8",
@ -141,14 +141,14 @@
"@types/recursive-readdir": "2.2.0",
"@types/redis": "2.8.28",
"@types/request": "2.48.5",
"@types/semver": "7.3.4",
"@types/semver": "7.3.5",
"@types/simple-oauth2": "4.1.0",
"@types/unzipper": "^0.10.3",
"@types/uuid": "8.3.0",
"@types/validator": "13.1.3",
"jest": "26.6.3",
"jest-junit": "12.0.0",
"ts-jest": "26.5.4",
"ts-jest": "26.5.6",
"ts-node": "9.1.1",
"typescript": "4.2.4"
}

53
pg.sql
Просмотреть файл

@ -188,4 +188,57 @@ CREATE INDEX IF NOT EXISTS voting_electionid ON voting ((metadata->>'electionid'
CREATE INDEX IF NOT EXISTS voting_results ON voting ((metadata->>'electionid'), (metadata->>'nominationid'));
CREATE INDEX IF NOT EXISTS voting_gin ON voting USING gin (metadata jsonb_path_ops);
CREATE TABLE IF NOT EXISTS repositories (
entitytype text,
entityid text,
metadata jsonb,
repositoryid bigint,
organizationid bigint,
cached timestamptz,
name text,
organizationlogin text,
fullname text,
private boolean,
visibility text,
fork boolean,
archived boolean,
disabled boolean,
pushedat timestamptz,
createdat timestamptz,
updatedat timestamptz,
description text,
homepage text,
language text,
forkscount integer,
stargazerscount integer,
watcherscount integer,
size bigint,
defaultbranch text,
openissuescount integer,
topics text[],
hasissues boolean,
hasprojects boolean,
haswiki boolean,
haspages boolean,
hasdownloads boolean,
subscriberscount integer,
networkcount integer,
license text,
parentid bigint,
parentname text,
parentorganizationname text,
parentorganizationid bigint,
PRIMARY KEY(entitytype, entityid)
);
CREATE INDEX IF NOT EXISTS repositories_byid ON repositories (repositoryid);
CREATE INDEX IF NOT EXISTS repositories_by_org ON repositories (organizationid);
CREATE INDEX IF NOT EXISTS repositories_byidpriv ON repositories (repositoryid, private);
CREATE INDEX IF NOT EXISTS repositories_byidvis ON repositories (repositoryid, visibility);
CREATE INDEX IF NOT EXISTS repositories_by_created ON repositories (createdat);
CREATE INDEX IF NOT EXISTS repositories_by_updated ON repositories (updatedat);
CREATE INDEX IF NOT EXISTS repositories_by_pushed ON repositories (pushedat);
COMMIT;

Просмотреть файл

@ -7,6 +7,7 @@ import { Response } from 'express';
import crypto from 'crypto';
import githubUsernameRegex from 'github-username-regex';
import { AxiosError } from 'axios';
import { DateTime } from 'luxon';
import { GitHubRepositoryPermission } from './entities/repositoryMetadata/repositoryMetadata';

Просмотреть файл

@ -296,3 +296,4 @@ block content
p.
This view is cached and eventually consistent within a few hours.
The presence of people in this list does not imply that they are an employee.