Cервис представляет собой HTTP API со следующими требованиями к бизнес-логике:
- аутентификация и авторизация пользователей;
- создание группы устройств (компании);
- создание рециркуляторов, которые принадлежат компаниям;
- для рециркулятора задается расписание, которое он получает и работает по нему;
- периодически рециркулятор передает свои состояния, сохраняем в базу данных и потом из них собираем журнал;
- у рециркулятора свой ресурс, из которого вычитаются записи журнала;
- устройство получает и обновляет прошивку с сервера;
- если закончилась лампа или рециркулятор выключился, то будет отправлено уведомление в телеграм.
Ниже представлена абстрактная бизнес-логика взаимодействия пользователя с сервисом:
- Пользователь регистрируется в сервисе «Рециркуляторы».
- Пользователь создает группу устройств и добавляет туда устройства.
- Физические устройства подключаются к сервису, обновляют прошивку и получают расписание.
- Рециркуляторы каждые несколько секунд передают свое состояние.
- Ведется учет оставшегося ресурса у рециркулятора.
- Воркер, который периодически удаляет слишком короткие записи в журнале.
- Воркер, который будет писать в телеграм, когда включается/выключается рециркулятор или закончился ресурс.
Сервис «Рециркуляторы» должен предоставлять следующие HTTP-хендлеры:
POST /api/user/register
- регистрация пользователя;POST /api/user/login
- аутентификация пользователя;GET /api/user
- информация о текущем пользователе;GET /api/company
- получить компании пользователя;POST /api/company
- создать новую компанию;GET /api/company/{id}
- информация о компании;GET /api/company/{id}/devices
- устройства в компании;POST /api/device
- создать устройство;GET /api/device/{id}
- получить информацию об устройстве;GET /api/device/{id}/schedule
- получить расписание устройства;POST /api/device/{id}/schedule
- добавить расписание устройства;DELETE /api/device/{id}/schedule/{schedule_id}
- удалить расписание устройства;GET /api/device/{id}/journal
- получить журнал устройства;GET /api/device/info
- информация об устройстве;GET /api/device/firmware
- прошивка устройства;GET /api/device/schedule
- расписание устройства;POST /api/device/state
- состояния устройства.
Хендлер: POST /api/user/register
Регистрация производится по паре логин/пароль. Каждый логин должен быть уникальным. После успешной регистрации должна происходить автоматическая аутентификация пользователя. Bearer токен будет возвращен в заголовке Authorization.
POST /api/user/register HTTP/1.1
Content-Type: application/json
...
{
"login": "<login>",
"password": "<password>"
}
Возможные коды ответа:
201
— пользователь успешно зарегистрирован и аутентифицирован;400
— неверный формат запроса;409
— логин уже занят;500
— внутренняя ошибка сервера.
Хендлер: POST /api/user/login
Аутентификация производится по паре логин/пароль. Bearer токен будет возвращен в заголовке Authorization.
POST /api/user/login HTTP/1.1
Content-Type: application/json
...
{
"login": "<login>",
"password": "<password>"
}
Возможные коды ответа:
200
— пользователь успешно аутентифицирован;400
— неверный формат запроса;401
— неверная пара логин/пароль;500
— внутренняя ошибка сервера.
Хендлер: GET /api/user
Хендлер доступен только авторизованному пользователю. В ответе должна содержаться информация о текущем пользователе.
GET /api/user HTTP/1.1
Content-Length: 0
Возможные коды ответа:
-
200
— успешная обработка запроса:200 OK HTTP/1.1 Content-Type: application/json ... { "id": "d6bbeac5-57c2-40fc-a1e0-50497f052cd8", "login": "impressionableracoon", "is_admin": false, "last_online": "2023-02-23T11:36:53.550406Z" }
-
401
— пользователь не авторизован; -
500
— внутренняя ошибка сервера.
Хендлер: GET /api/company
Хендлер доступен только авторизованному пользователю. В ответе должна содержаться информация о компаниях пользователя. Администратор получает список всех компаний, а не только своих.
GET /api/company HTTP/1.1
Content-Length: 0
Возможные коды ответа:
-
200
— успешная обработка запроса:200 OK HTTP/1.1 Content-Type: application/json ... [ { "id": "6531872f-7120-4372-9489-895a7efc3714", "owner_id": "d6bbeac5-57c2-40fc-a1e0-50497f052cd8", "name": "Hospital #1", "time_offset": "-05:00:00" }, { "id": "77a97a45-d938-48e2-97d2-3772e4188c0f", "owner_id": "d6bbeac5-57c2-40fc-a1e0-50497f052cd8", "name": "Центральная больница", "time_offset": "00:00:00" }, { "id": "2bd8eb50-960a-4d14-a713-f0c6f1c38a92", "owner_id": "d6bbeac5-57c2-40fc-a1e0-50497f052cd8", "name": "Главный офис", "time_offset": "09:00:00" } ]
-
204
— компании не найдены; -
401
— пользователь не авторизован; -
500
— внутренняя ошибка сервера.
Хендлер: POST /api/company
Пользователь должен быть авторизован. Создаем компанию, принадлежащую текущему пользователю; название должно быть уникальным в пределах компаний пользователя.
POST /api/company HTTP/1.1
Content-Type: application/json
...
{
"name": "Поликлиника",
"time_offset": "03:00:00"
}
Возможные коды ответа:
-
201
— успешная обработка запроса:201 OK HTTP/1.1 Content-Type: application/json ... { "id": "4b6e3329-6acf-484c-a7f9-76a60faee9e1", "owner_id": "d6bbeac5-57c2-40fc-a1e0-50497f052cd8", "name": "Поликлиника", "time_offset": "03:00:00" }
-
400
— неверный формат запроса; -
401
— пользователь не авторизован; -
409
— компания с таким названием уже существует у пользователя; -
500
— внутренняя ошибка сервера.
Хендлер: GET /api/company/{id}
Пользователь должен быть авторизован. Получаем информацию о компании по ID.
GET /api/company/{id} HTTP/1.1
Content-Length: 0
Возможные коды ответа:
-
200
— успешная обработка запроса:200 OK HTTP/1.1 Content-Type: application/json ... { "id": "4b6e3329-6acf-484c-a7f9-76a60faee9e1", "owner_id": "d6bbeac5-57c2-40fc-a1e0-50497f052cd8", "name": "Поликлиника", "time_offset": "03:00:00" }
-
400
— неверный формат запроса; -
401
— пользователь не авторизован; -
403
— компания не принадлежит текущему пользователю; -
404
— компания не найдена; -
500
— внутренняя ошибка сервера.
Хендлер: GET /api/company/{id}/devices
Пользователь должен быть авторизован. Получаем информацию об устройствах в компании.
GET /api/company/{id}/devices HTTP/1.1
Content-Length: 0
Возможные коды ответа:
-
200
— успешная обработка запроса:200 OK HTTP/1.1 Content-Type: application/json ... [ { "id": "0b2a97f0-88c4-46cc-bc2c-4cef7ac4bc28", "company_id": "4b6e3329-6acf-484c-a7f9-76a60faee9e1", "name": "Коридор", "resource": 300000, "minutes_remaining": 298920, "last_online": "1970-01-01T00:00:00Z" }, { "id": "94efe634-9eb9-4d99-8e89-42f62440ae75", "company_id": "4b6e3329-6acf-484c-a7f9-76a60faee9e1", "name": "Кабинет #1", "resource": 300000, "minutes_remaining": 300000, "last_online": "1970-01-01T00:00:00Z" }, { "id": "8bde5cc6-fae9-4534-8498-e01c99751561", "company_id": "4b6e3329-6acf-484c-a7f9-76a60faee9e1", "name": "Кабинет #2", "resource": 300000, "minutes_remaining": 300000, "last_online": "1970-01-01T00:00:00Z" } ]
-
204
— устройства не найдены; -
400
— неверный формат запроса; -
401
— пользователь не авторизован; -
403
— компания не принадлежит текущему пользователю; -
404
— компания не найдена; -
500
— внутренняя ошибка сервера.
Хендлер: POST /api/device
Пользователь должен быть авторизован. Создаем новое устройство, название должно быть уникальным в пределах компании.
POST /api/company HTTP/1.1
Content-Type: application/json
...
{
"company_id": "4b6e3329-6acf-484c-a7f9-76a60faee9e1",
"name": "Коридор",
"resource": 300000
}
Возможные коды ответа:
-
201
— успешная обработка запроса:201 OK HTTP/1.1 Content-Type: application/json ... { "id": "0b2a97f0-88c4-46cc-bc2c-4cef7ac4bc28", "company_id": "4b6e3329-6acf-484c-a7f9-76a60faee9e1", "name": "Коридор", "resource": 300000, "minutes_remaining": 300000, "last_online": "1970-01-01T00:00:00Z", "token": "346399537cd131b510506a5e8f30777e" }
-
400
— неверный формат запроса; -
401
— пользователь не авторизован; -
403
— компания не найдена или не принадлежит пользователю; -
409
— устройство с таким названием уже существует в компании; -
500
— внутренняя ошибка сервера.
Хендлер: GET /api/device/{id}
Пользователь должен быть авторизован. Получаем информацию по ID устройства.
GET /api/device/{id} HTTP/1.1
Content-Length: 0
Возможные коды ответа:
-
200
— успешная обработка запроса:200 OK HTTP/1.1 Content-Type: application/json ... { "id": "0b2a97f0-88c4-46cc-bc2c-4cef7ac4bc28", "company_id": "4b6e3329-6acf-484c-a7f9-76a60faee9e1", "name": "Коридор", "resource": 300000, "minutes_remaining": 298920, "last_online": "1970-01-01T00:00:00Z" }
-
400
— неверный формат запроса; -
401
— пользователь не авторизован; -
403
— устройство не принадлежит текущему пользователю; -
404
— устройство не найдено; -
500
— внутренняя ошибка сервера.
Хендлер: GET /api/device/{id}/schedule
Пользователь должен быть авторизован. Получаем расписание по ID устройства.
GET /api/device/{id}/schedule HTTP/1.1
Content-Length: 0
Возможные коды ответа:
-
200
— успешная обработка запроса:200 OK HTTP/1.1 Content-Type: application/json ... [ { "id": "a3f224cd-93bf-475b-bf6d-6d75ee820363", "device_id": "0b2a97f0-88c4-46cc-bc2c-4cef7ac4bc28", "week_day": 0, "time_start": 600, "time_stop": 1200 }, { "id": "5d912ec4-1685-49e4-a2fd-eb9dd1e8b0f1", "device_id": "0b2a97f0-88c4-46cc-bc2c-4cef7ac4bc28", "week_day": 1, "time_start": 600, "time_stop": 1200 }, { "id": "b49e92ec-ec19-47e5-ab99-a5865d6d0324", "device_id": "0b2a97f0-88c4-46cc-bc2c-4cef7ac4bc28", "week_day": 2, "time_start": 600, "time_stop": 1200 } ]
-
400
— неверный формат запроса; -
401
— пользователь не авторизован; -
403
— устройство не принадлежит текущему пользователю; -
404
— устройство не найдено; -
500
— внутренняя ошибка сервера.
Хендлер: POST /api/device/{id}/schedule
Пользователь должен быть авторизован. Добавляем записи о расписании для устройства.
POST /api/device/{id}/schedule HTTP/1.1
Content-Type: application/json
...
[
{
"week_day": 0,
"time_start": 600,
"time_stop": 1200
},
{
"week_day": 1,
"time_start": 600,
"time_stop": 1200
},
{
"week_day": 2,
"time_start": 600,
"time_stop": 1200
}
]
Возможные коды ответа:
-
201
— успешная обработка запроса:201 OK HTTP/1.1 Content-Type: application/json ... [ { "id": "a3f224cd-93bf-475b-bf6d-6d75ee820363", "device_id": "0b2a97f0-88c4-46cc-bc2c-4cef7ac4bc28", "week_day": 0, "time_start": 600, "time_stop": 1200 }, { "id": "5d912ec4-1685-49e4-a2fd-eb9dd1e8b0f1", "device_id": "0b2a97f0-88c4-46cc-bc2c-4cef7ac4bc28", "week_day": 1, "time_start": 600, "time_stop": 1200 }, { "id": "b49e92ec-ec19-47e5-ab99-a5865d6d0324", "device_id": "0b2a97f0-88c4-46cc-bc2c-4cef7ac4bc28", "week_day": 2, "time_start": 600, "time_stop": 1200 } ]
-
400
— неверный формат запроса; -
401
— пользователь не авторизован; -
403
— компания не принадлежит пользователю; -
404
— компания или устройство не найдено; -
500
— внутренняя ошибка сервера.
Хендлер: DELETE /api/device/{id}/schedule/{schedule_id}
Пользователь должен быть авторизован. Удаляем записать о расписании у устройства.
DELETE /api/device/{id}/schedule/{schedule_id} HTTP/1.1
Content-Length: 0
Возможные коды ответа:
200
— успешная обработка запроса;400
— неверный формат запроса;401
— пользователь не авторизован;403
— компания не принадлежит пользователю;404
— компания или устройство не найдено;500
— внутренняя ошибка сервера.
Хендлер: GET /api/device/{id}/journal
Пользователь должен быть авторизован. Получаем журнал по ID устройства.
GET /api/device/{id}/journal HTTP/1.1
Content-Length: 0
Возможные коды ответа:
-
200
— успешная обработка запроса:200 OK HTTP/1.1 Content-Type: application/json ... [ { "id": "587af98a-c368-416c-b273-958c9eb849b7", "device_id": "0b2a97f0-88c4-46cc-bc2c-4cef7ac4bc28", "time_start": "2023-02-22T00:00:00Z", "time_stop": "2023-02-22T09:00:00Z", "done": true }, { "id": "ac02bbda-4129-4ccb-b2ab-84618c1af0ad", "device_id": "0b2a97f0-88c4-46cc-bc2c-4cef7ac4bc28", "time_start": "2023-02-23T00:00:00Z", "time_stop": "2023-02-23T09:00:00Z", "done": true } ]
-
400
— неверный формат запроса; -
401
— пользователь не авторизован; -
403
— устройство не принадлежит текущему пользователю; -
404
— устройство не найдено; -
500
— внутренняя ошибка сервера.
Хендлер: GET /api/device/info
Устройство должно быть авторизовано через Basic Auth. Получаем информацию об устройстве (проверяем авторизацию).
GET /api/device/info HTTP/1.1
Content-Length: 0
Возможные коды ответа:
-
200
— успешная обработка запроса:200 OK HTTP/1.1 Content-Type: application/json ... { "id": "0b2a97f0-88c4-46cc-bc2c-4cef7ac4bc28", "company_id": "4b6e3329-6acf-484c-a7f9-76a60faee9e1", "name": "Коридор", "resource": 300000, "minutes_remaining": 298904, "last_online": "2023-02-25T18:16:09.390549Z" }
-
401
— устройство не авторизовано; -
500
— внутренняя ошибка сервера.
Хендлер: GET /api/device/schedule
Устройство должно быть авторизовано через Basic Auth. Получаем расписание устройства.
GET /api/device/schedule HTTP/1.1
Content-Length: 0
Возможные коды ответа:
-
200
— успешная обработка запроса:200 OK HTTP/1.1 Content-Type: application/json Sha256-Hash: <sha256-hash> ... [ { "id": "a3f224cd-93bf-475b-bf6d-6d75ee820363", "device_id": "0b2a97f0-88c4-46cc-bc2c-4cef7ac4bc28", "week_day": 0, "time_start": 600, "time_stop": 1200 }, { "id": "5d912ec4-1685-49e4-a2fd-eb9dd1e8b0f1", "device_id": "0b2a97f0-88c4-46cc-bc2c-4cef7ac4bc28", "week_day": 1, "time_start": 600, "time_stop": 1200 }, { "id": "b49e92ec-ec19-47e5-ab99-a5865d6d0324", "device_id": "0b2a97f0-88c4-46cc-bc2c-4cef7ac4bc28", "week_day": 2, "time_start": 600, "time_stop": 1200 } ]
-
204
— для устройства нет расписания; -
401
— устройство не авторизовано; -
500
— внутренняя ошибка сервера.
Хендлер: POST /api/device/state
Устройство должно быть авторизовано через Basic Auth. Передаем состояния устройства.
POST /api/device/state HTTP/1.1
Content-Type: application/json
...
[
{
"time": "2023-02-10T01:29:00Z",
"mode": "auto",
"enabled": 1
},
{
"time": "2023-02-10T01:29:30Z",
"mode": "auto",
"enabled": 1
},
{
"time": "2023-02-10T01:30:00Z",
"mode": "auto",
"enabled": 0
}
]
Возможные коды ответа:
201
— успешная обработка запроса;400
— неверный формат запроса;401
— устройство не авторизовано;500
— внутренняя ошибка сервера.
Хендлер: GET /api/device/firmware
Не проверяем авторизацию устройства. Если совпадает хотя бы один из хешей, то считаем прошивку актуальной и отдаем Not Modified. Иначе возвращаем свежий бинарник, в том числе для пустого запроса.
GET /api/device/firmware HTTP/1.1
Content-Length: 0
x-ESP32-sketch-md5: <md5-hash>
x-ESP32-sketch-sha256: <sha256-hash>
Возможные коды ответа:
200
— успешная обработка запроса, отдаем прошивку;304
— прошивка на устройстве актуальная;500
— внутренняя ошибка сервера.