ultisuite-backend/internal/api/contacts/validate.go
R3D347HR4Y f232aaf960 Enhance Contacts API with new features and improvements
- Updated the Contacts API to support contact synchronization with incremental updates using sync tokens.
- Added functionality for merging duplicate contacts on the server side.
- Introduced new endpoints for enriching contact interactions, including mail, meetings, and files.
- Implemented ETag support for contact updates to ensure data integrity.
- Enhanced validation for sync tokens and interaction queries.
- Updated project checklist to reflect the completion of Contacts API enhancements.
2026-05-22 20:50:46 +02:00

100 lines
2.6 KiB
Go

package contacts
import (
"net/mail"
"strconv"
"strings"
"github.com/ultisuite/ulti-backend/internal/api/apivalidate"
"github.com/ultisuite/ulti-backend/internal/nextcloud"
)
const (
maxRequestBody = 64 << 10
maxSyncTokenLen = 8192
)
func validateSyncToken(raw string) (string, *apivalidate.ValidationError) {
raw = strings.TrimSpace(raw)
if raw == "" {
return "", nil
}
if len(raw) > maxSyncTokenLen {
return "", apivalidate.NewValidationError(apivalidate.FieldDetail{
Field: "sync_token", Message: "too long",
})
}
if strings.ContainsAny(raw, "\r\n\x00") || strings.ContainsAny(raw, "<>&") {
return "", apivalidate.NewValidationError(apivalidate.FieldDetail{
Field: "sync_token", Message: "invalid",
})
}
return raw, nil
}
func validateCreateContact(contact *nextcloud.Contact) *apivalidate.ValidationError {
if strings.TrimSpace(contact.FullName) == "" {
return apivalidate.NewValidationError(apivalidate.FieldDetail{
Field: "full_name", Message: "required",
})
}
return nil
}
func validateIfMatch(ifMatch string) *apivalidate.ValidationError {
if strings.TrimSpace(ifMatch) == "" {
return apivalidate.NewValidationError(apivalidate.FieldDetail{
Field: "If-Match", Message: "required",
})
}
if strings.ContainsAny(ifMatch, "\r\n") {
return apivalidate.NewValidationError(apivalidate.FieldDetail{
Field: "If-Match", Message: "invalid",
})
}
return nil
}
func validateDeletePath(path string) *apivalidate.ValidationError {
if strings.TrimSpace(path) == "" {
return apivalidate.NewValidationError(apivalidate.FieldDetail{
Field: "path", Message: "required",
})
}
return nil
}
func validateInteractionQuery(emailRaw, limitRaw string) (string, int, *apivalidate.ValidationError) {
email := strings.TrimSpace(emailRaw)
if email == "" {
return "", 0, apivalidate.NewValidationError(apivalidate.FieldDetail{
Field: "email", Message: "required",
})
}
if len(email) > 320 || strings.ContainsAny(email, "\r\n") {
return "", 0, apivalidate.NewValidationError(apivalidate.FieldDetail{
Field: "email", Message: "invalid",
})
}
parsed, err := mail.ParseAddress(email)
if err != nil || parsed.Address == "" {
return "", 0, apivalidate.NewValidationError(apivalidate.FieldDetail{
Field: "email", Message: "invalid",
})
}
limit := 20
raw := strings.TrimSpace(limitRaw)
if raw != "" {
parsedLimit, err := strconv.Atoi(raw)
if err != nil || parsedLimit < 1 || parsedLimit > 100 {
return "", 0, apivalidate.NewValidationError(apivalidate.FieldDetail{
Field: "limit", Message: "must be between 1 and 100",
})
}
limit = parsedLimit
}
return parsed.Address, limit, nil
}