150 lines
4.6 KiB
Go
150 lines
4.6 KiB
Go
package models
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
RoleReader = "Reader"
|
|
RoleAdmin = "Admin"
|
|
)
|
|
|
|
var AbuseCategories = []string{
|
|
"physical",
|
|
"psychological",
|
|
"sexual",
|
|
"economic",
|
|
"material",
|
|
"digital",
|
|
"stalking",
|
|
"threats",
|
|
"honor_related",
|
|
}
|
|
|
|
var Genders = []string{"female", "male", "non_binary", "unknown"}
|
|
var Statuses = []string{"new", "open", "referred", "closed"}
|
|
|
|
// FaroeLocations is the fixed set of accepted location values for this POC.
|
|
var FaroeLocations = []string{
|
|
"Akrar", "Argir", "Ánirnar", "Árnafjørður", "Bøur", "Dalur", "Depil", "Eiði",
|
|
"Elduvík", "Fámjin", "Froðba", "Fuglafjørður", "Funningsfjørður", "Funningur",
|
|
"Gásadalur", "Gjógv", "Glyvrar", "Gøtugjógv", "Haldórsvík", "Haraldssund",
|
|
"Hattarvík", "Hellurnar", "Hestur", "Hov", "Hoyvík", "Hósvík", "Húsar",
|
|
"Húsavík", "Hvalba", "Hvalvík", "Hvannasund", "Hvítanes", "Innan Glyvur",
|
|
"Kaldbak", "Kaldbaksbotnur", "Kirkja", "Kirkjubøur", "Klaksvík", "Kolbeinagjógv",
|
|
"Kollafjørður", "Koltur", "Kunoy", "Kvívík", "Lamba", "Lambareiði", "Langasandur",
|
|
"Leirvík", "Leynar", "Ljósá", "Lopra", "Miðvágur", "Mikladalur", "Mjørkadalur",
|
|
"Morskranes", "Múli", "Mykines", "Nes", "Nesvík", "Nólsoy", "Norðdepil",
|
|
"Norðoyri", "Norðradalur", "Norðragøta", "Norðskáli", "Norðtoftir", "Oyndarfjørður",
|
|
"Oyrarbakki", "Oyrareingir", "Oyri", "Porkeri", "Rituvík", "Runavík", "Saksun",
|
|
"Saltangará", "Saltnes", "Sandavágur", "Sandur", "Sandvík", "Selatrað", "Signabøur",
|
|
"Skarvanes", "Skála", "Skálafjørður/Skálabotnur", "Skálafjørður (Eysturkommuna)",
|
|
"Skálavík", "Skipanes", "Skopun", "Skúgvoy", "Skælingur", "Stóra Dímun",
|
|
"Strendur", "Streymnes", "Stykkið", "Sumba", "Sund", "Svínáir", "Svínoy",
|
|
"Syðradalur (Kalsoy)", "Syðradalur (Streymoy)", "Syðrugøta", "Søldarfjørður",
|
|
"Sørvágur", "Tjørnuvík", "Toftir", "Tórshavn", "Trongisvágur", "Trøllanes",
|
|
"Tvøroyri", "Undir Gøtueiði", "Vatnsoyrar", "Vágur", "Válur", "Velbastaður",
|
|
"Vestmanna", "Viðareiði", "Víkarbyrgi", "Æðuvík", "Ørðavík/Øravík", "Øravíkarlíð",
|
|
}
|
|
|
|
type Registration struct {
|
|
ID string `json:"id"`
|
|
RegisteredAt string `json:"registered_at"`
|
|
Gender string `json:"gender"`
|
|
Location string `json:"location"`
|
|
AbuseType string `json:"abuse_type"`
|
|
Status string `json:"status"`
|
|
}
|
|
|
|
type RegistrationFilter struct {
|
|
Location string
|
|
AbuseType string
|
|
Gender string
|
|
Status string
|
|
Search string
|
|
From time.Time
|
|
To time.Time
|
|
HasFrom bool
|
|
HasTo bool
|
|
Limit int
|
|
Offset int
|
|
}
|
|
|
|
func NormalizeRegistration(input *Registration) error {
|
|
registeredAt := strings.TrimSpace(input.RegisteredAt)
|
|
if registeredAt == "" {
|
|
input.RegisteredAt = time.Now().UTC().Format(time.RFC3339)
|
|
} else {
|
|
parsed, err := ParseInputTime(registeredAt)
|
|
if err != nil {
|
|
return errors.New("registered_at must be RFC3339 or YYYY-MM-DD")
|
|
}
|
|
input.RegisteredAt = parsed.UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
gender := strings.ToLower(strings.TrimSpace(input.Gender))
|
|
if gender == "" {
|
|
gender = "unknown"
|
|
}
|
|
if !ContainsFold(Genders, gender) {
|
|
return errors.New("gender must be one of: female, male, non_binary, unknown")
|
|
}
|
|
input.Gender = gender
|
|
|
|
location, ok := CanonicalLocation(input.Location)
|
|
if !ok {
|
|
return errors.New("location must be one of the allowed Faroese towns/villages")
|
|
}
|
|
input.Location = location
|
|
|
|
abuseType := strings.ToLower(strings.TrimSpace(input.AbuseType))
|
|
if !ContainsFold(AbuseCategories, abuseType) {
|
|
return errors.New("abuse_type must be one of the allowed categories")
|
|
}
|
|
input.AbuseType = abuseType
|
|
|
|
status := strings.ToLower(strings.TrimSpace(input.Status))
|
|
if status == "" {
|
|
status = "new"
|
|
}
|
|
if !ContainsFold(Statuses, status) {
|
|
return errors.New("status must be one of: new, open, referred, closed")
|
|
}
|
|
input.Status = status
|
|
|
|
return nil
|
|
}
|
|
|
|
func ParseInputTime(value string) (time.Time, error) {
|
|
value = strings.TrimSpace(value)
|
|
if parsed, err := time.Parse(time.RFC3339, value); err == nil {
|
|
return parsed, nil
|
|
}
|
|
if parsed, err := time.Parse("2006-01-02", value); err == nil {
|
|
return parsed, nil
|
|
}
|
|
return time.Time{}, errors.New("invalid time")
|
|
}
|
|
|
|
func CanonicalLocation(value string) (string, bool) {
|
|
needle := strings.ToLower(strings.TrimSpace(value))
|
|
for _, location := range FaroeLocations {
|
|
if strings.ToLower(location) == needle {
|
|
return location, true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func ContainsFold(values []string, needle string) bool {
|
|
needle = strings.ToLower(strings.TrimSpace(needle))
|
|
for _, value := range values {
|
|
if strings.ToLower(strings.TrimSpace(value)) == needle {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|