mirror of
https://github.com/Akif9748/akf-forum.git
synced 2024-11-29 14:10:41 +03:00
Compare commits
No commits in common. "fa222ad68dba1610bacc1c96266cb4022662ff2f" and "24cdd86e3449e8c23cf0fcaddb4712f68516223d" have entirely different histories.
fa222ad68d
...
24cdd86e34
35 changed files with 62 additions and 1945 deletions
|
@ -1,27 +0,0 @@
|
||||||
{
|
|
||||||
"ignorePatterns": [
|
|
||||||
"test.js"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"node": true,
|
|
||||||
"commonjs": true,
|
|
||||||
"es2021": true
|
|
||||||
},
|
|
||||||
"extends": "eslint:recommended",
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": "latest"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"no-use-before-define": "error"
|
|
||||||
},
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"env": {
|
|
||||||
"browser": true
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"public/js/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
11
README.md
11
README.md
|
@ -54,16 +54,8 @@ Akf-forum has got an API for AJAX (fetch), other clients etc. And, you can learn
|
||||||
- Add a feature list to README.md
|
- Add a feature list to README.md
|
||||||
- delete admin???
|
- delete admin???
|
||||||
- change category name
|
- change category name
|
||||||
|
- enchanted theme: not take all of the public! `"/css`, patch user support!
|
||||||
- _id
|
- _id
|
||||||
- add support for transition aroun gravatar
|
|
||||||
|
|
||||||
### theme to do:
|
|
||||||
- theme change
|
|
||||||
- routes/api/routes/users.js check,
|
|
||||||
themes/default/extra/footer.ejs check,
|
|
||||||
themes/default/extra/meta.ejs check
|
|
||||||
- add theme support again, but only works with css folder. Put every css file into one file. (themes/default/css/main.css)
|
|
||||||
|
|
||||||
### front-end
|
### front-end
|
||||||
- better usermenu for user profile
|
- better usermenu for user profile
|
||||||
- old contents / titles add to forum interface
|
- old contents / titles add to forum interface
|
||||||
|
@ -73,6 +65,7 @@ themes/default/extra/meta.ejs check
|
||||||
- give admin button, not is admin
|
- give admin button, not is admin
|
||||||
|
|
||||||
## Major Version History
|
## Major Version History
|
||||||
|
- V5: Enchanted theme support
|
||||||
- V4: Caching
|
- V4: Caching
|
||||||
- V3: New Theme
|
- V3: New Theme
|
||||||
- V2: Backend fix, mongoose is fixed. Really big fix.
|
- V2: Backend fix, mongoose is fixed. Really big fix.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"def_theme": {
|
"def_theme": {
|
||||||
"name": "white",
|
"name": "white",
|
||||||
|
"color": "black",
|
||||||
"language": "en"
|
"language": "en"
|
||||||
},
|
},
|
||||||
"forum_name": "akf",
|
"forum_name": "akf",
|
||||||
|
@ -17,7 +18,7 @@
|
||||||
"windowMs": 60000
|
"windowMs": 60000
|
||||||
},
|
},
|
||||||
"discord_auth": "",
|
"discord_auth": "",
|
||||||
"default_thread_state": "OPEN",
|
"defaultThreadState": "OPEN",
|
||||||
"email_auth": false,
|
"email_auth": false,
|
||||||
"default_user_state": "ACTIVE",
|
"default_user_state": "ACTIVE",
|
||||||
"host": "https://akf-forum.glitch.me"
|
"host": "https://akf-forum.glitch.me"
|
||||||
|
|
20
index.js
20
index.js
|
@ -22,8 +22,8 @@ app.ips = [];
|
||||||
app.set("view engine", "ejs");
|
app.set("view engine", "ejs");
|
||||||
app.set("limits", limits);
|
app.set("limits", limits);
|
||||||
|
|
||||||
app.use(express.static("public"), express.json(), express.urlencoded({ extended: true }), IP(),
|
app.use(express.static("public", { maxAge: 86400 * 1000 }), express.json(), express.urlencoded({ extended: true }), IP(),
|
||||||
SES({ secret: process.env.SECRET, store: MS.create({ clientPromise: DB, stringify: false }), resave: false, saveUninitialized: false }),
|
SES({ secret: process.env.SECRET, store: MS.create({ clientPromise: DB, stringify: false }), resave: true, saveUninitialized: true }),
|
||||||
async (req, res, next) => {
|
async (req, res, next) => {
|
||||||
if (app.ips.includes(req.clientIp)) return res.status(403).send("You are banned from this forum.");
|
if (app.ips.includes(req.clientIp)) return res.status(403).send("You are banned from this forum.");
|
||||||
|
|
||||||
|
@ -31,14 +31,14 @@ app.use(express.static("public"), express.json(), express.urlencoded({ extended:
|
||||||
lastSeen: Date.now(), $addToSet: { ips: req.clientIp }
|
lastSeen: Date.now(), $addToSet: { ips: req.clientIp }
|
||||||
}) : null;
|
}) : null;
|
||||||
|
|
||||||
|
const theme = require(`./themes/${req.user?.theme?.name || def_theme.name}`);
|
||||||
res.reply = (page, options = {}, status = 200) => res.status(status).render(page, {
|
res.reply = (page, data = {}, status = 200) =>
|
||||||
user: req.user,
|
theme.render(page, { user: req.user, ...data }, {
|
||||||
theme: req.user?.theme || def_theme,
|
color: req.user?.theme?.color || def_theme.color,
|
||||||
lang: req.user?.theme?.language || def_theme.language,
|
lang: req.user?.theme?.language || def_theme.language,
|
||||||
forum_name, description, ...options
|
forum_name,
|
||||||
});
|
description
|
||||||
|
}, res.status(status));
|
||||||
|
|
||||||
res.error = (type, error) => res.reply("error", { type, error }, type);
|
res.error = (type, error) => res.reply("error", { type, error }, type);
|
||||||
|
|
||||||
|
@ -61,6 +61,6 @@ if (RLS.enabled) app.use(RL(RLS.windowMs, RLS.max));
|
||||||
for (const file of fs.readdirSync("./routes"))
|
for (const file of fs.readdirSync("./routes"))
|
||||||
app.use("/" + file.replace(".js", ""), require(`./routes/${file}`));
|
app.use("/" + file.replace(".js", ""), require(`./routes/${file}`));
|
||||||
|
|
||||||
app.all("*", (req, res) => res.error(404, "This page does not exist on this forum."));
|
app.all("*", (req, res) => res.error(404, "We have not got this page."));
|
||||||
|
|
||||||
app.listen(port, () => console.log(`${forum_name}-forum on port:`, port));
|
app.listen(port, () => console.log(`${forum_name}-forum on port:`, port));
|
6
lib.js
6
lib.js
|
@ -1,8 +1,6 @@
|
||||||
const RL = require('express-rate-limit');
|
const RL = require('express-rate-limit');
|
||||||
const nodemailer = require("nodemailer");
|
const nodemailer = require("nodemailer");
|
||||||
const config = require("./config.json");
|
const config = require("./config.json");
|
||||||
const crypto = require("crypto");
|
|
||||||
|
|
||||||
require("dotenv").config();
|
require("dotenv").config();
|
||||||
module.exports = {
|
module.exports = {
|
||||||
threadEnum: ["OPEN", "APPROVAL", "DELETED"],
|
threadEnum: ["OPEN", "APPROVAL", "DELETED"],
|
||||||
|
@ -13,10 +11,6 @@ module.exports = {
|
||||||
handler: (req, res, next, opts) => !req.user?.admin ? res.error(opts.statusCode, "You are begin ratelimited") : next()
|
handler: (req, res, next, opts) => !req.user?.admin ? res.error(opts.statusCode, "You are begin ratelimited") : next()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getGravatar(email, size) {
|
|
||||||
return `https://www.gravatar.com/avatar/${crypto.createHash('md5').update(email).digest("hex")}?d=mp${size ? `&size=${size}` : ''}`;
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line no-useless-escape
|
|
||||||
emailRegEx: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
|
emailRegEx: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ schema.methods.takeId = async function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
schema.methods.getLink = function (id = this.id) {
|
schema.methods.getLink = function (id = this.id) {
|
||||||
return "/categories/" + id;
|
return "/categories/" + this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const model = mongoose.model('category', schema);
|
const model = mongoose.model('category', schema);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const mongoose = require("mongoose");
|
const mongoose = require("mongoose");
|
||||||
const cache = require("./cache")
|
const cache = require("./cache")
|
||||||
const MessageModel = require("./Message");
|
const MessageModel = require("./Message");
|
||||||
const { limits, default_thread_state } = require("../config.json");
|
const { limits, defaultThreadState } = require("../config.json");
|
||||||
const { threadEnum } = require("../lib");
|
const { threadEnum } = require("../lib");
|
||||||
const schema = new mongoose.Schema({
|
const schema = new mongoose.Schema({
|
||||||
id: { type: String, unique: true },
|
id: { type: String, unique: true },
|
||||||
|
@ -22,7 +22,7 @@ const schema = new mongoose.Schema({
|
||||||
|
|
||||||
time: { type: Date, default: Date.now },
|
time: { type: Date, default: Date.now },
|
||||||
edited: { type: Boolean, default: false },
|
edited: { type: Boolean, default: false },
|
||||||
state: { type: String, default: default_thread_state, enum: threadEnum, uppercase: true },
|
state: { type: String, default: defaultThreadState, enum: threadEnum, uppercase: true },
|
||||||
messages: [String],
|
messages: [String],
|
||||||
views: { type: Number, default: 0 }
|
views: { type: Number, default: 0 }
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const mongoose = require("mongoose")
|
const mongoose = require("mongoose")
|
||||||
const { def_theme, limits, default_user_state } = require("../config.json");
|
const { def_theme, limits, email_auth, default_user_state } = require("../config.json");
|
||||||
const { userEnum } = require("../lib");
|
const { userEnum } = require("../lib");
|
||||||
|
|
||||||
const schema = new mongoose.Schema({
|
const schema = new mongoose.Schema({
|
||||||
|
@ -13,6 +13,7 @@ const schema = new mongoose.Schema({
|
||||||
admin: { type: Boolean, default: false },
|
admin: { type: Boolean, default: false },
|
||||||
theme: {
|
theme: {
|
||||||
name: { type: String, default: def_theme.name },
|
name: { type: String, default: def_theme.name },
|
||||||
|
color: { type: String, default: def_theme.color },
|
||||||
language: { type: String, default: def_theme.language }
|
language: { type: String, default: def_theme.language }
|
||||||
},
|
},
|
||||||
lastSeen: { type: Date, default: Date.now, select: false },
|
lastSeen: { type: Date, default: Date.now, select: false },
|
||||||
|
|
1875
package-lock.json
generated
1875
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "akf-forum",
|
"name": "akf-forum",
|
||||||
"version": "4.22.0",
|
"version": "5.0.0",
|
||||||
"description": "A Node.js based forum software",
|
"description": "A Node.js based forum software",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -37,8 +37,5 @@
|
||||||
"node-fetch": "^2.6.7",
|
"node-fetch": "^2.6.7",
|
||||||
"nodemailer": "^6.8.0",
|
"nodemailer": "^6.8.0",
|
||||||
"request-ip": "^3.3.0"
|
"request-ip": "^3.3.0"
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"eslint": "^8.25.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ app.patch("/:id", async (req, res) => {
|
||||||
if (req.user.id !== member.id && !user.admin) return res.error(403, "You have not got permission for this.");
|
if (req.user.id !== member.id && !user.admin) return res.error(403, "You have not got permission for this.");
|
||||||
if (!Object.keys(req.body).some(Boolean)) return res.error(400, "Missing member informations in request body.");
|
if (!Object.keys(req.body).some(Boolean)) return res.error(400, "Missing member informations in request body.");
|
||||||
|
|
||||||
const { name, about, admin, deleted, hideLastSeen } = req.body;
|
const { name, about, theme, admin, deleted, hideLastSeen } = req.body;
|
||||||
|
|
||||||
if ((admin?.length || "deleted" in req.body) && !req.user.admin) return res.error(403, "You have not got permission for edit 'admin' and 'deleted' information, or bad request.");
|
if ((admin?.length || "deleted" in req.body) && !req.user.admin) return res.error(403, "You have not got permission for edit 'admin' and 'deleted' information, or bad request.");
|
||||||
const { names, desp } = req.app.get("limits");
|
const { names, desp } = req.app.get("limits");
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const { UserModel } = require("../models");
|
const { UserModel } = require("../models");
|
||||||
const { Router } = require("express")
|
const { Router } = require("express")
|
||||||
const bcrypt = require("bcrypt");
|
const bcrypt = require("bcrypt");
|
||||||
const { RL, transporter, emailRegEx, getGravatar } = require('../lib');
|
const { RL, transporter, emailRegEx } = require('../lib');
|
||||||
const app = Router();
|
const app = Router();
|
||||||
const { email_auth, forum_name, host } = require("../config.json");
|
const { email_auth, forum_name, host } = require("../config.json");
|
||||||
app.get("/", (req, res) => res.reply("register", { user: null, discord: req.app.get("discord_auth"), mail: email_auth }));
|
app.get("/", (req, res) => res.reply("register", { user: null, discord: req.app.get("discord_auth"), mail: email_auth }));
|
||||||
|
@ -14,14 +14,12 @@ app.post("/", RL(24 * 60 * 60_000, 5), async (req, res) => {
|
||||||
|
|
||||||
if (!name || !password) return res.error(400, "You forgot entering some values");
|
if (!name || !password) return res.error(400, "You forgot entering some values");
|
||||||
const { names } = req.app.get("limits");
|
const { names } = req.app.get("limits");
|
||||||
if (name.length < 3 || name.length > names) return res.error(400, "Name must be between 3 - 25 characters");
|
if (name.length < 3 || names > 25) return res.error(400, "Name must be between 3 - 25 characters");
|
||||||
if (password.length < 3 || password.length > names) return res.error(400, "Password must be between 3 - 25 characters");
|
if (password.length < 3 || names > 25) return res.error(400, "Password must be between 3 - 25 characters");
|
||||||
|
|
||||||
if (await UserModel.exists({ name })) return res.error(400, `We have got an user named ${name}!`)
|
if (await UserModel.exists({ name })) return res.error(400, `We have got an user named ${name}!`)
|
||||||
const user = new UserModel({ name });
|
const user = new UserModel({ name });
|
||||||
|
|
||||||
|
|
||||||
user.avatar = getGravatar(name, 128);
|
|
||||||
if (about) {
|
if (about) {
|
||||||
if (about.length > 256) return res.error(400, "about must be under 256 characters");
|
if (about.length > 256) return res.error(400, "about must be under 256 characters");
|
||||||
user.about = about;
|
user.about = about;
|
||||||
|
@ -45,7 +43,7 @@ app.post("/", RL(24 * 60 * 60_000, 5), async (req, res) => {
|
||||||
<h1>Verify your email in ${forum_name}-forum</h1>
|
<h1>Verify your email in ${forum_name}-forum</h1>
|
||||||
<a href="${host}/auth/email?code=${user.email_code}">Click here to verify your email</a>
|
<a href="${host}/auth/email?code=${user.email_code}">Click here to verify your email</a>
|
||||||
`
|
`
|
||||||
}, (err) => {
|
}, (err, info) => {
|
||||||
if (err) return res.error(500, "Failed to send email");
|
if (err) return res.error(500, "Failed to send email");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
<title><%= title || forum_name +"-forum" %></title>
|
<title><%= title || forum_name +"-forum" %></title>
|
||||||
<meta name="description" content="<%= description %>">
|
<meta name="description" content="<%= description %>">
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||||
<link rel="stylesheet" href="/css/themes/<%= theme.name %>.css" />
|
<link rel="stylesheet" href="/css/themes/<%= color %>.css" />
|
||||||
<link rel="stylesheet" href="/css/common.css" />
|
<link rel="stylesheet" href="/css/common.css" />
|
||||||
|
<% if (color === "black") { %>
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<% } %>
|
||||||
</head>
|
</head>
|
22
themes/default/index.js
Normal file
22
themes/default/index.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
const path = require('path');
|
||||||
|
module.exports = {
|
||||||
|
name: "default",
|
||||||
|
colors: ["black", "white"],
|
||||||
|
languages: ["en"],
|
||||||
|
getFilePath(page) {
|
||||||
|
return path.resolve(__dirname, page) ;// path of the file for ejs rendering
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Renderer function for theme
|
||||||
|
* @param {String} file a page of forum
|
||||||
|
* @param {{ user: Object }} data informations about page
|
||||||
|
* @param {{ color: String, forum_name:String, description:String }} options Extra options
|
||||||
|
* @param {Object} render request object
|
||||||
|
*/
|
||||||
|
render(file, data, options, req) {
|
||||||
|
// const { color, language, forum_name, description } = options; // General informations, meta, forum name, user language and color
|
||||||
|
// const { user } = data; // specific informations about page, user (req.user || null), and more
|
||||||
|
|
||||||
|
return req.render(this.getFilePath(file), { ...options, ...data });
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
Forum description:
|
Forum description:
|
||||||
<input type="text" name="description" value="Akf-forum!" required>
|
<input type="text" name="description" value="Akf-forum!" required>
|
||||||
Default state for new threads, change with "APPROVAL" for approval system:
|
Default state for new threads, change with "APPROVAL" for approval system:
|
||||||
<input type="text" name="default_thread_state" value="OPEN" required>
|
<input type="text" name="defaultThreadState" value="OPEN" required>
|
||||||
Domain of the forum, defaulty setted:
|
Domain of the forum, defaulty setted:
|
||||||
<input type="text" name="host" id="domain" value="Akf-forum!" required>
|
<input type="text" name="host" id="domain" value="Akf-forum!" required>
|
||||||
<hr>
|
<hr>
|
6
themes/index.js
Normal file
6
themes/index.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
for (const theme of fs.readdirSync("./themes")) {
|
||||||
|
if (theme === "index.js") continue;
|
||||||
|
module.exports[theme] = require(`./${theme}`);
|
||||||
|
}
|
Loading…
Reference in a new issue