diff --git a/flutter/Makefile b/flutter/Makefile index c710369..a54bda6 100644 --- a/flutter/Makefile +++ b/flutter/Makefile @@ -4,6 +4,12 @@ run: flutter pub run build_runner build flutter run +run-android: + ping -c1 10.10.10.177 + adb connect 10.10.10.177:5555 + flutter pub run build_runner build + flutter run -d 10.10.10.177:5555 + test: dart analyze diff --git a/scnserver/db/impl/primary/clients.go b/scnserver/db/impl/primary/clients.go index 468cb84..e68fe74 100644 --- a/scnserver/db/impl/primary/clients.go +++ b/scnserver/db/impl/primary/clients.go @@ -52,6 +52,18 @@ func (db *Database) GetClient(ctx db.TxContext, userid models.UserID, clientid m }, sq.SModeExtended, sq.Safe) } +func (db *Database) GetClientOpt(ctx db.TxContext, userid models.UserID, clientid models.ClientID) (*models.Client, error) { + tx, err := ctx.GetOrCreateTransaction(db) + if err != nil { + return nil, err + } + + return sq.QuerySingleOpt[models.Client](ctx, tx, "SELECT * FROM clients WHERE deleted=0 AND user_id = :uid AND client_id = :cid LIMIT 1", sq.PP{ + "uid": userid, + "cid": clientid, + }, sq.SModeExtended, sq.Safe) +} + func (db *Database) DeleteClient(ctx db.TxContext, clientid models.ClientID) error { tx, err := ctx.GetOrCreateTransaction(db) if err != nil { diff --git a/scnserver/db/impl/primary/users.go b/scnserver/db/impl/primary/users.go index bf49199..72daf78 100644 --- a/scnserver/db/impl/primary/users.go +++ b/scnserver/db/impl/primary/users.go @@ -62,6 +62,15 @@ func (db *Database) GetUser(ctx db.TxContext, userid models.UserID) (models.User return sq.QuerySingle[models.User](ctx, tx, "SELECT * FROM users WHERE user_id = :uid LIMIT 1", sq.PP{"uid": userid}, sq.SModeExtended, sq.Safe) } +func (db *Database) GetUserOpt(ctx db.TxContext, userid models.UserID) (*models.User, error) { + tx, err := ctx.GetOrCreateTransaction(db) + if err != nil { + return nil, err + } + + return sq.QuerySingleOpt[models.User](ctx, tx, "SELECT * FROM users WHERE user_id = :uid LIMIT 1", sq.PP{"uid": userid}, sq.SModeExtended, sq.Safe) +} + func (db *Database) UpdateUserUsername(ctx db.TxContext, userid models.UserID, username *string) error { tx, err := ctx.GetOrCreateTransaction(db) if err != nil { diff --git a/scnserver/jobs/deliveryRetryJob.go b/scnserver/jobs/deliveryRetryJob.go index 87c99c0..ae5c450 100644 --- a/scnserver/jobs/deliveryRetryJob.go +++ b/scnserver/jobs/deliveryRetryJob.go @@ -134,12 +134,30 @@ func (j *DeliveryRetryJob) execute() (fastrr bool, err error) { func (j *DeliveryRetryJob) redeliver(ctx *simplectx.SimpleContext, delivery models.Delivery) { - client, err := j.app.Database.Primary.GetClient(ctx, delivery.ReceiverUserID, delivery.ReceiverClientID) + client, err := j.app.Database.Primary.GetClientOpt(ctx, delivery.ReceiverUserID, delivery.ReceiverClientID) if err != nil { log.Err(err).Str("ReceiverUserID", delivery.ReceiverUserID.String()).Str("ReceiverClientID", delivery.ReceiverClientID.String()).Msg("Failed to get client") ctx.RollbackTransaction() return } + if client == nil { + log.Error().Str("ReceiverUserID", delivery.ReceiverUserID.String()).Str("ReceiverClientID", delivery.ReceiverClientID.String()).Msg("Failed to get client (client no longer exists)") + + err = j.app.Database.Primary.SetDeliveryFailed(ctx, delivery) + if err != nil { + log.Err(err).Str("MessageID", delivery.MessageID.String()).Str("DeliveryID", delivery.DeliveryID.String()).Msg("Failed to update delivery") + ctx.RollbackTransaction() + return + } + log.Warn().Str("MessageID", delivery.MessageID.String()).Str("DeliveryID", delivery.DeliveryID.String()).Msg("Delivery failed because of [client==null] (set to FAILURE)") + + err = ctx.CommitTransaction() + if err != nil { + log.Err(err).Msg("Failed to commit transaction") + return + } + return + } msg, err := j.app.Database.Primary.GetMessage(ctx, delivery.MessageID, true) if err != nil { @@ -148,25 +166,6 @@ func (j *DeliveryRetryJob) redeliver(ctx *simplectx.SimpleContext, delivery mode return } - user, err := j.app.Database.Primary.GetUser(ctx, delivery.ReceiverUserID) - if err != nil { - log.Err(err).Str("ReceiverUserID", delivery.ReceiverUserID.String()).Msg("Failed to get user") - ctx.RollbackTransaction() - return - } - - channel, err := j.app.Database.Primary.GetChannelByID(ctx, msg.ChannelID) - if err != nil { - log.Err(err).Str("ChannelID", msg.ChannelID.String()).Msg("Failed to get channel") - ctx.RollbackTransaction() - return - } - if channel == nil { - log.Error().Str("ChannelID", msg.ChannelID.String()).Msg("Failed to get channel") - ctx.RollbackTransaction() - return - } - if msg.Deleted { err = j.app.Database.Primary.SetDeliveryFailed(ctx, delivery) if err != nil { @@ -174,9 +173,68 @@ func (j *DeliveryRetryJob) redeliver(ctx *simplectx.SimpleContext, delivery mode ctx.RollbackTransaction() return } - } else { + log.Warn().Str("MessageID", delivery.MessageID.String()).Str("DeliveryID", delivery.DeliveryID.String()).Msg("Delivery failed because of [message.deleted] (set to FAILURE)") - fcmDelivID, err := j.app.DeliverMessage(ctx, user, client, *channel, msg) + err = ctx.CommitTransaction() + if err != nil { + log.Err(err).Msg("Failed to commit transaction") + return + } + return + } + + user, err := j.app.Database.Primary.GetUserOpt(ctx, delivery.ReceiverUserID) + if err != nil { + log.Err(err).Str("ReceiverUserID", delivery.ReceiverUserID.String()).Msg("Failed to get user") + ctx.RollbackTransaction() + return + } + if user == nil { + log.Error().Str("ReceiverUserID", delivery.ReceiverUserID.String()).Str("ChannelID", msg.ChannelID.String()).Msg("Failed to get user (user no longer exists)") + + err = j.app.Database.Primary.SetDeliveryFailed(ctx, delivery) + if err != nil { + log.Err(err).Str("MessageID", delivery.MessageID.String()).Str("DeliveryID", delivery.DeliveryID.String()).Msg("Failed to update delivery") + ctx.RollbackTransaction() + return + } + log.Warn().Str("MessageID", delivery.MessageID.String()).Str("DeliveryID", delivery.DeliveryID.String()).Msg("Delivery failed because of [user==null] (set to FAILURE)") + + err = ctx.CommitTransaction() + if err != nil { + log.Err(err).Msg("Failed to commit transaction") + return + } + return + } + + channel, err := j.app.Database.Primary.GetChannelByID(ctx, msg.ChannelID) + if err != nil { + log.Err(err).Str("ChannelID", msg.ChannelID.String()).Msg("Failed to get channel") + ctx.RollbackTransaction() + return + } + if channel == nil { + log.Error().Str("ReceiverUserID", delivery.ReceiverUserID.String()).Str("ChannelID", msg.ChannelID.String()).Msg("Failed to get channel (client no longer exists)") + + err = j.app.Database.Primary.SetDeliveryFailed(ctx, delivery) + if err != nil { + log.Err(err).Str("MessageID", delivery.MessageID.String()).Str("DeliveryID", delivery.DeliveryID.String()).Msg("Failed to update delivery") + ctx.RollbackTransaction() + return + } + log.Warn().Str("MessageID", delivery.MessageID.String()).Str("DeliveryID", delivery.DeliveryID.String()).Msg("Delivery failed because of [channel==null] (set to FAILURE)") + + err = ctx.CommitTransaction() + if err != nil { + log.Err(err).Msg("Failed to commit transaction") + return + } + return + } + + { + fcmDelivID, err := j.app.DeliverMessage(ctx, *user, *client, *channel, msg) if err == nil { err = j.app.Database.Primary.SetDeliverySuccess(ctx, delivery, fcmDelivID) if err != nil { @@ -201,8 +259,11 @@ func (j *DeliveryRetryJob) redeliver(ctx *simplectx.SimpleContext, delivery mode } } + err = ctx.CommitTransaction() + if err != nil { + log.Err(err).Msg("Failed to commit transaction") + return + } } - err = ctx.CommitTransaction() - } diff --git a/scnserver/logic/message.go b/scnserver/logic/message.go index cd0c2cc..00aef9e 100644 --- a/scnserver/logic/message.go +++ b/scnserver/logic/message.go @@ -176,7 +176,7 @@ func (app *Application) SendMessage(g *gin.Context, ctx *AppContext, UserID *mod return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to inc token msg-counter", err)) } - log.Info().Msg(fmt.Sprintf("Sending new notification %s for user %s", msg.MessageID, UserID)) + log.Info().Msg(fmt.Sprintf("Sending new notification %s for user %s (to %d active subscriptions)", msg.MessageID, UserID, len(activeSubscriptions))) for _, sub := range activeSubscriptions { clients, err := app.Database.Primary.ListClients(ctx, sub.SubscriberUserID) @@ -186,6 +186,8 @@ func (app *Application) SendMessage(g *gin.Context, ctx *AppContext, UserID *mod for _, client := range clients { + log.Info().Msg(fmt.Sprintf("Create delivery for message %s to client %s (of user %s)", msg.MessageID, client.ClientID, client.UserID)) + fcmDelivID, err := app.DeliverMessage(ctx, user, client, channel, msg) if err != nil { _, err = app.Database.Primary.CreateRetryDelivery(ctx, client, msg) diff --git a/scnserver/swagger/swagger.json b/scnserver/swagger/swagger.json index 655ecd1..1046609 100644 --- a/scnserver/swagger/swagger.json +++ b/scnserver/swagger/swagger.json @@ -19,37 +19,61 @@ "parameters": [ { "type": "string", + "example": "test", + "name": "channel", + "in": "query" + }, + { + "type": "string", + "example": "This is a message", "name": "content", "in": "query" }, { "type": "string", + "example": "P3TNH8mvv14fm", + "name": "key", + "in": "query" + }, + { + "type": "string", + "example": "db8b0e6a-a08c-4646", "name": "msg_id", "in": "query" }, { + "enum": [ + 0, + 1, + 2 + ], "type": "integer", + "example": 1, "name": "priority", "in": "query" }, + { + "type": "string", + "example": "example-server", + "name": "sender_name", + "in": "query" + }, { "type": "number", + "example": 1669824037, "name": "timestamp", "in": "query" }, { "type": "string", + "example": "Hello World", "name": "title", "in": "query" }, - { - "type": "integer", - "name": "user_id", - "in": "query" - }, { "type": "string", - "name": "user_key", + "example": "7725", + "name": "user_id", "in": "query" }, { @@ -62,37 +86,61 @@ }, { "type": "string", + "example": "test", + "name": "channel", + "in": "formData" + }, + { + "type": "string", + "example": "This is a message", "name": "content", "in": "formData" }, { "type": "string", + "example": "P3TNH8mvv14fm", + "name": "key", + "in": "formData" + }, + { + "type": "string", + "example": "db8b0e6a-a08c-4646", "name": "msg_id", "in": "formData" }, { + "enum": [ + 0, + 1, + 2 + ], "type": "integer", + "example": 1, "name": "priority", "in": "formData" }, + { + "type": "string", + "example": "example-server", + "name": "sender_name", + "in": "formData" + }, { "type": "number", + "example": 1669824037, "name": "timestamp", "in": "formData" }, { "type": "string", + "example": "Hello World", "name": "title", "in": "formData" }, - { - "type": "integer", - "name": "user_id", - "in": "formData" - }, { "type": "string", - "name": "user_key", + "example": "7725", + "name": "user_id", "in": "formData" } ], @@ -2717,37 +2765,61 @@ "parameters": [ { "type": "string", + "example": "test", + "name": "channel", + "in": "query" + }, + { + "type": "string", + "example": "This is a message", "name": "content", "in": "query" }, { "type": "string", + "example": "P3TNH8mvv14fm", + "name": "key", + "in": "query" + }, + { + "type": "string", + "example": "db8b0e6a-a08c-4646", "name": "msg_id", "in": "query" }, { + "enum": [ + 0, + 1, + 2 + ], "type": "integer", + "example": 1, "name": "priority", "in": "query" }, + { + "type": "string", + "example": "example-server", + "name": "sender_name", + "in": "query" + }, { "type": "number", + "example": 1669824037, "name": "timestamp", "in": "query" }, { "type": "string", + "example": "Hello World", "name": "title", "in": "query" }, - { - "type": "integer", - "name": "user_id", - "in": "query" - }, { "type": "string", - "name": "user_key", + "example": "7725", + "name": "user_id", "in": "query" }, { @@ -2760,37 +2832,61 @@ }, { "type": "string", + "example": "test", + "name": "channel", + "in": "formData" + }, + { + "type": "string", + "example": "This is a message", "name": "content", "in": "formData" }, { "type": "string", + "example": "P3TNH8mvv14fm", + "name": "key", + "in": "formData" + }, + { + "type": "string", + "example": "db8b0e6a-a08c-4646", "name": "msg_id", "in": "formData" }, { + "enum": [ + 0, + 1, + 2 + ], "type": "integer", + "example": 1, "name": "priority", "in": "formData" }, + { + "type": "string", + "example": "example-server", + "name": "sender_name", + "in": "formData" + }, { "type": "number", + "example": 1669824037, "name": "timestamp", "in": "formData" }, { "type": "string", + "example": "Hello World", "name": "title", "in": "formData" }, - { - "type": "integer", - "name": "user_id", - "in": "formData" - }, { "type": "string", - "name": "user_key", + "example": "7725", + "name": "user_id", "in": "formData" } ], @@ -2839,72 +2935,120 @@ "parameters": [ { "type": "string", + "example": "test", + "name": "channel", + "in": "query" + }, + { + "type": "string", + "example": "This is a message", "name": "content", "in": "query" }, { "type": "string", + "example": "P3TNH8mvv14fm", + "name": "key", + "in": "query" + }, + { + "type": "string", + "example": "db8b0e6a-a08c-4646", "name": "msg_id", "in": "query" }, { + "enum": [ + 0, + 1, + 2 + ], "type": "integer", + "example": 1, "name": "priority", "in": "query" }, + { + "type": "string", + "example": "example-server", + "name": "sender_name", + "in": "query" + }, { "type": "number", + "example": 1669824037, "name": "timestamp", "in": "query" }, { "type": "string", + "example": "Hello World", "name": "title", "in": "query" }, { - "type": "integer", + "type": "string", + "example": "7725", "name": "user_id", "in": "query" }, { "type": "string", - "name": "user_key", - "in": "query" + "example": "test", + "name": "channel", + "in": "formData" }, { "type": "string", + "example": "This is a message", "name": "content", "in": "formData" }, { "type": "string", + "example": "P3TNH8mvv14fm", + "name": "key", + "in": "formData" + }, + { + "type": "string", + "example": "db8b0e6a-a08c-4646", "name": "msg_id", "in": "formData" }, { + "enum": [ + 0, + 1, + 2 + ], "type": "integer", + "example": 1, "name": "priority", "in": "formData" }, + { + "type": "string", + "example": "example-server", + "name": "sender_name", + "in": "formData" + }, { "type": "number", + "example": 1669824037, "name": "timestamp", "in": "formData" }, { "type": "string", + "example": "Hello World", "name": "title", "in": "formData" }, - { - "type": "integer", - "name": "user_id", - "in": "formData" - }, { "type": "string", - "name": "user_key", + "example": "7725", + "name": "user_id", "in": "formData" } ], @@ -3403,26 +3547,46 @@ "handler.SendMessage.combined": { "type": "object", "properties": { + "channel": { + "type": "string", + "example": "test" + }, "content": { - "type": "string" + "type": "string", + "example": "This is a message" + }, + "key": { + "type": "string", + "example": "P3TNH8mvv14fm" }, "msg_id": { - "type": "string" + "type": "string", + "example": "db8b0e6a-a08c-4646" }, "priority": { - "type": "integer" + "type": "integer", + "enum": [ + 0, + 1, + 2 + ], + "example": 1 + }, + "sender_name": { + "type": "string", + "example": "example-server" }, "timestamp": { - "type": "number" + "type": "number", + "example": 1669824037 }, "title": { - "type": "string" + "type": "string", + "example": "Hello World" }, "user_id": { - "type": "integer" - }, - "user_key": { - "type": "string" + "type": "string", + "example": "7725" } } }, @@ -3451,7 +3615,7 @@ "type": "integer" }, "scn_msg_id": { - "type": "integer" + "type": "string" }, "success": { "type": "boolean" diff --git a/scnserver/swagger/swagger.yaml b/scnserver/swagger/swagger.yaml index b11cfa0..ccaaba1 100644 --- a/scnserver/swagger/swagger.yaml +++ b/scnserver/swagger/swagger.yaml @@ -329,19 +329,36 @@ definitions: type: object handler.SendMessage.combined: properties: + channel: + example: test + type: string content: + example: This is a message + type: string + key: + example: P3TNH8mvv14fm type: string msg_id: + example: db8b0e6a-a08c-4646 type: string priority: + enum: + - 0 + - 1 + - 2 + example: 1 type: integer + sender_name: + example: example-server + type: string timestamp: + example: 1669824037 type: number title: + example: Hello World type: string user_id: - type: integer - user_key: + example: "7725" type: string type: object handler.SendMessage.response: @@ -361,7 +378,7 @@ definitions: quota_max: type: integer scn_msg_id: - type: integer + type: string success: type: boolean suppress_send: @@ -785,52 +802,90 @@ paths: description: All parameter can be set via query-parameter or the json body. Only UserID, UserKey and Title are required parameters: - - in: query + - example: test + in: query + name: channel + type: string + - example: This is a message + in: query name: content type: string - - in: query + - example: P3TNH8mvv14fm + in: query + name: key + type: string + - example: db8b0e6a-a08c-4646 + in: query name: msg_id type: string - - in: query + - enum: + - 0 + - 1 + - 2 + example: 1 + in: query name: priority type: integer - - in: query + - example: example-server + in: query + name: sender_name + type: string + - example: 1669824037 + in: query name: timestamp type: number - - in: query + - example: Hello World + in: query name: title type: string - - in: query + - example: "7725" + in: query name: user_id - type: integer - - in: query - name: user_key type: string - description: ' ' in: body name: post_body schema: $ref: '#/definitions/handler.SendMessage.combined' - - in: formData + - example: test + in: formData + name: channel + type: string + - example: This is a message + in: formData name: content type: string - - in: formData + - example: P3TNH8mvv14fm + in: formData + name: key + type: string + - example: db8b0e6a-a08c-4646 + in: formData name: msg_id type: string - - in: formData + - enum: + - 0 + - 1 + - 2 + example: 1 + in: formData name: priority type: integer - - in: formData + - example: example-server + in: formData + name: sender_name + type: string + - example: 1669824037 + in: formData name: timestamp type: number - - in: formData + - example: Hello World + in: formData name: title type: string - - in: formData + - example: "7725" + in: formData name: user_id - type: integer - - in: formData - name: user_key type: string responses: "200": @@ -2630,52 +2685,90 @@ paths: description: All parameter can be set via query-parameter or the json body. Only UserID, UserKey and Title are required parameters: - - in: query + - example: test + in: query + name: channel + type: string + - example: This is a message + in: query name: content type: string - - in: query + - example: P3TNH8mvv14fm + in: query + name: key + type: string + - example: db8b0e6a-a08c-4646 + in: query name: msg_id type: string - - in: query + - enum: + - 0 + - 1 + - 2 + example: 1 + in: query name: priority type: integer - - in: query + - example: example-server + in: query + name: sender_name + type: string + - example: 1669824037 + in: query name: timestamp type: number - - in: query + - example: Hello World + in: query name: title type: string - - in: query + - example: "7725" + in: query name: user_id - type: integer - - in: query - name: user_key type: string - description: ' ' in: body name: post_body schema: $ref: '#/definitions/handler.SendMessage.combined' - - in: formData + - example: test + in: formData + name: channel + type: string + - example: This is a message + in: formData name: content type: string - - in: formData + - example: P3TNH8mvv14fm + in: formData + name: key + type: string + - example: db8b0e6a-a08c-4646 + in: formData name: msg_id type: string - - in: formData + - enum: + - 0 + - 1 + - 2 + example: 1 + in: formData name: priority type: integer - - in: formData + - example: example-server + in: formData + name: sender_name + type: string + - example: 1669824037 + in: formData name: timestamp type: number - - in: formData + - example: Hello World + in: formData name: title type: string - - in: formData + - example: "7725" + in: formData name: user_id - type: integer - - in: formData - name: user_key type: string responses: "200": @@ -2708,47 +2801,85 @@ paths: description: All parameter can be set via query-parameter or form-data body. Only UserID, UserKey and Title are required parameters: - - in: query + - example: test + in: query + name: channel + type: string + - example: This is a message + in: query name: content type: string - - in: query + - example: P3TNH8mvv14fm + in: query + name: key + type: string + - example: db8b0e6a-a08c-4646 + in: query name: msg_id type: string - - in: query + - enum: + - 0 + - 1 + - 2 + example: 1 + in: query name: priority type: integer - - in: query + - example: example-server + in: query + name: sender_name + type: string + - example: 1669824037 + in: query name: timestamp type: number - - in: query + - example: Hello World + in: query name: title type: string - - in: query + - example: "7725" + in: query name: user_id - type: integer - - in: query - name: user_key type: string - - in: formData + - example: test + in: formData + name: channel + type: string + - example: This is a message + in: formData name: content type: string - - in: formData + - example: P3TNH8mvv14fm + in: formData + name: key + type: string + - example: db8b0e6a-a08c-4646 + in: formData name: msg_id type: string - - in: formData + - enum: + - 0 + - 1 + - 2 + example: 1 + in: formData name: priority type: integer - - in: formData + - example: example-server + in: formData + name: sender_name + type: string + - example: 1669824037 + in: formData name: timestamp type: number - - in: formData + - example: Hello World + in: formData name: title type: string - - in: formData + - example: "7725" + in: formData name: user_id - type: integer - - in: formData - name: user_key type: string responses: "200":