Skip to content

Latest commit

 

History

History
729 lines (574 loc) · 24.2 KB

SPECIFICATION.md

File metadata and controls

729 lines (574 loc) · 24.2 KB

Техническое задание

Сервис для управления устройствами обеззараживания воздуха «Рециркуляторы»

Общие требования

Cервис представляет собой HTTP API со следующими требованиями к бизнес-логике:

  • аутентификация и авторизация пользователей;
  • создание группы устройств (компании);
  • создание рециркуляторов, которые принадлежат компаниям;
  • для рециркулятора задается расписание, которое он получает и работает по нему;
  • периодически рециркулятор передает свои состояния, сохраняем в базу данных и потом из них собираем журнал;
  • у рециркулятора свой ресурс, из которого вычитаются записи журнала;
  • устройство получает и обновляет прошивку с сервера;
  • если закончилась лампа или рециркулятор выключился, то будет отправлено уведомление в телеграм.

Абстрактная схема взаимодействия с системой

Ниже представлена абстрактная бизнес-логика взаимодействия пользователя с сервисом:

  1. Пользователь регистрируется в сервисе «Рециркуляторы».
  2. Пользователь создает группу устройств и добавляет туда устройства.
  3. Физические устройства подключаются к сервису, обновляют прошивку и получают расписание.
  4. Рециркуляторы каждые несколько секунд передают свое состояние.
  5. Ведется учет оставшегося ресурса у рециркулятора.
  6. Воркер, который периодически удаляет слишком короткие записи в журнале.
  7. Воркер, который будет писать в телеграм, когда включается/выключается рециркулятор или закончился ресурс.

Сводное 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 — внутренняя ошибка сервера.