additional work on Import User Accounts (unfinished)

This commit is contained in:
2026-02-27 15:59:01 -07:00
parent 086e62c4b7
commit 5794cb8a10
5 changed files with 106 additions and 7 deletions
+6
View File
@@ -655,6 +655,9 @@ func AmGetCommunityTx(ctx context.Context, tx *sqlx.Tx, id int32) (*Community, e
func AmGetCommunityByAlias(ctx context.Context, alias string) (*Community, error) { func AmGetCommunityByAlias(ctx context.Context, alias string) (*Community, error) {
var cid int32 var cid int32
if err := amdb.GetContext(ctx, &cid, "SELECT commid FROM communities WHERE alias = ?", alias); err != nil { if err := amdb.GetContext(ctx, &cid, "SELECT commid FROM communities WHERE alias = ?", alias); err != nil {
if err == sql.ErrNoRows {
err = ErrNoCommunity
}
return nil, err return nil, err
} }
return AmGetCommunity(ctx, cid) return AmGetCommunity(ctx, cid)
@@ -672,6 +675,9 @@ func AmGetCommunityByAlias(ctx context.Context, alias string) (*Community, error
func AmGetCommunityByAliasTx(ctx context.Context, tx *sqlx.Tx, alias string) (*Community, error) { func AmGetCommunityByAliasTx(ctx context.Context, tx *sqlx.Tx, alias string) (*Community, error) {
var cid int32 var cid int32
if err := tx.GetContext(ctx, &cid, "SELECT commid FROM communities WHERE alias = ?", alias); err != nil { if err := tx.GetContext(ctx, &cid, "SELECT commid FROM communities WHERE alias = ?", alias); err != nil {
if err == sql.ErrNoRows {
err = ErrNoCommunity
}
return nil, err return nil, err
} }
return AmGetCommunityTx(ctx, tx, cid) return AmGetCommunityTx(ctx, tx, cid)
+19 -4
View File
@@ -35,6 +35,9 @@ import (
// ErrNoUser is an error returned if the user is not found in the database. // ErrNoUser is an error returned if the user is not found in the database.
var ErrNoUser error = errors.New("no such user") var ErrNoUser error = errors.New("no such user")
// ErrUserExists is an error returned if the user name already exists when trying to create a user.
var ErrUserExists error = errors.New("that user name already exists. Please try again")
// UserPrefs represents the user's preferences in a table (one row per user). // UserPrefs represents the user's preferences in a table (one row per user).
type UserPrefs struct { type UserPrefs struct {
Uid int32 `db:"uid"` // user ID Uid int32 `db:"uid"` // user ID
@@ -63,14 +66,15 @@ func (p *UserPrefs) Save(ctx context.Context, u, setter *User, ipaddr string) er
if u != nil && u.Uid != p.Uid { if u != nil && u.Uid != p.Uid {
return errors.New("internal mismatch of IDs") return errors.New("internal mismatch of IDs")
} }
var old *UserPrefs var old *UserPrefs = nil
if setter.Uid != u.Uid { if setter.Uid != u.Uid {
var pref UserPrefs var pref UserPrefs
err := amdb.GetContext(ctx, &pref, "SELECT * FROM userprefs WHERE uid = ?", u.Uid) err := amdb.GetContext(ctx, &pref, "SELECT * FROM userprefs WHERE uid = ?", u.Uid)
if err != nil { if err == nil {
old = &pref
} else if err != sql.ErrNoRows {
return err return err
} }
old = &pref
} }
_, err := amdb.NamedExecContext(ctx, "UPDATE userprefs SET localeid = :localeid, tzid = :tzid WHERE uid = :uid", p) _, err := amdb.NamedExecContext(ctx, "UPDATE userprefs SET localeid = :localeid, tzid = :tzid WHERE uid = :uid", p)
if err == nil && u != nil { if err == nil && u != nil {
@@ -444,6 +448,17 @@ func (u *User) SetSecurityData(ctx context.Context, baseLevel uint16, lockout, v
return err return err
} }
// SetHashedPassword sets the hashed password for the user. Should only be used by import.
func (u *User) SetHashedPassword(ctx context.Context, hashValue string) error {
u.Mutex.Lock()
defer u.Mutex.Unlock()
_, err := amdb.ExecContext(ctx, "UPDATE users SET passhash = ? WHERE uid = ?", hashValue, u.Uid)
if err != nil {
u.Passhash = hashValue
}
return err
}
/* AmGetUser returns a reference to the specified user. /* AmGetUser returns a reference to the specified user.
* Parameters: * Parameters:
* ctx - Standard Go context value. * ctx - Standard Go context value.
@@ -736,7 +751,7 @@ func AmCreateNewUser(ctx context.Context, username string, password string, remi
err := tx.GetContext(ctx, &tmpuid, "SELECT uid FROM users WHERE username = ?", username) err := tx.GetContext(ctx, &tmpuid, "SELECT uid FROM users WHERE username = ?", username)
if err == nil { if err == nil {
log.Warnf("username \"%s\" already exists", username) log.Warnf("username \"%s\" already exists", username)
return nil, errors.New("that user name already exists. Please try again") return nil, ErrUserExists
} else if err != sql.ErrNoRows { } else if err != sql.ErrNoRows {
return nil, err return nil, err
} }
+1 -1
View File
@@ -282,7 +282,7 @@ func VCardFromContactInfo(ctx context.Context, target *VCard, ci *database.Conta
return nil return nil
} }
// VCardSetContactINfo fills the ContactInfo object with data from the VCard. // VCardSetContactInfo fills the ContactInfo object with data from the VCard.
func VCardSetContactInfo(ci *database.ContactInfo, vc *VCard) { func VCardSetContactInfo(ci *database.ContactInfo, vc *VCard) {
ci.GivenName = util.IIF(vc.Name.Given == "", nil, &vc.Name.Given) ci.GivenName = util.IIF(vc.Name.Given == "", nil, &vc.Name.Given)
ci.FamilyName = util.IIF(vc.Name.Family == "", nil, &vc.Name.Family) ci.FamilyName = util.IIF(vc.Name.Family == "", nil, &vc.Name.Family)
+71 -1
View File
@@ -198,7 +198,9 @@ func VIUStreamCommunityMemberList(ctx context.Context, w io.Writer, comm *databa
return err return err
} }
// VIUCreateUser creates a new user based on the import data.
func VIUCreateUser(ctx context.Context, udata *VIUUser, loader *database.User, ipaddr string) error { func VIUCreateUser(ctx context.Context, udata *VIUUser, loader *database.User, ipaddr string) error {
// Do some initial error checking and pre-parsing.
if !database.AmIsValidAmsterdamID(udata.Username) { if !database.AmIsValidAmsterdamID(udata.Username) {
return fmt.Errorf("the username \"%s\" is not a valid Amsterdam ID", udata.Username) return fmt.Errorf("the username \"%s\" is not a valid Amsterdam ID", udata.Username)
} }
@@ -220,11 +222,32 @@ func VIUCreateUser(ctx context.Context, udata *VIUUser, loader *database.User, i
if udata.Password.Prehashed { if udata.Password.Prehashed {
pwd = "" pwd = ""
} }
role := database.AmRole(udata.Options.Role)
if role == nil {
return fmt.Errorf("the security role \"%s\" is not found", udata.Options.Role)
}
// Create the user and set its direct information.
user, err := database.AmCreateNewUser(ctx, udata.Username, pwd, udata.PasswordReminder, dob, ipaddr) user, err := database.AmCreateNewUser(ctx, udata.Username, pwd, udata.PasswordReminder, dob, ipaddr)
if err != nil { if err != nil {
return err return err
} }
err = user.SetProfileData(ctx, udata.PasswordReminder, dob, util.IIF(udata.Description == "", nil, &udata.Description), loader, ipaddr)
if err != nil {
return err
}
err = user.SetSecurityData(ctx, role.Level(), udata.Options.Locked, udata.Options.Confirmed, loader, ipaddr)
if err != nil {
return err
}
if udata.Password.Prehashed {
err = user.SetHashedPassword(ctx, udata.Password.Hash)
if err != nil {
return err
}
}
// Set the contact info.
ci := database.AmNewUserContactInfo(user.Uid) ci := database.AmNewUserContactInfo(user.Uid)
VCardSetContactInfo(ci, &(udata.VCard)) VCardSetContactInfo(ci, &(udata.VCard))
ci.PrivateAddr = udata.Options.HideAddr ci.PrivateAddr = udata.Options.HideAddr
@@ -239,10 +262,57 @@ func VIUCreateUser(ctx context.Context, udata *VIUUser, loader *database.User, i
if err != nil { if err != nil {
return err return err
} }
// TODO
// Set the user preferences and flags.
prefs := database.UserPrefs{
Uid: user.Uid,
LocaleID: udata.Options.Locale,
TimeZoneID: udata.Options.ZoneHint,
}
err = prefs.Save(ctx, user, loader, ipaddr)
if err != nil {
return err
}
flags := util.NewOptionSet()
flags.Set(database.UserFlagDisallowSetPhoto, udata.Options.NoPhoto)
flags.Set(database.UserFlagMassMailOptOut, udata.Options.OptOut)
flags.Set(database.UserFlagPicturesInPosts, udata.Options.PostPictures)
err = user.SaveFlags(ctx, flags)
if err != nil {
return err
}
// Autojoin base communities.
if udata.Options.AutoJoin {
err := database.AmAutoJoinCommunities(ctx, user)
if err != nil {
return err
}
}
// Set community membership based on the joins listed.
for _, j := range udata.Joins {
role = database.AmRole(j.Role)
if role == nil {
return fmt.Errorf("the security role \"%s\" is not found", udata.Options.Role)
}
comm, err := database.AmGetCommunityByAlias(ctx, j.Community)
if err == database.ErrNoCommunity {
log.Warnf("community \"%s\" not found, skipping", j.Community)
continue
} else if err != nil {
return err
}
err = comm.SetMembership(ctx, user, role.Level(), false, loader.Uid, ipaddr)
if err != nil {
return err
}
}
return nil return nil
} }
// VIUImportUserList takes a list of user accounts in VIU XML format and imports them.
func VIUImportUserList(ctx context.Context, r io.Reader, loader *database.User, ipaddr string) (int, []string, error) { func VIUImportUserList(ctx context.Context, r io.Reader, loader *database.User, ipaddr string) (int, []string, error) {
dec := xml.NewDecoder(r) dec := xml.NewDecoder(r)
var importData VIUBase var importData VIUBase
+9 -1
View File
@@ -21,6 +21,7 @@ import (
"time" "time"
"git.erbosoft.com/amy/amsterdam/database" "git.erbosoft.com/amy/amsterdam/database"
"git.erbosoft.com/amy/amsterdam/exports"
"git.erbosoft.com/amy/amsterdam/ui" "git.erbosoft.com/amy/amsterdam/ui"
"git.erbosoft.com/amy/amsterdam/util" "git.erbosoft.com/amy/amsterdam/util"
"github.com/CloudyKit/jet/v6" "github.com/CloudyKit/jet/v6"
@@ -792,8 +793,15 @@ func UserImport(ctxt ui.AmContext) (string, any) {
ctxt.SetFrameTitle("Import User Accounts") ctxt.SetFrameTitle("Import User Accounts")
return "framed", "import_users.jet" return "framed", "import_users.jet"
} }
count, scroll, err := exports.VIUImportUserList(ctxt.Ctx(), f, ctxt.CurrentUser(), ctxt.RemoteIP())
f.Close() f.Close()
if err != nil {
ctxt.VarMap().Set("errorMessage", err.Error())
ctxt.SetFrameTitle("Import User Accounts")
return "framed", "import_users.jet"
}
_ = count
_ = scroll
return "error", "Not yet implemented" return "error", "Not yet implemented"
} }