package dto // Имя пира проверяется правилом `peerName`, которое несёт и набор символов, и // длину. // // Раньше здесь стояло `min=1,max=32,validateStr`, где `validateStr` требовал // 6-32 символа. Два правила на одном поле противоречили друг другу: имя из // трёх символов проходило `min=1` и отказывалось на `validateStr`, а оператор // видел «invalid» и подсказку «короткий идентификатор пира». Длина живёт // внутри одного правила, чтобы такого расхождения больше не было. type PeerPageDto struct { BaseDto Name *string `json:"name" form:"name" validate:"omitempty,max=32"` Disabled *int64 `json:"disabled" form:"disabled" validate:"omitempty,oneof=0 1"` Remark *string `json:"remark" form:"remark" validate:"omitempty,max=64"` } // Normalize: очищенный фильтр — это отсутствие фильтра. // // Регрессия, которую это закрывает: `el-input` с крестиком очистки ставит // пустую строку, axios сериализует её как `?name=`, и поиск пиров отказывал с // «invalid» после нажатия на крестик. func (d *PeerPageDto) Normalize() { d.BaseDto.Normalize() blankToNil(&d.Name) blankToNil(&d.Remark) } type PeerSaveDto struct { Name *string `json:"name" form:"name" validate:"required,peerName"` Secret *string `json:"secret" form:"secret" validate:"omitempty,min=6,max=128"` QuotaBytes *int64 `json:"quotaBytes" form:"quotaBytes" validate:"required,min=-1"` ExpiresAt *int64 `json:"expiresAt" form:"expiresAt" validate:"required,min=0"` MaxDevices *int64 `json:"maxDevices" form:"maxDevices" validate:"required,min=1"` Disabled *int64 `json:"disabled" form:"disabled" validate:"required,oneof=0 1"` Remark *string `json:"remark" form:"remark" validate:"omitempty,max=64"` } // Normalize: пустой секрет означает «сгенерируй сам». // // Именно это обещает подпись под полем, и именно это умеет CreatePeer. Пустая // пометка при этом остаётся пустой пометкой — «нет комментария» и «не менять // комментарий» не одно и то же. func (d *PeerSaveDto) Normalize() { trimValue(d.Name) blankToNil(&d.Secret) trimValue(d.Remark) } type PeerUpdateDto struct { // Id приходит из пути `/peers/:id`, а не из тела, поэтому здесь он // НЕОБЯЗАТЕЛЕН. // // Раньше сюда встраивался IdDto с правилом `required,gt=0`, и тело запроса // обязано было повторять идентификатор, уже указанный в адресе. Панель его // повторяла, поэтому расхождение не проявлялось; любой другой клиент, // сделавший PATCH /peers/7 без `"id": 7` в теле, получал отказ «поле id // обязательно» — при том, что значение из тела всё равно затирается // значением из пути. Id *int64 `json:"id" form:"id" validate:"omitempty,gt=0"` Name *string `json:"name" form:"name" validate:"omitempty,peerName"` Secret *string `json:"secret" form:"secret" validate:"omitempty,min=6,max=128"` QuotaBytes *int64 `json:"quotaBytes" form:"quotaBytes" validate:"omitempty,min=-1"` ExpiresAt *int64 `json:"expiresAt" form:"expiresAt" validate:"omitempty,min=0"` MaxDevices *int64 `json:"maxDevices" form:"maxDevices" validate:"omitempty,min=1"` Disabled *int64 `json:"disabled" form:"disabled" validate:"omitempty,oneof=0 1"` Remark *string `json:"remark" form:"remark" validate:"omitempty,max=64"` } // Normalize: при изменении пустое имя и пустой секрет означают «не менять». // // Ровно так их и читает service.UpdatePeer (`!= nil && != ""`), поэтому // приведение здесь не добавляет поведения, а убирает расхождение: без него // правила отказывали на входе, который сервис считает законным. // // `remark` и `disabled` намеренно не трогаются: пустая пометка и ноль — это // значения, которые оператор устанавливает осознанно. func (d *PeerUpdateDto) Normalize() { blankToNil(&d.Name) blankToNil(&d.Secret) trimValue(d.Remark) } type PeerKickDto struct { BannedUntil *int64 `json:"bannedUntil" form:"bannedUntil" validate:"required,min=0"` }