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
|
||||
@ -29,12 +30,13 @@ const (
|
||||
CHANNEL_TOO_LONG APIError = 1207
|
||||
CHANNEL_NAME_WOULD_CHANGE APIError = 1207
|
||||
|
||||
USER_NOT_FOUND APIError = 1301
|
||||
CLIENT_NOT_FOUND APIError = 1302
|
||||
CHANNEL_NOT_FOUND APIError = 1303
|
||||
SUBSCRIPTION_NOT_FOUND APIError = 1304
|
||||
MESSAGE_NOT_FOUND APIError = 1305
|
||||
USER_AUTH_FAILED APIError = 1311
|
||||
USER_NOT_FOUND APIError = 1301
|
||||
CLIENT_NOT_FOUND APIError = 1302
|
||||
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"
|
||||
@ -501,8 +500,8 @@ func (h APIHandler) DeleteClient(g *gin.Context) ginresp.HTTPResponse {
|
||||
// @ID api-channels-list
|
||||
// @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 uid path int true "UserID"
|
||||
// @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 {
|
||||
@ -915,27 +918,27 @@ 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)
|
||||
// //
|
||||
// @ID api-user-subscriptions-list
|
||||
// @Tags API-v2
|
||||
// @Summary List all subscriptions of a user (incoming/owned)
|
||||
// @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)
|
||||
//
|
||||
// @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)
|
||||
// @ID api-user-subscriptions-list
|
||||
// @Tags API-v2
|
||||
//
|
||||
// @Success 200 {object} handler.ListUserSubscriptions.response
|
||||
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
|
||||
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
|
||||
// @Failure 500 {object} ginresp.apiError "internal server error"
|
||||
// @Param uid path int true "UserID"
|
||||
// @Param selector query string true "Filter subscriptions (default: owner_all)" Enums(outgoing_all, outgoing_confirmed, outgoing_unconfirmed, incoming_all, incoming_confirmed, incoming_unconfirmed)
|
||||
//
|
||||
// @Router /api/users/{uid}/subscriptions [GET]
|
||||
// @Success 200 {object} handler.ListUserSubscriptions.response
|
||||
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
|
||||
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
|
||||
// @Failure 500 {object} ginresp.apiError "internal server error"
|
||||
//
|
||||
// @Router /api/users/{uid}/subscriptions [GET]
|
||||
func (h APIHandler) ListUserSubscriptions(g *gin.Context) ginresp.HTTPResponse {
|
||||
type uri struct {
|
||||
UserID models.UserID `uri:"uid"`
|
||||
@ -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,27 +1175,29 @@ func (h APIHandler) CancelSubscription(g *gin.Context) ginresp.HTTPResponse {
|
||||
|
||||
// CreateSubscription swaggerdoc
|
||||
//
|
||||
// @Summary Creare/Request a subscription
|
||||
// @ID api-subscriptions-create
|
||||
// @Tags API-v2
|
||||
// @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
|
||||
//
|
||||
// @Param uid path int true "UserID"
|
||||
// @Param query_data query handler.CreateSubscription.query false " "
|
||||
// @Param post_data body handler.CreateSubscription.body false " "
|
||||
// @Param uid path int true "UserID"
|
||||
// @Param query_data query handler.CreateSubscription.query false " "
|
||||
// @Param post_data body handler.CreateSubscription.body false " "
|
||||
//
|
||||
// @Success 200 {object} models.SubscriptionJSON
|
||||
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
|
||||
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
|
||||
// @Failure 500 {object} ginresp.apiError "internal server error"
|
||||
// @Success 200 {object} models.SubscriptionJSON
|
||||
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
|
||||
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
|
||||
// @Failure 500 {object} ginresp.apiError "internal server error"
|
||||
//
|
||||
// @Router /api/users/{uid}/subscriptions [POST]
|
||||
// @Router /api/users/{uid}/subscriptions [POST]
|
||||
func (h APIHandler) CreateSubscription(g *gin.Context) ginresp.HTTPResponse {
|
||||
type uri struct {
|
||||
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
|
||||
|
||||
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 outchannel == nil {
|
||||
return ginresp.APIError(g, 400, apierr.CHANNEL_NOT_FOUND, "Channel not found", err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
channel, 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 {
|
||||
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)
|
||||
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)
|
||||
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"`
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -257,8 +257,8 @@ var messageExamples = []msgex{
|
||||
{11, "Promotions", "localhost", P2, SKEY, "New Product Launch: Introducing Our Latest Innovation", "We are excited to announce the release of our newest product, designed to revolutionize the industry. Don't miss out on this game-changing technology.", timeext.FromHours(-12.21)},
|
||||
{11, "Promotions", "#S0", P0, SKEY, "Limited Time Offer: Get 50% Off Your Next Purchase", "For a limited time, take advantage of our special offer and get half off your next purchase. Don't miss out on this amazing deal.", 0},
|
||||
{11, "Promotions", "#S0", P2, SKEY, "Customer Appreciation Sale: Save Up to 75% on Your Favorite Products", "", 0},
|
||||
{11, "Promotions", "", P0, SKEY, "Sign Up for Our Newsletter and Save 10% on Your Next Order", "", 0},
|
||||
{11, "Promotions", "", PX, AKEY, "New Arrivals: Check Out Our Latest Collection", "We've just added new items to our collection and we think you'll love them. Take a look and see what's new in fashion, home decor, and more.", 0},
|
||||
{11, " Promotions", "", P0, SKEY, "Sign Up for Our Newsletter and Save 10% on Your Next Order", "", 0},
|
||||
{11, "Promotions ", "", PX, AKEY, "New Arrivals: Check Out Our Latest Collection", "We've just added new items to our collection and we think you'll love them. Take a look and see what's new in fashion, home decor, and more.", 0},
|
||||
{11, "Promotions", "", PX, AKEY, "Join Our Rewards Program and Earn Points on Every Purchase", "Sign up for our rewards program and earn points on every purchase you make. Redeem your points for discounts, free products, and more.", 0},
|
||||
{11, "Promotions", "#S0", P0, SKEY, "Seasonal Special: Save on Your Favorite Fall Products", "As the leaves change color and the air gets cooler, we have the perfect products to help you enjoy the season. Take advantage of our special offers and save on your favorite fall products.", 0},
|
||||
{11, "Promotions", "192.168.0.1", P1, AKEY, "Refer a Friend and Save on Your Next Order", "Share the love and refer a friend to our store. When they make a purchase, you'll receive a discount on your next order. It's a win-win for both of you.", 0},
|
||||
@ -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