511 lines
13 KiB
JavaScript
511 lines
13 KiB
JavaScript
|
const ObjectId = require('mongodb').ObjectID
|
||
|
const getHash = require('pbkdf2-password')()
|
||
|
|
||
|
const { getDB } = require('../../utils/mongo')
|
||
|
|
||
|
const USERS_COLLECTION = 'users'
|
||
|
const LISTS_COLLECTION = 'lists'
|
||
|
const CATEGORY_COLLECTION = 'default_categories'
|
||
|
const USER_CATEGROY_COLLECTION = 'user_categories'
|
||
|
const ITEM_COLLECTION = 'items'
|
||
|
const fakeUserId = 'fakeUserId'
|
||
|
|
||
|
let db = null
|
||
|
|
||
|
const connect = async () => {
|
||
|
db = await getDB('basket')
|
||
|
}
|
||
|
|
||
|
const init = async () => {
|
||
|
await connect()
|
||
|
const categoriesCollection = db.collection(CATEGORY_COLLECTION)
|
||
|
const findData = await categoriesCollection.find({
|
||
|
}).toArray()
|
||
|
if (findData.length === 0) {
|
||
|
await categoriesCollection.insertMany([
|
||
|
{
|
||
|
name: 'Продукты',
|
||
|
color: '#08AE0F',
|
||
|
},
|
||
|
{
|
||
|
name: 'Одежда',
|
||
|
color: '#9D79B9',
|
||
|
},
|
||
|
{
|
||
|
name: 'Бытовая химия',
|
||
|
color: '#B11F1F',
|
||
|
},
|
||
|
{
|
||
|
name: 'Лекарства',
|
||
|
color: '#3414F5',
|
||
|
},
|
||
|
])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
init()
|
||
|
|
||
|
const _idToId = (data) => {
|
||
|
const { _id, ...rest } = data
|
||
|
|
||
|
return {
|
||
|
id: _id,
|
||
|
...rest,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const _idToIdArray = (data) => {
|
||
|
const _idToIdMap = data.map((item) => _idToId(item))
|
||
|
|
||
|
return _idToIdMap
|
||
|
}
|
||
|
|
||
|
const getResponse = (error, data, success = true) => {
|
||
|
if (error) {
|
||
|
return {
|
||
|
success: false,
|
||
|
error,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
success,
|
||
|
data,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const signUp = async ({ email, login, password }) => {
|
||
|
if (db === null) throw new Error('no db connection')
|
||
|
|
||
|
try {
|
||
|
const usersCollection = db.collection(USERS_COLLECTION)
|
||
|
const userData = await usersCollection.findOne({
|
||
|
$or: [{
|
||
|
login,
|
||
|
}, {
|
||
|
email,
|
||
|
}],
|
||
|
})
|
||
|
|
||
|
if (userData?.login === login) {
|
||
|
throw new Error('Логин занят')
|
||
|
}
|
||
|
if (userData?.email === email) {
|
||
|
throw new Error('Email занят')
|
||
|
}
|
||
|
|
||
|
getHash({ password }, async (err, pass, salt, hash) => {
|
||
|
if (err) throw new Error(err)
|
||
|
// eslint-disable-next-line max-len
|
||
|
const { insertedCount } = await usersCollection.insertOne({ email, login, pwd: hash, salt })
|
||
|
if (!insertedCount) throw new Error('insert error')
|
||
|
})
|
||
|
|
||
|
return {}
|
||
|
} catch (e) {
|
||
|
throw new Error(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const getUser = async ({ email }) => {
|
||
|
if (db === null) throw new Error('no db connection')
|
||
|
|
||
|
try {
|
||
|
const usersCollection = db.collection(USERS_COLLECTION)
|
||
|
const userData = await usersCollection.findOne(
|
||
|
{
|
||
|
email,
|
||
|
},
|
||
|
)
|
||
|
if (userData) return userData
|
||
|
throw new Error('Неправильный email или пароль')
|
||
|
} catch (e) {
|
||
|
throw new Error(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const addList = async ({ userId = fakeUserId, ...data }) => {
|
||
|
if (db === null) throw new Error('no db connection')
|
||
|
|
||
|
try {
|
||
|
const listsCollection = db.collection(LISTS_COLLECTION)
|
||
|
const insertData = await listsCollection.insertOne({
|
||
|
userId,
|
||
|
timeStamp: Date.now(),
|
||
|
...data,
|
||
|
})
|
||
|
|
||
|
const { insertedCount, ops } = insertData
|
||
|
if (insertedCount) { return _idToId(ops[0]) }
|
||
|
throw new Error('insert error')
|
||
|
} catch (e) {
|
||
|
throw new Error(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const getLists = async ({ userId = fakeUserId }) => {
|
||
|
if (db === null) throw new Error('no db connection')
|
||
|
|
||
|
try {
|
||
|
const listsCollection = db.collection(LISTS_COLLECTION)
|
||
|
const itemsCollection = db.collection(ITEM_COLLECTION)
|
||
|
let newLists = []
|
||
|
|
||
|
const data = await listsCollection.find({
|
||
|
userId,
|
||
|
}).toArray()
|
||
|
|
||
|
await Promise.all(data.map(async (element) => {
|
||
|
const total = await itemsCollection.countDocuments({
|
||
|
parentId: element._id,
|
||
|
})
|
||
|
const purchased = await itemsCollection.countDocuments({
|
||
|
parentId: element._id,
|
||
|
bought: true,
|
||
|
})
|
||
|
|
||
|
newLists.push({
|
||
|
...element, total, purchased,
|
||
|
})
|
||
|
}))
|
||
|
|
||
|
newLists.sort((a, b) => (b.timeStamp - a.timeStamp))
|
||
|
|
||
|
return _idToIdArray(newLists)
|
||
|
} catch (e) {
|
||
|
throw new Error(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* добавил логику рекурсивного удаления дочерних документов */
|
||
|
const deleteDoc = async ({ id, tag = false }) => {
|
||
|
if (db === null) throw new Error('no db connection')
|
||
|
|
||
|
try {
|
||
|
const listsCollection = db.collection(LISTS_COLLECTION)
|
||
|
const itemsCollection = db.collection(ITEM_COLLECTION)
|
||
|
|
||
|
const findData = await itemsCollection.find({
|
||
|
parentId: new ObjectId(id),
|
||
|
}).toArray()
|
||
|
|
||
|
findData.forEach(async (element) => {
|
||
|
await deleteDoc({
|
||
|
id: element._id,
|
||
|
tag: true,
|
||
|
})
|
||
|
})
|
||
|
|
||
|
let delData = null
|
||
|
if (tag) {
|
||
|
delData = await itemsCollection.deleteOne({
|
||
|
_id: new ObjectId(id),
|
||
|
})
|
||
|
} else {
|
||
|
delData = await listsCollection.deleteOne({
|
||
|
_id: new ObjectId(id),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
const { deletedCount } = delData
|
||
|
if (deletedCount) {
|
||
|
return {
|
||
|
}
|
||
|
} throw new Error('no data to delete')
|
||
|
} catch (e) {
|
||
|
throw new Error(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const renameList = async ({ id, listName }) => {
|
||
|
if (db === null) throw new Error('no db connection')
|
||
|
|
||
|
try {
|
||
|
const listsCollection = db.collection(LISTS_COLLECTION)
|
||
|
const data = await listsCollection.updateOne({
|
||
|
_id: new ObjectId(id),
|
||
|
},
|
||
|
{
|
||
|
$set: {
|
||
|
listName,
|
||
|
},
|
||
|
})
|
||
|
|
||
|
const { matchedCount } = data
|
||
|
if (matchedCount) {
|
||
|
const findData = await listsCollection.findOne({
|
||
|
_id: new ObjectId(id),
|
||
|
})
|
||
|
return _idToId(findData)
|
||
|
} throw new Error('no data to rename')
|
||
|
} catch (e) {
|
||
|
throw new Error(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const duplicateList = async ({ id, parentId = null }) => {
|
||
|
if (db === null) throw new Error('no db connection')
|
||
|
|
||
|
try {
|
||
|
const listsCollection = db.collection(LISTS_COLLECTION)
|
||
|
const itemsCollection = db.collection(ITEM_COLLECTION)
|
||
|
|
||
|
let addListData = null
|
||
|
let newId = null
|
||
|
|
||
|
if (parentId) {
|
||
|
const findData = await itemsCollection.findOne(
|
||
|
{
|
||
|
_id: new ObjectId(id),
|
||
|
},
|
||
|
)
|
||
|
|
||
|
const { _id, ...item } = findData
|
||
|
item.parentId = parentId
|
||
|
const insertData = await itemsCollection.insertOne({
|
||
|
...item,
|
||
|
})
|
||
|
const { insertedCount } = insertData
|
||
|
if (!insertedCount) throw new Error('insert new item error')
|
||
|
} else {
|
||
|
const findData = await listsCollection.findOne(
|
||
|
{
|
||
|
_id: new ObjectId(id),
|
||
|
},
|
||
|
)
|
||
|
|
||
|
const { _id, timeStamp, ...item } = findData
|
||
|
item.listName = `(КОПИЯ) ${item.listName}`
|
||
|
addListData = await addList({
|
||
|
...item,
|
||
|
})
|
||
|
newId = addListData.id
|
||
|
}
|
||
|
|
||
|
const childData = await itemsCollection.find({
|
||
|
parentId: new ObjectId(id),
|
||
|
}).toArray()
|
||
|
|
||
|
childData.forEach(async (element) => {
|
||
|
await duplicateList({
|
||
|
id: element._id, parentId: newId,
|
||
|
})
|
||
|
})
|
||
|
|
||
|
if (addListData) return _idToId(addListData)
|
||
|
} catch (e) {
|
||
|
throw new Error(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const getCategory = async ({ userId }) => {
|
||
|
if (db === null) throw new Error('no db connection')
|
||
|
|
||
|
try {
|
||
|
const categoriesCollection = db.collection(CATEGORY_COLLECTION)
|
||
|
const defaultCategories = await categoriesCollection.find({
|
||
|
}).toArray()
|
||
|
const defaultCategoriesData = _idToIdArray(defaultCategories).map((dc) => ({
|
||
|
...dc, userId,
|
||
|
}))
|
||
|
|
||
|
const userCollection = db.collection(USER_CATEGROY_COLLECTION)
|
||
|
const userCategoriesFilter = {}
|
||
|
|
||
|
if (userId) {
|
||
|
userCategoriesFilter.userId = userId
|
||
|
}
|
||
|
const userFindData = await userCollection.find(userCategoriesFilter).toArray()
|
||
|
|
||
|
return [...defaultCategoriesData, ..._idToIdArray(userFindData)]
|
||
|
} catch (e) {
|
||
|
throw new Error(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const postCategory = async ({ userId = fakeUserId, ...categoryData }) => {
|
||
|
if (db === null) throw new Error('no db connection')
|
||
|
|
||
|
try {
|
||
|
const userCollection = db.collection(USER_CATEGROY_COLLECTION)
|
||
|
const insertData = await userCollection.insertOne({
|
||
|
userId, ...categoryData,
|
||
|
})
|
||
|
|
||
|
// const {insertedCount, ops} = insertData
|
||
|
// if (insertedCount)
|
||
|
// _idToId(ops[0])
|
||
|
// else
|
||
|
// throw new Error('insert error')
|
||
|
const userFindData = await userCollection.find({
|
||
|
userId,
|
||
|
}).toArray()
|
||
|
return _idToIdArray(userFindData)
|
||
|
} catch (e) {
|
||
|
throw new Error(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const getShoppingList = async ({ userId = fakeUserId, id }) => {
|
||
|
if (db === null) throw new Error('no db connection')
|
||
|
|
||
|
try {
|
||
|
const listsCollection = db.collection(ITEM_COLLECTION)
|
||
|
const itemsList = await listsCollection.find({
|
||
|
parentId: new ObjectId(id),
|
||
|
}).toArray()
|
||
|
const categoryList = await getCategory({ })
|
||
|
const coloredItemsList = itemsList.map((item) => ({
|
||
|
...item,
|
||
|
// eslint-disable-next-line max-len
|
||
|
color: categoryList.find((category) => String(category.id) === String(item.categoryId))?.color,
|
||
|
}))
|
||
|
|
||
|
return _idToIdArray(coloredItemsList)
|
||
|
} catch (e) {
|
||
|
throw new Error(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const addListItem = async ({ userId = fakeUserId, listId, categoryId, text }) => {
|
||
|
if (db === null) throw new Error('no db connection')
|
||
|
|
||
|
try {
|
||
|
const dataToInsert = {
|
||
|
parentId: new ObjectId(listId),
|
||
|
categoryId: new ObjectId(categoryId),
|
||
|
text,
|
||
|
count: 1,
|
||
|
bought: false,
|
||
|
createdBy: userId,
|
||
|
createdDt: Date.now(),
|
||
|
modifiedBy: userId,
|
||
|
modifiedDt: Date.now(),
|
||
|
}
|
||
|
const itemCollection = db.collection(ITEM_COLLECTION)
|
||
|
await itemCollection.insertOne(dataToInsert)
|
||
|
|
||
|
return _idToId(dataToInsert)
|
||
|
} catch (e) {
|
||
|
throw new Error(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const boughtItem = async ({ userId = fakeUserId, itemId, bought }) => {
|
||
|
if (db === null) throw new Error('no db connection')
|
||
|
try {
|
||
|
const itemCollection = db.collection(ITEM_COLLECTION)
|
||
|
const chengedData = await itemCollection.findOneAndUpdate({
|
||
|
_id: new ObjectId(itemId),
|
||
|
},
|
||
|
[{
|
||
|
$set: {
|
||
|
bought: { $eq: [false, '$bought'] },
|
||
|
modifiedBy: userId,
|
||
|
modifiedDt: Date.now(),
|
||
|
},
|
||
|
}])
|
||
|
return _idToId(chengedData)
|
||
|
} catch (e) {
|
||
|
throw new Error(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const incCountItem = async ({ userId = fakeUserId, itemId, count }) => {
|
||
|
if (db === null) throw new Error('no db connection')
|
||
|
|
||
|
try {
|
||
|
const itemCollection = db.collection(ITEM_COLLECTION)
|
||
|
const chengedData = await itemCollection.findOneAndUpdate({
|
||
|
_id: new ObjectId(itemId),
|
||
|
},
|
||
|
{
|
||
|
$inc: {
|
||
|
count,
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
$set: {
|
||
|
modifiedBy: userId,
|
||
|
modifiedDt: Date.now(),
|
||
|
},
|
||
|
})
|
||
|
const chengeData = await itemCollection.findOneAndUpdate({
|
||
|
_id: new ObjectId(itemId),
|
||
|
count: {
|
||
|
$lt: 1,
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
$set: {
|
||
|
count: 1,
|
||
|
modifiedBy: userId,
|
||
|
modifiedDt: Date.now(),
|
||
|
},
|
||
|
})
|
||
|
return _idToId(chengedData || chengeData)
|
||
|
} catch (e) {
|
||
|
throw new Error(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const deleteItem = async ({ itemId }) => {
|
||
|
if (db === null) throw new Error('no db connection')
|
||
|
|
||
|
try {
|
||
|
const itemCollection = db.collection(ITEM_COLLECTION)
|
||
|
const findItemData = await itemCollection.find({
|
||
|
_id: new ObjectId(itemId),
|
||
|
})
|
||
|
|
||
|
findItemData.forEach((item) => {
|
||
|
deleteItem({
|
||
|
id: item._id,
|
||
|
})
|
||
|
})
|
||
|
|
||
|
const deleteItemData = await itemCollection.deleteOne({
|
||
|
_id: new ObjectId(itemId),
|
||
|
})
|
||
|
|
||
|
const { deletedButton } = deleteItemData
|
||
|
if (deletedButton) {
|
||
|
return {
|
||
|
}
|
||
|
}
|
||
|
} catch (e) {
|
||
|
throw new Error(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const requiredFields = (fields) => (req, res, next) => {
|
||
|
// eslint-disable-next-line no-restricted-syntax
|
||
|
for (const fieldName of fields) {
|
||
|
if (!req.body[fieldName]) {
|
||
|
throw new Error(`Параметр ${fieldName} не установлен`)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
next()
|
||
|
}
|
||
|
|
||
|
module.exports = {
|
||
|
getResponse,
|
||
|
addList,
|
||
|
getLists,
|
||
|
deleteDoc,
|
||
|
renameList,
|
||
|
duplicateList,
|
||
|
getCategory,
|
||
|
postCategory,
|
||
|
getShoppingList,
|
||
|
addListItem,
|
||
|
boughtItem,
|
||
|
deleteItem,
|
||
|
incCountItem,
|
||
|
signUp,
|
||
|
getUser,
|
||
|
_idToId,
|
||
|
requiredFields,
|
||
|
}
|