2023-01-29 14:14:31 +00:00
|
|
|
package pkg
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-05-10 16:56:07 +00:00
|
|
|
"fmt"
|
2023-02-03 11:30:56 +00:00
|
|
|
"github.com/julienschmidt/httprouter"
|
2023-02-01 13:34:40 +00:00
|
|
|
"html/template"
|
2023-01-29 14:14:31 +00:00
|
|
|
"net/http"
|
2023-05-10 16:56:07 +00:00
|
|
|
"strings"
|
2023-01-29 14:14:31 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type ContactEntry struct {
|
2023-02-03 12:29:10 +00:00
|
|
|
Slug string
|
2023-01-29 14:14:31 +00:00
|
|
|
Name string
|
|
|
|
Email string
|
|
|
|
Phone string
|
Add tags for contacts too
With Oriol we agreed that contacts should have tags, too, and that the
“tag pool”, as it were, should be shared with the one for invoices (and
all future tags we might add).
I added the contact_tag relation and tag_contact function, just like
with invoices, and then realized that the SQL queries that Go had to
execute were becoming “complex” enough: i had to get not only the slug,
but the contact id to call tag_contact, and all inside a transaction.
Therefore, i opted to create the add_contact and edit_contact functions,
that mirror those for invoice and products, so now each “major” section
has these functions. They also simplified a bit the handling of the
VATIN and phone numbers, because it is now encapsuled inside the
PL/pgSQL function and Go does not know how to assemble the parts.
2023-03-26 00:32:53 +00:00
|
|
|
Tags []string
|
2023-01-29 14:14:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ContactsIndexPage struct {
|
|
|
|
Contacts []*ContactEntry
|
2023-05-10 16:56:07 +00:00
|
|
|
Filters *contactFilterForm
|
2023-01-29 14:14:31 +00:00
|
|
|
}
|
|
|
|
|
2023-02-03 11:30:56 +00:00
|
|
|
func IndexContacts(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
|
|
conn := getConn(r)
|
2023-02-04 09:43:42 +00:00
|
|
|
company := mustGetCompany(r)
|
2023-05-10 16:56:07 +00:00
|
|
|
locale := getLocale(r)
|
|
|
|
filters := newContactFilterForm(locale)
|
|
|
|
if err := filters.Parse(r); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
2023-02-03 11:30:56 +00:00
|
|
|
page := &ContactsIndexPage{
|
2023-05-10 16:56:07 +00:00
|
|
|
Contacts: mustCollectContactEntries(r.Context(), conn, company, filters),
|
|
|
|
Filters: filters,
|
2023-02-03 11:30:56 +00:00
|
|
|
}
|
2023-03-23 09:55:02 +00:00
|
|
|
mustRenderMainTemplate(w, r, "contacts/index.gohtml", page)
|
2023-02-03 11:30:56 +00:00
|
|
|
}
|
|
|
|
|
2023-02-03 12:29:10 +00:00
|
|
|
func GetContactForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
2023-02-03 11:30:56 +00:00
|
|
|
locale := getLocale(r)
|
|
|
|
conn := getConn(r)
|
|
|
|
form := newContactForm(r.Context(), conn, locale)
|
2023-02-03 12:29:10 +00:00
|
|
|
slug := params[0].Value
|
|
|
|
if slug == "new" {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
mustRenderNewContactForm(w, r, form)
|
|
|
|
return
|
|
|
|
}
|
Add tags for contacts too
With Oriol we agreed that contacts should have tags, too, and that the
“tag pool”, as it were, should be shared with the one for invoices (and
all future tags we might add).
I added the contact_tag relation and tag_contact function, just like
with invoices, and then realized that the SQL queries that Go had to
execute were becoming “complex” enough: i had to get not only the slug,
but the contact id to call tag_contact, and all inside a transaction.
Therefore, i opted to create the add_contact and edit_contact functions,
that mirror those for invoice and products, so now each “major” section
has these functions. They also simplified a bit the handling of the
VATIN and phone numbers, because it is now encapsuled inside the
PL/pgSQL function and Go does not know how to assemble the parts.
2023-03-26 00:32:53 +00:00
|
|
|
if !form.MustFillFromDatabase(r.Context(), conn, slug) {
|
2023-02-14 11:46:11 +00:00
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
2023-02-03 12:29:10 +00:00
|
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
2023-03-23 09:46:14 +00:00
|
|
|
mustRenderEditContactForm(w, r, slug, form)
|
2023-02-03 11:30:56 +00:00
|
|
|
}
|
|
|
|
|
2023-02-03 12:29:10 +00:00
|
|
|
func mustRenderNewContactForm(w http.ResponseWriter, r *http.Request, form *contactForm) {
|
2023-04-25 13:28:55 +00:00
|
|
|
mustRenderMainTemplate(w, r, "contacts/new.gohtml", form)
|
2023-02-03 11:30:56 +00:00
|
|
|
}
|
|
|
|
|
2023-03-23 09:46:14 +00:00
|
|
|
type editContactPage struct {
|
|
|
|
Slug string
|
|
|
|
ContactName string
|
|
|
|
Form *contactForm
|
|
|
|
}
|
|
|
|
|
|
|
|
func mustRenderEditContactForm(w http.ResponseWriter, r *http.Request, slug string, form *contactForm) {
|
|
|
|
page := &editContactPage{
|
|
|
|
Slug: slug,
|
|
|
|
ContactName: form.BusinessName.Val,
|
|
|
|
Form: form,
|
|
|
|
}
|
2023-04-25 13:28:55 +00:00
|
|
|
mustRenderMainTemplate(w, r, "contacts/edit.gohtml", page)
|
2023-02-03 12:29:10 +00:00
|
|
|
}
|
|
|
|
|
2023-02-03 11:30:56 +00:00
|
|
|
func HandleAddContact(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
|
|
conn := getConn(r)
|
|
|
|
locale := getLocale(r)
|
|
|
|
form := newContactForm(r.Context(), conn, locale)
|
|
|
|
if err := form.Parse(r); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := verifyCsrfTokenValid(r); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !form.Validate(r.Context(), conn) {
|
2023-03-22 13:59:54 +00:00
|
|
|
if !IsHTMxRequest(r) {
|
|
|
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
|
|
|
}
|
2023-02-03 12:29:10 +00:00
|
|
|
mustRenderNewContactForm(w, r, form)
|
2023-02-03 11:30:56 +00:00
|
|
|
return
|
|
|
|
}
|
2023-02-04 09:43:42 +00:00
|
|
|
company := mustGetCompany(r)
|
Add tags for contacts too
With Oriol we agreed that contacts should have tags, too, and that the
“tag pool”, as it were, should be shared with the one for invoices (and
all future tags we might add).
I added the contact_tag relation and tag_contact function, just like
with invoices, and then realized that the SQL queries that Go had to
execute were becoming “complex” enough: i had to get not only the slug,
but the contact id to call tag_contact, and all inside a transaction.
Therefore, i opted to create the add_contact and edit_contact functions,
that mirror those for invoice and products, so now each “major” section
has these functions. They also simplified a bit the handling of the
VATIN and phone numbers, because it is now encapsuled inside the
PL/pgSQL function and Go does not know how to assemble the parts.
2023-03-26 00:32:53 +00:00
|
|
|
conn.MustExec(r.Context(), "select add_contact($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)", company.Id, form.BusinessName, form.VATIN, form.TradeName, form.Phone, form.Email, form.Web, form.Address, form.City, form.Province, form.PostalCode, form.Country, form.Tags)
|
2023-04-25 13:28:55 +00:00
|
|
|
htmxRedirect(w, r, companyURI(company, "/contacts"))
|
2023-01-29 14:14:31 +00:00
|
|
|
}
|
|
|
|
|
2023-02-03 12:29:10 +00:00
|
|
|
func HandleUpdateContact(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
|
|
|
conn := getConn(r)
|
|
|
|
locale := getLocale(r)
|
|
|
|
form := newContactForm(r.Context(), conn, locale)
|
|
|
|
if err := form.Parse(r); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := verifyCsrfTokenValid(r); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !form.Validate(r.Context(), conn) {
|
2023-03-23 09:46:14 +00:00
|
|
|
mustRenderEditContactForm(w, r, params[0].Value, form)
|
2023-02-03 12:29:10 +00:00
|
|
|
return
|
|
|
|
}
|
Add tags for contacts too
With Oriol we agreed that contacts should have tags, too, and that the
“tag pool”, as it were, should be shared with the one for invoices (and
all future tags we might add).
I added the contact_tag relation and tag_contact function, just like
with invoices, and then realized that the SQL queries that Go had to
execute were becoming “complex” enough: i had to get not only the slug,
but the contact id to call tag_contact, and all inside a transaction.
Therefore, i opted to create the add_contact and edit_contact functions,
that mirror those for invoice and products, so now each “major” section
has these functions. They also simplified a bit the handling of the
VATIN and phone numbers, because it is now encapsuled inside the
PL/pgSQL function and Go does not know how to assemble the parts.
2023-03-26 00:32:53 +00:00
|
|
|
slug := conn.MustGetText(r.Context(), "", "select edit_contact($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)", params[0].Value, form.BusinessName, form.VATIN, form.TradeName, form.Phone, form.Email, form.Web, form.Address, form.City, form.Province, form.PostalCode, form.Country, form.Tags)
|
2023-02-03 12:29:10 +00:00
|
|
|
if slug == "" {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
}
|
2023-04-25 13:28:55 +00:00
|
|
|
htmxRedirect(w, r, companyURI(mustGetCompany(r), "/contacts"))
|
2023-02-03 12:29:10 +00:00
|
|
|
}
|
|
|
|
|
2023-05-10 16:56:07 +00:00
|
|
|
type contactFilterForm struct {
|
|
|
|
Name *InputField
|
|
|
|
Tags *TagsField
|
|
|
|
TagsCondition *ToggleField
|
|
|
|
}
|
|
|
|
|
|
|
|
func newContactFilterForm(locale *Locale) *contactFilterForm {
|
|
|
|
return &contactFilterForm{
|
|
|
|
Name: &InputField{
|
|
|
|
Name: "number",
|
|
|
|
Label: pgettext("input", "Name", locale),
|
|
|
|
Type: "search",
|
|
|
|
},
|
|
|
|
Tags: &TagsField{
|
|
|
|
Name: "tags",
|
|
|
|
Label: pgettext("input", "Tags", locale),
|
|
|
|
},
|
|
|
|
TagsCondition: &ToggleField{
|
|
|
|
Name: "tags_condition",
|
|
|
|
Label: pgettext("input", "Tags Condition", locale),
|
|
|
|
Selected: "and",
|
|
|
|
FirstOption: &ToggleOption{
|
|
|
|
Value: "and",
|
|
|
|
Label: pgettext("tag condition", "All", locale),
|
|
|
|
Description: gettext("Invoices must have all the specified labels.", locale),
|
|
|
|
},
|
|
|
|
SecondOption: &ToggleOption{
|
|
|
|
Value: "or",
|
|
|
|
Label: pgettext("tag condition", "Any", locale),
|
|
|
|
Description: gettext("Invoices must have at least one of the specified labels.", locale),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (form *contactFilterForm) Parse(r *http.Request) error {
|
|
|
|
if err := r.ParseForm(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
form.Name.FillValue(r)
|
|
|
|
form.Tags.FillValue(r)
|
|
|
|
form.TagsCondition.FillValue(r)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func mustCollectContactEntries(ctx context.Context, conn *Conn, company *Company, filters *contactFilterForm) []*ContactEntry {
|
|
|
|
args := []interface{}{company.Id}
|
|
|
|
where := []string{"contact.company_id = $1"}
|
|
|
|
appendWhere := func(expression string, value interface{}) {
|
|
|
|
args = append(args, value)
|
|
|
|
where = append(where, fmt.Sprintf(expression, len(args)))
|
|
|
|
}
|
|
|
|
if filters != nil {
|
|
|
|
name := strings.TrimSpace(filters.Name.String())
|
|
|
|
if name != "" {
|
|
|
|
appendWhere("contact.business_name ilike $%d", "%"+name+"%")
|
|
|
|
}
|
|
|
|
if len(filters.Tags.Tags) > 0 {
|
|
|
|
if filters.TagsCondition.Selected == "and" {
|
|
|
|
appendWhere("contact.tags @> $%d", filters.Tags)
|
|
|
|
} else {
|
|
|
|
appendWhere("contact.tags && $%d", filters.Tags)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rows := conn.MustQuery(ctx, fmt.Sprintf(`
|
Add tags for contacts too
With Oriol we agreed that contacts should have tags, too, and that the
“tag pool”, as it were, should be shared with the one for invoices (and
all future tags we might add).
I added the contact_tag relation and tag_contact function, just like
with invoices, and then realized that the SQL queries that Go had to
execute were becoming “complex” enough: i had to get not only the slug,
but the contact id to call tag_contact, and all inside a transaction.
Therefore, i opted to create the add_contact and edit_contact functions,
that mirror those for invoice and products, so now each “major” section
has these functions. They also simplified a bit the handling of the
VATIN and phone numbers, because it is now encapsuled inside the
PL/pgSQL function and Go does not know how to assemble the parts.
2023-03-26 00:32:53 +00:00
|
|
|
select slug
|
|
|
|
, business_name
|
|
|
|
, email
|
|
|
|
, phone
|
Replace tag relations with array attributes
It all started when i wanted to try to filter invoices by multiple tags
using an “AND”, instead of “OR” as it was doing until now. But
something felt off and seemed to me that i was doing thing much more
complex than needed, all to be able to list the tags as a suggestion
in the input field—which i am not doing yet.
I found this article series[0] exploring different approaches for
tagging, which includes the one i was using, and comparing their
performance. I have not actually tested it, but it seems that i have
chosen the worst option, in both query time and storage.
I attempted to try using an array attribute to each table, which is more
or less the same they did in the articles but without using a separate
relation for tags, and i found out that all the queries were way easier
to write, and needed two joins less, so it was a no-brainer.
[0]: http://www.databasesoup.com/2015/01/tag-all-things.html
2023-04-07 19:31:35 +00:00
|
|
|
, tags
|
Add tags for contacts too
With Oriol we agreed that contacts should have tags, too, and that the
“tag pool”, as it were, should be shared with the one for invoices (and
all future tags we might add).
I added the contact_tag relation and tag_contact function, just like
with invoices, and then realized that the SQL queries that Go had to
execute were becoming “complex” enough: i had to get not only the slug,
but the contact id to call tag_contact, and all inside a transaction.
Therefore, i opted to create the add_contact and edit_contact functions,
that mirror those for invoice and products, so now each “major” section
has these functions. They also simplified a bit the handling of the
VATIN and phone numbers, because it is now encapsuled inside the
PL/pgSQL function and Go does not know how to assemble the parts.
2023-03-26 00:32:53 +00:00
|
|
|
from contact
|
2023-05-10 16:56:07 +00:00
|
|
|
where (%s)
|
Add tags for contacts too
With Oriol we agreed that contacts should have tags, too, and that the
“tag pool”, as it were, should be shared with the one for invoices (and
all future tags we might add).
I added the contact_tag relation and tag_contact function, just like
with invoices, and then realized that the SQL queries that Go had to
execute were becoming “complex” enough: i had to get not only the slug,
but the contact id to call tag_contact, and all inside a transaction.
Therefore, i opted to create the add_contact and edit_contact functions,
that mirror those for invoice and products, so now each “major” section
has these functions. They also simplified a bit the handling of the
VATIN and phone numbers, because it is now encapsuled inside the
PL/pgSQL function and Go does not know how to assemble the parts.
2023-03-26 00:32:53 +00:00
|
|
|
order by business_name
|
2023-05-10 16:56:07 +00:00
|
|
|
`, strings.Join(where, ") AND (")), args...)
|
2023-01-29 14:14:31 +00:00
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
var entries []*ContactEntry
|
|
|
|
for rows.Next() {
|
|
|
|
entry := &ContactEntry{}
|
2023-05-10 16:56:07 +00:00
|
|
|
if err := rows.Scan(&entry.Slug, &entry.Name, &entry.Email, &entry.Phone, &entry.Tags); err != nil {
|
2023-01-29 14:14:31 +00:00
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
entries = append(entries, entry)
|
|
|
|
}
|
|
|
|
if rows.Err() != nil {
|
|
|
|
panic(rows.Err())
|
|
|
|
}
|
|
|
|
|
|
|
|
return entries
|
|
|
|
}
|
|
|
|
|
2023-02-01 13:34:40 +00:00
|
|
|
type contactForm struct {
|
|
|
|
locale *Locale
|
|
|
|
BusinessName *InputField
|
|
|
|
VATIN *InputField
|
|
|
|
TradeName *InputField
|
|
|
|
Phone *InputField
|
|
|
|
Email *InputField
|
|
|
|
Web *InputField
|
|
|
|
Address *InputField
|
|
|
|
City *InputField
|
|
|
|
Province *InputField
|
|
|
|
PostalCode *InputField
|
|
|
|
Country *SelectField
|
Add tags for contacts too
With Oriol we agreed that contacts should have tags, too, and that the
“tag pool”, as it were, should be shared with the one for invoices (and
all future tags we might add).
I added the contact_tag relation and tag_contact function, just like
with invoices, and then realized that the SQL queries that Go had to
execute were becoming “complex” enough: i had to get not only the slug,
but the contact id to call tag_contact, and all inside a transaction.
Therefore, i opted to create the add_contact and edit_contact functions,
that mirror those for invoice and products, so now each “major” section
has these functions. They also simplified a bit the handling of the
VATIN and phone numbers, because it is now encapsuled inside the
PL/pgSQL function and Go does not know how to assemble the parts.
2023-03-26 00:32:53 +00:00
|
|
|
Tags *TagsField
|
2023-02-01 13:34:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newContactForm(ctx context.Context, conn *Conn, locale *Locale) *contactForm {
|
|
|
|
return &contactForm{
|
|
|
|
locale: locale,
|
|
|
|
BusinessName: &InputField{
|
|
|
|
Name: "business_name",
|
|
|
|
Label: pgettext("input", "Business name", locale),
|
|
|
|
Type: "text",
|
|
|
|
Required: true,
|
|
|
|
Attributes: []template.HTMLAttr{
|
|
|
|
`autocomplete="organization"`,
|
2023-04-18 19:01:29 +00:00
|
|
|
`minlength="2"`,
|
2023-02-01 13:34:40 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
VATIN: &InputField{
|
|
|
|
Name: "vatin",
|
|
|
|
Label: pgettext("input", "VAT number", locale),
|
|
|
|
Type: "text",
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
TradeName: &InputField{
|
|
|
|
Name: "trade_name",
|
|
|
|
Label: pgettext("input", "Trade name", locale),
|
|
|
|
Type: "text",
|
|
|
|
},
|
|
|
|
Phone: &InputField{
|
|
|
|
Name: "phone",
|
|
|
|
Label: pgettext("input", "Phone", locale),
|
|
|
|
Type: "tel",
|
|
|
|
Required: true,
|
|
|
|
Attributes: []template.HTMLAttr{
|
|
|
|
`autocomplete="tel"`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Email: &InputField{
|
|
|
|
Name: "email",
|
|
|
|
Label: pgettext("input", "Email", locale),
|
|
|
|
Type: "email",
|
|
|
|
Required: true,
|
|
|
|
Attributes: []template.HTMLAttr{
|
|
|
|
`autocomplete="email"`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Web: &InputField{
|
|
|
|
Name: "web",
|
|
|
|
Label: pgettext("input", "Web", locale),
|
|
|
|
Type: "url",
|
|
|
|
Attributes: []template.HTMLAttr{
|
|
|
|
`autocomplete="url"`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Address: &InputField{
|
|
|
|
Name: "address",
|
|
|
|
Label: pgettext("input", "Address", locale),
|
|
|
|
Type: "text",
|
|
|
|
Required: true,
|
|
|
|
Attributes: []template.HTMLAttr{
|
|
|
|
`autocomplete="address-line1"`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
City: &InputField{
|
|
|
|
Name: "city",
|
|
|
|
Label: pgettext("input", "City", locale),
|
|
|
|
Type: "text",
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
Province: &InputField{
|
|
|
|
Name: "province",
|
|
|
|
Label: pgettext("input", "Province", locale),
|
|
|
|
Type: "text",
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
PostalCode: &InputField{
|
|
|
|
Name: "postal_code",
|
|
|
|
Label: pgettext("input", "Postal code", locale),
|
|
|
|
Type: "text",
|
|
|
|
Required: true,
|
|
|
|
Attributes: []template.HTMLAttr{
|
|
|
|
`autocomplete="postal-code"`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Country: &SelectField{
|
|
|
|
Name: "country",
|
2023-02-08 12:47:36 +00:00
|
|
|
Label: pgettext("input", "Country", locale),
|
2023-02-01 13:34:40 +00:00
|
|
|
Options: mustGetCountryOptions(ctx, conn, locale),
|
2023-02-05 13:06:33 +00:00
|
|
|
Required: true,
|
2023-02-08 12:47:36 +00:00
|
|
|
Selected: []string{"ES"},
|
2023-02-01 13:34:40 +00:00
|
|
|
Attributes: []template.HTMLAttr{
|
|
|
|
`autocomplete="country"`,
|
|
|
|
},
|
|
|
|
},
|
Add tags for contacts too
With Oriol we agreed that contacts should have tags, too, and that the
“tag pool”, as it were, should be shared with the one for invoices (and
all future tags we might add).
I added the contact_tag relation and tag_contact function, just like
with invoices, and then realized that the SQL queries that Go had to
execute were becoming “complex” enough: i had to get not only the slug,
but the contact id to call tag_contact, and all inside a transaction.
Therefore, i opted to create the add_contact and edit_contact functions,
that mirror those for invoice and products, so now each “major” section
has these functions. They also simplified a bit the handling of the
VATIN and phone numbers, because it is now encapsuled inside the
PL/pgSQL function and Go does not know how to assemble the parts.
2023-03-26 00:32:53 +00:00
|
|
|
Tags: &TagsField{
|
|
|
|
Name: "tags",
|
|
|
|
Label: pgettext("input", "Tags", locale),
|
|
|
|
},
|
2023-02-01 13:34:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (form *contactForm) Parse(r *http.Request) error {
|
|
|
|
if err := r.ParseForm(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
form.BusinessName.FillValue(r)
|
|
|
|
form.VATIN.FillValue(r)
|
|
|
|
form.TradeName.FillValue(r)
|
|
|
|
form.Phone.FillValue(r)
|
|
|
|
form.Email.FillValue(r)
|
|
|
|
form.Web.FillValue(r)
|
|
|
|
form.Address.FillValue(r)
|
|
|
|
form.City.FillValue(r)
|
|
|
|
form.Province.FillValue(r)
|
|
|
|
form.PostalCode.FillValue(r)
|
|
|
|
form.Country.FillValue(r)
|
Add tags for contacts too
With Oriol we agreed that contacts should have tags, too, and that the
“tag pool”, as it were, should be shared with the one for invoices (and
all future tags we might add).
I added the contact_tag relation and tag_contact function, just like
with invoices, and then realized that the SQL queries that Go had to
execute were becoming “complex” enough: i had to get not only the slug,
but the contact id to call tag_contact, and all inside a transaction.
Therefore, i opted to create the add_contact and edit_contact functions,
that mirror those for invoice and products, so now each “major” section
has these functions. They also simplified a bit the handling of the
VATIN and phone numbers, because it is now encapsuled inside the
PL/pgSQL function and Go does not know how to assemble the parts.
2023-03-26 00:32:53 +00:00
|
|
|
form.Tags.FillValue(r)
|
2023-02-01 13:34:40 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (form *contactForm) Validate(ctx context.Context, conn *Conn) bool {
|
|
|
|
validator := newFormValidator()
|
2023-02-08 12:47:36 +00:00
|
|
|
|
|
|
|
country := ""
|
|
|
|
if validator.CheckValidSelectOption(form.Country, gettext("Selected country is not valid.", form.locale)) {
|
|
|
|
country = form.Country.Selected[0]
|
|
|
|
}
|
|
|
|
|
2023-02-01 13:34:40 +00:00
|
|
|
validator.CheckRequiredInput(form.BusinessName, gettext("Business name can not be empty.", form.locale))
|
2023-04-18 19:01:29 +00:00
|
|
|
validator.CheckInputMinLength(form.BusinessName, 2, gettext("Business name must have at least two letters.", form.locale))
|
2023-02-01 13:34:40 +00:00
|
|
|
if validator.CheckRequiredInput(form.VATIN, gettext("VAT number can not be empty.", form.locale)) {
|
2023-02-08 12:47:36 +00:00
|
|
|
validator.CheckValidVATINInput(form.VATIN, country, gettext("This value is not a valid VAT number.", form.locale))
|
2023-02-01 13:34:40 +00:00
|
|
|
}
|
|
|
|
if validator.CheckRequiredInput(form.Phone, gettext("Phone can not be empty.", form.locale)) {
|
2023-02-08 12:47:36 +00:00
|
|
|
validator.CheckValidPhoneInput(form.Phone, country, gettext("This value is not a valid phone number.", form.locale))
|
2023-02-01 13:34:40 +00:00
|
|
|
}
|
|
|
|
if validator.CheckRequiredInput(form.Email, gettext("Email can not be empty.", form.locale)) {
|
|
|
|
validator.CheckValidEmailInput(form.Email, gettext("This value is not a valid email. It should be like name@domain.com.", form.locale))
|
|
|
|
}
|
|
|
|
if form.Web.Val != "" {
|
2023-02-12 20:01:20 +00:00
|
|
|
validator.CheckValidURL(form.Web, gettext("This value is not a valid web address. It should be like https://domain.com/.", form.locale))
|
2023-02-01 13:34:40 +00:00
|
|
|
}
|
|
|
|
validator.CheckRequiredInput(form.Address, gettext("Address can not be empty.", form.locale))
|
|
|
|
validator.CheckRequiredInput(form.City, gettext("City can not be empty.", form.locale))
|
|
|
|
validator.CheckRequiredInput(form.Province, gettext("Province can not be empty.", form.locale))
|
|
|
|
if validator.CheckRequiredInput(form.PostalCode, gettext("Postal code can not be empty.", form.locale)) {
|
2023-02-08 12:47:36 +00:00
|
|
|
validator.CheckValidPostalCode(ctx, conn, form.PostalCode, country, gettext("This value is not a valid postal code.", form.locale))
|
2023-02-01 13:34:40 +00:00
|
|
|
}
|
|
|
|
return validator.AllOK()
|
2023-01-29 14:14:31 +00:00
|
|
|
}
|
Add tags for contacts too
With Oriol we agreed that contacts should have tags, too, and that the
“tag pool”, as it were, should be shared with the one for invoices (and
all future tags we might add).
I added the contact_tag relation and tag_contact function, just like
with invoices, and then realized that the SQL queries that Go had to
execute were becoming “complex” enough: i had to get not only the slug,
but the contact id to call tag_contact, and all inside a transaction.
Therefore, i opted to create the add_contact and edit_contact functions,
that mirror those for invoice and products, so now each “major” section
has these functions. They also simplified a bit the handling of the
VATIN and phone numbers, because it is now encapsuled inside the
PL/pgSQL function and Go does not know how to assemble the parts.
2023-03-26 00:32:53 +00:00
|
|
|
|
|
|
|
func (form *contactForm) MustFillFromDatabase(ctx context.Context, conn *Conn, slug string) bool {
|
|
|
|
return !notFoundErrorOrPanic(conn.QueryRow(ctx, `
|
|
|
|
select business_name
|
|
|
|
, substr(vatin::text, 3)
|
|
|
|
, trade_name
|
|
|
|
, phone
|
|
|
|
, email
|
|
|
|
, web
|
|
|
|
, address
|
|
|
|
, city
|
|
|
|
, province
|
|
|
|
, postal_code
|
|
|
|
, country_code
|
Replace tag relations with array attributes
It all started when i wanted to try to filter invoices by multiple tags
using an “AND”, instead of “OR” as it was doing until now. But
something felt off and seemed to me that i was doing thing much more
complex than needed, all to be able to list the tags as a suggestion
in the input field—which i am not doing yet.
I found this article series[0] exploring different approaches for
tagging, which includes the one i was using, and comparing their
performance. I have not actually tested it, but it seems that i have
chosen the worst option, in both query time and storage.
I attempted to try using an array attribute to each table, which is more
or less the same they did in the articles but without using a separate
relation for tags, and i found out that all the queries were way easier
to write, and needed two joins less, so it was a no-brainer.
[0]: http://www.databasesoup.com/2015/01/tag-all-things.html
2023-04-07 19:31:35 +00:00
|
|
|
, array_to_string(tags, ',')
|
Add tags for contacts too
With Oriol we agreed that contacts should have tags, too, and that the
“tag pool”, as it were, should be shared with the one for invoices (and
all future tags we might add).
I added the contact_tag relation and tag_contact function, just like
with invoices, and then realized that the SQL queries that Go had to
execute were becoming “complex” enough: i had to get not only the slug,
but the contact id to call tag_contact, and all inside a transaction.
Therefore, i opted to create the add_contact and edit_contact functions,
that mirror those for invoice and products, so now each “major” section
has these functions. They also simplified a bit the handling of the
VATIN and phone numbers, because it is now encapsuled inside the
PL/pgSQL function and Go does not know how to assemble the parts.
2023-03-26 00:32:53 +00:00
|
|
|
from contact
|
|
|
|
where slug = $1
|
|
|
|
`, slug).Scan(
|
|
|
|
form.BusinessName,
|
|
|
|
form.VATIN,
|
|
|
|
form.TradeName,
|
|
|
|
form.Phone,
|
|
|
|
form.Email,
|
|
|
|
form.Web,
|
|
|
|
form.Address,
|
|
|
|
form.City,
|
|
|
|
form.Province,
|
|
|
|
form.PostalCode,
|
|
|
|
form.Country,
|
|
|
|
form.Tags))
|
|
|
|
}
|
2023-05-12 09:32:39 +00:00
|
|
|
|
|
|
|
func ServeEditContactTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
|
|
|
conn := getConn(r)
|
|
|
|
locale := getLocale(r)
|
|
|
|
company := getCompany(r)
|
|
|
|
slug := params[0].Value
|
|
|
|
form := newTagsForm(companyURI(company, "/contacts/"+slug+"/tags"), slug, locale)
|
|
|
|
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), `select array_to_string(tags, ',') from contact where slug = $1`, form.Slug).Scan(&form.Tags)) {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
mustRenderStandaloneTemplate(w, r, "tags/edit.gohtml", form)
|
|
|
|
}
|
|
|
|
|
|
|
|
func HandleUpdateContactTags(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
|
|
|
locale := getLocale(r)
|
|
|
|
conn := getConn(r)
|
|
|
|
company := getCompany(r)
|
|
|
|
slug := params[0].Value
|
|
|
|
form := newTagsForm(companyURI(company, "/contacts/"+slug+"/tags/edit"), slug, locale)
|
|
|
|
if err := form.Parse(r); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := verifyCsrfTokenValid(r); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if conn.MustGetText(r.Context(), "", "update contact set tags = $1 where slug = $2 returning slug", form.Tags, form.Slug) == "" {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
}
|
|
|
|
mustRenderStandaloneTemplate(w, r, "tags/view.gohtml", form)
|
|
|
|
}
|