From abe7b6748ddfd5171db4b6527f01bde33ebff96e Mon Sep 17 00:00:00 2001 From: seaerchin Date: Tue, 26 Sep 2023 18:56:20 +0800 Subject: [PATCH] feat(formsg): clone repo on webhook trigger from forms --- src/config/config.ts | 7 +++ src/routes/formsgSiteCreation.ts | 86 +++++++++++++++++++++++++++++++- src/server.js | 6 ++- 3 files changed, 97 insertions(+), 2 deletions(-) diff --git a/src/config/config.ts b/src/config/config.ts index ec89cf26b..b36910f4a 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -283,6 +283,13 @@ const config = convict({ format: "required-string", default: "", }, + siteCloneFormKey: { + doc: "FormSG API key for site clone form", + env: "SITE_CLONE_FORM_KEY", + sensitive: true, + format: "required-string", + default: "", + }, }, postman: { apiKey: { diff --git a/src/routes/formsgSiteCreation.ts b/src/routes/formsgSiteCreation.ts index cd859ba11..8d80c9e6c 100644 --- a/src/routes/formsgSiteCreation.ts +++ b/src/routes/formsgSiteCreation.ts @@ -11,10 +11,12 @@ import { BadRequestError } from "@errors/BadRequestError" import { getField } from "@utils/formsg-utils" import { attachFormSGHandler } from "@root/middleware" +import GitFileSystemService from "@services/db/GitFileSystemService" import UsersService from "@services/identity/UsersService" import InfraService from "@services/infra/InfraService" import { mailer } from "@services/utilServices/MailClient" +const SITE_CLONE_FORM_KEY = config.get("formSg.siteCloneFormKey") const SITE_CREATE_FORM_KEY = config.get("formSg.siteCreateFormKey") const REQUESTER_EMAIL_FIELD = "Government E-mail" const SITE_NAME_FIELD = "Site Name" @@ -25,6 +27,7 @@ const LOGIN_TYPE_FIELD = "Login Type" export interface FormsgRouterProps { usersService: UsersService infraService: InfraService + gitFileSystemService: GitFileSystemService } export class FormsgRouter { @@ -32,9 +35,16 @@ export class FormsgRouter { private readonly infraService: FormsgRouterProps["infraService"] - constructor({ usersService, infraService }: FormsgRouterProps) { + private readonly gitFileSystemService: FormsgRouterProps["gitFileSystemService"] + + constructor({ + usersService, + infraService, + gitFileSystemService, + }: FormsgRouterProps) { this.usersService = usersService this.infraService = infraService + this.gitFileSystemService = gitFileSystemService // We need to bind all methods because we don't invoke them from the class directly autoBind(this) } @@ -157,9 +167,83 @@ export class FormsgRouter { await mailer.sendMail(email, subject, html) } + cloneSiteToEfs: RequestHandler< + never, + Record, + { data: { submissionId: string } }, + never, + { submission: DecryptedContent } + > = async (req, res) => { + // 1. Extract arguments + const { submissionId } = req.body.data + const { responses } = res.locals.submission + // NOTE: This is validated by formsg to be of domain `@open.gov.sg`; + // hence, not revalidating here + const requesterEmail = getField(responses, "Email") as string + // NOTE: The field is required by our form so this cannot be empty or undefined + const githubRepoName = getField(responses, "Github Repo Name") as string + + logger.info( + `${requesterEmail} requested for ${githubRepoName} to be cloned onto EFS` + ) + + this.gitFileSystemService + .clone(githubRepoName) + .map((path) => { + logger.info(`Cloned ${githubRepoName} to ${path}`) + this.sendCloneSuccess( + requesterEmail, + githubRepoName, + submissionId, + path + ) + }) + .mapErr((err) => { + logger.error( + `Cloning repo: ${githubRepoName} to EFS failed with error: ${JSON.stringify( + err + )}` + ) + this.sendCloneError( + requesterEmail, + githubRepoName, + submissionId, + err.message + ) + }) + } + + sendCloneSuccess = async ( + requesterEmail: string, + githubRepoName: string, + submissionId: string, + path: string + ) => { + const subject = `[Isomer] Clone site ${githubRepoName} SUCCESS` + const html = `

Isomer site ${githubRepoName} was cloned successfully to EFS path: ${path}. (Form submission id [${submissionId}])

` + await mailer.sendMail(requesterEmail, subject, html) + } + + async sendCloneError( + requesterEmail: string, + githubRepoName: string, + submissionId: string, + message: string + ) { + const subject = `[Isomer] Clone site ${githubRepoName} FAILURE` + const html = `

Isomer site ${githubRepoName} was not cloned successfully. Cloning failed with error: ${message} (Form submission id [${submissionId}])

` + await mailer.sendMail(requesterEmail, subject, html) + } + getRouter() { const router = express.Router({ mergeParams: true }) + router.post( + "/clone-site", + attachFormSGHandler(SITE_CLONE_FORM_KEY), + this.cloneSiteToEfs + ) + router.post( "/create-site", attachFormSGHandler(SITE_CREATE_FORM_KEY), diff --git a/src/server.js b/src/server.js index 707d1b0b2..044093a8f 100644 --- a/src/server.js +++ b/src/server.js @@ -351,7 +351,11 @@ const authV2Router = new AuthRouter({ statsMiddleware, sgidAuthRouter, }) -const formsgRouter = new FormsgRouter({ usersService, infraService }) +const formsgRouter = new FormsgRouter({ + usersService, + infraService, + gitFileSystemService, +}) const formsgSiteLaunchRouter = new FormsgSiteLaunchRouter({ usersService, infraService,