package httpapi import ( "context" "crypto/rand" "encoding/base64" "fmt" "strings" "unicode" "github.com/google/uuid" ) // slugify follows the spirit of backend/src/lib/slugify.ts: lowercase, // ASCII-fold common Spanish characters, dashes for everything else. func slugify(s string) string { replacer := strings.NewReplacer( "á", "a", "é", "e", "í", "i", "ó", "o", "ú", "u", "ü", "u", "ñ", "n", "Á", "a", "É", "e", "Í", "i", "Ó", "o", "Ú", "u", "Ü", "u", "Ñ", "n") s = replacer.Replace(strings.ToLower(strings.TrimSpace(s))) var b strings.Builder prevDash := true // avoids a leading dash for _, r := range s { switch { case unicode.IsLetter(r) && r < 128, unicode.IsDigit(r): b.WriteRune(r) prevDash = false default: if !prevDash { b.WriteByte('-') prevDash = true } } } out := strings.Trim(b.String(), "-") if len(out) > 140 { out = strings.Trim(out[:140], "-") } if out == "" { out = "gallery" } return out } // uniqueSlug appends -2, -3, ... until the slug is free. func (s *Server) uniqueSlug(ctx context.Context, title string) (string, error) { base := slugify(title) slug := base for i := 2; ; i++ { exists, err := s.db.SlugExists(ctx, slug) if err != nil { return "", err } if !exists { return slug, nil } slug = fmt.Sprintf("%s-%d", base, i) } } func newID() string { return uuid.NewString() } // newShareToken returns 32 random bytes, base64url (43 chars, no padding). func newShareToken() string { buf := make([]byte, 32) if _, err := rand.Read(buf); err != nil { panic(err) // crypto/rand failure is unrecoverable } return base64.RawURLEncoding.EncodeToString(buf) }