- база своя: два пула, захват одним UPDATE ... RETURNING, шаги схемы на goose под файловым замком, одна миграция начальной схемы вместо семи прежних - транспорт переписан на net/http: свои слои, свой ограничитель частоты, отдача файла с проверкой владельца; панель /_/ и пространство /api/ исчезли - по находкам ревью: журнал не пишет путь под корнем приложения, ключ бюджета читается справа налево, узнавание известного идёт читающим пулом
9.6 KiB
MODIFIED Requirements
Requirement: Приём записи по HTTP
Сервис SHALL принимать запись запросом POST /app/audiorecords с телом
multipart/form-data и полем audio только от узнанного отправителя.
Запрос от неузнанного MUST получать код 401, и по нему MUST не заводиться ни файл,
ни аудиозапись. Принятая запись от узнанного отправителя MUST быть сохранена и
получить заведённую под неё аудиозапись на рубеже uploaded.
Приём стоит тем же адресом, что и список записей, и отличается от него только методом: он заводит аудиозапись, а не кладёт файл.
Ответ MUST нести список заведённых записей и место под признак повторного файла у каждой, даже когда файл в запросе один. Форма согласована один раз и вперёд: приём, отдающий одну запись, пришлось бы переписывать вместе с приёмом нескольких файлов и с распознаванием повтора по содержимому, а экран загрузки — переделывать под вторую форму. Число файлов в запросе при этом остаётся прежним: меняется форма ответа, не число файлов.
Элемент списка MUST нести те же поля, что и карточка записи, плюс признак
повторного файла полем duplicate: две формы одной вещи разошлись бы молча.
Состав карточки нормирует capability archive.
Прежние имена полей ответа — job_id и status — MUST не употребляться:
идентификатор записи зовётся id.
Значение рубежа в ответе MUST принадлежать перечню рубежей конвейера и MUST не
перечисляться этой нормой порознь: рубеж объявлен одним дескриптором, и
перечисленный здесь второй раз он разошёлся бы с ним молча. Рубеж называет
достигнутое, а не предстоящее, и created в перечне отсутствует вовсе.
Запись сверх потолка размера MUST отвергаться до заведения файла и аудиозаписи,
и код с телом такого отказа нормирует capability archive наравне с прочими
ветвями.
Отказ неузнанному наступает раньше чтения тела: запись, за которую не заплатит узнанный отправитель, не должна попасть даже в память.
Приём не судит о годности записи сам: расширение он берёт из имени файла, а пригодность содержимого узнаёт у источника метаданных.
Куда именно ложится принятая запись, приёму не принадлежит: раскладку каталога
данных нормирует capability storage.
Владельцем принятой записи приём SHALL назначать узнанного предъявителя.
Обязательность владельца при этом MUST держаться и схемой: колонка владельца
пустого значения не принимает вовсе, и норму эту держит capability storage.
Проверка в приёме от этого не лишняя — она отвечает отправителю понятным отказом
до того, как запись попадёт в память, а схема отвечала бы отказом сохранения
после укладки файла.
Отдельной ветви «узнан, а учётной записи нет» у приёма больше нет: узнавание заводит учётную запись само, а предъявителя с собственным токеном хранилища не существует — токенов сервис не выдаёт и не принимает. Ветвь ушла вместе со своим единственным случаем.
Отказ после укладки записи потребовал бы убрать уже сохранённый файл, а уборки файлов сервис не умеет вовсе: норма, обязывающая к недостижимому, не пишется.
Scenario: Запись принята
- GIVEN источник метаданных читает запись и отдаёт её длительность
- AND отправитель узнан
- WHEN программа шлёт
POST /app/audiorecordsс полемaudio - THEN ответ имеет код
201, а в теле лежит список из одного элемента - AND элемент несёт непустой
id, полеstateсо значениемuploadedи место под признак повторного файла - AND содержимое записи целиком лежит в каталоге данных одним файлом
- AND владельцем заведённой аудиозаписи стоит узнанный предъявитель
Scenario: Пришедший не узнан
- WHEN программа шлёт
POST /app/audiorecordsс полемaudioнеузнанной - THEN ответ имеет код
401 - AND ни файла, ни аудиозаписи не заводится
- AND тело ответа не несёт данных записи
Scenario: Поля с записью нет
- GIVEN отправитель узнан
- WHEN программа шлёт
POST /app/audiorecordsбез поляaudio - THEN ответ имеет код
400и сообщение об отсутствии записи - AND ни файла, ни аудиозаписи не заводится
Scenario: Размеру записи приём не судья
- GIVEN источник метаданных читает запись и отдаёт её длительность
- AND отправитель узнан
- WHEN программа шлёт запись нулевой длины
- THEN ответ имеет код
201: собственного порога по размеру у приёма нет
Requirement: Имя файла в хранилище
Сервис SHALL сохранять принятую запись под собственным именем — идентификатором, к которому приписано расширение из имени файла отправителя. Имя, данное отправителем, MUST не попадать ни в имя файла на диске, ни в путь к нему: оно приходит извне и содержимым своим приёму не подконтрольно.
Норма сужена: имя отправителя доходит теперь до самой аудиозаписи собственной колонкой — по нему человек узнаёт свою запись, — но не до имени файла и не до журнала. Что с ним делает приём, нормирует требование «Имя файла отправителя подписывает запись».
Расширения в присланном имени нет — сервис MUST подставить .audio, чтобы у
файла на диске расширение было всегда.
Требование пережило смену раскладки: имя задаёт сервис, а не умолчание чужой библиотеки, строившее его из имени отправителя. Умолчания этого больше нет, и правило перестало быть отменой чужого поведения — оно стало прямым описанием своего.
Scenario: Расширение взято из имени отправителя
- WHEN программа шлёт запись с именем
test.mp3 - THEN имя файла на диске оканчивается на
.mp3
Scenario: Имени без расширения назначено своё
- WHEN программа шлёт запись с именем
testбез расширения - THEN имя файла на диске оканчивается на
.audio
Scenario: Имя отправителя в имя файла не попало
- WHEN программа шлёт запись с именем
секретное-слово.mp3 - THEN имя файла на диске не содержит
секретное-слово - AND путь к этому файлу не содержит его тоже