diff --git a/.vscode/launch.json b/.vscode/launch.json index f038857..1e2288b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -13,6 +13,19 @@ ], "program": "${workspaceFolder}/index.js", "envFile": "${workspaceFolder}/.env" + }, + { + "type": "node", + "request": "launch", + "name": "Run tests", + "skipFiles": [ + "/**" + ], + "args": [ + "--test" + ], + "envFile": "${workspaceFolder}/.env", + "console": "integratedTerminal" } ] } \ No newline at end of file diff --git a/README.md b/README.md index 44a26b9..c35e19c 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,39 @@ -# studiolovelies.com +# studio-lovelies.com +The source code for [studio-lovelies.com](https://studio-lovelies.com). -## Development -Development requires an email address to send emails via the contact form. Obtain the hostname, port, username and password of the email you want to use. +## Requirements +- `nvm` + - for Windows: Install [`nvm-windows` 1.1.9 or later](https://github.com/coreybutler/nvm-windows) + - for other operating systems: Install [`nvm` 0.39.2 or later](https://github.com/nvm-sh/nvm#installing-and-updating) +- SMTP account to send emails via the contact form _(optional)_ + - SMTP host + - SMTP port + - SMTP username + - SMTP password +- [Discord webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) to forward contact form submissions to a Discord channel _(optional)_ + - absolute URL to the webhook -1. - - For Windows: Install [`nvm-windows` 1.1.9 or later](https://github.com/coreybutler/nvm-windows) - - For other operating systems: Install [`nvm` 0.39.2 or later](https://github.com/nvm-sh/nvm#installing-and-updating) -2. Clone the repository -3. Duplicate `.env.sample` and rename it to `.env` -4. Replace the values in `.env` with your own -3. Inside your local clone of this repository run `nvm install && nvm use && npm install && npm run start` \ No newline at end of file +## Setup +1. Clone the repository. +2. Duplicate `.env.sample` and rename it to `.env`. +3. Replace the values in `.env` with your own (see [Requirements](#requirements)). +4. Inside your local clone of this repository run `nvm install && nvm use && npm install`. +5. To run the tests run `npm run test`; these should pass wihout any errors. +6. To start the server locally run `npm run start`. + +## Contributing +1. Create a new branch. +2. Build, test, commit and push your changes locally (see [Setup](#setup)). +3. [Create and submit a Pull Request](https://github.com/Studio-Lovelies/StudioLoveliesWebsite/compare) from your branch to `main` and fill out the Pull Request template. + 1. A second developer will review your changes. **Ping any developer, if this doesn't happen within 24 hours.** + 1. Address all feedback. Mark conversations as resolved after you pushed corresponding changes. Challenge feedback, if you disagree. + 2. In parallel, a GitHub Action tests, builds and deploys the website. + 1. If the GitHub Action **fails**, you will receive an email + notification on GitHub. Update your Pull Request until all checks pass. + 2. If the GitHub Action **succeeeds**, you can check the preview environment. **Note:** This might take up to 10 minutes. + 1. Verify [`preview.studio-lovelies.com/version`](https://preview.studio-lovelies.com/version) returns the commit hash of last Pull Request commit. + 2. Verify [`preview.studio-lovelies.com`](https://preview.studio-lovelies.com) shows all your changes. +4. `Rebase and merge` the Pull Request. +5. Your changes will be deployed to the production environment. **Note:** This might take up to 10 minutes. + 1. Verify the last commit of the `main` branch is the last commit of your Pull Request. **Note:** The commit hash might be different, if someone else merged a Pull Request in the meantime. + 2. Verify [`studio-lovelies.com/version`](https://studio-lovelies.com/version) returns the commit hash of last `main` commit. + 2. Verify [`studio-lovelies.com`](https://studio-lovelies.com) shows all your changes. \ No newline at end of file diff --git a/host.js b/host.js new file mode 100644 index 0000000..0792304 --- /dev/null +++ b/host.js @@ -0,0 +1,170 @@ +import express, { json } from "express"; +import { createTransport } from "nodemailer"; +import { renderFile } from "ejs"; + +export const hostPage = () => { + var app = express(); + app.use(express.static('public')); + + const PORT = process.env.PORT || 3000; + + app.use(json()); + app.set('views', 'public/views'); + app.engine('html', renderFile); + app.set('view engine', 'html'); + + let transporter = null; + if (process.env.SMTP_HOST && process.env.SMTP_PORT && process.env.SMTP_USERNAME && process.env.SMTP_PASSWORD) + { + transporter = createTransport({ + host: process.env.SMTP_HOST, + port: process.env.SMTP_PORT, + auth: { + user: process.env.SMTP_USERNAME, + pass: process.env.SMTP_PASSWORD, + }, + }); + + transporter.verify(function(error, success) { + if (error) { + console.log(error); + } else { + console.log("Mail server is ready to take messages"); + } + }); + } + + app.get("/", (req, res) => { + return res.sendFile("index.html", { root: "public/views" }); + }); + + app.get("/about", (req, res) => { + return res.sendFile("about.html", { root: "public/views" }); + }); + + app.get("/projects", (req, res) => { + return res.sendFile("projects.html", { root: "public/views" }); + }); + + app.get("/contact", (req, res) => { + return res.sendFile("contact.html", { root: "public/views" }); + }); + + app.get("/epik", (req, res) => { + return res.sendFile("epik.html", { root: "public/views" }); + }); + + app.get("/version", (req, res) => { + return res.sendFile("version.html", { root: "public/views" }); + }); + + app.post("/contact", (req, res) => { + if (req.query.sendEmail != "" && req.query.sendEmail != true) { + return; + } + + var discordPromise = new Promise((resolve, reject) => { + if (!process.env.DISCORD_WEBHOOK_URL || !process.env.DISCORD_USERNAME || !process.env.DISCORD_AVATAR_URL) { + console.log("Missing Discord webhook configuration, skipping Discord send"); + return resolve(); + } + + var options = { + 'method': 'POST', + 'url': process.env.DISCORD_WEBHOOK_URL, + 'headers': { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + "username": process.env.DISCORD_USERNAME, + "avatar_url": process.env.DISCORD_AVATAR_URL, + "embeds": [{ + "title": req.body.subject, + "description": req.body.message, + "color": 0x00adef, + "fields": [{ + "name": "From", + "value": req.body.email, + "inline": true + }, { + "name": "Sent", + "value": ``, + "inline": true + }], + }] + }) + }; + + fetch(options.url, options).then(res => { + if (res.status != 204) { + return reject({ + source: "discord", + message: res.status + " Discord returned a non-204 status code", + data: res + }); + } + + return resolve(); + }).catch(err => { + return reject({ + source: "discord", + message: res.status + " " + err, + data: res + }); + }); + }); + + var mailPromise = new Promise((resolve, reject) => { + const mail = { + from: process.env.SMTP_FROM, + to: process.env.SMTP_TO, + subject: req.body.subject, + text: req.body.message + "\n\nSent from: " + req.body.email, + }; + + if (!transporter) { + console.log("No mail server configured, skipping mail send"); + return resolve(); + } + + transporter.sendMail(mail, (err, data) => { + if (err) { + return reject({ + source: "mail", + message: err, + data + }); + } + + return resolve(); + }); + }); + + Promise.all([discordPromise, mailPromise]).then(() => { + return res.status(200).json({ + status: "ok", + success: true + }); + }).catch(error => { + return res.status(500).json({ + status: "error", + success: false, + error: "An internal server error occured", + message: error.message, + data: error.data + }); + }); + }); + + app.get("*", (req, res) => { + return res.status(404).sendFile("404.html", { root: "public/views" }); + }); + + return { + port: PORT, + server: app.listen( + PORT, + () => console.log("Website live and listening on port " + PORT) + ) + }; +} \ No newline at end of file diff --git a/index.js b/index.js index f604ab8..f7d7578 100644 --- a/index.js +++ b/index.js @@ -1,154 +1,5 @@ -import express, { json } from "express"; -import { createTransport } from "nodemailer"; -import { renderFile } from "ejs"; - -var app = express(); -app.use(express.static('public')); - -const PORT = process.env.PORT || 3000; - -app.use(json()); -app.set('views', 'public/views'); -app.engine('html', renderFile); -app.set('view engine', 'html'); - -const transporter = createTransport({ - host: process.env.SMTP_HOST, - port: process.env.SMTP_PORT, - auth: { - user: process.env.SMTP_USERNAME, - pass: process.env.SMTP_PASSWORD, - }, -}); - -transporter.verify(function(error, success) { - if (error) { - console.log(error); - } else { - console.log("Mail server is ready to take messages"); - } -}); - -app.listen( - PORT, - () => console.log("Website live and listening on port " + PORT) -); - -app.get("/", (req, res) => { - return res.sendFile("index.html", { root: "public/views" }); -}); - -app.get("/about", (req, res) => { - return res.sendFile("about.html", { root: "public/views" }); -}); - -app.get("/projects", (req, res) => { - return res.sendFile("projects.html", { root: "public/views" }); -}); - -app.get("/contact", (req, res) => { - return res.sendFile("contact.html", { root: "public/views" }); -}); - -app.get("/epik", (req, res) => { - return res.sendFile("epik.html", { root: "public/views" }); -}); - -app.get("/version", (req, res) => { - return res.sendFile("version.html", { root: "public/views" }); -}); - -app.post("/contact", (req, res) => { - if (req.query.sendEmail != "" && req.query.sendEmail != true) { - return; - } - - var discordPromise = new Promise((resolve, reject) => { - var options = { - 'method': 'POST', - 'url': process.env.DISCORD_WEBHOOK_URL, - 'headers': { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - "username": process.env.DISCORD_USERNAME, - "avatar_url": process.env.DISCORD_AVATAR_URL, - "embeds": [{ - "title": req.body.subject, - "description": req.body.message, - "color": 0x00adef, - "fields": [{ - "name": "From", - "value": req.body.email, - "inline": true - }, { - "name": "Sent", - "value": ``, - "inline": true - }], - }] - }) - }; - - fetch(options.url, options).then(res => { - if (res.status != 204) { - return reject({ - source: "discord", - message: res.status + " Discord returned a non-204 status code", - data: res - }); - } - - return resolve(); - }).catch(err => { - return reject({ - source: "discord", - message: res.status + " " + err, - data: res - }); - }); - }); - - var mailPromise = new Promise((resolve, reject) => { - const mail = { - from: process.env.SMTP_FROM, - to: process.env.SMTP_TO, - subject: req.body.subject, - text: req.body.message + "\n\nSent from: " + req.body.email, - }; - - transporter.sendMail(mail, (err, data) => { - if (err) { - return reject({ - source: "mail", - message: err, - data - }); - } - - return resolve(); - }); - }); - - Promise.all([discordPromise, mailPromise]).then(() => { - return res.status(200).json({ - status: "ok", - success: true - }); - }).catch(error => { - return res.status(500).json({ - status: "error", - success: false, - error: "An internal server error occured", - message: error.message, - data: error.data - }); - }); -}); - -app.get("*", (req, res) => { - return res.status(404).sendFile("404.html", { root: "public/views" }); -}); +import { hostPage } from "./host.js"; +const server = hostPage(); process.on("uncaughtException", error => { console.log(error); diff --git a/public/assets/css/default.css b/public/assets/css/default.css index f19c4fb..b192aa2 100644 --- a/public/assets/css/default.css +++ b/public/assets/css/default.css @@ -57,16 +57,16 @@ a { } .logo:hover { - transform: scale(1.02); + transform: scale(1.02) translate(0, -0.2%); } -.logo #studioLovelies-logo { - float: left; - margin: 5px 0 0 10px; +.logo img { + height: calc(100%); + box-sizing: border-box; + padding: 5px 5px 5px 10px; } .logo #studioLovelies-text { - margin-left: 5px; display: inline-block; position: absolute; } diff --git a/public/assets/images/logo-transparent-x180.png b/public/assets/images/logo-transparent-x180.png new file mode 100644 index 0000000..e2ba5ce Binary files /dev/null and b/public/assets/images/logo-transparent-x180.png differ diff --git a/public/assets/images/studio-lovelies-x180.png b/public/assets/images/studio-lovelies-x180.png new file mode 100644 index 0000000..69bbdec Binary files /dev/null and b/public/assets/images/studio-lovelies-x180.png differ diff --git a/public/views/404.html b/public/views/404.html index 3fb8347..aeca26a 100644 --- a/public/views/404.html +++ b/public/views/404.html @@ -7,6 +7,7 @@ 404 Not Found + diff --git a/public/views/about.html b/public/views/about.html index d904d55..cffb38c 100644 --- a/public/views/about.html +++ b/public/views/about.html @@ -6,6 +6,7 @@ + @@ -15,8 +16,8 @@