fix: harden group lifecycle and registration writes
This commit is contained in:
@@ -1983,8 +1983,12 @@ func (a *adapter) AuthAddRecord(uid t.Uid, scheme, unique string, authLvl auth.L
|
||||
} else if unique != storedUsername {
|
||||
return t.ErrMalformed
|
||||
}
|
||||
if _, err := a.db.ExecContext(ctx, "INSERT INTO im_auth(userid,authLvl,secret,expires) VALUES(?,?,?,?)",
|
||||
store.DecodeUid(uid), authLvl, secret, exp); err != nil {
|
||||
authID, err := nextSnowflakeID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := a.db.ExecContext(ctx, "INSERT INTO im_auth(id,userid,authLvl,secret,expires) VALUES(?,?,?,?,?)",
|
||||
authID, store.DecodeUid(uid), authLvl, secret, exp); err != nil {
|
||||
if isDupe(err) {
|
||||
return t.ErrDuplicate
|
||||
}
|
||||
@@ -2637,10 +2641,14 @@ func createSubscription(tx *sqlx.Tx, sub *t.Subscription, undelete bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
subID, err := nextSnowflakeID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.Exec(
|
||||
"INSERT INTO im_subscriptions(createdat,updatedat,deletedat,userid,topic,modeWant,modeGiven,private) "+
|
||||
"VALUES(?,?,NULL,?,?,?,?,?)",
|
||||
sub.CreatedAt, sub.UpdatedAt, decoded_uid, sub.Topic, sub.ModeWant.String(), sub.ModeGiven.String(), jpriv)
|
||||
"INSERT INTO im_subscriptions(id,createdat,updatedat,deletedat,userid,topic,modeWant,modeGiven,private) "+
|
||||
"VALUES(?,?,?,NULL,?,?,?,?,?)",
|
||||
subID, sub.CreatedAt, sub.UpdatedAt, decoded_uid, sub.Topic, sub.ModeWant.String(), sub.ModeGiven.String(), jpriv)
|
||||
|
||||
if err != nil && isDupe(err) {
|
||||
if undelete {
|
||||
@@ -4262,9 +4270,13 @@ func (a *adapter) CredUpsert(cred *t.Credential) (bool, error) {
|
||||
}
|
||||
}
|
||||
// Add new record.
|
||||
_, err = tx.Exec("INSERT INTO im_credentials(createdat,updatedat,method,value,synthetic,userid,resp,done) "+
|
||||
"VALUES(?,?,?,?,?,?,?,?)",
|
||||
cred.CreatedAt, cred.UpdatedAt, cred.Method, cred.Value, synth, userId, cred.Resp, cred.Done)
|
||||
credID, err := nextSnowflakeID()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, err = tx.Exec("INSERT INTO im_credentials(id,createdat,updatedat,method,value,synthetic,userid,resp,done) "+
|
||||
"VALUES(?,?,?,?,?,?,?,?,?)",
|
||||
credID, cred.CreatedAt, cred.UpdatedAt, cred.Method, cred.Value, synth, userId, cred.Resp, cred.Done)
|
||||
if err != nil {
|
||||
if isDupe(err) {
|
||||
return true, t.ErrDuplicate
|
||||
|
||||
@@ -2725,6 +2725,9 @@ func (a *adapter) TenantCallList(id t.TenantID, uid t.Uid, topic, cursor string,
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
if err := a.finalizeStaleCalls(ctx); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
rows, err := a.db.QueryxContext(ctx,
|
||||
`SELECT id,call_id,topic,seq_id,caller_id,callee_id,call_type,status,end_reason,duration_ms,
|
||||
started_at,accepted_at,finished_at,created_at,updated_at
|
||||
@@ -2786,6 +2789,23 @@ func (a *adapter) TenantCallList(id t.TenantID, uid t.Uid, topic, cursor string,
|
||||
return records, nextCursor, nil
|
||||
}
|
||||
|
||||
func (a *adapter) finalizeStaleCalls(ctx context.Context) error {
|
||||
now := t.TimeNow()
|
||||
startedBefore := now.Add(-2 * time.Minute)
|
||||
acceptedBefore := now.Add(-12 * time.Hour)
|
||||
if _, err := a.db.ExecContext(ctx, `UPDATE im_call_records
|
||||
SET status=?,end_reason=?,duration_ms=0,finished_at=?,updated_at=?
|
||||
WHERE status=? AND started_at<? AND finished_at IS NULL`,
|
||||
t.CallStatusMissed, t.CallStatusMissed, now, now, t.CallStatusStarted, startedBefore); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := a.db.ExecContext(ctx, `UPDATE im_call_records
|
||||
SET status=?,end_reason=?,duration_ms=GREATEST(TIMESTAMPDIFF(MICROSECOND,accepted_at,?) DIV 1000,0),finished_at=?,updated_at=?
|
||||
WHERE status=? AND accepted_at IS NOT NULL AND accepted_at<? AND finished_at IS NULL`,
|
||||
t.CallStatusDisconnected, t.CallStatusDisconnected, now, now, now, t.CallStatusAccepted, acceptedBefore)
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *adapter) TenantWorkbenchModules(id t.TenantID, uid t.Uid) ([]t.WorkbenchModule, int, error) {
|
||||
if !a.tenantScopedDB {
|
||||
tenantAdp, err := a.routedTenantAdapter(id)
|
||||
|
||||
@@ -171,6 +171,11 @@ func (a *adapter) TenantGroupSettingsUpdate(id t.TenantID, topic string, patch t
|
||||
}
|
||||
return nil, t.ErrNotFound
|
||||
}
|
||||
if patch.MuteAll != nil {
|
||||
if err = syncGroupMuteAllTx(tx, topic, *patch.MuteAll, time.Now().UTC().Round(time.Millisecond)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -199,7 +204,7 @@ func (a *adapter) TenantGroupDissolve(id t.TenantID, topic string, actor t.Uid,
|
||||
}
|
||||
defer tx.Rollback()
|
||||
res, err := tx.ExecContext(ctx, `UPDATE im_group_settings
|
||||
SET group_status='dissolved',dissolved_by=?,dissolved_at=?,version=version+1,updated_at=?
|
||||
SET group_status='dissolved',dissolved_by=?,dissolved_at=?,member_count=0,version=version+1,updated_at=?
|
||||
WHERE topic=? AND group_status<>'dissolved'`, decodeUID(actor), now, now, strings.TrimSpace(topic))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -207,6 +212,21 @@ func (a *adapter) TenantGroupDissolve(id t.TenantID, topic string, actor t.Uid,
|
||||
if affected, _ := res.RowsAffected(); affected == 0 {
|
||||
return nil, t.ErrNotFound
|
||||
}
|
||||
if _, err = tx.ExecContext(ctx, `UPDATE im_subscriptions
|
||||
SET updatedat=?,deletedat=?
|
||||
WHERE topic IN (?,?) AND deletedat IS NULL`, now, now, strings.TrimSpace(topic), t.GrpToChn(strings.TrimSpace(topic))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = tx.ExecContext(ctx, `UPDATE im_group_members
|
||||
SET member_status=?,left_at=?,removed_by=?,version=version+1,updated_at=?
|
||||
WHERE topic=? AND member_status=?`, t.GroupMemberStatusLeft, now, decodeUID(actor), now, strings.TrimSpace(topic), t.GroupMemberStatusActive); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = tx.ExecContext(ctx, `UPDATE im_topics
|
||||
SET updatedat=?,touchedat=?,state=?,stateat=?,subcnt=0
|
||||
WHERE name=?`, now, now, t.StateDeleted, now, strings.TrimSpace(topic)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -573,7 +593,7 @@ func (a *adapter) TenantGroupInvitationAccept(id t.TenantID, invitationID string
|
||||
if err = upsertGroupMemberTx(tx, member); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = recountGroupMembersTx(tx, inv.Topic); err != nil {
|
||||
if err = recountGroupMembersAndBumpVersionTx(tx, inv.Topic); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = updateGroupTopicSubCountTx(tx, inv.Topic); err != nil {
|
||||
@@ -757,11 +777,13 @@ func (a *adapter) TenantGroupJoinRequestResolve(id t.TenantID, topic, requestID
|
||||
WHERE topic=? AND request_id=? AND status=?`, status, rejectReason, decodeUID(actor), now, now, topic, requestID, t.GroupJoinRequestStatusPending); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = recountGroupMembersTx(tx, topic); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = updateGroupTopicSubCountTx(tx, topic); err != nil {
|
||||
return nil, err
|
||||
if action == "accept" {
|
||||
if err = recountGroupMembersAndBumpVersionTx(tx, topic); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = updateGroupTopicSubCountTx(tx, topic); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
@@ -1284,7 +1306,7 @@ func (a *adapter) TenantGroupInviteMember(id t.TenantID, topic string, inviter,
|
||||
if err = upsertGroupMemberTx(tx, member); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err = recountGroupMembersTx(tx, topic); err != nil {
|
||||
if err = recountGroupMembersAndBumpVersionTx(tx, topic); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err = updateGroupTopicSubCountTx(tx, topic); err != nil {
|
||||
@@ -1322,7 +1344,7 @@ func (a *adapter) TenantGroupLeave(id t.TenantID, topic string, uid t.Uid, now t
|
||||
WHERE topic=? AND user_id=? AND member_status=?`, t.GroupMemberStatusLeft, now, now, topic, decodeUID(uid), t.GroupMemberStatusActive); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = recountGroupMembersTx(tx, topic); err != nil {
|
||||
if err = recountGroupMembersAndBumpVersionTx(tx, topic); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = updateGroupTopicSubCountTx(tx, topic); err != nil {
|
||||
@@ -1485,12 +1507,33 @@ func recountGroupMembersTx(tx *sqlx.Tx, topic string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func recountGroupMembersAndBumpVersionTx(tx *sqlx.Tx, topic string) error {
|
||||
_, err := tx.Exec(`UPDATE im_group_settings SET member_count=(SELECT COUNT(*) FROM im_group_members
|
||||
WHERE topic=? AND member_status=?),version=version+1,updated_at=CURRENT_TIMESTAMP(3) WHERE topic=?`, topic, t.GroupMemberStatusActive, topic)
|
||||
return err
|
||||
}
|
||||
|
||||
func updateGroupTopicSubCountTx(tx *sqlx.Tx, topic string) error {
|
||||
_, err := tx.Exec(`UPDATE im_topics SET subcnt=(SELECT COUNT(*) FROM im_subscriptions
|
||||
WHERE topic IN (?,?) AND deletedat IS NULL) WHERE name=?`, topic, t.GrpToChn(topic), topic)
|
||||
return err
|
||||
}
|
||||
|
||||
func syncGroupMuteAllTx(tx *sqlx.Tx, topic string, muteAll bool, now time.Time) error {
|
||||
mode := t.ModeCPublic
|
||||
if muteAll {
|
||||
mode = t.ModeCPublic &^ t.ModeWrite
|
||||
}
|
||||
_, err := tx.Exec(`UPDATE im_subscriptions AS s
|
||||
JOIN im_group_members AS gm ON gm.topic=s.topic AND gm.user_id=s.userid
|
||||
SET s.modewant=?,s.modegiven=?,s.updatedat=?,
|
||||
gm.version=gm.version+1,gm.updated_at=?
|
||||
WHERE s.topic=? AND s.deletedat IS NULL
|
||||
AND gm.member_status=? AND gm.role=?`,
|
||||
mode.String(), mode.String(), now, now, topic, t.GroupMemberStatusActive, t.GroupMemberRoleMember)
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *adapter) routeGroupTenant(id t.TenantID) (*adapter, error) {
|
||||
if !a.tenantScopedDB {
|
||||
tenantAdp, err := a.routedTenantAdapter(id)
|
||||
|
||||
Reference in New Issue
Block a user