mirror of
https://github.com/Akif9748/akf-forum.git
synced 2024-11-22 20:10:40 +03:00
Page is OK! Edits, About me, ip ban
This commit is contained in:
parent
5b020ff548
commit
5c259f02f3
23 changed files with 4096 additions and 102 deletions
|
@ -20,18 +20,21 @@ But in front end, the API will works with session.
|
||||||
- POST `/api/users/:id/delete` for delete user.
|
- POST `/api/users/:id/delete` for delete user.
|
||||||
- POST `/api/users/:id/undelete` for undelete user.
|
- POST `/api/users/:id/undelete` for undelete user.
|
||||||
- POST `/api/users/:id/admin` for give admin permissions for a user.
|
- POST `/api/users/:id/admin` for give admin permissions for a user.
|
||||||
|
- POST `/api/users/:id/edit` for edit user.
|
||||||
|
|
||||||
- GET `/api/threads/:id` for fetch thread.
|
- GET `/api/threads/:id` for fetch thread.
|
||||||
- GET `/api/threads/:id/messages/` for fetch messages in thread.
|
- GET `/api/threads/:id/messages/` for fetch messages in thread.
|
||||||
- POST `/api/threads` for create thread.
|
- POST `/api/threads` for create thread.
|
||||||
- POST `/api/threads/:id/delete` for delete thread.
|
- POST `/api/threads/:id/delete` for delete thread.
|
||||||
- POST `/api/threads/:id/undelete` for undelete thread.
|
- POST `/api/threads/:id/undelete` for undelete thread.
|
||||||
|
- POST `/api/threads/:id/edit` for edit thread.
|
||||||
|
|
||||||
- GET `/api/messages/:id` for fetch message.
|
- GET `/api/messages/:id` for fetch message.
|
||||||
- POST `/api/messages` for create message.
|
- POST `/api/messages` for create message.
|
||||||
- POST `/api/messages/:id/delete` for delete message.
|
- POST `/api/messages/:id/delete` for delete message.
|
||||||
- POST `/api/messages/:id/undelete` for undelete message.
|
- POST `/api/messages/:id/undelete` for undelete message.
|
||||||
- POST `/api/messages/:id/react/:type` for react to a message.
|
- POST `/api/messages/:id/react/:type` for react to a message.
|
||||||
|
- POST `/api/messages/:id/edit` for edit message.
|
||||||
|
|
||||||
### Example request:
|
### Example request:
|
||||||
GET ```/api/messages/0```
|
GET ```/api/messages/0```
|
||||||
|
|
34
README.md
34
README.md
|
@ -31,17 +31,15 @@ Akf-forum has got an API for AJAX, other clients etc. And, you can learn about A
|
||||||
### TO-DO:
|
### TO-DO:
|
||||||
- If thread deleted, not show its messages in API.
|
- If thread deleted, not show its messages in API.
|
||||||
- Profile photos will store in database
|
- Profile photos will store in database
|
||||||
- regex for pfp for now and
|
- replacer function global
|
||||||
- admin perm for undelete, thread + message
|
|
||||||
- page support for threads, send, if multi page, send => other page
|
|
||||||
- message "<b>"
|
|
||||||
- author name of thread
|
- author name of thread
|
||||||
- page for threads - users
|
- page for threads - users []
|
||||||
- API, ?fast=
|
- API, ?fast=
|
||||||
- fix error messages, ~~declared as id~~, other...
|
- extra ratelimits
|
||||||
|
- better edits
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
### User
|
#### User
|
||||||
| To do | Is done? | Priority |
|
| To do | Is done? | Priority |
|
||||||
| ----- | -------- | -------- |
|
| ----- | -------- | -------- |
|
||||||
| Login via redirect query | 🟢 | HIGH |
|
| Login via redirect query | 🟢 | HIGH |
|
||||||
|
@ -51,12 +49,12 @@ Akf-forum has got an API for AJAX, other clients etc. And, you can learn about A
|
||||||
| Message count | 🟢 | MEDIUM |
|
| Message count | 🟢 | MEDIUM |
|
||||||
| Delete user | 🟢 | HIGH |
|
| Delete user | 🟢 | HIGH |
|
||||||
| Undelete | 🟢 | MEDIUM |
|
| Undelete | 🟢 | MEDIUM |
|
||||||
| PM | 🔴 | MEDIUM |
|
| About me | 🟢 | LOW |
|
||||||
| About me | 🔴 | LOW |
|
| Edit user | 🟡 | HIGH |
|
||||||
| Edit user | 🔴 | HIGH |
|
| IP ban | 🟢 | MEDIUM |
|
||||||
| IP ban | 🔴 | MEDIUM |
|
| Profile Message | 🔴 | MEDIUM |
|
||||||
|
|
||||||
### Messages
|
#### Messages
|
||||||
| To do | Is done? | Priority |
|
| To do | Is done? | Priority |
|
||||||
| ----- | -------- | -------- |
|
| ----- | -------- | -------- |
|
||||||
| Ratelimit | 🟢 | HIGH |
|
| Ratelimit | 🟢 | HIGH |
|
||||||
|
@ -65,16 +63,16 @@ Akf-forum has got an API for AJAX, other clients etc. And, you can learn about A
|
||||||
| Regex for scripts | 🟢 | HIGH |
|
| Regex for scripts | 🟢 | HIGH |
|
||||||
| Undelete | 🟢 | MEDIUM |
|
| Undelete | 🟢 | MEDIUM |
|
||||||
| React | 🟢 | MEDIUM |
|
| React | 🟢 | MEDIUM |
|
||||||
| Edit | 🔴 | MEDIUM |
|
| Edit | 🟢 | MEDIUM |
|
||||||
|
|
||||||
### Threads
|
#### Threads
|
||||||
| To do | Is done? | Priority |
|
| To do | Is done? | Priority |
|
||||||
| ----- | -------- | -------- |
|
| ----- | -------- | -------- |
|
||||||
| Ratelimit | 🟢 | HIGH |
|
| Ratelimit | 🟢 | HIGH |
|
||||||
| Create | 🟢 | HIGH |
|
| Create | 🟢 | HIGH |
|
||||||
| Delete | 🟢 | HIGH |
|
| Delete | 🟢 | HIGH |
|
||||||
| Undelete | 🟢 | MEDIUM |
|
| Undelete | 🟢 | MEDIUM |
|
||||||
| Edit | 🔴 | MEDIUM |
|
| Edit | 🟢 | MEDIUM |
|
||||||
|
|
||||||
### API
|
### API
|
||||||
| To do | Is done?
|
| To do | Is done?
|
||||||
|
@ -85,17 +83,17 @@ Akf-forum has got an API for AJAX, other clients etc. And, you can learn about A
|
||||||
| Get message & thread & user | 🟢
|
| Get message & thread & user | 🟢
|
||||||
| Delete message & thread & user | 🟢
|
| Delete message & thread & user | 🟢
|
||||||
| Undelete message & thread & user | 🟢
|
| Undelete message & thread & user | 🟢
|
||||||
| Edit message & thread & user | 🔴
|
| Edit message & thread & user | 🟢
|
||||||
|
|
||||||
### Other
|
### Other
|
||||||
| To do | Is done? | Priority |
|
| To do | Is done? | Priority |
|
||||||
| ----- | -------- | -------- |
|
| ----- | -------- | -------- |
|
||||||
| from form to AJAX | 🟢 | HIGH |
|
| from form to AJAX | 🟢 | HIGH |
|
||||||
| auto-scroll | 🟢 | LOW |
|
| auto-scroll | 🟢 | LOW |
|
||||||
|
| Page support, support message limit correct | 🟢 | MEDIUM |
|
||||||
| Multi-theme support, black theme | 🟡 | LOW |
|
| Multi-theme support, black theme | 🟡 | LOW |
|
||||||
| Search | 🔴 | MEDIUM |
|
| Search | 🔴 | MEDIUM |
|
||||||
| Page support, support message limit correct | 🔴 | MEDIUM |
|
| Locales | 🔴 | LOW |
|
||||||
| Locales | 🔴 | MEDIUM |
|
|
||||||
| Footer | 🔴 | LOW |
|
| Footer | 🔴 | LOW |
|
||||||
## Major Version History
|
## Major Version History
|
||||||
- V3: New Theme
|
- V3: New Theme
|
||||||
|
|
44
index.js
44
index.js
|
@ -1,37 +1,43 @@
|
||||||
const { def_theme } = require("./config.json"),
|
const { def_theme } = require("./config.json"),
|
||||||
session = require('express-session'),
|
ipBlock = require('express-ip-block'),
|
||||||
{ UserModel } = require("./models"),
|
session = require('express-session'),
|
||||||
bodyParser = require('body-parser'),
|
{ UserModel, BanModel } = require("./models"),
|
||||||
port = process.env.PORT || 3000,
|
bodyParser = require('body-parser'),
|
||||||
mongoose = require("mongoose"),
|
port = process.env.PORT || 3000,
|
||||||
express = require('express'),
|
mongoose = require("mongoose"),
|
||||||
fs = require("fs"),
|
express = require('express'),
|
||||||
app = express();
|
fs = require("fs"),
|
||||||
|
app = express();
|
||||||
|
app.ips = [];
|
||||||
|
|
||||||
require("dotenv").config();
|
require("dotenv").config();
|
||||||
mongoose.connect(process.env.MONGO_DB_URL, () => console.log("Database is connected"));
|
mongoose.connect(process.env.MONGO_DB_URL,
|
||||||
|
async () => console.log("Connected to mongoDB with", app.ips = await BanModel.find({}).select("ip"), "banned IPs"));
|
||||||
|
|
||||||
app.use(session({ secret: 'secret', resave: true, saveUninitialized: true }));
|
|
||||||
app.use(bodyParser.urlencoded({ extended: true }));
|
|
||||||
app.use(express.static("public"));
|
|
||||||
app.set("view engine", "ejs");
|
app.set("view engine", "ejs");
|
||||||
app.use(express.json());
|
|
||||||
app.use(async (req, res, next) => {
|
app.use(session({ secret: 'secret', resave: true, saveUninitialized: true }),
|
||||||
|
bodyParser.urlencoded({ extended: true }),
|
||||||
|
express.static("public"), express.json(), ipBlock(app.ips),
|
||||||
|
async (req, res, next) => {
|
||||||
req.user = await UserModel.get(req.session.userid);
|
req.user = await UserModel.get(req.session.userid);
|
||||||
res.reply = (page, options = {}, status = 200) => res.status(status)
|
res.reply = (page, options = {}, status = 200) => res.status(status)
|
||||||
.render(page, { user: req.user, theme: req.user?.theme || def_theme, ...options });
|
.render(page, { user: req.user, theme: req.user?.theme || def_theme, ...options });
|
||||||
|
|
||||||
res.error = (type, error) => res.reply("error", { type, error }, type);
|
res.error = (type, error) => res.reply("error", { type, error }, type);
|
||||||
|
|
||||||
if (req.user?.deleted) {
|
if (req.user?.deleted) {
|
||||||
req.session.destroy();
|
req.session.destroy();
|
||||||
return res.error(403, "Your account has been deleted.");
|
return res.error(403, "Your account has been deleted.");
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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, "We have not got this page."));
|
app.all("*", (req, res) => res.error(404, "We have not got this page."));
|
||||||
|
|
||||||
|
|
7
models/Ban.js
Normal file
7
models/Ban.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
const mongoose = require("mongoose")
|
||||||
|
|
||||||
|
const schema = new mongoose.Schema({
|
||||||
|
ip: { type: String, unique: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model('ban', schema);
|
|
@ -14,9 +14,7 @@ const schema = new mongoose.Schema({
|
||||||
react: {
|
react: {
|
||||||
like: [Number],
|
like: [Number],
|
||||||
dislike: [Number]
|
dislike: [Number]
|
||||||
},
|
}
|
||||||
index: { type: Number, default: 0 }
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
schema.virtual('authorID').get(function () { return this.author?.id; });
|
schema.virtual('authorID').get(function () { return this.author?.id; });
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const mongoose = require("mongoose")
|
const mongoose = require("mongoose");
|
||||||
const UserModel = require("./User");
|
const UserModel = require("./User");
|
||||||
|
const MessageModel = require("./Message");
|
||||||
const schema = new mongoose.Schema({
|
const schema = new mongoose.Schema({
|
||||||
id: { type: String, unique: true },
|
id: { type: String, unique: true },
|
||||||
|
|
||||||
|
@ -8,13 +9,19 @@ const schema = new mongoose.Schema({
|
||||||
title: String,
|
title: String,
|
||||||
time: { type: Date, default: Date.now },
|
time: { type: Date, default: Date.now },
|
||||||
deleted: { type: Boolean, default: false },
|
deleted: { type: Boolean, default: false },
|
||||||
|
edited: { type: Boolean, default: false },
|
||||||
|
|
||||||
messages: [String],
|
messages: [String],
|
||||||
views: { type: Number, default: 0 }
|
views: { type: Number, default: 0 }
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
schema.virtual('authorID').get(function() { return this.author?.id; });
|
schema.virtual('authorID').get(function () { return this.author?.id; });
|
||||||
|
schema.methods.messageCount = async function (admin = false) {
|
||||||
|
const query = { threadID: this.id };
|
||||||
|
if (!admin) query.deleted = false;
|
||||||
|
return await MessageModel.count(query) || 0;
|
||||||
|
};
|
||||||
schema.methods.push = function (messageID) {
|
schema.methods.push = function (messageID) {
|
||||||
this.messages.push(messageID);
|
this.messages.push(messageID);
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -7,6 +7,8 @@ const schema = new mongoose.Schema({
|
||||||
avatar: { type: String, default: "/images/guest.png" },
|
avatar: { type: String, default: "/images/guest.png" },
|
||||||
time: { type: Date, default: Date.now },
|
time: { type: Date, default: Date.now },
|
||||||
deleted: { type: Boolean, default: false },
|
deleted: { type: Boolean, default: false },
|
||||||
|
edited: { type: Boolean, default: false },
|
||||||
|
about: { type: String, default: "" },
|
||||||
admin: { type: Boolean, default: false },
|
admin: { type: Boolean, default: false },
|
||||||
theme: { type: String, default: def_theme }
|
theme: { type: String, default: def_theme }
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const UserModel = require("./User"),
|
const UserModel = require("./User"),
|
||||||
MessageModel = require("./Message"),
|
MessageModel = require("./Message"),
|
||||||
ThreadModel = require("./Thread"),
|
ThreadModel = require("./Thread"),
|
||||||
SecretModel = require("./Secret");
|
SecretModel = require("./Secret"),
|
||||||
|
BanModel = require("./Ban");
|
||||||
|
|
||||||
module.exports = { UserModel, MessageModel, ThreadModel, SecretModel };
|
module.exports = { UserModel, MessageModel, ThreadModel, SecretModel, BanModel };
|
3860
package-lock.json
generated
3860
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -29,8 +29,11 @@
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"ejs": "^3.1.6",
|
"ejs": "^3.1.6",
|
||||||
"express": "^4.17.3",
|
"express": "^4.17.3",
|
||||||
|
"express-ip-block": "^0.1.2",
|
||||||
"express-rate-limit": "^6.5.1",
|
"express-rate-limit": "^6.5.1",
|
||||||
"express-session": "^1.17.2",
|
"express-session": "^1.17.2",
|
||||||
"mongoose": "^6.5.1"
|
"i": "^0.3.7",
|
||||||
|
"mongoose": "^6.5.1",
|
||||||
|
"npm": "^8.18.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,40 @@
|
||||||
import request from "./request.js";
|
import request from "./request.js";
|
||||||
|
|
||||||
/**
|
window.edit_t = async function (id) {
|
||||||
* Message Sender
|
const title = prompt("Enter new title!");
|
||||||
*/
|
const res = await request(`/api/threads/${id}/edit`, "POST", { title });
|
||||||
document.getElementById("send")?.addEventListener("submit", async e => {
|
if (res.error) return;
|
||||||
|
alert(`Thread updated`);
|
||||||
|
document.getElementById("title").innerHTML = title;
|
||||||
|
|
||||||
e.preventDefault();
|
}
|
||||||
const form = e.target;
|
|
||||||
const data = new FormData(form);
|
|
||||||
request("/api/messages", "POST", { threadID: data.get("threadID"), content: data.get("content") })
|
window.thread = async function (id, un = "") {
|
||||||
.then(res => {
|
const res = await request(`/api/threads/${id}/${un}delete`);
|
||||||
if (res) location.href = `/messages/${res.id}`;
|
if (res.error) return;
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OTHER FUNCTIONS
|
|
||||||
*/
|
|
||||||
window.thread = async function (id, un= "") {
|
|
||||||
await request(`/api/threads/${id}/${un}delete`);
|
|
||||||
alert(`Thread ${un}deleted`);
|
alert(`Thread ${un}deleted`);
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
window.edit_message = async function (id) {
|
||||||
|
const content = prompt("Enter new content!");
|
||||||
|
const res = await request(`/api/messages/${id}/edit`, "POST", { content });
|
||||||
|
if (res.error) return;
|
||||||
|
|
||||||
|
alert(`Message updated`);
|
||||||
|
document.getElementById("message-" + id).querySelector(".content").innerHTML = content;
|
||||||
|
|
||||||
|
}
|
||||||
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}/undelete`);
|
||||||
if (response.deleted) return;
|
if (response.deleted) return;
|
||||||
document.getElementById("deleted-" + id).remove();
|
document.getElementById("deleted-" + id).remove();
|
||||||
document.getElementById("dot-" + id).innerHTML = `
|
document.getElementById("dot-" + id).innerHTML = `
|
||||||
<a onclick="delete_message('${id}');">DELETE</a>
|
<a onclick="delete_message('${id}');">DELETE</a>
|
||||||
<a onclick="edit_message('${id}');">EDIT</a>
|
<a onclick="edit_message('${id}');">EDIT</a>`
|
||||||
`
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.delete_message = async function (id) {
|
window.delete_message = async function (id) {
|
||||||
const response = await request(`/api/messages/${id}/delete`);
|
const response = await request(`/api/messages/${id}/delete`);
|
||||||
if (response.deleted) {
|
if (response.deleted) {
|
||||||
|
@ -40,10 +42,10 @@ window.delete_message = async function (id) {
|
||||||
document.getElementById("dots-" + id).innerHTML = `
|
document.getElementById("dots-" + id).innerHTML = `
|
||||||
<i class='bx bx-trash bx-sm' id="deleted-${id}" style="color: RED;"></i>
|
<i class='bx bx-trash bx-sm' id="deleted-${id}" style="color: RED;"></i>
|
||||||
`+ document.getElementById("dots-" + id).innerHTML;
|
`+ document.getElementById("dots-" + id).innerHTML;
|
||||||
|
|
||||||
document.getElementById("dot-" + id).innerHTML = `<a onclick="undelete_message('${id}');">UNDELETE</a>`;
|
document.getElementById("dot-" + id).innerHTML = `<a onclick="undelete_message('${id}');">UNDELETE</a>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.react = async function (id, type) {
|
window.react = async function (id, type) {
|
||||||
const res = await request(`/api/messages/${id}/react/${type}`)
|
const res = await request(`/api/messages/${id}/react/${type}`)
|
||||||
document.getElementById(`like-${id}`).innerHTML = res.react.like.length;
|
document.getElementById(`like-${id}`).innerHTML = res.react.like.length;
|
||||||
|
|
|
@ -23,7 +23,7 @@ app.use(async (req, res, next) => {
|
||||||
const user = await SecretModel.findOne({ username });
|
const user = await SecretModel.findOne({ username });
|
||||||
|
|
||||||
if (!user)
|
if (!user)
|
||||||
return res.error(401, "We have not got any user has got this 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!');
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,23 @@ app.get("/:id", async (req, res) => {
|
||||||
|
|
||||||
res.complate(message.toObject({ virtuals: true }));
|
res.complate(message.toObject({ virtuals: true }));
|
||||||
|
|
||||||
|
})
|
||||||
|
app.post("/:id/edit", async (req, res) => {
|
||||||
|
|
||||||
|
const message = await MessageModel.get(req.params.id);
|
||||||
|
|
||||||
|
if (!message || (message.deleted && req.user && !req.user.admin)) return res.error(404, `We don't have any message with id ${req.params.id}.`);
|
||||||
|
|
||||||
|
if (req.user.id !== message.authorID && !req.user.admin) return res.error(403, "You have not got permission for this.");
|
||||||
|
const { content = null } = req.body;
|
||||||
|
if (!content) return res.error(400, "Missing message content in request body.");
|
||||||
|
message.content = content;
|
||||||
|
message.edited=true;
|
||||||
|
|
||||||
|
await message.save();
|
||||||
|
|
||||||
|
res.complate(message.toObject({ virtuals: true }));
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
app.post("/", rateLimit({
|
app.post("/", rateLimit({
|
||||||
|
@ -31,7 +48,7 @@ app.post("/", rateLimit({
|
||||||
|
|
||||||
if (!thread) return res.error(404, `We don't have any thread with id ${threadID}.`);
|
if (!thread) return res.error(404, `We don't have any thread with id ${threadID}.`);
|
||||||
|
|
||||||
const message = await new MessageModel({ content, author: req.user, threadID: thread.id, index: thread.messages.length }).takeId();
|
const message = await new MessageModel({ content, author: req.user, threadID: thread.id }).takeId();
|
||||||
await message.save();
|
await message.save();
|
||||||
await thread.push(message.id).save();
|
await thread.push(message.id).save();
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,21 @@ app.post("/", async (req, res) => {
|
||||||
res.complate(thread.toObject({ virtuals: true }));
|
res.complate(thread.toObject({ virtuals: true }));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
app.post("/:id/edit", async (req, res) => {
|
||||||
|
|
||||||
|
const thread = await ThreadModel.get(req.params.id);
|
||||||
|
|
||||||
|
if (!thread || (thread.deleted && req.user && !req.user.admin)) return res.error(404, `We don't have any message with id ${req.params.id}.`);
|
||||||
|
|
||||||
|
if (req.user.id !== thread.authorID && !req.user.admin) return res.error(403, "You have not got permission for this.");
|
||||||
|
const { title = null } = req.body;
|
||||||
|
if (!title) return res.error(400, "Missing thread title in request body.");
|
||||||
|
thread.title = title;
|
||||||
|
await thread.save();
|
||||||
|
|
||||||
|
res.complate(thread.toObject({ virtuals: true }));
|
||||||
|
|
||||||
|
})
|
||||||
app.post("/:id/delete", async (req, res) => {
|
app.post("/:id/delete", async (req, res) => {
|
||||||
const thread = await ThreadModel.get(req.params.id);
|
const thread = await ThreadModel.get(req.params.id);
|
||||||
if (!thread || thread.deleted) return res.error(404, `We don't have any thread with id ${req.params.id}.`);
|
if (!thread || thread.deleted) return res.error(404, `We don't have any thread with id ${req.params.id}.`);
|
||||||
|
@ -77,6 +91,8 @@ app.post("/:id/undelete", async (req, res) => {
|
||||||
if (!thread.deleted) return res.error(404, "This thread is not deleted, first, delete it.");
|
if (!thread.deleted) return res.error(404, "This thread is not deleted, first, delete it.");
|
||||||
|
|
||||||
thread.deleted = false;
|
thread.deleted = false;
|
||||||
|
thread.edited=true;
|
||||||
|
|
||||||
await thread.save();
|
await thread.save();
|
||||||
|
|
||||||
res.complate(thread.toObject({ virtuals: true }));
|
res.complate(thread.toObject({ virtuals: true }));
|
||||||
|
|
|
@ -34,7 +34,7 @@ app.post("/:id/undelete/", async (req, res) => {
|
||||||
|
|
||||||
const member = await UserModel.get(req.params.id);
|
const member = await UserModel.get(req.params.id);
|
||||||
|
|
||||||
if (!member ) return res.error(404, `We don't have any user with id ${req.params.id}.`);
|
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.");
|
if (!member.deleted) return res.error(404, "This user is not deleted, first, delete it.");
|
||||||
|
|
||||||
|
@ -44,6 +44,31 @@ app.post("/:id/undelete/", async (req, res) => {
|
||||||
res.complate(member.toObject({ virtuals: true }));
|
res.complate(member.toObject({ virtuals: true }));
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
app.post("/:id/edit", async (req, res) => {
|
||||||
|
|
||||||
|
const member = await UserModel.get(req.params.id);
|
||||||
|
|
||||||
|
if (!member || (member.deleted && !req.user.admin)) return res.error(404, `We don't have any message with id ${req.params.id}.`);
|
||||||
|
|
||||||
|
if (req.user.id !== member.id && !req.user.admin) return res.error(403, "You have not got permission for this.");
|
||||||
|
const { avatar, name, about } = req.body;
|
||||||
|
if (!avatar && !name) return res.error(400, "Missing member informations in request body.");
|
||||||
|
if (avatar && /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g.test(avatar))
|
||||||
|
member.avatar = avatar;
|
||||||
|
if (name) member.name = name;
|
||||||
|
|
||||||
|
if (about) member.about = about;
|
||||||
|
member.edited = true;
|
||||||
|
|
||||||
|
|
||||||
|
await member.save();
|
||||||
|
|
||||||
|
res.complate(member.toObject({ virtuals: true }));
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
app.post("/:id/admin/", async (req, res) => {
|
app.post("/:id/admin/", async (req, res) => {
|
||||||
|
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
|
@ -54,10 +79,10 @@ app.post("/:id/admin/", async (req, res) => {
|
||||||
if (!user2)
|
if (!user2)
|
||||||
return res.error(404, `We don't have any user with id ${id}.`);
|
return res.error(404, `We don't have any user with id ${id}.`);
|
||||||
|
|
||||||
else {
|
|
||||||
user2.admin = true;
|
user2.admin = true;
|
||||||
await user2.save()
|
await user2.save()
|
||||||
}
|
|
||||||
|
|
||||||
res.complate(user2);
|
res.complate(user2);
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ app.get("/:id", async (req, res) => {
|
||||||
|
|
||||||
if (!message || (message.deleted && req.user && !req.user.admin)) return res.error( 404,
|
if (!message || (message.deleted && req.user && !req.user.admin)) return res.error( 404,
|
||||||
`We don't have any message with id ${req.params.id}.`);
|
`We don't have any message with id ${req.params.id}.`);
|
||||||
res.redirect(`/threads/${message.threadID}?scroll=${req.params.id}`);
|
res.redirect(`/threads/${message.threadID}?scroll=${message.id}`);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ app.post("/", async (req, res) => {
|
||||||
req.session.userid = null;
|
req.session.userid = null;
|
||||||
|
|
||||||
|
|
||||||
let { username = null, password: body_pass = null, avatar } = req.body;
|
let { username = null, password: body_pass = null, avatar, about } = req.body;
|
||||||
|
|
||||||
if (!username || !body_pass) return res.error(res, 400, "You forgot entering some values");
|
if (!username || !body_pass) return res.error(res, 400, "You forgot entering some values");
|
||||||
const user = await SecretModel.findOne({ username });
|
const user = await SecretModel.findOne({ username });
|
||||||
|
@ -24,6 +24,9 @@ app.post("/", async (req, res) => {
|
||||||
|
|
||||||
const user2 = new UserModel({ name: req.body.username })
|
const user2 = new UserModel({ name: req.body.username })
|
||||||
if (avatar && /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g.test(avatar)) user2.avatar = avatar;
|
if (avatar && /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g.test(avatar)) user2.avatar = avatar;
|
||||||
|
|
||||||
|
if (about) user2.about = about;
|
||||||
|
|
||||||
await user2.takeId()
|
await user2.takeId()
|
||||||
await user2.save();
|
await user2.save();
|
||||||
|
|
||||||
|
|
|
@ -18,11 +18,14 @@ app.get("/:id/", async (req, res) => {
|
||||||
|
|
||||||
const { user, params: { id } } = req
|
const { user, params: { id } } = req
|
||||||
|
|
||||||
const page = Number(req.query.page || 0);
|
let page = Number(req.query.page || 0);
|
||||||
|
|
||||||
const thread = await ThreadModel.get(id)
|
const thread = await ThreadModel.get(id)
|
||||||
|
thread.count = await thread.messageCount(user?.admin);
|
||||||
|
thread.pages = Math.ceil(thread.count / 10);
|
||||||
if (thread && (user?.admin || !thread.deleted)) {
|
if (thread && (user?.admin || !thread.deleted)) {
|
||||||
thread.views++;
|
thread.views++;
|
||||||
const query = { threadID: id };
|
const query = { threadID: id };
|
||||||
if (!user || !user.admin) query.deleted = false;
|
if (!user || !user.admin) query.deleted = false;
|
||||||
|
|
||||||
const messages = await MessageModel.find(query).sort({ time: 1 }).limit(10).skip(page * 10)
|
const messages = await MessageModel.find(query).sort({ time: 1 }).limit(10).skip(page * 10)
|
||||||
|
@ -34,13 +37,12 @@ app.get("/:id/", async (req, res) => {
|
||||||
return message.toObject({ virtuals: true });
|
return message.toObject({ virtuals: true });
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
res.reply("thread", { page, thread, messages, scroll: req.query.scroll || thread.messages[0].id });
|
res.reply("thread", { page, thread, messages, scroll: req.query.scroll || thread.messages[0].id });
|
||||||
|
|
||||||
thread.save();
|
thread.save();
|
||||||
|
|
||||||
} else
|
} else
|
||||||
res.error(404, "We have not got this thread.");
|
res.error(404, `We don't have any thread with id ${id}.`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,13 @@ app.get("/:id", async (req, res) => {
|
||||||
|
|
||||||
const message = await MessageModel.count({ "author.id": id });
|
const message = await MessageModel.count({ "author.id": id });
|
||||||
const thread = await ThreadModel.count({ "author.id": id });
|
const thread = await ThreadModel.count({ "author.id": id });
|
||||||
|
member.about = member.about.replaceAll("&", "&")
|
||||||
|
.replaceAll("<", "<").replaceAll(">", ">")
|
||||||
|
.replaceAll("\"", """).replaceAll("'", "'")
|
||||||
|
.replaceAll("\n", "<br>");
|
||||||
res.reply("user", { member, counts: { message, thread } })
|
res.reply("user", { member, counts: { message, thread } })
|
||||||
}
|
}
|
||||||
else res.error(404, `We don't have any user with id ${id}.`);
|
else res.error(404, `We don't have any user with id ${id}.`);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,11 @@
|
||||||
|
|
||||||
|
|
||||||
<input type="text" name="username" placeholder="Username" class="input" required>
|
<input type="text" name="username" 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="text" name="about" placeholder="About you... Not required" class="input">
|
||||||
<input type="text" name="avatar" placeholder="Avatar URL" class="input">
|
|
||||||
|
<input type="text" name="avatar" placeholder="Avatar URL (not required)" class="input">
|
||||||
|
|
||||||
<input type="submit" class="btn-primary" style="width:100%;" value="Register">
|
<input type="submit" class="btn-primary" style="width:100%;" value="Register">
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<% }; %>
|
<% }; %>
|
||||||
|
|
||||||
<div style="text-align:center;padding:8px">
|
<div style="text-align:center;padding:8px">
|
||||||
<div class="title"><%= thread.title %></div>
|
<div class="title" id="title"><%= thread.title %></div>
|
||||||
<div class="date">
|
<div class="date">
|
||||||
<%= new Date(thread.time).toLocaleString() %> • Views: <%= thread.views %>
|
<%= new Date(thread.time).toLocaleString() %> • Views: <%= thread.views %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
<% if (user && !thread.deleted){ %>
|
<% if (user && !thread.deleted){ %>
|
||||||
|
|
||||||
<a onclick="thread('<%= thread.id %>')" class="btn-outline-primary" >DELETE</a>
|
<a onclick="thread('<%= thread.id %>')" class="btn-outline-primary" >DELETE</a>
|
||||||
<a onclick="edit_thread('<%= thread.id %>')" class="btn-outline-primary" >EDIT</a>
|
<a onclick="edit_t('<%= thread.id %>')" class="btn-outline-primary" >EDIT</a>
|
||||||
<% } else if (thread.deleted) { %>
|
<% } else if (thread.deleted) { %>
|
||||||
<h3 style="display:inline;">This thread has been deleted</h3>
|
<h3 style="display:inline;">This thread has been deleted</h3>
|
||||||
<a onclick="thread('<%= thread.id %>', 'un')" class="btn-primary" >UNDELETE</a>
|
<a onclick="thread('<%= thread.id %>', 'un')" class="btn-primary" >UNDELETE</a>
|
||||||
|
@ -58,6 +58,9 @@
|
||||||
<% if (message.deleted){ %>
|
<% if (message.deleted){ %>
|
||||||
<i class='bx bx-trash bx-sm' id="deleted-<%=message.id %>" style="color: RED;"></i>
|
<i class='bx bx-trash bx-sm' id="deleted-<%=message.id %>" style="color: RED;"></i>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
<% if (message.edited){ %>
|
||||||
|
<i class='bx bx-pencil bx-sm' id="edited-<%=message.id %>" style="color: GREEN;"></i>
|
||||||
|
<% } %>
|
||||||
<i class='bx bx-dots-horizontal-rounded' ></i>
|
<i class='bx bx-dots-horizontal-rounded' ></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -88,32 +91,53 @@
|
||||||
|
|
||||||
<% }); %>
|
<% }); %>
|
||||||
</div>
|
</div>
|
||||||
|
<% if (user){ %>
|
||||||
|
|
||||||
<div class="message">
|
<div class="message">
|
||||||
<form id="send">
|
<form id="send">
|
||||||
<textarea rows="4" cols="100" name="content"></textarea>
|
<textarea rows="4" cols="100" name="content"></textarea>
|
||||||
<input name="threadID" type="hidden" value="<%= thread.id %>"></input>
|
<input name="threadID" type="hidden" value="<%= thread.id %>"></input>
|
||||||
|
<input name="page" type="hidden" value="<%= page %>"></input>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import request from "../../js/request.js";
|
||||||
|
|
||||||
|
document.getElementById("send").addEventListener("submit", async e => {
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
<% if (user){ %>
|
const data = new FormData(e.target);
|
||||||
|
|
||||||
|
request("/api/messages", "POST", { threadID: "<%= thread.id %>", content: data.get("content") })
|
||||||
|
.then(res => {
|
||||||
|
let tp = Number("<%= thread.pages %>")
|
||||||
|
let tm = Number("<%= thread.count %>")
|
||||||
|
if (tp*10===tm) tp++;
|
||||||
|
if (res) location.href = `/threads/${data.get("threadID")}?page=${tp-1}`;
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<button class="btn-primary">Send!</button>
|
<button class="btn-primary">Send!</button>
|
||||||
<%} else {%>
|
|
||||||
<a class="btn-outline-primary" href="/login?redirect=<%= thread.getLink() %>">Login for send</a>
|
|
||||||
<% }%>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<% }%>
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<div class="back">
|
<div class="back">
|
||||||
<% if (page > 0){ %>
|
<% if (page > 0){ %>
|
||||||
<a href="<%= thread.getLink() %>?page=<%= page-1 %>" class='bx bxs-chevron-left'></a>
|
<a href="<%= thread.getLink() %>?page=<%= page-1 %>" class='bx bxs-chevron-left'></a>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="numbers">
|
<div class="numbers">
|
||||||
<a class="number" href="<%= thread.getLink() %>?page=<%= page %>"><%= page+1 %></a>
|
<% for(let i=0; i < thread.pages; i++){ %>
|
||||||
|
<a class="number <%= i==page?'active':'' %>" href="<%= thread.getLink() %>?page=<%= i %>"><%= i+1 %></a>
|
||||||
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="after">
|
<div class="after">
|
||||||
<% if (Math.ceil(messages.length/10) > page){ %>
|
<% if (thread.pages-1 > page) { %>
|
||||||
<a href="<%= thread.getLink() %>?page=<%= page +1 %>" class='bx bxs-chevron-right'></a>
|
<a href="<%= thread.getLink() %>?page=<%= page +1 %>" class='bx bxs-chevron-right'></a>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -35,11 +35,19 @@
|
||||||
<h2 class="box-title">Thread:</h2>
|
<h2 class="box-title">Thread:</h2>
|
||||||
<h2 class="box-value"><%= counts.thread %></h2>
|
<h2 class="box-value"><%= counts.thread %></h2>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<h2 class="box-title">About:</h2><br>
|
||||||
|
</div>
|
||||||
|
<p class="box-value">
|
||||||
|
<%= member.about %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a class="btn-outline-primary" id="edit">Change name of the user!</a>
|
||||||
|
|
||||||
|
<% if (user?.admin && !member.deleted) {%>
|
||||||
|
<a class="btn-outline-primary" id="admin">Give admin permissions!</a>
|
||||||
|
<a class="btn-outline-primary" id="delete">Delete user!</a>
|
||||||
|
|
||||||
<% if (user?.admin && !member.deleted) {%>
|
|
||||||
<a class="btn-outline-primary" id="admin">Give admin permissions!</a>
|
|
||||||
<a class="btn-outline-primary" id="delete">Delete user!</a>
|
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
|
|
||||||
import request from "../../js/request.js";
|
import request from "../../js/request.js";
|
||||||
|
@ -59,6 +67,15 @@
|
||||||
alert("User is deleted!");
|
alert("User is deleted!");
|
||||||
location.reload()
|
location.reload()
|
||||||
|
|
||||||
|
}else if (e.target.id == "edit") {
|
||||||
|
|
||||||
|
const name = prompt("Enter new username!");
|
||||||
|
const res =await request(`/api/users/<%= member.id %>/edit`, "POST", { name });
|
||||||
|
if (res.error) return;
|
||||||
|
alert(`User updated!`);
|
||||||
|
location.reload();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue