Compare commits
8 Commits
96f819dc91
...
dsf-fix-1
| Author | SHA1 | Date | |
|---|---|---|---|
| a64ac93935 | |||
| 26c66f16b4 | |||
| fadc62c8f0 | |||
| 14f2164a82 | |||
| 14ef1f9bad | |||
| dc99318ff0 | |||
| d2fc5f4d5c | |||
| 938bd48fff |
@@ -81,7 +81,7 @@ app.use(require("./root"))
|
|||||||
app.use("/kfu-m-24-1", require("./routers/kfu-m-24-1"))
|
app.use("/kfu-m-24-1", require("./routers/kfu-m-24-1"))
|
||||||
app.use("/epja-2024-1", require("./routers/epja-2024-1"))
|
app.use("/epja-2024-1", require("./routers/epja-2024-1"))
|
||||||
app.use("/v1/todo", require("./routers/todo"))
|
app.use("/v1/todo", require("./routers/todo"))
|
||||||
// app.use("/dogsitters-finder", require("./routers/dogsitters-finder"))
|
app.use("/dogsitters-finder", require("./routers/dogsitters-finder"))
|
||||||
app.use("/kazan-explore", require("./routers/kazan-explore"))
|
app.use("/kazan-explore", require("./routers/kazan-explore"))
|
||||||
app.use("/edateam", require("./routers/edateam-legacy"))
|
app.use("/edateam", require("./routers/edateam-legacy"))
|
||||||
app.use("/dry-wash", require("./routers/dry-wash"))
|
app.use("/dry-wash", require("./routers/dry-wash"))
|
||||||
|
|||||||
2
server/routers/dogsitters-finder/const.js
Normal file
2
server/routers/dogsitters-finder/const.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
exports.DSF_AUTH_USER_MODEL_NAME = 'DSF_AUTH_USER'
|
||||||
|
exports.DSF_INTERACTION_MODEL_NAME = 'DSF_INTERACTION'
|
||||||
@@ -66,4 +66,163 @@ router.get("/auth/session", (request, response) => {
|
|||||||
console.log("token e:", e);
|
console.log("token e:", e);
|
||||||
return response.status(403).json({ error: "Invalid token" });
|
return response.status(403).json({ error: "Invalid token" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Проверка взаимодействия между пользователем и догситтером
|
||||||
|
router.get("/interactions/check", (req, res) => {
|
||||||
|
const { owner_id, dogsitter_id } = req.query;
|
||||||
|
|
||||||
|
const usersFilePath = path.resolve(__dirname, "./json/users/users.json");
|
||||||
|
|
||||||
|
delete require.cache[require.resolve(usersFilePath)];
|
||||||
|
const usersFile = require(usersFilePath);
|
||||||
|
|
||||||
|
const interactions = usersFile.interactions || [];
|
||||||
|
|
||||||
|
const exists = interactions.some(
|
||||||
|
(interaction) =>
|
||||||
|
interaction.owner_id === Number(owner_id) &&
|
||||||
|
interaction.dogsitter_id === Number(dogsitter_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
res.json({ exists });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Добавление нового взаимодействия
|
||||||
|
router.post("/interactions", (req, res) => {
|
||||||
|
const { owner_id, dogsitter_id, interaction_type } = req.body;
|
||||||
|
|
||||||
|
if (!owner_id || !dogsitter_id || !interaction_type) {
|
||||||
|
return res.status(400).json({ error: "Missing required fields" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const usersFilePath = path.resolve(__dirname, "./json/users/users.json");
|
||||||
|
|
||||||
|
delete require.cache[require.resolve(usersFilePath)];
|
||||||
|
const usersFile = require(usersFilePath);
|
||||||
|
|
||||||
|
if (!usersFile.interactions) {
|
||||||
|
usersFile.interactions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем, существует ли уже такое взаимодействие
|
||||||
|
const exists = usersFile.interactions.some(
|
||||||
|
(interaction) =>
|
||||||
|
interaction.owner_id === Number(owner_id) &&
|
||||||
|
interaction.dogsitter_id === Number(dogsitter_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
usersFile.interactions.push({
|
||||||
|
owner_id: Number(owner_id),
|
||||||
|
dogsitter_id: Number(dogsitter_id),
|
||||||
|
interaction_type,
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
usersFilePath,
|
||||||
|
JSON.stringify(usersFile, null, 2),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Добавлено взаимодействие: owner_id=${owner_id}, dogsitter_id=${dogsitter_id}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/dogsitter-viewing", (req, res) => {
|
||||||
|
const { id } = req.query;
|
||||||
|
console.log(`Получен запрос для dogsitter с ID: ${id}`);
|
||||||
|
|
||||||
|
const usersFile = require("./json/users/users.json");
|
||||||
|
const users = usersFile.data; // Извлекаем массив из свойства "data"
|
||||||
|
|
||||||
|
const user = users.find((user) => user.id === Number(id));
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
res.json(user); // Возвращаем найденного пользователя
|
||||||
|
} else {
|
||||||
|
res.status(404).json({ error: "User not found" }); // Если пользователь не найден
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
|
||||||
|
router.post('/dogsitter-viewing/rating/:id', (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
const { rating } = req.body;
|
||||||
|
|
||||||
|
if (!rating || rating < 1 || rating > 5) {
|
||||||
|
return res.status(400).json({ error: 'Некорректная оценка' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const usersFilePath = path.resolve(__dirname, "./json/users/users.json");
|
||||||
|
|
||||||
|
delete require.cache[require.resolve(usersFilePath)];
|
||||||
|
const usersFile = require(usersFilePath);
|
||||||
|
const users = usersFile.data;
|
||||||
|
|
||||||
|
const userIndex = users.findIndex(user => user.id === Number(id));
|
||||||
|
if (userIndex === -1) {
|
||||||
|
return res.status(404).json({ error: 'Догситтер не найден' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!users[userIndex].ratings) {
|
||||||
|
users[userIndex].ratings = [];
|
||||||
|
}
|
||||||
|
users[userIndex].ratings.push(rating);
|
||||||
|
|
||||||
|
if (users[userIndex].ratings.length > 100) {
|
||||||
|
users[userIndex].ratings.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = users[userIndex].ratings.reduce((sum, r) => sum + r, 0);
|
||||||
|
users[userIndex].rating = parseFloat((total / users[userIndex].ratings.length).toFixed(2));
|
||||||
|
|
||||||
|
fs.writeFileSync(usersFilePath, JSON.stringify({ data: users }, null, 2), 'utf8');
|
||||||
|
|
||||||
|
console.log(`Обновлен рейтинг догситтера ${id}: ${users[userIndex].rating}`);
|
||||||
|
|
||||||
|
res.json({ rating: users[userIndex].rating, ratings: users[userIndex].ratings });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
router.patch('/users/:id', (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
const updateData = req.body;
|
||||||
|
|
||||||
|
console.log('Полученные данные для обновления:', updateData);
|
||||||
|
|
||||||
|
|
||||||
|
const usersFilePath = path.resolve(__dirname, "./json/users/users.json");
|
||||||
|
|
||||||
|
delete require.cache[require.resolve(usersFilePath)];
|
||||||
|
const usersFile = require(usersFilePath);
|
||||||
|
const users = usersFile.data;
|
||||||
|
|
||||||
|
const userIndex = users.findIndex((user) => user.id === Number(id));
|
||||||
|
if (userIndex === -1) {
|
||||||
|
return res.status(404).json({ error: 'User not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
users[userIndex] = { ...users[userIndex], ...updateData };
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
usersFilePath,
|
||||||
|
JSON.stringify({ data: users }, null, 2),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('Обновлённые данные пользователя:', users[userIndex]);
|
||||||
|
|
||||||
|
res.json(users[userIndex]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = router
|
||||||
@@ -1,39 +1,69 @@
|
|||||||
[
|
{
|
||||||
{
|
"data": [
|
||||||
"id": 1,
|
{
|
||||||
"phone_number": 89283244141,
|
"id": 1,
|
||||||
"first_name": "Вася",
|
"phone_number": "89999999999",
|
||||||
"second_name": "Пупкин",
|
"first_name": "Вася",
|
||||||
"role": "dogsitter",
|
"second_name": "Пупкин",
|
||||||
"location": "Россия, республика Татарстан, Казань, улица Пушкина, 12",
|
"role": "dogsitter",
|
||||||
"price": 1500,
|
"location": "Россия, республика Татарстан, Казань, Пушкина, 12",
|
||||||
"about_me": "Я люблю собак"
|
"price": "1500",
|
||||||
},
|
"about_me": "Я люблю собак!",
|
||||||
{
|
"rating": 5,
|
||||||
"id": 2,
|
"ratings": [
|
||||||
"phone_number": 89272844541,
|
5,
|
||||||
"first_name": "Ваня",
|
5
|
||||||
"second_name": "Пуськин",
|
],
|
||||||
"role": "dogsitter",
|
"tg": "jullllllie"
|
||||||
"location": "Россия, республика Татарстан, Казань, улица Абсалямова, 19",
|
},
|
||||||
"price": 1000000,
|
{
|
||||||
"about_me": "Я не люблю собак. И вообще я котоман."
|
"id": 2,
|
||||||
},
|
"phone_number": 89272844541,
|
||||||
{
|
"first_name": "Ваня",
|
||||||
"id": 3,
|
"second_name": "Пуськин",
|
||||||
"phone_number": 89872855893,
|
"role": "dogsitter",
|
||||||
"first_name": "Гадий",
|
"location": "Россия, республика Татарстан, Казань, улица Абсалямова, 19",
|
||||||
"second_name": "Петрович",
|
"price": 2000,
|
||||||
"role": "owner"
|
"about_me": "Я не люблю собак. И вообще я котоман.",
|
||||||
},
|
"rating": 4,
|
||||||
{
|
"ratings": [
|
||||||
"id": 4,
|
4,
|
||||||
"phone_number": 89872844591,
|
4
|
||||||
"first_name": "Галкин",
|
],
|
||||||
"second_name": "Максим",
|
"tg": "vanya006"
|
||||||
"role": "dogsitter",
|
},
|
||||||
"location": "Россия, республика Татарстан, Казань, проспект Ямашева, 83",
|
{
|
||||||
"price": 1000000,
|
"id": 3,
|
||||||
"about_me": "Миллион алых роз"
|
"phone_number": 89559999999,
|
||||||
}
|
"first_name": "Гадий",
|
||||||
]
|
"second_name": "Петрович",
|
||||||
|
"role": "owner"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"phone_number": 89872844591,
|
||||||
|
"first_name": "Галкин",
|
||||||
|
"second_name": "Максим",
|
||||||
|
"role": "dogsitter",
|
||||||
|
"location": "Россия, республика Татарстан, Казань, проспект Ямашева, 83",
|
||||||
|
"price": 1750,
|
||||||
|
"about_me": "Миллион алых роз",
|
||||||
|
"rating": 4.5,
|
||||||
|
"ratings": [
|
||||||
|
4,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"tg": "maks100500"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"interactions": [
|
||||||
|
{
|
||||||
|
"owner_id": 3,
|
||||||
|
"dogsitter_id": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"owner_id": 1,
|
||||||
|
"dogsitter_id": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
24
server/routers/dogsitters-finder/model/interaction.js
Normal file
24
server/routers/dogsitters-finder/model/interaction.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const { Schema, model } = require("mongoose");
|
||||||
|
|
||||||
|
const { DSF_AUTH_USER_MODEL_NAME, DSF_INTERACTION_MODEL_NAME } = require("../../const");
|
||||||
|
|
||||||
|
const interactionSchema = new Schema({
|
||||||
|
owner_id: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: DSF_AUTH_USER_MODEL_NAME,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
dogsitter_id: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: DSF_AUTH_USER_MODEL_NAME,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
timestamp: {
|
||||||
|
type: Date,
|
||||||
|
default: Date.now
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interactionSchema.index({ owner_id: 1, dogsitter_id: 1 });
|
||||||
|
|
||||||
|
module.exports.Interaction = model(DSF_INTERACTION_MODEL_NAME, interactionSchema);
|
||||||
83
server/routers/dogsitters-finder/model/user.js
Normal file
83
server/routers/dogsitters-finder/model/user.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
const { Schema, model } = require("mongoose");
|
||||||
|
|
||||||
|
const { DSF_AUTH_USER_MODEL_NAME } = require("../../const");
|
||||||
|
|
||||||
|
const userSchema = new Schema({
|
||||||
|
phone_number: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true,
|
||||||
|
match: /^\+?\d{10,15}$/
|
||||||
|
},
|
||||||
|
first_name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
trim: true
|
||||||
|
},
|
||||||
|
second_name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
trim: true
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
type: String,
|
||||||
|
enum: ["dogsitter", "owner"],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
location: {
|
||||||
|
type: String,
|
||||||
|
required: function() {
|
||||||
|
return this.role === "dogsitter";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
price: {
|
||||||
|
type: Number,
|
||||||
|
min: 0,
|
||||||
|
required: function() {
|
||||||
|
return this.role === "dogsitter";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
about_me: {
|
||||||
|
type: String,
|
||||||
|
maxlength: 500
|
||||||
|
},
|
||||||
|
rating: {
|
||||||
|
type: Number,
|
||||||
|
min: 0,
|
||||||
|
max: 5,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
ratings: {
|
||||||
|
type: [Number],
|
||||||
|
default: [],
|
||||||
|
validate: {
|
||||||
|
validator: function(arr) {
|
||||||
|
return arr.every(v => v >= 0 && v <= 5);
|
||||||
|
},
|
||||||
|
message: "Рейтинг должен быть в диапазоне от 0 до 5!"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tg: {
|
||||||
|
type: String,
|
||||||
|
match: /^[a-zA-Z0-9_]{5,32}$/
|
||||||
|
},
|
||||||
|
created: {
|
||||||
|
type: Date,
|
||||||
|
default: Date.now
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
userSchema.virtual("id").get(function() {
|
||||||
|
return this._id.toHexString();
|
||||||
|
});
|
||||||
|
|
||||||
|
userSchema.set("toJSON", {
|
||||||
|
virtuals: true,
|
||||||
|
versionKey: false,
|
||||||
|
transform: function(doc, ret) {
|
||||||
|
delete ret._id;
|
||||||
|
delete ret.__v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.User = model(DSF_AUTH_USER_MODEL_NAME, userSchema);
|
||||||
149
server/routers/dogsitters-finder/routes.js
Normal file
149
server/routers/dogsitters-finder/routes.js
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
const { Router } = require('express')
|
||||||
|
const { expressjwt } = require('express-jwt')
|
||||||
|
|
||||||
|
const { getAnswer } = require('../../utils/common')
|
||||||
|
const { User, Interaction } = require('./model')
|
||||||
|
const { TOKEN_KEY } = require('./const')
|
||||||
|
const { requiredValidate } = require('./utils')
|
||||||
|
|
||||||
|
const router = Router()
|
||||||
|
|
||||||
|
// Получение списка пользователей
|
||||||
|
router.get('/users', async (req, res) => {
|
||||||
|
|
||||||
|
const users = await User.find()
|
||||||
|
.select('-__v -ratings -phone_number')
|
||||||
|
.lean()
|
||||||
|
|
||||||
|
console.log('get users successfull')
|
||||||
|
|
||||||
|
res.send(getAnswer(null, users))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Получение конкретного пользователя
|
||||||
|
router.get('/dogsitter-viewing', async (req, res) => {
|
||||||
|
const { userId } = req.params
|
||||||
|
|
||||||
|
const user = await User.findById(userId)
|
||||||
|
.select('-__v -ratings')
|
||||||
|
.lean()
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).send(getAnswer(new Error('Пользователь не найден')))
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send(getAnswer(null, user))
|
||||||
|
})
|
||||||
|
|
||||||
|
router.use(expressjwt({ secret: TOKEN_KEY, algorithms: ['HS256'] }))
|
||||||
|
|
||||||
|
// Добавление оценки пользователю
|
||||||
|
router.post('/dogsitter-viewing/rating', requiredValidate('value'), async (req, res) => {
|
||||||
|
const { userId } = req.params
|
||||||
|
const { value } = req.body
|
||||||
|
const authUserId = req.auth.id
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await User.findById(userId)
|
||||||
|
if (!user) throw new Error('Пользователь не найден')
|
||||||
|
if (user.role !== 'dogsitter') throw new Error('Нельзя оценивать этого пользователя')
|
||||||
|
if (user.id === authUserId) throw new Error('Нельзя оценивать самого себя')
|
||||||
|
|
||||||
|
user.ratings.push(Number(value))
|
||||||
|
user.rating = user.ratings.reduce((a, b) => a + b, 0) / user.ratings.length
|
||||||
|
|
||||||
|
const updatedUser = await user.save()
|
||||||
|
|
||||||
|
res.send(getAnswer(null, {
|
||||||
|
id: updatedUser.id,
|
||||||
|
rating: updatedUser.rating.toFixed(1),
|
||||||
|
totalRatings: updatedUser.ratings.length
|
||||||
|
}))
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).send(getAnswer(error))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Обновление информации пользователя
|
||||||
|
router.patch('/users', async (req, res) => {
|
||||||
|
const { userId } = req.params
|
||||||
|
const updates = req.body
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await User.findByIdAndUpdate(userId, updates, { new: true })
|
||||||
|
.select('-__v -ratings')
|
||||||
|
|
||||||
|
if (!user) throw new Error('Пользователь не найден')
|
||||||
|
res.send(getAnswer(null, user))
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).send(getAnswer(error))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Создание объекта взаимодействия
|
||||||
|
router.post('/interactions',
|
||||||
|
expressjwt({ secret: TOKEN_KEY, algorithms: ['HS256'] }),
|
||||||
|
requiredValidate('dogsitter_id'),
|
||||||
|
async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { dogsitter_id } = req.body
|
||||||
|
const owner_id = req.auth.id // ID из JWT токена
|
||||||
|
|
||||||
|
// Проверка существования пользователей
|
||||||
|
const [owner, dogsitter] = await Promise.all([
|
||||||
|
User.findById(owner_id),
|
||||||
|
User.findById(dogsitter_id)
|
||||||
|
])
|
||||||
|
|
||||||
|
if (!owner || owner.role !== 'owner') {
|
||||||
|
throw new Error('Владелец не найден или имеет неверную роль')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dogsitter || dogsitter.role !== 'dogsitter') {
|
||||||
|
throw new Error('Догситтер не найден или имеет неверную роль')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создание взаимодействия
|
||||||
|
const interaction = await Interaction.create({
|
||||||
|
owner_id,
|
||||||
|
dogsitter_id
|
||||||
|
})
|
||||||
|
|
||||||
|
res.send(getAnswer(null, {
|
||||||
|
id: interaction.id,
|
||||||
|
timestamp: interaction.timestamp
|
||||||
|
}))
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).send(getAnswer(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
router.get('/interactions/check', async (req, res) => {
|
||||||
|
const { owner_id, dogsitter_id } = req.query;
|
||||||
|
|
||||||
|
if (!owner_id || !dogsitter_id) {
|
||||||
|
return res.status(400).send(getAnswer('Missing owner_id or dogsitter_id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Поиск взаимодействий по owner_id и dogsitter_id
|
||||||
|
const interactions = await Interaction.find({ owner_id, dogsitter_id })
|
||||||
|
.select('-__v') // Выбираем только нужные поля
|
||||||
|
.lean();
|
||||||
|
|
||||||
|
if (interactions.length === 0) {
|
||||||
|
return res.status(404).send(getAnswer('No interactions found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send(getAnswer(null, interactions));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking interactions:', error);
|
||||||
|
res.status(500).send(getAnswer('Internal Server Error'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router
|
||||||
Reference in New Issue
Block a user