Skip to content

Commit

Permalink
feat(formsg): clone repo on webhook trigger from forms
Browse files Browse the repository at this point in the history
  • Loading branch information
seaerchin committed Sep 26, 2023
1 parent 824d669 commit abe7b67
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 2 deletions.
7 changes: 7 additions & 0 deletions src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
86 changes: 85 additions & 1 deletion src/routes/formsgSiteCreation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -25,16 +27,24 @@ const LOGIN_TYPE_FIELD = "Login Type"
export interface FormsgRouterProps {
usersService: UsersService
infraService: InfraService
gitFileSystemService: GitFileSystemService
}

export class FormsgRouter {
private readonly usersService: FormsgRouterProps["usersService"]

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)
}
Expand Down Expand Up @@ -157,9 +167,83 @@ export class FormsgRouter {
await mailer.sendMail(email, subject, html)
}

cloneSiteToEfs: RequestHandler<
never,
Record<string, never>,
{ 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 = `<p>Isomer site ${githubRepoName} was cloned successfully to EFS path: ${path}. (Form submission id [${submissionId}])</p>`
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 = `<p>Isomer site ${githubRepoName} was <b>not</b> cloned successfully. Cloning failed with error: ${message} (Form submission id [${submissionId}])</p>`
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),
Expand Down
6 changes: 5 additions & 1 deletion src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit abe7b67

Please sign in to comment.