mirror of
https://github.com/Akif9748/akf-forum.git
synced 2024-11-26 13:20:41 +03:00
view count for threads
This commit is contained in:
parent
1f737277b2
commit
98356a2a90
8 changed files with 95 additions and 170 deletions
|
@ -58,7 +58,7 @@ Akf-forum has got an API for AJAX, other clients etc. And, you can learn about A
|
||||||
| Send | 🟢 | HIGH |
|
| Send | 🟢 | HIGH |
|
||||||
| Delete | 🟢 | HIGH |
|
| Delete | 🟢 | HIGH |
|
||||||
| Regex for scripts | 🔴 | HIGH |
|
| Regex for scripts | 🔴 | HIGH |
|
||||||
| Undelete | 🟡 | MEDIUM |
|
| Undelete | 🟢 | MEDIUM |
|
||||||
| React | 🟢 | MEDIUM |
|
| React | 🟢 | MEDIUM |
|
||||||
| Edit | 🔴 | MEDIUM |
|
| Edit | 🔴 | MEDIUM |
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const mongoose = require("mongoose")
|
const mongoose = require("mongoose")
|
||||||
const UserModel = require("./User");
|
const UserModel = require("./User");
|
||||||
const schema = new mongoose.Schema({
|
const schema = new mongoose.Schema({
|
||||||
id: { type: String, unique: true },
|
id: { type: String, unique: true },
|
||||||
|
|
||||||
|
@ -8,7 +8,8 @@ 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 },
|
||||||
messages: [String]
|
messages: [String],
|
||||||
|
views: { type: Number, default: 0 }
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,10 @@
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/Akif9748/akf-forum/issues"
|
"url": "https://github.com/Akif9748/akf-forum/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://akf-forum.herokuapp.com/",
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"homepage": "https://akf-forum.glitch.me/",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
"body-parser": "^1.19.2",
|
"body-parser": "^1.19.2",
|
||||||
|
@ -30,4 +33,4 @@
|
||||||
"express-session": "^1.17.2",
|
"express-session": "^1.17.2",
|
||||||
"mongoose": "^6.5.1"
|
"mongoose": "^6.5.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -28,7 +28,6 @@
|
||||||
textarea {
|
textarea {
|
||||||
|
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
background-color: var(--col-bg);
|
|
||||||
border: 2px solid var(--col-8);
|
border: 2px solid var(--col-8);
|
||||||
color: var(--col-fg);
|
color: var(--col-fg);
|
||||||
width: auto;
|
width: auto;
|
||||||
|
@ -130,36 +129,6 @@ button:hover {
|
||||||
|
|
||||||
/* NAVBAR: */
|
/* NAVBAR: */
|
||||||
|
|
||||||
.navbar {
|
|
||||||
background-color: #333;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar a {
|
|
||||||
float: left;
|
|
||||||
color: var(--col-fg);
|
|
||||||
text-align: center;
|
|
||||||
padding: 14px 16px;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 17px;
|
|
||||||
border-right: 3px solid var(--col-8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar a:hover {
|
|
||||||
background-color: var(--col-bg);
|
|
||||||
color: var(--col-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar h1:hover {
|
|
||||||
background-color: var(--col-bg);
|
|
||||||
color: var(--col-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar a.active {
|
|
||||||
background-color: var(--col-14);
|
|
||||||
color: var(--col-15);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.user {
|
.user {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -211,52 +180,9 @@ img.logo {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
*****************************
|
|
||||||
FOOTER
|
|
||||||
*****************************
|
|
||||||
*/
|
|
||||||
.footer {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
background-color: var(--col-8);
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer p {
|
|
||||||
|
|
||||||
font-size: 16px;
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*****************************
|
*****************************
|
||||||
ADMIN TEXT
|
ADMIN TEXT
|
||||||
*****************************
|
*****************************
|
||||||
*/
|
*/
|
||||||
.admin {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
height: 20px;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin p {
|
|
||||||
font-size: 16px;
|
|
||||||
margin: auto;
|
|
||||||
color: var(--col-15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin a {
|
|
||||||
background-color: var(--col-13);
|
|
||||||
border: 2px solid var(--col-8);
|
|
||||||
height: 20px;
|
|
||||||
color: var(--col-15);
|
|
||||||
}
|
|
||||||
p {font-size: 18px;}
|
p {font-size: 18px;}
|
|
@ -23,7 +23,7 @@ app.post("/", rateLimit({
|
||||||
|
|
||||||
|
|
||||||
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;
|
||||||
await user2.takeId()
|
await user2.takeId()
|
||||||
await user2.save();
|
await user2.save();
|
||||||
|
|
||||||
|
|
|
@ -6,33 +6,26 @@ const { ThreadModel, MessageModel } = require("../models")
|
||||||
|
|
||||||
app.get("/", async (req, res) => {
|
app.get("/", async (req, res) => {
|
||||||
|
|
||||||
const threads = await ThreadModel.find(req.user?.admin ? {} : { deleted: false }).limit(10);
|
const threads = await ThreadModel.find(req.user?.admin ? {} : { deleted: false })//.limit(10);
|
||||||
|
|
||||||
return res.reply("threads", { threads });
|
return res.reply("threads", { threads });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
app.get("/create*", async (req, res) => {
|
app.get("/create*", (req, res) => res.reply("create_thread"));
|
||||||
res.reply("create_thread")
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get("/:id", async (req, res) => {
|
app.get("/:id", async (req, res) => {
|
||||||
|
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
const thread = await ThreadModel.get(id);
|
const thread = await ThreadModel.get(id);
|
||||||
const user = req.user;
|
thread.views++;
|
||||||
|
|
||||||
if (thread && (user?.admin || !thread.deleted)) {
|
if (thread && (req.user?.admin || !thread.deleted))
|
||||||
|
res.reply("thread", { thread, scroll: req.query.scroll || false });
|
||||||
const messages = await Promise.all(thread.messages.map(async id => {
|
else
|
||||||
const message = await MessageModel.get(id)
|
|
||||||
return user?.admin || !message?.deleted ? message.toObject({ virtuals: true }) : null;
|
|
||||||
}));
|
|
||||||
|
|
||||||
res.reply("thread", { thread, messages, scroll: req.query.scroll || false });
|
|
||||||
} else
|
|
||||||
res.error(404, "We have not got this thread.");
|
res.error(404, "We have not got this thread.");
|
||||||
|
thread.save();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
|
|
||||||
<h1 style="font-size: 35px;color: #4d18e6;" ><%= thread.title %></h1>
|
<h1 style="font-size: 35px;color: #4d18e6;" ><%= thread.title %></h1>
|
||||||
|
<h1 style="font-size: 19px; " >View count: <%= thread.views %></h1>
|
||||||
|
|
||||||
<h2 style="display:inline;">By <a href="<%='/users/' + thread.author.id %>"> <%= thread.author.name %></a>
|
<h2 style="display:inline;">By <a href="<%='/users/' + thread.author.id %>"> <%= thread.author.name %></a>
|
||||||
<img class="circle" src="<%=thread.author.avatar %>">
|
<img class="circle" src="<%=thread.author.avatar %>">
|
||||||
|
|
153
views/user.ejs
153
views/user.ejs
|
@ -4,93 +4,94 @@
|
||||||
<%- include("extra/meta", {title: member.name }) %>
|
<%- include("extra/meta", {title: member.name }) %>
|
||||||
|
|
||||||
|
|
||||||
<body style="text-align: center;">
|
<body style="text-align: center;">
|
||||||
<%- include("extra/navbar") %>
|
<%- include("extra/navbar") %>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<h1 style="color: #4d18e6;">Avatar:</h1>
|
<h1 style="color: #4d18e6;">Avatar:</h1>
|
||||||
<img style="width:256px;height:256px;" src="<%=member.avatar %>">
|
<img style="width:256px;height:256px;" src="<%=member.avatar %>">
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<h2 style="color: #606060;">Name: <%= member.name %>
|
<h2 style="color: #606060;">Name: <%= member.name %>
|
||||||
</h2>
|
</h2>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<h2 style="color: #606060;">Created at:
|
<h2 style="color: #606060;">Created at:
|
||||||
<%= new Date(member.time).toLocaleString() %>
|
<%= new Date(member.time).toLocaleString() %>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<h2 style="color: #606060;">Is admin? <%= member.admin ? "Yes" : "No" %>
|
<h2 style="color: #606060;">Is admin? <%= member.admin ? "Yes" : "No" %>
|
||||||
</h2>
|
</h2>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<h2 style="color: #606060;"> Message: <%= counts.message %>
|
<h2 style="color: #606060;"> Message: <%= counts.message %>
|
||||||
</h2>
|
</h2>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<h2 style="color: #606060;"> Thread: <%= counts.thread %>
|
<h2 style="color: #606060;"> Thread: <%= counts.thread %>
|
||||||
</h2>
|
</h2>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<% if (user?.admin && !member.deleted) {%>
|
<% if (user?.admin && !member.deleted) {%>
|
||||||
<a class="big" id="admin">Give admin permissions!</a>
|
<a class="big" id="admin">Give admin permissions!</a>
|
||||||
|
|
||||||
<a class="big" id="delete">Delete user!</a>
|
<a class="big" id="delete">Delete user!</a>
|
||||||
|
|
||||||
<script type="module">
|
|
||||||
|
|
||||||
import request from "../../js/request.js";
|
|
||||||
document.addEventListener("click", async e => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (e.target.id == "admin") {
|
|
||||||
|
|
||||||
const response = await request("/api/users/<%= member.id %>/admin");
|
|
||||||
|
|
||||||
if (response.admin)
|
|
||||||
return alert("Making admin of " + response.name + " is success!");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await request("/api/users/<%= member.id %>/delete");
|
|
||||||
|
|
||||||
if (!response.deleted) return
|
|
||||||
alert("User is deleted!");
|
|
||||||
location.reload()
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<% }; %>
|
|
||||||
<% if (member.deleted) {%>
|
|
||||||
<h1>This user has been deleted!</h1>
|
|
||||||
<a onclick="undelete();" type="">Undelete user! </a>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
|
|
||||||
import request from "../../js/request.js";
|
import request from "../../js/request.js";
|
||||||
async function undelete(params) {
|
document.addEventListener("click", async e => {
|
||||||
|
e.preventDefault();
|
||||||
const response = await request("/api/users/<%= member.id %>/undelete");
|
if (e.target.id == "admin") {
|
||||||
|
|
||||||
if (response.deleted) return;
|
const response = await request("/api/users/<%= member.id %>/admin");
|
||||||
alert("User is undeleted successfully!");
|
|
||||||
|
if (response.admin)
|
||||||
|
return alert("Making admin of " + response.name + " is success!");
|
||||||
|
|
||||||
|
} else if (e.target.id == "delete") {
|
||||||
|
|
||||||
|
const response = await request("/api/users/<%= member.id %>/delete");
|
||||||
|
|
||||||
|
if (!response.deleted) return;
|
||||||
|
alert("User is deleted!");
|
||||||
location.reload()
|
location.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<% }; %>
|
<% }; %>
|
||||||
</body>
|
<% if (member.deleted) {%>
|
||||||
|
<h1>This user has been deleted!</h1>
|
||||||
|
<a onclick="undelete();" type="">Undelete user! </a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
|
||||||
|
import request from "../../js/request.js";
|
||||||
|
async function undelete(params) {
|
||||||
|
|
||||||
|
const response = await request("/api/users/<%= member.id %>/undelete");
|
||||||
|
|
||||||
|
if (response.deleted) return;
|
||||||
|
alert("User is undeleted successfully!");
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<% }; %>
|
||||||
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
Loading…
Reference in a new issue