export async function CreateRepository()

in api/createRepo.ts [61:371]


export async function CreateRepository(req, organization: Organization, logic: ICustomizedNewRepositoryLogic, createContext: INewRepositoryContext, bodyOverride: unknown, entrypoint: CreateRepositoryEntrypoint, individualContext?: IndividualContext): Promise<ICreateRepositoryApiResult> {
  if (!organization) {
    throw jsonError(new Error('No organization available in the route.'), 400);
  }
  const providers = getProviders(req);
  const { operations, mailProvider, insights } = providers;
  const repositoryMetadataProvider = getRepositoryMetadataProvider(organization.operations);
  const ourFields = [
    'ms.onBehalfOf',
    'ms.license',
    'ms.approval',
    'ms.approval-url',
    'ms.justification',
    'ms.notify',
    'ms.administrators',
    'ms.teams',
    'ms.template',
    'ms.project-type',
  ];
  const properties = {};
  const parameters = bodyOverride || req.body;
  ourFields.forEach((fieldName) => {
    if (parameters[fieldName] !== undefined) {
      properties[fieldName] = parameters[fieldName];
      delete parameters[fieldName];
    }
  });
  const msProperties = {
    onBehalfOf: properties['ms.onBehalfOf'] || req.headers['ms-onbehalfof'],
    justification: properties['ms.justification'] || req.headers['ms-justification'],
    license: properties['ms.license'] || req.headers['ms-license'],
    approvalType: properties['ms.approval'] || req.headers['ms-approval'],
    approvalUrl: properties['ms.approval-url'] || req.headers['ms-approval-url'],
    notify: properties['ms.notify'] || req.headers['ms-notify'],
    administrators: properties['ms.administrators'] || req.headers['ms-administrators'],
    teams: properties['ms.teams'] || req.headers['ms-teams'],
    template: properties['ms.template'] || req.headers['ms-template'],
    projectType: properties['ms.project-type'] || req.headers['ms-project-type'],
  };
  // Validate licenses when present
  let msLicense = msProperties.license;
  if (msLicense) {
    msLicense = msLicense.toLowerCase();
    if (supportedLicenseExpressions.indexOf(msLicense) < 0) {
      throw jsonError(new Error('The provided license expression is not currently supported'), 422);
    }
  }
  if (msProperties.administrators && !Array.isArray(msProperties.administrators)) {
    throw jsonError(new Error('Administrators must be an array of logins'), 422);
  }
  parameters.org = organization.name;
  const existingRepoId = req.body.existingrepoid;
  let metadata: RepositoryMetadataEntity;
  let response: any = null;
  if (existingRepoId && !organization.isNewRepositoryLockdownSystemEnabled()) {
    throw jsonError(new Error(`Repository ID ${existingRepoId} provided for a repository within the ${organization.name} org that is not configured for existing repository classification`), 422);
  }
  let repository: Repository = null;
  let repoCreateResponse: ICreateRepositoryApiResult = null;
  if (!existingRepoId) {
    providers.insights?.trackEvent({
      name: 'ApiRepoTryCreateForOrg',
      properties: {
        parameterName: parameters.name,
        description: parameters.description,
        private: parameters.private,
        visibility: parameters.visibility,
        org: parameters.org,
        entrypoint,
      },
    });
    let createResult: ICreateRepositoryResult = null;
    try {
      createResult = await organization.createRepository(parameters.name, parameters);
      if (createResult && createResult.repository) {
        repository = organization.repositoryFromEntity(createResult.repository);
      }
    } catch (error) {
      providers.insights?.trackEvent({
        name: 'ApiRepoCreateForOrgGitHubFailure',
        properties: {
          parameterName: parameters.name,
          private: parameters.private,
          org: parameters.org,
          parameters: JSON.stringify(parameters),
          entrypoint,
        },
      });
      if (error && error.innerError) {
        const inner = error.innerError;
        req.insights.trackException({
          exception: inner,
          properties: {
            event: 'ApiRepoCreateGitHubErrorInside',
            message: inner && inner.message ? inner.message : inner,
            status: inner && inner.status ? inner.status : '',
            statusCode: inner && inner.statusCode ? inner.statusCode : '',
          },
        });
      }
      req.insights.trackException({
        exception: error,
        properties: {
          event: 'ApiRepoCreateGitHubError',
          message: error && error.message ? error.message : error,
        },
      });
      throw jsonError(error, error.status || 500);
    }
    response = createResult.response;
    providers.insights?.trackEvent({
      name: 'ApiRepoCreateForOrg',
      properties: {
        parameterName: parameters.name,
        description: parameters.description,
        private: parameters.private,
        org: parameters.org,
        entrypoint,
        result: JSON.stringify(response),
      },
    });
    // strip an internal "cost" part off our response object
    delete response.cost;
    // from this point on any errors should roll back
    const repoCreateResponse: ICreateRepositoryApiResult = {
      github: response,
      name: response && response.name ? response.name : undefined,
      organizationName: repository.organization.name,
      repositoryId: response?.id ? Number(response.id) : undefined,
    };
    req.repoCreateResponse = repoCreateResponse;
    metadata = new RepositoryMetadataEntity();
    // Store create metadata
    metadata.created = new Date();
    metadata.createdByThirdPartyUsername = msProperties.onBehalfOf;
    if (individualContext && individualContext.corporateIdentity.id) {
      metadata.createdByCorporateId = individualContext.corporateIdentity.id;
      metadata.createdByCorporateUsername = individualContext.corporateIdentity.username;
      metadata.createdByCorporateDisplayName = individualContext.corporateIdentity.displayName;
    }
    // TODO: we also want to store corporate manager information, eventually
    try {
      const account = await operations.getAccountByUsername(metadata.createdByThirdPartyUsername);
      metadata.createdByThirdPartyId = account.id.toString();
    } catch (noAvailableUsername) {
      providers.insights?.trackEvent({
        name: 'ApiRepoCreateInvalidUsername',
        properties: {
          username: metadata.createdByThirdPartyUsername,
          error: noAvailableUsername.message,
          encodedError: JSON.stringify(noAvailableUsername),
        },
      });
    }
    metadata.repositoryName = response.name;
    metadata.repositoryId = response.id;
    metadata.initialRepositoryDescription = response.description;
    metadata.initialRepositoryHomepage = response.homepage;
    if (response.visibility === GitHubRepositoryVisibility.Internal) {
      metadata.initialRepositoryVisibility = GitHubRepositoryVisibility.Internal;
    } else {
      metadata.initialRepositoryVisibility = response.private ? GitHubRepositoryVisibility.Private : GitHubRepositoryVisibility.Public;
    }
    metadata.organizationName = organization.name.toLowerCase();
    if (organization.id) {
      metadata.organizationId = organization.id.toString();
    }
  } else {
    // Locked down new repo, this is a classification call
    try {
      metadata = await repositoryMetadataProvider.getRepositoryMetadata(existingRepoId);
    } catch (existingError) {
      if (existingError.status && existingError.status === 404) {
        throw new Error(`The existing repository with id=${existingRepoId} cannot be classified as it was not processed as a new repository`);
      } else {
        throw existingError;
      }
    }
    // Verify that the active user is the same person who created it
    if (!individualContext) {
      throw new Error('Existing repository reclassification requires an authenticated identity');
    }
    NewRepositoryLockdownSystem.ValidateUserCanConfigureRepository(metadata, individualContext);
    // CONSIDER: or a org sudo user or a portal administrator
    const repositoryByName = organization.repository(metadata.repositoryName);
    const response = await repositoryByName.getDetails();
    if (response.id != /* loose */ existingRepoId) {
      throw new Error(`The ID of the repo ${metadata.repositoryName} does not match ${existingRepoId}`);
    }
    repository = repositoryByName;
    metadata.lockdownState = RepositoryLockdownState.Unlocked;
    repoCreateResponse = {
      github: response,
      name: response && response.name ? response.name : undefined,
      repositoryId: Number(existingRepoId),
      organizationName: repository.organization.name,
    };
    req.repoCreateResponse = repoCreateResponse;
  }
  metadata.releaseReviewJustification = msProperties.justification;
  metadata.initialLicense = msProperties.license;
  metadata.releaseReviewType = msProperties.approvalType;
  metadata.releaseReviewUrl = msProperties.approvalUrl;
  metadata.initialTemplate = msProperties.template;
  if (msProperties.administrators) {
    metadata.initialAdministrators = msProperties.administrators;
  }
  metadata.projectType = msProperties.projectType;
  metadata.initialCorrelationId = req.correlationId;
  // team permissions
  const teamTypes = [
    'pull',
    'push',
    'admin',
    'maintain',
  ];
  const typeValues = [
    GitHubRepositoryPermission.Pull,
    GitHubRepositoryPermission.Push,
    GitHubRepositoryPermission.Admin,
    GitHubRepositoryPermission.Maintain,
  ];
  downgradeBroadAccessTeams(organization, msProperties.teams || {});
  for (let i = 0; msProperties.teams && i < teamTypes.length; i++) {
    const teamType = teamTypes[i];
    const enumValue = typeValues[i];
    const idList = msProperties.teams[teamType];
    if (idList && idList.length) {
      for (let j = 0; j < idList.length; j++) {
        metadata.initialTeamPermissions.push({
          permission: enumValue,
          teamId: idList[j],
        });
      }
    }
  }
  let entityId = existingRepoId || null;
  try {
    if (!entityId) {
      entityId = await repositoryMetadataProvider.createRepositoryMetadata(metadata);
    }
  } catch (insertRequestError) {
    const err = jsonError(new Error(`Rolling back, problems creating repo metadata for ${metadata.repositoryName} and repo ${metadata.repositoryId}`), 500);
    req.insights.trackException({
      exception: insertRequestError,
      properties: {
        event: 'ApiRepoCreateRollbackError',
        message: insertRequestError && insertRequestError.message ? insertRequestError.message : insertRequestError,
      },
    });
    if (!organization || !metadata || !metadata.repositoryName) {
      throw insertRequestError; // if GitHub never returned
    }
    const newlyCreatedRepo = (organization as Organization).repository(metadata.repositoryName);
    await newlyCreatedRepo.delete();
    throw err;
  }
  if (existingRepoId) {
    try {
      await repositoryMetadataProvider.updateRepositoryMetadata(metadata);
    } catch (updateError) {
      throw updateError;
    }
  }
  const approvalPackage: IApprovalPackage = {
    id: entityId,
    repositoryMetadata: metadata,
    createResponse: response,
    isUnlockingExistingRepository: existingRepoId,
    isFork: response ? response.fork : false,
    isTransfer: metadata && metadata.transferSource ? true : false,
    repoCreateResponse,
    createEntrypoint: entrypoint,
  };
  // req.approvalRequest['ms.approvalId'] = requestId; // TODO: is this ever used?
  const repoWorkflow = new RepoWorkflowEngine(providers, organization, approvalPackage);
  let output = [];
  try {
    output = await generateAndRunSecondaryTasks(repoWorkflow);
  } catch (rollbackNeededError) {
    console.dir(rollbackNeededError);
  }
  if (!req.repoCreateResponse) {
    req.repoCreateResponse = { tasks: null };
  }
  req.repoCreateResponse.tasks = output;
  if (logic?.afterRepositoryCreated) {
    try {
      await logic.afterRepositoryCreated(createContext, individualContext?.corporateIdentity?.id, req.repoCreateResponse, organization);
    } catch (ignoredCustomError) {
      insights?.trackException({ exception: ignoredCustomError });
    }
  }
  if (msProperties.notify && mailProvider) {
    try {
      let createdUserLink: ICorporateLink = individualContext?.link || null;
      if (!createdUserLink && providers.linkProvider) {
        try {
          createdUserLink = await providers.linkProvider.getByThirdPartyId(repoWorkflow.request.createdByThirdPartyId);
        } catch (linkError) {
          console.log(`Ignored link error during new repo notification: ${linkError}`);
        }
      }
      await sendEmail(req, logic, createContext, mailProvider, req.apiKeyRow, req.correlationId, output, repoWorkflow.request, msProperties, existingRepoId, repository, createdUserLink);
    } catch (mailSendError) {
      insights?.trackException({ exception: mailSendError });
      console.dir(mailSendError);
    }
  }
  return req.repoCreateResponse;
}