Migrate to PostgreSQL and fix related issues

This commit is contained in:
William Bouzourène 2025-05-12 21:10:40 +02:00
parent 0b8fbea6c3
commit a89a9776c3
Signed by: bouzoure
SSH key fingerprint: SHA256:19MbXpLua4rUtk8tunMesD8KUKb91LXLHg8E/qTooww
17 changed files with 176 additions and 174 deletions

14
compose.yml Normal file
View file

@ -0,0 +1,14 @@
services:
postgres:
image: postgres:latest
container_name: camarades-postgres
ports:
- "127.0.0.1:5432:5432"
environment:
POSTGRES_USER: camarades
POSTGRES_PASSWORD: camarades
POSTGRES_DB: camarades
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:

View file

@ -69,7 +69,7 @@ func Contacts(c *fiber.Ctx) error {
params.PersonType = "contacts" params.PersonType = "contacts"
var sections []models.Section var sections []models.Section
db.Order("name collate nocase asc").Find(&sections, "contains_contacts = ? AND id IN ?", true, allowedSections) db.Order("name asc").Find(&sections, "contains_contacts = ? AND id IN ?", true, allowedSections)
params.AllowedSections = allowedSections params.AllowedSections = allowedSections
// Security for active contacts // Security for active contacts
@ -156,7 +156,7 @@ func ContactsExport(c *fiber.Ctx) error {
params.PersonType = "contacts" params.PersonType = "contacts"
var sections []models.Section var sections []models.Section
db.Order("name collate nocase asc").Find(&sections, "contains_members = ? AND id IN ?", true, allowedSections) db.Order("name asc").Find(&sections, "contains_members = ? AND id IN ?", true, allowedSections)
params.AllowedSections = allowedSections params.AllowedSections = allowedSections
// Security for active contacts // Security for active contacts
@ -280,7 +280,7 @@ func ContactsExport(c *fiber.Ctx) error {
} else if field.FieldType == "list" { } else if field.FieldType == "list" {
if field.List.Multi { if field.List != nil && field.List.Multi {
if countMulti == 0 { if countMulti == 0 {
c.Writef("\"") c.Writef("\"")
@ -323,7 +323,7 @@ func ContactShow(c *fiber.Ctx) error {
var person models.Person var person models.Person
result := db.Unscoped().Preload("Section").Find( result := db.Unscoped().Preload("Section").Find(
&person, "id = ? AND is_contact", id, true, &person, "id = ? AND is_contact = ?", id, true,
) )
if result.Error != nil { if result.Error != nil {
@ -416,7 +416,7 @@ func ContactAdd(c *fiber.Ctx) error {
} }
var sections []models.Section var sections []models.Section
db.Order("name collate nocase asc").Find( db.Order("name asc").Find(
&sections, &sections,
"contains_contacts = ?", "contains_contacts = ?",
true, true,
@ -511,7 +511,7 @@ func ContactAdd(c *fiber.Ctx) error {
} }
for _, field := range fields { for _, field := range fields {
if field.List.Multi { if field.List != nil && field.List.Multi {
for _, listItem := range field.List.ListItems { for _, listItem := range field.List.ListItems {
key := fmt.Sprintf("field-%d-%d", field.ID, listItem.ID) key := fmt.Sprintf("field-%d-%d", field.ID, listItem.ID)
value := c.FormValue(key) value := c.FormValue(key)
@ -520,7 +520,7 @@ func ContactAdd(c *fiber.Ctx) error {
var fieldValue models.FieldValue var fieldValue models.FieldValue
fieldValue.FieldID = field.ID fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID fieldValue.PersonID = person.ID
fieldValue.ListItemID = listItem.ID fieldValue.ListItemID = &listItem.ID
db.Create(&fieldValue) db.Create(&fieldValue)
} }
} }
@ -594,7 +594,8 @@ func ContactAdd(c *fiber.Ctx) error {
var fieldValue models.FieldValue var fieldValue models.FieldValue
fieldValue.FieldID = field.ID fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID fieldValue.PersonID = person.ID
fieldValue.ListItemID = uint(valueInt) valueUint := uint(valueInt)
fieldValue.ListItemID = &valueUint
db.Create(&fieldValue) db.Create(&fieldValue)
} }
} }
@ -659,7 +660,7 @@ func ContactEdit(c *fiber.Ctx) error {
} }
var sections []models.Section var sections []models.Section
db.Order("name collate nocase asc").Find( db.Order("name asc").Find(
&sections, &sections,
"contains_contacts = ?", "contains_contacts = ?",
true, true,
@ -760,7 +761,7 @@ func ContactEdit(c *fiber.Ctx) error {
field.ID, field.ID,
) )
if field.List.Multi { if field.List != nil && field.List.Multi {
for _, listItem := range field.List.ListItems { for _, listItem := range field.List.ListItems {
key := fmt.Sprintf("field-%d-%d", field.ID, listItem.ID) key := fmt.Sprintf("field-%d-%d", field.ID, listItem.ID)
value := c.FormValue(key) value := c.FormValue(key)
@ -769,7 +770,7 @@ func ContactEdit(c *fiber.Ctx) error {
var fieldValue models.FieldValue var fieldValue models.FieldValue
fieldValue.FieldID = field.ID fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID fieldValue.PersonID = person.ID
fieldValue.ListItemID = listItem.ID fieldValue.ListItemID = &listItem.ID
db.Create(&fieldValue) db.Create(&fieldValue)
} }
} }
@ -843,7 +844,8 @@ func ContactEdit(c *fiber.Ctx) error {
var fieldValue models.FieldValue var fieldValue models.FieldValue
fieldValue.FieldID = field.ID fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID fieldValue.PersonID = person.ID
fieldValue.ListItemID = uint(valueInt) valueUint := uint(valueInt)
fieldValue.ListItemID = &valueUint
db.Create(&fieldValue) db.Create(&fieldValue)
} }
} }

View file

@ -110,7 +110,7 @@ func DebugFixFieldsOrder(c *fiber.Ctx) error {
var memberFields []models.Field var memberFields []models.Field
result := db.Order( result := db.Order(
"name collate nocase asc", "name asc",
).Find( ).Find(
&memberFields, "person_type = ?", "member", &memberFields, "person_type = ?", "member",
) )
@ -130,7 +130,7 @@ func DebugFixFieldsOrder(c *fiber.Ctx) error {
var contactFields []models.Field var contactFields []models.Field
result2 := db.Order( result2 := db.Order(
"name collate nocase asc", "name asc",
).Find( ).Find(
&contactFields, "person_type = ?", "contact", &contactFields, "person_type = ?", "contact",
) )

View file

@ -74,7 +74,7 @@ func FieldAdd(c *fiber.Ctx) error {
} }
var lists []models.List var lists []models.List
db.Order("name collate nocase asc").Find(&lists) db.Order("name asc").Find(&lists)
if c.Method() == "POST" { if c.Method() == "POST" {
field.Name = c.FormValue("name") field.Name = c.FormValue("name")
@ -107,7 +107,8 @@ func FieldAdd(c *fiber.Ctx) error {
if err != nil || listID < 1 { if err != nil || listID < 1 {
errors = append(errors, "Liste incorrecte") errors = append(errors, "Liste incorrecte")
} else { } else {
field.ListID = uint(listID) listID2 := uint(listID)
field.ListID = &listID2
} }
} }

View file

@ -17,7 +17,7 @@ func Lists(c *fiber.Ctx) error {
} }
var lists []models.List var lists []models.List
result := db.Order("name collate nocase asc").Find(&lists) result := db.Order("name asc").Find(&lists)
if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
return err return err
@ -54,7 +54,7 @@ func ListShow(c *fiber.Ctx) error {
) )
var listItems []models.ListItem var listItems []models.ListItem
db.Order("value collate nocase asc").Find(&listItems, "list_id = ?", id) db.Order("value asc").Find(&listItems, "list_id = ?", id)
return c.Render("list", fiber.Map{ return c.Render("list", fiber.Map{
"PageTitle": title, "PageTitle": title,

View file

@ -84,7 +84,7 @@ func Members(c *fiber.Ctx) error {
params.PersonType = "members" params.PersonType = "members"
var sections []models.Section var sections []models.Section
db.Order("name collate nocase asc").Find(&sections, "contains_members = ? AND id IN ?", true, allowedSections) db.Order("name asc").Find(&sections, "contains_members = ? AND id IN ?", true, allowedSections)
params.AllowedSections = allowedSections params.AllowedSections = allowedSections
// Security for active contacts // Security for active contacts
@ -171,7 +171,7 @@ func MembersExport(c *fiber.Ctx) error {
params.PersonType = "members" params.PersonType = "members"
var sections []models.Section var sections []models.Section
db.Order("name collate nocase asc").Find(&sections, "contains_members = ? AND id IN ?", true, allowedSections) db.Order("name asc").Find(&sections, "contains_members = ? AND id IN ?", true, allowedSections)
params.AllowedSections = allowedSections params.AllowedSections = allowedSections
// Security for active contacts // Security for active contacts
@ -338,7 +338,7 @@ func MemberShow(c *fiber.Ctx) error {
var person models.Person var person models.Person
result := db.Unscoped().Preload("Section").Find( result := db.Unscoped().Preload("Section").Find(
&person, "id = ? AND is_member", id, true, &person, "id = ? AND is_member = ?", id, true,
) )
if result.Error != nil { if result.Error != nil {
@ -431,7 +431,7 @@ func MemberAdd(c *fiber.Ctx) error {
} }
var sections []models.Section var sections []models.Section
db.Order("name collate nocase asc").Find( db.Order("name asc").Find(
&sections, &sections,
"contains_members = ?", "contains_members = ?",
true, true,
@ -526,7 +526,7 @@ func MemberAdd(c *fiber.Ctx) error {
} }
for _, field := range fields { for _, field := range fields {
if field.List.Multi { if field.List != nil && field.List.Multi {
for _, listItem := range field.List.ListItems { for _, listItem := range field.List.ListItems {
key := fmt.Sprintf("field-%d-%d", field.ID, listItem.ID) key := fmt.Sprintf("field-%d-%d", field.ID, listItem.ID)
value := c.FormValue(key) value := c.FormValue(key)
@ -535,7 +535,7 @@ func MemberAdd(c *fiber.Ctx) error {
var fieldValue models.FieldValue var fieldValue models.FieldValue
fieldValue.FieldID = field.ID fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID fieldValue.PersonID = person.ID
fieldValue.ListItemID = listItem.ID fieldValue.ListItemID = &listItem.ID
db.Create(&fieldValue) db.Create(&fieldValue)
} }
} }
@ -609,7 +609,8 @@ func MemberAdd(c *fiber.Ctx) error {
var fieldValue models.FieldValue var fieldValue models.FieldValue
fieldValue.FieldID = field.ID fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID fieldValue.PersonID = person.ID
fieldValue.ListItemID = uint(valueInt) valueUint := uint(valueInt)
fieldValue.ListItemID = &valueUint
db.Create(&fieldValue) db.Create(&fieldValue)
} }
} }
@ -674,7 +675,7 @@ func MemberEdit(c *fiber.Ctx) error {
} }
var sections []models.Section var sections []models.Section
db.Order("name collate nocase asc").Find( db.Order("name asc").Find(
&sections, &sections,
"contains_members = ?", "contains_members = ?",
true, true,
@ -775,7 +776,7 @@ func MemberEdit(c *fiber.Ctx) error {
field.ID, field.ID,
) )
if field.List.Multi { if field.List != nil && field.List.Multi {
for _, listItem := range field.List.ListItems { for _, listItem := range field.List.ListItems {
key := fmt.Sprintf("field-%d-%d", field.ID, listItem.ID) key := fmt.Sprintf("field-%d-%d", field.ID, listItem.ID)
value := c.FormValue(key) value := c.FormValue(key)
@ -784,7 +785,7 @@ func MemberEdit(c *fiber.Ctx) error {
var fieldValue models.FieldValue var fieldValue models.FieldValue
fieldValue.FieldID = field.ID fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID fieldValue.PersonID = person.ID
fieldValue.ListItemID = listItem.ID fieldValue.ListItemID = &listItem.ID
db.Create(&fieldValue) db.Create(&fieldValue)
} }
} }
@ -858,7 +859,8 @@ func MemberEdit(c *fiber.Ctx) error {
var fieldValue models.FieldValue var fieldValue models.FieldValue
fieldValue.FieldID = field.ID fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID fieldValue.PersonID = person.ID
fieldValue.ListItemID = uint(valueInt) valueUint := uint(valueInt)
fieldValue.ListItemID = &valueUint
db.Create(&fieldValue) db.Create(&fieldValue)
} }
} }

View file

@ -17,7 +17,7 @@ func Roles(c *fiber.Ctx) error {
} }
var roles []models.Role var roles []models.Role
result := db.Order("name collate nocase asc").Find(&roles) result := db.Order("name asc").Find(&roles)
if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
return err return err

View file

@ -18,7 +18,7 @@ func Sections(c *fiber.Ctx) error {
} }
var sections []models.Section var sections []models.Section
result := db.Order("name collate nocase asc").Preload("ParentSection").Find(&sections) result := db.Order("name asc").Preload("ParentSection").Find(&sections)
if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
return err return err
@ -70,8 +70,8 @@ func SectionAdd(c *fiber.Ctx) error {
} }
var sections []models.Section var sections []models.Section
db.Order("name collate nocase asc").Find( db.Order("name asc").Find(
&sections, "id <> ? AND parent_section_id = 0", section.ID, &sections, "id <> ? AND parent_section_id IS NULL", section.ID,
) )
if c.Method() == "POST" { if c.Method() == "POST" {
@ -106,12 +106,12 @@ func SectionAdd(c *fiber.Ctx) error {
section.Name = name section.Name = name
section.ShortName = shortName section.ShortName = shortName
section.ParentSectionID = 0
parentSectionID, err := strconv.ParseUint(parentSection, 10, 0) parentSectionID, err := strconv.ParseUint(parentSection, 10, 0)
if err == nil { if err == nil {
for _, parentSection := range sections { for _, parentSection := range sections {
if parentSection.ID == uint(parentSectionID) { parentSectionID2 := uint(parentSectionID)
section.ParentSectionID = uint(parentSectionID) if parentSection.ID == parentSectionID2 {
section.ParentSectionID = &parentSectionID2
break break
} }
} }
@ -177,8 +177,8 @@ func SectionEdit(c *fiber.Ctx) error {
) )
var sections []models.Section var sections []models.Section
db.Order("name collate nocase asc").Find( db.Order("name asc").Find(
&sections, "id <> ? AND parent_section_id = 0", section.ID, &sections, "id <> ? AND parent_section_id IS NULL", section.ID,
) )
var errors []string var errors []string
@ -200,12 +200,12 @@ func SectionEdit(c *fiber.Ctx) error {
section.Name = name section.Name = name
section.ShortName = shortName section.ShortName = shortName
section.ParentSectionID = 0
parentSectionID, err := strconv.ParseUint(parentSection, 10, 0) parentSectionID, err := strconv.ParseUint(parentSection, 10, 0)
if err == nil { if err == nil {
for _, parentSection := range sections { for _, parentSection := range sections {
if parentSection.ID == uint(parentSectionID) { parentSectionID2 := uint(parentSectionID)
section.ParentSectionID = uint(parentSectionID) if parentSection.ID == parentSectionID2 {
section.ParentSectionID = &parentSectionID2
break break
} }
} }

View file

@ -25,7 +25,7 @@ func Users(c *fiber.Ctx) error {
} }
var users []models.User var users []models.User
result := db.Order("name collate nocase asc").Find(&users) result := db.Order("name asc").Find(&users)
if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
return err return err
@ -62,7 +62,7 @@ func UserShow(c *fiber.Ctx) error {
) )
var userRoles []models.UserRole var userRoles []models.UserRole
db.Joins("Role").Joins("Section").Order("Section__name collate nocase asc").Find( db.Joins("Role").Joins("Section").Order("\"Section\".\"name\" asc").Find(
&userRoles, "user_id = ?", id, &userRoles, "user_id = ?", id,
) )
@ -284,10 +284,10 @@ func UserPermissions(c *fiber.Ctx) error {
) )
var roles []models.Role var roles []models.Role
db.Order("name collate nocase asc").Find(&roles) db.Order("name asc").Find(&roles)
var sections []models.Section var sections []models.Section
db.Order("name collate nocase asc").Find(&sections) db.Order("name asc").Find(&sections)
var errors []string var errors []string
if c.Method() == "POST" { if c.Method() == "POST" {

14
go.mod
View file

@ -6,7 +6,6 @@ require (
github.com/brianvoe/gofakeit/v7 v7.2.1 github.com/brianvoe/gofakeit/v7 v7.2.1
github.com/charmbracelet/log v0.4.1 github.com/charmbracelet/log v0.4.1
github.com/flosch/pongo2/v6 v6.0.0 github.com/flosch/pongo2/v6 v6.0.0
github.com/glebarez/sqlite v1.11.0
github.com/go-playground/validator/v10 v10.26.0 github.com/go-playground/validator/v10 v10.26.0
github.com/gofiber/fiber/v2 v2.52.6 github.com/gofiber/fiber/v2 v2.52.6
github.com/gofiber/helmet/v2 v2.2.26 github.com/gofiber/helmet/v2 v2.2.26
@ -17,6 +16,7 @@ require (
github.com/pquerna/otp v1.4.0 github.com/pquerna/otp v1.4.0
github.com/yuin/goldmark v1.7.11 github.com/yuin/goldmark v1.7.11
golang.org/x/crypto v0.38.0 golang.org/x/crypto v0.38.0
gorm.io/driver/postgres v1.5.11
gorm.io/gorm v1.26.1 gorm.io/gorm v1.26.1
) )
@ -36,7 +36,6 @@ require (
github.com/dgraph-io/ristretto v0.2.0 // indirect github.com/dgraph-io/ristretto v0.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
@ -49,6 +48,10 @@ require (
github.com/golang/snappy v1.0.0 // indirect github.com/golang/snappy v1.0.0 // indirect
github.com/golobby/cast v1.3.3 // indirect github.com/golobby/cast v1.3.3 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.4 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
@ -58,10 +61,8 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/muesli/termenv v0.16.0 // indirect github.com/muesli/termenv v0.16.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.62.0 // indirect github.com/valyala/fasthttp v1.62.0 // indirect
@ -69,11 +70,8 @@ require (
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
golang.org/x/net v0.40.0 // indirect golang.org/x/net v0.40.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.33.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect golang.org/x/text v0.25.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.6 // indirect
modernc.org/libc v1.65.4 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.10.0 // indirect
modernc.org/sqlite v1.37.0 // indirect
) )

49
go.sum
View file

@ -63,10 +63,6 @@ github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vt
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@ -130,13 +126,19 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg=
github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
@ -169,8 +171,6 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -181,8 +181,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@ -204,6 +202,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
@ -242,8 +241,6 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -286,8 +283,6 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -323,31 +318,9 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw= gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw=
gorm.io/gorm v1.26.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= gorm.io/gorm v1.26.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8=
modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.65.4 h1:8oVL/29p3e+ZvMv4nE1pryq5p8grHiFsU8bN8Eah/rs=
modernc.org/libc v1.65.4/go.mod h1:MOiGAM9lrMBT9L8xT1nO41qYl5eg9gCp9/kWhz5L7WA=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4=
modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

View file

@ -16,6 +16,7 @@ type Config struct {
} }
Database struct { Database struct {
Location string `env:"DATABASE_LOCATION"` Location string `env:"DATABASE_LOCATION"`
DSN string `env:"DATABASE_DSN"`
} }
Sessions struct { Sessions struct {
Location string `env:"SESSIONS_LOCATION"` Location string `env:"SESSIONS_LOCATION"`

View file

@ -2,7 +2,7 @@ package helpers
import ( import (
"git.readonly.ch/bouzoure/pop-camarades/models" "git.readonly.ch/bouzoure/pop-camarades/models"
"github.com/glebarez/sqlite" "gorm.io/driver/postgres"
"gorm.io/gorm" "gorm.io/gorm"
gormLogger "gorm.io/gorm/logger" gormLogger "gorm.io/gorm/logger"
) )
@ -30,7 +30,7 @@ func GetDatabase() (*gorm.DB, error) {
} }
database, err = gorm.Open( database, err = gorm.Open(
sqlite.Open(config.Database.Location), postgres.Open(config.Database.DSN),
&gormConfig, &gormConfig,
) )
if err != nil { if err != nil {

View file

@ -44,7 +44,7 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
} }
// SQL qeury for results // SQL qeury for results
sqlQuery := `--sql sqlQuery := `
SELECT people.*, SELECT people.*,
sections.name AS Section__name sections.name AS Section__name
FROM people FROM people
@ -52,7 +52,7 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
ON people.section_id = sections.id` ON people.section_id = sections.id`
// SQL query to count results // SQL query to count results
sqlQueryCount := `--sql sqlQueryCount := `
SELECT people.id SELECT people.id
FROM people FROM people
` `
@ -62,7 +62,7 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
var sqlFilters string var sqlFilters string
// Filter: member or contact // Filter: member or contact
sqlFilters = `--sql sqlFilters = `
WHERE is_member = @is_member WHERE is_member = @is_member
AND is_contact = @is_contact AND is_contact = @is_contact
` `
@ -78,11 +78,11 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
// Filter: name -> first_name + last_name // Filter: name -> first_name + last_name
if len(params.Name) > 0 { if len(params.Name) > 0 {
nameFilter := `--sql nameFilter := `
people.first_name LIKE @name LOWER(people.first_name) LIKE LOWER(@name)
OR people.last_name LIKE @name OR LOWER(people.last_name) LIKE LOWER(@name)
OR CONCAT(people.first_name, ' ', people.last_name) LIKE @name OR LOWER(CONCAT(people.first_name, ' ', people.last_name)) LIKE LOWER(@name)
OR CONCAT(people.last_name, ' ', people.first_name) LIKE @name OR LOWER(CONCAT(people.last_name, ' ', people.first_name)) LIKE LOWER(@name)
` `
sqlParams = append(sqlParams, sql.Named( sqlParams = append(sqlParams, sql.Named(
"name", "name",
@ -92,10 +92,10 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
if strings.Contains(params.Name, " ") { if strings.Contains(params.Name, " ") {
names := strings.Split(params.Name, " ") names := strings.Split(params.Name, " ")
for index, name := range names { for index, name := range names {
nameFilter = fmt.Sprintf(`--sql nameFilter = fmt.Sprintf(`
%s %s
OR people.first_name LIKE @name_%d OR LOWER(people.first_name) LIKE LOWER(@name_%d)
OR people.last_name LIKE @name_%d OR LOWER(people.last_name) LIKE LOWER(@name_%d)
`, nameFilter, index, index) `, nameFilter, index, index)
sqlParams = append(sqlParams, sql.Named( sqlParams = append(sqlParams, sql.Named(
@ -105,7 +105,7 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
} }
} }
sqlFilters = fmt.Sprintf(`--sql sqlFilters = fmt.Sprintf(`
%s %s
AND (%s) AND (%s)
`, sqlFilters, nameFilter) `, sqlFilters, nameFilter)
@ -113,7 +113,7 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
// Filter: section // Filter: section
if params.Section > 0 { if params.Section > 0 {
sqlFilters = fmt.Sprintf(`--sql sqlFilters = fmt.Sprintf(`
%s %s
AND people.section_id = @section AND people.section_id = @section
`, sqlFilters) `, sqlFilters)
@ -122,7 +122,7 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
// Filter: active (only apply if archived is false) // Filter: active (only apply if archived is false)
if params.Active && !params.Archive { if params.Active && !params.Archive {
sqlFilters = fmt.Sprintf(`--sql sqlFilters = fmt.Sprintf(`
%s %s
AND people.deleted_at IS NULL AND people.deleted_at IS NULL
`, sqlFilters) `, sqlFilters)
@ -130,7 +130,7 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
// Filter: archived (only apply if active is false) // Filter: archived (only apply if active is false)
if params.Archive && !params.Active { if params.Archive && !params.Active {
sqlFilters = fmt.Sprintf(`--sql sqlFilters = fmt.Sprintf(`
%s %s
AND people.deleted_at IS NOT NULL AND people.deleted_at IS NOT NULL
`, sqlFilters) `, sqlFilters)
@ -138,7 +138,7 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
// Filter: if both active and archived are turned off, return nothing // Filter: if both active and archived are turned off, return nothing
if !params.Archive && !params.Active { if !params.Archive && !params.Active {
sqlFilters = fmt.Sprintf(`--sql sqlFilters = fmt.Sprintf(`
%s %s
AND 0=1 AND 0=1
`, sqlFilters) `, sqlFilters)
@ -146,9 +146,9 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
// Filters: email // Filters: email
if len(params.Email) > 0 { if len(params.Email) > 0 {
sqlFilters = fmt.Sprintf(`--sql sqlFilters = fmt.Sprintf(`
%s %s
AND people.email LIKE @email AND LOWER(people.email) LIKE LOWER(@email)
`, sqlFilters) `, sqlFilters)
sqlParams = append(sqlParams, sql.Named( sqlParams = append(sqlParams, sql.Named(
"email", "email",
@ -158,35 +158,29 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
// Filters: phone // Filters: phone
if len(params.Phone) > 0 { if len(params.Phone) > 0 {
params.Phone = strings.ReplaceAll(params.Phone, " ", "") var phoneWithoutZeroFilter string
params.Phone = strings.ReplaceAll(params.Phone, "-", "") if string(params.Phone[0]) == "0" {
params.Phone = strings.ReplaceAll(params.Phone, ".", "") phoneWithoutZeroFilter = `
params.Phone = strings.ReplaceAll(params.Phone, "/", "") OR
params.Phone = strings.ReplaceAll(params.Phone, "+", "") TRANSLATE(people.phone, ' ,-,.,/,+', '') LIKE TRANSLATE(@phone2, ' ,-,.,/,+', '') OR
TRANSLATE(people.mobile, ' ,-,.,/,+', '') LIKE TRANSLATE(@phone2, ' ,-,.,/,+', '')
`
sqlFilters = fmt.Sprintf(`--sql phone2 := params.Phone[1:]
sqlParams = append(sqlParams, sql.Named(
"phone2",
fmt.Sprintf("%%%s%%", phone2),
))
}
sqlFilters = fmt.Sprintf(`
%s %s
AND ( AND (
REPLACE( TRANSLATE(people.phone, ' ,-,.,/,+', '') LIKE TRANSLATE(@phone, ' ,-,.,/,+', '') OR
REPLACE( TRANSLATE(people.mobile, ' ,-,.,/,+', '') LIKE TRANSLATE(@phone, ' ,-,.,/,+', '')
REPLACE( %s
REPLACE(
REPLACE(people.phone, ' ', '')
, '-', '')
, '.', '')
, '/', '')
, '+', '') LIKE @phone
OR REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(people.mobile, ' ', '')
, '-', '')
, '.', '')
, '/', '')
, '+', '') LIKE @phone
) )
`, sqlFilters) `, sqlFilters, phoneWithoutZeroFilter)
sqlParams = append(sqlParams, sql.Named( sqlParams = append(sqlParams, sql.Named(
"phone", "phone",
fmt.Sprintf("%%%s%%", params.Phone), fmt.Sprintf("%%%s%%", params.Phone),
@ -195,7 +189,7 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
// Filters: address // Filters: address
if len(params.Address) > 0 { if len(params.Address) > 0 {
sqlFilters = fmt.Sprintf(`--sql sqlFilters = fmt.Sprintf(`
%s %s
AND ( AND (
people.address1 LIKE @address people.address1 LIKE @address
@ -210,7 +204,7 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
// Filters: postal code // Filters: postal code
if len(params.PostalCode) > 0 { if len(params.PostalCode) > 0 {
sqlFilters = fmt.Sprintf(`--sql sqlFilters = fmt.Sprintf(`
%s %s
AND people.postal_code = @postal_code AND people.postal_code = @postal_code
`, sqlFilters) `, sqlFilters)
@ -222,7 +216,7 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
// Filters: city // Filters: city
if len(params.City) > 0 { if len(params.City) > 0 {
sqlFilters = fmt.Sprintf(`--sql sqlFilters = fmt.Sprintf(`
%s %s
AND people.city LIKE @city AND people.city LIKE @city
`, sqlFilters) `, sqlFilters)
@ -233,7 +227,7 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
} }
// Security filter: only show results in allowed secitons // Security filter: only show results in allowed secitons
sqlFilters = fmt.Sprintf(`--sql sqlFilters = fmt.Sprintf(`
%s %s
AND people.section_id IN @allowed_sections AND people.section_id IN @allowed_sections
`, sqlFilters) `, sqlFilters)
@ -250,7 +244,7 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
continue continue
} }
sqlFieldJoins = fmt.Sprintf(`--sql sqlFieldJoins = fmt.Sprintf(`
%s %s
LEFT JOIN field_values LEFT JOIN field_values
AS field_%d AS field_%d
@ -271,36 +265,53 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
} }
switch v := value.(type) { switch v := value.(type) {
default:
fmt.Println(v)
continue
case string: case string:
filter = fmt.Sprintf(`--sql if field.FieldType == "date" {
%s filter = fmt.Sprintf(`
field_%d.value_string LIKE @field_%d_%d %s
OR field_%d.value_date LIKE @field_%d_%d TO_CHAR(field_%d.value_date, 'YYYY-MM-DD') = @field_%d_%d
`, filter, field.ID, field.ID, index, field.ID, field.ID, index) `, filter, field.ID, field.ID, index)
sqlParams = append(sqlParams, sql.Named( sqlParams = append(sqlParams, sql.Named(
fmt.Sprintf("field_%d_%d", field.ID, index), fmt.Sprintf("field_%d_%d", field.ID, index),
fmt.Sprintf("%%%s%%", v), v,
)) ))
} else {
filter = fmt.Sprintf(`
%s
LOWER(field_%d.value_string) LIKE LOWER(@field_%d_%d)
`, filter, field.ID, field.ID, index)
sqlParams = append(sqlParams, sql.Named(
fmt.Sprintf("field_%d_%d", field.ID, index),
fmt.Sprintf("%%%s%%", v),
))
}
case float64: case float64:
filter = fmt.Sprintf(`--sql if field.FieldType == "list" {
%s filter = fmt.Sprintf(`
field_%d.value_int = @field_%d_%d %s
OR field_%d.list_item_id = @field_%d_%d field_%d.list_item_id = @field_%d_%d
`, filter, field.ID, field.ID, index, field.ID, field.ID, index) `, filter, field.ID, field.ID, index)
sqlParams = append(sqlParams, sql.Named( sqlParams = append(sqlParams, sql.Named(
fmt.Sprintf("field_%d_%d", field.ID, index), fmt.Sprintf("field_%d_%d", field.ID, index),
v, v,
)) ))
} else {
filter = fmt.Sprintf(`
%s
field_%d.value_int = @field_%d_%d
`, filter, field.ID, field.ID, index)
sqlParams = append(sqlParams, sql.Named(
fmt.Sprintf("field_%d_%d", field.ID, index),
v,
))
}
} }
fieldFilter = fmt.Sprintf("%s %s", fieldFilter, filter) fieldFilter = fmt.Sprintf("%s %s", fieldFilter, filter)
} }
if fieldFilter != "" { if fieldFilter != "" {
sqlFilters = fmt.Sprintf(`--sql sqlFilters = fmt.Sprintf(`
%s %s
AND (%s) AND (%s)
`, sqlFilters, fieldFilter) `, sqlFilters, fieldFilter)
@ -308,7 +319,7 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
} }
// Build and run count query // Build and run count query
sqlQueryCount = fmt.Sprintf(`--sql sqlQueryCount = fmt.Sprintf(`
%s %s
%s %s
%s %s
@ -330,7 +341,7 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
var sqlPagination string var sqlPagination string
if params.PageSize > 0 { if params.PageSize > 0 {
sqlPagination = `--sql sqlPagination = `
LIMIT @pagination_limit LIMIT @pagination_limit
OFFSET @pagination_offset OFFSET @pagination_offset
` `
@ -339,12 +350,12 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
} }
// Build and run paginated result query // Build and run paginated result query
sqlQuery = fmt.Sprintf(`--sql sqlQuery = fmt.Sprintf(`
%s %s
%s %s
%s %s
GROUP BY people.id GROUP BY people.id, Section__name
ORDER BY CONCAT(people.last_name, people.first_name) COLLATE NOCASE ASC ORDER BY CONCAT(people.last_name, people.first_name) ASC
%s %s
`, sqlQuery, sqlFieldJoins, sqlFilters, sqlPagination) `, sqlQuery, sqlFieldJoins, sqlFilters, sqlPagination)

View file

@ -16,7 +16,7 @@ func FirstAccountCheck() (bool, error) {
} }
var user models.User var user models.User
result := db.First(&user, "is_admin = 1") result := db.First(&user, "is_admin = ?", true)
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return false, nil return false, nil

View file

@ -11,8 +11,8 @@ type Field struct {
Name string Name string
PersonType string PersonType string
FieldType string FieldType string
ListID uint ListID *uint
List List List *List
Position int Position int
} }
@ -25,8 +25,8 @@ type FieldValue struct {
ValueString sql.NullString ValueString sql.NullString
ValueInt sql.NullInt64 ValueInt sql.NullInt64
ValueDate sql.NullTime ValueDate sql.NullTime
ListItemID uint ListItemID *uint
ListItem ListItem ListItem *ListItem
} }
var PersonTypes = map[string]string{ var PersonTypes = map[string]string{

View file

@ -6,7 +6,7 @@ type Section struct {
gorm.Model gorm.Model
Name string Name string
ShortName string ShortName string
ParentSectionID uint ParentSectionID *uint
ParentSection *Section ParentSection *Section
ContainsMembers bool ContainsMembers bool
ContainsContacts bool ContainsContacts bool