Merge pull request 'dogsitters-finder' (#91) from dogsitters-finder into master
Reviewed-on: #91
This commit is contained in:
commit
106f835934
74
server/routers/dogsitters-finder/auth.js
Normal file
74
server/routers/dogsitters-finder/auth.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
const { Router } = require("express");
|
||||||
|
const hash = require("pbkdf2-password")();
|
||||||
|
const { promisify } = require("node:util");
|
||||||
|
const jwt = require('jsonwebtoken')
|
||||||
|
|
||||||
|
const { getAnswer } = require("../../utils/common");
|
||||||
|
|
||||||
|
const { AuthModel } = require("./model/todo/auth");
|
||||||
|
const { TOKEN_KEY } = require('./const')
|
||||||
|
const { UserModel } = require("./model/todo/user");
|
||||||
|
|
||||||
|
const { requiredValidate } = require('./utils')
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/signup",
|
||||||
|
requiredValidate("login", "password", "email"),
|
||||||
|
async (req, res, next) => {
|
||||||
|
const { login, password, email } = req.body
|
||||||
|
|
||||||
|
const user = await AuthModel.findOne({ login });
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
throw new Error("Пользователь с таким логином уже существует");
|
||||||
|
}
|
||||||
|
|
||||||
|
hash({ password }, async function (err, pass, salt, hash) {
|
||||||
|
if (err) return next(err);
|
||||||
|
|
||||||
|
const user = await UserModel.create({ login, email });
|
||||||
|
await AuthModel.create({ login, hash, salt, userId: user.id });
|
||||||
|
|
||||||
|
res.json(getAnswer(null, { ok: true }))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function authenticate(login, pass, cb) {
|
||||||
|
AuthModel.findOne({ login }).populate('userId').exec().then((user) => {
|
||||||
|
if (!user) return cb(null, null)
|
||||||
|
|
||||||
|
hash({ password: pass, salt: user.salt }, function (err, pass, salt, hash) {
|
||||||
|
if (err) return cb(err)
|
||||||
|
if (hash === user.hash) return cb(null, user)
|
||||||
|
cb(null, null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const auth = promisify(authenticate)
|
||||||
|
|
||||||
|
router.post('/signin', requiredValidate('login', 'password'), async (req, res) => {
|
||||||
|
const { login, password } = req.body
|
||||||
|
|
||||||
|
const user = await auth(login, password)
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error("Неверный логин или пароль")
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessToken = jwt.sign({
|
||||||
|
...JSON.parse(JSON.stringify(user.userId)),
|
||||||
|
}, TOKEN_KEY, {
|
||||||
|
expiresIn: '12h'
|
||||||
|
})
|
||||||
|
|
||||||
|
res.json(getAnswer(null, {
|
||||||
|
user: user.userId,
|
||||||
|
token: accessToken,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = router
|
3
server/routers/dogsitters-finder/const.js
Normal file
3
server/routers/dogsitters-finder/const.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
exports.DSF_AUTH_PASSWD_MODEL_NAME = 'DSF_AUTH_PASSWD'
|
||||||
|
exports.DSF_AUTH_USER_MODEL_NAME = 'DSF_AUTH_USER'
|
||||||
|
exports.DSF_INTERACTION_MODEL_NAME = 'DSF_INTERACTION'
|
@ -67,5 +67,3 @@ router.get("/auth/session", (request, response) => {
|
|||||||
return response.status(403).json({ error: "Invalid token" });
|
return response.status(403).json({ error: "Invalid token" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
|
44
server/routers/dogsitters-finder/model/auth.js
Normal file
44
server/routers/dogsitters-finder/model/auth.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
const { Schema, model } = require("mongoose");
|
||||||
|
|
||||||
|
const {
|
||||||
|
DSF_AUTH_PASSWD_MODEL_NAME,
|
||||||
|
DSF_AUTH_USER_MODEL_NAME,
|
||||||
|
} = require("../../const");
|
||||||
|
|
||||||
|
const schema = new Schema({
|
||||||
|
login: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
hash: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
salt: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: DSF_AUTH_USER_MODEL_NAME
|
||||||
|
},
|
||||||
|
created: {
|
||||||
|
type: Date,
|
||||||
|
default: () => new Date().toISOString(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
schema.set("toJSON", {
|
||||||
|
virtuals: true,
|
||||||
|
versionKey: false,
|
||||||
|
transform: function (doc, ret) {
|
||||||
|
delete ret._id;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
schema.virtual("id").get(function () {
|
||||||
|
return this._id.toHexString();
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.AuthModel = model(DSF_AUTH_PASSWD_MODEL_NAME, schema);
|
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
|
Loading…
Reference in New Issue
Block a user