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, }