Clear puppet phone numbers when phone ID changes

This commit is contained in:
Tulir Asokan 2024-03-18 15:12:56 +02:00
parent e3619dbe7b
commit 0456b8c3de
6 changed files with 76 additions and 9 deletions

View file

@ -18,6 +18,7 @@ package database
import ( import (
"context" "context"
"database/sql"
"time" "time"
"go.mau.fi/util/dbutil" "go.mau.fi/util/dbutil"
@ -43,12 +44,22 @@ const (
SET phone=$3, contact_id=$4, name=$5, name_set=$6, avatar_hash=$7, avatar_mxc=$8, avatar_set=$9, avatar_update_ts=$10, contact_info_set=$11 SET phone=$3, contact_id=$4, name=$5, name_set=$6, avatar_hash=$7, avatar_mxc=$8, avatar_set=$9, avatar_update_ts=$10, contact_info_set=$11
WHERE id=$1 AND receiver=$2 WHERE id=$1 AND receiver=$2
` `
resetPuppetMetadataQuery = `
UPDATE puppet SET phone=NULL, contact_info_set=false, avatar_update_ts=0, contact_id='' WHERE receiver=$1
`
) )
func (pq *PuppetQuery) Get(ctx context.Context, key Key) (*Puppet, error) { func (pq *PuppetQuery) Get(ctx context.Context, key Key) (*Puppet, error) {
return pq.QueryOne(ctx, getPuppetQuery, key.ID, key.Receiver) return pq.QueryOne(ctx, getPuppetQuery, key.ID, key.Receiver)
} }
// Reset clears the phone number of all puppets, so that puppets can be upserted safely on relogin
// even if the internal IDs shuffled around. This does *not* delete the puppets, because otherwise
// avatars wouldn't get reset appropriately.
func (pq *PuppetQuery) Reset(ctx context.Context, userID int) error {
return pq.Exec(ctx, resetPuppetMetadataQuery, userID)
}
type Puppet struct { type Puppet struct {
qh *dbutil.QueryHelper[*Puppet] qh *dbutil.QueryHelper[*Puppet]
@ -67,8 +78,9 @@ type Puppet struct {
func (puppet *Puppet) Scan(row dbutil.Scannable) (*Puppet, error) { func (puppet *Puppet) Scan(row dbutil.Scannable) (*Puppet, error) {
var avatarHash []byte var avatarHash []byte
var avatarUpdateTS int64 var avatarUpdateTS int64
var phone sql.NullString
err := row.Scan( err := row.Scan(
&puppet.ID, &puppet.Receiver, &puppet.Phone, &puppet.ContactID, &puppet.Name, &puppet.NameSet, &puppet.ID, &puppet.Receiver, &phone, &puppet.ContactID, &puppet.Name, &puppet.NameSet,
&avatarHash, &puppet.AvatarMXC, &puppet.AvatarSet, &avatarUpdateTS, &puppet.ContactInfoSet, &avatarHash, &puppet.AvatarMXC, &puppet.AvatarSet, &avatarUpdateTS, &puppet.ContactInfoSet,
) )
if err != nil { if err != nil {
@ -77,14 +89,21 @@ func (puppet *Puppet) Scan(row dbutil.Scannable) (*Puppet, error) {
if len(avatarHash) == 32 { if len(avatarHash) == 32 {
puppet.AvatarHash = *(*[32]byte)(avatarHash) puppet.AvatarHash = *(*[32]byte)(avatarHash)
} }
puppet.AvatarUpdateTS = time.UnixMilli(avatarUpdateTS) puppet.Phone = phone.String
if avatarUpdateTS > 0 {
puppet.AvatarUpdateTS = time.UnixMilli(avatarUpdateTS)
}
return puppet, nil return puppet, nil
} }
func (puppet *Puppet) sqlVariables() []any { func (puppet *Puppet) sqlVariables() []any {
var avatarUpdateTS int64
if !puppet.AvatarUpdateTS.IsZero() {
avatarUpdateTS = puppet.AvatarUpdateTS.UnixMilli()
}
return []any{ return []any{
puppet.ID, puppet.Receiver, puppet.Phone, puppet.ContactID, puppet.Name, puppet.NameSet, puppet.ID, puppet.Receiver, dbutil.StrPtr(puppet.Phone), puppet.ContactID, puppet.Name, puppet.NameSet,
puppet.AvatarHash[:], &puppet.AvatarMXC, puppet.AvatarSet, puppet.AvatarUpdateTS.UnixMilli(), puppet.ContactInfoSet, puppet.AvatarHash[:], &puppet.AvatarMXC, puppet.AvatarSet, avatarUpdateTS, puppet.ContactInfoSet,
} }
} }

View file

@ -1,4 +1,4 @@
-- v0 -> v8: Latest revision -- v0 -> v9: Latest revision
CREATE TABLE "user" ( CREATE TABLE "user" (
-- only: postgres -- only: postgres
@ -23,7 +23,7 @@ CREATE TABLE "user" (
CREATE TABLE puppet ( CREATE TABLE puppet (
id TEXT NOT NULL, id TEXT NOT NULL,
receiver BIGINT NOT NULL, receiver BIGINT NOT NULL,
phone TEXT NOT NULL, phone TEXT,
contact_id TEXT NOT NULL, contact_id TEXT NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
name_set BOOLEAN NOT NULL DEFAULT false, name_set BOOLEAN NOT NULL DEFAULT false,

View file

@ -0,0 +1,3 @@
-- v9: Make phone nullable for puppets
-- transaction: off
ALTER TABLE puppet ALTER COLUMN phone DROP NOT NULL;

View file

@ -0,0 +1,32 @@
-- v9: Make phone nullable for puppets
-- transaction: off
PRAGMA foreign_keys = OFF;
BEGIN TRANSACTION;
CREATE TABLE puppet_new (
id TEXT NOT NULL,
receiver BIGINT NOT NULL,
phone TEXT,
contact_id TEXT NOT NULL,
name TEXT NOT NULL,
name_set BOOLEAN NOT NULL DEFAULT false,
avatar_hash bytea CHECK ( LENGTH(avatar_hash) = 32 ),
avatar_update_ts BIGINT NOT NULL,
avatar_mxc TEXT NOT NULL,
avatar_set BOOLEAN NOT NULL DEFAULT false,
contact_info_set BOOLEAN NOT NULL DEFAULT false,
PRIMARY KEY (id, receiver),
CONSTRAINT puppet_user_fkey FOREIGN KEY (receiver) REFERENCES "user"(rowid) ON DELETE CASCADE,
CONSTRAINT puppet_phone_unique UNIQUE (phone, receiver)
);
INSERT INTO puppet_new (id, receiver, phone, contact_id, name, name_set, avatar_hash, avatar_update_ts, avatar_mxc, avatar_set, contact_info_set)
SELECT id, receiver, phone, contact_id, name, name_set, avatar_hash, avatar_update_ts, avatar_mxc, avatar_set, contact_info_set
FROM puppet;
DROP TABLE puppet;
ALTER TABLE puppet_new RENAME TO puppet;
PRAGMA foreign_key_check;
COMMIT;
PRAGMA FOREIGN_KEYS = ON;

View file

@ -281,8 +281,9 @@ func (puppet *Puppet) UpdateContactInfo(ctx context.Context) bool {
return false return false
} }
idents := []string{ idents := make([]string, 0, 2)
fmt.Sprintf("tel:%s", puppet.Phone), if puppet.Phone != "" {
idents = append(idents, fmt.Sprintf("tel:%s", puppet.Phone))
} }
if puppet.ContactID != "" { if puppet.ContactID != "" {
idents = append(idents, fmt.Sprintf("gmsg-contact:%s", puppet.ContactID)) idents = append(idents, fmt.Sprintf("gmsg-contact:%s", puppet.ContactID))
@ -312,8 +313,15 @@ func (puppet *Puppet) Sync(ctx context.Context, source *User, contact *gmproto.P
update := false update := false
if contact.ID.Number != "" && puppet.Phone != contact.ID.Number { if contact.ID.Number != "" && puppet.Phone != contact.ID.Number {
oldPhone := puppet.Phone
puppet.Phone = contact.ID.Number puppet.Phone = contact.ID.Number
puppet.ContactInfoSet = false puppet.ContactInfoSet = false
puppet.log = puppet.bridge.ZLog.With().
Str("phone", puppet.Phone).
Str("puppet_id", puppet.ID).
Int("puppet_receiver", puppet.Receiver).
Logger()
puppet.log.Debug().Str("old_phone", oldPhone).Msg("Phone number changed")
update = true update = true
} }
if contact.ContactID != puppet.ContactID { if contact.ContactID != puppet.ContactID {

View file

@ -820,10 +820,15 @@ func (user *User) syncHandleEvent(event any) {
} }
func (user *User) ResetState() { func (user *User) ResetState() {
ctx := context.TODO()
portals := user.bridge.GetAllPortalsForUser(user.RowID) portals := user.bridge.GetAllPortalsForUser(user.RowID)
user.zlog.Debug().Int("portal_count", len(portals)).Msg("Deleting portals") user.zlog.Debug().Int("portal_count", len(portals)).Msg("Deleting portals")
for _, portal := range portals { for _, portal := range portals {
portal.Delete(context.TODO()) portal.Delete(ctx)
}
err := user.bridge.DB.Puppet.Reset(ctx, user.RowID)
if err != nil {
user.zlog.Err(err).Msg("Failed to reset puppet state")
} }
user.PhoneID = "" user.PhoneID = ""
go func() { go func() {