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 express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var createError = require("http-errors");
var express = require("express");
var path = require("path");
var cookieParser = require("cookie-parser");
var logger = require("morgan");
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var indexRouter = require("./routes/index");
var usersRouter = require("./routes/users");
const codeRouter = require("./routes/code");
const db = require("./models");
var cors = require("cors");
@@ -13,17 +14,18 @@ var cors = require("cors");
var app = express();
app.set("db", db);
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "jade");
app.use(cors());
app.use(logger('dev'));
app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, "public")));
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use("/", indexRouter);
app.use("/users", usersRouter);
app.use("/api/v1/code", codeRouter);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
@@ -34,11 +36,11 @@ app.use(function (req, res, next) {
app.use(function (err, req, res, next) {
// set locals, only providing error in development
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
res.status(err.status || 500);
res.render('error');
res.render("error");
});
module.exports = app;
+16 -19
View File
@@ -4,16 +4,16 @@
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('day-1:server');
var http = require('http');
var app = require("../app");
var debug = require("debug")("day-1:server");
var http = require("http");
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
var port = normalizePort(process.env.PORT || "3000");
app.set("port", port);
/**
* Create HTTP server.
@@ -26,8 +26,8 @@ var server = http.createServer(app);
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
server.on("error", onError);
server.on("listening", onListening);
/**
* Normalize a port into a number, string, or false.
@@ -54,22 +54,20 @@ function normalizePort(val) {
*/
function onError(error) {
if (error.syscall !== 'listen') {
if (error.syscall !== "listen") {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
var bind = typeof port === "string" ? "Pipe " + port : "Port " + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
case "EACCES":
console.error(bind + " requires elevated privileges");
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
case "EADDRINUSE":
console.error(bind + " is already in use");
process.exit(1);
break;
default:
@@ -83,8 +81,7 @@ function onError(error) {
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
var bind = typeof addr === "string" ? "pipe " + addr : "port " + addr.port;
debug("Listening on " + bind);
console.log("Server 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",
"private": true,
"scripts": {
"start": "node ./bin/www"
"start": "node ./bin/www",
"dev": "node --watch --env-file=.env ./bin/www"
},
"dependencies": {
"cookie-parser": "~1.4.4",
"cors": "^2.8.5",
"debug": "~2.6.9",
"express": "~4.16.1",
"html-pdf-node": "^1.0.8",
"http-errors": "~1.6.3",
"jade": "~1.11.0",
"jsonwebtoken": "^8.5.1",
"morgan": "~1.9.1",
"mysql2": "^2.3.3",
"node-input-validator": "^4.5.1",
"qrcode": "^1.5.4",
"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();
const QRCode = require("qrcode");
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
router.get("/", function (req, res, next) {
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;
+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 express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var createError = require("http-errors");
var express = require("express");
var path = require("path");
var cookieParser = require("cookie-parser");
var logger = require("morgan");
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var indexRouter = require("./routes/index");
var usersRouter = require("./routes/users");
const db = require("./models");
var cors = require("cors");
const maintenanceMiddleware = require("./middleware/Maintenance");
const roleCheckMiddleware = require("./middleware/RoleCheckMiddleware");
var app = express();
app.set("db", db);
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "jade");
app.use(cors());
app.use(logger('dev'));
app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
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('/users', usersRouter);
app.use("/", indexRouter);
app.use("/users", usersRouter);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
@@ -34,11 +38,14 @@ app.use(function (req, res, next) {
app.use(function (err, req, res, next) {
// set locals, only providing error in development
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.render('error');
res.json({
success: false,
error: err.message || "Internal Server Error",
});
});
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",
"private": true,
"scripts": {
"start": "node ./bin/www"
"start": "node ./bin/www",
"dev": "node --watch ./bin/www"
},
"dependencies": {
"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();
const authMiddleware = require("../middleware/AuthMiddleware");
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
router.get("/", authMiddleware, function (req, res, next) {
res.json({
success: true,
data: "respond with a resource",
});
});
module.exports = router;