progress update: functioning Read functionality

This commit is contained in:
ak 2023-09-07 20:56:08 -07:00
parent 3d8373b859
commit d1c0ec6221
16 changed files with 2210 additions and 0 deletions

64
.gitignore vendored Normal file
View file

@ -0,0 +1,64 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
# deta-space internal files
.space

51
app.js Normal file
View file

@ -0,0 +1,51 @@
const createError = require("http-errors");
const express = require("express");
const path = require("path");
const cookieParser = require("cookie-parser");
const logger = require("morgan");
const mongoose = require("mongoose");
require("dotenv").config();
const app = express();
// get db
const mongoDB = `mongodb+srv://${process.env.USER}:${process.env.PASS}@odin.eftl02o.mongodb.net/?retryWrites=true&w=majority`;
// or throw error
main().catch((err) => console.log(err));
async function main() {
await mongoose.connect(mongoDB);
}
// bootstrap
app.use(
"/styles/css",
express.static(path.join(__dirname, "node_modules/bootstrap/dist/css"))
);
// view engine setup
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "ejs");
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")));
const router = require("./routes/index");
app.use("/", router);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// render the error page
res.status(err.status || 500);
res.render("error");
});
module.exports = app;

90
bin/www Executable file
View file

@ -0,0 +1,90 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('express-inventory-application:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
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');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}

49
controllers/category.js Normal file
View file

@ -0,0 +1,49 @@
const Category = require("../models/category.js");
const Item = require("../models/item.js");
const asyncHandler = require("express-async-handler");
exports.index = asyncHandler(async (req, res, next) => {
// get array of relevant variables for displaying category and its items
// get current category from URL
const category = await Category.findOne({
simpleName: req.params.category,
})
.lean()
.exec();
// check
if (category === null) {
const error = new Error("Category not found");
return next(error);
}
// get all items from category
const items = await Item.find({ category: category._id }).lean().exec();
// render with all relevant vars
res.render("category", {
category: category,
items: items,
});
});
exports.category_create_get = asyncHandler(async (req, res, next) => {
res.send("ligma");
});
exports.category_create_post = asyncHandler(async (req, res, next) => {
res.send("ligma");
});
exports.category_delete_get = asyncHandler(async (req, res, next) => {
res.send("ligma");
});
exports.category_delete_post = asyncHandler(async (req, res, next) => {
res.send("ligma");
});
exports.category_update_get = asyncHandler(async (req, res, next) => {
res.send("ligma");
});
exports.category_update_post = asyncHandler(async (req, res, next) => {
res.send("ligma");
});

24
controllers/index.js Normal file
View file

@ -0,0 +1,24 @@
const Category = require("../models/category.js");
const Item = require("../models/item.js");
const asyncHandler = require("express-async-handler");
exports.index = asyncHandler(async (req, res, next) => {
// gets categories from database
const rawCats = await Category.find().lean().exec();
// function to get items for specified category from database
const getItems = async (category) => {
return await Item.find({ category: category._id }).lean().exec();
};
// creats a copy of the raw categories array with each category's items included as .items (copying is easier than overwriting)
const categories = [];
for (let rawCat of rawCats) {
rawCat.items = await getItems(rawCat);
categories.push(rawCat);
}
res.render("index", {
categories: categories,
});
});

52
controllers/item.js Normal file
View file

@ -0,0 +1,52 @@
const Category = require("../models/category.js");
const Item = require("../models/item.js");
const asyncHandler = require("express-async-handler");
exports.index = asyncHandler(async (req, res, next) => {
// get current item from URL
const item = await Item.findOne({
_id: req.params.item,
})
.lean()
.exec();
// get current category from item
const category = await Category.findById(item.category).lean().exec();
// check
if (item === null) {
const error = new Error("Item not found");
return next(error);
}
// render with all relevant vars
res.render("item", {
item: item,
category: category,
});
});
exports.item_detail = asyncHandler(async (req, res, next) => {
res.send("ligma");
});
exports.item_create_get = asyncHandler(async (req, res, next) => {
res.send("ligma");
});
exports.item_create_post = asyncHandler(async (req, res, next) => {
res.send("ligma");
});
exports.item_delete_get = asyncHandler(async (req, res, next) => {
res.send("ligma");
});
exports.item_delete_post = asyncHandler(async (req, res, next) => {
res.send("ligma");
});
exports.item_update_get = asyncHandler(async (req, res, next) => {
res.send("ligma");
});
exports.item_update_post = asyncHandler(async (req, res, next) => {
res.send("ligma");
});

18
models/category.js Normal file
View file

@ -0,0 +1,18 @@
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const CategorySchema = new Schema({
name: { type: String, required: true },
simpleName: { type: String, required: true },
description: { type: String, required: true },
});
// Virtual for author's URL
CategorySchema.virtual("url").get(function () {
// We don't use an arrow function as we'll need the this object
return `/${this.simpleName}`;
});
// Export model
module.exports = mongoose.model("Category", CategorySchema);

21
models/item.js Normal file
View file

@ -0,0 +1,21 @@
const { ObjectId } = require("mongoose");
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const ItemSchema = new Schema({
name: { type: String, required: true },
category: { type: ObjectId, required: true },
description: { type: String, required: true },
price: { type: Number, required: true },
quantity: { type: Number, required: true },
});
// Virtual for author's URL
ItemSchema.virtual("url").get(function () {
// We don't use an arrow function as we'll need the this object
return `/${this.category.simpleName}/${this._id}}`;
});
// Export model
module.exports = mongoose.model("Item", ItemSchema);

1377
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

22
package.json Normal file
View file

@ -0,0 +1,22 @@
{
"name": "express-inventory-application",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"bootstrap": "^5.3.1",
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"dotenv": "^16.3.1",
"ejs": "^3.1.9",
"express": "^4.18.2",
"express-async-handler": "^1.2.0",
"http-errors": "~1.6.3",
"morgan": "~1.9.1"
},
"devDependencies": {
"mongoose": "^7.5.0"
}
}

307
populatedb.js Normal file
View file

@ -0,0 +1,307 @@
#! /usr/bin/env node
console.log(
'This script populates some test books, authors, genres and bookinstances to your database. Specified database as argument - e.g.: node populatedb "mongodb+srv://cooluser:coolpassword@cluster0.lz91hw2.mongodb.net/local_library?retryWrites=true&w=majority"'
);
// Get arguments passed on command line
const userArgs = process.argv.slice(2);
const Item = require("./models/item");
const Category = require("./models/category");
const items = [];
const categories = [];
const mongoose = require("mongoose");
const mongoDB = userArgs[0];
main().catch((err) => console.log(err));
async function main() {
console.log("Debug: About to connect");
await mongoose.connect(mongoDB);
console.log("Debug: Should be connected?");
await createCategories();
await createItems();
console.log("Debug: Closing mongoose");
mongoose.connection.close();
}
async function categoryCreate(index, name, simpleName, description) {
const category = new Category({
name: name,
simpleName: simpleName,
description: description,
});
await category.save();
categories[index] = category;
console.log(`Added category: ${name}`);
}
async function itemCreate(index, name, category, description, price, quantity) {
const item = new Item({
name: name,
category: category,
description: description,
price: price,
quantity: quantity,
});
await item.save();
items[index] = item;
console.log(`Added item: ${name}`);
}
async function createCategories() {
console.log("Adding categories");
await Promise.all([
categoryCreate(
0,
"Feed and Seed",
"feedandseed",
"Well well well, look at the city slicker pulling up in his fancy German car!"
),
categoryCreate(
1,
"Food",
"food",
"Frozen or fresh, we've got what you need!"
),
categoryCreate(
2,
"Household Goods",
"householdgoods",
"Basics and essentials for your home"
),
categoryCreate(
3,
"Personal Care",
"personalcare",
"Cleanliness is next to Godliness"
),
]);
}
async function createItems() {
console.log("Adding items");
await Promise.all([
itemCreate(
0,
"Corn Seed",
categories[0]._id,
"25lb. bag of corn seed",
19.99,
20
),
itemCreate(
1,
"Wheat Seed",
categories[0]._id,
"25lb. bag of wheat seed",
19.99,
20
),
itemCreate(
2,
"Seed Potatoes",
categories[0]._id,
"25lb. bag of seed potatoes",
14.99,
20
),
itemCreate(
3,
"Duck Feed",
categories[0]._id,
"25lb. bag of duck feed",
19.99,
20
),
itemCreate(
4,
"Chicken Feed",
categories[0]._id,
"25lb. bag of chicken feed",
19.99,
20
),
itemCreate(
5,
"Barn Pellets",
categories[0]._id,
"25lb. bag of dehydrated pine pellets",
19.99,
20
),
itemCreate(
6,
"Tuna Salad Sandwich",
categories[1]._id,
"Freshly-prepared tuna salad sandwich",
4.99,
15
),
itemCreate(
7,
"Egg Salad Sandwich",
categories[1]._id,
"Freshly-prepared egg salad sandwich",
4.99,
15
),
itemCreate(
8,
"Cola (2L.)",
categories[1]._id,
"2 liter bottle of cola",
1.99,
25
),
itemCreate(
9,
"Cola (12-pack)",
categories[1]._id,
"12 pack of cola",
3.99,
25
),
itemCreate(
10,
"Espresso Coffee Grounds",
categories[1]._id,
"Medium-strength espresso coffee grounds (10 oz.)",
3.99,
20
),
itemCreate(
11,
"Sliced American Cheese",
categories[1]._id,
"1 pack of sliced American cheese (24 slices per pack)",
3.99,
50
),
itemCreate(
12,
"Lasagna",
categories[1]._id,
"Frozen lasagna (1 lb.)",
9.99,
25
),
itemCreate(
13,
"Burritos",
categories[1]._id,
"Frozen burritos (12x)",
9.99,
25
),
itemCreate(
14,
"Sliders",
categories[1]._id,
"Frozen sliders (12x)",
9.99,
25
),
itemCreate(15, "Tuna", categories[1]._id, "One can of tuna", 0.99, 25),
itemCreate(
16,
"Crinkle-Cut Fries",
categories[1]._id,
"Frozen crinkle-cut fries (80 oz.)",
7.99,
25
),
itemCreate(
17,
"Supreme(R) Pizza",
categories[1]._id,
`18" frozen pizza with rising crust, officially Supreme(R) branded`,
7.99,
25
),
itemCreate(
18,
"Soap Bar",
categories[3]._id,
`1 soap bar (unsecented)`,
0.99,
25
),
itemCreate(
19,
"Body Wash",
categories[3]._id,
`Liquid body wash (18 fl. oz.)`,
9.99,
15
),
itemCreate(
20,
"Sponge",
categories[2]._id,
"Sponge with scrub strip",
0.99,
100
),
itemCreate(
21,
"Hair Conditioner",
categories[3]._id,
`Revitalizing and moisturizing hair conditioner (18 fl. oz.)`,
9.99,
15
),
itemCreate(
22,
"Shampoo",
categories[3]._id,
`Anti-dandruff shampoo (18 fl. oz.)`,
9.99,
15
),
itemCreate(
23,
"Toothpaste",
categories[3]._id,
`Anticavity fluoride toothpaste (60g)`,
2.99,
50
),
itemCreate(
24,
"Deodorant Stick",
categories[3]._id,
`Deodorant/antiperspirant stick (3oz)`,
3.99,
20
),
itemCreate(
25,
"Hand Soap",
categories[2]._id,
"Liquid hand soap (12 oz.)",
2.99,
25
),
itemCreate(
26,
"Toilet Paper",
categories[2]._id,
"Triple-ply toilet paper roll (6x)",
5.99,
25
),
itemCreate(
27,
"Paper towel",
categories[2]._id,
"Triple-ply paper towel roll (6x)",
5.99,
25
),
]);
}

31
routes/index.js Normal file
View file

@ -0,0 +1,31 @@
const express = require("express");
const router = express.Router();
const category_controller = require("../controllers/category.js");
const index_controller = require("../controllers/index.js");
const item_controller = require("../controllers/item.js");
// home page
router.get("/", index_controller.index);
// create category page
router.get("/createcategory", category_controller.category_create_get);
router.post("/createcategory", category_controller.category_create_post);
// category functions
router.get("/:category", category_controller.index);
router.get("/:category/update", category_controller.category_update_get);
router.post("/:category/update", category_controller.category_update_post);
router.get("/:category/delete", category_controller.category_delete_get);
router.post("/:category/delete", category_controller.category_delete_post);
// item functions
router.get("/:category/:item", item_controller.index);
router.get("/:category/createitem", item_controller.item_create_get);
router.post("/:category/createitem", item_controller.item_create_post);
router.get("/:category/:item/update", item_controller.item_update_get);
router.post("/:category/:item/update", item_controller.item_update_post);
router.get("/:category/:item/delete", item_controller.item_delete_get);
router.post("/:category/:item/delete", item_controller.item_delete_post);
module.exports = router;

32
views/category.ejs Normal file
View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<title>Good Pickin's - <%= category.name %></title>
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
</head>
<body class="bg-dark text-light">
<div class="d-flex flex-column py-4 px-4 align-items-center">
<h1 class="mb-0"><%= category.name %></h1>
<p class="mb-0"><%= category.description %></p>
<div class="pb-4"></div>
<% items.forEach(item => { %>
<a
href="/<%= category.simpleName %>/<%= item._id %>"
type="button"
class="btn btn-outline-light px-4"
>
<%= item.name %> (<%= item.quantity %>)
</a>
<div class="pb-4"></div>
<% }) %>
<div class="pb-4"></div>
</div>
<footer
class="position-fixed bottom-0 border-light border-top w-100 py-2 d-flex justify-content-center bg-dark"
>
<a href="/" type="button" class="btn btn-outline-light px-4 py-1">
Back to Home
</a>
</footer>
</body>
</html>

3
views/error.ejs Normal file
View file

@ -0,0 +1,3 @@
<h1><%= message = 'Error!' %></h1>
<h2><%= error.status %></h2>
<pre><%= error.stack %></pre>

32
views/index.ejs Normal file
View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<title>Good Pickin's</title>
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
</head>
<body class="bg-dark text-light min-vh-100">
<div class="d-flex flex-column py-4 px-4 align-items-center">
<h1 class="mb-0">Welcome to Good Pickin's General Store!</h1>
<p class="mb-0">(formerly Chuck's)</p>
<p>est. 2023</p>
<div class="pb-4"></div>
<% categories.forEach(category => {%>
<a
href="/<%= category.simpleName %>"
type="button"
class="btn btn-outline-light px-4"
>
<%= category.name %> (<%= category.items.length %>)
</a>
<div class="pb-4"></div>
<% }) %>
<a
href="/createcategory"
type="button"
class="btn btn-outline-light px-4"
>
+
</a>
</div>
</body>
</html>

37
views/item.ejs Normal file
View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<title>Good Pickin's - <%= item.name %></title>
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
</head>
<body class="bg-dark text-light">
<div class="d-flex flex-column py-4 px-4 align-items-center">
<h1 class="mb-0"><%= item.name %></h1>
<p><%= item.description %></p>
<p class="mb-0"><%= item.quantity %> available at $<%= item.price %></p>
<div class="pb-4"></div>
<a
href="/<%= category.simpleName %>/<%= item._id %>/update"
type="button"
class="btn btn-outline-light px-4"
>
Edit this item
</a>
<div class="pb-4"></div>
<a
href="/<%= category.simpleName %>/<%= item._id %>/delete"
type="button"
class="btn btn-outline-light px-4"
>
Delete this item
</a>
</div>
<footer
class="position-fixed bottom-0 border-light border-top w-100 py-2 d-flex justify-content-center"
>
<a href="/" type="button" class="btn btn-outline-light px-4 py-1">
Back to Home
</a>
</footer>
</body>
</html>