pop-camarades/controllers/contacts.go

1110 lines
24 KiB
Go

package controllers
import (
"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"
)
func Contacts(c *fiber.Ctx) error {
userid, err := helpers.GetSessionUserId(c)
if err != nil {
return err
}
allowedSections, err := helpers.PermissionsGetSections(
userid, "show_contact",
)
if err != nil {
return err
}
allowedSectionsArchived, err := helpers.PermissionsGetSections(
userid, "show_archived_contact",
)
if err != nil {
return err
}
permShow := (len(allowedSections) > 0)
permShowArchived := (len(allowedSectionsArchived) > 0)
if !permShow && !permShowArchived {
return fiber.NewError(fiber.StatusForbidden, "Forbidden")
}
db, err := helpers.GetDatabase()
if err != nil {
return err
}
searchJSON := c.Query("s")
var params database.PeopleSearchParams
if len(searchJSON) > 0 {
err = json.Unmarshal([]byte(searchJSON), &params)
if err != nil {
log.Warn(err)
searchJSON = ""
}
}
// If active and archive are false, default to active
// This needs to be placed before the security verification
if !params.Active && !params.Archive {
params.Active = true
}
params.PageNumber, _ = strconv.Atoi(c.Query("p"))
params.PageSize = 50
params.PersonType = "contacts"
params.OrderColumn = c.Query("c")
params.OrderDirection = c.Query("o")
var sections []models.Section
db.Order("name asc").Find(&sections, "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": "Sympathisants",
"MembersPage": false,
"People": results.Results,
"Pagination": results.Pagination,
"PermShow": permShow,
"PermShowArchived": permShowArchived,
"SearchJSON": searchJSON,
"Sections": sections,
"Fields": fields,
"OrderCol": params.OrderColumn,
"OrderDir": params.OrderDirection,
})
}
func ContactsExport(c *fiber.Ctx) error {
userid, err := helpers.GetSessionUserId(c)
if err != nil {
return err
}
allowedSections, err := helpers.PermissionsGetSections(
userid, "show_contact",
)
if err != nil {
return err
}
allowedSectionsArchived, err := helpers.PermissionsGetSections(
userid, "show_archived_contact",
)
if err != nil {
return err
}
permShow := (len(allowedSections) > 0)
permShowArchived := (len(allowedSectionsArchived) > 0)
if !permShow && !permShowArchived {
return fiber.NewError(fiber.StatusForbidden, "Forbidden")
}
db, err := helpers.GetDatabase()
if err != nil {
return err
}
searchJSON := c.Query("s")
var params database.PeopleSearchParams
if len(searchJSON) > 0 {
err = json.Unmarshal([]byte(searchJSON), &params)
if err != nil {
log.Warn(err)
searchJSON = ""
}
}
// If active and archive are false, default to active
// This needs to be placed before the security verification
if !params.Active && !params.Archive {
params.Active = true
}
params.PageSize = 0
params.PersonType = "contacts"
var sections []models.Section
db.Order("name asc").Find(&sections, "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
}
var fields []models.Field
db.Order("position asc").Preload(
"List",
).Find(
&fields, "person_type = ?", "contact",
)
var fieldValues []models.FieldValue
db.Preload("ListItem").Find(&fieldValues)
c.Set("Content-Type", "text/csv")
c.Set("Content-Disposition", fmt.Sprintf(
"attachment;filename=contacts_%s.csv",
time.Now().Format(time.RFC3339),
))
csvFields := []string{
"first_name",
"last_name",
"email",
"phone",
"mobile",
"address1",
"address2",
"postal_code",
"city",
"section_id",
"section_name",
}
for _, field := range fields {
csvFields = append(csvFields, field.Name)
}
for key, csvField := range csvFields {
csvField = strings.ReplaceAll(csvField, "\r", "")
csvField = strings.ReplaceAll(csvField, "\n", " ")
csvField = strings.ReplaceAll(csvField, ",", "_")
csvField = strings.ReplaceAll(csvField, ";", "_")
if key+1 == len(csvFields) {
c.Writef("\"%s\"\n", csvField)
} else {
c.Writef("\"%s\";", csvField)
}
}
for _, person := range results.Results {
c.Writef("\"%s\";", person.FirstName)
c.Writef("\"%s\";", person.LastName)
c.Writef("\"%s\";", person.Email)
c.Writef("\"%s\";", person.Phone)
c.Writef("\"%s\";", person.Mobile)
c.Writef("\"%s\";", person.Address1)
c.Writef("\"%s\";", person.Address2)
c.Writef("\"%s\";", person.PostalCode)
c.Writef("\"%s\";", person.City)
c.Writef("\"%d\";", person.SectionID)
if len(fields) > 0 {
c.Writef("\"%s\";", person.Section.Name)
} else {
c.Writef("\"%s\"\n", person.Section.Name)
}
for key, field := range fields {
endLine := ";"
if key+1 == len(fields) {
endLine = "\n"
}
countMulti := 0
found := false
for _, value := range fieldValues {
if value.FieldID == field.ID && value.PersonID == person.ID {
found = true
if field.FieldType == "text" || field.FieldType == "longtext" {
text := value.ValueString.String
text = strings.ReplaceAll(text, "\r", "")
text = strings.ReplaceAll(text, "\n", " ")
text = strings.ReplaceAll(text, ",", "_")
text = strings.ReplaceAll(text, ";", "_")
c.Writef("\"%s\"%s", text, endLine)
} else if field.FieldType == "number" {
if value.ValueInt.Valid {
c.Writef("\"%d\"%s", value.ValueInt.Int64, endLine)
} else {
c.Writef("\"\"%s", endLine)
}
} else if field.FieldType == "date" {
if value.ValueDate.Valid {
date := value.ValueDate.Time.Format("2006-01-02")
c.Writef("\"%s\"%s", date, endLine)
} else {
c.Writef("\"\"%s", endLine)
}
} else if field.FieldType == "list" {
if field.List != nil && field.List.Multi {
if countMulti == 0 {
c.Writef("\"")
} else {
c.Writef(",")
}
c.Writef("%s", value.ListItem.Value)
countMulti++
} else {
c.Writef("\"%s\"%s", value.ListItem.Value, endLine)
}
}
}
}
if countMulti > 0 {
c.Writef("\"%s", endLine)
}
if !found {
c.Writef("\"\"%s", endLine)
}
}
}
return nil
}
func ContactShow(c *fiber.Ctx) error {
id := c.Params("id")
db, err := helpers.GetDatabase()
if err != nil {
return err
}
var person models.Person
result := db.Unscoped().Preload("Section").Find(
&person, "id = ? AND is_contact = ?", id, true,
)
if result.Error != nil {
return result.Error
}
if result.RowsAffected < 1 {
return fiber.NewError(fiber.StatusNotFound, "Not found")
}
userid, err := helpers.GetSessionUserId(c)
if err != nil {
return err
}
persmissionName := "show_contact"
if person.DeletedAt.Valid {
persmissionName = "show_archived_contact"
}
allow, err := helpers.PermissionsCheckSection(
userid, persmissionName, person.SectionID,
)
if err != nil {
return err
}
if !allow {
return fiber.NewError(fiber.StatusForbidden, "Forbidden")
}
title := fmt.Sprintf("%s | Contact", person.FirstName)
if person.LastName != "" {
title = fmt.Sprintf("%s %s", person.LastName, title)
}
var fields []models.Field
db.Order("position asc").Preload(
"List",
).Find(
&fields, "person_type = ?", "contact",
)
var fieldValues []models.FieldValue
db.Preload("ListItem").Find(
&fieldValues,
"person_id = ?",
person.ID,
)
permEdit, _ := helpers.PermissionsGetSections(userid, "edit_contact")
permConvert, _ := helpers.PermissionsGetSections(userid, "convert_contact_to_member")
permArchive, _ := helpers.PermissionsGetSections(userid, "archive_contact")
permRestore, _ := helpers.PermissionsGetSections(userid, "restore_contact")
permPurge, _ := helpers.PermissionsGetSections(userid, "purge_contact")
return c.Render("person", fiber.Map{
"PageTitle": title,
"Person": person,
"Fields": fields,
"FieldValues": fieldValues,
"PermEdit": permEdit,
"PermConvert": permConvert,
"PermArchive": permArchive,
"PermRestore": permRestore,
"PermPurge": permPurge,
})
}
func ContactAdd(c *fiber.Ctx) error {
userid, err := helpers.GetSessionUserId(c)
if err != nil {
return err
}
allowedSections, err := helpers.PermissionsGetSections(
userid, "create_contact",
)
if err != nil {
return err
}
if len(allowedSections) < 1 {
return fiber.NewError(fiber.StatusForbidden, "Forbidden")
}
db, err := helpers.GetDatabase()
if err != nil {
return err
}
var sections []models.Section
db.Order("name asc").Find(
&sections,
"contains_contacts = ?",
true,
)
var fields []models.Field
db.Preload("List").Preload("List.ListItems").Order(
"position asc",
).Find(
&fields, "person_type = ?", "contact",
)
var person models.Person
var errors []string
if c.Method() == "POST" {
data := PersonValidation{
LastName: c.FormValue("last_name"),
FirstName: c.FormValue("first_name"),
Email: c.FormValue("email"),
Phone: c.FormValue("phone"),
Mobile: c.FormValue("mobile"),
Address1: c.FormValue("address1"),
Address2: c.FormValue("address2"),
PostalCode: c.FormValue("postal_code"),
City: c.FormValue("city"),
Section: c.FormValue("section"),
}
validate := validator.New()
validErrs := validate.Struct(data)
if validErrs != nil {
for _, validErr := range validErrs.(validator.ValidationErrors) {
switch validErr.Field() {
case "LastName":
errors = append(errors, "Le nom de famille ne peut pas contenir plus de 100 caractères")
case "FirstName":
errors = append(errors, "Le prénom est requis et ne peut pas contenir plus de 100 caractères")
case "Email":
if len(data.Email) > 0 {
errors = append(errors, "L'adresse email doit être valide")
}
case "Phone":
errors = append(errors, "Le numéro de téléphone fixe est trop long")
case "Mobile":
errors = append(errors, "Le numéro de téléphone mobile est trop long")
case "Address1":
errors = append(errors, "La ligne 1 de l'adresse est trop longue")
case "Address2":
errors = append(errors, "La ligne 2 de l'adresse est trop longue")
case "PostalCode":
if len(data.PostalCode) > 0 {
errors = append(errors, "Le code postal n'est pas valide")
}
case "City":
errors = append(errors, "Le lieu est trop long")
case "Section":
errors = append(errors, "La section n'est pas valide")
}
}
}
person.IsContact = true
person.IsMember = false
person.LastName = data.LastName
person.FirstName = data.FirstName
person.Email = data.Email
person.Phone = data.Phone
person.Mobile = data.Mobile
person.Address1 = data.Address1
person.Address2 = data.Address2
person.PostalCode = data.PostalCode
person.City = data.City
if len(data.Email) > 0 {
var personEmail []models.Person
result := db.Find(&personEmail, "LOWER(email) = LOWER(?)", data.Email)
if result.Error != nil {
return result.Error
}
if result.RowsAffected > 0 {
errors = append(errors, "L'adresse email est déjà utilisée par un membre ou un sympathisant")
}
}
sectionID, err := strconv.ParseUint(data.Section, 10, 0)
if err == nil {
for _, section := range sections {
if section.ID == uint(sectionID) {
person.SectionID = uint(sectionID)
break
}
}
}
if person.SectionID == 0 {
errors = append(errors, "La section est introuvable")
}
if len(errors) == 0 {
result := db.Create(&person)
if result.Error != nil {
return result.Error
}
for _, field := range fields {
if field.List != nil && field.List.Multi {
for _, listItem := range field.List.ListItems {
key := fmt.Sprintf("field-%d-%d", field.ID, listItem.ID)
value := c.FormValue(key)
if value == "on" {
var fieldValue models.FieldValue
fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID
fieldValue.ListItemID = &listItem.ID
db.Create(&fieldValue)
}
}
} else {
key := fmt.Sprintf("field-%d", field.ID)
value := c.FormValue(key)
if (field.FieldType == "text" || field.FieldType == "longtext") && len(value) > 0 {
var fieldValue models.FieldValue
fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID
err = fieldValue.ValueString.Scan(value)
if err != nil {
continue
}
db.Create(&fieldValue)
}
if field.FieldType == "number" && len(value) > 0 {
valueInt, err := strconv.ParseInt(value, 10, 0)
if err != nil {
continue
}
var fieldValue models.FieldValue
fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID
err = fieldValue.ValueInt.Scan(valueInt)
if err != nil {
continue
}
db.Create(&fieldValue)
}
if field.FieldType == "date" && len(value) > 0 {
valueDate, err := time.Parse("2006-01-02", value)
if err != nil {
continue
}
var fieldValue models.FieldValue
fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID
err = fieldValue.ValueDate.Scan(valueDate)
if err != nil {
continue
}
db.Create(&fieldValue)
}
if field.FieldType == "list" && len(value) > 0 {
valueInt, err := strconv.ParseUint(value, 10, 0)
if err != nil {
continue
}
found := false
for _, listItem := range field.List.ListItems {
if listItem.ID == uint(valueInt) {
found = true
}
}
if found {
var fieldValue models.FieldValue
fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID
valueUint := uint(valueInt)
fieldValue.ListItemID = &valueUint
db.Create(&fieldValue)
}
}
}
}
c.Redirect(fmt.Sprintf(
"/contacts/%d",
person.ID,
))
}
}
return c.Render("person_form", fiber.Map{
"PageTitle": "Ajouter un contact",
"MembersPage": false,
"Person": person,
"Sections": sections,
"Fields": fields,
"Errors": errors,
})
}
func ContactEdit(c *fiber.Ctx) error {
id := c.Params("id")
db, err := helpers.GetDatabase()
if err != nil {
return err
}
var person models.Person
result := db.Find(&person, "id = ?", id)
if result.Error != nil {
return result.Error
}
if result.RowsAffected < 1 {
return fiber.NewError(fiber.StatusNotFound, "Not found")
}
userid, err := helpers.GetSessionUserId(c)
if err != nil {
return err
}
allow, err := helpers.PermissionsCheckSection(
userid, "edit_contact", person.SectionID,
)
if err != nil {
return err
}
if !allow {
return fiber.NewError(fiber.StatusForbidden, "Forbidden")
}
title := fmt.Sprintf("%s | Modifier contact", person.FirstName)
if person.LastName != "" {
title = fmt.Sprintf("%s %s", person.LastName, title)
}
var sections []models.Section
db.Order("name asc").Find(
&sections,
"contains_contacts = ?",
true,
)
var fields []models.Field
db.Preload("List").Preload("List.ListItems").Order(
"position asc",
).Find(
&fields, "person_type = ?", "contact",
)
var fieldValues []models.FieldValue
db.Preload("ListItem").Find(
&fieldValues,
"person_id = ?",
person.ID,
)
var errors []string
if c.Method() == "POST" {
data := PersonValidation{
LastName: c.FormValue("last_name"),
FirstName: c.FormValue("first_name"),
Email: c.FormValue("email"),
Phone: c.FormValue("phone"),
Mobile: c.FormValue("mobile"),
Address1: c.FormValue("address1"),
Address2: c.FormValue("address2"),
PostalCode: c.FormValue("postal_code"),
City: c.FormValue("city"),
Section: c.FormValue("section"),
}
validate := validator.New()
validErrs := validate.Struct(data)
if validErrs != nil {
for _, validErr := range validErrs.(validator.ValidationErrors) {
switch validErr.Field() {
case "LastName":
errors = append(errors, "Le nom de famille ne peut pas contenir plus de 100 caractères")
case "FirstName":
errors = append(errors, "Le prénom est requis et ne peut pas contenir plus de 100 caractères")
case "Email":
if len(data.Email) > 0 {
errors = append(errors, "L'adresse email doit être valide")
}
case "Phone":
errors = append(errors, "Le numéro de téléphone fixe est trop long")
case "Mobile":
errors = append(errors, "Le numéro de téléphone mobile est trop long")
case "Address1":
errors = append(errors, "La ligne 1 de l'adresse est trop longue")
case "Address2":
errors = append(errors, "La ligne 2 de l'adresse est trop longue")
case "PostalCode":
if len(data.PostalCode) > 0 {
errors = append(errors, "Le code postal n'est pas valide")
}
case "City":
errors = append(errors, "Le lieu est trop long")
case "Section":
errors = append(errors, "La section n'est pas valide")
}
}
}
person.IsContact = true
person.IsMember = false
person.LastName = data.LastName
person.FirstName = data.FirstName
person.Email = data.Email
person.Phone = data.Phone
person.Mobile = data.Mobile
person.Address1 = data.Address1
person.Address2 = data.Address2
person.PostalCode = data.PostalCode
person.City = data.City
if len(data.Email) > 0 {
var personEmail []models.Person
result := db.Find(&personEmail, "LOWER(email) = LOWER(?)", data.Email)
if result.Error != nil {
return result.Error
}
if result.RowsAffected > 0 {
errors = append(errors, "L'adresse email est déjà utilisée par un membre ou un sympathisant")
}
}
sectionID, err := strconv.ParseUint(data.Section, 10, 0)
if err == nil {
for _, section := range sections {
if section.ID == uint(sectionID) {
person.SectionID = uint(sectionID)
break
}
}
}
if person.SectionID == 0 {
errors = append(errors, "La section est introuvable")
}
for _, field := range fields {
db.Unscoped().Delete(
&models.FieldValue{},
"person_id = ? AND field_id = ?",
person.ID,
field.ID,
)
if field.List != nil && field.List.Multi {
for _, listItem := range field.List.ListItems {
key := fmt.Sprintf("field-%d-%d", field.ID, listItem.ID)
value := c.FormValue(key)
if value == "on" {
var fieldValue models.FieldValue
fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID
fieldValue.ListItemID = &listItem.ID
db.Create(&fieldValue)
}
}
} else {
key := fmt.Sprintf("field-%d", field.ID)
value := c.FormValue(key)
if (field.FieldType == "text" || field.FieldType == "longtext") && len(value) > 0 {
var fieldValue models.FieldValue
fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID
err = fieldValue.ValueString.Scan(value)
if err != nil {
continue
}
db.Create(&fieldValue)
}
if field.FieldType == "number" && len(value) > 0 {
valueInt, err := strconv.ParseInt(value, 10, 0)
if err != nil {
continue
}
var fieldValue models.FieldValue
fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID
err = fieldValue.ValueInt.Scan(valueInt)
if err != nil {
continue
}
db.Create(&fieldValue)
}
if field.FieldType == "date" && len(value) > 0 {
valueDate, err := time.Parse("2006-01-02", value)
if err != nil {
continue
}
var fieldValue models.FieldValue
fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID
err = fieldValue.ValueDate.Scan(valueDate)
if err != nil {
continue
}
db.Create(&fieldValue)
}
if field.FieldType == "list" && len(value) > 0 {
valueInt, err := strconv.ParseUint(value, 10, 0)
if err != nil {
continue
}
found := false
for _, listItem := range field.List.ListItems {
if listItem.ID == uint(valueInt) {
found = true
}
}
if found {
var fieldValue models.FieldValue
fieldValue.FieldID = field.ID
fieldValue.PersonID = person.ID
valueUint := uint(valueInt)
fieldValue.ListItemID = &valueUint
db.Create(&fieldValue)
}
}
}
}
if len(errors) == 0 {
result := db.Save(&person)
if result.Error != nil {
return result.Error
}
c.Redirect(fmt.Sprintf(
"/contacts/%d",
person.ID,
))
}
}
return c.Render("person_form", fiber.Map{
"PageTitle": title,
"Person": person,
"Sections": sections,
"Fields": fields,
"FieldValues": fieldValues,
"Errors": errors,
})
}
func ContactConvert(c *fiber.Ctx) error {
id := c.Params("id")
db, err := helpers.GetDatabase()
if err != nil {
return err
}
var person models.Person
result := db.Find(&person, "id = ?", id)
if result.Error != nil {
return result.Error
}
if result.RowsAffected < 1 {
return fiber.NewError(fiber.StatusNotFound, "Not found")
}
userid, err := helpers.GetSessionUserId(c)
if err != nil {
return err
}
allow, err := helpers.PermissionsCheckSection(
userid, "convert_contact_to_member", person.SectionID,
)
if err != nil {
return err
}
if !allow {
return fiber.NewError(fiber.StatusForbidden, "Forbidden")
}
person.IsContact = false
person.IsMember = true
result = db.Save(&person)
if result.Error != nil {
return result.Error
}
return c.Redirect(fmt.Sprintf(
"/members/%d",
person.ID,
))
}
func ContactArchive(c *fiber.Ctx) error {
id := c.Params("id")
db, err := helpers.GetDatabase()
if err != nil {
return err
}
var person models.Person
result := db.Find(&person, "id = ? AND is_contact = ? AND deleted_at IS NULL", id, true)
if result.Error != nil {
return result.Error
}
if result.RowsAffected < 1 {
return fiber.NewError(fiber.StatusNotFound, "Not found")
}
userid, err := helpers.GetSessionUserId(c)
if err != nil {
return err
}
allow, err := helpers.PermissionsCheckSection(
userid, "archive_contact", person.SectionID,
)
if err != nil {
return err
}
if !allow {
return fiber.NewError(fiber.StatusForbidden, "Forbidden")
}
result = db.Delete(&person)
if result.Error != nil {
return result.Error
}
return c.Redirect(fmt.Sprintf(
"/contacts/%s",
id,
))
}
func ContactRestore(c *fiber.Ctx) error {
id := c.Params("id")
db, err := helpers.GetDatabase()
if err != nil {
return err
}
var person models.Person
result := db.Unscoped().Find(
&person, "id = ? AND is_contact = ? AND deleted_at IS NOT NULL", id, true,
)
if result.Error != nil {
return result.Error
}
if result.RowsAffected < 1 {
return fiber.NewError(fiber.StatusNotFound, "Not found")
}
userid, err := helpers.GetSessionUserId(c)
if err != nil {
return err
}
allow, err := helpers.PermissionsCheckSection(
userid, "restore_contact", person.SectionID,
)
if err != nil {
return err
}
if !allow {
return fiber.NewError(fiber.StatusForbidden, "Forbidden")
}
person.DeletedAt.Valid = false
result = db.Save(&person)
if result.Error != nil {
return result.Error
}
return c.Redirect(fmt.Sprintf(
"/contacts/%s",
id,
))
}
func ContactPurge(c *fiber.Ctx) error {
id := c.Params("id")
db, err := helpers.GetDatabase()
if err != nil {
return err
}
var person models.Person
result := db.Unscoped().Find(
&person, "id = ? AND is_contact = ?", id, true,
)
if result.Error != nil {
return result.Error
}
if result.RowsAffected < 1 {
return fiber.NewError(fiber.StatusNotFound, "Not found")
}
userid, err := helpers.GetSessionUserId(c)
if err != nil {
return err
}
allow, err := helpers.PermissionsCheckSection(
userid, "purge_contact", person.SectionID,
)
if err != nil {
return err
}
if !allow {
return fiber.NewError(fiber.StatusForbidden, "Forbidden")
}
result = db.Unscoped().Delete(
&models.FieldValue{}, "person_id = ?", id,
)
if result.Error != nil {
return result.Error
}
result = db.Unscoped().Delete(
&models.Person{}, id,
)
if result.Error != nil {
return result.Error
}
return c.Redirect("/contacts")
}