diff --git a/controllers/contacts.go b/controllers/contacts.go index f5af97f..c49386f 100644 --- a/controllers/contacts.go +++ b/controllers/contacts.go @@ -1,17 +1,18 @@ package controllers import ( - "errors" + "encoding/json" "fmt" "strconv" "strings" "time" "git.readonly.ch/bouzoure/pop-camarades/helpers" + "git.readonly.ch/bouzoure/pop-camarades/helpers/database" "git.readonly.ch/bouzoure/pop-camarades/models" + "github.com/charmbracelet/log" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" - "gorm.io/gorm" ) func Contacts(c *fiber.Ctx) error { @@ -46,131 +47,53 @@ func Contacts(c *fiber.Ctx) error { return err } - filterSection := c.Query("se") - filterArchive := c.Query("a") - filterSearch := c.Query("s") + searchJSON := c.Query("s") + var params database.PeopleSearchParams - sqlFilterSections := allowedSections - sqlFilterAppend := "IS NULL" - if filterArchive == "1" { - sqlFilterAppend = "IS NOT NULL" - sqlFilterSections = allowedSectionsArchived - } - - var filterSectionUint uint - if len(filterSection) > 0 { - filterSectionUint64, err := strconv.ParseUint(filterSection, 10, 0) - if err == nil { - for _, s := range sqlFilterSections { - if s == uint(filterSectionUint64) { - filterSectionUint = uint(filterSectionUint64) - } - } + if len(searchJSON) > 0 { + err = json.Unmarshal([]byte(searchJSON), ¶ms) + if err != nil { + log.Warn(err) + searchJSON = "" } } - if filterSectionUint > 0 { - sqlFilterAppend = fmt.Sprintf( - "%s AND section_id = %d", - sqlFilterAppend, - filterSectionUint, - ) - } - - var sqlFilterSearch string - if len(filterSearch) > 0 { - sqlFilterSearch = fmt.Sprintf( - "%%%s%%", filterSearch, - ) - } - - var count int64 - if len(filterSearch) > 0 { - db.Model(&models.Person{}).Where( - fmt.Sprintf( - "is_contact = ? AND section_id IN ? AND (first_name LIKE ? OR last_name LIKE ?) AND deleted_at %s", - sqlFilterAppend, - ), - true, allowedSections, sqlFilterSearch, sqlFilterSearch, - ).Count(&count) - } else { - db.Model(&models.Person{}).Where( - fmt.Sprintf( - "is_contact = ? AND section_id IN ? AND deleted_at %s", - sqlFilterAppend, - ), - true, allowedSections, - ).Count(&count) - } - - page, _ := strconv.Atoi(c.Query("p")) - pagination := helpers.Paginate(50, int(count), page) - - var people []models.Person - if len(filterSearch) > 0 { - result := db.Unscoped().Offset( - pagination.Offset, - ).Limit( - pagination.PageSize, - ).Order( - "concat(last_name, first_name) collate nocase asc", - ).Preload( - "Section", - ).Find( - &people, - fmt.Sprintf( - "is_contact = ? AND section_id IN ? AND (first_name LIKE ? OR last_name LIKE ?) AND deleted_at %s", - sqlFilterAppend, - ), - true, sqlFilterSections, sqlFilterSearch, sqlFilterSearch, - ) - - if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { - return err - } - } else { - result := db.Unscoped().Offset( - pagination.Offset, - ).Limit( - pagination.PageSize, - ).Order( - "concat(last_name, first_name) collate nocase asc", - ).Preload( - "Section", - ).Find( - &people, - fmt.Sprintf( - "is_contact = ? AND section_id IN ? AND deleted_at %s", - sqlFilterAppend, - ), - true, sqlFilterSections, - ) - - if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { - return err - } - } + params.PageNumber, _ = strconv.Atoi(c.Query("p")) + params.PageSize = 50 + params.PersonType = "contacts" var sections []models.Section - db.Order( - "name collate nocase asc", - ).Find( - §ions, - "contains_contacts = ? AND id IN ?", - true, sqlFilterSections, - ) + db.Order("name collate nocase asc").Find(§ions, "contains_contacts = ? AND id IN ?", true, allowedSections) + params.AllowedSections = allowedSections + + // Security for active contacts + if !permShow { + params.Active = false + } + + // Security for archived contacts + if !permShowArchived { + params.Archive = false + } + + results, err := database.PeopleSearch(params) + if err != nil { + return err + } + + var fields []models.Field + db.Order("position asc").Find(&fields, "person_type = ?", "contact") return c.Render("people", fiber.Map{ "PageTitle": "Contacts", "MembersPage": false, - "People": people, - "Pagination": pagination, + "People": results.Results, + "Pagination": results.Pagination, "PermShow": permShow, "PermShowArchived": permShowArchived, + "SearchJSON": searchJSON, "Sections": sections, - "FilterArchive": filterArchive, - "FilterSection": filterSectionUint, - "FilterSearch": filterSearch, + "Fields": fields, }) } @@ -206,75 +129,37 @@ func ContactsExport(c *fiber.Ctx) error { return err } - filterSection := c.Query("se") - filterArchive := c.Query("a") - filterSearch := c.Query("s") + searchJSON := c.Query("s") + var params database.PeopleSearchParams - sqlFilterSections := allowedSections - sqlFilterAppend := "IS NULL" - if filterArchive == "1" { - sqlFilterAppend = "IS NOT NULL" - sqlFilterSections = allowedSectionsArchived - } - - var filterSectionUint uint - if len(filterSection) > 0 { - filterSectionUint64, err := strconv.ParseUint(filterSection, 10, 0) - if err == nil { - for _, s := range sqlFilterSections { - if s == uint(filterSectionUint64) { - filterSectionUint = uint(filterSectionUint64) - } - } + if len(searchJSON) > 0 { + err = json.Unmarshal([]byte(searchJSON), ¶ms) + if err != nil { + log.Warn(err) + searchJSON = "" } } - if filterSectionUint > 0 { - sqlFilterAppend = fmt.Sprintf( - "%s AND section_id = %d", - sqlFilterAppend, - filterSectionUint, - ) + params.PageSize = 0 + params.PersonType = "members" + + var sections []models.Section + db.Order("name collate nocase asc").Find(§ions, "contains_members = ? AND id IN ?", true, allowedSections) + params.AllowedSections = allowedSections + + // Security for active contacts + if !permShow { + params.Active = false } - var sqlFilterSearch string - if len(filterSearch) > 0 { - sqlFilterSearch = fmt.Sprintf( - "%%%s%%", filterSearch, - ) + // Security for archived contacts + if !permShowArchived { + params.Archive = false } - var people []models.Person - if len(filterSearch) > 0 { - result := db.Unscoped().Order( - "last_name collate nocase asc, first_name collate nocase asc", - ).Preload("Section").Find( - &people, - fmt.Sprintf( - "is_contact = ? AND section_id IN ? AND (first_name LIKE ? OR last_name LIKE ?) AND deleted_at %s", - sqlFilterAppend, - ), - true, sqlFilterSections, sqlFilterSearch, sqlFilterSearch, - ) - - if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { - return err - } - } else { - result := db.Unscoped().Order( - "last_name collate nocase asc, first_name collate nocase asc", - ).Preload("Section").Find( - &people, - fmt.Sprintf( - "is_contact = ? AND section_id IN ? AND deleted_at %s", - sqlFilterAppend, - ), - true, sqlFilterSections, - ) - - if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { - return err - } + results, err := database.PeopleSearch(params) + if err != nil { + return err } var fields []models.Field @@ -324,7 +209,7 @@ func ContactsExport(c *fiber.Ctx) error { } } - for _, person := range people { + for _, person := range results.Results { c.Writef("\"%s\";", person.FirstName) c.Writef("\"%s\";", person.LastName) c.Writef("\"%s\";", person.Email) diff --git a/controllers/members.go b/controllers/members.go index f39d690..b42d472 100644 --- a/controllers/members.go +++ b/controllers/members.go @@ -2,7 +2,6 @@ package controllers import ( "encoding/json" - "errors" "fmt" "strconv" "strings" @@ -11,9 +10,9 @@ import ( "git.readonly.ch/bouzoure/pop-camarades/helpers" "git.readonly.ch/bouzoure/pop-camarades/helpers/database" "git.readonly.ch/bouzoure/pop-camarades/models" + "github.com/charmbracelet/log" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" - "gorm.io/gorm" ) type PersonValidation struct { @@ -82,6 +81,16 @@ func Members(c *fiber.Ctx) error { db.Order("name collate nocase asc").Find(§ions, "contains_members = ? AND id IN ?", true, allowedSections) params.AllowedSections = allowedSections + // Security for active contacts + if !permShow { + params.Active = false + } + + // Security for archived contacts + if !permShowArchived { + params.Archive = false + } + results, err := database.PeopleSearch(params) if err != nil { return err @@ -135,75 +144,37 @@ func MembersExport(c *fiber.Ctx) error { return err } - filterSection := c.Query("se") - filterArchive := c.Query("a") - filterSearch := c.Query("s") + searchJSON := c.Query("s") + var params database.PeopleSearchParams - sqlFilterSections := allowedSections - sqlFilterAppend := "IS NULL" - if filterArchive == "1" { - sqlFilterAppend = "IS NOT NULL" - sqlFilterSections = allowedSectionsArchived - } - - var filterSectionUint uint - if len(filterSection) > 0 { - filterSectionUint64, err := strconv.ParseUint(filterSection, 10, 0) - if err == nil { - for _, s := range sqlFilterSections { - if s == uint(filterSectionUint64) { - filterSectionUint = uint(filterSectionUint64) - } - } + if len(searchJSON) > 0 { + err = json.Unmarshal([]byte(searchJSON), ¶ms) + if err != nil { + log.Warn(err) + searchJSON = "" } } - if filterSectionUint > 0 { - sqlFilterAppend = fmt.Sprintf( - "%s AND section_id = %d", - sqlFilterAppend, - filterSectionUint, - ) + params.PageSize = 0 + params.PersonType = "members" + + var sections []models.Section + db.Order("name collate nocase asc").Find(§ions, "contains_members = ? AND id IN ?", true, allowedSections) + params.AllowedSections = allowedSections + + // Security for active contacts + if !permShow { + params.Active = false } - var sqlFilterSearch string - if len(filterSearch) > 0 { - sqlFilterSearch = fmt.Sprintf( - "%%%s%%", filterSearch, - ) + // Security for archived contacts + if !permShowArchived { + params.Archive = false } - var people []models.Person - if len(filterSearch) > 0 { - result := db.Unscoped().Order( - "last_name collate nocase asc, first_name collate nocase asc", - ).Preload("Section").Find( - &people, - fmt.Sprintf( - "is_member = ? AND section_id IN ? AND (first_name LIKE ? OR last_name LIKE ?) AND deleted_at %s", - sqlFilterAppend, - ), - true, sqlFilterSections, sqlFilterSearch, sqlFilterSearch, - ) - - if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { - return err - } - } else { - result := db.Unscoped().Order( - "last_name collate nocase asc, first_name collate nocase asc", - ).Preload("Section").Find( - &people, - fmt.Sprintf( - "is_member = ? AND section_id IN ? AND deleted_at %s", - sqlFilterAppend, - ), - true, sqlFilterSections, - ) - - if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { - return err - } + results, err := database.PeopleSearch(params) + if err != nil { + return err } var fields []models.Field @@ -253,7 +224,7 @@ func MembersExport(c *fiber.Ctx) error { } } - for _, person := range people { + for _, person := range results.Results { c.Writef("\"%s\";", person.FirstName) c.Writef("\"%s\";", person.LastName) c.Writef("\"%s\";", person.Email) diff --git a/helpers/database/people.go b/helpers/database/people.go index 779b97b..ef4d80e 100644 --- a/helpers/database/people.go +++ b/helpers/database/people.go @@ -45,15 +45,7 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) { // SQL qeury for results sqlQuery := `--sql - SELECT people.id, - people.is_member, - people.is_contact, - people.first_name, - people.last_name, - people.address1, - people.postal_code, - people.city, - people.section_id, + SELECT people.*, sections.name AS Section__name FROM people INNER JOIN sections @@ -328,6 +320,16 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) { params.PageNumber, ) + var sqlPagination string + if params.PageSize > 0 { + sqlPagination = `--sql + LIMIT @pagination_limit + OFFSET @pagination_offset + ` + sqlParams = append(sqlParams, sql.Named("pagination_limit", results.Pagination.PageSize)) + sqlParams = append(sqlParams, sql.Named("pagination_offset", results.Pagination.Offset)) + } + // Build and run paginated result query sqlQuery = fmt.Sprintf(`--sql %s @@ -335,12 +337,8 @@ func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) { %s GROUP BY people.id ORDER BY CONCAT(people.last_name, people.first_name) COLLATE NOCASE ASC - LIMIT @pagination_limit - OFFSET @pagination_offset - `, sqlQuery, sqlFieldJoins, sqlFilters) - - sqlParams = append(sqlParams, sql.Named("pagination_limit", results.Pagination.PageSize)) - sqlParams = append(sqlParams, sql.Named("pagination_offset", results.Pagination.Offset)) + %s + `, sqlQuery, sqlFieldJoins, sqlFilters, sqlPagination) sqlResult := db.Raw(sqlQuery, sqlParams...).Scan(&results.Results) if sqlResult.Error != nil { diff --git a/static/search.js b/static/search.js index 34f4ecb..825bfac 100644 --- a/static/search.js +++ b/static/search.js @@ -172,6 +172,10 @@ function search() { fields = {} $("[data-optional-field]:not(:disabled)").each(function() { + if (!advancedSearch) { + return; + } + var index = $(this).attr("data-optional-field"); var value = $(this).val(); diff --git a/views/people.html b/views/people.html index 1eeacd5..292734b 100644 --- a/views/people.html +++ b/views/people.html @@ -67,23 +67,27 @@
-
- - {% if MembersPage %} - - {% else %} - - {% endif %} -
+ {% if PermShow %} +
+ + {% if MembersPage %} + + {% else %} + + {% endif %} +
+ {% endif %} -
- - {% if MembersPage %} - - {% else %} - - {% endif %} -
+ {% if PermShowArchived %} +
+ + {% if MembersPage %} + + {% else %} + + {% endif %} +
+ {% endif %}