fully functioning
This commit is contained in:
parent
6dc16493e5
commit
fc87aaa0c8
20 changed files with 2624 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
|
||||
51
app.js
Normal file
51
app.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
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/membersOnly?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 router = require("./routes.js");
|
||||
app.use("/", router);
|
||||
|
||||
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);
|
||||
}
|
||||
12
controllers/index.js
Normal file
12
controllers/index.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
const Message = require("../models/message.js");
|
||||
const asyncHandler = require("express-async-handler");
|
||||
|
||||
exports.index = asyncHandler(async (req, res, next) => {
|
||||
// gets messages from database
|
||||
const messages = await Message.find().lean().exec();
|
||||
|
||||
res.render("index", {
|
||||
messages: messages,
|
||||
user: req.user,
|
||||
});
|
||||
});
|
||||
69
controllers/message.js
Normal file
69
controllers/message.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
const Message = require("../models/message.js");
|
||||
const asyncHandler = require("express-async-handler");
|
||||
const { body, validationResult } = require("express-validator");
|
||||
const { default: mongoose } = require("mongoose");
|
||||
|
||||
exports.index = asyncHandler(async (req, res, next) => {
|
||||
// gets message from database
|
||||
const message = await Message.findOne({ _id: req.params.message })
|
||||
.lean()
|
||||
.exec();
|
||||
res.render("message", {
|
||||
message: message,
|
||||
user: req.user,
|
||||
});
|
||||
});
|
||||
|
||||
exports.message_create_get = (req, res, next) => {
|
||||
res.render("createmessage");
|
||||
};
|
||||
|
||||
exports.message_create_post = [
|
||||
// Validate and sanitize title
|
||||
body("title", "Please enter Insight title!")
|
||||
.trim()
|
||||
.isLength({ min: 1 })
|
||||
.escape(),
|
||||
|
||||
// Validate and sanitize text
|
||||
body("message", "Please enter your Insight!")
|
||||
.trim()
|
||||
.isLength({ min: 1 })
|
||||
.escape(),
|
||||
|
||||
// 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("createmessage", {
|
||||
errors: errors.array(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Data from form is valid.
|
||||
else {
|
||||
// create new user with temporary password
|
||||
let message = new Message({
|
||||
title: req.body.title,
|
||||
date: new Date(),
|
||||
text: req.body.message,
|
||||
author: req.user.name,
|
||||
_id: new mongoose.Types.ObjectId(),
|
||||
});
|
||||
|
||||
await message.save();
|
||||
// saved. Redirect to home page.
|
||||
res.redirect(`/`);
|
||||
}
|
||||
}),
|
||||
];
|
||||
|
||||
exports.message_delete_get = asyncHandler(async (req, res, next) => {
|
||||
// delete message from database
|
||||
await Message.findByIdAndDelete(req.params.message);
|
||||
res.redirect("/");
|
||||
});
|
||||
165
controllers/user.js
Normal file
165
controllers/user.js
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
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");
|
||||
|
||||
exports.user_create_get = (req, res, next) => {
|
||||
res.render("createuser");
|
||||
};
|
||||
|
||||
exports.user_create_post = [
|
||||
// Validate and sanitize name
|
||||
body("name", "Please enter your name!").trim().isLength({ min: 1 }).escape(),
|
||||
|
||||
// Validate and sanitize username
|
||||
body("username", "Please enter your username!")
|
||||
.trim()
|
||||
.isLength({ min: 1 })
|
||||
.escape(),
|
||||
|
||||
// Validate and sanitize password
|
||||
body("password", "Please enter your password!")
|
||||
.trim()
|
||||
.isLength({ min: 1 })
|
||||
.escape(),
|
||||
|
||||
// Validate and sanitize password confirmation
|
||||
body("password_confirmation", "Please confirm your password!")
|
||||
.trim()
|
||||
.isLength({ min: 1 })
|
||||
.custom((value, { req }) => {
|
||||
if (value !== req.body.password)
|
||||
throw new Error("Passwords don't match!");
|
||||
else return value;
|
||||
})
|
||||
.escape(),
|
||||
|
||||
// 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("createuser", {
|
||||
errors: errors.array(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
// else data is valid
|
||||
// create new user with hashed password
|
||||
const user = new User({
|
||||
name: req.body.name,
|
||||
username: req.body.username,
|
||||
password: await bcrypt.hash(req.body.password, 10),
|
||||
isMember: false,
|
||||
isAdmin: false,
|
||||
_id: new mongoose.Types.ObjectId(),
|
||||
});
|
||||
|
||||
await user.save();
|
||||
// saved. Redirect to home page.
|
||||
res.redirect(`/`);
|
||||
}),
|
||||
];
|
||||
|
||||
exports.user_signin_get = (req, res, next) => {
|
||||
res.render("signin");
|
||||
};
|
||||
|
||||
exports.user_initiation_get = (req, res, next) => {
|
||||
res.render("initiation");
|
||||
};
|
||||
|
||||
exports.user_initiation_post = [
|
||||
// Validate and sanitize Secret Code
|
||||
body("code", "Secret Code not entered!")
|
||||
.trim()
|
||||
.isLength({ min: 1 })
|
||||
.custom((value) => {
|
||||
if (value !== process.env.INITIATION_CODE)
|
||||
throw new Error("Incorrect Secret Code");
|
||||
else return value;
|
||||
})
|
||||
.escape(),
|
||||
|
||||
// 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 Initiation form again with sanitized values/error messages.
|
||||
res.render("initiation", {
|
||||
errors: errors.array(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Data from form is valid.
|
||||
else {
|
||||
// find user from DB
|
||||
const dbUser = await User.findById(req.user._id).lean().exec();
|
||||
// make a "new" user with same fields except member set to true
|
||||
const user = new User({
|
||||
name: dbUser.name,
|
||||
username: dbUser.username,
|
||||
password: dbUser.password,
|
||||
isMember: true,
|
||||
isAdmin: false, //always false if not a member
|
||||
_id: dbUser._id,
|
||||
});
|
||||
await User.findByIdAndUpdate(dbUser._id, user, {});
|
||||
// saved. Redirect to home page.
|
||||
res.redirect(`/`);
|
||||
}
|
||||
}),
|
||||
];
|
||||
|
||||
exports.user_sudo_get = (req, res, next) => {
|
||||
res.render("sudo");
|
||||
};
|
||||
|
||||
exports.user_sudo_post = [
|
||||
// Validate and sanitize Great Secret
|
||||
body("greatsecret", "Answer not entered!")
|
||||
.trim()
|
||||
.isLength({ min: 1 })
|
||||
.custom((value) => {
|
||||
if (value != process.env.GREAT_SECRET)
|
||||
throw new Error("You are incorrect, Initiate");
|
||||
else return value;
|
||||
})
|
||||
.escape(),
|
||||
|
||||
// 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 Initiation form again with sanitized values/error messages.
|
||||
res.render("sudo", {
|
||||
errors: errors.array(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
// else data from form is valid.
|
||||
// find user from DB
|
||||
const dbUser = await User.findById(req.user._id).lean().exec();
|
||||
// make a "new" user with same fields except admin set to true
|
||||
const user = new User({
|
||||
name: dbUser.name,
|
||||
username: dbUser.username,
|
||||
password: dbUser.password,
|
||||
isMember: true, // always true if admin
|
||||
isAdmin: true,
|
||||
_id: dbUser._id,
|
||||
});
|
||||
await User.findByIdAndUpdate(dbUser._id, user, {});
|
||||
// saved. Redirect to home page.
|
||||
res.redirect(`/`);
|
||||
}),
|
||||
];
|
||||
20
models/message.js
Normal file
20
models/message.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
const mongoose = require("mongoose");
|
||||
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const MessageSchema = new Schema({
|
||||
title: { type: String, required: true },
|
||||
date: { type: Date, required: true },
|
||||
text: { type: String, required: true },
|
||||
author: { 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);
|
||||
16
models/user.js
Normal file
16
models/user.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const UserSchema = new Schema({
|
||||
name: { type: String, required: true },
|
||||
username: { type: String, required: true },
|
||||
password: { type: String, required: true },
|
||||
isMember: { type: Boolean, required: true },
|
||||
isAdmin: { type: Boolean, required: false },
|
||||
_id: { type: mongoose.ObjectId, required: true },
|
||||
});
|
||||
|
||||
// we will NOT be exposing any URLs for users
|
||||
|
||||
// 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 });
|
||||
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;
|
||||
8
public/stylesheets/index.css
Normal file
8
public/stylesheets/index.css
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
@import url("https://fonts.googleapis.com/css2?family=Cinzel+Decorative:wght@900&display=swap");
|
||||
|
||||
.logotext {
|
||||
color: gold;
|
||||
text-shadow: 1px 1px 2px black;
|
||||
font-family: "Cinzel Decorative", cursive;
|
||||
font-weight: bold;
|
||||
}
|
||||
34
routes.js
Normal file
34
routes.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const passport = require("passport");
|
||||
const index_controller = require("./controllers/index.js");
|
||||
const user_controller = require("./controllers/user.js");
|
||||
const message_controller = require("./controllers/message.js");
|
||||
|
||||
// home page
|
||||
router.get("/", index_controller.index);
|
||||
|
||||
// user sign in page
|
||||
router.get("/user/signin", user_controller.user_signin_get);
|
||||
router.post("/user/signin", passport.authenticate("local"), (req, res) => {
|
||||
res.redirect("/");
|
||||
});
|
||||
// user sign up page
|
||||
router.get("/user/create", user_controller.user_create_get);
|
||||
router.post("/user/create", user_controller.user_create_post);
|
||||
// initiate a member with secret password
|
||||
router.get("/user/initiation", user_controller.user_initiation_get);
|
||||
router.post("/user/initiation", user_controller.user_initiation_post);
|
||||
// member enters all the names of God to become 33rd degree Illumined Grandmaster
|
||||
router.get("/user/sudo", user_controller.user_sudo_get);
|
||||
router.post("/user/sudo", user_controller.user_sudo_post);
|
||||
|
||||
// message functions
|
||||
// create URL has to go first before :matchers
|
||||
router.get("/msg/create", message_controller.message_create_get);
|
||||
router.post("/msg/create", message_controller.message_create_post);
|
||||
//
|
||||
router.get("/msg/:message", message_controller.index);
|
||||
router.get("/msg/:message/delete", message_controller.message_delete_get);
|
||||
|
||||
module.exports = router;
|
||||
43
views/createmessage.ejs
Normal file
43
views/createmessage.ejs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CVLT - New Insight</title>
|
||||
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
|
||||
</head>
|
||||
<body class="bg-dark text-light">
|
||||
<div class="d-flex flex-column py-4 px-4 align-items-center">
|
||||
<h1 class="mb-4">Create Insight</h1>
|
||||
<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="title">Title:</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
aria-label="insight title"
|
||||
aria-describedby="title"
|
||||
name="title"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group mb-4" data-bs-theme="dark">
|
||||
<span class="input-group-text" id="message">Insight:</span>
|
||||
<textarea
|
||||
class="form-control"
|
||||
aria-label="insight"
|
||||
aria-describedby="message"
|
||||
name="message"
|
||||
></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-outline-light px-4 py-2">
|
||||
Submit Insight
|
||||
</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="/" type="button" class="btn btn-outline-light px-4 py-1">
|
||||
Back to Home
|
||||
</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
66
views/createuser.ejs
Normal file
66
views/createuser.ejs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CVLT - New User</title>
|
||||
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
|
||||
</head>
|
||||
<body class="bg-dark text-light">
|
||||
<div class="d-flex flex-column py-4 px-4 align-items-center">
|
||||
<h1 class="mb-4">New User</h1>
|
||||
<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="name">Full Name:</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
aria-label="name"
|
||||
aria-describedby="name"
|
||||
name="name"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group mb-4" data-bs-theme="dark">
|
||||
<span class="input-group-text" id="username">Username:</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
aria-label="username"
|
||||
aria-describedby="username"
|
||||
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="password_confirmation"
|
||||
>Confirm Password:</span
|
||||
>
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
aria-label="confirm password"
|
||||
aria-describedby="password_confirmation"
|
||||
name="password_confirmation"
|
||||
/>
|
||||
</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="/" type="button" class="btn btn-outline-light px-4 py-1">
|
||||
Back to Home
|
||||
</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
61
views/index.ejs
Normal file
61
views/index.ejs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CVLT</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">
|
||||
<div class="d-flex flex-column py-4 px-4 align-items-center">
|
||||
<h1 class="logotext mb-4">CVLT</h1>
|
||||
<p class="fs-3">Insights:</p>
|
||||
<% messages.forEach(message => { %>
|
||||
<a
|
||||
href="/msg/<%= message._id %>"
|
||||
type="button"
|
||||
class="btn btn-outline-light px-4"
|
||||
>
|
||||
<h3 class="pt-2"><%= message.title %></h3>
|
||||
<% if (user && user.isMember) { %>
|
||||
<p>Penned: <%= message.date %> by <%= message.author %></p>
|
||||
<% } %>
|
||||
<p><%= message.text %></p>
|
||||
</a>
|
||||
<div class="pb-4"></div>
|
||||
<% }) %> <% if (user && user.isMember) { %>
|
||||
<a href="/msg/create" type="button" class="btn btn-success px-4">
|
||||
+ Post Message
|
||||
</a>
|
||||
<% } %>
|
||||
</div>
|
||||
<% if (user && user.isMember === true && user.isAdmin === false) { %>
|
||||
<footer
|
||||
class="position-fixed bottom-0 border-light border-top w-100 py-2 d-flex justify-content-center bg-dark"
|
||||
>
|
||||
<a href="/user/sudo" type="button" class="btn btn-light px-4">
|
||||
Join the Light
|
||||
</a>
|
||||
</footer>
|
||||
<% } %> <% if (user && user.isMember === false) { %>
|
||||
<footer
|
||||
class="position-fixed bottom-0 border-light border-top w-100 py-2 d-flex justify-content-center bg-dark"
|
||||
>
|
||||
<a
|
||||
href="/user/initiation"
|
||||
type="button"
|
||||
class="btn btn-outline-light px-4"
|
||||
>
|
||||
Become an Initiate
|
||||
</a>
|
||||
</footer>
|
||||
<% } %> <% if (!user) { %>
|
||||
<footer
|
||||
class="position-fixed bottom-0 border-light border-top w-100 py-2 d-flex justify-content-center bg-dark"
|
||||
>
|
||||
<a href="/user/signin" type="button" class="btn btn-outline-light px-4">
|
||||
Sign In
|
||||
</a>
|
||||
</footer>
|
||||
<% } %>
|
||||
</body>
|
||||
</html>
|
||||
34
views/initiation.ejs
Normal file
34
views/initiation.ejs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CVLT - Initiation</title>
|
||||
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
|
||||
</head>
|
||||
<body class="bg-dark text-light">
|
||||
<div class="d-flex flex-column py-4 px-4 align-items-center">
|
||||
<h1 class="mb-4">Initiation</h1>
|
||||
<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="code">Enter the Secret Code:</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
aria-label="secret code"
|
||||
aria-describedby="code"
|
||||
name="code"
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success px-4 py-2">
|
||||
✓ I wish to be an Initiate
|
||||
</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="/" type="button" class="btn btn-outline-light px-4 py-1">
|
||||
Back to Home
|
||||
</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
34
views/message.ejs
Normal file
34
views/message.ejs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CVLT - <%= message.title %></title>
|
||||
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
|
||||
</head>
|
||||
<body class="bg-dark text-light">
|
||||
<div class="d-flex flex-column py-4 px-4 align-items-center">
|
||||
<h1 class="mb-0"><%= message.title %></h1>
|
||||
<% if (user && user.isMember) { %>
|
||||
<p>Penned: <%= message.date %> by <%= message.author %></p>
|
||||
<% } %>
|
||||
<div class="pb-4"></div>
|
||||
<p><%= message.text %></p>
|
||||
<div class="pb-4"></div>
|
||||
<% if (user && user.isAdmin) { %>
|
||||
<a
|
||||
href="/msg/<%= message._id %>/delete"
|
||||
type="button"
|
||||
class="btn btn-danger px-4"
|
||||
>
|
||||
🗙 Countermand this Insight
|
||||
</a>
|
||||
<% } %>
|
||||
</div>
|
||||
<footer
|
||||
class="position-fixed bottom-0 border-light border-top w-100 py-2 d-flex justify-content-center"
|
||||
>
|
||||
<a href="/" type="button" class="btn btn-outline-light px-4 py-1">
|
||||
Back to Home
|
||||
</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
51
views/signin.ejs
Normal file
51
views/signin.ejs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CVLT - Sign In</title>
|
||||
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
|
||||
</head>
|
||||
<body class="bg-dark text-light">
|
||||
<div class="d-flex flex-column py-4 px-4 align-items-center">
|
||||
<h1 class="mb-4">Sign In</h1>
|
||||
<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-4"></div>
|
||||
<div class="d-flex flex-column align-items-center">
|
||||
<p class="mb-2">Don't have an account?</p>
|
||||
<a href="/user/create" type="button" class="btn btn-success px-4">
|
||||
Join Us!
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<footer
|
||||
class="position-fixed bottom-0 border-light border-top w-100 py-2 d-flex justify-content-center bg-dark"
|
||||
>
|
||||
<a href="/" type="button" class="btn btn-outline-light px-4 py-1">
|
||||
Back to Home
|
||||
</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
34
views/sudo.ejs
Normal file
34
views/sudo.ejs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CVLT - Ascension</title>
|
||||
<link rel="stylesheet" href="/styles/css/bootstrap.min.css" />
|
||||
</head>
|
||||
<body class="bg-dark text-light">
|
||||
<div class="d-flex flex-column py-4 px-4 align-items-center">
|
||||
<h1 class="mb-4">Ascension</h1>
|
||||
<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="greatsecret-label"
|
||||
>Life, the Universe and Everything:</span
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
aria-label="great secret"
|
||||
aria-describedby="greatsecret-label"
|
||||
name="greatsecret"
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-light px-4 py-2">ASCEND</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="/" type="button" class="btn btn-outline-light px-4 py-1">
|
||||
Back to Home
|
||||
</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Reference in a new issue