mirror of
https://github.com/Akif9748/akf-forum.git
synced 2024-11-22 12:00:41 +03:00
Major update named as minor!
Forum Setup page, edit forum config w/api ban user's all ips, secretmodel deleted, /undelete is disabled and better themes if user reacted a message, view it fixs for reactions of messages discord auth in config.json
This commit is contained in:
parent
98d379d82c
commit
db61361a95
25 changed files with 228 additions and 153 deletions
|
@ -1,3 +1,2 @@
|
||||||
MONGO_DB_URL = mongodb://localhost:27017/akf-forum
|
MONGO_DB_URL = mongodb://localhost:27017/akf-forum
|
||||||
DISCORD_CLIENT = discord_app_id
|
|
||||||
SECRET = secret
|
SECRET = secret
|
|
@ -41,11 +41,9 @@ You can change them in config.json.
|
||||||
- GET `/:id` for fetch message.
|
- GET `/:id` for fetch message.
|
||||||
- DELETE `/:id` for delete message.
|
- DELETE `/:id` for delete message.
|
||||||
- PATCH `/:id` for edit message.
|
- PATCH `/:id` for edit message.
|
||||||
- POST `/:id/undelete` for undelete message.
|
|
||||||
- POST `/:id/react/:type` for react to a message.
|
- POST `/:id/react/:type` for react to a message.
|
||||||
- POST `/` for create message.
|
- POST `/` for create message.
|
||||||
|
|
||||||
|
|
||||||
#### `/api/search` use `?limit=&skip=` for skip and limit
|
#### `/api/search` use `?limit=&skip=` for skip and limit
|
||||||
- GET `/users?q=query` find users.
|
- GET `/users?q=query` find users.
|
||||||
- GET `/threads?q=query&authorID=not_required` find threads.
|
- GET `/threads?q=query&authorID=not_required` find threads.
|
||||||
|
@ -63,6 +61,7 @@ You can change them in config.json.
|
||||||
- DELETE `/:id` for delete user.
|
- DELETE `/:id` for delete user.
|
||||||
- PATCH `/:id` for edit user.
|
- PATCH `/:id` for edit user.
|
||||||
- PUT `/:id` for add profile photo to user.
|
- PUT `/:id` for add profile photo to user.
|
||||||
|
- POST `/:id/ban` for ban all ips of user.
|
||||||
|
|
||||||
### Example request:
|
### Example request:
|
||||||
GET ```/api/messages/0```
|
GET ```/api/messages/0```
|
||||||
|
|
31
README.md
31
README.md
|
@ -6,14 +6,14 @@ A Node.js based forum software.
|
||||||
- Run `npm i` to install **dependencies**.
|
- Run `npm i` to install **dependencies**.
|
||||||
- Enter your database credentials in `.env`.
|
- Enter your database credentials in `.env`.
|
||||||
- Run `npm start` for run it.
|
- Run `npm start` for run it.
|
||||||
|
- Go /setup page for setup your forum.
|
||||||
|
|
||||||
### Extra
|
### Extra (If you are not use `setup` page)
|
||||||
Run `node util/reset` to **reset the database** for duplicate key errors, and run `node util/admin` for give admin perms to first member.
|
Run `node util/reset` to **reset the database** for duplicate key errors, and run `node util/admin` for give admin perms to first member.
|
||||||
Edit `config.json` for default themes (`black` or `default`) of users, and forum name, meta description, character limits, discord auth enabler, global ratelimit.
|
Edit `config.json` for default themes (`black` or `default`) of users, and forum name, meta description, character limits, discord auth enabler, global ratelimit.
|
||||||
|
|
||||||
### DISCORD AUTH:
|
### DISCORD AUTH:
|
||||||
`discord_auth: true` in config.json.
|
`"discord_auth": "your_app_id"` in config.json.
|
||||||
Enter application id to `.env`.
|
|
||||||
Create a redirect url in discord developer portal:
|
Create a redirect url in discord developer portal:
|
||||||
`https://forum_url.com/discord_auth/hash`
|
`https://forum_url.com/discord_auth/hash`
|
||||||
|
|
||||||
|
@ -39,21 +39,20 @@ Akf-forum has got an API for AJAX (fetch), other clients etc. And, you can learn
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## TO-DO list
|
## TO-DO list
|
||||||
| To do | Is done? |
|
- Profile Message or DM
|
||||||
| ----- | -------- |
|
- mod role, permissions
|
||||||
| Profile Message or DM | ⚪ |
|
- upload other photos, model for it
|
||||||
| Better Auth for API way | 🟢 |
|
- categories page is need a update, thread count in category
|
||||||
| mod role, permissions | ⚪ |
|
- DC auth will store code for taking tokens, and create secret model setting
|
||||||
| upload other photos, model for it | ⚪ |
|
- Disable last seen button for web.
|
||||||
| categories page is need a update, thread count in category | ⚪ |
|
|
||||||
| preview for send messages in markdown format | 🟢 |
|
|
||||||
| DC auth will store code for taking tokens, and create secret model setting | ⚪ |
|
|
||||||
- IF a person liked a message, view.
|
|
||||||
- Disable last seen button.
|
|
||||||
- email auth.
|
- email auth.
|
||||||
- thread.state =="approval" for threads.
|
|
||||||
- old contents / titles add to forum interface
|
- old contents / titles add to forum interface
|
||||||
- limits
|
- add ban button to user profile.
|
||||||
|
- change password.
|
||||||
|
- add approval threads page.
|
||||||
|
- who liked a message for web.
|
||||||
|
- edit config from web admin panel.
|
||||||
|
|
||||||
## Major Version History
|
## Major Version History
|
||||||
- V4: Caching
|
- V4: Caching
|
||||||
- V3: New Theme
|
- V3: New Theme
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"max": 25,
|
"max": 25,
|
||||||
"windowMs": 60000
|
"windowMs": 60000
|
||||||
},
|
},
|
||||||
"discord_auth": false,
|
"discord_auth": "",
|
||||||
"defaultThreadState": "OPEN",
|
"defaultThreadState": "OPEN",
|
||||||
"host": "https://akf-forum.glitch.me"
|
"host": "https://akf-forum.glitch.me"
|
||||||
}
|
}
|
2
index.js
2
index.js
|
@ -44,7 +44,7 @@ app.use(express.static("public"), express.json(), express.urlencoded({extended:t
|
||||||
);
|
);
|
||||||
|
|
||||||
if (discord_auth)
|
if (discord_auth)
|
||||||
app.set("discord_auth", `https://discord.com/api/oauth2/authorize?client_id=${process.env.DISCORD_CLIENT}&redirect_uri=${host}%2Fdiscord_auth%2Fhash&response_type=token&scope=identify`);
|
app.set("discord_auth", `https://discord.com/api/oauth2/authorize?client_id=${discord_auth}&redirect_uri=${host}%2Fdiscord_auth%2Fhash&response_type=token&scope=identify`);
|
||||||
|
|
||||||
if (RLS.enabled) app.use(RL(RLS.windowMs, RLS.max));
|
if (RLS.enabled) app.use(RL(RLS.windowMs, RLS.max));
|
||||||
|
|
||||||
|
|
1
lib.js
1
lib.js
|
@ -2,6 +2,7 @@ const RL = require('express-rate-limit');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
threadEnum: ["OPEN", "APPROVAL", "DELETED"],
|
threadEnum: ["OPEN", "APPROVAL", "DELETED"],
|
||||||
|
themes: ["default", "black"],
|
||||||
RL(windowMs = 60_000, max = 1) {
|
RL(windowMs = 60_000, max = 1) {
|
||||||
return RL({
|
return RL({
|
||||||
windowMs, max, standardHeaders: true, legacyHeaders: false,
|
windowMs, max, standardHeaders: true, legacyHeaders: false,
|
||||||
|
|
|
@ -6,4 +6,5 @@ const schema = new mongoose.Schema({
|
||||||
authorID: { type: String }
|
authorID: { type: String }
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = mongoose.model('ban', schema);
|
const model = mongoose.model('ban', schema);
|
||||||
|
module.exports = model;
|
|
@ -13,8 +13,8 @@ const schema = new mongoose.Schema({
|
||||||
deleted: { type: Boolean, default: false },
|
deleted: { type: Boolean, default: false },
|
||||||
edited: { type: Boolean, default: false },
|
edited: { type: Boolean, default: false },
|
||||||
react: {
|
react: {
|
||||||
like: [Number],
|
like: [String],
|
||||||
dislike: [Number]
|
dislike: [String]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
const mongoose = require("mongoose")
|
|
||||||
const { limits } = require("../config.json");
|
|
||||||
|
|
||||||
const schema = new mongoose.Schema({
|
|
||||||
username: { type: String, unique: true, maxlength: limits.names },
|
|
||||||
password: String,
|
|
||||||
id: { type: String, unique: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = mongoose.model('secret', schema);
|
|
|
@ -2,7 +2,7 @@ const mongoose = require("mongoose")
|
||||||
const { def_theme, limits } = require("../config.json");
|
const { def_theme, limits } = require("../config.json");
|
||||||
const schema = new mongoose.Schema({
|
const schema = new mongoose.Schema({
|
||||||
id: { type: String, unique: true },
|
id: { type: String, unique: true },
|
||||||
discordID: { type: String, unique: true },
|
discordID: { type: String },
|
||||||
name: { type: String, maxlength: limits.names },
|
name: { type: String, maxlength: limits.names },
|
||||||
avatar: { type: String, default: "/images/avatars/default.jpg" },
|
avatar: { type: String, default: "/images/avatars/default.jpg" },
|
||||||
time: { type: Date, default: Date.now },
|
time: { type: Date, default: Date.now },
|
||||||
|
@ -13,7 +13,8 @@ const schema = new mongoose.Schema({
|
||||||
theme: { type: String, default: def_theme },
|
theme: { type: String, default: def_theme },
|
||||||
lastSeen: { type: Date, default: Date.now, select: false },
|
lastSeen: { type: Date, default: Date.now, select: false },
|
||||||
hideLastSeen: { type: Boolean, default: false },
|
hideLastSeen: { type: Boolean, default: false },
|
||||||
ips: { type: [String], select: false }
|
ips: { type: [String], default: [], select: false },
|
||||||
|
password: { type: String, select: false }
|
||||||
});
|
});
|
||||||
|
|
||||||
schema.methods.takeId = async function () {
|
schema.methods.takeId = async function () {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
const CategoryModel = require("./Category"),
|
module.exports = {
|
||||||
MessageModel = require("./Message"),
|
CategoryModel: require("./Category"),
|
||||||
ThreadModel = require("./Thread"),
|
MessageModel: require("./Message"),
|
||||||
SecretModel = require("./Secret"),
|
ThreadModel: require("./Thread"),
|
||||||
UserModel = require("./User"),
|
UserModel: require("./User"),
|
||||||
BanModel = require("./Ban");
|
BanModel: require("./Ban")
|
||||||
|
};
|
||||||
module.exports = { CategoryModel, MessageModel, ThreadModel, SecretModel, UserModel, BanModel };
|
|
26
public/css/setup.css
Normal file
26
public/css/setup.css
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
.title {
|
||||||
|
color: var(--main);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
input {
|
||||||
|
padding: 8px 10px;
|
||||||
|
font-family: inherit;
|
||||||
|
display: block;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--input-clr);
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 2px solid var(--borders);
|
||||||
|
}
|
|
@ -53,7 +53,7 @@ window.edit_message = async function (id) {
|
||||||
editing.value(content.rawText)
|
editing.value(content.rawText)
|
||||||
}
|
}
|
||||||
window.undelete_message = async function (id) {
|
window.undelete_message = async function (id) {
|
||||||
const response = await request(`/api/messages/${id}/undelete`);
|
const response = await request(`/api/messages/${id}/`, "PATCH", { deleted: false });
|
||||||
if (response.deleted) return;
|
if (response.deleted) return;
|
||||||
const message = document.getElementById("message-" + id);
|
const message = document.getElementById("message-" + id);
|
||||||
|
|
||||||
|
|
30
routes/.js
30
routes/.js
|
@ -1,17 +1,45 @@
|
||||||
const { UserModel, ThreadModel, MessageModel } = require("../models")
|
const { UserModel, ThreadModel, MessageModel } = require("../models")
|
||||||
const { Router } = require("express");
|
const { Router } = require("express");
|
||||||
const app = Router();
|
const app = Router();
|
||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
app.get("/", async (req, res) => {
|
app.get("/", async (req, res) => {
|
||||||
|
|
||||||
const
|
const
|
||||||
mem = process.memoryUsage().heapUsed / Math.pow(2, 20),
|
mem = process.memoryUsage().heapUsed / Math.pow(2, 20),
|
||||||
users = await UserModel.count({ deleted: false }),
|
users = await UserModel.count({ deleted: false }),
|
||||||
threads = await ThreadModel.count({ deleted: false }),
|
threads = await ThreadModel.count({ state: "DELETED"}),
|
||||||
messages = await MessageModel.count({ deleted: false });
|
messages = await MessageModel.count({ deleted: false });
|
||||||
|
|
||||||
res.reply("index", { mem, users, threads, messages })
|
res.reply("index", { mem, users, threads, messages })
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.get("/setup", async (req, res) => {
|
||||||
|
if (await UserModel.exists({ admin: true })) return res.error(400, "You have already setuped the site.");
|
||||||
|
res.reply("setup");
|
||||||
|
})
|
||||||
|
app.post("/setup", async (req, res) => {
|
||||||
|
if (await UserModel.exists({ admin: true })) return res.error(400, "You have already setuped the site.");
|
||||||
|
let original = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
original = JSON.parse(fs.readFileSync("./config.json", "utf8"));
|
||||||
|
} catch (e) {
|
||||||
|
try {
|
||||||
|
original = JSON.parse(fs.readFileSync("./config.json.example", "utf8"));
|
||||||
|
} catch (e) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = req.body;
|
||||||
|
|
||||||
|
for (const key in content)
|
||||||
|
if (key in original && content[key])
|
||||||
|
original[key] = content[key];
|
||||||
|
|
||||||
|
fs.writeFileSync("./config.json", JSON.stringify(original,null,4));
|
||||||
|
require.cache[require.resolve("../config.json")] = require("../config.json");
|
||||||
|
res.redirect("/register");
|
||||||
|
})
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
|
@ -1,36 +1,29 @@
|
||||||
const { Router, request, response } = require("express");
|
const { Router } = require("express");
|
||||||
const app = Router();
|
const app = Router();
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const bcrypt = require("bcrypt");
|
const bcrypt = require("bcrypt");
|
||||||
const { SecretModel, UserModel } = require("../../models");
|
const { UserModel } = require("../../models");
|
||||||
|
|
||||||
/**
|
|
||||||
* Auth checker
|
|
||||||
* @param {request} req
|
|
||||||
* @param {response} res
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.use(async (req, res, next) => {
|
app.use(async (req, res, next) => {
|
||||||
res.error = (status, error) => res.status(status).json({ error });
|
res.error = (status, error) => res.status(status).json({ error });
|
||||||
|
|
||||||
res.complate = result => res.status(200).json(result);
|
res.complate = result => res.status(200).json(result);
|
||||||
|
|
||||||
if (req.user) return next();
|
if (req.user) return next();
|
||||||
const authHeader = req.headers.authorization;
|
const authHeader = req.headers.authorization;
|
||||||
if (!authHeader) return res.error(401, "No authorization header");
|
if (!authHeader) return res.error(401, "No authorization header");
|
||||||
const [username, password] = Buffer.from(authHeader.split(' ')[1], "base64").toString().split(":");
|
const [name, password] = Buffer.from(authHeader.split(' ')[1], "base64").toString().split(":");
|
||||||
|
|
||||||
if (!username || !password)
|
if (!name || !password)
|
||||||
return res.error(401, "Authorise headers are missing")
|
return res.error(400, "Authorise headers are not well formed");
|
||||||
|
|
||||||
const user = await SecretModel.findOne({ username });
|
const user = await UserModel.findOne({ name });
|
||||||
|
|
||||||
if (!user)
|
if (!user || user.deleted) return res.error(401, `We don't have any user with name ${name}.`)
|
||||||
return res.error(401, `We don't have any thread with name ${username}.`)
|
|
||||||
|
|
||||||
if (!await bcrypt.compare(password, user.password)) return res.error(401, 'Incorrect Password!');
|
if (!await bcrypt.compare(password, user.password)) return res.error(401, 'Incorrect Password!');
|
||||||
|
|
||||||
req.user = await UserModel.findOne({ name: req.headers.username });
|
req.user = user;
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
24
routes/api/routes/config.js
Normal file
24
routes/api/routes/config.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
const { Router } = require("express")
|
||||||
|
const fs = require("fs");
|
||||||
|
const app = Router();
|
||||||
|
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
if (!req.user.admin)
|
||||||
|
return res.error(403, "You have not got permission for this.");
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/", (req, res) => {
|
||||||
|
try {
|
||||||
|
return res.reply(JSON.parse(fs.readFileSync("./config.json", "utf8")));
|
||||||
|
} catch (e) {
|
||||||
|
res.error(500, e.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
app.post("/", (req, res) => {
|
||||||
|
fs.writeFileSync("./config.json", JSON.stringify(req.body, null, 4));
|
||||||
|
require.cache[require.resolve("../config.json")] = require("../../../config.json");
|
||||||
|
res.complate(require("../../../config.json"));
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = app;
|
|
@ -18,18 +18,35 @@ app.param("id", async (req, res, next, id) => {
|
||||||
|
|
||||||
app.get("/:id", async (req, res) => res.complate(req.message));
|
app.get("/:id", async (req, res) => res.complate(req.message));
|
||||||
|
|
||||||
|
app.delete("/:id/", async (req, res) => {
|
||||||
|
|
||||||
|
|
||||||
|
const { message, user } = req;
|
||||||
|
|
||||||
|
if (user.id != message.authorID && !user.admin)
|
||||||
|
return res.error(403, "You have not got permission for this.");
|
||||||
|
if (message.deleted) return res.error(403, "This message is already deleted.");
|
||||||
|
|
||||||
|
message.deleted = true;
|
||||||
|
await message.save()
|
||||||
|
res.complate(message);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
app.patch("/:id/", async (req, res) => {
|
app.patch("/:id/", async (req, res) => {
|
||||||
|
|
||||||
|
|
||||||
const { message, user } = req;
|
const { message, user } = req;
|
||||||
|
|
||||||
if (user.id !== message.authorID && !user.admin) return res.error(403, "You have not got permission for this.");
|
if (user.id !== message.authorID && !user.admin) return res.error(403, "You have not got permission for this.");
|
||||||
const { content = null } = req.body;
|
if (!Object.values(req.body).some(Boolean)) return res.error(400, "Missing message informations for update in request body.");
|
||||||
if (!content) return res.error(400, "Missing message content in request body.");
|
const { content, deleted } = req.body;
|
||||||
|
|
||||||
const limits = req.app.get("limits");
|
const limits = req.app.get("limits");
|
||||||
if (content.length < 5 || content.length > limits.message) return res.error(400, `content must be between 5 - ${limits.message} characters`);
|
if (content.length < 5 || content.length > limits.message) return res.error(400, `content must be between 5 - ${limits.message} characters`);
|
||||||
|
|
||||||
|
if (deleted === false) message.deleted = false;
|
||||||
|
|
||||||
message.content = content;
|
message.content = content;
|
||||||
|
|
||||||
if (!message.oldContents.includes(content))
|
if (!message.oldContents.includes(content))
|
||||||
|
@ -93,33 +110,4 @@ app.post("/:id/react/:type", async (req, res) => {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.delete("/:id/", async (req, res) => {
|
|
||||||
|
|
||||||
|
|
||||||
const { message, user } = req;
|
|
||||||
|
|
||||||
if (user.id != message.authorID && !user.admin)
|
|
||||||
return res.error(403, "You have not got permission for this.");
|
|
||||||
if (message.deleted) return res.error(403, "This message is already deleted.");
|
|
||||||
|
|
||||||
message.deleted = true;
|
|
||||||
await message.save();
|
|
||||||
res.complate(message);
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post("/:id/undelete", async (req, res) => {
|
|
||||||
|
|
||||||
|
|
||||||
const { message } = req;
|
|
||||||
|
|
||||||
if (!message.deleted) return res.error(404, "This message is not deleted, first, delete it.");
|
|
||||||
|
|
||||||
message.deleted = false;
|
|
||||||
await message.save();
|
|
||||||
|
|
||||||
res.complate(message);
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
|
@ -1,11 +1,12 @@
|
||||||
const { UserModel, SecretModel } = require("../../../models");
|
const { UserModel, BanModel } = require("../../../models");
|
||||||
const { Router } = require("express");
|
const { Router } = require("express");
|
||||||
const multer = require("multer");
|
const multer = require("multer");
|
||||||
|
const { themes } = require("../../../lib");
|
||||||
|
|
||||||
const app = Router();
|
const app = Router();
|
||||||
|
|
||||||
app.param("id", async (req, res, next, id) => {
|
app.param("id", async (req, res, next, id) => {
|
||||||
req.member = await UserModel.get(id, req.user.admin ? "+lastSeen" : "");
|
req.member = await UserModel.get(id, req.user.admin ? "+lastSeen +ips" : "");
|
||||||
|
|
||||||
if (!req.member) return res.error(404, `We don't have any user with id ${id}.`);
|
if (!req.member) return res.error(404, `We don't have any user with id ${id}.`);
|
||||||
|
|
||||||
|
@ -17,37 +18,21 @@ app.param("id", async (req, res, next, id) => {
|
||||||
|
|
||||||
app.get("/:id", async (req, res) => res.complate(req.member));
|
app.get("/:id", async (req, res) => res.complate(req.member));
|
||||||
|
|
||||||
app.delete("/:id/", async (req, res) => {
|
app.delete("/:id", async (req, res) => {
|
||||||
const { user, member } = req;
|
const { user, member } = req;
|
||||||
|
|
||||||
if (!user.admin)
|
if (!user.admin)
|
||||||
return res.error(403, "You have not got permission for this.");
|
return res.error(403, "You have not got permission for this.");
|
||||||
|
|
||||||
const { id = null } = req.params;
|
if (member.deleted) return res.error(404, `This user is with id ${member.id} already deleted.`);
|
||||||
|
|
||||||
if (member.deleted) return res.error(404, `This user is with id ${id} already deleted.`);
|
|
||||||
|
|
||||||
member.deleted = true;
|
member.deleted = true;
|
||||||
await member.save();
|
await member.save();
|
||||||
|
|
||||||
res.complate(member);
|
res.complate(member);
|
||||||
});
|
});
|
||||||
app.post("/:id/undelete/", async (req, res) => {
|
|
||||||
if (!req.user.admin) return res.error(403, "You have not got permission for this.");
|
|
||||||
|
|
||||||
const { user, member } = req;
|
app.patch("/:id", async (req, res) => {
|
||||||
|
|
||||||
if (!member) return res.error(404, `We don't have any user with id ${req.params.id}.`);
|
|
||||||
|
|
||||||
if (!member.deleted) return res.error(404, "This user is not deleted, first, delete it.");
|
|
||||||
|
|
||||||
member.deleted = false;
|
|
||||||
|
|
||||||
res.complate(await member.save());
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
app.patch("/:id/", async (req, res) => {
|
|
||||||
const { user, member } = req;
|
const { user, member } = req;
|
||||||
|
|
||||||
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.");
|
||||||
|
@ -59,9 +44,7 @@ app.patch("/:id/", async (req, res) => {
|
||||||
const { names, desp } = req.app.get("limits");
|
const { names, desp } = req.app.get("limits");
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
|
|
||||||
if (name.length < 3 || names > 25) return res.error(400, `Username must be between 3 - ${names} characters`);
|
if (name.length < 3 || names > 25) return res.error(400, `Username must be between 3 - ${names} characters`);
|
||||||
await SecretModel.updateOne({ id: member.id }, { username: name });
|
|
||||||
member.name = name;
|
member.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +52,7 @@ app.patch("/:id/", async (req, res) => {
|
||||||
if (about.length > desp) return res.error(400, `About must be under ${desp} characters`);
|
if (about.length > desp) return res.error(400, `About must be under ${desp} characters`);
|
||||||
member.about = about;
|
member.about = about;
|
||||||
}
|
}
|
||||||
if (theme || ["default", "black"].includes(theme)) member.theme = theme;
|
if (theme || themes.includes(theme)) member.theme = theme;
|
||||||
|
|
||||||
if (typeof admin === "boolean" || ["false", "true"].includes(admin)) member.admin = admin;
|
if (typeof admin === "boolean" || ["false", "true"].includes(admin)) member.admin = admin;
|
||||||
if (deleted === false) member.deleted = false;
|
if (deleted === false) member.deleted = false;
|
||||||
|
@ -78,6 +61,22 @@ app.patch("/:id/", async (req, res) => {
|
||||||
res.complate(await member.save());
|
res.complate(await member.save());
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.post("/:id/ban", async (req, res) => {
|
||||||
|
if (!req.user.admin) return res.error(403, "You have not got permission for this.");
|
||||||
|
const { member } = req;
|
||||||
|
for (const ip of member.ips)
|
||||||
|
try {
|
||||||
|
await BanModel.create({ ip, reason: `Ban for ${member.name}`, authorID: req.user.id });
|
||||||
|
req.app.ips.push(ip);
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.complate(member);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
const storage = multer.diskStorage({
|
const storage = multer.diskStorage({
|
||||||
destination: function (_req, _file, cb) {
|
destination: function (_req, _file, cb) {
|
||||||
cb(null, './public/images/avatars')
|
cb(null, './public/images/avatars')
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const { UserModel, SecretModel } = require("../models");
|
const { UserModel } = require("../models");
|
||||||
const { Router } = require("express");
|
const { Router } = require("express");
|
||||||
const app = Router();
|
const app = Router();
|
||||||
const bcrypt = require("bcrypt");
|
const bcrypt = require("bcrypt");
|
||||||
|
@ -8,19 +8,15 @@ app.get("/", (req, res) => res.reply("login", { redirect: req.query.redirect, us
|
||||||
app.post("/", async (req, res) => {
|
app.post("/", async (req, res) => {
|
||||||
req.session.userID = null;
|
req.session.userID = null;
|
||||||
|
|
||||||
const { username = null, password = null } = req.body;
|
const { name, password } = req.body;
|
||||||
|
|
||||||
if (!username || !password)
|
if (!name || !password) return res.error(400, "You forgot entering some values")
|
||||||
return res.error(400, "You forgot entering some values")
|
|
||||||
|
|
||||||
const user = await SecretModel.findOne({ username });
|
const member = await UserModel.findOne({ name }, "+password");
|
||||||
if (!user) return res.error(403, 'Incorrect Username and/or Password!');
|
if (!member || member.deleted) return res.error(403, 'Incorrect username!');
|
||||||
|
if (!await bcrypt.compare(password, member.password)) return res.error(403, 'Incorrect password!');
|
||||||
|
|
||||||
if (!await bcrypt.compare(password, user.password)) return res.error(403, 'Incorrect Password!')
|
req.session.userID = member.id;
|
||||||
const member = await UserModel.findOne({ name: username });
|
|
||||||
if (!member || member.deleted) return res.error(403, 'Incorrect Username and/or Password!')
|
|
||||||
|
|
||||||
req.session.userID = user.id;
|
|
||||||
|
|
||||||
res.redirect(req.query.redirect || '/');
|
res.redirect(req.query.redirect || '/');
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const { UserModel, SecretModel } = require("../models");
|
const { UserModel } = require("../models");
|
||||||
const { Router } = require("express")
|
const { Router } = require("express")
|
||||||
const bcrypt = require("bcrypt");
|
const bcrypt = require("bcrypt");
|
||||||
const { RL } = require('../lib');
|
const { RL } = require('../lib');
|
||||||
|
@ -10,37 +10,31 @@ app.post("/", RL(24 * 60 * 60_000, 5), async (req, res) => {
|
||||||
|
|
||||||
req.session.userID = null;
|
req.session.userID = null;
|
||||||
|
|
||||||
let { username, password: body_pass, about } = req.body;
|
let { name, password, about } = req.body;
|
||||||
|
|
||||||
if (!username || !body_pass) 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 (username.length < 3 || names > 25) return res.error(400, "Username must be between 3 - 25 characters");
|
if (name.length < 3 || names > 25) return res.error(400, "Name must be between 3 - 25 characters");
|
||||||
if (body_pass.length < 3 || names > 25) 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");
|
||||||
|
|
||||||
const user = await SecretModel.findOne({ username });
|
if (await UserModel.exists({ name })) return res.error(400, `We have got an user named ${name}!`)
|
||||||
|
const user = new UserModel({ name });
|
||||||
if (user) return res.error(400, `We have got an user named ${username}!`)
|
|
||||||
|
|
||||||
const user2 = new UserModel({ name: username })
|
|
||||||
|
|
||||||
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");
|
||||||
user2.about = about;
|
user.about = about;
|
||||||
}
|
}
|
||||||
|
|
||||||
await user2.takeId()
|
await user.takeId()
|
||||||
await user2.save();
|
if (user.id === "0") user.admin = true;
|
||||||
|
|
||||||
const salt = await bcrypt.genSalt(10);
|
user.password = await bcrypt.hash(password, await bcrypt.genSalt(10));
|
||||||
const password = await bcrypt.hash(body_pass, salt);
|
await user.save();
|
||||||
await SecretModel.create({ username, password, id: user2.id })
|
|
||||||
req.session.userID = user2.id;
|
req.session.userID = user.id;
|
||||||
|
|
||||||
res.redirect('/');
|
res.redirect('/');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
<form action="/login?redirect=<%= redirect !== "/register" ? redirect : "/" %>" method="post">
|
<form action="/login?redirect=<%= redirect !== "/register" ? redirect : "/" %>" method="post">
|
||||||
|
|
||||||
<input type="text" name="username" placeholder="Username" class="input" required>
|
<input type="text" name="name" placeholder="Username" class="input" required>
|
||||||
<input type="password" name="password" placeholder="Password" class="input" required>
|
<input type="password" name="password" placeholder="Password" class="input" required>
|
||||||
<input type="submit" style="width:100%" class="btn-primary" value="Login">
|
<input type="submit" style="width:100%" class="btn-primary" value="Login">
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
|
|
||||||
<form action="/register" method="post">
|
<form action="/register" method="post">
|
||||||
<input type="text" name="username" placeholder="Username" class="input" required>
|
<input type="text" name="name" placeholder="Username" class="input" required>
|
||||||
<input type="password" name="password" placeholder="Password" class="input" required>
|
<input type="password" name="password" placeholder="Password" class="input" required>
|
||||||
<textarea class="input" name="about" rows="4" placeholder="About you... You can use markdown"></textarea>
|
<textarea class="input" name="about" rows="4" placeholder="About you... You can use markdown"></textarea>
|
||||||
<input type="submit" class="btn-primary" style="width:100%;" value="Register">
|
<input type="submit" class="btn-primary" style="width:100%;" value="Register">
|
||||||
|
|
34
views/setup.ejs
Normal file
34
views/setup.ejs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<%- include("extra/meta", {title: "Setup the Akf-forum!" }) %>
|
||||||
|
|
||||||
|
|
||||||
|
<body style="text-align: center;">
|
||||||
|
<%- include("extra/navbar") %>
|
||||||
|
<link rel="stylesheet" href="/css/setup.css">
|
||||||
|
<h1 style="color: var(--main);">Setup</h1>
|
||||||
|
<h2 style="color: var(--second);">There is default settings for akf-forum, you not need to edit them, but you can! And, the first registered user will be admin.</h2>
|
||||||
|
<form method="POST">
|
||||||
|
Default theme:
|
||||||
|
<input type="text" name="def_theme" value="default" required>
|
||||||
|
Forum name:
|
||||||
|
<input type="text" name="forum_name" value="akf" required>
|
||||||
|
Forum description:
|
||||||
|
<input type="text" name="description" value="Akf-forum!" required>
|
||||||
|
Default state for new threads, change with "APPROVAL" for approval system:
|
||||||
|
<input type="text" name="defaultThreadState" value="OPEN" required>
|
||||||
|
Domain of the forum, defaulty setted:
|
||||||
|
<input type="text" name="host" id="domain" value="Akf-forum!" required>
|
||||||
|
<hr>
|
||||||
|
(Optional) Discord app ID for Discord login:
|
||||||
|
<input type="text" name="discord_auth">
|
||||||
|
|
||||||
|
<input type="submit" class="btn-primary" value="Setup">
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
|
document.getElementById("domain").value = location.origin;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -78,11 +78,15 @@
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<div class="reactions">
|
<div class="reactions">
|
||||||
<div>
|
<div <% if (message.react.like.includes(user?.id)) { %>
|
||||||
|
style="color: var(--main)"
|
||||||
|
<% } %> >
|
||||||
<i onclick='react("<%= message.id %>","like");' class='bx bx-like'></i>
|
<i onclick='react("<%= message.id %>","like");' class='bx bx-like'></i>
|
||||||
<div id="like"><%=message.react.like.length %></div>
|
<div id="like"><%=message.react.like.length %></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div <% if (message.react.dislike.includes(user?.id)) { %>
|
||||||
|
style="color: var(--main)"
|
||||||
|
<% } %>>
|
||||||
<i onclick='react("<%= message.id %>","dislike");' class='bx bx-dislike'></i>
|
<i onclick='react("<%= message.id %>","dislike");' class='bx bx-dislike'></i>
|
||||||
<div id="dislike"><%=message.react.dislike.length %></div>
|
<div id="dislike"><%=message.react.dislike.length %></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
alert("User is deleted!");
|
alert("User is deleted!");
|
||||||
location.reload()
|
location.reload()
|
||||||
} else if (e.target.id == "undelete") {
|
} else if (e.target.id == "undelete") {
|
||||||
const response = await request("/api/users/<%= member.id %>/undelete");
|
const response = await request("/api/users/<%= member.id %>/", "PATCH", { deleted: false });
|
||||||
if (response.deleted) return;
|
if (response.deleted) return;
|
||||||
alert("User is undeleted successfully!");
|
alert("User is undeleted successfully!");
|
||||||
location.reload()
|
location.reload()
|
||||||
|
|
Loading…
Reference in a new issue