From d4f27ece719d7ec68093d8129bf53c15cb6f2013 Mon Sep 17 00:00:00 2001 From: Jefferson Felix Date: Fri, 13 Feb 2026 12:38:34 -0300 Subject: [PATCH 1/7] fix: s3 delivery after connect --- handlers.go | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/handlers.go b/handlers.go index 285a7a2f..2be05072 100644 --- a/handlers.go +++ b/handlers.go @@ -154,15 +154,16 @@ func (s *server) authalice(next http.Handler) http.Handler { if !found { log.Info().Msg("Looking for user information in DB") // Checks DB from matching user and store user values in context - rows, err := s.db.Query("SELECT id,name,webhook,jid,events,proxy_url,qrcode,history,hmac_key IS NOT NULL AND length(hmac_key) > 0 FROM users WHERE token=$1 LIMIT 1", token) + rows, err := s.db.Query("SELECT id,name,webhook,jid,events,proxy_url,qrcode,history,hmac_key IS NOT NULL AND length(hmac_key) > 0,CASE WHEN s3_enabled THEN 'true' ELSE 'false' END,COALESCE(media_delivery, 'base64') FROM users WHERE token=$1 LIMIT 1", token) if err != nil { s.Respond(w, r, http.StatusInternalServerError, err) return } defer rows.Close() var history sql.NullInt64 + var s3Enabled, mediaDelivery string for rows.Next() { - err = rows.Scan(&txtid, &name, &webhook, &jid, &events, &proxy_url, &qrcode, &history, &hasHmac) + err = rows.Scan(&txtid, &name, &webhook, &jid, &events, &proxy_url, &qrcode, &history, &hasHmac, &s3Enabled, &mediaDelivery) if err != nil { s.Respond(w, r, http.StatusInternalServerError, err) return @@ -176,16 +177,18 @@ func (s *server) authalice(next http.Handler) http.Handler { log.Debug().Str("userId", txtid).Bool("historyValid", history.Valid).Int64("historyValue", history.Int64).Str("historyStr", historyStr).Msg("User authentication - history debug") v := Values{map[string]string{ - "Id": txtid, - "Name": name, - "Jid": jid, - "Webhook": webhook, - "Token": token, - "Proxy": proxy_url, - "Events": events, - "Qrcode": qrcode, - "History": historyStr, - "HasHmac": strconv.FormatBool(hasHmac), + "Id": txtid, + "Name": name, + "Jid": jid, + "Webhook": webhook, + "Token": token, + "Proxy": proxy_url, + "Events": events, + "Qrcode": qrcode, + "History": historyStr, + "HasHmac": strconv.FormatBool(hasHmac), + "S3Enabled": s3Enabled, + "MediaDelivery": mediaDelivery, }} userinfocache.Set(token, v, cache.NoExpiration) From 580fcd038e1894db6377f2f006cd103095c9c848 Mon Sep 17 00:00:00 2001 From: Jefferson Felix Date: Sat, 14 Feb 2026 17:26:09 -0300 Subject: [PATCH 2/7] fix: ensure s3 client started before upload media --- helpers.go | 1 + main.go | 3 ++ s3manager.go | 57 ++++++++++++++++++++++++++++++++++++- wmiau.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 1 deletion(-) diff --git a/helpers.go b/helpers.go index c1d8fabc..e1f4aabe 100644 --- a/helpers.go +++ b/helpers.go @@ -538,6 +538,7 @@ func ProcessOutgoingMedia(userID string, contactJID string, messageID string, da // Process S3 upload if enabled if s3Config.Enabled && (s3Config.MediaDelivery == "s3" || s3Config.MediaDelivery == "both") { + ensureS3ClientForUser(db, userID) // Process S3 upload (outgoing messages are always in outbox) s3Data, err := GetS3Manager().ProcessMediaForS3( context.Background(), diff --git a/main.go b/main.go index f70e7ff3..b0502240 100755 --- a/main.go +++ b/main.go @@ -377,6 +377,9 @@ func main() { } }() + // Set DB reference in S3Manager for lazy client initialization + GetS3Manager().SetDB(db) + // Initialize the schema if err = initializeSchema(db); err != nil { log.Fatal().Err(err).Msg("Failed to initialize schema") diff --git a/s3manager.go b/s3manager.go index ba5bd502..5e7f3265 100644 --- a/s3manager.go +++ b/s3manager.go @@ -9,6 +9,7 @@ import ( "time" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/jmoiron/sqlx" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" @@ -32,6 +33,7 @@ type S3Config struct { // S3Manager manages S3 operations type S3Manager struct { mu sync.RWMutex + db *sqlx.DB clients map[string]*s3.Client configs map[string]*S3Config } @@ -47,6 +49,53 @@ func GetS3Manager() *S3Manager { return s3Manager } +// SetDB sets the database reference for lazy S3 client initialization +func (m *S3Manager) SetDB(db *sqlx.DB) { + m.mu.Lock() + defer m.mu.Unlock() + m.db = db +} + +// tryLazyInitFromDB attempts to load S3 config from DB and initialize client. Returns true if successful. +func (m *S3Manager) tryLazyInitFromDB(userID string) bool { + m.mu.RLock() + db := m.db + m.mu.RUnlock() + if db == nil { + return false + } + var s3DbConfig struct { + Enabled bool `db:"s3_enabled"` + Endpoint string `db:"s3_endpoint"` + Region string `db:"s3_region"` + Bucket string `db:"s3_bucket"` + AccessKey string `db:"s3_access_key"` + SecretKey string `db:"s3_secret_key"` + PathStyle bool `db:"s3_path_style"` + PublicURL string `db:"s3_public_url"` + MediaDelivery string `db:"media_delivery"` + RetentionDays int `db:"s3_retention_days"` + } + query := `SELECT s3_enabled, s3_endpoint, s3_region, s3_bucket, s3_access_key, s3_secret_key, s3_path_style, s3_public_url, COALESCE(media_delivery, 'base64') AS media_delivery, COALESCE(s3_retention_days, 30) AS s3_retention_days FROM users WHERE id = $1` + query = db.Rebind(query) + if err := db.Get(&s3DbConfig, query, userID); err != nil || !s3DbConfig.Enabled { + return false + } + config := &S3Config{ + Enabled: s3DbConfig.Enabled, + Endpoint: s3DbConfig.Endpoint, + Region: s3DbConfig.Region, + Bucket: s3DbConfig.Bucket, + AccessKey: s3DbConfig.AccessKey, + SecretKey: s3DbConfig.SecretKey, + PathStyle: s3DbConfig.PathStyle, + PublicURL: s3DbConfig.PublicURL, + MediaDelivery: s3DbConfig.MediaDelivery, + RetentionDays: s3DbConfig.RetentionDays, + } + return m.InitializeS3Client(userID, config) == nil +} + // InitializeS3Client creates or updates S3 client for a user func (m *S3Manager) InitializeS3Client(userID string, config *S3Config) error { if !config.Enabled { @@ -192,7 +241,13 @@ func (m *S3Manager) GenerateS3Key(userID, contactJID, messageID string, mimeType func (m *S3Manager) UploadToS3(ctx context.Context, userID string, key string, data []byte, mimeType string) error { client, config, ok := m.GetClient(userID) if !ok { - return fmt.Errorf("S3 client not initialized for user %s", userID) + // Try lazy init from DB if available (handles reconnect-after-restart) + if m.tryLazyInitFromDB(userID) { + client, config, ok = m.GetClient(userID) + } + if !ok { + return fmt.Errorf("S3 client not initialized for user %s", userID) + } } // Set content type and cache headers for preview diff --git a/wmiau.go b/wmiau.go index f34012ac..2739b135 100644 --- a/wmiau.go +++ b/wmiau.go @@ -43,6 +43,43 @@ type MyClient struct { s *server } +// ensureS3ClientForUser loads S3 config from DB and initializes client if not already present (lazy init for reconnect-after-restart) +func ensureS3ClientForUser(db *sqlx.DB, userID string) { + if _, _, ok := GetS3Manager().GetClient(userID); ok { + return + } + var s3DbConfig struct { + Enabled bool `db:"s3_enabled"` + Endpoint string `db:"s3_endpoint"` + Region string `db:"s3_region"` + Bucket string `db:"s3_bucket"` + AccessKey string `db:"s3_access_key"` + SecretKey string `db:"s3_secret_key"` + PathStyle bool `db:"s3_path_style"` + PublicURL string `db:"s3_public_url"` + MediaDelivery string `db:"media_delivery"` + RetentionDays int `db:"s3_retention_days"` + } + query := `SELECT s3_enabled, s3_endpoint, s3_region, s3_bucket, s3_access_key, s3_secret_key, s3_path_style, s3_public_url, COALESCE(media_delivery, 'base64') AS media_delivery, COALESCE(s3_retention_days, 30) AS s3_retention_days FROM users WHERE id = $1` + query = db.Rebind(query) + if err := db.Get(&s3DbConfig, query, userID); err != nil || !s3DbConfig.Enabled { + return + } + config := &S3Config{ + Enabled: s3DbConfig.Enabled, + Endpoint: s3DbConfig.Endpoint, + Region: s3DbConfig.Region, + Bucket: s3DbConfig.Bucket, + AccessKey: s3DbConfig.AccessKey, + SecretKey: s3DbConfig.SecretKey, + PathStyle: s3DbConfig.PathStyle, + PublicURL: s3DbConfig.PublicURL, + MediaDelivery: s3DbConfig.MediaDelivery, + RetentionDays: s3DbConfig.RetentionDays, + } + _ = GetS3Manager().InitializeS3Client(userID, config) +} + func sendToGlobalWebHook(jsonData []byte, token string, userID string) { jsonDataStr := string(jsonData) @@ -461,6 +498,39 @@ func (s *server) startClient(userID string, textjid string, token string, subscr } clientManager.SetHTTPClient(userID, httpClient) + // Initialize S3 client if configured (needed when user reconnects after container restart - connectOnStartup only runs for connected=1) + var s3DbConfig struct { + Enabled bool `db:"s3_enabled"` + Endpoint string `db:"s3_endpoint"` + Region string `db:"s3_region"` + Bucket string `db:"s3_bucket"` + AccessKey string `db:"s3_access_key"` + SecretKey string `db:"s3_secret_key"` + PathStyle bool `db:"s3_path_style"` + PublicURL string `db:"s3_public_url"` + MediaDelivery string `db:"media_delivery"` + RetentionDays int `db:"s3_retention_days"` + } + if err := s.db.Get(&s3DbConfig, `SELECT s3_enabled, s3_endpoint, s3_region, s3_bucket, s3_access_key, s3_secret_key, s3_path_style, s3_public_url, COALESCE(media_delivery, 'base64'), COALESCE(s3_retention_days, 30) FROM users WHERE id = $1`, userID); err == nil && s3DbConfig.Enabled { + config := &S3Config{ + Enabled: s3DbConfig.Enabled, + Endpoint: s3DbConfig.Endpoint, + Region: s3DbConfig.Region, + Bucket: s3DbConfig.Bucket, + AccessKey: s3DbConfig.AccessKey, + SecretKey: s3DbConfig.SecretKey, + PathStyle: s3DbConfig.PathStyle, + PublicURL: s3DbConfig.PublicURL, + MediaDelivery: s3DbConfig.MediaDelivery, + RetentionDays: s3DbConfig.RetentionDays, + } + if initErr := GetS3Manager().InitializeS3Client(userID, config); initErr != nil { + log.Error().Err(initErr).Str("userID", userID).Msg("Failed to initialize S3 client on connect") + } else { + log.Info().Str("userID", userID).Msg("S3 client initialized on connect") + } + } + if client.Store.ID == nil { // No ID stored, new login qrChan, err := client.GetQRChannel(context.Background()) @@ -817,6 +887,11 @@ func (mycli *MyClient) myEventHandler(rawEvt interface{}) { s3Config.MediaDelivery = myuserinfo.(Values).Get("MediaDelivery") } + // Lazy init S3 client if needed (handles reconnect-after-restart when connectOnStartup skipped this user) + if s3Config.Enabled == "true" && (s3Config.MediaDelivery == "s3" || s3Config.MediaDelivery == "both") { + ensureS3ClientForUser(mycli.db, txtid) + } + postmap["type"] = "Message" dowebhook = 1 metaParts := []string{fmt.Sprintf("pushname: %s", evt.Info.PushName), fmt.Sprintf("timestamp: %s", evt.Info.Timestamp)} @@ -867,6 +942,7 @@ func (mycli *MyClient) myEventHandler(rawEvt interface{}) { // Process S3 upload if enabled if s3Config.Enabled == "true" && (s3Config.MediaDelivery == "s3" || s3Config.MediaDelivery == "both") { + ensureS3ClientForUser(mycli.db, txtid) // Get sender JID for inbox/outbox determination isIncoming := evt.Info.IsFromMe == false contactJID := evt.Info.Sender.String() @@ -955,6 +1031,7 @@ func (mycli *MyClient) myEventHandler(rawEvt interface{}) { // Process S3 upload if enabled if s3Config.Enabled == "true" && (s3Config.MediaDelivery == "s3" || s3Config.MediaDelivery == "both") { + ensureS3ClientForUser(mycli.db, txtid) // Get sender JID for inbox/outbox determination isIncoming := evt.Info.IsFromMe == false contactJID := evt.Info.Sender.String() @@ -1048,6 +1125,7 @@ func (mycli *MyClient) myEventHandler(rawEvt interface{}) { // Process S3 upload if enabled if s3Config.Enabled == "true" && (s3Config.MediaDelivery == "s3" || s3Config.MediaDelivery == "both") { + ensureS3ClientForUser(mycli.db, txtid) // Get sender JID for inbox/outbox determination isIncoming := evt.Info.IsFromMe == false contactJID := evt.Info.Sender.String() @@ -1130,6 +1208,7 @@ func (mycli *MyClient) myEventHandler(rawEvt interface{}) { // Process S3 upload if enabled if s3Config.Enabled == "true" && (s3Config.MediaDelivery == "s3" || s3Config.MediaDelivery == "both") { + ensureS3ClientForUser(mycli.db, txtid) // Get sender JID for inbox/outbox determination isIncoming := evt.Info.IsFromMe == false contactJID := evt.Info.Sender.String() @@ -1212,6 +1291,7 @@ func (mycli *MyClient) myEventHandler(rawEvt interface{}) { // if using S3 (same stream as other media) if s3Config.Enabled == "true" && (s3Config.MediaDelivery == "s3" || s3Config.MediaDelivery == "both") { + ensureS3ClientForUser(mycli.db, txtid) isIncoming := evt.Info.IsFromMe == false contactJID := evt.Info.Sender.String() if evt.Info.IsGroup { From 3dc4b532cdba74fec12306c65e58526c4e10a367 Mon Sep 17 00:00:00 2001 From: Jefferson Felix Date: Sat, 14 Feb 2026 17:33:52 -0300 Subject: [PATCH 3/7] refactor: remove repetead code --- helpers.go | 2 +- s3manager.go | 9 ++-- wmiau.go | 124 +++++---------------------------------------------- 3 files changed, 17 insertions(+), 118 deletions(-) diff --git a/helpers.go b/helpers.go index e1f4aabe..83c4c628 100644 --- a/helpers.go +++ b/helpers.go @@ -538,7 +538,7 @@ func ProcessOutgoingMedia(userID string, contactJID string, messageID string, da // Process S3 upload if enabled if s3Config.Enabled && (s3Config.MediaDelivery == "s3" || s3Config.MediaDelivery == "both") { - ensureS3ClientForUser(db, userID) + ensureS3ClientForUser(userID) // Process S3 upload (outgoing messages are always in outbox) s3Data, err := GetS3Manager().ProcessMediaForS3( context.Background(), diff --git a/s3manager.go b/s3manager.go index 5e7f3265..77c71e9a 100644 --- a/s3manager.go +++ b/s3manager.go @@ -56,8 +56,11 @@ func (m *S3Manager) SetDB(db *sqlx.DB) { m.db = db } -// tryLazyInitFromDB attempts to load S3 config from DB and initialize client. Returns true if successful. -func (m *S3Manager) tryLazyInitFromDB(userID string) bool { +// EnsureClientFromDB loads S3 config from DB and initializes client if enabled. Returns true if client is available. +func (m *S3Manager) EnsureClientFromDB(userID string) bool { + if _, _, ok := m.GetClient(userID); ok { + return true + } m.mu.RLock() db := m.db m.mu.RUnlock() @@ -242,7 +245,7 @@ func (m *S3Manager) UploadToS3(ctx context.Context, userID string, key string, d client, config, ok := m.GetClient(userID) if !ok { // Try lazy init from DB if available (handles reconnect-after-restart) - if m.tryLazyInitFromDB(userID) { + if m.EnsureClientFromDB(userID) { client, config, ok = m.GetClient(userID) } if !ok { diff --git a/wmiau.go b/wmiau.go index 2739b135..517ea280 100644 --- a/wmiau.go +++ b/wmiau.go @@ -44,40 +44,8 @@ type MyClient struct { } // ensureS3ClientForUser loads S3 config from DB and initializes client if not already present (lazy init for reconnect-after-restart) -func ensureS3ClientForUser(db *sqlx.DB, userID string) { - if _, _, ok := GetS3Manager().GetClient(userID); ok { - return - } - var s3DbConfig struct { - Enabled bool `db:"s3_enabled"` - Endpoint string `db:"s3_endpoint"` - Region string `db:"s3_region"` - Bucket string `db:"s3_bucket"` - AccessKey string `db:"s3_access_key"` - SecretKey string `db:"s3_secret_key"` - PathStyle bool `db:"s3_path_style"` - PublicURL string `db:"s3_public_url"` - MediaDelivery string `db:"media_delivery"` - RetentionDays int `db:"s3_retention_days"` - } - query := `SELECT s3_enabled, s3_endpoint, s3_region, s3_bucket, s3_access_key, s3_secret_key, s3_path_style, s3_public_url, COALESCE(media_delivery, 'base64') AS media_delivery, COALESCE(s3_retention_days, 30) AS s3_retention_days FROM users WHERE id = $1` - query = db.Rebind(query) - if err := db.Get(&s3DbConfig, query, userID); err != nil || !s3DbConfig.Enabled { - return - } - config := &S3Config{ - Enabled: s3DbConfig.Enabled, - Endpoint: s3DbConfig.Endpoint, - Region: s3DbConfig.Region, - Bucket: s3DbConfig.Bucket, - AccessKey: s3DbConfig.AccessKey, - SecretKey: s3DbConfig.SecretKey, - PathStyle: s3DbConfig.PathStyle, - PublicURL: s3DbConfig.PublicURL, - MediaDelivery: s3DbConfig.MediaDelivery, - RetentionDays: s3DbConfig.RetentionDays, - } - _ = GetS3Manager().InitializeS3Client(userID, config) +func ensureS3ClientForUser(userID string) { + GetS3Manager().EnsureClientFromDB(userID) } func sendToGlobalWebHook(jsonData []byte, token string, userID string) { @@ -332,49 +300,7 @@ func (s *server) connectOnStartup() { // Initialize S3 client if configured go func(userID string) { - var s3Config struct { - Enabled bool `db:"s3_enabled"` - Endpoint string `db:"s3_endpoint"` - Region string `db:"s3_region"` - Bucket string `db:"s3_bucket"` - AccessKey string `db:"s3_access_key"` - SecretKey string `db:"s3_secret_key"` - PathStyle bool `db:"s3_path_style"` - PublicURL string `db:"s3_public_url"` - RetentionDays int `db:"s3_retention_days"` - } - - err := s.db.Get(&s3Config, ` - SELECT s3_enabled, s3_endpoint, s3_region, s3_bucket, - s3_access_key, s3_secret_key, s3_path_style, - s3_public_url, s3_retention_days - FROM users WHERE id = $1`, userID) - - if err != nil { - log.Error().Err(err).Str("userID", userID).Msg("Failed to get S3 config") - return - } - - if s3Config.Enabled { - config := &S3Config{ - Enabled: s3Config.Enabled, - Endpoint: s3Config.Endpoint, - Region: s3Config.Region, - Bucket: s3Config.Bucket, - AccessKey: s3Config.AccessKey, - SecretKey: s3Config.SecretKey, - PathStyle: s3Config.PathStyle, - PublicURL: s3Config.PublicURL, - RetentionDays: s3Config.RetentionDays, - } - - err = GetS3Manager().InitializeS3Client(userID, config) - if err != nil { - log.Error().Err(err).Str("userID", userID).Msg("Failed to initialize S3 client on startup") - } else { - log.Info().Str("userID", userID).Msg("S3 client initialized on startup") - } - } + GetS3Manager().EnsureClientFromDB(userID) }(txtid) } } @@ -499,37 +425,7 @@ func (s *server) startClient(userID string, textjid string, token string, subscr clientManager.SetHTTPClient(userID, httpClient) // Initialize S3 client if configured (needed when user reconnects after container restart - connectOnStartup only runs for connected=1) - var s3DbConfig struct { - Enabled bool `db:"s3_enabled"` - Endpoint string `db:"s3_endpoint"` - Region string `db:"s3_region"` - Bucket string `db:"s3_bucket"` - AccessKey string `db:"s3_access_key"` - SecretKey string `db:"s3_secret_key"` - PathStyle bool `db:"s3_path_style"` - PublicURL string `db:"s3_public_url"` - MediaDelivery string `db:"media_delivery"` - RetentionDays int `db:"s3_retention_days"` - } - if err := s.db.Get(&s3DbConfig, `SELECT s3_enabled, s3_endpoint, s3_region, s3_bucket, s3_access_key, s3_secret_key, s3_path_style, s3_public_url, COALESCE(media_delivery, 'base64'), COALESCE(s3_retention_days, 30) FROM users WHERE id = $1`, userID); err == nil && s3DbConfig.Enabled { - config := &S3Config{ - Enabled: s3DbConfig.Enabled, - Endpoint: s3DbConfig.Endpoint, - Region: s3DbConfig.Region, - Bucket: s3DbConfig.Bucket, - AccessKey: s3DbConfig.AccessKey, - SecretKey: s3DbConfig.SecretKey, - PathStyle: s3DbConfig.PathStyle, - PublicURL: s3DbConfig.PublicURL, - MediaDelivery: s3DbConfig.MediaDelivery, - RetentionDays: s3DbConfig.RetentionDays, - } - if initErr := GetS3Manager().InitializeS3Client(userID, config); initErr != nil { - log.Error().Err(initErr).Str("userID", userID).Msg("Failed to initialize S3 client on connect") - } else { - log.Info().Str("userID", userID).Msg("S3 client initialized on connect") - } - } + GetS3Manager().EnsureClientFromDB(userID) if client.Store.ID == nil { // No ID stored, new login @@ -889,7 +785,7 @@ func (mycli *MyClient) myEventHandler(rawEvt interface{}) { // Lazy init S3 client if needed (handles reconnect-after-restart when connectOnStartup skipped this user) if s3Config.Enabled == "true" && (s3Config.MediaDelivery == "s3" || s3Config.MediaDelivery == "both") { - ensureS3ClientForUser(mycli.db, txtid) + ensureS3ClientForUser(txtid) } postmap["type"] = "Message" @@ -942,7 +838,7 @@ func (mycli *MyClient) myEventHandler(rawEvt interface{}) { // Process S3 upload if enabled if s3Config.Enabled == "true" && (s3Config.MediaDelivery == "s3" || s3Config.MediaDelivery == "both") { - ensureS3ClientForUser(mycli.db, txtid) + ensureS3ClientForUser(txtid) // Get sender JID for inbox/outbox determination isIncoming := evt.Info.IsFromMe == false contactJID := evt.Info.Sender.String() @@ -1031,7 +927,7 @@ func (mycli *MyClient) myEventHandler(rawEvt interface{}) { // Process S3 upload if enabled if s3Config.Enabled == "true" && (s3Config.MediaDelivery == "s3" || s3Config.MediaDelivery == "both") { - ensureS3ClientForUser(mycli.db, txtid) + ensureS3ClientForUser(txtid) // Get sender JID for inbox/outbox determination isIncoming := evt.Info.IsFromMe == false contactJID := evt.Info.Sender.String() @@ -1125,7 +1021,7 @@ func (mycli *MyClient) myEventHandler(rawEvt interface{}) { // Process S3 upload if enabled if s3Config.Enabled == "true" && (s3Config.MediaDelivery == "s3" || s3Config.MediaDelivery == "both") { - ensureS3ClientForUser(mycli.db, txtid) + ensureS3ClientForUser(txtid) // Get sender JID for inbox/outbox determination isIncoming := evt.Info.IsFromMe == false contactJID := evt.Info.Sender.String() @@ -1208,7 +1104,7 @@ func (mycli *MyClient) myEventHandler(rawEvt interface{}) { // Process S3 upload if enabled if s3Config.Enabled == "true" && (s3Config.MediaDelivery == "s3" || s3Config.MediaDelivery == "both") { - ensureS3ClientForUser(mycli.db, txtid) + ensureS3ClientForUser(txtid) // Get sender JID for inbox/outbox determination isIncoming := evt.Info.IsFromMe == false contactJID := evt.Info.Sender.String() @@ -1291,7 +1187,7 @@ func (mycli *MyClient) myEventHandler(rawEvt interface{}) { // if using S3 (same stream as other media) if s3Config.Enabled == "true" && (s3Config.MediaDelivery == "s3" || s3Config.MediaDelivery == "both") { - ensureS3ClientForUser(mycli.db, txtid) + ensureS3ClientForUser(txtid) isIncoming := evt.Info.IsFromMe == false contactJID := evt.Info.Sender.String() if evt.Info.IsGroup { From 92e2d146fadbff99a764dfd8e147ef55dd97abd9 Mon Sep 17 00:00:00 2001 From: devLucasMoraes Date: Sat, 28 Feb 2026 22:19:03 -0300 Subject: [PATCH 4/7] fix: resolve issue with S3 objects not deleting on /full route This reorders the logic in DeleteUserComplete so the S3 config is queried before the user is deleted from the database. Fixes #268 --- handlers.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/handlers.go b/handlers.go index 2be05072..2deec02d 100644 --- a/handlers.go +++ b/handlers.go @@ -5263,7 +5263,15 @@ func (s *server) DeleteUserComplete() http.HandlerFunc { client.Disconnect() } - // 2. Remove from DB + // 2. Query S3 config before deleting the user + var s3Enabled bool + err = s.db.QueryRow("SELECT s3_enabled FROM users WHERE id = $1", id).Scan(&s3Enabled) + if err != nil { + log.Error().Err(err).Str("id", id).Msg("problem retrieving user s3 configuration") + // Continue anyway since we have the ID to delete local files + } + + // 3. Remove from DB _, err = s.db.Exec("DELETE FROM users WHERE id = $1", id) if err != nil { s.respondWithJSON(w, http.StatusInternalServerError, map[string]interface{}{ @@ -5275,13 +5283,13 @@ func (s *server) DeleteUserComplete() http.HandlerFunc { return } - // 3. Cleanup from memory + // 4. Cleanup from memory clientManager.DeleteWhatsmeowClient(id) clientManager.DeleteMyClient(id) clientManager.DeleteHTTPClient(id) userinfocache.Delete(token) - // 4. Remove media files + // 5. Remove media files userDirectory := filepath.Join(s.exPath, "files", id) if stat, err := os.Stat(userDirectory); err == nil && stat.IsDir() { log.Info().Str("dir", userDirectory).Msg("deleting media and history files from disk") @@ -5291,9 +5299,7 @@ func (s *server) DeleteUserComplete() http.HandlerFunc { } } - // 5. Remove files from S3 (if enabled) - var s3Enabled bool - err = s.db.QueryRow("SELECT s3_enabled FROM users WHERE id = $1", id).Scan(&s3Enabled) + // 6. Remove files from S3 (if enabled) if err == nil && s3Enabled { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() From 1a28af22185b29d7c8903b231c3cc995020fca66 Mon Sep 17 00:00:00 2001 From: devLucasMoraes Date: Sun, 1 Mar 2026 23:41:47 -0300 Subject: [PATCH 5/7] ci: update Docker image repository from asternic/wuzapi to devlucasmoraes/wuzapi. --- .github/workflows/docker-publish.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 35df6c64..b88f9ce8 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -14,35 +14,35 @@ jobs: runs-on: ubuntu-latest environment: DOCKER if: github.event_name != 'pull_request' || github.event.pull_request.merged == true - + steps: - name: Check out the repo uses: actions/checkout@v4 - + - name: Set up QEMU uses: docker/setup-qemu-action@v3 - + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: driver-opts: | image=moby/buildkit:buildx-stable-1 - + - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - + - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 with: - images: asternic/wuzapi + images: devlucasmoraes/wuzapi tags: | type=raw,value=latest type=sha,format=short - + - name: Build and push Docker image uses: docker/build-push-action@v5 with: @@ -52,4 +52,4 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha - cache-to: type=gha,mode=max + cache-to: type=gha,mode=max From 2fe69d4279c5537feb5f4eded52c7ca1202d7f38 Mon Sep 17 00:00:00 2001 From: devLucasMoraes Date: Sun, 1 Mar 2026 23:46:57 -0300 Subject: [PATCH 6/7] Trigger GitHub Actions From aa12c5a3510ce8e29ca13075e86e191b81268813 Mon Sep 17 00:00:00 2001 From: devLucasMoraes Date: Mon, 2 Mar 2026 00:09:59 -0300 Subject: [PATCH 7/7] feat: Add manual trigger to the Docker publish workflow. --- .github/workflows/docker-publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index b88f9ce8..9446ce4c 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -8,6 +8,7 @@ on: branches: - main types: [closed] + workflow_dispatch: jobs: build-and-push: