progress update
everything tested and functional except direct messages
This commit is contained in:
parent
c70c9b3473
commit
d5bf0927a6
23 changed files with 2807 additions and 0 deletions
65
.gitignore
vendored
Normal file
65
.gitignore
vendored
Normal 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
55
app.js
Normal 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
90
bin/www
Executable 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
20
controllers/index.js
Normal 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
102
controllers/message.js
Normal 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
172
controllers/user.js
Normal 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
21
models/message.js
Normal 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
17
models/user.js
Normal 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
1712
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
24
package.json
Normal file
24
package.json
Normal 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
35
passportInit.js
Normal 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;
|
||||||
9
public/stylesheets/index.css
Normal file
9
public/stylesheets/index.css
Normal 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
8
routes/index.js
Normal 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
19
routes/message.js
Normal 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
25
routes/user.js
Normal 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
45
views/editmsg.ejs
Normal 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
70
views/edituser.ejs
Normal 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
65
views/index.ejs
Normal 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
51
views/login.ejs
Normal 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
40
views/message.ejs
Normal 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
52
views/newmsg.ejs
Normal 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
67
views/newuser.ejs
Normal 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
43
views/user.ejs
Normal 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>
|
||||||
Loading…
Add table
Reference in a new issue