put together the new account path

This commit is contained in:
2025-10-05 22:49:50 -06:00
parent 8ad88c4957
commit 347e96d81d
4 changed files with 209 additions and 38 deletions
+140 -27
View File
@@ -10,6 +10,7 @@
package database package database
import ( import (
"errors"
"fmt" "fmt"
"sync" "sync"
"time" "time"
@@ -19,32 +20,132 @@ import (
// ContactInfo stores the contact information for a user or community. // ContactInfo stores the contact information for a user or community.
type ContactInfo struct { type ContactInfo struct {
ContactId int32 `db:"contactid"` Mutex sync.Mutex
GivenName string `db:"given_name"` ContactId int32 `db:"contactid"`
FamilyName string `db:"family_name"` GivenName string `db:"given_name"`
MiddleInit string `db:"middle_init"` FamilyName string `db:"family_name"`
Prefix *string `db:"prefix"` MiddleInit string `db:"middle_init"`
Suffix *string `db:"suffix"` Prefix *string `db:"prefix"`
Company *string `db:"company"` Suffix *string `db:"suffix"`
Addr1 *string `db:"addr1"` Company *string `db:"company"`
Addr2 *string `db:"addr2"` Addr1 *string `db:"addr1"`
Locality *string `db:"locality"` Addr2 *string `db:"addr2"`
Region *string `db:"region"` Locality *string `db:"locality"`
PostalCode *string `db:"pcode"` Region *string `db:"region"`
Country *string `db:"country"` PostalCode *string `db:"pcode"`
Phone *string `db:"phone"` Country *string `db:"country"`
Fax *string `db:"fax"` Phone *string `db:"phone"`
Mobile *string `db:"mobile"` Fax *string `db:"fax"`
Email *string `db:"email"` Mobile *string `db:"mobile"`
PrivateAddr bool `db:"pvt_addr"` Email *string `db:"email"`
PrivatePhone bool `db:"pvt_phone"` PrivateAddr bool `db:"pvt_addr"`
PrivateFax bool `db:"pvt_fax"` PrivatePhone bool `db:"pvt_phone"`
PrivateEmail bool `db:"pvt_email"` PrivateFax bool `db:"pvt_fax"`
OwnerUid int32 `db:"owner_uid"` PrivateEmail bool `db:"pvt_email"`
OwnerCommId int32 `db:"owner_commid"` OwnerUid int32 `db:"owner_uid"`
PhotoURL *string `db:"photo_url"` OwnerCommId int32 `db:"owner_commid"`
URL *string `db:"url"` PhotoURL *string `db:"photo_url"`
LastUpdate *time.Time URL *string `db:"url"`
LastUpdate *time.Time `db:"lastupdate"`
}
// lookupCommunityContact looks up the ID of a contact for a community.
func lookupCommunityContact(id int32) (int32, error) {
var rc int32 = -1
rs, err := amdb.Query("SELECT contactid FROM contacts WHERE onwer_commid = ?", id)
if err == nil {
if rs.Next() {
rs.Scan(&rc)
}
}
return rc, err
}
// lookupUserContact looks up the ID of a contact for a user.
func lookupUserContact(uid int32) (int32, error) {
var rc int32 = -1
rs, err := amdb.Query("SELECT contactid FROM contacts WHERE owner_uid = ? AND owner_commid = -1", uid)
if err == nil {
if rs.Next() {
rs.Scan(&rc)
}
}
return rc, err
}
/* Save saves the contact info to the database.
* Returns:
* true if the E-mail address on this account has been changed, false if not.
* Standard Go error status.
*/
func (ci *ContactInfo) Save() (bool, error) {
ci.Mutex.Lock()
defer ci.Mutex.Unlock()
updateMode := false
emailChange := true
if ci.ContactId <= 0 {
var nx int32
var err error
if ci.OwnerCommId > 0 {
nx, err = lookupCommunityContact(ci.OwnerCommId)
} else {
nx, err = lookupUserContact(ci.OwnerUid)
}
if err != nil {
return false, err
}
if nx > 0 {
ci.ContactId = nx
updateMode = true
emailChange = false
}
} else {
updateMode = true
emailChange = false
}
if !emailChange {
rs, err := amdb.Query("SELECT contactid FROM contacts WHERE contactid = ? AND email = ?", ci.ContactId, ci.Email)
if err != nil {
return false, err
}
if !rs.Next() {
emailChange = true
}
}
if updateMode {
_, err := amdb.Exec(`UPDATE contacts SET given_name = ?, family_name = ?, middle_init = ?, prefix = ?, suffix = ?, company = ?,
addr1 = ?, addr2 = ?, locality = ?, region = ?, pcode = ?, country = ?, phone = ?, fax = ?, mobile = ?, email = ?,
pvt_addr = ?, pvt_phone = ?, pvt_fax = ?, pvt_email = ?, photo_url = ?, url = ?, lastupdate = NOW()
WHERE contactid = ?`, ci.GivenName, ci.FamilyName, ci.MiddleInit, ci.Prefix, ci.Suffix, ci.Company,
ci.Addr1, ci.Addr2, ci.Locality, ci.Region, ci.PostalCode, ci.Country, ci.Phone, ci.Fax, ci.Mobile, ci.Email,
ci.PrivateAddr, ci.PrivatePhone, ci.PrivateFax, ci.PrivateEmail, ci.PhotoURL, ci.URL, ci.ContactId)
if err != nil {
return false, err
}
} else {
res, err := amdb.Exec(`INSERT INTO contacts (given_name, family_name, middle_init, prefix, suffix, company, addr1,
addr2, locality, region, pcode, country, phone, fax, mobile, email, pvt_addr, pvt_phone, pvt_fax,
pvt_email, owner_uid, owner_commid, photo_url, url, lastupdate)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())`,
ci.GivenName, ci.FamilyName, ci.MiddleInit, ci.Prefix, ci.Suffix, ci.Company, ci.Addr1, ci.Addr2,
ci.Locality, ci.Region, ci.PostalCode, ci.Country, ci.Phone, ci.Fax, ci.Mobile, ci.Email,
ci.PrivateAddr, ci.PrivatePhone, ci.PrivateFax, ci.PrivateEmail, ci.OwnerUid, ci.OwnerCommId, ci.PhotoURL, ci.URL)
if err != nil {
return false, err
}
lii, _ := res.LastInsertId()
ci.ContactId = int32(lii)
contactCache.Add(ci.ContactId, ci)
}
rs, err := amdb.Query("SELECT lastupdate FROM contacts WHERE contactid = ?", ci.ContactId)
if err != nil {
return false, err
}
if !rs.Next() {
return false, errors.New("internal error rereading update timestamp")
}
rs.Scan(&ci.LastUpdate)
return emailChange, nil
} }
// contactCache is the cache for ContactInfo objects. // contactCache is the cache for ContactInfo objects.
@@ -82,7 +183,8 @@ func internalContactInfo(id int32) (*ContactInfo, error) {
* Parameters: * Parameters:
* id - The contact info ID top retrieve. * id - The contact info ID top retrieve.
* Returns: * Returns:
* * ContactInfo retrieved, or nil.
* Standard Go error status.
*/ */
func AmGetContactInfo(id int32) (*ContactInfo, error) { func AmGetContactInfo(id int32) (*ContactInfo, error) {
getContactMutex.Lock() getContactMutex.Lock()
@@ -100,3 +202,14 @@ func AmGetContactInfo(id int32) (*ContactInfo, error) {
} }
return nil, err return nil, err
} }
/* AmNewUserContactInfo creates a new contact info record for the user.
* Parameters:
* uid - The UID of the owner of this contact info.
* Returns:
* New ContactInfo structure.
*/
func AmNewUserContactInfo(uid int32) *ContactInfo {
rc := ContactInfo{OwnerUid: uid, OwnerCommId: -1}
return &rc
}
+12
View File
@@ -136,6 +136,18 @@ func (u *User) ConfirmEMailAddress(confnum int32, remoteIP string) error {
return err return err
} }
// NewEmailConfirmationNumber creates a new confirmation number for a user and saves it off.
func (u *User) NewEmailConfirmationNumber() error {
u.Mutex.Lock()
defer u.Mutex.Unlock()
newnum := newEmailConfirmationNumber()
_, err := amdb.Exec("UPDATE user SET email_confnum = ? WHERE uid = ?", newnum, u.Uid)
if err != nil {
u.EmailConfNum = newnum
}
return err
}
/* AmGetUser returns a reference to the specified user. /* AmGetUser returns a reference to the specified user.
* Parameters: * Parameters:
* uid - The UID of the user. * uid - The UID of the user.
+49 -11
View File
@@ -187,6 +187,20 @@ func VerifyEmailForm(ctxt ui.AmContext) (string, any, error) {
return ui.ErrorPage(ctxt, err) return ui.ErrorPage(ctxt, err)
} }
func sendEmailConfirmationEmail(user *database.User, ci *database.ContactInfo, remoteIP string) error {
if ci != nil && ci.Email != nil && *ci.Email != "" {
msg := email.AmNewEmailMessage(user.Uid, remoteIP)
msg.AddTo(*ci.Email, "")
msg.SetTemplate("verify_email.jet")
msg.AddVariable("username", user.Username)
msg.AddVariable("confnum", user.EmailConfNum)
msg.Send()
return nil
} else {
return errors.New("cannot find email address")
}
}
/* VerifyEmail handles E-mail address verification. /* VerifyEmail handles E-mail address verification.
* Parameters: * Parameters:
* ctxt - The AmContext for the request. * ctxt - The AmContext for the request.
@@ -223,15 +237,9 @@ func VerifyEMail(ctxt ui.AmContext) (string, any, error) {
var ci *database.ContactInfo var ci *database.ContactInfo
ci, err = user.ContactInfo() ci, err = user.ContactInfo()
if err == nil { if err == nil {
if ci != nil && ci.Email != nil && *ci.Email != "" { err = user.NewEmailConfirmationNumber()
msg := email.AmNewEmailMessage(user.Uid, ctxt.RemoteIP()) if err == nil {
msg.AddTo(*ci.Email, "") err = sendEmailConfirmationEmail(user, ci, ctxt.RemoteIP())
msg.SetTemplate("verify_email.jet")
msg.AddVariable("username", user.Username)
msg.AddVariable("confnum", user.EmailConfNum)
msg.Send()
} else {
err = errors.New("cannot find email address")
} }
} }
if err == nil { if err == nil {
@@ -309,6 +317,14 @@ func NewAccountForm(ctxt ui.AmContext) (string, any, error) {
return ui.ErrorPage(ctxt, err) return ui.ErrorPage(ctxt, err)
} }
/* NewAccount handles creating a new Amsterdam account.
* Parameters:
* ctxt - The AmContext for the request.
* Returns:
* Command string dictating what to be rendered.
* Data as a parameter for the command string.
* Standard Go error status.
*/
func NewAccount(ctxt ui.AmContext) (string, any, error) { func NewAccount(ctxt ui.AmContext) (string, any, error) {
// If user is already logged in, this is an error. // If user is already logged in, this is an error.
if !ctxt.CurrentUser().IsAnon { if !ctxt.CurrentUser().IsAnon {
@@ -344,8 +360,30 @@ func NewAccount(ctxt ui.AmContext) (string, any, error) {
user, err = database.AmCreateNewUser(dlg.Field("user").Value, dlg.Field("pass1").Value, user, err = database.AmCreateNewUser(dlg.Field("user").Value, dlg.Field("pass1").Value,
dlg.Field("remind").Value, dlg.Field("dob").AsDate(), ctxt.RemoteIP()) dlg.Field("remind").Value, dlg.Field("dob").AsDate(), ctxt.RemoteIP())
if err == nil { if err == nil {
// TODO: set up contact info // create and save contact info
_ = user ci := database.AmNewUserContactInfo(user.Uid)
ci.Prefix = dlg.Field("prefix").ValPtr()
ci.GivenName = dlg.Field("first").Value
ci.MiddleInit = dlg.Field("mid").Value
if ci.MiddleInit == "" {
ci.MiddleInit = " "
}
ci.FamilyName = dlg.Field("last").Value
ci.Suffix = dlg.Field("suffix").ValPtr()
ci.Locality = dlg.Field("loc").ValPtr()
ci.Region = dlg.Field("reg").ValPtr()
ci.PostalCode = dlg.Field("pcode").ValPtr()
ci.Country = dlg.Field("country").ValPtr()
ci.Email = dlg.Field("email").ValPtr()
_, err = ci.Save()
if err == nil {
err = sendEmailConfirmationEmail(user, ci, ctxt.RemoteIP())
}
if err == nil {
// user is now logged in! redirect to E-mail verification
ctxt.ReplaceUser(user)
return "redirect", "/verify?tgt=" + url.PathEscape(target), nil
}
} }
} }
} }
+8
View File
@@ -119,6 +119,14 @@ func (fld *DialogItem) AsDate() *time.Time {
return nil return nil
} }
// ValPtr returns the value of a field as a string pointer, or nil if the field is empty.
func (fld *DialogItem) ValPtr() *string {
if fld.Value == "" {
return nil
}
return &fld.Value
}
/* Field returns a pointer to a dialog's field, given its name. /* Field returns a pointer to a dialog's field, given its name.
* Parameters: * Parameters:
* name - The name of the field to find. * name - The name of the field to find.