feat: migrate IM identity and snowflake primary keys
This commit is contained in:
+123
-151
@@ -246,6 +246,12 @@ func (a *adapter) Open(jsonconfig json.RawMessage) error {
|
||||
if err = ensureIMHTTPInfrastructureTables(a.db); err != nil {
|
||||
return errors.New("mysql adapter failed to ensure HTTP infrastructure schema: " + err.Error())
|
||||
}
|
||||
if err = ensureSnowflakePrimaryKeys(a.db); err != nil {
|
||||
return errors.New("mysql adapter failed to ensure snowflake primary keys: " + err.Error())
|
||||
}
|
||||
if err = ensureUserIdentitySchema(a.db); err != nil {
|
||||
return errors.New("mysql adapter failed to ensure user identity schema: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
@@ -440,6 +446,14 @@ func (a *adapter) TenantAdapterFor(tenantID t.TenantID) (dbadapter.TenantBusines
|
||||
db.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
if err = ensureSnowflakePrimaryKeys(db); err != nil {
|
||||
db.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
if err = ensureUserIdentitySchema(db); err != nil {
|
||||
db.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
tenantAdp := &adapter{
|
||||
db: db,
|
||||
dsn: mysqlDefaultString(dsn, a.dsn),
|
||||
@@ -589,7 +603,13 @@ func (a *adapter) CreateDb(reset bool) error {
|
||||
if _, err = tx.Exec("DROP DATABASE IF EXISTS " + quoteIdent(businessDB)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = tx.Exec("DROP DATABASE IF EXISTS " + quoteIdent(registryDB)); err != nil {
|
||||
if _, err = tx.Exec("CREATE DATABASE IF NOT EXISTS " + quoteIdent(registryDB) + " CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = tx.Exec("DROP TABLE IF EXISTS " + quoteIdent(registryDB) + ".im_tenant_config"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = tx.Exec("DROP TABLE IF EXISTS " + quoteIdent(registryDB) + ".im_tenant"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -616,10 +636,11 @@ func (a *adapter) CreateDb(reset bool) error {
|
||||
access JSON,
|
||||
lastseen DATETIME,
|
||||
useragent VARCHAR(255) DEFAULT '',
|
||||
phone VARCHAR(32),
|
||||
public JSON,
|
||||
trusted JSON,
|
||||
tags JSON,
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX users_phone(phone),
|
||||
INDEX users_state_stateat(state, stateat),
|
||||
INDEX users_lastseen_updatedat(lastseen, updatedat)
|
||||
)`); err != nil {
|
||||
@@ -628,12 +649,14 @@ func (a *adapter) CreateDb(reset bool) error {
|
||||
|
||||
if _, err = tx.Exec(
|
||||
`CREATE TABLE im_user_security(
|
||||
id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
auth_version INT NOT NULL DEFAULT 1,
|
||||
password_updated_at DATETIME(3),
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(user_id)
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_user_security_user(user_id)
|
||||
)`); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -645,20 +668,6 @@ func (a *adapter) CreateDb(reset bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Indexed user tags.
|
||||
if _, err = tx.Exec(
|
||||
`CREATE TABLE im_usertags(
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
userid BIGINT NOT NULL,
|
||||
tag VARCHAR(96) NOT NULL,
|
||||
PRIMARY KEY(id),
|
||||
FOREIGN KEY(userid) REFERENCES im_users(id),
|
||||
INDEX usertags_tag(tag),
|
||||
UNIQUE INDEX usertags_userid_tag(userid, tag)
|
||||
)`); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Indexed devices. Normalized into a separate table.
|
||||
if _, err = tx.Exec(
|
||||
`CREATE TABLE im_devices(
|
||||
@@ -919,15 +928,21 @@ func (a *adapter) CreateDb(reset bool) error {
|
||||
|
||||
if _, err = tx.Exec(
|
||||
`CREATE TABLE im_kvmeta(` +
|
||||
"id BIGINT NOT NULL," +
|
||||
"`key` VARCHAR(64) NOT NULL," +
|
||||
"createdat DATETIME(3)," +
|
||||
"`value` TEXT," +
|
||||
"PRIMARY KEY(`key`)," +
|
||||
"PRIMARY KEY(id)," +
|
||||
"UNIQUE INDEX uk_im_kvmeta_key(`key`)," +
|
||||
"INDEX kvmeta_createdat_key(createdat, `key`)" +
|
||||
`)`); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = tx.Exec("INSERT INTO im_kvmeta(`key`, `value`) VALUES('version',?)", adpVersion); err != nil {
|
||||
versionID, err := nextSnowflakeID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = tx.Exec("INSERT INTO im_kvmeta(id, `key`, `value`) VALUES(?,'version',?)", versionID, adpVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1225,6 +1240,7 @@ func createIMMailboxTables(tx *sql.Tx) error {
|
||||
func createIMUserProductTables(tx *sql.Tx) error {
|
||||
statements := []string{
|
||||
`CREATE TABLE im_user_profiles(
|
||||
id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
avatar_file_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
nickname VARCHAR(64) NOT NULL DEFAULT '',
|
||||
@@ -1232,13 +1248,16 @@ func createIMUserProductTables(tx *sql.Tx) error {
|
||||
signature VARCHAR(256) NOT NULL DEFAULT '',
|
||||
gender VARCHAR(16) NOT NULL DEFAULT '',
|
||||
region VARCHAR(128) NOT NULL DEFAULT '',
|
||||
email VARCHAR(128) NOT NULL DEFAULT '',
|
||||
public_account VARCHAR(64) NOT NULL DEFAULT '',
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(user_id)
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_user_profiles_user(user_id)
|
||||
)`,
|
||||
`CREATE TABLE im_user_privacy_settings(
|
||||
id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
add_me_methods JSON,
|
||||
profile_visibility VARCHAR(32) NOT NULL DEFAULT 'friends',
|
||||
@@ -1247,7 +1266,8 @@ func createIMUserProductTables(tx *sql.Tx) error {
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(user_id)
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_user_privacy_settings_user(user_id)
|
||||
)`,
|
||||
`CREATE TABLE im_app_releases(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
@@ -1268,6 +1288,7 @@ func createIMUserProductTables(tx *sql.Tx) error {
|
||||
UNIQUE INDEX uk_im_app_releases_platform_channel(platform, channel)
|
||||
)`,
|
||||
`CREATE TABLE im_device_push_tokens(
|
||||
id BIGINT NOT NULL,
|
||||
device_id VARCHAR(128) NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
provider VARCHAR(32) NOT NULL,
|
||||
@@ -1276,7 +1297,8 @@ func createIMUserProductTables(tx *sql.Tx) error {
|
||||
platform VARCHAR(32) NOT NULL DEFAULT '',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(device_id),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_device_push_tokens_device(device_id),
|
||||
UNIQUE INDEX uk_im_device_push_tokens_provider_token(provider, token),
|
||||
INDEX idx_im_device_push_tokens_user(user_id, updated_at)
|
||||
)`,
|
||||
@@ -1443,6 +1465,7 @@ func createIMRelationTables(exec sqlExecutor) error {
|
||||
INDEX idx_im_friend_requests_inbox(target_id, status, created_at)
|
||||
)`,
|
||||
`CREATE TABLE im_friendships(
|
||||
id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
peer_id BIGINT NOT NULL,
|
||||
status VARCHAR(16) NOT NULL,
|
||||
@@ -1450,15 +1473,18 @@ func createIMRelationTables(exec sqlExecutor) error {
|
||||
topic CHAR(25) NOT NULL DEFAULT '',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(user_id, peer_id),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_friendships_pair(user_id, peer_id),
|
||||
INDEX idx_im_friendships_peer(peer_id, status)
|
||||
)`,
|
||||
`CREATE TABLE im_user_blocks(
|
||||
id BIGINT NOT NULL,
|
||||
blocker_id BIGINT NOT NULL,
|
||||
blocked_id BIGINT NOT NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(blocker_id, blocked_id),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_user_blocks_pair(blocker_id, blocked_id),
|
||||
INDEX idx_im_user_blocks_blocked(blocked_id)
|
||||
)`,
|
||||
`CREATE TABLE im_favorites(
|
||||
@@ -1536,6 +1562,7 @@ func createIMSecurityTables(tx *sql.Tx) error {
|
||||
}
|
||||
_, err := tx.Exec(
|
||||
`CREATE TABLE im_youth_mode_settings(
|
||||
id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
purpose VARCHAR(64) NOT NULL,
|
||||
password_hash VARBINARY(255) NOT NULL,
|
||||
@@ -1547,7 +1574,8 @@ func createIMSecurityTables(tx *sql.Tx) error {
|
||||
locked_until DATETIME(3),
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(user_id, purpose)
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_youth_mode_settings_user_purpose(user_id, purpose)
|
||||
)`)
|
||||
return err
|
||||
}
|
||||
@@ -1650,27 +1678,31 @@ func (a *adapter) UserCreate(user *t.User) error {
|
||||
}()
|
||||
|
||||
decoded_uid := store.DecodeUid(user.Uid())
|
||||
if _, err = tx.Exec("INSERT INTO im_users(id,createdat,updatedat,state,access,public,trusted,tags) VALUES(?,?,?,?,?,?,?,?)",
|
||||
securityID, err := nextSnowflakeID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var phone any
|
||||
if strings.TrimSpace(user.Phone) != "" {
|
||||
phone = strings.TrimSpace(user.Phone)
|
||||
}
|
||||
if _, err = tx.Exec("INSERT INTO im_users(id,createdat,updatedat,state,access,phone,public,trusted) VALUES(?,?,?,?,?,?,?,?)",
|
||||
decoded_uid,
|
||||
user.CreatedAt,
|
||||
user.UpdatedAt,
|
||||
user.State,
|
||||
user.Access,
|
||||
phone,
|
||||
common.ToJSON(user.Public),
|
||||
common.ToJSON(user.Trusted),
|
||||
user.Tags); err != nil {
|
||||
common.ToJSON(user.Trusted)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = tx.Exec("INSERT INTO im_user_security(user_id,auth_version,created_at,updated_at) VALUES(?,?,?,?)",
|
||||
decoded_uid, 1, user.CreatedAt, user.UpdatedAt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save user's tags to a separate table to make user findable.
|
||||
if err = addTags(tx, "im_usertags", "userid", decoded_uid, user.Tags, false); err != nil {
|
||||
if _, err = tx.Exec("INSERT INTO im_user_security(id,user_id,auth_version,created_at,updated_at) VALUES(?,?,?,?,?)",
|
||||
securityID, decoded_uid, 1, user.CreatedAt, user.UpdatedAt); err != nil {
|
||||
return err
|
||||
}
|
||||
user.Tags = nil
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
@@ -1895,7 +1927,8 @@ func (a *adapter) UserGet(uid t.Uid) (*t.User, error) {
|
||||
defer cancel()
|
||||
}
|
||||
var user t.User
|
||||
err := a.db.GetContext(ctx, &user, "SELECT * FROM im_users WHERE id=? AND state!=?", store.DecodeUid(uid), t.StateDeleted)
|
||||
err := a.db.GetContext(ctx, &user, `SELECT id,createdat,updatedat,state,stateat,access,lastseen,useragent,
|
||||
COALESCE(phone,'') AS phone,public,trusted FROM im_users WHERE id=? AND state!=?`, store.DecodeUid(uid), t.StateDeleted)
|
||||
if err == nil {
|
||||
user.SetUid(uid)
|
||||
user.Public = common.FromJSON(user.Public)
|
||||
@@ -1925,7 +1958,8 @@ func (a *adapter) UserGetAll(ids ...t.Uid) ([]t.User, error) {
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
q, uids, _ := sqlx.In("SELECT * FROM im_users WHERE id IN (?) AND state!=?", uids, t.StateDeleted)
|
||||
q, uids, _ := sqlx.In(`SELECT id,createdat,updatedat,state,stateat,access,lastseen,useragent,
|
||||
COALESCE(phone,'') AS phone,public,trusted FROM im_users WHERE id IN (?) AND state!=?`, uids, t.StateDeleted)
|
||||
rows, err := a.db.QueryxContext(ctx, a.db.Rebind(q), uids...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -2048,10 +2082,6 @@ func (a *adapter) UserDelete(uid t.Uid, hard bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = tx.Exec("DELETE FROM im_usertags WHERE userid=?", decoded_uid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = tx.Exec("DELETE FROM im_users WHERE id=?", decoded_uid); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2150,7 +2180,11 @@ func (a *adapter) UserUpdate(uid t.Uid, update map[string]any) error {
|
||||
}
|
||||
}()
|
||||
|
||||
update = withoutUserTags(update)
|
||||
cols, args := common.UpdateByMap(update)
|
||||
if len(cols) == 0 {
|
||||
return tx.Commit()
|
||||
}
|
||||
decoded_uid := store.DecodeUid(uid)
|
||||
args = append(args, decoded_uid)
|
||||
_, err = tx.Exec("UPDATE im_users SET "+strings.Join(cols, ",")+" WHERE id=?", args...)
|
||||
@@ -2166,76 +2200,12 @@ func (a *adapter) UserUpdate(uid t.Uid, update map[string]any) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Tags are also stored in a separate table
|
||||
if tags := common.ExtractTags(update); tags != nil {
|
||||
// First delete all user tags
|
||||
_, err = tx.Exec("DELETE FROM im_usertags WHERE userid=?", decoded_uid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Now insert new tags
|
||||
err = addTags(tx, "im_usertags", "userid", decoded_uid, tags, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// UserUpdateTags adds, removes, or resets user's tags.
|
||||
// UserUpdateTags is retained for adapter compatibility; user tags are disabled.
|
||||
func (a *adapter) UserUpdateTags(uid t.Uid, add, remove, reset []string) ([]string, error) {
|
||||
ctx, cancel := a.getContextForTx()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
tx, err := a.db.BeginTxx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
decoded_uid := store.DecodeUid(uid)
|
||||
|
||||
if reset != nil {
|
||||
// Delete all tags first if resetting.
|
||||
_, err = tx.Exec("DELETE FROM im_usertags WHERE userid=?", decoded_uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
add = reset
|
||||
remove = nil
|
||||
}
|
||||
|
||||
// Now insert new tags. Ignore duplicates if resetting.
|
||||
err = addTags(tx, "im_usertags", "userid", decoded_uid, add, reset == nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Delete tags.
|
||||
err = removeTags(tx, "im_usertags", "userid", decoded_uid, remove)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var allTags []string
|
||||
err = tx.Select(&allTags, "SELECT tag FROM im_usertags WHERE userid=?", decoded_uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = tx.Exec("UPDATE im_users SET tags=? WHERE id=?", t.StringSlice(allTags), decoded_uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return allTags, tx.Commit()
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// UserGetByCred returns user ID for the given validated credential.
|
||||
@@ -2245,7 +2215,12 @@ func (a *adapter) UserGetByCred(method, value string) (t.Uid, error) {
|
||||
defer cancel()
|
||||
}
|
||||
var decoded_uid int64
|
||||
err := a.db.GetContext(ctx, &decoded_uid, "SELECT userid FROM im_credentials WHERE synthetic=?", method+":"+value)
|
||||
var err error
|
||||
if method == "tel" {
|
||||
err = a.db.GetContext(ctx, &decoded_uid, "SELECT id FROM im_users WHERE phone=? AND state!=?", value, t.StateDeleted)
|
||||
} else {
|
||||
err = a.db.GetContext(ctx, &decoded_uid, "SELECT userid FROM im_credentials WHERE synthetic=?", method+":"+value)
|
||||
}
|
||||
if err == nil {
|
||||
return store.EncodeUid(decoded_uid), nil
|
||||
}
|
||||
@@ -2266,6 +2241,22 @@ func (a *adapter) TenantUserGetByCred(tenantID t.TenantID, method, value string)
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.UserGetByCred(method, value)
|
||||
}
|
||||
if !a.tenantScopedDB && method == "tel" {
|
||||
ctx, cancel := a.getContext()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
var decodedUID int64
|
||||
err = a.db.GetContext(ctx, &decodedUID,
|
||||
"SELECT id FROM im_users WHERE tenant_id=? AND phone=? AND state!=?", tenantID, value, t.StateDeleted)
|
||||
if err == sql.ErrNoRows {
|
||||
return t.ZeroUid, nil
|
||||
}
|
||||
if err != nil {
|
||||
return t.ZeroUid, err
|
||||
}
|
||||
return store.EncodeUid(decodedUID), nil
|
||||
}
|
||||
return a.UserGetByCred(method, value)
|
||||
}
|
||||
|
||||
@@ -3329,17 +3320,21 @@ func (a *adapter) SubsDelForUser(user t.Uid, hard bool) error {
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// Find returns a list of users or group topics who match given tags, such as "email:jdoe@example.com" or "tel:+18003287448".
|
||||
// Find returns group topics which match the requested discovery tags.
|
||||
func (a *adapter) Find(caller, promoPrefix string, req [][]string, opt []string, activeOnly bool) ([]t.Subscription, error) {
|
||||
index := make(map[string]struct{})
|
||||
allReq := t.FlattenDoubleSlice(req)
|
||||
allTags := append(append([]string{}, allReq...), opt...)
|
||||
if len(allTags) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var args []any
|
||||
stateConstraint := ""
|
||||
if activeOnly {
|
||||
args = append(args, t.StateOK)
|
||||
stateConstraint = "u.state=? AND "
|
||||
stateConstraint = "t.state=? AND "
|
||||
}
|
||||
index := make(map[string]struct{})
|
||||
allReq := t.FlattenDoubleSlice(req)
|
||||
for _, tag := range append(allReq, opt...) {
|
||||
for _, tag := range allTags {
|
||||
args = append(args, tag)
|
||||
index[tag] = struct{}{}
|
||||
}
|
||||
@@ -3352,29 +3347,9 @@ func (a *adapter) Find(caller, promoPrefix string, req [][]string, opt []string,
|
||||
matcher = "COUNT(*)"
|
||||
}
|
||||
|
||||
query := "SELECT u.id,u.createdat,u.updatedat,0,u.access,0 AS subcnt,u.public,u.trusted,u.tags," + matcher + " AS matches " +
|
||||
"FROM im_users AS u JOIN im_usertags AS tg ON tg.userid=u.id " +
|
||||
"WHERE " + stateConstraint + "tg.tag IN (?" + strings.Repeat(",?", len(allReq)+len(opt)-1) + ") " +
|
||||
"GROUP BY u.id,u.createdat,u.updatedat,u.access,u.public,u.trusted,u.tags "
|
||||
if len(allReq) > 0 {
|
||||
q, a := common.DisjunctionSql(req, "tg.tag")
|
||||
query += q
|
||||
args = append(args, a...)
|
||||
}
|
||||
|
||||
query += "UNION ALL "
|
||||
|
||||
if activeOnly {
|
||||
args = append(args, t.StateOK)
|
||||
stateConstraint = "t.state=? AND "
|
||||
}
|
||||
for _, tag := range append(allReq, opt...) {
|
||||
args = append(args, tag)
|
||||
}
|
||||
|
||||
query += "SELECT t.name AS topic,t.createdat,t.updatedat,t.usebt,t.access,t.subcnt,t.public,t.trusted,t.tags," + matcher + " AS matches " +
|
||||
query := "SELECT t.name AS topic,t.createdat,t.updatedat,t.usebt,t.access,t.subcnt,t.public,t.trusted,t.tags," + matcher + " AS matches " +
|
||||
"FROM im_topics AS t JOIN im_topictags AS tg ON t.name=tg.topic " +
|
||||
"WHERE " + stateConstraint + "tg.tag IN (?" + strings.Repeat(",?", len(allReq)+len(opt)-1) + ") " +
|
||||
"WHERE " + stateConstraint + "tg.tag IN (?" + strings.Repeat(",?", len(allTags)-1) + ") " +
|
||||
"GROUP BY t.name,t.createdat,t.updatedat,t.usebt,t.access,t.subcnt,t.public,t.trusted,t.tags "
|
||||
if len(allReq) > 0 {
|
||||
q, a := common.DisjunctionSql(req, "tg.tag")
|
||||
@@ -3443,29 +3418,17 @@ func (a *adapter) Find(caller, promoPrefix string, req [][]string, opt []string,
|
||||
return subs, err
|
||||
}
|
||||
|
||||
// FindOne returns the first topic or user which matches the given tag.
|
||||
// FindOne returns the first topic which matches the given tag.
|
||||
func (a *adapter) FindOne(tag string) (string, error) {
|
||||
var args []any
|
||||
|
||||
query := "SELECT t.name AS topic FROM im_topics AS t LEFT JOIN im_topictags AS tt ON t.name=tt.topic " +
|
||||
"WHERE tt.tag=?"
|
||||
args = append(args, tag)
|
||||
|
||||
query += " UNION ALL "
|
||||
|
||||
query += "SELECT u.id AS topic FROM im_users AS u LEFT JOIN im_usertags AS ut ON ut.userid=u.id " +
|
||||
"WHERE ut.tag=?"
|
||||
args = append(args, tag)
|
||||
|
||||
// LIMIT is applied to all resultant rows.
|
||||
query += " LIMIT 1"
|
||||
"WHERE tt.tag=? LIMIT 1"
|
||||
|
||||
ctx, cancel := a.getContext()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
rows, err := a.db.QueryxContext(ctx, query, args...)
|
||||
rows, err := a.db.QueryxContext(ctx, query, tag)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -3998,6 +3961,11 @@ func (a *adapter) CredUpsert(cred *t.Credential) (bool, error) {
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
if cred.Done && cred.Method == "tel" {
|
||||
if _, err = tx.Exec("UPDATE im_users SET phone=? WHERE id=? AND (phone IS NULL OR phone='')", cred.Value, userId); err != nil {
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
return true, tx.Commit()
|
||||
}
|
||||
|
||||
@@ -4635,7 +4603,11 @@ func (a *adapter) PCacheUpsert(key string, value string, failOnDuplicate bool) e
|
||||
action = "REPLACE"
|
||||
}
|
||||
|
||||
_, err := a.db.ExecContext(ctx, action+" INTO im_kvmeta(`key`,createdat,`value`) VALUES(?,?,?)", key, t.TimeNow(), value)
|
||||
id, err := nextSnowflakeID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = a.db.ExecContext(ctx, action+" INTO im_kvmeta(id,`key`,createdat,`value`) VALUES(?,?,?,?)", id, key, t.TimeNow(), value)
|
||||
if isDupe(err) {
|
||||
return t.ErrDuplicate
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user