Skip to content

Commit

Permalink
Refactor database tables and query wrappers (#402)
Browse files Browse the repository at this point in the history
* Change most columns to `NOT NULL`, including primary keys
  (because SQLite).
* Change columns only storing signal user IDs (portal receiver, message
  sender, user uuid) to use the `uuid` type instead of `TEXT`.
* Drop chat ID from message table primary key.
* Add part index to message table to replace timestamp hack for storing
  multiple parts of the same message.
* Change query wrappers to use new QureyHelper struct in dbutil, and 
  pass contexts and errors everywhere.

As a part of changing the portal receiver from phone number to uuid,
old portals whose receiver isn't logged in anymore may be discarded.
The discarded portals will be stored in the lost_portals table for
cleanup or recovery.
  • Loading branch information
tulir committed Dec 31, 2023
1 parent 18e0063 commit 35db0ab
Show file tree
Hide file tree
Showing 28 changed files with 1,595 additions and 1,401 deletions.
58 changes: 52 additions & 6 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package main

import (
"context"
"strings"

"github.com/google/uuid"
"github.com/skip2/go-qrcode"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/bridge/commands"
Expand Down Expand Up @@ -55,6 +57,7 @@ func (br *SignalBridge) RegisterCommands() {
cmdUnsetRelay,
cmdDeletePortal,
cmdDeleteAllPortals,
cmdCleanupLostPortals,
)
}

Expand Down Expand Up @@ -88,7 +91,7 @@ func fnSetRelay(ce *WrappedCommandEvent) {
ce.Reply("Only bridge admins are allowed to enable relay mode on this instance of the bridge")
} else {
ce.Portal.RelayUserID = ce.User.MXID
ce.Portal.Update()
ce.Portal.Update(context.TODO())
ce.Reply("Messages from non-logged-in users in this room will now be bridged through your Signal account")
}
}
Expand All @@ -110,7 +113,7 @@ func fnUnsetRelay(ce *WrappedCommandEvent) {
ce.Reply("Only bridge admins are allowed to enable relay mode on this instance of the bridge")
} else {
ce.Portal.RelayUserID = ""
ce.Portal.Update()
ce.Portal.Update(context.TODO())
ce.Reply("Messages from non-logged-in users will no longer be bridged in this room")
}
}
Expand Down Expand Up @@ -143,7 +146,7 @@ var cmdPing = &commands.FullHandler{
}

func fnPing(ce *WrappedCommandEvent) {
if ce.User.SignalID == "" {
if ce.User.SignalID == uuid.Nil {
ce.Reply("You're not logged in")
} else if !ce.User.SignalDevice.IsDeviceLoggedIn() {
ce.Reply("You were logged in at some point, but are not anymore")
Expand Down Expand Up @@ -306,13 +309,20 @@ func fnLogin(ce *WrappedCommandEvent) {

// Update user with SignalID
if signalID != "" {
ce.User.SignalID = signalID
ce.User.SignalID, err = uuid.Parse(signalID)
if err != nil {
ce.Reply("Problem logging in - SignalID is not a valid UUID")
return
}
ce.User.SignalUsername = signalUsername
} else {
ce.Reply("Problem logging in - No SignalID received")
return
}
ce.User.Update()
err = ce.User.Update(context.TODO())
if err != nil {
ce.ZLog.Err(err).Msg("Failed to save user to database")
}

// Connect to Signal
ce.User.Connect()
Expand Down Expand Up @@ -415,7 +425,7 @@ var cmdDeleteAllPortals = &commands.FullHandler{
}

func fnDeleteAllPortals(ce *WrappedCommandEvent) {
portals := ce.Bridge.getAllPortals()
portals := ce.Bridge.GetAllPortalsWithMXID()
var portalsToDelete []*Portal

if ce.User.Admin {
Expand Down Expand Up @@ -465,3 +475,39 @@ func fnDeleteAllPortals(ce *WrappedCommandEvent) {
ce.Reply("Finished background cleanup of deleted portal rooms.")
}()
}

var cmdCleanupLostPortals = &commands.FullHandler{
Func: wrapCommand(fnCleanupLostPortals),
Name: "cleanup-lost-portals",
Help: commands.HelpMeta{
Section: HelpSectionPortalManagement,
Description: "Clean up portals that were discarded due to the receiver not being logged into the bridge",
},
RequiresAdmin: true,
}

func fnCleanupLostPortals(ce *WrappedCommandEvent) {
portals, err := ce.Bridge.DB.LostPortal.GetAll(context.TODO())
if err != nil {
ce.Reply("Failed to get portals: %v", err)
return
} else if len(portals) == 0 {
ce.Reply("No lost portals found")
return
}

ce.Reply("Found %d lost portals, deleting...", len(portals))
for _, portal := range portals {
dmUUID, err := uuid.Parse(portal.ChatID)
intent := ce.Bot
if err == nil {
intent = ce.Bridge.GetPuppetBySignalID(dmUUID).DefaultIntent()
}
ce.Bridge.CleanupRoom(ce.ZLog, intent, portal.MXID, false)
err = portal.Delete(context.TODO())
if err != nil {
ce.ZLog.Err(err).Msg("Failed to delete lost portal from database after cleanup")
}
}
ce.Reply("Finished cleaning up portals")
}
19 changes: 14 additions & 5 deletions custompuppet.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,20 @@
package main

import (
"context"
"fmt"

"maunium.net/go/mautrix/id"
)

func (puppet *Puppet) SwitchCustomMXID(accessToken string, mxid id.UserID) error {
puppet.CustomMXID = mxid
puppet.AccessToken = accessToken
puppet.Update()
err := puppet.StartCustomMXID(false)
err := puppet.Update(context.TODO())
if err != nil {
return fmt.Errorf("failed to save access token: %w", err)
}
err = puppet.StartCustomMXID(false)
if err != nil {
return err
}
Expand All @@ -44,7 +50,10 @@ func (puppet *Puppet) ClearCustomMXID() {
puppet.customIntent = nil
puppet.customUser = nil
if save {
puppet.Update()
err := puppet.Update(context.TODO())
if err != nil {
puppet.log.Err(err).Msg("Failed to clear custom MXID")
}
}
}

Expand All @@ -59,11 +68,11 @@ func (puppet *Puppet) StartCustomMXID(reloginOnFail bool) error {
puppet.bridge.puppetsLock.Unlock()
if puppet.AccessToken != newAccessToken {
puppet.AccessToken = newAccessToken
puppet.Update()
err = puppet.Update(context.TODO())
}
puppet.customIntent = newIntent
puppet.customUser = puppet.bridge.GetUserByMXID(puppet.CustomMXID)
return nil
return err
}

func (user *User) tryAutomaticDoublePuppeting() {
Expand Down
47 changes: 12 additions & 35 deletions database/database.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// mautrix-signal - A Matrix-signal puppeting bridge.
// Copyright (C) 2023 Scott Weber
// Copyright (C) 2023 Scott Weber, Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
Expand All @@ -22,7 +22,6 @@ import (
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
"go.mau.fi/util/dbutil"
"maunium.net/go/maulogger/v2"

"go.mau.fi/mautrix-signal/database/upgrades"
)
Expand All @@ -32,45 +31,23 @@ type Database struct {

User *UserQuery
Portal *PortalQuery
LostPortal *LostPortalQuery
Puppet *PuppetQuery
Message *MessageQuery
Reaction *ReactionQuery
DisappearingMessage *DisappearingMessageQuery
}

func New(baseDB *dbutil.Database, log maulogger.Logger) *Database {
db := &Database{Database: baseDB}
func New(db *dbutil.Database) *Database {
db.UpgradeTable = upgrades.Table
db.User = &UserQuery{
db: db,
log: log.Sub("User"),
return &Database{
Database: db,
User: &UserQuery{dbutil.MakeQueryHelper(db, newUser)},
Portal: &PortalQuery{dbutil.MakeQueryHelper(db, newPortal)},
LostPortal: &LostPortalQuery{dbutil.MakeQueryHelper(db, newLostPortal)},
Puppet: &PuppetQuery{dbutil.MakeQueryHelper(db, newPuppet)},
Message: &MessageQuery{dbutil.MakeQueryHelper(db, newMessage)},
Reaction: &ReactionQuery{dbutil.MakeQueryHelper(db, newReaction)},
DisappearingMessage: &DisappearingMessageQuery{dbutil.MakeQueryHelper(db, newDisappearingMessage)},
}
db.Portal = &PortalQuery{
db: db,
log: log.Sub("Portal"),
}
db.Puppet = &PuppetQuery{
db: db,
log: log.Sub("Puppet"),
}
db.Message = &MessageQuery{
db: db,
log: log.Sub("Message"),
}
db.Reaction = &ReactionQuery{
db: db,
log: log.Sub("Reaction"),
}
db.DisappearingMessage = &DisappearingMessageQuery{
db: db,
log: log.Sub("DisappearingMessage"),
}
return db
}

func strPtr(val string) *string {
if val == "" {
return nil
}
return &val
}
Loading

0 comments on commit 35db0ab

Please sign in to comment.