- база своя: два пула, захват одним UPDATE ... RETURNING, шаги схемы на goose под файловым замком, одна миграция начальной схемы вместо семи прежних - транспорт переписан на net/http: свои слои, свой ограничитель частоты, отдача файла с проверкой владельца; панель /_/ и пространство /api/ исчезли - по находкам ревью: журнал не пишет путь под корнем приложения, ключ бюджета читается справа налево, узнавание известного идёт читающим пулом
103 lines
5.0 KiB
Go
103 lines
5.0 KiB
Go
package entity
|
|
|
|
import (
|
|
"net/mail"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// MaxProviderLoginLength — предел длины логина у провайдера.
|
|
//
|
|
// Значение приходит заголовком, то есть целиком задаётся тем, кто шлёт запрос, и
|
|
// без предела в колонку уехало бы столько, сколько влезет в заголовки. Число то
|
|
// же, что у имени: длиннее имени логин не бывает, а два разных предела на
|
|
// соседних колонках одной записи разошлись бы молча.
|
|
const MaxProviderLoginLength = 255
|
|
|
|
// MaxDisplayNameLength — предел длины имени, пригодного к показу.
|
|
const MaxDisplayNameLength = 255
|
|
|
|
// AcceptProviderLogin приводит пришедшее значение к годному логину либо
|
|
// отвергает его.
|
|
//
|
|
// Отвергается пустое, состоящее из одних пробельных знаков, длиннее предела и
|
|
// несущее управляющие знаки. Пустое значение — не крайний случай: обратный
|
|
// прокси штатно шлёт заголовок пустым там, где никого не назвал, и без этой
|
|
// проверки все неназванные собрались бы в одну учётную запись с общим архивом.
|
|
//
|
|
// Обрамляющие пробелы срезаются: заголовок с ведущим пробелом и без него
|
|
// называет одного человека, а две записи о нём разошлись бы молча. Сравнение при
|
|
// поиске остаётся точным, знак в знак: приведение регистра завело бы правило,
|
|
// которого у провайдера нет.
|
|
//
|
|
// Живёт в домене, а не в транспорте и не в хранилище: правило одно на все
|
|
// способы представиться, а уложенное куском в слой оно разошлось бы двумя
|
|
// копиями при первом же втором способе.
|
|
func AcceptProviderLogin(value string) (string, bool) {
|
|
login := strings.TrimSpace(value)
|
|
|
|
// Предел считается в **знаках**, а не в байтах: колонка считает знаки, и два
|
|
// предела в разных единицах разошлись бы вдвое на любой кириллице.
|
|
if login == "" || utf8.RuneCountInString(login) > MaxProviderLoginLength {
|
|
return "", false
|
|
}
|
|
|
|
for _, r := range login {
|
|
if unicode.IsControl(r) {
|
|
return "", false
|
|
}
|
|
}
|
|
|
|
return login, true
|
|
}
|
|
|
|
// AcceptDisplayName приводит имя к годному для колонки значению.
|
|
//
|
|
// Обрезается по пределу и чистится от управляющих знаков — тем же приёмом, каким
|
|
// приём записи чистит имя файла отправителя. Пустое значение законно: имени у
|
|
// человека может не быть вовсе, и негодное значение необязательного поля не
|
|
// вправе отменять заведение учётной записи.
|
|
func AcceptDisplayName(value string) string {
|
|
name := strings.TrimSpace(stripControls(value))
|
|
|
|
runes := []rune(name)
|
|
if len(runes) > MaxDisplayNameLength {
|
|
return string(runes[:MaxDisplayNameLength])
|
|
}
|
|
|
|
return name
|
|
}
|
|
|
|
// AcceptEmail отдаёт адрес почты, если он вообще похож на адрес.
|
|
//
|
|
// Негодный отбрасывается здесь, а не отказом сохранения: иначе опечатка в
|
|
// заголовке кончалась бы либо отказом сервиса, либо — что хуже — ветвью «почта
|
|
// занята», и владелец искал бы общий ящик там, где сломан контур.
|
|
func AcceptEmail(value string) (string, bool) {
|
|
email := strings.TrimSpace(value)
|
|
if email == "" {
|
|
return "", false
|
|
}
|
|
|
|
// Разбор адреса принимает и форму «Имя <адрес>»: такую строку заголовок
|
|
// приносить не должен, и адресом она не считается.
|
|
parsed, err := mail.ParseAddress(email)
|
|
if err != nil || parsed.Name != "" || parsed.Address != email {
|
|
return "", false
|
|
}
|
|
|
|
return email, true
|
|
}
|
|
|
|
// stripControls убирает управляющие знаки: они приезжают заголовком и в колонке
|
|
// им делать нечего.
|
|
func stripControls(value string) string {
|
|
return strings.Map(func(r rune) rune {
|
|
if unicode.IsControl(r) {
|
|
return -1
|
|
}
|
|
return r
|
|
}, value)
|
|
}
|