feat: complete day 13

This commit is contained in:
Ayobami
2025-07-17 21:55:47 +01:00
parent 9c84737fed
commit 7d9350c3df
10 changed files with 271 additions and 40 deletions
+43 -33
View File
@@ -1,4 +1,4 @@
'use strict';
"use strict";
/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2020*/
/**
* Sequelize File
@@ -8,49 +8,59 @@
* @author Ryan Wong
*
*/
const fs = require('fs');
const path = require('path');
let Sequelize = require('sequelize');
const fs = require("fs");
const path = require("path");
let Sequelize = require("sequelize");
const basename = path.basename(__filename);
const { DataTypes } = require('sequelize');
const { DataTypes } = require("sequelize");
const config = {
DB_DATABASE: 'mysql',
DB_USERNAME: 'root',
DB_PASSWORD: 'root',
DB_ADAPTER: 'mysql',
DB_NAME: 'day_1',
DB_HOSTNAME: 'localhost',
DB_DATABASE: "mysql",
DB_USERNAME: "root",
DB_PASSWORD: process.env.DB_PASSWORD || "root",
DB_ADAPTER: "mysql",
DB_NAME: "day_13",
DB_HOSTNAME: "localhost",
DB_PORT: 3306,
};
let db = {};
let sequelize = new Sequelize(config.DB_DATABASE, config.DB_USERNAME, config.DB_PASSWORD, {
dialect: config.DB_ADAPTER,
username: config.DB_USERNAME,
password: config.DB_PASSWORD,
database: config.DB_NAME,
host: config.DB_HOSTNAME,
port: config.DB_PORT,
logging: console.log,
timezone: '-04:00',
pool: {
maxConnections: 1,
minConnections: 0,
maxIdleTime: 100,
},
define: {
timestamps: false,
underscoredAll: true,
underscored: true,
},
});
let sequelize = new Sequelize(
config.DB_NAME,
config.DB_USERNAME,
config.DB_PASSWORD,
{
dialect: config.DB_ADAPTER,
username: config.DB_USERNAME,
password: config.DB_PASSWORD,
database: config.DB_NAME,
host: config.DB_HOSTNAME,
port: config.DB_PORT,
logging: console.log,
timezone: "-04:00",
pool: {
maxConnections: 1,
minConnections: 0,
maxIdleTime: 100,
},
define: {
timestamps: false,
underscoredAll: true,
underscored: true,
},
}
);
// sequelize.sync({ force: true });
sequelize
.sync()
.then(() => console.log("Tables synced successfully!"))
.catch((err) => console.log(err));
fs.readdirSync(__dirname)
.filter((file) => {
return file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js';
return (
file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js"
);
})
.forEach((file) => {
var model = require(path.join(__dirname, file))(sequelize, DataTypes);
+24
View File
@@ -0,0 +1,24 @@
module.exports = (sequelize, DataTypes) => {
const order = sequelize.define("order", {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
total: { type: DataTypes.INTEGER, allowNull: false },
stripe_id: {
type: DataTypes.STRING,
allowNull: false,
},
product_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
status: {
type: DataTypes.INTEGER,
allowNull: false,
},
});
return order;
};
+15
View File
@@ -0,0 +1,15 @@
module.exports = (sequelize, DataTypes) => {
const product = sequelize.define("product", {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
title: DataTypes.STRING,
description: DataTypes.STRING,
price: DataTypes.INTEGER,
image: DataTypes.STRING,
});
return product;
};
+4 -2
View File
@@ -3,7 +3,8 @@
"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",
@@ -14,6 +15,7 @@
"jade": "~1.11.0",
"morgan": "~1.9.1",
"mysql2": "^2.3.3",
"sequelize": "^6.15.1"
"sequelize": "^6.15.1",
"stripe": "^18.3.0"
}
}
+107 -3
View File
@@ -1,9 +1,113 @@
var express = require('express');
var express = require("express");
var router = express.Router();
var db = require("../models");
const stripe = require("stripe")(process.env.STRIPE_TEST_KEY); // Replace with your Stripe test secret key
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
router.get("/", async function (req, res, next) {
try {
const products = await db.product.findAll();
res.render("index", { title: "Products", products });
} catch (err) {
next(err);
}
});
router.get("/product/:id", async function (req, res, next) {
try {
const product = await db.product.findByPk(req.params.id);
if (!product) return res.status(404).send("Product not found");
res.render("product", { title: product.title, product });
} catch (err) {
next(err);
}
});
router.post("/buy/:id", async function (req, res, next) {
try {
const product = await db.product.findByPk(req.params.id);
if (!product) return res.status(404).send("Product not found");
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
line_items: [
{
price_data: {
currency: "usd",
product_data: {
name: product.title,
images: product.image ? [product.image] : [],
},
unit_amount: Math.round(product.price * 100),
},
quantity: 1,
},
],
mode: "payment",
success_url:
req.protocol +
"://" +
req.get("host") +
"/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url:
req.protocol + "://" + req.get("host") + "/product/" + product.id,
});
res.redirect(303, session.url);
} catch (err) {
next(err);
}
});
router.get("/success", async function (req, res, next) {
try {
const session_id = req.query.session_id;
if (!session_id) return res.status(400).send("Missing session ID");
const session = await stripe.checkout.sessions.retrieve(session_id);
// Find or create order
let order = await db.order.findOne({ where: { stripe_id: session.id } });
if (!order) {
// Get product_id from session metadata or line_items
const lineItems = await stripe.checkout.sessions.listLineItems(
session.id,
{ limit: 1 }
);
const productName =
lineItems.data[0].description || lineItems.data[0].price.product;
const dbProduct = await db.product.findOne({
where: { title: productName },
});
await db.order.create({
product_id: dbProduct ? dbProduct.id : null,
total: session.amount_total,
stripe_id: session.id,
status: session.payment_status === "paid" ? 1 : 0,
});
}
res.render("success", { title: "Thank You", session });
} catch (err) {
next(err);
}
});
router.get("/create-product", function (req, res) {
res.render("create-product", { title: "Add Product" });
});
router.post("/create-product", async function (req, res, next) {
try {
const { title, description, price, image } = req.body;
if (!title || !description || !price) {
return res
.status(400)
.render("create-product", {
title: "Add Product",
error: "All fields except image are required.",
});
}
await db.product.create({ title, description, price, image });
res.redirect("/");
} catch (err) {
next(err);
}
});
module.exports = router;
+22
View File
@@ -0,0 +1,22 @@
extends layout
block content
.container.mt-5
h2 Add Product
if error
.alert.alert-danger= error
form(method="POST" action="/create-product")
.form-group
label(for="title") Title
input.form-control(type="text" name="title" id="title" required)
.form-group
label(for="description") Description
textarea.form-control(name="description" id="description" required)
.form-group
label(for="price") Price (in dollars)
input.form-control(type="number" name="price" id="price" min="0" step="0.01" required)
.form-group
label(for="image") Image URL
input.form-control(type="text" name="image" id="image")
button.btn.btn-primary(type="submit") Add Product
a.btn.btn-secondary.ml-2(href="/") Cancel
+16 -1
View File
@@ -2,4 +2,19 @@ extends layout
block content
h1= title
p Welcome to #{title}
a.btn.btn-success.mb-4(href="/create-product") Create Product
if products && products.length
.row
each product in products
.col-md-4.mb-4
.card
if product.image
img.card-img-top(src=product.image, alt=product.title, style="width:100px;height:100px;object-fit:contain;")
.card-body
h5.card-title= product.title
p.card-text= product.description
p.card-text
strong $#{product.price}
a.btn.btn-primary(href=`/product/${product.id}`) View Details
else
p No products found.
+1
View File
@@ -2,6 +2,7 @@ doctype html
html
head
title= title
link(rel='stylesheet', href='https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css')
link(rel='stylesheet', href='/stylesheets/style.css')
body
block content
+15
View File
@@ -0,0 +1,15 @@
extends layout
block content
.container.mt-5
.row
.col-md-6
if product.image
img.img-fluid(src=product.image, alt=product.title style="width:100px;height:100px;object-fit:contain;")
.col-md-6
h2= product.title
p= product.description
h4.text-success $#{product.price}
form(action=`/buy/${product.id}` method="POST")
button.btn.btn-success(type="submit") Buy Now
a.btn.btn-secondary.mt-3(href="/") Back to Products
+23
View File
@@ -0,0 +1,23 @@
extends layout
block content
.container.mt-5
.alert.alert-success
h2 Thank you for your purchase!
p Your payment was successful.
if session
h4 Payment Details
table.table
tr
th Session ID
td= session.id
tr
th Payment Status
td= session.payment_status
tr
th Amount Total
td $#{(session.amount_total / 100).toFixed(2)}
tr
th Payment Method
td= session.payment_method_types && session.payment_method_types[0]
a.btn.btn-primary.mt-4(href="/") Back to Products