- Config: try ENV_FILE, .env, ../.env for loading; trim trailing slash from BaseURL - Log BASE_URL at server startup for verification - .env.example: document BASE_URL - Tasks, projects, tags, migrations and related API/handlers Made-with: Cursor
132 lines
3.0 KiB
Go
132 lines
3.0 KiB
Go
package utils
|
|
|
|
import (
|
|
"net/mail"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/calendarapi/internal/models"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
var hexColorRegex = regexp.MustCompile(`^#[0-9A-Fa-f]{6}$`)
|
|
|
|
func ValidateEmail(email string) error {
|
|
if email == "" {
|
|
return models.NewValidationError("email is required")
|
|
}
|
|
if _, err := mail.ParseAddress(email); err != nil {
|
|
return models.NewValidationError("invalid email format")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidatePassword(password string) error {
|
|
if len(password) < 10 {
|
|
return models.NewValidationError("password must be at least 10 characters")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateTimezone(tz string) error {
|
|
if tz == "" {
|
|
return nil
|
|
}
|
|
if _, err := time.LoadLocation(tz); err != nil {
|
|
return models.NewValidationError("invalid IANA timezone: " + tz)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateCalendarName(name string) error {
|
|
if len(name) < 1 || len(name) > 80 {
|
|
return models.NewValidationError("calendar name must be 1-80 characters")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateColor(color string) error {
|
|
if color == "" {
|
|
return nil
|
|
}
|
|
if !hexColorRegex.MatchString(color) {
|
|
return models.NewValidationError("color must be hex format #RRGGBB")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateEventTitle(title string) error {
|
|
if len(title) < 1 || len(title) > 140 {
|
|
return models.NewValidationError("event title must be 1-140 characters")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateTimeRange(start, end time.Time) error {
|
|
if !end.After(start) {
|
|
return models.NewValidationError("end_time must be after start_time")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateReminderMinutes(minutes int32) error {
|
|
if minutes < 0 || minutes > 10080 {
|
|
return models.NewValidationError("minutes_before must be 0-10080")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateUUID(s string) (uuid.UUID, error) {
|
|
id, err := uuid.Parse(s)
|
|
if err != nil {
|
|
return uuid.Nil, models.NewValidationError("invalid UUID: " + s)
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
func NormalizeEmail(email string) string {
|
|
return strings.ToLower(strings.TrimSpace(email))
|
|
}
|
|
|
|
func ValidateRecurrenceRangeLimit(start, end time.Time) error {
|
|
if end.Sub(start) > 366*24*time.Hour {
|
|
return models.NewValidationError("date range cannot exceed 1 year")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateTaskTitle(title string) error {
|
|
if len(title) < 1 || len(title) > 500 {
|
|
return models.NewValidationError("task title must be 1-500 characters")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateTaskStatus(status string) error {
|
|
valid := map[string]bool{"todo": true, "in_progress": true, "done": true, "archived": true}
|
|
if !valid[status] {
|
|
return models.NewValidationError("invalid task status")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ParseTime(s string) (time.Time, error) {
|
|
t, err := time.Parse(time.RFC3339, s)
|
|
if err != nil {
|
|
t, err = time.Parse("2006-01-02T15:04:05Z07:00", s)
|
|
}
|
|
if err != nil {
|
|
t, err = time.Parse("2006-01-02", s)
|
|
}
|
|
return t, err
|
|
}
|
|
|
|
func ValidateTaskPriority(priority string) error {
|
|
valid := map[string]bool{"low": true, "medium": true, "high": true, "critical": true}
|
|
if !valid[priority] {
|
|
return models.NewValidationError("invalid task priority")
|
|
}
|
|
return nil
|
|
}
|