Иди, иди, иди! Это неделя Capstone в моей школе, Galvanize Austin, двухнедельный проект, в котором мы используем все навыки, полученные за последние шесть месяцев, для создания полнофункционального приложения!
Для моего Capstone я решил создать приложение под названием Smiles. Smiles — это мобильная платформа, которая помогает доставлять остатки еды в приюты для бездомных. Для моего мобильного интерфейса я использую React и React-native в дополнение к Axios, чтобы получить доступ к моему абстрактному API. До этого проекта единственным фреймворком, который я использовал, был Angular, но услышать о React и возможностях React-native для создания приложений как для iOS, так и для Android, казалось очень заманчивым. Мне очень нравится использовать React, она не похожа ни на одну другую библиотеку, которую я использовал раньше (не забудьте привязать эту!!!)
Для моего API я использую Express и Knex для запроса моей базы данных PostgreSQL, а также Bcrypt для авторизации. Все это размещается на сервере Heroku, чтобы мой интерфейс мог получить к нему доступ.
В моем приложении есть три разных типа пользователей: продавцы еды, волонтеры и приюты. Однако все они начинаются на странице входа и после этого разветвляются.
Страница входа принимает адрес электронной почты и пароль и отправляет запрос Axios POST с учетными данными при нажатии кнопки входа. Оттуда мой API должен найти пользователя и отправить обратно его данные или «неверный логин». Сложность заключается в том, что в базе данных есть таблица для каждого типа пользователей, и, имея только адрес электронной почты и пароль, я не могу узнать, в какой таблице они находятся.
Первой моей мыслью было, что я буду проверять все таблицы одну за другой, пока не найду пользователя или не дойду до конца, а пользователя нет. Хотя это сработало бы, очень быстро стало трудно читать и казалось, что код жестко запрограммирован. Я знал, что должно быть лучшее решение.
Следующей моей мыслью было то, что процесс проверки каждой таблицы был идентичен, поэтому я мог бы использовать итератор для уменьшения объема кода, вот мое решение.
const titles = ['foodvendors','volunteers','shelters']; router.post('/login', (req, res) => { titles.forEach(title => { knex(title) .where({ email: req.body.email }) .first() .then(user => { if(user) { bcrypt.compare(req.body.password, user.hashedPassword) .then((valid) => { if (valid) { user.title = title; res.send(user); } }); } }); }); res.send('invalid login'); });
Хотя это значительно уменьшило объем кода, что сделало его легко читаемым, в нем был недостаток. В то время как forEach является синхронным, запросы knex являются асинхронными, поэтому forEach отправляет асинхронный запрос для каждой таблицы. До того, как какой-либо из ответов вернулся, forEach завершается, и сразу же отправляется «неверный логин». Неееет!! Так близко!
Эта проблема заставила меня почесать голову, мне нужен был какой-то способ убедиться, что я получил ответы на все запросы knex до того, как смогу отправить «неверный логин». Мое решение состояло в том, чтобы использовать счетчик, который будет подсчитываться после возврата асинхронного вызова knex, таким образом, как только подсчет достигнет длины массива таблиц, я знал, что все они вернулись!
const titles = ['foodvendors','volunteers','shelters']; router.post('/login', (req, res) => { let completed = 0; titles.forEach(title => { knex(title) .where({ email: req.body.email }) .first() .then(user => { if(user) { bcrypt.compare(req.body.password, user.hashedPassword) .then((valid) => { if (valid) { user.title = title; res.send(user); } completed++; if(completed === titles.length) { res.send('invalid login') } }); } else { completed++; if(completed === titles.length) { res.send('invalid login') } } }); }); });
Успех! Это сработало, к сожалению, мне понадобилось две проверки счетчика, одна на обнаружение пользователя, а другая на отсутствие. Однако я по-прежнему стремился найти лучшее решение.
Затем я обратился к Javascript Promises. Обещание — это возможное завершение асинхронной операции. Для моей проблемы Promise.all — идеальное решение! Promise.all принимает массив промисов и имеет метод .then, который возвращает все результирующие значения только после того, как все промисы выполнены. Для этого я использовал карту, чтобы превратить мой массив таблиц для запроса в массив промисов, запрашивающих указанные таблицы. Оттуда я вызываю Promise.all.
const titles = ['foodvendors','volunteers','shelters']; router.post('/login', (req, res) => { let queries = titles.map(title => { return knex(title) .where({ email: req.body.email }) .first() }); Promise.all(queries) .then((results) => { results.forEach(tableResult => { if(tableResult){ res.send(tableResult); } }); }); res.send('invalid login'); });
Хотя это решение работало, мне по-прежнему требовался способ реализации моей авторизации с помощью сравнения Bcrypt. Проблема заключалась в том, что Bcrypt также является асинхронной операцией. Мне нужно было бы связать другой Promise.all, чтобы после возврата запросов knex Bcrypt мог выполняться, а ответ «неверный вход» ждал, пока не вернутся результаты Bcrypt. Мне казалось, что добавление еще одного Promise.all сделает код трудным для чтения.
Моя следующая мысль заключалась в том, что если бы я создал новую таблицу в своей базе данных, называемую электронной почтой, содержащую электронные письма и соответствующие им заголовки, то я мог бы сначала проверить таблицу электронной почты, чтобы найти таблицу, в которой находится пользователь, а затем я мог бы перейти прямо к этой таблице. и найти пользователя. Если электронная почта не находится в таблице электронной почты, я сразу узнаю, что электронной почты нет ни в одной таблице.
Это решение, хотя и добавляет в базу данных дополнительную таблицу, обеспечивает решение не только этой проблемы, но и многих других. Например, при добавлении нового поставщика продуктов питания я не смогу использовать адрес электронной почты, используемый в другой таблице. Эта таблица электронной почты поможет сделать эту проверку намного проще. Вот мое выполнение этого решения:
router.post('/login', (req, res) => { knex('emails') .where({ email: req.body.email }) .first() .then(account => { if(account) { console.log(account.title); knex(account.title) .where({ email: req.body.email }) .first() .then(user => { console.log(user); bcrypt.compare(req.body.password, user.hashedPassword) .then((valid) => { if (valid) { user.title = account.title; res.send(user); } }); }); } else { res.send('invalid login'); } }); });
Отлично, все работает, и я могу войти в свое приложение под любым пользователем!