158 lines
5.1 KiB
Go
158 lines
5.1 KiB
Go
package admin
|
|
|
|
import (
|
|
"net/mail"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/ultisuite/ulti-backend/internal/api/apivalidate"
|
|
"github.com/ultisuite/ulti-backend/internal/permission"
|
|
)
|
|
|
|
const maxQuotaRequestBody = 4 << 10
|
|
|
|
type setQuotaRequest struct {
|
|
MailMaxStorageBytes *int64 `json:"mail_max_storage_bytes"`
|
|
DriveMaxStorageBytes *int64 `json:"drive_max_storage_bytes"`
|
|
PhotosMaxStorageBytes *int64 `json:"photos_max_storage_bytes"`
|
|
}
|
|
|
|
func validateSetQuota(req *setQuotaRequest) *apivalidate.ValidationError {
|
|
if req.MailMaxStorageBytes == nil && req.DriveMaxStorageBytes == nil && req.PhotosMaxStorageBytes == nil {
|
|
return apivalidate.NewValidationError(apivalidate.FieldDetail{Field: "quota", Message: "at least one quota field is required"})
|
|
}
|
|
details := make([]apivalidate.FieldDetail, 0, 3)
|
|
if req.MailMaxStorageBytes != nil && *req.MailMaxStorageBytes < 0 {
|
|
details = append(details, apivalidate.FieldDetail{Field: "mail_max_storage_bytes", Message: "must be non-negative"})
|
|
}
|
|
if req.DriveMaxStorageBytes != nil && *req.DriveMaxStorageBytes < 0 {
|
|
details = append(details, apivalidate.FieldDetail{Field: "drive_max_storage_bytes", Message: "must be non-negative"})
|
|
}
|
|
if req.PhotosMaxStorageBytes != nil && *req.PhotosMaxStorageBytes < 0 {
|
|
details = append(details, apivalidate.FieldDetail{Field: "photos_max_storage_bytes", Message: "must be non-negative"})
|
|
}
|
|
if len(details) > 0 {
|
|
return apivalidate.NewValidationError(details...)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type createUserRequest struct {
|
|
ExternalID string `json:"external_id"`
|
|
Email string `json:"email"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
func validateCreateUser(req *createUserRequest) *apivalidate.ValidationError {
|
|
details := make([]apivalidate.FieldDetail, 0, 2)
|
|
if strings.TrimSpace(req.ExternalID) == "" {
|
|
details = append(details, apivalidate.FieldDetail{Field: "external_id", Message: "required"})
|
|
}
|
|
if _, err := mail.ParseAddress(strings.TrimSpace(req.Email)); err != nil {
|
|
details = append(details, apivalidate.FieldDetail{Field: "email", Message: "must be valid"})
|
|
}
|
|
if len(details) > 0 {
|
|
return apivalidate.NewValidationError(details...)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type inviteUserRequest struct {
|
|
Email string `json:"email"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
func validateInviteUser(req *inviteUserRequest) *apivalidate.ValidationError {
|
|
if _, err := mail.ParseAddress(strings.TrimSpace(req.Email)); err != nil {
|
|
return apivalidate.NewValidationError(apivalidate.FieldDetail{Field: "email", Message: "must be valid"})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type updateUserRequest struct {
|
|
Email *string `json:"email"`
|
|
Name *string `json:"name"`
|
|
}
|
|
|
|
func validateUpdateUser(req *updateUserRequest) *apivalidate.ValidationError {
|
|
if req.Email == nil && req.Name == nil {
|
|
return apivalidate.NewValidationError(apivalidate.FieldDetail{Field: "user", Message: "at least one field is required"})
|
|
}
|
|
if req.Email != nil {
|
|
if _, err := mail.ParseAddress(strings.TrimSpace(*req.Email)); err != nil {
|
|
return apivalidate.NewValidationError(apivalidate.FieldDetail{Field: "email", Message: "must be valid"})
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateExportFormat(raw string) (string, *apivalidate.ValidationError) {
|
|
format := strings.TrimSpace(raw)
|
|
if format == "" {
|
|
return "ndjson", nil
|
|
}
|
|
if !slices.Contains([]string{"ndjson", "csv"}, format) {
|
|
return "", apivalidate.NewValidationError(apivalidate.FieldDetail{Field: "format", Message: "must be one of: ndjson,csv"})
|
|
}
|
|
return format, nil
|
|
}
|
|
|
|
func validateExportLimit(raw string) (int, *apivalidate.ValidationError) {
|
|
val := strings.TrimSpace(raw)
|
|
if val == "" {
|
|
return 5000, nil
|
|
}
|
|
limit, err := strconv.Atoi(val)
|
|
if err != nil || limit < 1 || limit > 10000 {
|
|
return 0, apivalidate.NewValidationError(apivalidate.FieldDetail{Field: "limit", Message: "must be between 1 and 10000"})
|
|
}
|
|
return limit, nil
|
|
}
|
|
|
|
func validateStatus(raw string) (string, *apivalidate.ValidationError) {
|
|
status := strings.ToLower(strings.TrimSpace(raw))
|
|
if status == "" {
|
|
return "", nil
|
|
}
|
|
if !slices.Contains([]string{"active", "disabled", "invited"}, status) {
|
|
return "", apivalidate.NewValidationError(apivalidate.FieldDetail{Field: "status", Message: "must be one of: active,disabled,invited"})
|
|
}
|
|
return status, nil
|
|
}
|
|
|
|
func validateUserID(userID string) *apivalidate.ValidationError {
|
|
if strings.TrimSpace(userID) == "" {
|
|
return apivalidate.NewValidationError(apivalidate.FieldDetail{Field: "userID", Message: "required"})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type setUserRoleRequest struct {
|
|
Role string `json:"role"`
|
|
}
|
|
|
|
func validateSetUserRole(req *setUserRoleRequest) *apivalidate.ValidationError {
|
|
if _, ok := permission.ParseAccountRole(req.Role); !ok {
|
|
return apivalidate.NewValidationError(apivalidate.FieldDetail{
|
|
Field: "role",
|
|
Message: "must be one of: admin,user,guest,suspended",
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateAccountRoleFilter(raw string) (string, *apivalidate.ValidationError) {
|
|
role := strings.ToLower(strings.TrimSpace(raw))
|
|
if role == "" {
|
|
return "", nil
|
|
}
|
|
if _, ok := permission.ParseAccountRole(role); !ok {
|
|
return "", apivalidate.NewValidationError(apivalidate.FieldDetail{
|
|
Field: "role",
|
|
Message: "must be one of: admin,user,guest,suspended",
|
|
})
|
|
}
|
|
return role, nil
|
|
}
|