parent
38df242ee7
commit
c9d13320d5
|
@ -0,0 +1,4 @@
|
||||||
|
.env
|
||||||
|
/node_modules
|
||||||
|
/data
|
||||||
|
*.code-workspace
|
|
@ -0,0 +1,36 @@
|
||||||
|
const { PrismaClient } = require("@prisma/client");
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
const crypto = require("crypto");
|
||||||
|
const sharp = require("sharp");
|
||||||
|
const { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, ListObjectsCommand, DeleteObjectsCommand } = require("@aws-sdk/client-s3");
|
||||||
|
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
|
||||||
|
const s3 = new S3Client({
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: process.env.S3_ACCESS_KEY,
|
||||||
|
secretAccessKey: process.env.S3_SECRET_KEY,
|
||||||
|
},
|
||||||
|
region: process.env.S3_REGION,
|
||||||
|
endpoint: process.env.S3_ENDPOINT,
|
||||||
|
});
|
||||||
|
|
||||||
|
const persistent_setting = require("node-persist");
|
||||||
|
persistent_setting.init({ dir: "data/" });
|
||||||
|
|
||||||
|
async function registerUser(username, password) {
|
||||||
|
const new_user = await prisma.user.create({ data: { username: username, password: password } });
|
||||||
|
await persistent_setting.setItem("SETUP_COMPLETE", true);
|
||||||
|
if (new_user) return { success: true, message: `Successfully created ${new_user.username}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUser({ id, username } = {}) {
|
||||||
|
if (id || username) {
|
||||||
|
let user;
|
||||||
|
if (id) user = await prisma.user.findUnique({ where: { id: id } });
|
||||||
|
else if (username) user = await prisma.user.findUnique({ where: { username: username } });
|
||||||
|
|
||||||
|
if (!user) return { success: false, message: "No matching user" };
|
||||||
|
else return { success: true, data: user };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { registerUser, getUser };
|
|
@ -0,0 +1,32 @@
|
||||||
|
const { PrismaClient } = require("@prisma/client");
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
const core = require("./core");
|
||||||
|
|
||||||
|
// Check if user registration is allowed via the settings
|
||||||
|
function registration() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function userRegistration(username, password) {
|
||||||
|
if (!username) return { success: false, message: "No username provided" };
|
||||||
|
if (!password) return { success: false, message: "No password provided" };
|
||||||
|
// TODO: Admin customizable minimum password length
|
||||||
|
if (password.length < 4) return { success: false, message: "Password not long enough" };
|
||||||
|
|
||||||
|
const existing_user = await core.getUser({ username: username });
|
||||||
|
|
||||||
|
if (existing_user.success) return { success: false, message: "Username already exists" };
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function userLogin(username, password) {
|
||||||
|
const existing_user = await core.getUser({ username: username });
|
||||||
|
|
||||||
|
if (!existing_user.success) return { success: false, message: "User does not exist" };
|
||||||
|
if (existing_user.role === "LOCKED") return { success: false, message: "Account is locked: Contact your administrator" };
|
||||||
|
|
||||||
|
return { success: true, data: existing_user.data };
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { registration, userRegistration, userLogin };
|
|
@ -0,0 +1,22 @@
|
||||||
|
const validate = require("./form_validation");
|
||||||
|
const core = require("./core");
|
||||||
|
|
||||||
|
async function registerUser(username, password) {
|
||||||
|
const registration_allowed = validate.registration(); // Check if user registration is allowed
|
||||||
|
const form_valid = await validate.userRegistration(username, password); // Check form for errors
|
||||||
|
|
||||||
|
// Register the user in the database
|
||||||
|
if (registration_allowed && form_valid.success) return await core.registerUser(username, password);
|
||||||
|
|
||||||
|
// Something went wrong!
|
||||||
|
return { success: false, message: form_valid.message };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loginUser(username, password) {
|
||||||
|
const user = await validate.userLogin(username);
|
||||||
|
if (!user.success) return user;
|
||||||
|
|
||||||
|
return { success: true, data: { username: user.data.username, id: user.data.id, password: user.data.password } };
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { registerUser, loginUser };
|
|
@ -0,0 +1,34 @@
|
||||||
|
const internal = require("./core/internal_api");
|
||||||
|
const bcrypt = require("bcrypt");
|
||||||
|
const persistent_setting = require("node-persist");
|
||||||
|
persistent_setting.init({ dir: "data/" });
|
||||||
|
|
||||||
|
async function index(request, response) {
|
||||||
|
// Check if the master admin has been created
|
||||||
|
const is_setup_complete = (await persistent_setting.getItem("SETUP_COMPLETE")) || false;
|
||||||
|
if (!is_setup_complete) return response.redirect("/register");
|
||||||
|
|
||||||
|
response.render("index.ejs", { website_name: process.env.WEBSITE_NAME });
|
||||||
|
}
|
||||||
|
|
||||||
|
function register(request, response) {
|
||||||
|
response.render("register.ejs", { website_name: process.env.WEBSITE_NAME });
|
||||||
|
}
|
||||||
|
function login(request, response) {
|
||||||
|
response.render("login.ejs", { website_name: process.env.WEBSITE_NAME });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function registerPost(request, response) {
|
||||||
|
const hashedPassword = await bcrypt.hash(request.body.password, 10); // Hash the password for security :^)
|
||||||
|
response.json(await internal.registerUser(request.body.username, hashedPassword));
|
||||||
|
}
|
||||||
|
async function loginPost(request, response) {
|
||||||
|
const login = await internal.loginUser(request.body.username, request.body.password);
|
||||||
|
|
||||||
|
const password_match = await bcrypt.compare(request.body.password, login.data.password);
|
||||||
|
if (!password_match) return { success: false, message: "Incorrect password" };
|
||||||
|
|
||||||
|
request.session.user = { username: login.data.username, id: login.data.id };
|
||||||
|
response.json({ success: true });
|
||||||
|
}
|
||||||
|
module.exports = { index, register, login, registerPost, loginPost };
|
|
@ -0,0 +1 @@
|
||||||
|
function checkPermissions(role, { minimum = true }) {}
|
|
@ -0,0 +1,112 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background-color: #111;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background-color: #222;
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 0 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.header .left {
|
||||||
|
margin: auto auto auto 0;
|
||||||
|
font-size: 20px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.header .right {
|
||||||
|
margin: auto 0 auto auto;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.header .right a:hover,
|
||||||
|
.header .right a:focus {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
.header a {
|
||||||
|
height: 100%;
|
||||||
|
width: 130px;
|
||||||
|
margin: auto 0;
|
||||||
|
display: flex;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background-color ease-in-out 0.1s;
|
||||||
|
}
|
||||||
|
.header a div {
|
||||||
|
margin: auto;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #1c478a;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover,
|
||||||
|
button:focus {
|
||||||
|
background-color: #122d57;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
width: 1000px;
|
||||||
|
min-height: 10px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.page .horizontal-button-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.page .horizontal-button-container button {
|
||||||
|
width: 100px;
|
||||||
|
min-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-modal {
|
||||||
|
margin: auto;
|
||||||
|
width: 400px;
|
||||||
|
background-color: #222;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0 20px 20px 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.center-modal .modal-title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 26px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.center-modal .input-line {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.center-modal .input-line div {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.center-modal .input-line input {
|
||||||
|
background-color: #0d0d0d;
|
||||||
|
border: 0;
|
||||||
|
padding: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.center-modal .horizontal-button-container {
|
||||||
|
flex-direction: row-reverse !important;
|
||||||
|
}
|
||||||
|
.center-modal .horizontal-button-container a {
|
||||||
|
color: white;
|
||||||
|
margin: auto auto auto 0;
|
||||||
|
}
|
||||||
|
.center-modal .horizontal-button-container button {
|
||||||
|
width: 200px !important;
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
@use "theme";
|
||||||
|
|
||||||
|
.center-modal {
|
||||||
|
margin: auto;
|
||||||
|
width: 400px;
|
||||||
|
background-color: theme.$header-color;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0 20px 20px 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 26px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-line {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
div {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
background-color: #0d0d0d;
|
||||||
|
border: 0;
|
||||||
|
padding: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-button-container {
|
||||||
|
flex-direction: row-reverse !important;
|
||||||
|
a {
|
||||||
|
color: white;
|
||||||
|
margin: auto auto auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 200px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background-color: #111;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background-color: #222;
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 0 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.header .left {
|
||||||
|
margin: auto auto auto 0;
|
||||||
|
font-size: 20px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.header .left a {
|
||||||
|
width: inherit;
|
||||||
|
}
|
||||||
|
.header .right {
|
||||||
|
margin: auto 0 auto auto;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.header .right a:hover,
|
||||||
|
.header .right a:focus {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
.header a {
|
||||||
|
height: 100%;
|
||||||
|
width: 130px;
|
||||||
|
margin: auto 0;
|
||||||
|
display: flex;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background-color ease-in-out 0.1s;
|
||||||
|
}
|
||||||
|
.header a div {
|
||||||
|
margin: auto;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #1c478a;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover,
|
||||||
|
button:focus {
|
||||||
|
background-color: #122d57;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
width: 1000px;
|
||||||
|
min-height: 10px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.page .horizontal-button-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.page .horizontal-button-container button {
|
||||||
|
width: 100px;
|
||||||
|
min-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1010px) {
|
||||||
|
.page {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
$header-color: #222;
|
||||||
|
$button-generic: #1c478a;
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background-color: #111;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background-color: $header-color;
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 0 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
margin: auto auto auto 0;
|
||||||
|
font-size: 20px;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
a {
|
||||||
|
width: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
margin: auto 0 auto auto;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
a:hover,
|
||||||
|
a:focus {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
height: 100%;
|
||||||
|
width: 130px;
|
||||||
|
margin: auto 0;
|
||||||
|
display: flex;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background-color ease-in-out 0.1s;
|
||||||
|
|
||||||
|
div {
|
||||||
|
margin: auto;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: $button-generic;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover,
|
||||||
|
button:focus {
|
||||||
|
background-color: #122d57;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
width: 1000px;
|
||||||
|
min-height: 10px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.horizontal-button-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100px;
|
||||||
|
min-height: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1010px) {
|
||||||
|
.page {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Quick document selectors
|
||||||
|
const qs = (selector) => document.querySelector(selector);
|
||||||
|
const qsa = (selector) => document.querySelectorAll(selector);
|
||||||
|
|
||||||
|
async function request(url, method, body) {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: method,
|
||||||
|
body: body,
|
||||||
|
});
|
||||||
|
return { status: response.status, body: await response.json() };
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
async function requestLogin() {
|
||||||
|
const account_information = {
|
||||||
|
username: qs("#username").value,
|
||||||
|
password: qs("#password").value,
|
||||||
|
};
|
||||||
|
|
||||||
|
const form = new FormData();
|
||||||
|
form.append("username", account_information.username);
|
||||||
|
form.append("password", account_information.password);
|
||||||
|
|
||||||
|
const account_response = await request("/login", "POST", form);
|
||||||
|
|
||||||
|
// Check response for errors
|
||||||
|
|
||||||
|
// If success, return to account
|
||||||
|
console.log(account_response);
|
||||||
|
|
||||||
|
if (account_response.body.success) {
|
||||||
|
location.href = "/";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
async function requestRegister() {
|
||||||
|
const account_information = {
|
||||||
|
username: qs("#username").value,
|
||||||
|
password: qs("#password").value,
|
||||||
|
};
|
||||||
|
|
||||||
|
const form = new FormData();
|
||||||
|
form.append("username", account_information.username);
|
||||||
|
form.append("password", account_information.password);
|
||||||
|
|
||||||
|
const account_response = await request("/register", "POST", form);
|
||||||
|
|
||||||
|
// Check response for errors
|
||||||
|
|
||||||
|
// If success, return to account
|
||||||
|
console.log(account_response);
|
||||||
|
|
||||||
|
if (account_response.body.success) {
|
||||||
|
location.href = "/login";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/index.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/theme.css" />
|
||||||
|
<script src="/js/generic.js"></script>
|
||||||
|
<title><%= website_name %> | Home</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%- include("partials/header.ejs", {selected: 'home'}) %>
|
||||||
|
|
||||||
|
<div class="page"></div>
|
||||||
|
|
||||||
|
<%- include("partials/footer.ejs") %>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<script defer src="/js/login.js"></script>
|
|
@ -0,0 +1,36 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/signin.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/theme.css" />
|
||||||
|
<script src="/js/generic.js"></script>
|
||||||
|
<title><%= website_name %> | Login</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%- include("partials/header.ejs", {selected: 'home'}) %>
|
||||||
|
|
||||||
|
<div class="page">
|
||||||
|
<div class="center-modal">
|
||||||
|
<div class="modal-title">Login</div>
|
||||||
|
<div class="input-line">
|
||||||
|
<div>Username</div>
|
||||||
|
<input id="username" type="text" />
|
||||||
|
</div>
|
||||||
|
<div class="input-line">
|
||||||
|
<div>Password</div>
|
||||||
|
<input id="password" type="password" />
|
||||||
|
</div>
|
||||||
|
<div class="horizontal-button-container">
|
||||||
|
<button onclick="requestLogin()"><div>Login</div></button>
|
||||||
|
<a href="/register">Register</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%- include("partials/footer.ejs") %>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<script defer src="/js/login.js"></script>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<div class="header">
|
||||||
|
<div class="left">
|
||||||
|
<a class="nav-button" href="/">
|
||||||
|
<div><%= website_name %></div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<a class="nav-button" href="/blog">
|
||||||
|
<div>Blog</div>
|
||||||
|
</a>
|
||||||
|
<a class="nav-button" href="/projects">
|
||||||
|
<div>Projects</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,36 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/signin.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/theme.css" />
|
||||||
|
<script src="/js/generic.js"></script>
|
||||||
|
<title><%= website_name %> | Register</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%- include("partials/header.ejs", {selected: 'home'}) %>
|
||||||
|
|
||||||
|
<div class="page">
|
||||||
|
<div class="center-modal">
|
||||||
|
<div class="modal-title">Register</div>
|
||||||
|
<div class="input-line">
|
||||||
|
<div>Username</div>
|
||||||
|
<input id="username" type="text" />
|
||||||
|
</div>
|
||||||
|
<div class="input-line">
|
||||||
|
<div>Password</div>
|
||||||
|
<input id="password" type="password" />
|
||||||
|
</div>
|
||||||
|
<div class="horizontal-button-container">
|
||||||
|
<button onclick="requestRegister()"><div>Register</div></button>
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%- include("partials/footer.ejs") %>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<script defer src="/js/register.js"></script>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"name": "yet-another-blog",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "A open source blogging website made for both personal blogs and big projects.",
|
||||||
|
"main": "yab.js",
|
||||||
|
"scripts": {
|
||||||
|
"devstart": "nodemon -r dotenv/config yab.js dotenv_config_path=.env"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.armoreddragon.com/ArmoredDragon/yet-another-blog"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"blog",
|
||||||
|
"personal-blog",
|
||||||
|
"rss"
|
||||||
|
],
|
||||||
|
"author": "Armored Dragon",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"dotenv": "^16.3.1",
|
||||||
|
"nodemon": "^3.0.1",
|
||||||
|
"prisma": "^5.2.0",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"typescript": "^5.2.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.410.0",
|
||||||
|
"@aws-sdk/s3-request-presigner": "^3.410.0",
|
||||||
|
"@prisma/client": "^5.2.0",
|
||||||
|
"bcrypt": "^5.1.1",
|
||||||
|
"body-parser": "^1.20.2",
|
||||||
|
"ejs": "^3.1.9",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"express-session": "^1.17.3",
|
||||||
|
"markdown-it": "^13.0.1",
|
||||||
|
"multer": "^1.4.5-lts.1",
|
||||||
|
"node-persist": "^3.1.3",
|
||||||
|
"sharp": "^0.32.5"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
// This is your Prisma schema file,
|
||||||
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @unique @default(uuid())
|
||||||
|
username String @unique
|
||||||
|
password String
|
||||||
|
role Role @default(VISITOR)
|
||||||
|
|
||||||
|
blog_posts BlogPost[]
|
||||||
|
edits Edit[]
|
||||||
|
|
||||||
|
@@index([username, role])
|
||||||
|
}
|
||||||
|
|
||||||
|
model BlogPost {
|
||||||
|
id String @id @unique @default(uuid())
|
||||||
|
title String?
|
||||||
|
short_description String?
|
||||||
|
content String?
|
||||||
|
thumbnail String?
|
||||||
|
tags String[]
|
||||||
|
status PostStatus @default(DRAFT)
|
||||||
|
group Role @default(AUTHOR)
|
||||||
|
owner User? @relation(fields: [ownerid], references: [id])
|
||||||
|
ownerid String?
|
||||||
|
edits Edit[]
|
||||||
|
|
||||||
|
// Dates
|
||||||
|
publish_date DateTime?
|
||||||
|
created_date DateTime @default(now())
|
||||||
|
}
|
||||||
|
|
||||||
|
model Edit {
|
||||||
|
id String @id @unique @default(uuid())
|
||||||
|
owner User @relation(fields: [ownerid], references: [id])
|
||||||
|
ownerid String
|
||||||
|
reason String? @default("No reason provided.")
|
||||||
|
|
||||||
|
blogpost BlogPost? @relation(fields: [blogpost_id], references: [id], onDelete: Cascade)
|
||||||
|
blogpost_id String?
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Role {
|
||||||
|
LOCKED
|
||||||
|
VISITOR
|
||||||
|
AUTHOR
|
||||||
|
ADMIN
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PostStatus {
|
||||||
|
DRAFT
|
||||||
|
PUBLISHED
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ProjectStatus {
|
||||||
|
DELAYED
|
||||||
|
IN_PROGRESS
|
||||||
|
SUCCESS
|
||||||
|
FAILURE
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
ENVIRONMENT='production'
|
||||||
|
|
||||||
|
# The URL your site is hosted on. (example: https://myawesomeblog.com)
|
||||||
|
BASE_URL=""
|
||||||
|
|
||||||
|
# PostgreSQL database url
|
||||||
|
DATABASE_URL=""
|
||||||
|
|
||||||
|
# S3 bucket settings used to store images and videos
|
||||||
|
S3_BUCKET_NAME=''
|
||||||
|
S3_ACCESS_KEY=""
|
||||||
|
S3_SECRET_KEY=""
|
||||||
|
S3_REGION=""
|
||||||
|
S3_ENDPOINT=""
|
|
@ -0,0 +1,68 @@
|
||||||
|
// Multer file handling
|
||||||
|
// REVIEW: Look for a way to drop this dependency?
|
||||||
|
const multer = require("multer");
|
||||||
|
const multer_storage = multer.memoryStorage();
|
||||||
|
const upload = multer({ storage: multer_storage });
|
||||||
|
|
||||||
|
// Express
|
||||||
|
const express = require("express");
|
||||||
|
const session = require("express-session");
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
// Security and encryption
|
||||||
|
const bcrypt = require("bcrypt");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
|
||||||
|
// Local modules
|
||||||
|
const page_scripts = require("./backend/page_scripts");
|
||||||
|
|
||||||
|
// Express settings
|
||||||
|
app.set("view-engine", "ejs");
|
||||||
|
app.set("views", path.join(__dirname, "frontend/views"));
|
||||||
|
app.use(express.static(path.join(__dirname, "frontend/public")));
|
||||||
|
const bodyParser = require("body-parser");
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
app.use(bodyParser.urlencoded({ extended: true }));
|
||||||
|
app.use(upload.array());
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
session({
|
||||||
|
secret: crypto.randomBytes(128).toString("base64"),
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Account Creation Endpoints
|
||||||
|
app.get("/login", page_scripts.login);
|
||||||
|
app.post("/login", checkNotAuthenticated, page_scripts.loginPost);
|
||||||
|
app.get("/register", checkNotAuthenticated, page_scripts.register);
|
||||||
|
app.post("/register", checkNotAuthenticated, page_scripts.registerPost);
|
||||||
|
|
||||||
|
// Account Required Endpoints
|
||||||
|
// app.get("/blog/new", checkAuthenticated, page_scripts.blogNew);
|
||||||
|
// app.post("/blog", checkAuthenticated, page_scripts.postBlog);
|
||||||
|
// app.delete("/logout", page_scripts.logout);
|
||||||
|
|
||||||
|
// Image Endpoints
|
||||||
|
// app.post("/api/image", checkAuthenticated, upload.fields([{ name: "image" }]), page_scripts.uploadImage);
|
||||||
|
|
||||||
|
// Endpoints
|
||||||
|
app.get("/", page_scripts.index);
|
||||||
|
// app.get("/blog", page_scripts.blogList);
|
||||||
|
// app.get("/blog/:id", page_scripts.blogSingle);
|
||||||
|
// app.get("/projects", page_scripts.projectList);
|
||||||
|
|
||||||
|
function checkAuthenticated(req, res, next) {
|
||||||
|
if (req.session.user) return next();
|
||||||
|
res.redirect("/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkNotAuthenticated(req, res, next) {
|
||||||
|
if (req.session.user) return res.redirect("/");
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.listen(8080);
|
Loading…
Reference in New Issue