diff --git a/commit_and_push.sh b/commit_and_push.sh old mode 100644 new mode 100755 diff --git a/package-lock.json b/package-lock.json index 093c36f..1cfc014 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "builder-api", + "name": "website-builder-api", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "builder-api", + "name": "website-builder-api", "version": "1.0.0", "license": "ISC", "dependencies": { @@ -13,6 +13,7 @@ "cors": "^2.8.5", "dotenv": "^16.3.2", "express": "^4.18.2", + "fs-extra": "^11.2.0", "mariadb": "^3.2.3", "pino": "^8.17.2", "pino-pretty": "^10.3.1", @@ -22,7 +23,9 @@ "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^4.17.21", + "@types/fs-extra": "^11.0.4", "@types/swagger-ui-express": "^4.1.6", + "@types/uuid": "^9.0.7", "concurrently": "^8.2.2", "typescript": "^5.3.3" } @@ -99,6 +102,16 @@ "@types/send": "*" } }, + "node_modules/@types/fs-extra": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", + "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", + "dev": true, + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, "node_modules/@types/geojson": { "version": "7946.0.13", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", @@ -110,6 +123,15 @@ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, + "node_modules/@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -172,6 +194,12 @@ "@types/serve-static": "*" } }, + "node_modules/@types/uuid": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", + "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", + "dev": true + }, "node_modules/@types/validator": { "version": "13.11.8", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.8.tgz", @@ -739,6 +767,19 @@ "node": ">= 0.6" } }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -781,6 +822,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -922,6 +968,17 @@ "node": ">=10" } }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -1715,6 +1772,14 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index 987cba2..ba09eff 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,16 @@ "scripts": { "build": "npx tsc", "start": "node build/server.js", - "dev": "concurrently \"npx tsc --watch\" \"nodemon -q build/server.js | pino-pretty\"" + "dev": "concurrently \"npx tsc --watch\" \"nodemon --ignore ./tmp/ --ignore ./websites/ -q build/server.js | pino-pretty\"" }, "author": "", "license": "ISC", "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^4.17.21", + "@types/fs-extra": "^11.0.4", "@types/swagger-ui-express": "^4.1.6", + "@types/uuid": "^9.0.7", "concurrently": "^8.2.2", "typescript": "^5.3.3" }, @@ -22,6 +24,7 @@ "cors": "^2.8.5", "dotenv": "^16.3.2", "express": "^4.18.2", + "fs-extra": "^11.2.0", "mariadb": "^3.2.3", "pino": "^8.17.2", "pino-pretty": "^10.3.1", diff --git a/server.ts b/server.ts index 2ac9ed3..b20dc2a 100644 --- a/server.ts +++ b/server.ts @@ -2,11 +2,13 @@ import express, { Express } from "express"; import dotenv from "dotenv"; import bodyParser from "body-parser"; import cors from "cors"; +import fs from "fs-extra"; import logger from "./src/logger/logger"; import syncModels from "./src/models"; import websiteRoutes from "./src/routes/websiteRoutes"; +import { fstat } from "fs-extra"; dotenv.config(); @@ -33,6 +35,12 @@ app.use((err: any, req: any, res: any, next: any) => { syncModels(); +// create tmp folder if not exists + +fs.mkdir(process.env.TMP_DIR as string, { recursive: true }, (err) => { + if (err) throw err; +}); + app.listen(port, host, () => logger.info(`⚡️[server]: Server is running at http://${host}:${port}`) ); diff --git a/src/controllers/websiteController.ts b/src/controllers/websiteController.ts index 7a5767e..b80d8d2 100644 --- a/src/controllers/websiteController.ts +++ b/src/controllers/websiteController.ts @@ -2,21 +2,83 @@ import { Request, Response } from "express"; import logger from "../logger/logger"; import util from "util"; import { exec } from "child_process"; +import fs from "fs-extra"; +import { v4 as uuidv4 } from "uuid"; const execPromise = util.promisify(exec); +// this is a webhook that will be called by github when a new commit is pushed to the website template repository export async function UpdateTemplateWebhook(req: Request, res: Response) { try { + let tmpDirWebsiteTemplate = process.env.TMP_DIR_WEBSITE_TEMPLATE as string; + logger.info("UpdateTemplateWebhook", req.body); - const { stdout } = await execPromise( - "git clone https://git.ex.umbach.dev/KK/website", - { - cwd: `../../tmp/template`, - } - ); + // remove website folder in tmp + //await fs.remove(tmpDirWebsiteTemplate); - console.log(stdout); + // if website folder exists, pull, else clone + + if (await fs.pathExists(tmpDirWebsiteTemplate)) { + let { stdout } = await execPromise(`git pull`, { + cwd: tmpDirWebsiteTemplate, + }); + + console.log(stdout); + } else { + let { stdout } = await execPromise( + `git clone ${process.env.GIT_WEBSITE_REPOSITORY_URL} ${tmpDirWebsiteTemplate}` + ); + + console.log(stdout); + } + + // execute npm install + + let { stdout: installOutput } = await execPromise(`npm install`, { + cwd: tmpDirWebsiteTemplate, + }); + + console.log(installOutput); + + res.status(200).send({ message: "ok" }); + } catch (error) { + console.log("error", error); + res.status(500).send({ error: "invalid request" }); + } +} + +export async function CreateWebsite(req: Request, res: Response) { + try { + let tmpId = uuidv4(); + + const tmpDirWebsiteTemplate = process.env + .TMP_DIR_WEBSITE_TEMPLATE as string; + const tmpDirCustomerWebsite = `${process.env.TMP_DIR}/${tmpId}`; + const customerWebsiteDir = `${process.env.CUSTOMER_WEBSITES_DIR}/${tmpId}`; + + // check if website template folder exists + + if (!(await fs.pathExists(tmpDirWebsiteTemplate))) { + logger.error("Website template folder does not exist. Clone it first."); + res.status(500).send({ error: "invalid request" }); + return; + } + + // copy website-template folder to customer website folder + await fs.copy(tmpDirWebsiteTemplate, tmpDirCustomerWebsite); + + // run npm build + + let { stdout: buildOutput } = await execPromise(`npm run build`, { + cwd: tmpDirCustomerWebsite, + }); + + console.log(buildOutput); + + // copy the build folder to customer websites folder + + await fs.copy(`${tmpDirCustomerWebsite}/build`, customerWebsiteDir); res.status(200).send({ message: "ok" }); } catch (error) { diff --git a/src/routes/websiteRoutes.ts b/src/routes/websiteRoutes.ts index 884ce07..de671b0 100644 --- a/src/routes/websiteRoutes.ts +++ b/src/routes/websiteRoutes.ts @@ -7,5 +7,6 @@ router.post( "/update-template-webhook", websiteController.UpdateTemplateWebhook ); +router.post("/", websiteController.CreateWebsite); export default router;