init from origin + update

This commit is contained in:
2023-08-01 13:14:02 +03:00
commit 321dc4c3c5
1109 changed files with 16019 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
const router = require('express').Router()
const checkPwd = require('pbkdf2-password')()
const jwt = require('jsonwebtoken')
const { BASKET_JWT_TOKEN } = require('./key')
const { getResponse, signUp, getUser, _idToId, requiredFields } = require('./controller')
router.post('/sign-in', requiredFields(['email', 'password']), async (req, res) => {
try {
const user = await getUser(req.body)
// eslint-disable-next-line max-len
checkPwd({ password: req.body.password, salt: user.salt }, async (err, pass, salt, hash) => {
if (err) throw new Error(err)
if (user.pwd === hash) {
const { pwd, salt: _salt, ...rest } = user
const token = jwt.sign(_idToId(rest), BASKET_JWT_TOKEN)
res.send(getResponse(null, { token, user: _idToId(rest) }))
} else {
res.status(400).send(getResponse('Неправильный email или пароль'))
}
})
} catch (e) {
res.status(400).send(getResponse(e.message))
}
})
router.post('/sign-up', requiredFields(['email', 'login', 'password']), async (req, res) => {
let error = null
const data = await signUp(req.body).catch((e) => error = e.message)
res.status(error ? 400 : 200).send(getResponse(error, data))
})
module.exports = router

View File

@@ -0,0 +1,25 @@
const router = require('express').Router()
const { expressjwt } = require('express-jwt')
const ObjectId = require('mongodb').ObjectID
const { BASKET_JWT_TOKEN } = require('./key')
const { getResponse, getCategory, postCategory } = require('./controller')
router.use(expressjwt({ secret: BASKET_JWT_TOKEN, algorithms: ['HS256'] }))
router.get('/', async (req, res) => {
const userId = new ObjectId(req.auth.id)
let error = null
const categoryData = await getCategory({ userId }).catch((e) => error = e.message)
res.send(getResponse(error, categoryData))
})
router.post('/', async (req, res) => {
const userId = new ObjectId(req.auth.id)
let error = null
const categoryData = await postCategory({ userId, ...req.body }).catch((e) => error = e.message)
res.send(getResponse(error, categoryData))
})
module.exports = router

View File

@@ -0,0 +1,510 @@
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,
}

View File

@@ -0,0 +1,54 @@
const router = require('express').Router()
const { expressjwt } = require('express-jwt')
const ObjectId = require('mongodb').ObjectID
const { BASKET_JWT_TOKEN } = require('./key')
const {
getResponse, addList,
getLists, deleteDoc, renameList, duplicateList,
} = require('./controller')
const wait = (req, res, next) => setTimeout(next, 0)
router.use(expressjwt({ secret: BASKET_JWT_TOKEN, algorithms: ['HS256'] }))
/* получить списки покупок*/
router.get('/list', wait, async (req, res) => {
const userId = new ObjectId(req.auth.id)
let error = null
const listData = await getLists({ userId }).catch((e) => error = e.message)
res.status(error ? 400 : 200).send(getResponse(error, listData))
})
/* удалить список*/
router.delete('/list', wait, async (req, res) => {
let error = null
const listData = await deleteDoc(req.body).catch((e) => error = e.message)
res.status(error ? 400 : 200).send(getResponse(error, listData))
})
/* добавить новый список*/
router.post('/list', wait, async (req, res) => {
const userId = new ObjectId(req.auth.id)
let error = null
// eslint-disable-next-line max-len
const listData = await addList({ userId, ...req.body }).catch((e) => error = e.message)
res.status(error ? 400 : 200).send(getResponse(error, listData))
})
/* переименовать список*/
router.put('/list', wait, async (req, res) => {
let error = null
const listData = await renameList(req.body).catch((e) => error = e.message)
res.status(error ? 400 : 200).send(getResponse(error, listData))
})
/* дублировать список*/
router.post('/list/duplicate', wait, async (req, res) => {
let error = null
const listData = await duplicateList(req.body).catch((e) => error = e.message)
res.status(error ? 400 : 200).send(getResponse(error, listData))
})
module.exports = router

View File

@@ -0,0 +1,9 @@
const router = require('express').Router()
router.use('/dashboard', require('./dashboard'))
router.use('/landing', require('./landing'))
router.use('/categories', require('./categories'))
router.use('/shoppingList', require('./listItem'))
router.use('/auth', require('./auth'))
module.exports = router

View File

@@ -0,0 +1,10 @@
{
"data": {
"token": "qwert",
"user": {
"id": "1234",
"login": "eldar",
"email": "www@www.ru"
}
}
}

View File

@@ -0,0 +1,23 @@
{
"success": true,
"data":
{
"category":[
{
"id":1,
"name": "Продукты",
"color": "#08AE0F"
},
{
"id":2,
"name": "Бытовая химия",
"color": "#3414F5"
},
{
"id":3,
"name": "Одежда",
"color": "#FA8803"
}
]
}
}

View File

@@ -0,0 +1,18 @@
{
"success": true,
"data":
{
"category":[
{
"id":1,
"name": "Продукты",
"color": "#08AE0F"
},
{
"id":2,
"name": "Бытовая химия",
"color": "#3414F5"
}
]
}
}

View File

@@ -0,0 +1,3 @@
{
"error": "Не получилось..."
}

View File

@@ -0,0 +1,8 @@
{
"data": {
"id": "vrgbtrgbtrbryn",
"listName": "2 список",
"purchased": 1,
"total": 5
}
}

View File

@@ -0,0 +1,4 @@
{
"success": true,
"data": []
}

View File

@@ -0,0 +1,4 @@
{
"success": false,
"error": "Список не загрузился"
}

View File

@@ -0,0 +1,23 @@
{
"success": true,
"data": [
{
"id": "uuid1",
"listName": "Состоялась 94-я церемония вручения премии «Оскар»: награда за лучший фильм присуждена картине...",
"purchased": 0,
"total": 5
},
{
"id": "uuid2",
"listName": "Второй список",
"purchased": 1,
"total": 5
},
{
"id": "uuid3",
"listName": "Первый список",
"purchased": 5,
"total": 5
}
]
}

View File

@@ -0,0 +1,4 @@
{
"success": false,
"error": "Ошибка получения данных для Landing"
}

View File

@@ -0,0 +1,80 @@
{
"success": true,
"data": [
{
"features": [
{
"id": "1",
"nameImg": "image-shopping-list.png",
"altImg": "image-shopping-list",
"title": "Список покупок",
"text": "Отличный интерфейс, удобно и понятно для использования. Удобно! Если нет нужного в списке, можно добавить и оно сохраниться."
},
{
"id": "2",
"nameImg": "image-shared-access.png",
"altImg": "image-shared-access",
"title": "Общий доступ",
"text": "Делитесь списками покупок, чтобы планировать вместе с другими. Синхронизация для нескольких покупателей — это вещь!"
},
{
"id": "3",
"nameImg": "image-messaging.png",
"altImg": "image-messaging",
"title": "Обмен сообщениями",
"text": "Хотите что-то обсудить при составление списка покупок, то есть возможность обмениваться сообщениями в режиме реального времени."
},
{
"id": "4",
"nameImg": "image-share-photos.png",
"altImg": "mage-share-photos",
"title": "Делитесь фотографиями",
"text": "Не перепутайте товар при покупки — загружайте фотографии и обменивайтесь ими с другими пользователями. Это очень удобно!"
},
{
"id": "5",
"nameImg": "image-prices.png",
"altImg": "image-prices",
"title": "Цены",
"text": "Расходы под контролем — вводите цены в спиок покупок. Приложение оценит стоимость и будет известно, чего ожидать на кассе."
},
{
"id": "6",
"nameImg": "image-ecology.png",
"altImg": "image-ecology",
"title": "Экология",
"text": "Бумажные списки покупок — это деревья, которые могли бы еще расти. Вместо тысяч слов — возьми с собой приложение!"
},
{
"id": "7",
"nameImg": "image-buy.png",
"altImg": "image-buy",
"title": "Покупайте",
"text": "97% пользователей поробывали приложение и теперь совершают меньше лишних и ненужных покупок. Покупайте нужное!"
}
],
"helps": [
{
"id": "1",
"title": "Как работает вМагазин?",
"text": "Задача организации, в особенности же консультация с широким активом обеспечивает широкому кругу специалистов новых принципов формирования материально-технической и кадровой базы."
},
{
"id": "2",
"title": "Условия оплаты",
"text": "Задача организации, в особенности же консультация с широким активом обеспечивает широкому кругу специалистов новых принципов формирования материально-технической и кадровой базы."
},
{
"id": "3",
"title": "Как с нами связаться",
"text": "Задача организации, в особенности же консультация с широким активом обеспечивает широкому кругу специалистов новых принципов формирования материально-технической и кадровой базы."
},
{
"id": "4",
"title": "Текст",
"text": "Задача организации, в особенности же консультация с широким активом обеспечивает широкому кругу специалистов новых принципов формирования материально-технической и кадровой базы."
}
]
}
]
}

View File

@@ -0,0 +1,3 @@
{
"error": "Не удалось изменить..."
}

View File

@@ -0,0 +1,15 @@
{
"success": true,
"data":
{
"id": 23,
"categoryId": 1,
"text": "Курица",
"count": 17,
"bought": false,
"createdBy": "",
"createdDt": "",
"modifiedBy": "",
"modifiedDt": ""
}
}

View File

@@ -0,0 +1,4 @@
{
"success": false,
"error": "Ошибка получения данных"
}

View File

@@ -0,0 +1,34 @@
{
"success": true,
"data":
{
"id": 8,
"listName":"Мой список",
"data":[
{
"id": 1,
"categoryId": 1,
"text": "Курица",
"count": 2,
"bought": true,
"delete":false
},
{
"id": 2,
"categoryId":1,
"text": "Хлеб",
"count": 1,
"bought":false,
"delete":true
},
{
"id": 3,
"categoryId": "3",
"text": "Шампунь",
"count": 1,
"bought":true,
"delete":false
}
]
}
}

View File

@@ -0,0 +1,5 @@
const BASKET_JWT_TOKEN = 'super super secret key'
module.exports = {
BASKET_JWT_TOKEN,
}

View File

@@ -0,0 +1,7 @@
const router = require('express').Router()
router.get('/', (req, res) => {
res.send(require('./json/landing/success.json'))
})
module.exports = router

View File

@@ -0,0 +1,67 @@
const router = require('express').Router()
const { expressjwt } = require('express-jwt')
const ObjectId = require('mongodb').ObjectID
const { BASKET_JWT_TOKEN } = require('./key')
const { getShoppingList, deleteItem, boughtItem, incCountItem, getResponse, addListItem } = require('./controller')
router.use(expressjwt({ secret: BASKET_JWT_TOKEN, algorithms: ['HS256'] }))
router.get('/:id', async (req, res) => {
const userId = new ObjectId(req.auth.id)
let error = null
// eslint-disable-next-line no-return-assign
const { id } = req.params
// eslint-disable-next-line no-return-assign
const listData = await getShoppingList({ userId, id }).catch((e) => error = e.message)
res.send(getResponse(error, listData))
})
router.post('/item/:id', async (req, res) => {
const userId = new ObjectId(req.auth.id)
let error = null
// eslint-disable-next-line no-return-assign
const { id } = req.params
const { categoryId, text } = req.body
const shoppingListData = await addListItem({
userId, listId: id, categoryId, text,
// eslint-disable-next-line no-return-assign
}).catch((e) => error = e.message)
res.send(getResponse(error, shoppingListData))
})
router.patch('/item/:id', async (req, res) => {
const userId = new ObjectId(req.auth.id)
let error = null
// eslint-disable-next-line no-return-assign
const { id } = req.params
const { bought } = req.body.item
// eslint-disable-next-line no-return-assign
// eslint-disable-next-line max-len
const itemData = await boughtItem({ userId, itemId: id, bought }).catch((e) => error = e.message)
res.send(getResponse(error, itemData))
})
router.put('/item/:id', async (req, res) => {
const userId = new ObjectId(req.auth.id)
let error = null
// eslint-disable-next-line no-return-assign
const { id } = req.params
const { count } = req.body
// eslint-disable-next-line no-return-assign
// eslint-disable-next-line max-len
const itemData = await incCountItem({ userId, itemId: id, count }).catch((e) => error = e.message)
res.send(getResponse(error, itemData))
})
router.delete('/item/:id', async (req, res) => {
let error = null
// eslint-disable-next-line no-return-assign
const { id } = req.params
// eslint-disable-next-line no-return-assign
const itemData = await deleteItem({ itemId: id }).catch((e) => error = e.message)
res.send(getResponse(error, itemData))
})
module.exports = router