progress update

everything tested and functional except direct messages
This commit is contained in:
ak 2023-09-19 22:54:57 -07:00
parent c70c9b3473
commit d5bf0927a6
23 changed files with 2807 additions and 0 deletions

65
.gitignore vendored Normal file
View file

@ -0,0 +1,65 @@
# 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
Spacefile

55
app.js Normal file
View file

@ -0,0 +1,55 @@
const express = require("express");
const path = require("path");
const session = require("express-session");
const passport = require("passport");
const passportInit = require("./passportInit.js");
const mongoose = require("mongoose");
require("dotenv").config();
// make app
const app = express();
// get db
const mongoDB = `mongodb+srv://${process.env.USER}:${process.env.PASS}@odin.eftl02o.mongodb.net/messageBoard?retryWrites=true&w=majority`;
// or throw error
main().catch((err) => console.log(err));
async function main() {
await mongoose.connect(mongoDB);
}
// set paths
app.use(
"/styles/css",
express.static(path.join(__dirname, "node_modules/bootstrap/dist/css"))
);
app.use(express.static(path.join(__dirname, "public")));
// view engine setup
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "ejs");
// passport setup
passportInit();
app.use(
session({
secret: process.env.SECRET_KEY,
resave: false,
saveUninitialized: true,
})
);
app.use(passport.initialize());
app.use(passport.session());
// URL parsing
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// routing
const indexRouter = require("./routes/index.js");
const userRouter = require("./routes/user.js");
const messageRouter = require("./routes/message.js");
app.use("/", indexRouter);
app.use("/user", userRouter);
app.use("/msg", messageRouter);
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);
}

20
controllers/index.js Normal file
View file

@ -0,0 +1,20 @@
const Message = require("../models/message.js");
const asyncHandler = require("express-async-handler");
exports.index = asyncHandler(async (req, res, next) => {
// if user is logged in
if (req.user) {
// gets messages addressed to user from database
const DMs = await Message.find({ to: req.user.username }).lean().exec();
// gets messages addressed to all chat from database
const allChat = await Message.find({ to: "all" }).lean().exec();
// render index page
return res.render("index", {
DMs: DMs,
allChat: allChat,
user: req.user,
});
}
// otherwise redirect to login page
res.redirect("/user/login");
});

102
controllers/message.js Normal file
View file

@ -0,0 +1,102 @@
const Message = require("../models/message.js");
const asyncHandler = require("express-async-handler");
const { body, validationResult } = require("express-validator");
const { default: mongoose } = require("mongoose");
exports.new_get = (req, res, next) => {
res.render("newmsg", {
user: req.user,
errors: false,
});
};
exports.new_post = [
// Validate and sanitize fields
body("to", "Please enter recipient!").optional().trim().escape(),
body("text", "Please enter message!").trim().isLength({ min: 1 }),
// Process request after validation and sanitization.
asyncHandler(async (req, res, next) => {
// Extract the validation errors from a request.
const errors = validationResult(req);
// if there are validation errors
if (!errors.isEmpty()) {
// Render the creation form again with sanitized values/error messages.
res.render("newmsg", {
errors: errors.array(),
user: req.user,
});
return;
}
// check for to field entry - if empty, set to "all"
let to;
req.body.to.length > 0 ? (to = req.body.to) : (to = "all");
// else data from form is valid.
let message = new Message({
to: to,
from: req.user.username,
date: new Date(),
updated: new Date(),
text: req.body.text,
_id: new mongoose.Types.ObjectId(),
});
await message.save();
// saved. Redirect to index.
res.redirect(`/`);
}),
];
exports.get = asyncHandler(async (req, res, next) => {
// find message
const message = await Message.findById(req.params.messageID).lean().exec();
res.render("message", {
message: message,
user: req.user,
});
});
exports.put_get = asyncHandler(async (req, res, next) => {
const message = await Message.findById(req.params.messageID).lean().exec();
res.render("editmsg", {
user: req.user,
message: message,
errors: false,
});
});
exports.put = [
// Validate and sanitize fields
body("text", "Please enter updated message!").trim().isLength({ min: 1 }),
asyncHandler(async (req, res, next) => {
// find message
const dbMessage = await Message.findById(req.params.messageID)
.lean()
.exec();
const message = new Message({
to: dbMessage.to,
from: dbMessage.from,
date: dbMessage.date,
updated: new Date(),
text: req.body.text,
_id: dbMessage._id,
});
await Message.findByIdAndUpdate(dbMessage._id, message, {});
// saved. Redirect to message page.
res.redirect(`/msg/${message._id}`);
}),
];
exports.delete = asyncHandler(async (req, res, next) => {
// delete message from database
await Message.findByIdAndDelete(req.params.messageID);
// redirect home
res.redirect("/");
});

172
controllers/user.js Normal file
View file

@ -0,0 +1,172 @@
const User = require("../models/user.js");
const asyncHandler = require("express-async-handler");
const { body, validationResult } = require("express-validator");
const { default: mongoose } = require("mongoose");
const bcrypt = require("bcryptjs");
const passport = require("passport");
exports.new_get = (req, res, next) => {
res.render("newuser", { errors: false });
};
exports.new_post = [
// Validate and sanitize fields
body("username", "Please enter a username!")
.trim()
.escape()
.isLength({ min: 1 })
.custom(async (value) => {
const found = await User.findOne({ username: value }).lean().exec();
if (found) throw new Error("User already exists!");
else return value;
}),
body("password", "Please enter a password!")
.trim()
.escape()
.isLength({ min: 1 }),
body("confirm", "Please confirm password!")
.trim()
.escape()
.isLength({ min: 1 })
.custom((value, { req }) => {
if (value !== req.body.password)
throw new Error("Passwords don't match!");
else return value;
}),
// Process request after validation and sanitization.
asyncHandler(async (req, res, next) => {
// Extract the validation errors from a request.
const errors = validationResult(req);
// if there are validation errors
if (!errors.isEmpty()) {
// Render the creation form again with sanitized values/error messages.
res.render("newuser", {
errors: errors.array(),
});
return;
}
// else data is valid
// create new user with hashed password
const user = new User({
username: req.body.username,
password: await bcrypt.hash(req.body.password, 10),
_id: new mongoose.Types.ObjectId(),
});
await user.save();
// saved. Redirect to login page
res.redirect(`/user/login`);
}),
];
exports.login_get = (req, res, next) => {
res.render("login", { error: false });
};
exports.login_post = (req, res, next) => {
passport.authenticate("local", (err, user, options) => {
if (!user) {
return res.render("login", { error: options.message });
}
req.login(user, (error) => {
if (error) {
console.log(error);
}
return res.redirect("/");
});
})(req, res, next);
};
exports.logout = (req, res, next) => {
req.logout(() => {
res.redirect("/");
});
};
exports.get = asyncHandler(async (req, res, next) => {
const user = await User.findById(req.params.userID).exec();
res.render("user", {
user: user,
currentUser: req.user,
});
});
exports.put_get = asyncHandler(async (req, res, next) => {
const user = await User.findById(req.params.userID).lean().exec();
res.render("edituser", {
user: user,
});
});
exports.put = [
// Validate and sanitize fields
// username cannot be changed for security/impersonation reasons
body("oldpassword", "Please enter current password to authorize changes!")
.trim()
.escape()
.isLength({ min: 1 }),
body("password", "Please enter new password!").optional().trim().escape(),
body("confirm", "Please confirm new password!")
.optional()
.trim()
.escape()
.custom((value, { req }) => {
if (value !== req.body.password)
throw new Error("Passwords don't match!");
else return value;
}),
// tbd: allow profile pic changes with multer
// Process request after validation and sanitization.
asyncHandler(async (req, res, next) => {
// Extract the validation errors from a request.
const errors = validationResult(req);
// if there are validation errors
if (!errors.isEmpty()) {
// Render the creation form again with sanitized values/error messages.
res.render("edituser", {
errors: errors.array(),
});
return;
}
// else data is valid
// find existing user in DB
const dbUser = User.findById(req.params.userID).lean().exec();
// make new user
const user = new User({
username: dbUser.username,
password: await bcrypt.hash(req.body.password, 10),
_id: dbUser._id,
});
// update user in DB to new user
await User.findByIdAndUpdate(dbUser._id, user, {});
// saved. logout and redirect to log in again
req.logout((err) => {
if (err) {
return next(err);
}
res.redirect("/user/login");
});
}),
];
exports.delete = asyncHandler(async (req, res, next) => {
await User.findByIdAndDelete(req.params.userID);
req.logout((err) => {
if (err) {
return next(err);
}
res.redirect("/user/login");
});
});

21
models/message.js Normal file
View file

@ -0,0 +1,21 @@
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const MessageSchema = new Schema({
to: { type: String, required: true },
from: { type: String, required: true },
date: { type: Date, required: true },
updated: { type: Date, required: true },
text: { type: String, required: true },
_id: { type: mongoose.ObjectId, required: true },
});
// Virtual for message URL
MessageSchema.virtual("url").get(function () {
// We don't use an arrow function as we'll need the this object
return `/msg/${this._id}`;
});
// Export model
module.exports = mongoose.model("Message", MessageSchema);

17
models/user.js Normal file
View file

@ -0,0 +1,17 @@
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const UserSchema = new Schema({
username: { type: String, required: true },
password: { type: String, required: true },
_id: { type: mongoose.ObjectId, required: true },
});
// Virtual for user URL
UserSchema.virtual("url").get(function () {
// We don't use an arrow function as we'll need the this object
return `/user/${this._id}`;
});
// Export model
module.exports = mongoose.model("User", UserSchema);

1712
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

24
package.json Normal file
View file

@ -0,0 +1,24 @@
{
"name": "express-members-only",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"bcryptjs": "^2.4.3",
"bootstrap": "^5.3.1",
"debug": "~2.6.9",
"dotenv": "^16.3.1",
"ejs": "^3.1.9",
"ejs-lint": "^2.0.0",
"express": "^4.18.2",
"express-async-handler": "^1.2.0",
"express-session": "^1.17.3",
"express-validator": "^7.0.1",
"mongoose": "^7.5.1",
"passport": "^0.6.0",
"passport-local": "^1.0.0",
"passport-local-mongoose": "^8.0.0"
}
}

35
passportInit.js Normal file
View file

@ -0,0 +1,35 @@
const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const bcrypt = require("bcryptjs");
const User = require("./models/user.js");
const passportInit = () => {
passport.use(
"local",
new LocalStrategy(async function verify(username, password, done) {
const user = await User.findOne({ username: username }).lean().exec();
if (!user) {
return done(null, false, { message: "Incorrect username" });
}
const match = await bcrypt.compare(password, user.password);
if (!match) {
return done(null, false, { message: "Incorrect password" });
}
return done(null, user);
})
);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser(async (user, done) => {
try {
done(null, user);
} catch (err) {
done(err);
}
});
};
module.exports = passportInit;

View file

@ -0,0 +1,9 @@
@import url("https://fonts.googleapis.com/css2?family=Orbitron&display=swap");
.logotext {
color: springgreen;
text-shadow: 1px 1px 2px black;
font-family: "Orbitron", sans-serif;
letter-spacing: 1rem;
text-decoration: none;
}

8
routes/index.js Normal file
View file

@ -0,0 +1,8 @@
const express = require("express");
const router = express.Router();
const index_controller = require("../controllers/index.js");
// home page
router.get("/", index_controller.index);
module.exports = router;

19
routes/message.js Normal file
View file

@ -0,0 +1,19 @@
const express = require("express");
const router = express.Router();
const message_controller = require("../controllers/message.js");
// create URL has to go first before :matchers
router.get("/new", message_controller.new_get);
router.post("/new", message_controller.new_post); // C
// message functions
router.get("/:messageID", message_controller.get); // R
router.put("/:messageID", message_controller.put); // U
router.get("/:messageID/edit", message_controller.put_get);
router.post("/:messageID/edit", message_controller.put);
router.delete("/:messageID", message_controller.delete); //D
router.get("/:messageID/delete", message_controller.delete);
module.exports = router;

25
routes/user.js Normal file
View file

@ -0,0 +1,25 @@
const express = require("express");
const router = express.Router();
const user_controller = require("../controllers/user.js");
// user login page
router.get("/login", user_controller.login_get);
router.post("/login", user_controller.login_post);
// new user page
router.get("/new", user_controller.new_get);
router.post("/new", user_controller.new_post); // C
// user profile page - functions
router.get("/:userID", user_controller.get); // R
router.put("/:userID", user_controller.put); // U
router.get("/:userID/edit", user_controller.put_get);
router.post("/:userID/edit", user_controller.put);
router.delete("/:userID", user_controller.delete); // D
router.get("/:userID/delete", user_controller.delete);
router.get("/:userID/logout", user_controller.logout);
module.exports = router;

45
views/editmsg.ejs Normal file
View file

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<title>b0ard - Editing Message</title>
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
<link rel="stylesheet" href="/stylesheets/index.css" />
</head>
<body class="bg-dark text-light">
<header
class="d-flex w-100 align-items-center justify-content-between border-bottom mb-2 border-light py-2 px-4 w-100"
>
<a href="/" class="logotext p-0 m-0 fs-1">b0ard</a>
<a
href="/user/<%= user._id %>"
type="button"
class="btn btn-outline-light px-4 py-2"
>
<%= user.username %>
</a>
</header>
<div class="d-flex flex-column py-4 px-4 align-items-center">
<h2 class="mb-4">Editing Message</h2>
<form class="d-flex flex-column align-items-center w-50" method="post">
<div class="input-group mb-4" data-bs-theme="dark">
<span class="input-group-text" id="text-desc">Message:</span>
<textarea
class="form-control"
aria-label="text"
aria-describedby="text-desc"
name="text"
>
<%= message.text.trim() %></textarea
>
</div>
<button type="submit" class="btn btn-outline-light px-4 py-2">
Submit
</button>
<div class="pb-4"></div>
<a href="delete" type="button" class="btn btn-danger px-4 py-2">
x Delete Message
</a>
</form>
</div>
</body>
</html>

70
views/edituser.ejs Normal file
View file

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html>
<head>
<title>b0ard - Editing <%= user.username %></title>
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
<link rel="stylesheet" href="/stylesheets/index.css" />
</head>
<body class="bg-dark text-light">
<header
class="d-flex w-100 align-items-center justify-content-between border-bottom mb-2 border-light py-2 px-4 w-100"
>
<a href="/" class="logotext p-0 m-0 fs-1">b0ard</a>
<a
href="/user/<%= user._id %>"
type="button"
class="btn btn-outline-light px-4 py-2"
>
<%= user.username %>
</a>
</header>
<div class="d-flex flex-column py-4 px-4 align-items-center">
<h2 class="mb-4">Editing <%= user.username %></h2>
<form class="d-flex flex-column align-items-center w-50" method="post">
<div class="input-group mb-4" data-bs-theme="dark">
<span class="input-group-text" id="oldpassword-desc"
>Current Password:</span
>
<input
type="password"
class="form-control"
aria-label="current password"
aria-describedby="oldpassword-desc"
name="oldpassword"
/>
</div>
<div class="input-group mb-4" data-bs-theme="dark">
<span class="input-group-text" id="password-desc"
>New Password (optional):</span
>
<input
type="password"
class="form-control"
aria-label="new password"
aria-describedby="password-desc"
name="password"
/>
</div>
<div class="input-group mb-4" data-bs-theme="dark">
<span class="input-group-text" id="confirm-desc"
>Confirm New Password (optional):</span
>
<input
type="password"
class="form-control"
aria-label="confirm password"
aria-describedby="confirm-desc"
name="confirm"
/>
</div>
<button type="submit" class="btn btn-outline-light px-4 py-2">
Submit
</button>
<div class="pb-4"></div>
<a href="delete" type="button" class="btn btn-danger px-4 py-2">
Delete <%= user.username %>
</a>
</form>
</div>
</body>
</html>

65
views/index.ejs Normal file
View file

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html>
<head>
<title>b0ard</title>
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
<link rel="stylesheet" href="/stylesheets/index.css" />
</head>
<body class="bg-dark text-light min-vh-100">
<header
class="d-flex w-100 align-items-center justify-content-between border-bottom mb-2 border-light py-2 px-4 w-100"
>
<a href="/" class="logotext p-0 m-0 fs-1">b0ard</a>
<a
href="/user/<%= user._id %>"
type="button"
class="btn btn-outline-light px-4 py-2"
>
<%= user.username %>
</a>
</header>
<div class="d-flex flex-row p-4 w-100 justify-content-between">
<div class="d-flex flex-column w-50">
<center>
<h1 class="fs-4">Direct Messages:</h1>
</center>
<% DMs.forEach(DM => { %>
<a
href="/msg/<%= DM._id %>"
type="button"
class="btn btn-outline-light p-2"
>
<p class="p-0 m-0">From: <%= DM.from %></p>
<p class="p-0 m-0">Sent: <%= DM.date.toDateString() %></p>
<p>Last Updated: <%= DM.updated %></p>
<p><%= DM.text %></p>
</a>
<div class="pb-4"></div>
<% }) %>
</div>
<div class="d-flex flex-column w-50">
<center>
<h1 class="fs-4">Public Messages:</h1>
</center>
<% allChat.forEach(chat => { %>
<a
href="/msg/<%= chat._id %>"
type="button"
class="btn btn-outline-light p-2"
>
<p class="p-0 m-0">From: <%= chat.from %></p>
<p class="p-0 m-0">Sent: <%= chat.date.toDateString() %></p>
<p>Last Updated: <%= chat.updated.toDateString() %></p>
<p><%= chat.text %></p>
</a>
<div class="pb-4"></div>
<% }) %>
</div>
</div>
<center>
<a href="/msg/new" type="button" class="btn btn-outline-light px-4 py-2">
+ New Message
</a>
</center>
</body>
</html>

51
views/login.ejs Normal file
View file

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<title>b0ard - Sign In</title>
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
<link rel="stylesheet" href="/stylesheets/index.css" />
</head>
<body class="bg-dark text-light">
<% if (error) { %>
<p class="text-center bg-danger"><%= error %></p>
<% } %>
<div class="d-flex flex-column py-4 px-4 align-items-center">
<header>
<h1 class="logotext mb-4">b0ard</h1>
</header>
<h2 class="mb-4">Login</h2>
<form class="d-flex flex-column align-items-center w-50" method="post">
<div class="input-group mb-4" data-bs-theme="dark">
<span class="input-group-text" id="username-aria">Username:</span>
<input
type="text"
class="form-control"
aria-label="username"
aria-describedby="username-aria"
name="username"
/>
</div>
<div class="input-group mb-4" data-bs-theme="dark">
<span class="input-group-text" id="password-aria">Password:</span>
<input
type="password"
class="form-control"
aria-label="password"
aria-describedby="password-aria"
name="password"
/>
</div>
<button type="submit" class="btn btn-outline-light px-4 py-2">
Submit
</button>
</form>
<div class="pb-5"></div>
<div class="d-flex flex-column align-items-center">
<p class="mb-2">Don't have an account?</p>
<a href="/user/new" type="button" class="btn btn-success px-4">
New Account
</a>
</div>
</div>
</body>
</html>

40
views/message.ejs Normal file
View file

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title>b0ard - Message</title>
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
<link rel="stylesheet" href="/stylesheets/index.css" />
</head>
<body class="bg-dark text-light">
<header
class="d-flex w-100 align-items-center justify-content-between border-bottom mb-2 border-light py-2 px-4 w-100"
>
<a href="/" class="logotext p-0 m-0 fs-1">b0ard</a>
<a
href="/user/<%= user._id %>"
type="button"
class="btn btn-outline-light px-4 py-2"
>
<%= user.username %>
</a>
</header>
<div class="d-flex flex-column py-4 px-4 align-items-center">
<p>From: <%= message.from %></p>
<p>Sent: <%= message.date.toDateString() %></p>
<p>Last Updated: <%= message.updated.toDateString() %></p>
<div class="pb-4"></div>
<p>" <%= message.text %> "</p>
</div>
<% if (user.username === message.from) { %>
<center>
<a
href="/msg/<%= message._id %>/edit"
type="button"
class="btn btn-outline-light px-4 py-2"
>
Edit Message
</a>
</center>
<% } %>
</body>
</html>

52
views/newmsg.ejs Normal file
View file

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<title>b0ard - New Message</title>
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
<link rel="stylesheet" href="/stylesheets/index.css" />
</head>
<body class="bg-dark text-light">
<% if (errors) { errors.forEach(error => { %>
<p class="text-center bg-danger"><%= error.msg %></p>
<% }) } %>
<header
class="d-flex w-100 align-items-center justify-content-between border-bottom mb-2 border-light py-2 px-4 w-100"
>
<a href="/" class="logotext p-0 m-0 fs-1">b0ard</a>
<a
href="/user/<%= user._id %>"
type="button"
class="btn btn-outline-light px-4 py-2"
>
<%= user.username %>
</a>
</header>
<div class="d-flex flex-column py-4 px-4 align-items-center">
<h2 class="mb-4">New Message</h2>
<form class="d-flex flex-column align-items-center w-50" method="post">
<div class="input-group mb-4" data-bs-theme="dark">
<span class="input-group-text" id="to-desc">To:</span>
<input
type="text"
class="form-control"
aria-label="recipient"
aria-describedby="to-desc"
name="to"
/>
</div>
<div class="input-group mb-4" data-bs-theme="dark">
<span class="input-group-text" id="text-desc">Message:</span>
<textarea
class="form-control"
aria-label="text"
aria-describedby="text-desc"
name="text"
></textarea>
</div>
<button type="submit" class="btn btn-outline-light px-4 py-2">
Send Message
</button>
</form>
</div>
</body>
</html>

67
views/newuser.ejs Normal file
View file

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html>
<head>
<title>b0ard - New User</title>
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
<link rel="stylesheet" href="/stylesheets/index.css" />
</head>
<body class="bg-dark text-light">
<% if (errors) { errors.forEach(error => { %>
<p class="text-center bg-danger"><%= error.msg %></p>
<% }) } %>
<div class="d-flex flex-column py-4 px-4 align-items-center">
<header>
<h1 class="logotext mb-4">b0ard</h1>
</header>
<h2 class="mb-4">New User</h2>
<form class="d-flex flex-column align-items-center w-50" method="post">
<div class="input-group mb-4" data-bs-theme="dark">
<span class="input-group-text" id="username-desc">Username:</span>
<input
type="text"
class="form-control"
aria-label="username"
aria-describedby="username-desc"
name="username"
/>
</div>
<div class="input-group mb-4" data-bs-theme="dark">
<span class="input-group-text" id="password">Password:</span>
<input
type="password"
class="form-control"
aria-label="password"
aria-describedby="password"
name="password"
/>
</div>
<div class="input-group mb-4" data-bs-theme="dark">
<span class="input-group-text" id="confirm-desc"
>Confirm Password:</span
>
<input
type="password"
class="form-control"
aria-label="confirm password"
aria-describedby="confirm-desc"
name="confirm"
/>
</div>
<button type="submit" class="btn btn-outline-light px-4 py-2">
Submit
</button>
</form>
</div>
<footer
class="position-fixed bottom-0 border-light border-top w-100 py-2 d-flex justify-content-center bg-dark"
>
<a
href="/user/login"
type="button"
class="btn btn-outline-light px-4 py-1"
>
Back to Login
</a>
</footer>
</body>
</html>

43
views/user.ejs Normal file
View file

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<title>b0ard - <%= user.username %></title>
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
<link rel="stylesheet" href="/stylesheets/index.css" />
</head>
<body class="bg-dark text-light">
<header
class="d-flex w-100 align-items-center justify-content-between border-bottom mb-2 border-light py-2 px-4 w-100"
>
<a href="/" class="logotext p-0 m-0 fs-1">b0ard</a>
<a
href="/user/<%= user._id %>"
type="button"
class="btn btn-outline-light px-4 py-2"
>
<%= currentUser.username %>
</a>
</header>
<div class="d-flex flex-column py-4 px-4 align-items-center">
<h1><%= user.username %></h1>
<p>One day there will be avatars here</p>
<% if (user.username === currentUser.username) { %>
<a
href="/user/<%= user._id %>/edit"
type="button"
class="btn btn-outline-light px-4 py-2"
>
Edit User
</a>
<div class="pb-4"></div>
<a
href="/user/<%= user._id %>/logout"
type="button"
class="btn btn-danger px-4 py-2"
>
Log Out
</a>
<% } %>
</div>
</body>
</html>