feat: assign IM user account numbers
This commit is contained in:
+54
-13
@@ -9,6 +9,7 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -762,19 +763,21 @@ func (a *adapter) CreateDb(reset bool) error {
|
||||
|
||||
if _, err = tx.Exec(
|
||||
`CREATE TABLE im_users(
|
||||
id BIGINT NOT NULL,
|
||||
username VARCHAR(32) NOT NULL,
|
||||
createdat DATETIME(3) NOT NULL,
|
||||
updatedat DATETIME(3) NOT NULL,
|
||||
state SMALLINT NOT NULL DEFAULT 0,
|
||||
stateat DATETIME(3),
|
||||
access JSON,
|
||||
lastseen DATETIME,
|
||||
useragent VARCHAR(255) DEFAULT '',
|
||||
phone VARCHAR(32),
|
||||
public JSON,
|
||||
trusted JSON,
|
||||
id BIGINT NOT NULL,
|
||||
account_no INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
username VARCHAR(32) NOT NULL,
|
||||
createdat DATETIME(3) NOT NULL,
|
||||
updatedat DATETIME(3) NOT NULL,
|
||||
state SMALLINT NOT NULL DEFAULT 0,
|
||||
stateat DATETIME(3),
|
||||
access JSON,
|
||||
lastseen DATETIME,
|
||||
useragent VARCHAR(255) DEFAULT '',
|
||||
phone VARCHAR(32),
|
||||
public JSON,
|
||||
trusted JSON,
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_users_account_no(account_no),
|
||||
UNIQUE INDEX users_username(username),
|
||||
UNIQUE INDEX users_phone(phone),
|
||||
INDEX users_state_stateat(state, stateat),
|
||||
@@ -2230,8 +2233,18 @@ func (a *adapter) UserCreate(user *t.User) error {
|
||||
user.Phone = canonicalPhone
|
||||
phone = canonicalPhone
|
||||
}
|
||||
if _, err = tx.Exec("INSERT INTO im_users(id,username,createdat,updatedat,state,access,phone,public,trusted) VALUES(?,?,?,?,?,?,?,?,?)",
|
||||
releaseAccountNoLock, err := lockUserAccountNoSequence(ctx, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer releaseAccountNoLock()
|
||||
accountNo, err := nextUserAccountNo(ctx, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = tx.Exec("INSERT INTO im_users(id,account_no,username,createdat,updatedat,state,access,phone,public,trusted) VALUES(?,?,?,?,?,?,?,?,?,?)",
|
||||
decoded_uid,
|
||||
accountNo,
|
||||
user.Username,
|
||||
user.CreatedAt,
|
||||
user.UpdatedAt,
|
||||
@@ -2260,6 +2273,34 @@ func (a *adapter) UserCreate(user *t.User) error {
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func lockUserAccountNoSequence(ctx context.Context, tx *sqlx.Tx) (func(), error) {
|
||||
var locked sql.NullInt64
|
||||
if err := tx.QueryRowContext(ctx, "SELECT GET_LOCK(CONCAT(DATABASE(), ':im_users_account_no'), 10)").Scan(&locked); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !locked.Valid || locked.Int64 != 1 {
|
||||
return nil, fmt.Errorf("lock user account number sequence timeout")
|
||||
}
|
||||
return func() {
|
||||
_, _ = tx.ExecContext(ctx, "SELECT RELEASE_LOCK(CONCAT(DATABASE(), ':im_users_account_no'))")
|
||||
}, nil
|
||||
}
|
||||
|
||||
func nextUserAccountNo(ctx context.Context, tx *sqlx.Tx) (int64, error) {
|
||||
var current sql.NullInt64
|
||||
err := tx.QueryRowContext(ctx, "SELECT account_no FROM im_users ORDER BY account_no DESC LIMIT 1 FOR UPDATE").Scan(¤t)
|
||||
if err == sql.ErrNoRows {
|
||||
return 1000, nil
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !current.Valid || current.Int64 < 999 {
|
||||
return 1000, nil
|
||||
}
|
||||
return current.Int64 + 1, nil
|
||||
}
|
||||
|
||||
// TenantAuthAddRecord adds the user's Basic authentication record.
|
||||
func (a *adapter) TenantAuthAddRecord(tenantID t.TenantID, uid t.Uid,
|
||||
authLvl auth.Level, secret []byte, expires time.Time) error {
|
||||
|
||||
@@ -289,8 +289,8 @@ func TestTenantAuthIsolation(t *testing.T) {
|
||||
userBID := userAID + 1
|
||||
for index, userID := range []int64{userAID, userBID} {
|
||||
tenantDB := quoteIdent(dbNames[index])
|
||||
if _, err := db.Exec("INSERT INTO "+tenantDB+".im_users(id,username,createdat,updatedat,state,access,phone,public,trusted) "+
|
||||
"VALUES(?,?,?,?,?,?,?,?,?)", userID, "alice", now, now, types.StateOK, `{}`, "13800000000", nil, nil); err != nil {
|
||||
if _, err := db.Exec("INSERT INTO "+tenantDB+".im_users(id,account_no,username,createdat,updatedat,state,access,phone,public,trusted) "+
|
||||
"VALUES(?,?,?,?,?,?,?,?,?,?)", userID, 1000+index, "alice", now, now, types.StateOK, `{}`, "13800000000", nil, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Exec("INSERT INTO "+tenantDB+".im_auth(userid,authlvl,secret) VALUES(?,?,?)",
|
||||
@@ -360,9 +360,9 @@ func TestTenantUserSearchMatchesUsernamePhoneAndPublicAccount(t *testing.T) {
|
||||
{targetID, "potter", "+8615500000002", `{"fn":"Potter"}`},
|
||||
} {
|
||||
if _, err := db.Exec(
|
||||
`INSERT INTO im_users(id,username,createdat,updatedat,state,access,phone,public,trusted)
|
||||
VALUES(?,?,?,?,?,?,?,?,?)`,
|
||||
user.id, user.username, now, now, types.StateOK, `{"Auth":"JRWPAS","Anon":"N"}`, user.phone, user.public, `{}`); err != nil {
|
||||
`INSERT INTO im_users(id,account_no,username,createdat,updatedat,state,access,phone,public,trusted)
|
||||
VALUES(?,?,?,?,?,?,?,?,?,?)`,
|
||||
user.id, user.id%1000000, user.username, now, now, types.StateOK, `{"Auth":"JRWPAS","Anon":"N"}`, user.phone, user.public, `{}`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Exec(
|
||||
|
||||
@@ -88,6 +88,9 @@ func ensureUserIdentitySchema(db *sqlx.DB) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = ensureUserAccountNoSchema(ctx, conn); err != nil {
|
||||
return err
|
||||
}
|
||||
if profilesExist {
|
||||
if exists, err := identityColumnExists(ctx, conn, "im_user_profiles", "email"); err != nil {
|
||||
return err
|
||||
@@ -568,6 +571,39 @@ func identityCandidates(ctx context.Context, conn *sqlx.Conn, scoped bool, metho
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func ensureUserAccountNoSchema(ctx context.Context, conn *sqlx.Conn) error {
|
||||
if exists, err := identityColumnExists(ctx, conn, "im_users", "account_no"); err != nil {
|
||||
return err
|
||||
} else if !exists {
|
||||
if _, err = conn.ExecContext(ctx, "ALTER TABLE im_users ADD COLUMN account_no INT UNSIGNED NOT NULL DEFAULT 0 AFTER id"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var start int64 = 999
|
||||
if err := conn.QueryRowContext(ctx, "SELECT GREATEST(COALESCE(MAX(account_no), 999), 999) FROM im_users WHERE account_no>0").Scan(&start); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := conn.ExecContext(ctx, `
|
||||
UPDATE im_users AS u
|
||||
JOIN (
|
||||
SELECT ordered.id, (@im_user_account_no := @im_user_account_no + 1) AS next_no
|
||||
FROM (SELECT id FROM im_users WHERE account_no=0 ORDER BY createdat, id) AS ordered
|
||||
CROSS JOIN (SELECT @im_user_account_no := ?) AS vars
|
||||
) AS seq ON seq.id=u.id
|
||||
SET u.account_no=seq.next_no
|
||||
WHERE u.account_no=0`, start); err != nil {
|
||||
return err
|
||||
}
|
||||
if exists, err := identityIndexExists(ctx, conn, "im_users", "uk_im_users_account_no"); err != nil {
|
||||
return err
|
||||
} else if !exists {
|
||||
if _, err = conn.ExecContext(ctx, "CREATE UNIQUE INDEX uk_im_users_account_no ON im_users(account_no)"); err != nil {
|
||||
return fmt.Errorf("create unique user account number index: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func identityTableExists(ctx context.Context, conn *sqlx.Conn, table string) (bool, error) {
|
||||
var exists int
|
||||
err := conn.QueryRowContext(ctx, `SELECT EXISTS(SELECT 1 FROM information_schema.tables
|
||||
|
||||
Reference in New Issue
Block a user