Fix panic in /preview/channel/{id}
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m49s
Build Docker and Deploy / Run Unit-Tests (push) Successful in 7m56s
Build Docker and Deploy / Deploy to Server (push) Successful in 39s

This commit is contained in:
2026-03-27 12:57:19 +01:00
parent 1f9abb8574
commit e98a804efc
9 changed files with 34 additions and 31 deletions

View File

@@ -93,13 +93,13 @@ func (h APIHandler) GetChannelPreview(pctx ginext.PreContext) ginext.HTTPRespons
userid := *ctx.GetPermissionUserID() userid := *ctx.GetPermissionUserID()
channel, err := h.database.GetChannelByID(ctx, u.ChannelID) channel, err := h.database.GetChannelByIDOpt(ctx, u.ChannelID)
if errors.Is(err, sql.ErrNoRows) {
return ginresp.APIError(g, 404, apierr.CHANNEL_NOT_FOUND, "Channel not found", err)
}
if err != nil { if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channel", err) return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channel", err)
} }
if channel == nil {
return ginresp.APIError(g, 404, apierr.CHANNEL_NOT_FOUND, "Channel not found", err)
}
sub, err := h.database.GetSubscriptionBySubscriber(ctx, userid, channel.ChannelID) sub, err := h.database.GetSubscriptionBySubscriber(ctx, userid, channel.ChannelID)
if err != nil { if err != nil {
@@ -162,13 +162,13 @@ func (h APIHandler) GetUserKeyPreview(pctx ginext.PreContext) ginext.HTTPRespons
// Query by token.token // Query by token.token
keytoken, err := h.database.GetKeyTokenByToken(ctx, u.KeyID) keytoken, err := h.database.GetKeyTokenByTokenOpt(ctx, u.KeyID)
if keytoken == nil {
return ginresp.APIError(g, 404, apierr.KEY_NOT_FOUND, "Key not found", err)
}
if err != nil { if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query client", err) return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query client", err)
} }
if keytoken == nil {
return ginresp.APIError(g, 404, apierr.KEY_NOT_FOUND, "Key not found", err)
}
return finishSuccess(ginext.JSON(http.StatusOK, keytoken.Preview())) return finishSuccess(ginext.JSON(http.StatusOK, keytoken.Preview()))
@@ -215,13 +215,13 @@ func (h APIHandler) GetClientPreview(pctx ginext.PreContext) ginext.HTTPResponse
return *permResp return *permResp
} }
client, err := h.database.GetClientByID(ctx, u.ClientID) client, err := h.database.GetClientByIDOpt(ctx, u.ClientID)
if client == nil {
return ginresp.APIError(g, 404, apierr.CLIENT_NOT_FOUND, "Client not found", err)
}
if err != nil { if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query client", err) return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query client", err)
} }
if client == nil {
return ginresp.APIError(g, 404, apierr.CLIENT_NOT_FOUND, "Client not found", err)
}
user, err := h.database.GetUser(ctx, client.UserID) user, err := h.database.GetUser(ctx, client.UserID)
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {

View File

@@ -362,7 +362,7 @@ func (h APIHandler) CreateSubscription(pctx ginext.PreContext) ginext.HTTPRespon
} else if b.ChannelOwnerUserID == nil && b.ChannelInternalName == nil && b.ChannelID != nil { } else if b.ChannelOwnerUserID == nil && b.ChannelInternalName == nil && b.ChannelID != nil {
outchannel, err := h.database.GetChannelByID(ctx, *b.ChannelID) outchannel, err := h.database.GetChannelByIDOpt(ctx, *b.ChannelID)
if err != nil { if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channel", err) return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channel", err)
} }

View File

@@ -305,7 +305,7 @@ func (h CompatHandler) Info(pctx ginext.PreContext) ginext.HTTPResponse {
return ginresp.CompatAPIError(0, "Failed to query user") return ginresp.CompatAPIError(0, "Failed to query user")
} }
keytok, err := h.database.GetKeyTokenByToken(ctx, *data.UserKey) keytok, err := h.database.GetKeyTokenByTokenOpt(ctx, *data.UserKey)
if err != nil { if err != nil {
return ginresp.CompatAPIError(0, "Failed to query token") return ginresp.CompatAPIError(0, "Failed to query token")
} }
@@ -417,7 +417,7 @@ func (h CompatHandler) Ack(pctx ginext.PreContext) ginext.HTTPResponse {
return ginresp.CompatAPIError(0, "Failed to query user") return ginresp.CompatAPIError(0, "Failed to query user")
} }
keytok, err := h.database.GetKeyTokenByToken(ctx, *data.UserKey) keytok, err := h.database.GetKeyTokenByTokenOpt(ctx, *data.UserKey)
if err != nil { if err != nil {
return ginresp.CompatAPIError(0, "Failed to query token") return ginresp.CompatAPIError(0, "Failed to query token")
} }
@@ -523,7 +523,7 @@ func (h CompatHandler) Requery(pctx ginext.PreContext) ginext.HTTPResponse {
return ginresp.CompatAPIError(0, "Failed to query user") return ginresp.CompatAPIError(0, "Failed to query user")
} }
keytok, err := h.database.GetKeyTokenByToken(ctx, *data.UserKey) keytok, err := h.database.GetKeyTokenByTokenOpt(ctx, *data.UserKey)
if err != nil { if err != nil {
return ginresp.CompatAPIError(0, "Failed to query token") return ginresp.CompatAPIError(0, "Failed to query token")
} }
@@ -644,7 +644,7 @@ func (h CompatHandler) Update(pctx ginext.PreContext) ginext.HTTPResponse {
return ginresp.CompatAPIError(0, "Failed to query user") return ginresp.CompatAPIError(0, "Failed to query user")
} }
keytok, err := h.database.GetKeyTokenByToken(ctx, *data.UserKey) keytok, err := h.database.GetKeyTokenByTokenOpt(ctx, *data.UserKey)
if err != nil { if err != nil {
return ginresp.CompatAPIError(0, "Failed to query token") return ginresp.CompatAPIError(0, "Failed to query token")
} }
@@ -778,7 +778,7 @@ func (h CompatHandler) Expand(pctx ginext.PreContext) ginext.HTTPResponse {
return ginresp.CompatAPIError(0, "Failed to query user") return ginresp.CompatAPIError(0, "Failed to query user")
} }
keytok, err := h.database.GetKeyTokenByToken(ctx, *data.UserKey) keytok, err := h.database.GetKeyTokenByTokenOpt(ctx, *data.UserKey)
if err != nil { if err != nil {
return ginresp.CompatAPIError(0, "Failed to query token") return ginresp.CompatAPIError(0, "Failed to query token")
} }
@@ -901,7 +901,7 @@ func (h CompatHandler) Upgrade(pctx ginext.PreContext) ginext.HTTPResponse {
return ginresp.CompatAPIError(0, "Failed to query user") return ginresp.CompatAPIError(0, "Failed to query user")
} }
keytok, err := h.database.GetKeyTokenByToken(ctx, *data.UserKey) keytok, err := h.database.GetKeyTokenByTokenOpt(ctx, *data.UserKey)
if err != nil { if err != nil {
return ginresp.CompatAPIError(0, "Failed to query token") return ginresp.CompatAPIError(0, "Failed to query token")
} }

View File

@@ -1,10 +1,11 @@
package primary package primary
import ( import (
"time"
"blackforestbytes.com/simplecloudnotifier/db" "blackforestbytes.com/simplecloudnotifier/db"
"blackforestbytes.com/simplecloudnotifier/models" "blackforestbytes.com/simplecloudnotifier/models"
"git.blackforestbytes.com/BlackForestBytes/goext/sq" "git.blackforestbytes.com/BlackForestBytes/goext/sq"
"time"
) )
func (db *Database) GetChannelByName(ctx db.TxContext, userid models.UserID, chanName string) (*models.Channel, error) { func (db *Database) GetChannelByName(ctx db.TxContext, userid models.UserID, chanName string) (*models.Channel, error) {
@@ -16,7 +17,7 @@ func (db *Database) GetChannelByName(ctx db.TxContext, userid models.UserID, cha
return sq.QuerySingleOpt[models.Channel](ctx, tx, "SELECT * FROM channels WHERE owner_user_id = :uid AND internal_name = :nam AND deleted=0 LIMIT 1", sq.PP{"uid": userid, "nam": chanName}, sq.SModeExtended, sq.Safe) return sq.QuerySingleOpt[models.Channel](ctx, tx, "SELECT * FROM channels WHERE owner_user_id = :uid AND internal_name = :nam AND deleted=0 LIMIT 1", sq.PP{"uid": userid, "nam": chanName}, sq.SModeExtended, sq.Safe)
} }
func (db *Database) GetChannelByID(ctx db.TxContext, chanid models.ChannelID) (*models.Channel, error) { func (db *Database) GetChannelByIDOpt(ctx db.TxContext, chanid models.ChannelID) (*models.Channel, error) {
tx, err := ctx.GetOrCreateTransaction(db) tx, err := ctx.GetOrCreateTransaction(db)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -53,7 +53,7 @@ func (db *Database) GetClient(ctx db.TxContext, userid models.UserID, clientid m
}, sq.SModeExtended, sq.Safe) }, sq.SModeExtended, sq.Safe)
} }
func (db *Database) GetClientByID(ctx db.TxContext, clientid models.ClientID) (*models.Client, error) { func (db *Database) GetClientByIDOpt(ctx db.TxContext, clientid models.ClientID) (*models.Client, error) {
tx, err := ctx.GetOrCreateTransaction(db) tx, err := ctx.GetOrCreateTransaction(db)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -1,12 +1,13 @@
package primary package primary
import ( import (
"strings"
"time"
"blackforestbytes.com/simplecloudnotifier/db" "blackforestbytes.com/simplecloudnotifier/db"
"blackforestbytes.com/simplecloudnotifier/models" "blackforestbytes.com/simplecloudnotifier/models"
"git.blackforestbytes.com/BlackForestBytes/goext/langext" "git.blackforestbytes.com/BlackForestBytes/goext/langext"
"git.blackforestbytes.com/BlackForestBytes/goext/sq" "git.blackforestbytes.com/BlackForestBytes/goext/sq"
"strings"
"time"
) )
func (db *Database) CreateKeyToken(ctx db.TxContext, name string, owner models.UserID, allChannels bool, channels []models.ChannelID, permissions models.TokenPermissionList, token string) (models.KeyToken, error) { func (db *Database) CreateKeyToken(ctx db.TxContext, name string, owner models.UserID, allChannels bool, channels []models.ChannelID, permissions models.TokenPermissionList, token string) (models.KeyToken, error) {
@@ -67,7 +68,7 @@ func (db *Database) GetKeyTokenByID(ctx db.TxContext, keyTokenid models.KeyToken
return sq.QuerySingle[models.KeyToken](ctx, tx, "SELECT * FROM keytokens WHERE keytoken_id = :cid AND deleted=0 LIMIT 1", sq.PP{"cid": keyTokenid}, sq.SModeExtended, sq.Safe) return sq.QuerySingle[models.KeyToken](ctx, tx, "SELECT * FROM keytokens WHERE keytoken_id = :cid AND deleted=0 LIMIT 1", sq.PP{"cid": keyTokenid}, sq.SModeExtended, sq.Safe)
} }
func (db *Database) GetKeyTokenByToken(ctx db.TxContext, key string) (*models.KeyToken, error) { func (db *Database) GetKeyTokenByTokenOpt(ctx db.TxContext, key string) (*models.KeyToken, error) {
tx, err := ctx.GetOrCreateTransaction(db) tx, err := ctx.GetOrCreateTransaction(db)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -1,14 +1,15 @@
package jobs package jobs
import ( import (
"errors"
"fmt"
"time"
"blackforestbytes.com/simplecloudnotifier/db/simplectx" "blackforestbytes.com/simplecloudnotifier/db/simplectx"
"blackforestbytes.com/simplecloudnotifier/logic" "blackforestbytes.com/simplecloudnotifier/logic"
"blackforestbytes.com/simplecloudnotifier/models" "blackforestbytes.com/simplecloudnotifier/models"
"errors"
"fmt"
"git.blackforestbytes.com/BlackForestBytes/goext/syncext" "git.blackforestbytes.com/BlackForestBytes/goext/syncext"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"time"
) )
type DeliveryRetryJob struct { type DeliveryRetryJob struct {
@@ -208,7 +209,7 @@ func (j *DeliveryRetryJob) redeliver(ctx *simplectx.SimpleContext, delivery mode
return return
} }
channel, err := j.app.Database.Primary.GetChannelByID(ctx, msg.ChannelID) channel, err := j.app.Database.Primary.GetChannelByIDOpt(ctx, msg.ChannelID)
if err != nil { if err != nil {
log.Err(err).Str("ChannelID", msg.ChannelID.String()).Msg("Failed to get channel") log.Err(err).Str("ChannelID", msg.ChannelID.String()).Msg("Failed to get channel")
ctx.RollbackTransaction() ctx.RollbackTransaction()

View File

@@ -245,7 +245,7 @@ func (app *Application) getPermissions(ctx db.TxContext, hdr string) (models.Per
key := strings.TrimSpace(hdr[4:]) key := strings.TrimSpace(hdr[4:])
tok, err := app.Database.Primary.GetKeyTokenByToken(ctx, key) tok, err := app.Database.Primary.GetKeyTokenByTokenOpt(ctx, key)
if err != nil { if err != nil {
return models.PermissionSet{}, err return models.PermissionSet{}, err
} }

View File

@@ -75,7 +75,7 @@ func (ac *AppContext) CheckPermissionUserAdmin(userid models.UserID) *ginext.HTT
func (ac *AppContext) CheckPermissionSend(channel models.Channel, key string) (*models.KeyToken, *ginext.HTTPResponse) { func (ac *AppContext) CheckPermissionSend(channel models.Channel, key string) (*models.KeyToken, *ginext.HTTPResponse) {
keytok, err := ac.app.Database.Primary.GetKeyTokenByToken(ac, key) keytok, err := ac.app.Database.Primary.GetKeyTokenByTokenOpt(ac, key)
if err != nil { if err != nil {
return nil, langext.Ptr(ginresp.APIError(ac.ginContext, 500, apierr.DATABASE_ERROR, "Failed to query token", err)) return nil, langext.Ptr(ginresp.APIError(ac.ginContext, 500, apierr.DATABASE_ERROR, "Failed to query token", err))
} }