progress update

image uploading works
hosted at deta space
This commit is contained in:
ak 2023-09-20 18:00:32 -07:00
parent d5bf0927a6
commit f36b48f4ce
15 changed files with 479 additions and 69 deletions

1
app.js
View file

@ -32,6 +32,7 @@ app.set("view engine", "ejs");
passportInit();
app.use(
session({
proxy: true,
secret: process.env.SECRET_KEY,
resave: false,
saveUninitialized: true,

View file

@ -1,13 +1,26 @@
const Message = require("../models/message.js");
const User = require("../models/user.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();
let DMs = await Message.find({ to: req.user.username }).lean().exec();
// replaces from field with actual user
for (let i = 0; i < DMs.length; i++) {
const user = await User.findOne({ username: DMs[i].from }).lean().exec();
DMs[i].from = user;
}
// gets messages addressed to all chat from database
const allChat = await Message.find({ to: "all" }).lean().exec();
// replaces from field with actual user
for (let z = 0; z < allChat.length; z++) {
const user = await User.findOne({ username: allChat[z].from })
.lean()
.exec();
allChat[z].from = user;
}
// render index page
return res.render("index", {
DMs: DMs,

View file

@ -1,4 +1,5 @@
const Message = require("../models/message.js");
const User = require("../models/user.js");
const asyncHandler = require("express-async-handler");
const { body, validationResult } = require("express-validator");
const { default: mongoose } = require("mongoose");
@ -7,6 +8,7 @@ exports.new_get = (req, res, next) => {
res.render("newmsg", {
user: req.user,
errors: false,
to: false,
});
};
@ -26,6 +28,7 @@ exports.new_post = [
res.render("newmsg", {
errors: errors.array(),
user: req.user,
to: false,
});
return;
}
@ -49,9 +52,20 @@ exports.new_post = [
}),
];
exports.respond_get = (req, res, next) => {
res.render("newmsg", {
user: req.user,
errors: false,
to: req.params.recipient,
});
};
exports.get = asyncHandler(async (req, res, next) => {
// find message
const message = await Message.findById(req.params.messageID).lean().exec();
let message = await Message.findById(req.params.messageID).lean().exec();
// replace from field with actual user
const user = await User.findOne({ username: message.from }).lean().exec();
message.from = user;
res.render("message", {
message: message,
user: req.user,

View file

@ -1,15 +1,25 @@
const fs = require("fs");
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");
const multer = require("multer");
const storage = multer.memoryStorage();
const upload = multer({
storage: storage,
limits: { fileSize: 1840000 },
});
exports.new_get = (req, res, next) => {
res.render("newuser", { errors: false });
};
exports.new_post = [
// this goes first as it replaces bodyparser to some extent
upload.single("avatar"),
// Validate and sanitize fields
body("username", "Please enter a username!")
.trim()
@ -48,14 +58,28 @@ exports.new_post = [
});
return;
}
if (!req.file) {
const error = {
msg: "Avatar not uploaded",
};
// add to errors array
errArr = [error];
// Render the creation form again with error message.
res.render("newuser", {
errors: errArr,
});
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(),
avatar: req.file.buffer,
});
// save to mongo
await user.save();
// saved. Redirect to login page
@ -99,34 +123,37 @@ exports.put_get = asyncHandler(async (req, res, next) => {
const user = await User.findById(req.params.userID).lean().exec();
res.render("edituser", {
user: user,
errors: false,
});
});
exports.put = [
// this goes first as it replaces bodyparser to some extent
upload.single("avatar"),
// 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 }),
.isLength({ min: 1 })
.custom(async (value, { req }) => {
const dbUser = await User.findById(req.params.userID).lean().exec();
const match = await bcrypt.compare(value, dbUser.password);
if (!match) {
return Promise.reject();
}
})
.withMessage("Incorrect password!"),
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) => {
// find existing user in DB
const dbUser = await User.findById(req.params.userID).lean().exec();
// Extract the validation errors from a request.
const errors = validationResult(req);
// if there are validation errors
@ -134,23 +161,66 @@ exports.put = [
// Render the creation form again with sanitized values/error messages.
res.render("edituser", {
errors: errors.array(),
user: dbUser,
});
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({
// ascertain which fields have been changed by the user
let password = (image = false);
let user = null;
// check for password field
if (
req.body.password &&
req.body.password != "" &&
req.body.password != "null" &&
req.body.password !== null
) {
password = true;
}
// check for image
if (req.file) image = true;
// if there is a password
if (password) {
// if there is an image
if (image) {
// make new user with new avatar and password
user = new User({
username: dbUser.username,
password: await bcrypt.hash(req.body.password, 10),
_id: dbUser._id,
avatar: req.file.buffer,
});
}
// if there is not an image
else {
// make new user with new password only
user = new User({
username: dbUser.username,
password: await bcrypt.hash(req.body.password, 10),
_id: dbUser._id,
avatar: dbUser.avatar,
});
}
}
// if there is not a password but there is an image
else if (image) {
// make new user with new image only
user = new User({
username: dbUser.username,
password: dbUser.password,
_id: dbUser._id,
avatar: req.file.buffer,
});
}
// if nothing was passed, user stays null
// update user in DB to new user
// update user in DB to new user if needed
if (user != null) {
await User.findByIdAndUpdate(dbUser._id, user, {});
// saved. logout and redirect to log in again
req.logout((err) => {
if (err) {
@ -158,6 +228,21 @@ exports.put = [
}
res.redirect("/user/login");
});
return;
}
// if nothing updated, redirect to edit page with error
const error = {
msg: "Nothing updated",
};
// add to errors array
errArr = [error];
// render error
res.render("edituser", {
user: dbUser,
errors: errArr,
});
return;
}),
];

View file

@ -5,6 +5,7 @@ const UserSchema = new Schema({
username: { type: String, required: true },
password: { type: String, required: true },
_id: { type: mongoose.ObjectId, required: true },
avatar: { type: Buffer, required: true },
});
// Virtual for user URL

145
package-lock.json generated
View file

@ -19,6 +19,7 @@
"express-session": "^1.17.3",
"express-validator": "^7.0.1",
"mongoose": "^7.5.1",
"multer": "^1.4.5-lts.1",
"passport": "^0.6.0",
"passport-local": "^1.0.0",
"passport-local-mongoose": "^8.0.0"
@ -157,6 +158,11 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/append-field": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@ -246,6 +252,22 @@
"node": ">=14.20.1"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"dependencies": {
"streamsearch": "^1.1.0"
},
"engines": {
"node": ">=10.16.0"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -311,6 +333,20 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/concat-stream": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
"engines": [
"node >= 0.8"
],
"dependencies": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^2.2.2",
"typedarray": "^0.0.6"
}
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -343,6 +379,11 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -858,6 +899,11 @@
"node": ">=0.12.0"
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
},
"node_modules/jake": {
"version": "10.8.7",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz",
@ -991,6 +1037,25 @@
"node": "*"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/mongodb": {
"version": "5.8.1",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.8.1.tgz",
@ -1111,6 +1176,23 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/multer": {
"version": "1.4.5-lts.1",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
"integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
"dependencies": {
"append-field": "^1.0.0",
"busboy": "^1.0.0",
"concat-stream": "^1.5.2",
"mkdirp": "^0.5.4",
"object-assign": "^4.1.1",
"type-is": "^1.6.4",
"xtend": "^4.0.0"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@ -1119,6 +1201,14 @@
"node": ">= 0.6"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
@ -1232,6 +1322,11 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -1320,6 +1415,25 @@
"resolved": "https://registry.npmjs.org/read-input/-/read-input-0.3.1.tgz",
"integrity": "sha512-J1ZkWCnB4altU7RTe+62PSfa21FrEtfKyO9fuqR3yP8kZku3nIwaw2Krj383JC7egAIl5Zyz2w+EOu9uXH5HZw=="
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/readable-stream/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -1503,6 +1617,27 @@
"node": ">= 0.8"
}
},
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/string_decoder/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@ -1588,6 +1723,11 @@
"node": ">= 0.6"
}
},
"node_modules/typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
},
"node_modules/uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
@ -1607,6 +1747,11 @@
"node": ">= 0.8"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",

View file

@ -17,6 +17,7 @@
"express-session": "^1.17.3",
"express-validator": "^7.0.1",
"mongoose": "^7.5.1",
"multer": "^1.4.5-lts.1",
"passport": "^0.6.0",
"passport-local": "^1.0.0",
"passport-local-mongoose": "^8.0.0"

View file

@ -6,6 +6,8 @@ const message_controller = require("../controllers/message.js");
router.get("/new", message_controller.new_get);
router.post("/new", message_controller.new_post); // C
router.get("/new/:recipient", message_controller.respond_get);
// message functions
router.get("/:messageID", message_controller.get); // R

View file

@ -15,7 +15,14 @@
type="button"
class="btn btn-outline-light px-4 py-2"
>
<div class="d-flex align-items-center gap-3">
<img
src="data:image/image/png;base64,
<%=user.avatar.toString('base64')%>"
width="24px"
/>
<%= user.username %>
</div>
</a>
</header>
<div class="d-flex flex-column py-4 px-4 align-items-center">

View file

@ -6,6 +6,9 @@
<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"
>
@ -15,15 +18,27 @@
type="button"
class="btn btn-outline-light px-4 py-2"
>
<div class="d-flex align-items-center gap-3">
<img
src="data:image/image/png;base64,
<%=user.avatar.toString('base64')%>"
width="24px"
/>
<%= user.username %>
</div>
</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">
<p>Required fields are labeled *</p>
<form
class="d-flex flex-column align-items-center w-50"
method="post"
enctype="multipart/form-data"
>
<div class="input-group mb-4" data-bs-theme="dark">
<span class="input-group-text" id="oldpassword-desc"
>Current Password:</span
>Current Password*:</span
>
<input
type="password"
@ -34,9 +49,7 @@
/>
</div>
<div class="input-group mb-4" data-bs-theme="dark">
<span class="input-group-text" id="password-desc"
>New Password (optional):</span
>
<span class="input-group-text" id="password-desc">New Password:</span>
<input
type="password"
class="form-control"
@ -46,15 +59,13 @@
/>
</div>
<div class="input-group mb-4" data-bs-theme="dark">
<span class="input-group-text" id="confirm-desc"
>Confirm New Password (optional):</span
>
<span class="input-group-text" id="avatar-desc">New Avatar:</span>
<input
type="password"
type="file"
class="form-control"
aria-label="confirm password"
aria-describedby="confirm-desc"
name="confirm"
aria-label="avatar"
aria-describedby="avatar-desc"
name="avatar"
/>
</div>
<button type="submit" class="btn btn-outline-light px-4 py-2">

View file

@ -15,11 +15,18 @@
type="button"
class="btn btn-outline-light px-4 py-2"
>
<div class="d-flex align-items-center gap-4">
<img
src="data:image/image/png;base64,
<%=user.avatar.toString('base64')%>"
width="24px"
/>
<%= user.username %>
</div>
</a>
</header>
<div class="d-flex flex-row p-4 w-100 justify-content-between">
<div class="d-flex flex-column w-50">
<div class="d-flex flex-column w-50 pe-3">
<center>
<h1 class="fs-4">Direct Messages:</h1>
</center>
@ -27,17 +34,37 @@
<a
href="/msg/<%= DM._id %>"
type="button"
class="btn btn-outline-light p-2"
class="btn btn-outline-light py-4"
>
<div class="d-flex align-items-between">
<div
class="d-flex flex-column align-items-center justify-content-center w-50"
>
<div class="d-flex align-items-center gap-4">
<img
src="data:image/image/png;base64,
<%=DM.from.avatar.toString('base64')%>"
width="48px"
/>
<p class="p-0 m-0 fs-3"><%= DM.from.username %></p>
</div>
</div>
<div
class="d-flex flex-column w-50 align-items-center justify-content-center"
>
<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>
<p class="p-0 m-0">
Last Updated: <%= DM.updated.toDateString() %>
</p>
</div>
</div>
<div class="pb-3"></div>
<p class="p-0 m-0"><%= DM.text %></p>
</a>
<div class="pb-4"></div>
<% }) %>
</div>
<div class="d-flex flex-column w-50">
<div class="d-flex flex-column w-50 ps-3">
<center>
<h1 class="fs-4">Public Messages:</h1>
</center>
@ -45,12 +72,32 @@
<a
href="/msg/<%= chat._id %>"
type="button"
class="btn btn-outline-light p-2"
class="btn btn-outline-light py-4"
>
<div class="d-flex align-items-between">
<div
class="d-flex flex-column align-items-center justify-content-center w-50"
>
<div class="d-flex align-items-center gap-3">
<img
src="data:image/image/png;base64,
<%=chat.from.avatar.toString('base64')%>"
width="48px"
/>
<p class="p-0 m-0 fs-3"><%= chat.from.username %></p>
</div>
</div>
<div
class="d-flex flex-column w-50 align-items-center justify-content-center"
>
<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>
<p class="p-0 m-0">
Last Updated: <%= chat.updated.toDateString() %>
</p>
</div>
</div>
<div class="pb-3"></div>
<p class="p-0 m-0"><%= chat.text %></p>
</a>
<div class="pb-4"></div>
<% }) %>

View file

@ -15,17 +15,44 @@
type="button"
class="btn btn-outline-light px-4 py-2"
>
<div class="d-flex align-items-center gap-3">
<img
src="data:image/image/png;base64,
<%=user.avatar.toString('base64')%>"
width="24px"
/>
<%= user.username %>
</div>
</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 class="d-flex align-items-around w-100">
<div
class="d-flex flex-column align-items-center justify-content-center w-50"
>
<div class="d-flex align-items-center gap-4">
<img
src="data:image/image/png;base64,
<%=message.from.avatar.toString('base64')%>"
width="48px"
/>
<p class="p-0 m-0 fs-3"><%= message.from.username %></p>
</div>
<% if (user.username === message.from) { %>
</div>
<div
class="d-flex flex-column w-50 align-items-center justify-content-center"
>
<p class="p-0 m-0">Sent: <%= message.date.toDateString() %></p>
<p class="p-0 m-0">
Last Updated: <%= message.updated.toDateString() %>
</p>
</div>
</div>
<div class="pb-5"></div>
<p class="p-0 m-0">" <%= message.text %> "</p>
</div>
<div class="pb-4"></div>
<% if (user.username === message.from.username) { %>
<center>
<a
href="/msg/<%= message._id %>/edit"
@ -35,6 +62,16 @@
Edit Message
</a>
</center>
<% } else { %>
<center>
<a
href="/msg/new/<%= message.from.username %>"
type="button"
class="btn btn-outline-light px-4 py-2"
>
Respond
</a>
</center>
<% } %>
</body>
</html>

View file

@ -18,7 +18,14 @@
type="button"
class="btn btn-outline-light px-4 py-2"
>
<div class="d-flex align-items-center gap-3">
<img
src="data:image/image/png;base64,
<%=user.avatar.toString('base64')%>"
width="24px"
/>
<%= user.username %>
</div>
</a>
</header>
<div class="d-flex flex-column py-4 px-4 align-items-center">
@ -26,6 +33,16 @@
<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>
<% if (to) { %>
<input
type="text"
class="form-control"
aria-label="recipient"
aria-describedby="to-desc"
name="to"
value="<%= to %>"
/>
<% } else { %>
<input
type="text"
class="form-control"
@ -33,6 +50,7 @@
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>

View file

@ -14,7 +14,11 @@
<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">
<form
class="d-flex flex-column align-items-center w-50"
method="post"
enctype="multipart/form-data"
>
<div class="input-group mb-4" data-bs-theme="dark">
<span class="input-group-text" id="username-desc">Username:</span>
<input
@ -47,6 +51,16 @@
name="confirm"
/>
</div>
<div class="input-group mb-4" data-bs-theme="dark">
<span class="input-group-text" id="avatar-desc">Avatar:</span>
<input
type="file"
class="form-control"
aria-label="avatar"
aria-describedby="avatar-desc"
name="avatar"
/>
</div>
<button type="submit" class="btn btn-outline-light px-4 py-2">
Submit
</button>

View file

@ -15,12 +15,26 @@
type="button"
class="btn btn-outline-light px-4 py-2"
>
<div class="d-flex align-items-center gap-3">
<img
src="data:image/image/png;base64,
<%=user.avatar.toString('base64')%>"
width="24px"
/>
<%= currentUser.username %>
</div>
</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>
<div class="d-flex align-items-center gap-5 mb-5">
<img
src="data:image/image/png;base64,
<%=user.avatar.toString('base64')%>"
width="120rem"
class="border-light border p-2 rounded"
/>
<h1 class="text-decoration-underline"><%= user.username %></h1>
</div>
<% if (user.username === currentUser.username) { %>
<a
href="/user/<%= user._id %>/edit"