Fix SQL unmarshalling of optional nested structs (LEFT JOIN)
This commit is contained in:
parent
0cb2a977a0
commit
0112d681ac
@ -17,6 +17,8 @@
|
||||
|
||||
- diff my currently used scnsend script vs the one in the docs here
|
||||
|
||||
- Pagination for ListChannels / ListSubscriptions / ListClients / ListChannelSubscriptions / ListUserSubscriptions
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
- in my script: use (backupname || hostname) for sendername
|
||||
|
@ -18,6 +18,7 @@ const (
|
||||
BINDFAIL_QUERY_PARAM APIError = 1151
|
||||
BINDFAIL_BODY_PARAM APIError = 1152
|
||||
BINDFAIL_URI_PARAM APIError = 1153
|
||||
INVALID_BODY_PARAM APIError = 1161
|
||||
INVALID_ENUM_VALUE APIError = 1171
|
||||
|
||||
NO_TITLE APIError = 1201
|
||||
@ -34,6 +35,7 @@ const (
|
||||
CHANNEL_NOT_FOUND APIError = 1303
|
||||
SUBSCRIPTION_NOT_FOUND APIError = 1304
|
||||
MESSAGE_NOT_FOUND APIError = 1305
|
||||
SUBSCRIPTION_USER_MISMATCH APIError = 1306
|
||||
USER_AUTH_FAILED APIError = 1311
|
||||
|
||||
NO_DEVICE_LINKED APIError = 1401
|
||||
|
@ -2,7 +2,6 @@ package handler
|
||||
|
||||
import (
|
||||
"blackforestbytes.com/simplecloudnotifier/api/apierr"
|
||||
hl "blackforestbytes.com/simplecloudnotifier/api/apihighlight"
|
||||
"blackforestbytes.com/simplecloudnotifier/api/ginresp"
|
||||
"blackforestbytes.com/simplecloudnotifier/db"
|
||||
"blackforestbytes.com/simplecloudnotifier/db/cursortoken"
|
||||
@ -502,7 +501,7 @@ func (h APIHandler) DeleteClient(g *gin.Context) ginresp.HTTPResponse {
|
||||
// @Tags API-v2
|
||||
//
|
||||
// @Param uid path int true "UserID"
|
||||
// @Param selector query string true "Filter channels (default: owned)" Enums(owned, subscribed, all, subscribed_any, all_any)
|
||||
// @Param selector query string false "Filter channels (default: owned)" Enums(owned, subscribed, all, subscribed_any, all_any)
|
||||
//
|
||||
// @Success 200 {object} handler.ListChannels.response
|
||||
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
|
||||
@ -667,6 +666,10 @@ func (h APIHandler) CreateChannel(g *gin.Context) ginresp.HTTPResponse {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
if b.Name == "" {
|
||||
return ginresp.APIError(g, 400, apierr.INVALID_BODY_PARAM, "Missing parameter: name", nil)
|
||||
}
|
||||
|
||||
channelDisplayName := h.app.NormalizeChannelDisplayName(b.Name)
|
||||
channelInternalName := h.app.NormalizeChannelInternalName(b.Name)
|
||||
|
||||
@ -677,17 +680,17 @@ func (h APIHandler) CreateChannel(g *gin.Context) ginresp.HTTPResponse {
|
||||
|
||||
user, err := h.database.GetUser(ctx, u.UserID)
|
||||
if err == sql.ErrNoRows {
|
||||
return ginresp.SendAPIError(g, 400, apierr.USER_NOT_FOUND, hl.USER_ID, "User not found", nil)
|
||||
return ginresp.APIError(g, 400, apierr.USER_NOT_FOUND, "User not found", nil)
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query user", err)
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query user", err)
|
||||
}
|
||||
|
||||
if len(channelDisplayName) > user.MaxChannelNameLength() {
|
||||
return ginresp.SendAPIError(g, 400, apierr.CHANNEL_TOO_LONG, hl.CHANNEL, fmt.Sprintf("Channel too long (max %d characters)", user.MaxChannelNameLength()), nil)
|
||||
return ginresp.APIError(g, 400, apierr.CHANNEL_TOO_LONG, fmt.Sprintf("Channel too long (max %d characters)", user.MaxChannelNameLength()), nil)
|
||||
}
|
||||
if len(channelInternalName) > user.MaxChannelNameLength() {
|
||||
return ginresp.SendAPIError(g, 400, apierr.CHANNEL_TOO_LONG, hl.CHANNEL, fmt.Sprintf("Channel too long (max %d characters)", user.MaxChannelNameLength()), nil)
|
||||
return ginresp.APIError(g, 400, apierr.CHANNEL_TOO_LONG, fmt.Sprintf("Channel too long (max %d characters)", user.MaxChannelNameLength()), nil)
|
||||
}
|
||||
|
||||
if channelExisting != nil {
|
||||
@ -916,19 +919,19 @@ func (h APIHandler) ListChannelMessages(g *gin.Context) ginresp.HTTPResponse {
|
||||
// ListUserSubscriptions swaggerdoc
|
||||
//
|
||||
// @Summary List all subscriptions of a user (incoming/owned)
|
||||
// // @Description The possible values for 'selector' are:
|
||||
// // @Description - "owner_all" All subscriptions (confirmed/unconfirmed) with the user as owner (= subscriptions he can use to read channels)
|
||||
// // @Description - "owner_confirmed" Confirmed subscriptions with the user as owner
|
||||
// // @Description - "owner_unconfirmed" Unconfirmed (Pending) subscriptions with the user as owner
|
||||
// // @Description - "incoming_all" All subscriptions (confirmed/unconfirmed) from other users to channels of this user (= incoming subscriptions and subscription requests)
|
||||
// // @Description - "incoming_confirmed" Confirmed subscriptions from other users to channels of this user
|
||||
// // @Description - "incoming_unconfirmed" Unconfirmed subscriptions from other users to channels of this user (= requests)
|
||||
// //
|
||||
// @Description The possible values for 'selector' are:
|
||||
// @Description - "outgoing_all" All subscriptions (confirmed/unconfirmed) with the user as subscriber (= subscriptions he can use to read channels)
|
||||
// @Description - "outgoing_confirmed" Confirmed subscriptions with the user as subscriber
|
||||
// @Description - "outgoing_unconfirmed" Unconfirmed (Pending) subscriptions with the user as subscriber
|
||||
// @Description - "incoming_all" All subscriptions (confirmed/unconfirmed) from other users to channels of this user (= incoming subscriptions and subscription requests)
|
||||
// @Description - "incoming_confirmed" Confirmed subscriptions from other users to channels of this user
|
||||
// @Description - "incoming_unconfirmed" Unconfirmed subscriptions from other users to channels of this user (= requests)
|
||||
//
|
||||
// @ID api-user-subscriptions-list
|
||||
// @Tags API-v2
|
||||
//
|
||||
// @Param uid path int true "UserID"
|
||||
// @Param selector query string true "Filter subscribptions (default: owner_all)" Enums(owner_all, owner_confirmed, owner_unconfirmed, incoming_all, incoming_confirmed, incoming_unconfirmed)
|
||||
// @Param selector query string true "Filter subscriptions (default: owner_all)" Enums(outgoing_all, outgoing_confirmed, outgoing_unconfirmed, incoming_all, incoming_confirmed, incoming_unconfirmed)
|
||||
//
|
||||
// @Success 200 {object} handler.ListUserSubscriptions.response
|
||||
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
|
||||
@ -964,48 +967,48 @@ func (h APIHandler) ListUserSubscriptions(g *gin.Context) ginresp.HTTPResponse {
|
||||
var res []models.Subscription
|
||||
var err error
|
||||
|
||||
if sel == "owner_all" {
|
||||
|
||||
res, err = h.database.ListSubscriptionsByOwner(ctx, u.UserID, nil)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscriptions", err)
|
||||
}
|
||||
|
||||
} else if sel == "owner_confirmed" {
|
||||
|
||||
res, err = h.database.ListSubscriptionsByOwner(ctx, u.UserID, langext.Ptr(true))
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscriptions", err)
|
||||
}
|
||||
|
||||
} else if sel == "owner_unconfirmed" {
|
||||
|
||||
res, err = h.database.ListSubscriptionsByOwner(ctx, u.UserID, langext.Ptr(false))
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscriptions", err)
|
||||
}
|
||||
|
||||
} else if sel == "incoming_all" {
|
||||
if sel == "outgoing_all" {
|
||||
|
||||
res, err = h.database.ListSubscriptionsBySubscriber(ctx, u.UserID, nil)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscriptions", err)
|
||||
}
|
||||
|
||||
} else if sel == "incoming_confirmed" {
|
||||
} else if sel == "outgoing_confirmed" {
|
||||
|
||||
res, err = h.database.ListSubscriptionsBySubscriber(ctx, u.UserID, langext.Ptr(true))
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscriptions", err)
|
||||
}
|
||||
|
||||
} else if sel == "incoming_unconfirmed" {
|
||||
} else if sel == "outgoing_unconfirmed" {
|
||||
|
||||
res, err = h.database.ListSubscriptionsBySubscriber(ctx, u.UserID, langext.Ptr(false))
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscriptions", err)
|
||||
}
|
||||
|
||||
} else if sel == "incoming_all" {
|
||||
|
||||
res, err = h.database.ListSubscriptionsByChannelOwner(ctx, u.UserID, nil)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscriptions", err)
|
||||
}
|
||||
|
||||
} else if sel == "incoming_confirmed" {
|
||||
|
||||
res, err = h.database.ListSubscriptionsByChannelOwner(ctx, u.UserID, langext.Ptr(true))
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscriptions", err)
|
||||
}
|
||||
|
||||
} else if sel == "incoming_unconfirmed" {
|
||||
|
||||
res, err = h.database.ListSubscriptionsByChannelOwner(ctx, u.UserID, langext.Ptr(false))
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscriptions", err)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
return ginresp.APIError(g, 400, apierr.INVALID_ENUM_VALUE, "Invalid value for the [selector] parameter", nil)
|
||||
@ -1111,9 +1114,8 @@ func (h APIHandler) GetSubscription(g *gin.Context) ginresp.HTTPResponse {
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
||||
}
|
||||
|
||||
if subscription.SubscriberUserID != u.UserID {
|
||||
return ginresp.APIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
if subscription.SubscriberUserID != u.UserID && subscription.ChannelOwnerUserID != u.UserID {
|
||||
return ginresp.APIError(g, 404, apierr.SUBSCRIPTION_USER_MISMATCH, "Subscription not found", nil)
|
||||
}
|
||||
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, subscription.JSON()))
|
||||
@ -1159,9 +1161,8 @@ func (h APIHandler) CancelSubscription(g *gin.Context) ginresp.HTTPResponse {
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
||||
}
|
||||
|
||||
if subscription.SubscriberUserID != u.UserID && subscription.ChannelOwnerUserID != u.UserID {
|
||||
return ginresp.APIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
return ginresp.APIError(g, 404, apierr.SUBSCRIPTION_USER_MISMATCH, "Subscription not found", nil)
|
||||
}
|
||||
|
||||
err = h.database.DeleteSubscription(ctx, u.SubscriptionID)
|
||||
@ -1174,7 +1175,8 @@ func (h APIHandler) CancelSubscription(g *gin.Context) ginresp.HTTPResponse {
|
||||
|
||||
// CreateSubscription swaggerdoc
|
||||
//
|
||||
// @Summary Creare/Request a subscription
|
||||
// @Summary Create/Request a subscription
|
||||
// @Description Either [channel_owner_user_id, channel_internal_name] or [channel_id] must be supplied in the request body
|
||||
// @ID api-subscriptions-create
|
||||
// @Tags API-v2
|
||||
//
|
||||
@ -1193,8 +1195,9 @@ func (h APIHandler) CreateSubscription(g *gin.Context) ginresp.HTTPResponse {
|
||||
UserID models.UserID `uri:"uid"`
|
||||
}
|
||||
type body struct {
|
||||
ChannelOwnerUserID models.UserID `form:"channel_owner_user_id" binding:"required"`
|
||||
Channel string `form:"channel_name" binding:"required"`
|
||||
ChannelOwnerUserID *models.UserID `json:"channel_owner_user_id"`
|
||||
ChannelInternalName *string `json:"channel_internal_name"`
|
||||
ChannelID *models.ChannelID `json:"channel_id"`
|
||||
}
|
||||
type query struct {
|
||||
ChanSubscribeKey *string `json:"chan_subscribe_key" form:"chan_subscribe_key"`
|
||||
@ -1213,21 +1216,45 @@ func (h APIHandler) CreateSubscription(g *gin.Context) ginresp.HTTPResponse {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
channelInternalName := h.app.NormalizeChannelInternalName(b.Channel)
|
||||
var channel models.Channel
|
||||
|
||||
channel, err := h.database.GetChannelByName(ctx, b.ChannelOwnerUserID, channelInternalName)
|
||||
if b.ChannelOwnerUserID != nil && b.ChannelInternalName != nil && b.ChannelID == nil {
|
||||
|
||||
channelInternalName := h.app.NormalizeChannelInternalName(*b.ChannelInternalName)
|
||||
|
||||
outchannel, err := h.database.GetChannelByName(ctx, *b.ChannelOwnerUserID, channelInternalName)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channel", err)
|
||||
}
|
||||
if channel == nil {
|
||||
if outchannel == nil {
|
||||
return ginresp.APIError(g, 400, apierr.CHANNEL_NOT_FOUND, "Channel not found", err)
|
||||
}
|
||||
|
||||
if channel.OwnerUserID != u.UserID && (q.ChanSubscribeKey == nil || *q.ChanSubscribeKey != channel.SubscribeKey) {
|
||||
ginresp.APIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
channel = *outchannel
|
||||
|
||||
} else if b.ChannelOwnerUserID == nil && b.ChannelInternalName == nil && b.ChannelID != nil {
|
||||
|
||||
outchannel, err := h.database.GetChannelByID(ctx, *b.ChannelID)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channel", err)
|
||||
}
|
||||
if outchannel == nil {
|
||||
return ginresp.APIError(g, 400, apierr.CHANNEL_NOT_FOUND, "Channel not found", err)
|
||||
}
|
||||
|
||||
sub, err := h.database.CreateSubscription(ctx, u.UserID, *channel, channel.OwnerUserID == u.UserID)
|
||||
channel = *outchannel
|
||||
|
||||
} else {
|
||||
|
||||
return ginresp.APIError(g, 400, apierr.INVALID_BODY_PARAM, "Must either supply [channel_owner_user_id, channel_internal_name] or [channel_id]", nil)
|
||||
|
||||
}
|
||||
|
||||
if channel.OwnerUserID != u.UserID && (q.ChanSubscribeKey == nil || *q.ChanSubscribeKey != channel.SubscribeKey) {
|
||||
return ginresp.APIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
}
|
||||
|
||||
sub, err := h.database.CreateSubscription(ctx, u.UserID, channel, channel.OwnerUserID == u.UserID)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create subscription", err)
|
||||
}
|
||||
@ -1279,9 +1306,8 @@ func (h APIHandler) UpdateSubscription(g *gin.Context) ginresp.HTTPResponse {
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
||||
}
|
||||
|
||||
if subscription.ChannelOwnerUserID != u.UserID {
|
||||
return ginresp.APIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
if subscription.SubscriberUserID != u.UserID {
|
||||
return ginresp.APIError(g, 404, apierr.SUBSCRIPTION_USER_MISMATCH, "Subscription not found", nil)
|
||||
}
|
||||
|
||||
if b.Confirmed != nil {
|
||||
|
@ -124,7 +124,7 @@ func (h CommonHandler) Health(g *gin.Context) ginresp.HTTPResponse {
|
||||
_, libVersionNumber, _ := sqlite3.Version()
|
||||
|
||||
if libVersionNumber < 3039000 {
|
||||
ginresp.InternalError(errors.New("sqlite version too low"))
|
||||
return ginresp.InternalError(errors.New("sqlite version too low"))
|
||||
}
|
||||
|
||||
err := h.app.Database.Ping(ctx)
|
||||
|
@ -57,6 +57,30 @@ func (db *Database) GetChannelByNameAndSendKey(ctx TxContext, chanName string, s
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetChannelByID(ctx TxContext, chanid models.ChannelID) (*models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM channels WHERE channel_id = :cid LIMIT 1", sq.PP{
|
||||
"cid": chanid,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channel, err := models.DecodeChannel(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (db *Database) CreateChannel(ctx TxContext, userid models.UserID, dispName string, intName string, subscribeKey string, sendKey string) (models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
|
@ -206,12 +206,7 @@ func (pp *DBPreprocessor) getTableColumns(ctx context.Context, tablename string)
|
||||
}
|
||||
|
||||
type res struct {
|
||||
CID int64 `db:"cid"`
|
||||
Name string `db:"name"`
|
||||
Type string `db:"type"`
|
||||
NotNull int `db:"notnull"`
|
||||
DFLT *string `db:"dflt_value"`
|
||||
PK int `db:"pk"`
|
||||
}
|
||||
|
||||
rows, err := pp.db.Query(ctx, "PRAGMA table_info('"+tablename+"');", sq.PP{})
|
||||
@ -219,7 +214,7 @@ func (pp *DBPreprocessor) getTableColumns(ctx context.Context, tablename string)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resrows, err := sq.ScanAll[res](rows, true)
|
||||
resrows, err := sq.ScanAll[res](rows, sq.SModeFast, sq.Unsafe, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ func (db *Database) ListSubscriptionsByChannel(ctx TxContext, channelID models.C
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) ListSubscriptionsByOwner(ctx TxContext, ownerUserID models.UserID, confirmed *bool) ([]models.Subscription, error) {
|
||||
func (db *Database) ListSubscriptionsByChannelOwner(ctx TxContext, ownerUserID models.UserID, confirmed *bool) ([]models.Subscription, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -8,7 +8,7 @@ require (
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/rs/zerolog v1.28.0
|
||||
github.com/swaggo/swag v1.8.7
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.42
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.46
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -122,6 +122,12 @@ gogs.mikescher.com/BlackForestBytes/goext v0.0.41 h1:3p/MtkHZ2gulSdizXql3VnFf2v7
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.41/go.mod h1:/u9JtMwCP68ix4R9BJ/MT0Lm+QScmqIoyYZFKBGzv9g=
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.42 h1:u6+pDRrL9wSvJG7gVsGUO4dA54qzac5LsqoXqi6oo9E=
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.42/go.mod h1:/u9JtMwCP68ix4R9BJ/MT0Lm+QScmqIoyYZFKBGzv9g=
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.44 h1:YC8SrQk1BEDR5wCdLZ2trnNvkUg/sssW94XYKrsKyc4=
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.44/go.mod h1:/u9JtMwCP68ix4R9BJ/MT0Lm+QScmqIoyYZFKBGzv9g=
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.45 h1:1naABIgSa5hhWPT7kYAAEeIUBNLo7nVvE6/kz9LoY9Q=
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.45/go.mod h1:/u9JtMwCP68ix4R9BJ/MT0Lm+QScmqIoyYZFKBGzv9g=
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.46 h1:7nV9RKnnz/qgkVWvlj4MOAITbe+Gas1niVQgvbHnNk8=
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.46/go.mod h1:/u9JtMwCP68ix4R9BJ/MT0Lm+QScmqIoyYZFKBGzv9g=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
|
@ -117,7 +117,7 @@ func (c ChannelWithSubscriptionDB) Model() ChannelWithSubscription {
|
||||
}
|
||||
|
||||
func DecodeChannel(r *sqlx.Rows) (Channel, error) {
|
||||
data, err := sq.ScanSingle[ChannelDB](r, true)
|
||||
data, err := sq.ScanSingle[ChannelDB](r, sq.SModeFast, sq.Safe, true)
|
||||
if err != nil {
|
||||
return Channel{}, err
|
||||
}
|
||||
@ -125,7 +125,7 @@ func DecodeChannel(r *sqlx.Rows) (Channel, error) {
|
||||
}
|
||||
|
||||
func DecodeChannels(r *sqlx.Rows) ([]Channel, error) {
|
||||
data, err := sq.ScanAll[ChannelDB](r, true)
|
||||
data, err := sq.ScanAll[ChannelDB](r, sq.SModeFast, sq.Safe, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -133,7 +133,7 @@ func DecodeChannels(r *sqlx.Rows) ([]Channel, error) {
|
||||
}
|
||||
|
||||
func DecodeChannelWithSubscription(r *sqlx.Rows) (ChannelWithSubscription, error) {
|
||||
data, err := sq.ScanSingle[ChannelWithSubscriptionDB](r, true)
|
||||
data, err := sq.ScanSingle[ChannelWithSubscriptionDB](r, sq.SModeExtended, sq.Safe, true)
|
||||
if err != nil {
|
||||
return ChannelWithSubscription{}, err
|
||||
}
|
||||
@ -141,7 +141,7 @@ func DecodeChannelWithSubscription(r *sqlx.Rows) (ChannelWithSubscription, error
|
||||
}
|
||||
|
||||
func DecodeChannelsWithSubscription(r *sqlx.Rows) ([]ChannelWithSubscription, error) {
|
||||
data, err := sq.ScanAll[ChannelWithSubscriptionDB](r, true)
|
||||
data, err := sq.ScanAll[ChannelWithSubscriptionDB](r, sq.SModeExtended, sq.Safe, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ func (c ClientDB) Model() Client {
|
||||
}
|
||||
|
||||
func DecodeClient(r *sqlx.Rows) (Client, error) {
|
||||
data, err := sq.ScanSingle[ClientDB](r, true)
|
||||
data, err := sq.ScanSingle[ClientDB](r, sq.SModeFast, sq.Safe, true)
|
||||
if err != nil {
|
||||
return Client{}, err
|
||||
}
|
||||
@ -77,7 +77,7 @@ func DecodeClient(r *sqlx.Rows) (Client, error) {
|
||||
}
|
||||
|
||||
func DecodeClients(r *sqlx.Rows) ([]Client, error) {
|
||||
data, err := sq.ScanAll[ClientDB](r, true)
|
||||
data, err := sq.ScanAll[ClientDB](r, sq.SModeFast, sq.Safe, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ func (d DeliveryDB) Model() Delivery {
|
||||
}
|
||||
|
||||
func DecodeDelivery(r *sqlx.Rows) (Delivery, error) {
|
||||
data, err := sq.ScanSingle[DeliveryDB](r, true)
|
||||
data, err := sq.ScanSingle[DeliveryDB](r, sq.SModeFast, sq.Safe, true)
|
||||
if err != nil {
|
||||
return Delivery{}, err
|
||||
}
|
||||
@ -97,7 +97,7 @@ func DecodeDelivery(r *sqlx.Rows) (Delivery, error) {
|
||||
}
|
||||
|
||||
func DecodeDeliveries(r *sqlx.Rows) ([]Delivery, error) {
|
||||
data, err := sq.ScanAll[DeliveryDB](r, true)
|
||||
data, err := sq.ScanAll[DeliveryDB](r, sq.SModeFast, sq.Safe, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ func (m MessageDB) Model() Message {
|
||||
}
|
||||
|
||||
func DecodeMessage(r *sqlx.Rows) (Message, error) {
|
||||
data, err := sq.ScanSingle[MessageDB](r, true)
|
||||
data, err := sq.ScanSingle[MessageDB](r, sq.SModeFast, sq.Safe, true)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
@ -154,7 +154,7 @@ func DecodeMessage(r *sqlx.Rows) (Message, error) {
|
||||
}
|
||||
|
||||
func DecodeMessages(r *sqlx.Rows) ([]Message, error) {
|
||||
data, err := sq.ScanAll[MessageDB](r, true)
|
||||
data, err := sq.ScanAll[MessageDB](r, sq.SModeFast, sq.Safe, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ func (s SubscriptionDB) Model() Subscription {
|
||||
}
|
||||
|
||||
func DecodeSubscription(r *sqlx.Rows) (Subscription, error) {
|
||||
data, err := sq.ScanSingle[SubscriptionDB](r, true)
|
||||
data, err := sq.ScanSingle[SubscriptionDB](r, sq.SModeFast, sq.Safe, true)
|
||||
if err != nil {
|
||||
return Subscription{}, err
|
||||
}
|
||||
@ -70,7 +70,7 @@ func DecodeSubscription(r *sqlx.Rows) (Subscription, error) {
|
||||
}
|
||||
|
||||
func DecodeSubscriptions(r *sqlx.Rows) ([]Subscription, error) {
|
||||
data, err := sq.ScanAll[SubscriptionDB](r, true)
|
||||
data, err := sq.ScanAll[SubscriptionDB](r, sq.SModeFast, sq.Safe, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ func (u UserDB) Model() User {
|
||||
}
|
||||
|
||||
func DecodeUser(r *sqlx.Rows) (User, error) {
|
||||
data, err := sq.ScanSingle[UserDB](r, true)
|
||||
data, err := sq.ScanSingle[UserDB](r, sq.SModeFast, sq.Safe, true)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
@ -171,7 +171,7 @@ func DecodeUser(r *sqlx.Rows) (User, error) {
|
||||
}
|
||||
|
||||
func DecodeUsers(r *sqlx.Rows) ([]User, error) {
|
||||
data, err := sq.ScanAll[UserDB](r, true)
|
||||
data, err := sq.ScanAll[UserDB](r, sq.SModeFast, sq.Safe, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1176,8 +1176,7 @@
|
||||
"type": "string",
|
||||
"description": "Filter channels (default: owned)",
|
||||
"name": "selector",
|
||||
"in": "query",
|
||||
"required": true
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@ -1743,6 +1742,7 @@
|
||||
},
|
||||
"/api/users/{uid}/subscriptions": {
|
||||
"get": {
|
||||
"description": "The possible values for 'selector' are:\n- \"owner_all\" All subscriptions (confirmed/unconfirmed) with the user as owner (= subscriptions he can use to read channels)\n- \"owner_confirmed\" Confirmed subscriptions with the user as owner\n- \"owner_unconfirmed\" Unconfirmed (Pending) subscriptions with the user as owner\n- \"incoming_all\" All subscriptions (confirmed/unconfirmed) from other users to channels of this user (= incoming subscriptions and subscription requests)\n- \"incoming_confirmed\" Confirmed subscriptions from other users to channels of this user\n- \"incoming_unconfirmed\" Unconfirmed subscriptions from other users to channels of this user (= requests)",
|
||||
"tags": [
|
||||
"API-v2"
|
||||
],
|
||||
@ -1766,7 +1766,7 @@
|
||||
"incoming_unconfirmed"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "Filter subscribptions (default: owner_all)",
|
||||
"description": "Filter subscriptions (default: owner_all)",
|
||||
"name": "selector",
|
||||
"in": "query",
|
||||
"required": true
|
||||
@ -1800,10 +1800,11 @@
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Either [channel_owner_user_id, channel_internal_name] or [channel_id] must be supplied in the request body",
|
||||
"tags": [
|
||||
"API-v2"
|
||||
],
|
||||
"summary": "Creare/Request a subscription",
|
||||
"summary": "Create/Request a subscription",
|
||||
"operationId": "api-subscriptions-create",
|
||||
"parameters": [
|
||||
{
|
||||
@ -2410,11 +2411,15 @@
|
||||
"handler.CreateSubscription.body": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"channel",
|
||||
"channel_id",
|
||||
"channel_internal_name",
|
||||
"channel_owner_user_id"
|
||||
],
|
||||
"properties": {
|
||||
"channel": {
|
||||
"channel_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"channel_internal_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"channel_owner_user_id": {
|
||||
|
@ -60,12 +60,15 @@ definitions:
|
||||
type: object
|
||||
handler.CreateSubscription.body:
|
||||
properties:
|
||||
channel:
|
||||
channel_id:
|
||||
type: integer
|
||||
channel_internal_name:
|
||||
type: string
|
||||
channel_owner_user_id:
|
||||
type: integer
|
||||
required:
|
||||
- channel
|
||||
- channel_id
|
||||
- channel_internal_name
|
||||
- channel_owner_user_id
|
||||
type: object
|
||||
handler.CreateUser.body:
|
||||
@ -1315,7 +1318,6 @@ paths:
|
||||
- all_any
|
||||
in: query
|
||||
name: selector
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
@ -1697,6 +1699,14 @@ paths:
|
||||
- API-v2
|
||||
/api/users/{uid}/subscriptions:
|
||||
get:
|
||||
description: |-
|
||||
The possible values for 'selector' are:
|
||||
- "owner_all" All subscriptions (confirmed/unconfirmed) with the user as owner (= subscriptions he can use to read channels)
|
||||
- "owner_confirmed" Confirmed subscriptions with the user as owner
|
||||
- "owner_unconfirmed" Unconfirmed (Pending) subscriptions with the user as owner
|
||||
- "incoming_all" All subscriptions (confirmed/unconfirmed) from other users to channels of this user (= incoming subscriptions and subscription requests)
|
||||
- "incoming_confirmed" Confirmed subscriptions from other users to channels of this user
|
||||
- "incoming_unconfirmed" Unconfirmed subscriptions from other users to channels of this user (= requests)
|
||||
operationId: api-user-subscriptions-list
|
||||
parameters:
|
||||
- description: UserID
|
||||
@ -1704,7 +1714,7 @@ paths:
|
||||
name: uid
|
||||
required: true
|
||||
type: integer
|
||||
- description: 'Filter subscribptions (default: owner_all)'
|
||||
- description: 'Filter subscriptions (default: owner_all)'
|
||||
enum:
|
||||
- owner_all
|
||||
- owner_confirmed
|
||||
@ -1737,6 +1747,8 @@ paths:
|
||||
tags:
|
||||
- API-v2
|
||||
post:
|
||||
description: Either [channel_owner_user_id, channel_internal_name] or [channel_id]
|
||||
must be supplied in the request body
|
||||
operationId: api-subscriptions-create
|
||||
parameters:
|
||||
- description: UserID
|
||||
@ -1769,7 +1781,7 @@ paths:
|
||||
description: internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
summary: Creare/Request a subscription
|
||||
summary: Create/Request a subscription
|
||||
tags:
|
||||
- API-v2
|
||||
/api/users/{uid}/subscriptions/{sid}:
|
||||
|
@ -161,10 +161,10 @@ func TestListChannelsOwned(t *testing.T) {
|
||||
}
|
||||
|
||||
testdata := map[int][]string{
|
||||
0: {"main", "chattingchamber", "unicdhll", "promotions", "reminders"},
|
||||
0: {"main", "chatting chamber", "unicôdé häll \U0001f92a", "promotions", "reminders"},
|
||||
1: {"main", "private"},
|
||||
2: {"main", "ü", "ö", "ä"},
|
||||
3: {"main", "innovations", "reminders"},
|
||||
3: {"main", "\U0001f5ff", "innovations", "reminders"},
|
||||
4: {"main"},
|
||||
5: {"main", "test1", "test2", "test3", "test4", "test5"},
|
||||
6: {"main", "security", "lipsum"},
|
||||
@ -175,8 +175,8 @@ func TestListChannelsOwned(t *testing.T) {
|
||||
11: {"promotions"},
|
||||
12: {},
|
||||
13: {},
|
||||
14: {"", "chan_self_subscribed", "chan_self_unsub"}, //TODO these two have the interesting cases
|
||||
15: {"", "chan_other_nosub", "chan_other_request", "chan_other_request", "chan_other_accepted"}, //TODO these two have the interesting cases
|
||||
14: {"main", "chan_self_subscribed", "chan_self_unsub"}, //TODO these two have the interesting cases
|
||||
15: {"main", "chan_other_nosub", "chan_other_request", "chan_other_accepted"}, //TODO these two have the interesting cases
|
||||
}
|
||||
|
||||
for k, v := range testdata {
|
||||
@ -186,19 +186,143 @@ func TestListChannelsOwned(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestListChannelsSubscribedAny(t *testing.T) {
|
||||
t.SkipNow() //TODO
|
||||
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||
defer stop()
|
||||
|
||||
data := tt.InitDefaultData(t, ws)
|
||||
|
||||
type chanlist struct {
|
||||
Channels []gin.H `json:"channels"`
|
||||
}
|
||||
|
||||
testdata := map[int][]string{
|
||||
0: {"main", "chatting chamber", "unicôdé häll \U0001f92a", "promotions", "reminders"},
|
||||
1: {"main", "private"},
|
||||
2: {"main", "ü", "ö", "ä"},
|
||||
3: {"main", "\U0001f5ff", "innovations", "reminders"},
|
||||
4: {"main"},
|
||||
5: {"main", "test1", "test2", "test3", "test4", "test5"},
|
||||
6: {"main", "security", "lipsum"},
|
||||
7: {"main"},
|
||||
8: {"main"},
|
||||
9: {"main", "manual@chan"},
|
||||
10: {"main"},
|
||||
11: {"promotions"},
|
||||
12: {},
|
||||
13: {},
|
||||
14: {"main", "chan_self_subscribed", "chan_self_unsub"}, //TODO these two have the interesting cases
|
||||
15: {"main", "chan_other_nosub", "chan_other_request", "chan_other_accepted"}, //TODO these two have the interesting cases
|
||||
}
|
||||
|
||||
for k, v := range testdata {
|
||||
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels", data.User[k].UID))
|
||||
tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListChannelsAllAny(t *testing.T) {
|
||||
t.SkipNow() //TODO
|
||||
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||
defer stop()
|
||||
|
||||
data := tt.InitDefaultData(t, ws)
|
||||
|
||||
type chanlist struct {
|
||||
Channels []gin.H `json:"channels"`
|
||||
}
|
||||
|
||||
testdata := map[int][]string{
|
||||
0: {"main", "chatting chamber", "unicôdé häll \U0001f92a", "promotions", "reminders"},
|
||||
1: {"main", "private"},
|
||||
2: {"main", "ü", "ö", "ä"},
|
||||
3: {"main", "\U0001f5ff", "innovations", "reminders"},
|
||||
4: {"main"},
|
||||
5: {"main", "test1", "test2", "test3", "test4", "test5"},
|
||||
6: {"main", "security", "lipsum"},
|
||||
7: {"main"},
|
||||
8: {"main"},
|
||||
9: {"main", "manual@chan"},
|
||||
10: {"main"},
|
||||
11: {"promotions"},
|
||||
12: {},
|
||||
13: {},
|
||||
14: {"main", "chan_self_subscribed", "chan_self_unsub"}, //TODO these two have the interesting cases
|
||||
15: {"main", "chan_other_nosub", "chan_other_request", "chan_other_accepted"}, //TODO these two have the interesting cases
|
||||
}
|
||||
|
||||
for k, v := range testdata {
|
||||
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels", data.User[k].UID))
|
||||
tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListChannelsSubscribed(t *testing.T) {
|
||||
t.SkipNow() //TODO
|
||||
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||
defer stop()
|
||||
|
||||
data := tt.InitDefaultData(t, ws)
|
||||
|
||||
type chanlist struct {
|
||||
Channels []gin.H `json:"channels"`
|
||||
}
|
||||
|
||||
testdata := map[int][]string{
|
||||
0: {"main", "chatting chamber", "unicôdé häll \U0001f92a", "promotions", "reminders"},
|
||||
1: {"main", "private"},
|
||||
2: {"main", "ü", "ö", "ä"},
|
||||
3: {"main", "\U0001f5ff", "innovations", "reminders"},
|
||||
4: {"main"},
|
||||
5: {"main", "test1", "test2", "test3", "test4", "test5"},
|
||||
6: {"main", "security", "lipsum"},
|
||||
7: {"main"},
|
||||
8: {"main"},
|
||||
9: {"main", "manual@chan"},
|
||||
10: {"main"},
|
||||
11: {"promotions"},
|
||||
12: {},
|
||||
13: {},
|
||||
14: {"main", "chan_self_subscribed", "chan_self_unsub"}, //TODO these two have the interesting cases
|
||||
15: {"main", "chan_other_nosub", "chan_other_request", "chan_other_accepted"}, //TODO these two have the interesting cases
|
||||
}
|
||||
|
||||
for k, v := range testdata {
|
||||
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels", data.User[k].UID))
|
||||
tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListChannelsAll(t *testing.T) {
|
||||
t.SkipNow() //TODO
|
||||
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||
defer stop()
|
||||
|
||||
data := tt.InitDefaultData(t, ws)
|
||||
|
||||
type chanlist struct {
|
||||
Channels []gin.H `json:"channels"`
|
||||
}
|
||||
|
||||
testdata := map[int][]string{
|
||||
0: {"main", "chatting chamber", "unicôdé häll \U0001f92a", "promotions", "reminders"},
|
||||
1: {"main", "private"},
|
||||
2: {"main", "ü", "ö", "ä"},
|
||||
3: {"main", "\U0001f5ff", "innovations", "reminders"},
|
||||
4: {"main"},
|
||||
5: {"main", "test1", "test2", "test3", "test4", "test5"},
|
||||
6: {"main", "security", "lipsum"},
|
||||
7: {"main"},
|
||||
8: {"main"},
|
||||
9: {"main", "manual@chan"},
|
||||
10: {"main"},
|
||||
11: {"promotions"},
|
||||
12: {},
|
||||
13: {},
|
||||
14: {"main", "chan_self_subscribed", "chan_self_unsub"}, //TODO these two have the interesting cases
|
||||
15: {"main", "chan_other_nosub", "chan_other_request", "chan_other_accepted"}, //TODO these two have the interesting cases
|
||||
}
|
||||
|
||||
for k, v := range testdata {
|
||||
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels", data.User[k].UID))
|
||||
tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name")
|
||||
}
|
||||
}
|
||||
|
||||
//TODO test missing channel-xx methods
|
||||
|
@ -384,10 +384,10 @@ func InitDefaultData(t *testing.T, ws *logic.Application) DefData {
|
||||
// Sub/Unsub for Users 12+13
|
||||
|
||||
{
|
||||
//TODO User 12 unsubscribe from 12:chan_self_unsub
|
||||
//TODO User 13 request-subscribe to 13:chan_other_request
|
||||
//TODO User 13 request-subscribe to 13:chan_other_accepted
|
||||
//TODO User 13 accept subscription from user 12 to 13:chan_other_accepted
|
||||
doUnsubscribe(t, baseUrl, users[14], users[14], "chan_self_unsub")
|
||||
doSubscribe(t, baseUrl, users[14], users[15], "chan_other_request")
|
||||
doSubscribe(t, baseUrl, users[14], users[15], "chan_other_accepted")
|
||||
doAcceptSub(t, baseUrl, users[15], users[14], "chan_other_accepted")
|
||||
}
|
||||
|
||||
success = true
|
||||
@ -395,6 +395,78 @@ func InitDefaultData(t *testing.T, ws *logic.Application) DefData {
|
||||
return DefData{User: users}
|
||||
}
|
||||
|
||||
func doSubscribe(t *testing.T, baseUrl string, user Userdat, chanOwner Userdat, chanInternalName string) {
|
||||
|
||||
if user == chanOwner {
|
||||
|
||||
RequestAuthPost[Void](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels", user.UID), gin.H{
|
||||
"channel_owner_user_id": chanOwner.UID,
|
||||
"channel_internal_name": chanInternalName,
|
||||
})
|
||||
|
||||
} else {
|
||||
type chanlist struct {
|
||||
Channels []gin.H `json:"channels"`
|
||||
}
|
||||
|
||||
clist := RequestAuthGet[chanlist](t, chanOwner.AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels?selector=owned", chanOwner.UID))
|
||||
|
||||
var chandat gin.H
|
||||
for _, v := range clist.Channels {
|
||||
if v["internal_name"].(string) == chanInternalName {
|
||||
chandat = v
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
RequestAuthPost[Void](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/subscriptions?chan_subscribe_key=%s", user.UID, chandat["subscribe_key"].(string)), gin.H{
|
||||
"channel_id": chandat["channel_id"].(float64),
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func doUnsubscribe(t *testing.T, baseUrl string, user Userdat, chanOwner Userdat, chanInternalName string) {
|
||||
|
||||
type chanlist struct {
|
||||
Subscriptions []gin.H `json:"subscriptions"`
|
||||
}
|
||||
|
||||
slist := RequestAuthGet[chanlist](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/subscriptions?selector=outgoing_confirmed", user.UID))
|
||||
|
||||
var subdat gin.H
|
||||
for _, v := range slist.Subscriptions {
|
||||
if v["channel_internal_name"].(string) == chanInternalName && int64(v["channel_owner_user_id"].(float64)) == chanOwner.UID {
|
||||
subdat = v
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
RequestAuthDelete[Void](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/subscriptions/%v", user.UID, subdat["subscription_id"]), gin.H{})
|
||||
|
||||
}
|
||||
|
||||
func doAcceptSub(t *testing.T, baseUrl string, user Userdat, subscriber Userdat, chanInternalName string) {
|
||||
|
||||
type chanlist struct {
|
||||
Subscriptions []gin.H `json:"subscriptions"`
|
||||
}
|
||||
|
||||
slist := RequestAuthGet[chanlist](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/subscriptions?selector=incoming_unconfirmed", user.UID))
|
||||
|
||||
var subdat gin.H
|
||||
for _, v := range slist.Subscriptions {
|
||||
if v["channel_internal_name"].(string) == chanInternalName && int64(v["subscriber_user_id"].(float64)) == subscriber.UID {
|
||||
subdat = v
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
RequestAuthDelete[Void](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/subscriptions/%v", user.UID, subdat["subscription_id"]), gin.H{})
|
||||
|
||||
}
|
||||
|
||||
func Lipsum(seed int64, paracount int) string {
|
||||
return loremipsum.NewWithSeed(seed).Paragraphs(paracount)
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ func RequestAny[TResult any](t *testing.T, akey string, method string, baseURL s
|
||||
TPrintln("")
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
TestFail(t, "Statuscode != 200")
|
||||
TestFailFmt(t, "Statuscode != 200 (actual = %d)", resp.StatusCode)
|
||||
}
|
||||
|
||||
var data TResult
|
||||
|
Loading…
Reference in New Issue
Block a user