FIrst commit
This commit is contained in:
55
.eslintrc.json
Normal file
55
.eslintrc.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:security/recommended"
|
||||
],
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2022
|
||||
},
|
||||
"rules": {
|
||||
"arrow-spacing": ["warn", { "before": true, "after": true }],
|
||||
"brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
|
||||
"comma-dangle": ["error", "always-multiline"],
|
||||
"comma-spacing": "error",
|
||||
"comma-style": "error",
|
||||
"curly": ["error", "multi-line", "consistent"],
|
||||
"dot-location": ["error", "property"],
|
||||
"handle-callback-err": "off",
|
||||
"indent": ["error", "tab"],
|
||||
"keyword-spacing": "error",
|
||||
"max-nested-callbacks": ["error", { "max": 4 }],
|
||||
"max-statements-per-line": ["error", { "max": 2 }],
|
||||
"no-console": "off",
|
||||
"no-empty-function": "error",
|
||||
"no-floating-decimal": "error",
|
||||
"no-inline-comments": "error",
|
||||
"no-lonely-if": "error",
|
||||
"no-multi-spaces": "error",
|
||||
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }],
|
||||
"no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }],
|
||||
"no-trailing-spaces": ["error"],
|
||||
"no-var": "error",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"prefer-const": "error",
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "always"],
|
||||
"space-before-blocks": "error",
|
||||
"space-before-function-paren": ["error", {
|
||||
"anonymous": "never",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}],
|
||||
"space-in-parens": "error",
|
||||
"space-infix-ops": "error",
|
||||
"space-unary-ops": "error",
|
||||
"spaced-comment": "error",
|
||||
"yoda": "error",
|
||||
"security/detect-non-literal-fs-filename": "off",
|
||||
"security/detect-non-literal-require": "off",
|
||||
"security/detect-object-injection": "off"
|
||||
}
|
||||
}
|
||||
175
.gitignore
vendored
Normal file
175
.gitignore
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Caches
|
||||
|
||||
.cache
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# 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
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://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/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
109
Classes/Computer.js
Normal file
109
Classes/Computer.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import { database } from './Database';
|
||||
|
||||
class Computer {
|
||||
constructor(id, brand, model, state, status) {
|
||||
this.id = id;
|
||||
this.brand = brand;
|
||||
this.model = model;
|
||||
this.status = status;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
async get (id) {
|
||||
try {
|
||||
const rows = await database.execute('SELECT * FROM computers WHERE id = ?', [id]);
|
||||
if (rows.length === 0) {
|
||||
throw new Error('Computer not found');
|
||||
} else {
|
||||
this.id = rows[0].id;
|
||||
this.brand = rows[0].brand;
|
||||
this.model = rows[0].model;
|
||||
this.status = rows[0].status;
|
||||
this.state = rows[0].state;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting computer:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async create() {
|
||||
try {
|
||||
const result = await database.execute(
|
||||
'INSERT INTO computers (brand, model, state, status) VALUES (?, ?, ?, ?)',
|
||||
[this.brand, this.model, this.state, this.status]
|
||||
);
|
||||
this.id = result.insertId;
|
||||
} catch (error) {
|
||||
console.error('Error creating computer:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async update() {
|
||||
try {
|
||||
await database.execute(
|
||||
'UPDATE computers SET brand = ?, model = ?, state = ?, status = ? WHERE id = ?',
|
||||
[this.brand, this.model, this.state, this.status, this.id]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error updating computer:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async delete() {
|
||||
try {
|
||||
await database.execute('DELETE FROM computers WHERE id = ?', [this.id]);
|
||||
} catch (error) {
|
||||
console.error('Error deleting computer:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Computer list class
|
||||
class Computers extends Array {
|
||||
|
||||
async getAll() {
|
||||
try {
|
||||
const rows = await database.execute('SELECT * FROM computers');
|
||||
this.length = 0; // Clear the existing array
|
||||
rows.forEach(row => {
|
||||
const computer = new Computer(row.id, row.brand, row.model, row.state, row.status);
|
||||
this.push(computer);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error getting computers:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async create(brand, model, state, status) {
|
||||
try {
|
||||
const computer = new Computer(null, brand, model, state, status);
|
||||
await computer.create();
|
||||
this.push(computer);
|
||||
} catch (error) {
|
||||
console.error('Error creating computer:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
try {
|
||||
const computerIndex = this.findIndex(computer => computer.id === id);
|
||||
if (computerIndex === -1) {
|
||||
throw new Error('Computer not found');
|
||||
}
|
||||
const computer = this[computerIndex];
|
||||
await computer.delete();
|
||||
this.splice(computerIndex, 1);
|
||||
} catch (error) {
|
||||
console.error('Error deleting computer:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { Computer, Computers };
|
||||
52
Classes/Database.js
Normal file
52
Classes/Database.js
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
const mysql = require('mysql2/promise');
|
||||
|
||||
class Database {
|
||||
constructor(config) {
|
||||
this.pool = mysql.createPool(config);
|
||||
}
|
||||
|
||||
async query(sql, params) {
|
||||
const connection = await this.pool.getConnection();
|
||||
try {
|
||||
const [rows] = await connection.query(sql, params);
|
||||
return rows;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
async execute(sql, params) {
|
||||
const connection = await this.pool.getConnection();
|
||||
try {
|
||||
const [result] = await connection.execute(sql, params);
|
||||
return result;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
async transaction(callback) {
|
||||
const connection = await this.pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
const result = await callback(connection);
|
||||
await connection.commit();
|
||||
return result;
|
||||
} catch (error) {
|
||||
await connection.rollback();
|
||||
throw error;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const database = new Database({
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASS,
|
||||
database: process.env.DB_NAME
|
||||
});
|
||||
|
||||
export { Database, database };
|
||||
15
README.md
Normal file
15
README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# inventory
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
bun run index.js
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.0.13. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||
83
Routes/computer.js
Normal file
83
Routes/computer.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import express from 'express';
|
||||
import { Computer, Computers } from '../Classes/Computer';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// GET all computers
|
||||
router.get('/', async (req, res) => {
|
||||
const computers = new Computers();
|
||||
await computers.getAll();
|
||||
try {
|
||||
|
||||
res.status(200).json(computers);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
// GET a computer by ID
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const computer = new Computer();
|
||||
await computer.get(req.params.id);
|
||||
if (computer.brand === null) {
|
||||
res.status(404).json({ error: 'Computer not found' });
|
||||
} else {
|
||||
res.status(200).json(computer);
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST a new computer
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const { brand, model, state, status } = req.body;
|
||||
if (!brand || !model || !state || !status) {
|
||||
res.status(400).json({ error: 'Bad Request' });
|
||||
}
|
||||
const computer = new Computer(null, brand, model, state, status);
|
||||
await computer.create();
|
||||
res.status(201).json({ message: 'Computer created successfully' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT (update) a computer by ID
|
||||
router.put('/:id', async (req, res) => {
|
||||
try {
|
||||
const computer = new Computer();
|
||||
await computer.get(req.params.id);
|
||||
if (computer.brand === null) {
|
||||
res.status(404).json({ error: 'Computer not found' });
|
||||
}
|
||||
const { brand, model, state, status } = req.body;
|
||||
computer.brand = brand;
|
||||
computer.model = model;
|
||||
computer.state = state;
|
||||
computer.status = status;
|
||||
await computer.update();
|
||||
res.status(200).json({ message: 'Computer updated successfully' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE a computer by ID
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const computer = new Computer();
|
||||
await computer.get(req.params.id);
|
||||
if (computer.brand === null) {
|
||||
res.status(404).json({ error: 'Computer not found' });
|
||||
}
|
||||
await computer.delete();
|
||||
res.status(200).json({ message: 'Computer deleted successfully' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
67
Routes/user.js
Normal file
67
Routes/user.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import express from 'express';
|
||||
import { database } from '../Classes/Database';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// POST to login using username and password
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
if (!username || !password) {
|
||||
res.status(400).json({ error: 'Bad Request' });
|
||||
}
|
||||
const rows = await database.execute('SELECT * FROM users WHERE username = ?', [username]);
|
||||
if (rows.length === 0) {
|
||||
res.status(401).json({ error: 'Unauthorized' });
|
||||
} else {
|
||||
const isPasswordValid = await Bun.password.verify(password, rows[0].password);
|
||||
if (!isPasswordValid) {
|
||||
res.status(401).json({ error: 'Unauthorized' });
|
||||
} else {
|
||||
const token = jwt.sign({ username, password }, process.env.SECRET);
|
||||
res.status(200).json({ token });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST to check if token is valid
|
||||
router.post('/verify', async (req, res) => {
|
||||
try {
|
||||
const token = req.body.token;
|
||||
if (!token) {
|
||||
res.status(401).json({ error: 'Unauthorized' });
|
||||
} else {
|
||||
jwt.verify(token, process.env.SECRET, async (err, decoded) => {
|
||||
if (err) res.status(401).json({ error: 'Unauthorized' });
|
||||
const rows = await database.execute('SELECT * FROM users WHERE username = ?', [decoded.username]);
|
||||
if (rows.length === 0) res.status(401).json({ error: 'Unauthorized' });
|
||||
const isPasswordValid = await Bun.password.verify(decoded.password, rows[0].password);
|
||||
if (!isPasswordValid) res.status(401).json({ error: 'Unauthorized' });
|
||||
res.status(200).json({ message: 'Authorized' });
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST to register a new user
|
||||
router.post('/register', async (req, res) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
if (!username || !password) {
|
||||
res.status(400).json({ error: 'Bad Request' });
|
||||
}
|
||||
const hashedPassword = await Bun.password.hash(password);
|
||||
await database.execute('INSERT INTO users (username, password) VALUES (?, ?)', [username, hashedPassword]);
|
||||
res.status(201).json({ message: 'User created successfully' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
33
database.sql
Normal file
33
database.sql
Normal file
@@ -0,0 +1,33 @@
|
||||
SET default_storage_engine = InnoDB;
|
||||
DROP DATABASE IF EXISTS `inventory`;
|
||||
CREATE DATABASE IF NOT EXISTS `inventory`
|
||||
CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
DROP USER IF EXISTS 'inventory';
|
||||
CREATE USER 'inventory'@'localhost' IDENTIFIED BY 'irCd3CH9MELH7Pa9KEHWxoz4S987iGWCP+JQZe4w+3s=';
|
||||
GRANT ALL PRIVILEGES ON inventory.* TO 'inventory'@'localhost';
|
||||
|
||||
USE `inventory`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS computers (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
brand VARCHAR(255) NOT NULL,
|
||||
model VARCHAR(255) NOT NULL,
|
||||
status VARCHAR(255) NOT NULL,
|
||||
state VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
INSERT INTO computers (brand, model, status, state) VALUES
|
||||
('Dell', 'Latitude 7400', 'Available', 'RAS'),
|
||||
('Dell', 'Latitude 7400', 'Available', 'RAS'),
|
||||
('Dell', 'Latitude 7400', 'Available', 'RAS');
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
username VARCHAR(255) NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB;
|
||||
40
index.js
Normal file
40
index.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import express from 'express';
|
||||
import morgan from 'morgan';
|
||||
import computerRouter from './Routes/computer.js';
|
||||
import userRouter from './Routes/user.js';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use(morgan('dev'));
|
||||
app.use(express.static('public'));
|
||||
|
||||
const authMiddleware = async (req, res, next) => {
|
||||
const token = req.headers.authorization;
|
||||
if (!token) {
|
||||
return res.status(401).json({ message: 'No auth token provided' });
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.SECRET);
|
||||
if (!decoded) {
|
||||
return res.status(401).json({ message: 'Invalid auth token' });
|
||||
}
|
||||
const rows = await database.execute('SELECT * FROM users WHERE username = ?', [decoded.username]);
|
||||
if (rows.length === 0) res.status(401).json({ error: 'Unauthorized' });
|
||||
const isPasswordValid = await Bun.password.verify(decoded.password, rows[0].password);
|
||||
if (!isPasswordValid) res.status(401).json({ error: 'Unauthorized' });
|
||||
next();
|
||||
} catch (error) {
|
||||
return res.status(401).json({ message: 'Invalid auth token' });
|
||||
}
|
||||
};
|
||||
|
||||
computerRouter.use(authMiddleware);
|
||||
app.use('/computer', computerRouter);
|
||||
app.use('/user', userRouter);
|
||||
|
||||
// Start the server
|
||||
app.listen(3000, () => {
|
||||
console.log('Server is running on port 3000');
|
||||
});
|
||||
22
jsconfig.json
Normal file
22
jsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext"],
|
||||
"module": "esnext",
|
||||
"target": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"moduleDetection": "force",
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true,
|
||||
"composite": true,
|
||||
"strict": true,
|
||||
"downlevelIteration": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "react-jsx",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"allowJs": true,
|
||||
"types": [
|
||||
"bun-types" // add Bun global
|
||||
]
|
||||
}
|
||||
}
|
||||
18
package.json
Normal file
18
package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "inventory",
|
||||
"module": "index.js",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"bun-types": "latest",
|
||||
"eslint": "^8.54.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"morgan": "^1.10.0",
|
||||
"mysql2": "^3.6.3"
|
||||
}
|
||||
}
|
||||
354
public/c/index.html
Normal file
354
public/c/index.html
Normal file
@@ -0,0 +1,354 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Computer Inventory</title>
|
||||
<style>
|
||||
|
||||
h1, h2 {
|
||||
text-align: center;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-right: 1px solid #ddd; /* Add border-right for separation */
|
||||
}
|
||||
th:last-child, td:last-child {
|
||||
border-right: none; /* Remove border-right for last column */
|
||||
}
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.edit-button {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 4px 8px; /* Reduce padding */
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
}
|
||||
.edit-button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.delete-button {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
padding: 4px 8px; /* Reduce padding */
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
}
|
||||
.delete-button:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
.add-button {
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
padding: 4px 8px; /* Reduce padding */
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 18px; /* Reduce font size */
|
||||
}
|
||||
.add-button:hover {
|
||||
background-color: #1976D2;
|
||||
}
|
||||
.computer-table-container {
|
||||
width: 95vw;
|
||||
padding: 16px;
|
||||
background-color: #f2f2f2;
|
||||
margin: 0 auto;
|
||||
border-radius: 8px; /* Add rounded border */
|
||||
}
|
||||
.computer-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.computer-table th, .computer-table td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-right: 1px solid #ddd; /* Add border-right for separation */
|
||||
}
|
||||
.computer-table th:last-child, .computer-table td:last-child {
|
||||
border-right: none; /* Remove border-right for last column */
|
||||
}
|
||||
.computer-table tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.computer-table input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.form-container {
|
||||
text-align: center; /* Center the form */
|
||||
}
|
||||
.form-container input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Computer Inventory</h1>
|
||||
<br>
|
||||
<div class="computer-table-container">
|
||||
<table class="computer-table">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Brand</th>
|
||||
<th>Model</th>
|
||||
<th>Status</th>
|
||||
<th>State</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</table>
|
||||
<br>
|
||||
<div class="form-container">
|
||||
<input type="text" id="brand" name="brand" placeholder="Brand" required>
|
||||
<input type="text" id="model" name="model" placeholder="Model" required>
|
||||
<input type="text" id="status" name="status" placeholder="Status" required>
|
||||
<input type="text" id="state" name="state" placeholder="State" required>
|
||||
<button class="add-button" onclick="addComputer()">Add Computer</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function createTable() {
|
||||
fetch(`http://localhost:3000/computer`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `${localStorage.getItem('token')}`
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const table = document.querySelector('.computer-table');
|
||||
table.innerHTML = '<tr><th>ID</th><th>Brand</th><th>Model</th><th>Status</th><th>State</th><th>Actions</th></tr>';
|
||||
data.forEach(computer => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${computer.id}</td>
|
||||
<td>${computer.brand}</td>
|
||||
<td>${computer.model}</td>
|
||||
<td>${computer.status}</td>
|
||||
<td>${computer.state}</td>
|
||||
<td>
|
||||
<button class="edit-button" onclick="editComputer(${computer.id})">Edit</button>
|
||||
<button class="delete-button" onclick="deleteComputer(${computer.id})">Delete</button>
|
||||
</td>
|
||||
`;
|
||||
document.querySelector('.computer-table').appendChild(row);
|
||||
});
|
||||
})
|
||||
.catch(error => console.error(error));
|
||||
}
|
||||
|
||||
// Function to handle edit button click
|
||||
function editComputer(id) {
|
||||
const table = document.querySelector('.computer-table');
|
||||
const rows = table.querySelectorAll('tr');
|
||||
let row;
|
||||
for(let i = 0; i < rows.length; i++) {
|
||||
if (rows[i].cells[0].innerText == id) {
|
||||
row = rows[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the cells of the row
|
||||
const cells = row.querySelectorAll('td');
|
||||
|
||||
// Replace the "Edit" button with a checkmark and a crossmark
|
||||
const editButton = row.querySelector('.edit-button');
|
||||
editButton.innerHTML = '✓'; // Checkmark button
|
||||
editButton.classList.add('confirm-button');
|
||||
editButton.setAttribute('onclick', `confirmEdit(${id})`);
|
||||
|
||||
const deleteButton = row.querySelector('.delete-button');
|
||||
deleteButton.innerHTML = '✕'; // Crossmark button
|
||||
deleteButton.classList.add('cancel-button');
|
||||
deleteButton.setAttribute('onclick', `cancelEdit(${id})`);
|
||||
|
||||
// Enable editing for each cell
|
||||
cells.forEach(cell => {
|
||||
if (cell.innerText == id || cell.innerText == "✓ ✕") return;
|
||||
const value = cell.innerText;
|
||||
cell.innerHTML = `<input type="text" value="${value}">`;
|
||||
});
|
||||
}
|
||||
|
||||
// Function to handle confirm edit button click
|
||||
function confirmEdit(id) {
|
||||
// Get the row of the computer being edited
|
||||
const table = document.querySelector('.computer-table');
|
||||
const rows = table.querySelectorAll('tr');
|
||||
let row;
|
||||
for(let i = 0; i < rows.length; i++) {
|
||||
if (rows[i].cells[0].innerText == id) {
|
||||
row = rows[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the cells of the row
|
||||
const cells = row.querySelectorAll('td');
|
||||
const values = [];
|
||||
cells.forEach(cell => {
|
||||
if (cell.innerText == id || cell.innerText == "✓ ✕") return;
|
||||
const value = cell.querySelector('input').value;
|
||||
values.push(value);
|
||||
cell.innerHTML = `${value}`;
|
||||
});
|
||||
|
||||
// Send a PUT request to update the computer
|
||||
fetch(`http://localhost:3000/computer/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `${localStorage.getItem('token')}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
brand: values[0],
|
||||
model: values[1],
|
||||
status: values[2],
|
||||
state: values[3]
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
createTable();
|
||||
} else {
|
||||
alert('Failed to update computer');
|
||||
console.error('Failed to update computer');
|
||||
}
|
||||
})
|
||||
.catch(error => console.error(error));
|
||||
}
|
||||
|
||||
// Function to handle cancel edit button click
|
||||
function cancelEdit(id) {
|
||||
createTable();
|
||||
}
|
||||
|
||||
// Function to handle delete button click
|
||||
function deleteComputer(id) {
|
||||
// Display a confirmation dialog
|
||||
const confirmDelete = confirm("Are you sure you want to delete this computer?");
|
||||
|
||||
if (confirmDelete) {
|
||||
// Send a DELETE request to the server to delete the computer
|
||||
fetch(`http://localhost:3000/computer/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `${localStorage.getItem('token')}`
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
// Refresh the table by re-fetching the computer data
|
||||
createTable();
|
||||
} else {
|
||||
alert('Failed to delete computer');
|
||||
console.error('Failed to delete computer');
|
||||
}
|
||||
})
|
||||
.catch(error => console.error(error));
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle add computer button click
|
||||
function addComputer() {
|
||||
const brand = document.getElementById('brand').value;
|
||||
const model = document.getElementById('model').value;
|
||||
const status = document.getElementById('status').value;
|
||||
const state = document.getElementById('state').value;
|
||||
|
||||
// Send a POST request to add the computer
|
||||
fetch('http://localhost:3000/computer', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `${localStorage.getItem('token')}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
brand: brand,
|
||||
model: model,
|
||||
status: status,
|
||||
state: state
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
createTable();
|
||||
document.getElementById('brand').value = '';
|
||||
document.getElementById('model').value = '';
|
||||
document.getElementById('status').value = '';
|
||||
document.getElementById('state').value = '';
|
||||
} else {
|
||||
alert('Failed to add computer');
|
||||
console.error('Failed to add computer');
|
||||
}
|
||||
})
|
||||
.catch(error => console.error(error));
|
||||
}
|
||||
|
||||
// check if user is logged in
|
||||
window.onload = checkIfLoggedIn();
|
||||
async function checkIfLoggedIn() {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
const valid = await checkIfTokenIsValid(token);
|
||||
if (!valid) {
|
||||
localStorage.removeItem('token');
|
||||
window.location.href = '../';
|
||||
}
|
||||
else {
|
||||
createTable();
|
||||
}
|
||||
}
|
||||
else {
|
||||
window.location.href = '../';
|
||||
}
|
||||
}
|
||||
|
||||
async function checkIfTokenIsValid(token) {
|
||||
// Make a POST request to the login endpoint
|
||||
return await fetch('http://localhost:3000/user/verify', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ token })
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('An error occurred');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
170
public/index.html
Normal file
170
public/index.html
Normal file
@@ -0,0 +1,170 @@
|
||||
<!-- FILEPATH: /home/emerald/Documents/IPR/Inventory/public/index.html -->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Login Page</title>
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.menu {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* CSS styles for inputs */
|
||||
input[type="text"],
|
||||
input[type="password"] {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
width: 200px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
padding: 10px 20px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button[type="submit"]:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
/* CSS styles for the selection menu */
|
||||
.selection-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.selection-menu h2 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.selection-menu button {
|
||||
margin: 10px;
|
||||
padding: 10px 20px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selection-menu button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<form id="loginForm">
|
||||
<h1>Login</h1>
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" required><br><br>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" required><br><br>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
<div id="menu" class="menu selection-menu">
|
||||
<h2>Menu</h2>
|
||||
<button onclick="redirectTo('computers')">Computers</button>
|
||||
<button onclick="redirectTo('tablets')">Tablets</button>
|
||||
<button onclick="redirectTo('projectors')">Projectors</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
const menu = document.getElementById('menu');
|
||||
|
||||
loginForm.addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
|
||||
// Make a POST request to the login endpoint
|
||||
fetch('http://localhost:3000/user/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ username, password })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Check if login is valid
|
||||
if (data.token) {
|
||||
localStorage.setItem('token', data.token);
|
||||
loginForm.style.display = 'none'; // Hide the login form
|
||||
menu.style.display = 'block';
|
||||
} else {
|
||||
alert('Invalid login');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('An error occurred');
|
||||
});
|
||||
});
|
||||
|
||||
function redirectTo(page) {
|
||||
// Redirect to the proper page based on the user's choice
|
||||
window.location.href = `/${page.charAt(0)}`;
|
||||
}
|
||||
|
||||
// check if user is logged in
|
||||
window.onload = checkIfLoggedIn();
|
||||
async function checkIfLoggedIn() {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
loginForm.style.display = 'none'; // Hide the login form
|
||||
menu.style.display = 'block';
|
||||
const valid = await checkIfTokenIsValid(token);
|
||||
if (!valid) {
|
||||
localStorage.removeItem('token');
|
||||
loginForm.style.display = 'block'; // Show the login form
|
||||
menu.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function checkIfTokenIsValid(token) {
|
||||
// Make a POST request to the login endpoint
|
||||
return await fetch('http://localhost:3000/user/verify', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ token })
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('An error occurred');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user