feat: complete day 13
This commit is contained in:
+43
-33
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user