From d7e278867943dbe3508ae8c3033b32dd1bfb719d Mon Sep 17 00:00:00 2001 From: Alexander Lee Date: Mon, 10 Apr 2023 14:27:43 +0800 Subject: [PATCH] Feat/site creation form email (#679) * Feat: add findOrCreateByEmail * Feat: add site member instead of github team * Feat: update forms parsing * Fix: update infraService initialisation * Chore: remove unused createTeamOnGithub * nit: update variable name * chore: update todo with issue number * Fix: trim user input --- src/routes/formsgSiteCreation.ts | 14 +++++++++++--- src/server.js | 21 +++++++++++---------- src/services/identity/ReposService.ts | 10 ---------- src/services/identity/UsersService.ts | 7 +++++++ src/services/infra/InfraService.ts | 25 +++++++++++++++++++++++-- 5 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/routes/formsgSiteCreation.ts b/src/routes/formsgSiteCreation.ts index a41ec0a5d..881cc0e87 100644 --- a/src/routes/formsgSiteCreation.ts +++ b/src/routes/formsgSiteCreation.ts @@ -20,6 +20,7 @@ const SITE_CREATE_FORM_KEY = config.get("formSg.siteCreateFormKey") const REQUESTER_EMAIL_FIELD = "Government E-mail" const SITE_NAME_FIELD = "Site Name" const REPO_NAME_FIELD = "Repository Name" +const OWNER_NAME_FIELD = "Site Owner E-mail" export interface FormsgRouterProps { usersService: UsersService @@ -51,6 +52,9 @@ export class FormsgRouter { const requesterEmail = getField(responses, REQUESTER_EMAIL_FIELD) const siteName = getField(responses, SITE_NAME_FIELD) const repoName = getField(responses, REPO_NAME_FIELD) + const ownerEmail = getField(responses, OWNER_NAME_FIELD) + ?.toLowerCase() + .trim() logger.info( `Create site form submission [${submissionId}] (repoName '${repoName}', siteName '${siteName}') requested by <${requesterEmail}>` @@ -75,16 +79,20 @@ export class FormsgRouter { await this.sendCreateError(requesterEmail, repoName, submissionId, err) return res.sendStatus(200) } - const foundUser = await this.usersService.findByEmail(requesterEmail) - if (!foundUser) { + const foundIsomerRequester = await this.usersService.findByEmail( + requesterEmail + ) + if (!foundIsomerRequester) { const err = `Form submitter ${requesterEmail} is not an Isomer user. Register an account for this user and try again.` await this.sendCreateError(requesterEmail, repoName, submissionId, err) return res.sendStatus(200) } + const foundOwner = await this.usersService.findOrCreateByEmail(ownerEmail) // 3. Use service to create site const { deployment } = await this.infraService.createSite( - foundUser, + foundIsomerRequester, + foundOwner, siteName, repoName ) diff --git a/src/server.js b/src/server.js index cd9ec6aba..140b4aa3c 100644 --- a/src/server.js +++ b/src/server.js @@ -159,16 +159,6 @@ const launchesService = new LaunchesService({ launchClient, }) const queueService = new QueueService() -const infraService = new InfraService({ - sitesService, - reposService, - deploymentsService, - launchesService, - queueService, -}) - -// poller for incoming queue -infraService.pollQueue() const identityAuthService = getIdentityAuthService(gitHubService) const collaboratorsService = new CollaboratorsService({ @@ -179,6 +169,17 @@ const collaboratorsService = new CollaboratorsService({ whitelist: Whitelist, }) +const infraService = new InfraService({ + sitesService, + reposService, + deploymentsService, + launchesService, + queueService, + collaboratorsService, +}) +// poller for incoming queue +infraService.pollQueue() + const authenticationMiddleware = getAuthenticationMiddleware() const authorizationMiddleware = getAuthorizationMiddleware({ identityAuthService, diff --git a/src/services/identity/ReposService.ts b/src/services/identity/ReposService.ts index cc2c0a775..a218eec3b 100644 --- a/src/services/identity/ReposService.ts +++ b/src/services/identity/ReposService.ts @@ -64,7 +64,6 @@ export default class ReposService { const repoUrl = `https://github.com/isomerpages/${repoName}` await this.createRepoOnGithub(repoName) - await this.createTeamOnGitHub(repoName) await this.generateRepoAndPublishToGitHub(repoName, repoUrl) return this.create({ name: repoName, @@ -148,15 +147,6 @@ export default class ReposService { private: false, }) - createTeamOnGitHub = ( - repoName: string - ): Promise => - octokit.teams.create({ - org: ISOMER_GITHUB_ORGANIZATION_NAME, - name: repoName, - privacy: "closed", - }) - setRepoAndTeamPermissions = async (repoName: string): Promise => { await octokit.repos.updateBranchProtection({ owner: ISOMER_GITHUB_ORGANIZATION_NAME, diff --git a/src/services/identity/UsersService.ts b/src/services/identity/UsersService.ts index c0c479cf6..794cb4e00 100644 --- a/src/services/identity/UsersService.ts +++ b/src/services/identity/UsersService.ts @@ -167,6 +167,13 @@ class UsersService { return user } + async findOrCreateByEmail(email: string | undefined) { + const [user] = await this.repository.findOrCreate({ + where: { email }, + }) + return user + } + async login(githubId: string): Promise { return this.sequelize.transaction(async (transaction) => { // NOTE: The service's findOrCreate is not being used here as this requires an explicit transaction diff --git a/src/services/infra/InfraService.ts b/src/services/infra/InfraService.ts index 055bb0333..8ee113aac 100644 --- a/src/services/infra/InfraService.ts +++ b/src/services/infra/InfraService.ts @@ -20,6 +20,7 @@ import ReposService from "@services/identity/ReposService" import SitesService from "@services/identity/SitesService" import { mailer } from "@services/utilServices/MailClient" +import CollaboratorsService from "../identity/CollaboratorsService" import QueueService from "../identity/QueueService" const SITE_LAUNCH_UPDATE_INTERVAL = 30000 @@ -31,6 +32,7 @@ interface InfraServiceProps { deploymentsService: DeploymentsService launchesService: LaunchesService queueService: QueueService + collaboratorsService: CollaboratorsService } interface dnsRecordDto { @@ -49,27 +51,45 @@ export default class InfraService { private readonly queueService: InfraServiceProps["queueService"] + private readonly collaboratorsService: InfraServiceProps["collaboratorsService"] + constructor({ sitesService, reposService, deploymentsService, launchesService, queueService, + collaboratorsService, }: InfraServiceProps) { this.sitesService = sitesService this.reposService = reposService this.deploymentsService = deploymentsService this.launchesService = launchesService this.queueService = queueService + this.collaboratorsService = collaboratorsService } - createSite = async (creator: User, siteName: string, repoName: string) => { + createSite = async ( + creator: User, + member: User, + siteName: string, + repoName: string + ) => { let site: Site | undefined // For error handling + const memberEmail = member.email + if (!memberEmail) { + logger.error( + `createSite: initial member for ${siteName} does not have associated email` + ) + throw new Error( + `createSite: initial member for ${siteName} does not have associated email` + ) + } try { // 1. Create a new site record in the Sites table const newSiteParams = { name: siteName, - apiTokenName: "", // TODO: figure this out + apiTokenName: "", // TODO (IS-76): Remove once DB has removed this param creator, creatorId: creator.id, } @@ -96,6 +116,7 @@ export default class InfraService { // 5. Set up permissions await this.reposService.setRepoAndTeamPermissions(repoName) + await this.collaboratorsService.create(repoName, memberEmail, true) // 6. Update status const updateSuccessSiteInitParams = {