Compare commits

...

2 Commits

Author SHA1 Message Date
Ayobami 001e4b6d00 feat: complete day 10 2025-07-16 18:59:35 +01:00
Ayobami 825583e645 feat: complete day 9 2025-07-15 19:04:00 +01:00
14 changed files with 363 additions and 58 deletions
+17 -15
View File
@@ -1,11 +1,12 @@
var createError = require('http-errors'); var createError = require("http-errors");
var express = require('express'); var express = require("express");
var path = require('path'); var path = require("path");
var cookieParser = require('cookie-parser'); var cookieParser = require("cookie-parser");
var logger = require('morgan'); var logger = require("morgan");
var indexRouter = require('./routes/index'); var indexRouter = require("./routes/index");
var usersRouter = require('./routes/users'); var usersRouter = require("./routes/users");
const codeRouter = require("./routes/code");
const db = require("./models"); const db = require("./models");
var cors = require("cors"); var cors = require("cors");
@@ -13,17 +14,18 @@ var cors = require("cors");
var app = express(); var app = express();
app.set("db", db); app.set("db", db);
// view engine setup // view engine setup
app.set('views', path.join(__dirname, 'views')); app.set("views", path.join(__dirname, "views"));
app.set('view engine', 'jade'); app.set("view engine", "jade");
app.use(cors()); app.use(cors());
app.use(logger('dev')); app.use(logger("dev"));
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: false })); app.use(express.urlencoded({ extended: false }));
app.use(cookieParser()); app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public'))); app.use(express.static(path.join(__dirname, "public")));
app.use('/', indexRouter); app.use("/", indexRouter);
app.use('/users', usersRouter); app.use("/users", usersRouter);
app.use("/api/v1/code", codeRouter);
// catch 404 and forward to error handler // catch 404 and forward to error handler
app.use(function (req, res, next) { app.use(function (req, res, next) {
@@ -34,11 +36,11 @@ app.use(function (req, res, next) {
app.use(function (err, req, res, next) { app.use(function (err, req, res, next) {
// set locals, only providing error in development // set locals, only providing error in development
res.locals.message = err.message; res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {}; res.locals.error = req.app.get("env") === "development" ? err : {};
// render the error page // render the error page
res.status(err.status || 500); res.status(err.status || 500);
res.render('error'); res.render("error");
}); });
module.exports = app; module.exports = app;
+16 -19
View File
@@ -4,16 +4,16 @@
* Module dependencies. * Module dependencies.
*/ */
var app = require('../app'); var app = require("../app");
var debug = require('debug')('day-1:server'); var debug = require("debug")("day-1:server");
var http = require('http'); var http = require("http");
/** /**
* Get port from environment and store in Express. * Get port from environment and store in Express.
*/ */
var port = normalizePort(process.env.PORT || '3000'); var port = normalizePort(process.env.PORT || "3000");
app.set('port', port); app.set("port", port);
/** /**
* Create HTTP server. * Create HTTP server.
@@ -26,8 +26,8 @@ var server = http.createServer(app);
*/ */
server.listen(port); server.listen(port);
server.on('error', onError); server.on("error", onError);
server.on('listening', onListening); server.on("listening", onListening);
/** /**
* Normalize a port into a number, string, or false. * Normalize a port into a number, string, or false.
@@ -54,22 +54,20 @@ function normalizePort(val) {
*/ */
function onError(error) { function onError(error) {
if (error.syscall !== 'listen') { if (error.syscall !== "listen") {
throw error; throw error;
} }
var bind = typeof port === 'string' var bind = typeof port === "string" ? "Pipe " + port : "Port " + port;
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages // handle specific listen errors with friendly messages
switch (error.code) { switch (error.code) {
case 'EACCES': case "EACCES":
console.error(bind + ' requires elevated privileges'); console.error(bind + " requires elevated privileges");
process.exit(1); process.exit(1);
break; break;
case 'EADDRINUSE': case "EADDRINUSE":
console.error(bind + ' is already in use'); console.error(bind + " is already in use");
process.exit(1); process.exit(1);
break; break;
default: default:
@@ -83,8 +81,7 @@ function onError(error) {
function onListening() { function onListening() {
var addr = server.address(); var addr = server.address();
var bind = typeof addr === 'string' var bind = typeof addr === "string" ? "pipe " + addr : "port " + addr.port;
? 'pipe ' + addr debug("Listening on " + bind);
: 'port ' + addr.port; console.log("Server listening on:", bind);
debug('Listening on ' + bind);
} }
+191
View File
@@ -0,0 +1,191 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>A simple, clean, and responsive HTML invoice template</title>
<style>
.invoice-box {
max-width: 800px;
margin: auto;
padding: 30px;
border: 1px solid #eee;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
font-size: 16px;
line-height: 24px;
font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
color: #555;
}
.invoice-box table {
width: 100%;
line-height: inherit;
text-align: left;
}
.invoice-box table td {
padding: 5px;
vertical-align: top;
}
.invoice-box table tr td:nth-child(2) {
text-align: right;
}
.invoice-box table tr.top table td {
padding-bottom: 20px;
}
.invoice-box table tr.top table td.title {
font-size: 45px;
line-height: 45px;
color: #333;
}
.invoice-box table tr.information table td {
padding-bottom: 40px;
}
.invoice-box table tr.heading td {
background: #eee;
border-bottom: 1px solid #ddd;
font-weight: bold;
}
.invoice-box table tr.details td {
padding-bottom: 20px;
}
.invoice-box table tr.item td {
border-bottom: 1px solid #eee;
}
.invoice-box table tr.item.last td {
border-bottom: none;
}
.invoice-box table tr.total td:nth-child(2) {
border-top: 2px solid #eee;
font-weight: bold;
}
@media only screen and (max-width: 600px) {
.invoice-box table tr.top table td {
width: 100%;
display: block;
text-align: center;
}
.invoice-box table tr.information table td {
width: 100%;
display: block;
text-align: center;
}
}
/** RTL **/
.invoice-box.rtl {
direction: rtl;
font-family: Tahoma, "Helvetica Neue", "Helvetica", Helvetica, Arial,
sans-serif;
}
.invoice-box.rtl table {
text-align: right;
}
.invoice-box.rtl table tr td:nth-child(2) {
text-align: left;
}
</style>
</head>
<body>
<div class="invoice-box">
<table cellpadding="0" cellspacing="0">
<tr class="top">
<td colspan="2">
<table>
<tr>
<td class="title">
<img
src="https://sparksuite.github.io/simple-html-invoice-template/images/logo.png"
style="width: 100%; max-width: 300px"
/>
</td>
<td>
Invoice #: 123<br />
Created: January 1, 2023<br />
Due: February 1, 2023
</td>
</tr>
</table>
</td>
</tr>
<tr class="information">
<td colspan="2">
<table>
<tr>
<td>
Sparksuite, Inc.<br />
12345 Sunny Road<br />
Sunnyville, CA 12345
</td>
<td>
Acme Corp.<br />
John Doe<br />
john@example.com
</td>
</tr>
</table>
</td>
</tr>
<tr class="heading">
<td>Payment Method</td>
<td>Check #</td>
</tr>
<tr class="details">
<td>Check</td>
<td>1000</td>
</tr>
<tr class="heading">
<td>Item</td>
<td>Price</td>
</tr>
<tr class="item last">
<td>Website design</td>
<td>$300.00</td>
</tr>
<!-- <tr class="item">
<td>Hosting (3 months)</td>
<td>$75.00</td>
</tr>
<tr class="item last">
<td>Domain name (1 year)</td>
<td>$10.00</td>
</tr> -->
<tr class="total">
<td></td>
<td>Total: $385.00</td>
</tr>
</table>
</div>
</body>
</html>
+5 -1
View File
@@ -3,18 +3,22 @@
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node ./bin/www" "start": "node ./bin/www",
"dev": "node --watch --env-file=.env ./bin/www"
}, },
"dependencies": { "dependencies": {
"cookie-parser": "~1.4.4", "cookie-parser": "~1.4.4",
"cors": "^2.8.5", "cors": "^2.8.5",
"debug": "~2.6.9", "debug": "~2.6.9",
"express": "~4.16.1", "express": "~4.16.1",
"html-pdf-node": "^1.0.8",
"http-errors": "~1.6.3", "http-errors": "~1.6.3",
"jade": "~1.11.0", "jade": "~1.11.0",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"morgan": "~1.9.1", "morgan": "~1.9.1",
"mysql2": "^2.3.3", "mysql2": "^2.3.3",
"node-input-validator": "^4.5.1",
"qrcode": "^1.5.4",
"sequelize": "^6.15.1" "sequelize": "^6.15.1"
} }
} }
+29
View File
@@ -0,0 +1,29 @@
const express = require("express");
const router = express.Router();
const fs = require("fs");
const path = require("path");
const pdf = require("html-pdf-node");
router.get("/:code", async (req, res) => {
const { amount = 1, service = "software service" } = req.query;
// Read the invoice template
const templatePath = path.join(__dirname, "../invoice.html");
let html = fs.readFileSync(templatePath, "utf8");
// Replace placeholders in the template
html = html
.replace("Website design", service)
.replace("$300.00", `$${amount}.00`)
.replace("Total: $385.00", `Total: $${amount}.00`);
// Generate PDF
let file = { content: html };
pdf.generatePdf(file, { format: "A4" }).then((pdfBuffer) => {
res.setHeader("Content-Type", "application/pdf");
res.setHeader("Content-Disposition", "attachment; filename=invoice.pdf");
res.send(pdfBuffer);
});
});
module.exports = router;
+16 -3
View File
@@ -1,9 +1,22 @@
var express = require('express'); var express = require("express");
var router = express.Router(); var router = express.Router();
const QRCode = require("qrcode");
/* GET home page. */ /* GET home page. */
router.get('/', function(req, res, next) { router.get("/", function (req, res, next) {
res.render('index', { title: 'Express' }); res.render("index", { title: "Express" });
});
router.get("/code", async function (req, res, next) {
const code = Math.random().toString(36).substring(2, 8); // random code
const qrUrl = `/api/v1/code/${code}?amount=1&service=software%20service`;
const qrData = await QRCode.toDataURL(
`http://localhost:${process.env.PORT || 3000}${qrUrl}`
);
res.render("code", {
qrData,
qrUrl: `http://localhost:${process.env.PORT || 3000}${qrUrl}`,
});
}); });
module.exports = router; module.exports = router;
+6
View File
@@ -0,0 +1,6 @@
extends layout
block content
h1 QR Code
img(src=qrData)
p Link: #{qrUrl}
+23 -16
View File
@@ -1,29 +1,33 @@
var createError = require('http-errors'); var createError = require("http-errors");
var express = require('express'); var express = require("express");
var path = require('path'); var path = require("path");
var cookieParser = require('cookie-parser'); var cookieParser = require("cookie-parser");
var logger = require('morgan'); var logger = require("morgan");
var indexRouter = require('./routes/index'); var indexRouter = require("./routes/index");
var usersRouter = require('./routes/users'); var usersRouter = require("./routes/users");
const db = require("./models"); const db = require("./models");
var cors = require("cors"); var cors = require("cors");
const maintenanceMiddleware = require("./middleware/Maintenance");
const roleCheckMiddleware = require("./middleware/RoleCheckMiddleware");
var app = express(); var app = express();
app.set("db", db); app.set("db", db);
// view engine setup // view engine setup
app.set('views', path.join(__dirname, 'views')); app.set("views", path.join(__dirname, "views"));
app.set('view engine', 'jade'); app.set("view engine", "jade");
app.use(cors()); app.use(cors());
app.use(logger('dev')); app.use(logger("dev"));
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: false })); app.use(express.urlencoded({ extended: false }));
app.use(cookieParser()); app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public'))); app.use(express.static(path.join(__dirname, "public")));
app.use(maintenanceMiddleware);
app.use("/api/v1", roleCheckMiddleware);
app.use('/', indexRouter); app.use("/", indexRouter);
app.use('/users', usersRouter); app.use("/users", usersRouter);
// catch 404 and forward to error handler // catch 404 and forward to error handler
app.use(function (req, res, next) { app.use(function (req, res, next) {
@@ -34,11 +38,14 @@ app.use(function (req, res, next) {
app.use(function (err, req, res, next) { app.use(function (err, req, res, next) {
// set locals, only providing error in development // set locals, only providing error in development
res.locals.message = err.message; res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {}; res.locals.error = req.app.get("env") === "development" ? err : {};
// render the error page // standardized error response
res.status(err.status || 500); res.status(err.status || 500);
res.render('error'); res.json({
success: false,
error: err.message || "Internal Server Error",
});
}); });
module.exports = app; module.exports = app;
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
maintenance: false,
};
+23
View File
@@ -0,0 +1,23 @@
const JwtService = require("../services/JwtService");
module.exports = function (req, res, next) {
const token = JwtService.getToken(req);
if (!token) {
return res.status(401).json({
success: false,
error: "Access denied. No token provided.",
});
}
const payload = JwtService.verifyAccessToken(token);
if (!payload) {
return res.status(401).json({
success: false,
error: "Invalid or expired token.",
});
}
req.tokenPayload = payload;
if (payload && payload.user_id) {
req.user_id = payload.user_id;
}
next();
};
+11
View File
@@ -0,0 +1,11 @@
const config = require("../config");
module.exports = function (req, res, next) {
if (config.maintenance) {
return res.status(503).json({
success: false,
error: "Service is under maintenance. Please try again later.",
});
}
next();
};
+14
View File
@@ -0,0 +1,14 @@
module.exports = function (req, res, next) {
const match = req.path.match(/^\/api\/v1\/(\w+)\//);
if (match) {
const portal = match[1];
const userRole = req.tokenPayload && req.tokenPayload.role;
if (userRole !== portal) {
return res.status(403).json({
success: false,
error: `Access denied. Role '${userRole}' does not match portal '${portal}'.`,
});
}
}
next();
};
+2 -1
View File
@@ -3,7 +3,8 @@
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node ./bin/www" "start": "node ./bin/www",
"dev": "node --watch ./bin/www"
}, },
"dependencies": { "dependencies": {
"cookie-parser": "~1.4.4", "cookie-parser": "~1.4.4",
+7 -3
View File
@@ -1,9 +1,13 @@
var express = require('express'); var express = require("express");
var router = express.Router(); var router = express.Router();
const authMiddleware = require("../middleware/AuthMiddleware");
/* GET users listing. */ /* GET users listing. */
router.get('/', function(req, res, next) { router.get("/", authMiddleware, function (req, res, next) {
res.send('respond with a resource'); res.json({
success: true,
data: "respond with a resource",
});
}); });
module.exports = router; module.exports = router;