Нужно создать новостной веб-сервер с REST API, который принимает HTTP-запросы и отдает ответы в формате JSON. Можно представить, что это сервер для мобильного приложения.
-
Пользователи
- Имя
- Логин
- Пароль
- Дата создания
- Админ (булевая переменная)
- Может ли создавать новости (булева переменная)
-
Категории (могут быть вложенными, то есть одна категория является подкатегорией для другой).
Допустим, есть категория "Языки программирования", и у нее может быть подкатегория "Динамически Типизированные ЯП", и далее, соответственно, подкатегория подкатегории "Python" — и таких уровней вложенности может быть произвольное количество.
-
Новости
- Краткое название
- Дата создания
- Один пользователь (создатель)
- Одна категория
- Текстовый контент
- Любое количество фотографий, от нуля
- Опубликована ли новость (булева переменная).
-
Когда по API запрашиваем новости, в каждой новости должны быть вложены все сущности (автор, категория (рекурсивно) и т.д.), за исключением картинок: в ответе нужно вернуть их URI.
-
API новостей должно поддерживать фильтрацию. Фильтрация определяется параметрами в URI query (см. примеры ниже). Клиент может использовать сразу несколько фильтров по разным полям, и тогда они объединяются при помощи логического "И". Например, фильтр
/news?created_at=2011-01-01&author=Ivan
выдает новости, созданные 1 января 2011 г. пользователем по имениIvan
. Поддерживаются фильтры для полей:-
День создания (созданные до даты, созданные с даты, созданные в указанный день)
Примеры (можно выбрать другие названия):
/news?created_at=2018-05-21
/news?created_until=2018-05-21
/news?created_since=2018-05-21
-
Имя пользователя-автора
-
Категория по айди
-
Название (вхождение подстроки)
-
Контент (вхождение подстроки)
-
-
API новостей должно поддерживать поиск по строке, которая может быть найдена либо в текстовом контенте, либо в имени автора, либо в названии категории.
-
API новостей должно поддерживать сортировку отдельным параметром URI query. Клиент может использовать сортировку вместе с любым набором фильтров. Критерии сортировки:
- Дата,
- Автор (имя по алфавиту),
- Категория (название по алфавиту),
- Количество фотографий
Пример:
/news?sort_by=category
. -
Должны быть отдельные эндпойнты для сущностей:
- Категории:
- создание только для админов
- получение списка для всех
- редактирование только для админов (редактирование названия и смена родительской категории)
- Новости:
- создание для юзеров, имеющих право создания новостей
- получение списка всем. Опубликованные новости видят все, а неопубликованные - только их автор.
- редактирование только автору новости. Редактироваться могут и опубликованные, и неопубликованные новости.
- Пользователи:
- создание только для админов
- получение списка всем
- Картинки:
- получение одной картинки.
- Категории:
-
Запросы на редактирование должны поддерживать редактирование любого количества полей сущности, т.е. одного поля, двух полей и т.д., вплоть до сразу всех. Например, при редактировании новости у нас должна быть возможность изменить только текст, не трогая остальные поля, или категорию вместе с заголовком, или любые другие поля в любых сочетаниях.
-
Что доступно только админам, при запросе не-админам возвращать 404, а не 403, чтобы не показать само наличие такого API.
-
Запросы по всем остальным URL должны возвращать 404.
-
Для аутентификации рекомендуем использовать basic auth как самый простой вариант. Аутентификацию не надо проверять в эндпойнтах, которые доступны всем.
-
Все API, возвращающие список данных, должны быть пагинированы, т.е. выдавать ограниченное количество элементов. Пагинация нужна для сокращения нагрузки на сервер: представьте, как она вырастет, если юзеры начнут массово запрашивать полный список из тысяч новостей, накопленных за годы работы сервера.
-
сервер никогда не выдает в ответе больше
N
элементов (т.е. новостей в запросе новостей, юзеров в запросе юзеров и т.п.). Значение N должно быть задано в одном месте (в коде или в конфиге), а не скопировано в разные места кода, чтобы ее можно было изменить правкой единственной строчки. -
клиент может указать, сколько элементов он хочет получить и начиная с какого по счету элемента. Пример для новостей:
/news?offset=15&limit=20
- вернуть 20 новостей, пропустив 15 штук от начала списка (названия параметров даны только для примера). Клиент может не указывать любой параметр или даже оба:- если
limit
опущен, подразумеваетсяN
- а если
offset
опущен, подразумевается0
.
- если
-
если клиент запросил значение
limit
большее, чемN
, он все равно получает не болееN
элементов. -
пагинацию реализуйте ключевыми словами
LIMIT
иOFFSET
в соответствующих SQL-запросах.
-
-
Сервер никогда не возвращает пароль пользователя.
-
В БД нужно хранить хеши паролей, а не сами пароли. Можно (но совсем необязательно) сделать сложнее и правильнее: использовать хеширование c солью, например, при помощи случайно сгенерированной соли и PBKDF2 (см. модуль
Crypto.KDF.PBKDF2
в библиотекеcryptonite
). -
В ответе на запрос картинки должен быть правильный заголовок
Content-Type
, например,Content-Type: image/png
. Как его узнать? Можно его передавать в запросе, который создает картинку. -
Для создания картинки достаточно передать ее содержимое, закодированное в base64, в запросе на создание или редактирование новости. Но при желании вы можете передать картинку в запросе типа
multipart/form-data
.
-
В качестве веб-сервера под капотом использовать Warp
Обязательно прочесть описание, как он работает: http://www.aosabook.org/en/posa/warp.html.
В целом эту книгу (AOSA Book) очень рекомендуем, там огромное количество инсайтов по многим крутым опенсорсным проектам.
Чтобы использовать Warp, необходимо понимать WAI, так что еще по нему прочитать — https://www.yesodweb.com/book/web-application-interface.
-
В качестве базы использовать PostgreSQL
-
Рекомендуемые библиотеки:
cryptonite
для хеширования паролейwarp
,wai
иservant
как основа для веб-сервера- библиотека для работы с PostgreSQL, любая, на выбор:
- попроще:
postgresql-simple
,hasql
- дольше изучать, но более безопасные в плане типов и чаще применяемые на
практике:
beam
,pesistent
+esqueleto
- попроще:
- библиотека для чтения конфига: любая, например,
aeson
,yaml
,configurator
,dhall
- вероятно, вам понадобится
text
,bytestring
,http-types
,network-uri
,containers
,unordeded-containers
,base64
,base64-bytestring
,vector
,array
и другие библиотеки из Haskell Platform. В целом можно использовать почти все подобные простые библиотеки. Библиотека должна решать одну задачу, а не вести себя как фреймворк, т.е. не работать одновременно с базой и вебом. Если у вас есть сомнения, можно ли использовать библиотеку, спросите в чате.
-
Для работы с базой можно использовать любые библиотеки, главное — самим создавать и поддерживать миграции. В идеале, в готовом проекте по миграциям должна прослеживаться история изменения таблиц в процессе разработки - то есть дожна быть не только инициализирующая миграция (разве что вы сразу всё красиво спроектировали:)), но и миграции для добавления новых таблиц, изменения названий и типов полей и т.д.
Миграции — код, который задает структуру базе (то есть создание таблиц, изменение, переименование, удаление полей и тд).
Обязательно держите в голове, что после того, как проект выпускается в продакшен, база на проде заполняется данными, которые ни в коем случае нельзя потерять. Но требование менять структуру базы регулярно появляется за время развития проекта, и надо уметь развивать базу без потери данных. Для этого нужно как можно тщательней формализовать все изменения, которые вы проводите над базой от версии к версии.
-
Должна быть предоставлена отдельная команда, которая позволит применить все миграции к локальной базе данных и создать всю нужную структуру для бд.
-
В отдельной папке проекта сделать sh-скрипты со всеми возможными CURL-запросами, чтобы мы могли быстро склонировать проект и потестить его. Сделать по sh-файлу на каждый CURL-запрос.
-
Также следовать общим правилам, описанным в прошлом задании.
Lessons learned while writing a Haskell application — не всем рекомендациям отсюда мы бы советовали следовать, но почитать про релевантный опыт автора статьи будет однозначно полезно.