ultisuite-backend/internal/api/mail/validate_folders_labels.go
R3D347HR4Y 95196f7777 Add mail attachment and draft management features
- Introduced new functionality for managing email attachments and drafts in the mail API.
- Added handlers for listing, uploading, and downloading message attachments in `internal/api/mail/handlers_attachments.go`.
- Implemented draft management endpoints for creating, updating, and deleting drafts in `internal/api/mail/handlers_drafts.go`.
- Created new service methods for handling draft and attachment operations in `internal/api/mail/drafts.go` and `internal/api/mail/storage.go`.
- Added validation and error handling for draft and attachment operations.
- Included unit tests for draft and folder functionalities in `internal/api/mail/drafts_test.go` and `internal/api/mail/folders_test.go`.
- Updated API routes to support new draft and attachment features, enhancing overall mail management capabilities.
2026-05-22 17:14:36 +02:00

193 lines
5.1 KiB
Go

package mail
import (
"strings"
"github.com/ultisuite/ulti-backend/internal/api/apivalidate"
)
const (
maxFoldersRequestBody = 32 << 10 // 32 KiB
maxLabelsRequestBody = 8 << 10 // 8 KiB
maxFolderName = 256
maxRemoteName = 512
maxLabelName = 128
maxLabelColor = 32
)
var allowedFolderTypes = map[string]struct{}{
"inbox": {},
"sent": {},
"drafts": {},
"trash": {},
"archive": {},
"spam": {},
"custom": {},
}
type createFolderRequest struct {
AccountID string `json:"account_id"`
Name string `json:"name"`
RemoteName string `json:"remote_name"`
FolderType string `json:"folder_type"`
}
type updateFolderRequest struct {
Name string `json:"name"`
RemoteName string `json:"remote_name"`
FolderType string `json:"folder_type"`
}
type createUserLabelRequest struct {
Name string `json:"name"`
Color string `json:"color"`
}
type updateUserLabelRequest struct {
Name string `json:"name"`
Color string `json:"color"`
}
func validateFolderType(field, folderType string) *apivalidate.FieldDetail {
folderType = strings.TrimSpace(strings.ToLower(folderType))
if folderType == "" {
return nil
}
if _, ok := allowedFolderTypes[folderType]; !ok {
return &apivalidate.FieldDetail{Field: field, Message: "invalid"}
}
return nil
}
func normalizeFolderType(folderType string) string {
folderType = strings.TrimSpace(strings.ToLower(folderType))
if folderType == "" {
return "custom"
}
return folderType
}
func validateFolderName(field, name string, required bool) *apivalidate.FieldDetail {
name = strings.TrimSpace(name)
if name == "" {
if required {
return &apivalidate.FieldDetail{Field: field, Message: "required"}
}
return nil
}
if len(name) > maxFolderName || containsNewline(name) {
return &apivalidate.FieldDetail{Field: field, Message: "invalid"}
}
return nil
}
func validateRemoteName(field, remoteName string, required bool) *apivalidate.FieldDetail {
remoteName = strings.TrimSpace(remoteName)
if remoteName == "" {
if required {
return &apivalidate.FieldDetail{Field: field, Message: "required"}
}
return nil
}
if len(remoteName) > maxRemoteName || containsNewline(remoteName) {
return &apivalidate.FieldDetail{Field: field, Message: "invalid"}
}
return nil
}
func validateCreateFolder(req *createFolderRequest) *apivalidate.ValidationError {
var details []apivalidate.FieldDetail
if strings.TrimSpace(req.AccountID) == "" {
details = append(details, apivalidate.FieldDetail{Field: "account_id", Message: "required"})
}
if d := validateFolderName("name", req.Name, true); d != nil {
details = append(details, *d)
}
if d := validateRemoteName("remote_name", req.RemoteName, false); d != nil {
details = append(details, *d)
}
if d := validateFolderType("folder_type", req.FolderType); d != nil {
details = append(details, *d)
}
if len(details) == 0 {
return nil
}
return apivalidate.NewValidationError(details...)
}
func validateUpdateFolder(req *updateFolderRequest) *apivalidate.ValidationError {
var details []apivalidate.FieldDetail
if d := validateFolderName("name", req.Name, true); d != nil {
details = append(details, *d)
}
if d := validateRemoteName("remote_name", req.RemoteName, true); d != nil {
details = append(details, *d)
}
if d := validateFolderType("folder_type", req.FolderType); d != nil {
details = append(details, *d)
}
if len(details) == 0 {
return nil
}
return apivalidate.NewValidationError(details...)
}
func validateLabelName(field, name string, required bool) *apivalidate.FieldDetail {
name = strings.TrimSpace(name)
if name == "" {
if required {
return &apivalidate.FieldDetail{Field: field, Message: "required"}
}
return nil
}
if len(name) > maxLabelName || containsNewline(name) {
return &apivalidate.FieldDetail{Field: field, Message: "invalid"}
}
return nil
}
func validateLabelColor(color string) *apivalidate.FieldDetail {
color = strings.TrimSpace(color)
if len(color) > maxLabelColor || containsNewline(color) {
return &apivalidate.FieldDetail{Field: "color", Message: "invalid"}
}
return nil
}
func validateCreateUserLabel(req *createUserLabelRequest) *apivalidate.ValidationError {
var details []apivalidate.FieldDetail
if d := validateLabelName("name", req.Name, true); d != nil {
details = append(details, *d)
}
if d := validateLabelColor(req.Color); d != nil {
details = append(details, *d)
}
if len(details) == 0 {
return nil
}
return apivalidate.NewValidationError(details...)
}
func validateUpdateUserLabel(req *updateUserLabelRequest) *apivalidate.ValidationError {
var details []apivalidate.FieldDetail
if d := validateLabelName("name", req.Name, true); d != nil {
details = append(details, *d)
}
if d := validateLabelColor(req.Color); d != nil {
details = append(details, *d)
}
if len(details) == 0 {
return nil
}
return apivalidate.NewValidationError(details...)
}
func validateListFoldersAccountID(accountID string) *apivalidate.ValidationError {
if strings.TrimSpace(accountID) == "" {
return apivalidate.NewValidationError(apivalidate.FieldDetail{
Field: "account_id", Message: "required",
})
}
return nil
}