Advanced search on members
This commit is contained in:
parent
8e002415d0
commit
d8662a32d1
4 changed files with 396 additions and 176 deletions
|
|
@ -3,23 +3,30 @@ package database
|
|||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.readonly.ch/bouzoure/pop-camarades/helpers"
|
||||
"git.readonly.ch/bouzoure/pop-camarades/models"
|
||||
)
|
||||
|
||||
type PeopleSearchParams struct {
|
||||
Advanced bool `json:"advanced"`
|
||||
Name string `json:"name"`
|
||||
Section uint `json:"section"`
|
||||
Active bool `json:"active"`
|
||||
Archived bool `json:"archived"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Address string `json:"address"`
|
||||
PostalCode string `json:"postal_code"`
|
||||
City string `json:"city"`
|
||||
Fields map[uint]any `json:"fields"`
|
||||
// JSON, provided by user
|
||||
Advanced bool `json:"advanced"`
|
||||
Name string `json:"name"`
|
||||
Section uint `json:"section"`
|
||||
Active bool `json:"active"`
|
||||
Archive bool `json:"archive"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Address string `json:"address"`
|
||||
PostalCode string `json:"postal_code"`
|
||||
City string `json:"city"`
|
||||
Fields map[uint][]any `json:"fields"`
|
||||
// Not JSON, defined in backend
|
||||
PersonType string `json:"-"`
|
||||
PageSize int `json:"-"`
|
||||
PageNumber int `json:"-"`
|
||||
AllowedSections []uint `json:"-"`
|
||||
}
|
||||
|
||||
type PeopleSearchResults struct {
|
||||
|
|
@ -28,7 +35,7 @@ type PeopleSearchResults struct {
|
|||
Pagination helpers.Pagination
|
||||
}
|
||||
|
||||
func PeopleSearch(params PeopleSearchParams, personType string, pageSize int, page int) (PeopleSearchResults, error) {
|
||||
func PeopleSearch(params PeopleSearchParams) (PeopleSearchResults, error) {
|
||||
var results PeopleSearchResults
|
||||
|
||||
db, err := helpers.GetDatabase()
|
||||
|
|
@ -36,10 +43,9 @@ func PeopleSearch(params PeopleSearchParams, personType string, pageSize int, pa
|
|||
return results, nil
|
||||
}
|
||||
|
||||
var sqlQuery string
|
||||
var sqlParams []any
|
||||
sqlQuery = `--sql
|
||||
SELECT people.ID,
|
||||
// SQL qeury for results
|
||||
sqlQuery := `--sql
|
||||
SELECT people.id,
|
||||
people.is_member,
|
||||
people.is_contact,
|
||||
people.first_name,
|
||||
|
|
@ -51,22 +57,292 @@ func PeopleSearch(params PeopleSearchParams, personType string, pageSize int, pa
|
|||
sections.name AS Section__name
|
||||
FROM people
|
||||
INNER JOIN sections
|
||||
ON people.section_id = sections.id
|
||||
WHERE is_member = @is_member
|
||||
AND is_contact = @is_contact`
|
||||
ON people.section_id = sections.id`
|
||||
|
||||
if personType == "members" {
|
||||
// SQL query to count results
|
||||
sqlQueryCount := `--sql
|
||||
SELECT people.id
|
||||
FROM people
|
||||
`
|
||||
|
||||
// Create filters for both queries
|
||||
var sqlParams []any
|
||||
var sqlFilters string
|
||||
|
||||
// Filter: member or contact
|
||||
sqlFilters = `--sql
|
||||
WHERE is_member = @is_member
|
||||
AND is_contact = @is_contact
|
||||
`
|
||||
if params.PersonType == "members" {
|
||||
sqlParams = append(sqlParams, sql.Named("is_member", true))
|
||||
sqlParams = append(sqlParams, sql.Named("is_contact", false))
|
||||
} else if personType == "contacts" {
|
||||
} else if params.PersonType == "contacts" {
|
||||
sqlParams = append(sqlParams, sql.Named("is_member", false))
|
||||
sqlParams = append(sqlParams, sql.Named("is_contact", true))
|
||||
} else {
|
||||
return results, fmt.Errorf("unkown person type")
|
||||
}
|
||||
|
||||
sqlResult := db.Raw(sqlQuery, sqlParams...).Scan(&results.Results)
|
||||
// Filter: name -> first_name + last_name
|
||||
if len(params.Name) > 0 {
|
||||
nameFilter := `--sql
|
||||
people.first_name LIKE @name
|
||||
OR people.last_name LIKE @name
|
||||
OR CONCAT(people.first_name, ' ', people.last_name) LIKE @name
|
||||
OR CONCAT(people.last_name, ' ', people.first_name) LIKE @name
|
||||
`
|
||||
sqlParams = append(sqlParams, sql.Named(
|
||||
"name",
|
||||
fmt.Sprintf("%%%s%%", params.Name),
|
||||
))
|
||||
|
||||
if strings.Contains(params.Name, " ") {
|
||||
names := strings.Split(params.Name, " ")
|
||||
for index, name := range names {
|
||||
nameFilter = fmt.Sprintf(`--sql
|
||||
%s
|
||||
OR people.first_name LIKE @name_%d
|
||||
OR people.last_name LIKE @name_%d
|
||||
`, nameFilter, index, index)
|
||||
|
||||
sqlParams = append(sqlParams, sql.Named(
|
||||
fmt.Sprintf("name_%d", index),
|
||||
fmt.Sprintf("%%%s%%", name),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
sqlFilters = fmt.Sprintf(`--sql
|
||||
%s
|
||||
AND (%s)
|
||||
`, sqlFilters, nameFilter)
|
||||
}
|
||||
|
||||
// Filter: section
|
||||
if params.Section > 0 {
|
||||
sqlFilters = fmt.Sprintf(`--sql
|
||||
%s
|
||||
AND people.section_id = @section
|
||||
`, sqlFilters)
|
||||
sqlParams = append(sqlParams, sql.Named("section", params.Section))
|
||||
}
|
||||
|
||||
// Filter: active (only apply if archived is false)
|
||||
if params.Active && !params.Archive {
|
||||
sqlFilters = fmt.Sprintf(`--sql
|
||||
%s
|
||||
AND people.deleted_at IS NULL
|
||||
`, sqlFilters)
|
||||
}
|
||||
|
||||
// Filter: archived (only apply if active is false)
|
||||
if params.Archive && !params.Active {
|
||||
sqlFilters = fmt.Sprintf(`--sql
|
||||
%s
|
||||
AND people.deleted_at IS NOT NULL
|
||||
`, sqlFilters)
|
||||
}
|
||||
|
||||
// Filters: email
|
||||
if len(params.Email) > 0 {
|
||||
sqlFilters = fmt.Sprintf(`--sql
|
||||
%s
|
||||
AND people.email LIKE @email
|
||||
`, sqlFilters)
|
||||
sqlParams = append(sqlParams, sql.Named(
|
||||
"email",
|
||||
fmt.Sprintf("%%%s%%", params.Email),
|
||||
))
|
||||
}
|
||||
|
||||
// Filters: phone
|
||||
if len(params.Phone) > 0 {
|
||||
params.Phone = strings.ReplaceAll(params.Phone, " ", "")
|
||||
params.Phone = strings.ReplaceAll(params.Phone, "-", "")
|
||||
params.Phone = strings.ReplaceAll(params.Phone, ".", "")
|
||||
params.Phone = strings.ReplaceAll(params.Phone, "/", "")
|
||||
params.Phone = strings.ReplaceAll(params.Phone, "+", "")
|
||||
|
||||
sqlFilters = fmt.Sprintf(`--sql
|
||||
%s
|
||||
AND (
|
||||
REPLACE(
|
||||
REPLACE(
|
||||
REPLACE(
|
||||
REPLACE(
|
||||
REPLACE(people.phone, ' ', '')
|
||||
, '-', '')
|
||||
, '.', '')
|
||||
, '/', '')
|
||||
, '+', '') LIKE @phone
|
||||
OR REPLACE(
|
||||
REPLACE(
|
||||
REPLACE(
|
||||
REPLACE(
|
||||
REPLACE(people.mobile, ' ', '')
|
||||
, '-', '')
|
||||
, '.', '')
|
||||
, '/', '')
|
||||
, '+', '') LIKE @phone
|
||||
)
|
||||
`, sqlFilters)
|
||||
sqlParams = append(sqlParams, sql.Named(
|
||||
"phone",
|
||||
fmt.Sprintf("%%%s%%", params.Phone),
|
||||
))
|
||||
}
|
||||
|
||||
// Filters: address
|
||||
if len(params.Address) > 0 {
|
||||
sqlFilters = fmt.Sprintf(`--sql
|
||||
%s
|
||||
AND (
|
||||
people.address1 LIKE @address
|
||||
OR people.address2 LIKE @address
|
||||
)
|
||||
`, sqlFilters)
|
||||
sqlParams = append(sqlParams, sql.Named(
|
||||
"address",
|
||||
fmt.Sprintf("%%%s%%", params.Address),
|
||||
))
|
||||
}
|
||||
|
||||
// Filters: postal code
|
||||
if len(params.PostalCode) > 0 {
|
||||
sqlFilters = fmt.Sprintf(`--sql
|
||||
%s
|
||||
AND people.postal_code = @postal_code
|
||||
`, sqlFilters)
|
||||
sqlParams = append(sqlParams, sql.Named(
|
||||
"postal_code",
|
||||
params.PostalCode,
|
||||
))
|
||||
}
|
||||
|
||||
// Filters: city
|
||||
if len(params.City) > 0 {
|
||||
sqlFilters = fmt.Sprintf(`--sql
|
||||
%s
|
||||
AND people.city LIKE @city
|
||||
`, sqlFilters)
|
||||
sqlParams = append(sqlParams, sql.Named(
|
||||
"city",
|
||||
fmt.Sprintf("%%%s%%", params.City),
|
||||
))
|
||||
}
|
||||
|
||||
// Security filter: only show results in allowed secitons
|
||||
sqlFilters = fmt.Sprintf(`--sql
|
||||
%s
|
||||
AND people.section_id IN @allowed_sections
|
||||
`, sqlFilters)
|
||||
sqlParams = append(sqlParams, sql.Named("allowed_sections", params.AllowedSections))
|
||||
|
||||
// Filter: optional fields
|
||||
var sqlFieldJoins string
|
||||
for fieldID, values := range params.Fields {
|
||||
var field models.Field
|
||||
db.First(&field, "id = ?", fieldID)
|
||||
|
||||
// Skip if field ID not found in DB
|
||||
if field.ID <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
sqlFieldJoins = fmt.Sprintf(`--sql
|
||||
%s
|
||||
LEFT JOIN field_values
|
||||
AS field_%d
|
||||
ON people.id = field_%d.person_id
|
||||
AND field_%d.field_id = @field_%d_id
|
||||
`, sqlFieldJoins, field.ID, field.ID, field.ID, field.ID)
|
||||
|
||||
sqlParams = append(sqlParams, sql.Named(
|
||||
fmt.Sprintf("field_%d_id", field.ID),
|
||||
field.ID,
|
||||
))
|
||||
|
||||
var fieldFilter string
|
||||
for index, value := range values {
|
||||
var filter string
|
||||
if fieldFilter != "" {
|
||||
filter = "OR"
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
default:
|
||||
fmt.Println(v)
|
||||
continue
|
||||
case string:
|
||||
filter = fmt.Sprintf(`--sql
|
||||
%s
|
||||
field_%d.value_string LIKE @field_%d_%d
|
||||
OR field_%d.value_date LIKE @field_%d_%d
|
||||
`, filter, field.ID, field.ID, index, field.ID, field.ID, index)
|
||||
sqlParams = append(sqlParams, sql.Named(
|
||||
fmt.Sprintf("field_%d_%d", field.ID, index),
|
||||
fmt.Sprintf("%%%s%%", v),
|
||||
))
|
||||
case float64:
|
||||
filter = fmt.Sprintf(`--sql
|
||||
%s
|
||||
field_%d.value_int = @field_%d_%d
|
||||
OR field_%d.list_item_id = @field_%d_%d
|
||||
`, filter, field.ID, field.ID, index, 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)
|
||||
}
|
||||
|
||||
if fieldFilter != "" {
|
||||
sqlFilters = fmt.Sprintf(`--sql
|
||||
%s
|
||||
AND (%s)
|
||||
`, sqlFilters, fieldFilter)
|
||||
}
|
||||
}
|
||||
|
||||
// Build and run count query
|
||||
sqlQueryCount = fmt.Sprintf(`--sql
|
||||
%s
|
||||
%s
|
||||
%s
|
||||
GROUP BY people.id
|
||||
`, sqlQueryCount, sqlFieldJoins, sqlFilters)
|
||||
|
||||
var count []uint
|
||||
sqlCountResult := db.Raw(sqlQueryCount, sqlParams...).Scan(&count)
|
||||
if sqlCountResult.Error != nil {
|
||||
return results, sqlCountResult.Error
|
||||
}
|
||||
|
||||
// Create pagination with count
|
||||
results.Pagination = helpers.Paginate(
|
||||
params.PageSize,
|
||||
len(count),
|
||||
params.PageNumber,
|
||||
)
|
||||
|
||||
// Build and run paginated result query
|
||||
sqlQuery = fmt.Sprintf(`--sql
|
||||
%s
|
||||
%s
|
||||
%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))
|
||||
|
||||
sqlResult := db.Raw(sqlQuery, sqlParams...).Scan(&results.Results)
|
||||
if sqlResult.Error != nil {
|
||||
return results, sqlResult.Error
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue