generated from VLADIMIR/template
fix
This commit is contained in:
@@ -5,11 +5,8 @@ import "context"
|
||||
type Message struct {
|
||||
To string
|
||||
Subject string
|
||||
// Body — текстовая версия письма (text/plain).
|
||||
Body string
|
||||
// HTML — версия письма для почтовых клиентов (text/html).
|
||||
// Если пусто, письмо уходит только в text/plain.
|
||||
HTML string
|
||||
Body string
|
||||
HTML string
|
||||
}
|
||||
|
||||
type IEmailSender interface {
|
||||
|
||||
@@ -9,7 +9,7 @@ func ButtonHTML(link, label string) string {
|
||||
</td></tr></table>`
|
||||
}
|
||||
|
||||
// WrapHTML is the shared light/dark email layout used by all service emails.
|
||||
// WrapHTML возвращает общую светлую/тёмную HTML-обёртку для писем сервиса.
|
||||
func WrapHTML(content, baseURL string) string {
|
||||
fontURL := ""
|
||||
if baseURL != "" {
|
||||
|
||||
@@ -95,9 +95,9 @@ func mimeFor(f *File) string {
|
||||
return mimeFromData(f.Data, f.Name)
|
||||
}
|
||||
|
||||
// mimeFromData — Content-Type по содержимому (первые 512 байт) с fallback
|
||||
// mimeFromData — Content-Type по содержимому (первые 512 байт) с запасным вариантом
|
||||
// на расширение. text/* (например, HTML) трактуется как octet-stream и
|
||||
// уходит в fallback: при публичном скачивании не должно отдаваться как
|
||||
// уходит в запасной вариант: при публичном скачивании не должно отдаваться как
|
||||
// html с доверенного домена.
|
||||
func mimeFromData(data []byte, filename string) string {
|
||||
detected := http.DetectContentType(data)
|
||||
|
||||
@@ -2,7 +2,7 @@ package network_utils
|
||||
|
||||
import "net"
|
||||
|
||||
// GetLocalIPv4Addresses returns non-loopback IPv4 addresses of the host.
|
||||
// GetLocalIPv4Addresses возвращает IPv4-адреса хоста, кроме loopback-адресов.
|
||||
func GetLocalIPv4Addresses() ([]string, error) {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
|
||||
@@ -9,7 +9,7 @@ func ContextWithClaims(ctx context.Context, claims *JWTClaims) context.Context {
|
||||
return context.WithValue(ctx, claimsContextKey{}, claims)
|
||||
}
|
||||
|
||||
// ClaimsFromContext returns authenticated claims stored in ctx.
|
||||
// ClaimsFromContext возвращает данные аутентификации, сохранённые в ctx.
|
||||
func ClaimsFromContext(ctx context.Context) (*JWTClaims, bool) {
|
||||
claims, ok := ctx.Value(claimsContextKey{}).(*JWTClaims)
|
||||
return claims, ok && claims != nil
|
||||
|
||||
@@ -7,8 +7,6 @@ type subject struct {
|
||||
roles []string
|
||||
}
|
||||
|
||||
// NewSubject создаёт субъект с заданными ролями (например, для тестов
|
||||
// авторизации или in-memory проверок прав).
|
||||
func NewSubject(roles ...string) IHasRole {
|
||||
return &subject{roles: roles}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,8 @@ import (
|
||||
|
||||
const testDomain = "http://storage.test/api/files/"
|
||||
|
||||
// testArchive — экземпляр модуля для тестов.
|
||||
var testArchive = NewScenarioArchive()
|
||||
|
||||
// buildScenario собирает сценарий с историей и ссылками на изображения.
|
||||
func buildScenario() *repos.Scenario {
|
||||
description := "Детективная история"
|
||||
image := "cover.png"
|
||||
@@ -35,7 +33,6 @@ func buildScenario() *repos.Scenario {
|
||||
}
|
||||
}
|
||||
|
||||
// memoryFiles — замена хранилища для Pack: имя → содержимое.
|
||||
type memoryFiles map[string][]byte
|
||||
|
||||
func (m memoryFiles) get(_ context.Context, name string) ([]byte, error) {
|
||||
@@ -183,7 +180,7 @@ func TestPackExternalURLsKeptAndNotFetched(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPackLegacyFullURLImage(t *testing.T) {
|
||||
// Legacy-данные: полный URL хранилища срезается до базового имени файла.
|
||||
// Устаревшие данные: полный URL хранилища срезается до базового имени файла.
|
||||
description := "desc"
|
||||
scenario := &repos.Scenario{
|
||||
ID: 1,
|
||||
@@ -258,7 +255,6 @@ func TestPackImageNameCollision(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// writeZip собирает архив с заданными записями (для негативных тестов Unpack).
|
||||
func writeZip(t *testing.T, entries map[string][]byte) []byte {
|
||||
t.Helper()
|
||||
var buf bytes.Buffer
|
||||
@@ -278,7 +274,6 @@ func writeZip(t *testing.T, entries map[string][]byte) []byte {
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// validScenarioJSON — минимальный корректный scenario.json.
|
||||
func validScenarioJSON() []byte {
|
||||
b, _ := json.Marshal(ScenarioJSON{
|
||||
Version: Version,
|
||||
|
||||
@@ -23,7 +23,6 @@ type ScenarioJSON struct {
|
||||
Story *storytelling.Story `json:"story"`
|
||||
}
|
||||
|
||||
// Bundle — результат разбора входящего архива.
|
||||
type Bundle struct {
|
||||
Scenario *ScenarioJSON
|
||||
Files map[string][]byte
|
||||
|
||||
@@ -8,13 +8,11 @@ type IStory interface {
|
||||
type Story struct {
|
||||
Background string `json:"background,omitempty"`
|
||||
|
||||
// Введение - то, что игрок получает в начале игры
|
||||
Introduction *Introduction `json:"introduction,omitempty"`
|
||||
|
||||
// Закрывашка доступна в редакторе сценария, игрокам не передаётся.
|
||||
Conclusion *Conclusion `json:"conclusion,omitempty"`
|
||||
|
||||
// Список точек
|
||||
Places []*Place `json:"places"`
|
||||
|
||||
// Вопросы дела - список вопросов, на которые команда должна ответить
|
||||
@@ -24,11 +22,8 @@ type Story struct {
|
||||
// Вопрос дела - вопрос, который автор сценария добавляет вместе с
|
||||
// правильным ответом; команда отвечает на вопросы в конце игры
|
||||
type Question struct {
|
||||
|
||||
// Код - идентификатор вопроса, используется при отправке ответов
|
||||
Code string `json:"code"`
|
||||
|
||||
// Текст вопроса
|
||||
Text string `json:"text"`
|
||||
|
||||
// Правильный ответ - виден только автору и организаторам,
|
||||
@@ -44,11 +39,8 @@ type Conclusion struct {
|
||||
|
||||
// Введение - текст и аудио, которые игрок получает в начале игры.
|
||||
type Introduction struct {
|
||||
|
||||
// Текст введения
|
||||
Text string `json:"text"`
|
||||
|
||||
// Аудио введения
|
||||
Audio string `json:"audio,omitempty"`
|
||||
}
|
||||
|
||||
@@ -57,23 +49,17 @@ type Introduction struct {
|
||||
// Это может быть персонаж - пострадавший, продавец, полицейский...
|
||||
// Это может быть описание действия игрока - ответ на вопрос, реакция на выбитую дверь...
|
||||
type Place struct {
|
||||
|
||||
// Код - то что вбивает игрок
|
||||
Code string `json:"code"`
|
||||
|
||||
// Название места, имя персонажа, заданный игроком вопрос...
|
||||
Name string `json:"name"`
|
||||
|
||||
// Текст - получаемая текстовая информация
|
||||
Text string `json:"text"`
|
||||
|
||||
// Картинка - получаемая визуальная информация
|
||||
Image string `json:"image"`
|
||||
|
||||
// Признак скрытой карточки - некоторые места изначально не доступны
|
||||
Hidden bool `json:"hidden"`
|
||||
|
||||
// Приложения - получаемые улики
|
||||
Applications []*Application `json:"applications,omitempty"`
|
||||
|
||||
// Двери к другим точкам - действия или диалоги, открывающие другие точки
|
||||
@@ -86,11 +72,8 @@ type Place struct {
|
||||
// Приложение - улика
|
||||
// Это может быть оружие, отпечатки, фотография...
|
||||
type Application struct {
|
||||
|
||||
// Название улики
|
||||
Name string `json:"name"`
|
||||
|
||||
// Картинка
|
||||
Image string `json:"image"`
|
||||
|
||||
// Тип файла улики: image | pdf | audio.
|
||||
@@ -99,11 +82,8 @@ type Application struct {
|
||||
|
||||
// Дверь - действие или диалог
|
||||
type Door struct {
|
||||
|
||||
// Код
|
||||
Code string `json:"code"`
|
||||
|
||||
// Название - заданный вопрос, проделанное действие...
|
||||
Name string `json:"name"`
|
||||
|
||||
// Некоторые двери изначально не доступны
|
||||
@@ -114,7 +94,5 @@ type Door struct {
|
||||
|
||||
// Аналог карт допуска в "Тайны Аркхема"
|
||||
type Key struct {
|
||||
|
||||
// Название
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
@@ -67,7 +67,6 @@ func (s *story) GetStory(
|
||||
}
|
||||
}
|
||||
}
|
||||
// Собираем ключи
|
||||
if currentPlace != nil {
|
||||
for _, key := range currentPlace.Keys {
|
||||
keys[key.Name] = struct{}{}
|
||||
|
||||
@@ -48,18 +48,14 @@ var (
|
||||
// заменяет пробелы на _, удаляет все остальные символы, кроме цифр (знаки препинания и т.д.)
|
||||
func Transliterate(text string) string {
|
||||
|
||||
// Приводим к нижнему регистру и транслитерируем
|
||||
result := replacer.Replace(strings.ToLower(text))
|
||||
|
||||
// Удаляем все символы, кроме букв латиницы, подчеркивания и цифр
|
||||
reg := regexp.MustCompile(`[^a-z0-9_]`)
|
||||
result = reg.ReplaceAllString(result, "")
|
||||
|
||||
// Удаляем множественные подчеркивания (опционально)
|
||||
regMulti := regexp.MustCompile(`_+`)
|
||||
result = regMulti.ReplaceAllString(result, "_")
|
||||
|
||||
// Удаляем подчеркивания в начале и конце строки (опционально)
|
||||
result = strings.Trim(result, "_")
|
||||
|
||||
return result
|
||||
|
||||
@@ -57,7 +57,7 @@ func Generate(cards []Card) ([]byte, error) {
|
||||
}
|
||||
x := margin + float64(i%3)*width
|
||||
y := margin + float64(i%12/3)*height
|
||||
// Bitmap включает свободное поле QR; векторные квадраты сохраняют
|
||||
// Растровый режим включает свободное поле QR; векторные квадраты сохраняют
|
||||
// чёткость при печати без растрового масштабирования.
|
||||
bitmap := qr.Bitmap()
|
||||
pixel := qrSize / float64(len(bitmap))
|
||||
|
||||
Reference in New Issue
Block a user