test channel sharing

This commit is contained in:
Roland Osborne 2022-02-16 00:00:07 -08:00
parent ae2435e915
commit c851320749
9 changed files with 274 additions and 40 deletions

View File

@ -48,11 +48,6 @@ func GetChannelAssets(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
func GetChannelDetail(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
}
func GetChannelSize(w http.ResponseWriter, r *http.Request) { func GetChannelSize(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
@ -113,11 +108,6 @@ func RemoveChannelTopicTag(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
func SetChannelCard(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
}
func SetChannelConfirmed(w http.ResponseWriter, r *http.Request) { func SetChannelConfirmed(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)

View File

@ -19,6 +19,7 @@ func GetArticleSubjectField(w http.ResponseWriter, r *http.Request) {
field := params["field"] field := params["field"]
elements := strings.Split(field, ".") elements := strings.Split(field, ".")
var guid string
var act *store.Account var act *store.Account
tokenType := r.Header.Get("TokenType") tokenType := r.Header.Get("TokenType")
if tokenType == APP_TOKENAPP { if tokenType == APP_TOKENAPP {
@ -35,6 +36,7 @@ func GetArticleSubjectField(w http.ResponseWriter, r *http.Request) {
return return
} }
act = &card.Account act = &card.Account
guid = card.Guid
} else { } else {
ErrResponse(w, http.StatusBadRequest, errors.New("unknown token type")) ErrResponse(w, http.StatusBadRequest, errors.New("unknown token type"))
return return
@ -42,7 +44,7 @@ func GetArticleSubjectField(w http.ResponseWriter, r *http.Request) {
// load article // load article
var slot store.ArticleSlot var slot store.ArticleSlot
if err := store.DB.Preload("Article").Where("account_id = ? AND article_slot_id = ?", act.ID, articleId).First(&slot).Error; err != nil { if err := store.DB.Preload("Article.Groups.Cards").Where("account_id = ? AND article_slot_id = ?", act.ID, articleId).First(&slot).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
ErrResponse(w, http.StatusNotFound, err) ErrResponse(w, http.StatusNotFound, err)
} else { } else {
@ -55,6 +57,12 @@ func GetArticleSubjectField(w http.ResponseWriter, r *http.Request) {
return return
} }
// check if article is shared
if tokenType == APP_TOKENCONTACT && !isArticleShared(guid, slot.Article) {
ErrResponse(w, http.StatusNotFound, errors.New("referenced article not shared"))
return
}
// decode data // decode data
strData := fastjson.GetString([]byte(slot.Article.Data), elements...) strData := fastjson.GetString([]byte(slot.Article.Data), elements...)
binData, err := base64.StdEncoding.DecodeString(strData) binData, err := base64.StdEncoding.DecodeString(strData)

View File

@ -0,0 +1,66 @@
package databag
import (
"errors"
"net/http"
"gorm.io/gorm"
"github.com/gorilla/mux"
"databag/internal/store"
)
func GetChannel(w http.ResponseWriter, r *http.Request) {
// scan parameters
params := mux.Vars(r)
channelId := params["channelId"]
var guid string
var act *store.Account
tokenType := r.Header.Get("TokenType")
if tokenType == APP_TOKENAPP {
account, code, err := BearerAppToken(r, false);
if err != nil {
ErrResponse(w, code, err)
return
}
act = account
} else if tokenType == APP_TOKENCONTACT {
card, code, err := BearerContactToken(r, true)
if err != nil {
ErrResponse(w, code, err)
return
}
act = &card.Account
guid = card.Guid
} else {
ErrResponse(w, http.StatusBadRequest, errors.New("unknown token type"))
return
}
// load channel
var slot store.ChannelSlot
if err := store.DB.Preload("Channel.Cards.CardSlot").Preload("Channel.Groups.Cards").Preload("Channel.Groups.GroupSlot").Where("account_id = ? AND channel_slot_id = ?", act.ID, channelId).First(&slot).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
ErrResponse(w, http.StatusNotFound, err)
} else {
ErrResponse(w, http.StatusInternalServerError, err)
}
return
}
if slot.Channel == nil {
ErrResponse(w, http.StatusNotFound, errors.New("referenced channel missing"))
return
}
// return model data
if guid != "" {
if isChannelShared(guid, slot.Channel) {
WriteResponse(w, getChannelModel(&slot, true, false))
} else {
WriteResponse(w, getChannelModel(&slot, false, false))
}
} else {
WriteResponse(w, getChannelModel(&slot, true, true))
}
}

View File

@ -60,12 +60,12 @@ func GetChannels(w http.ResponseWriter, r *http.Request) {
var slots []store.ChannelSlot var slots []store.ChannelSlot
if channelRevisionSet { if channelRevisionSet {
if err := store.DB.Preload("Channel").Where("account_id = ? AND revision > ?", account.ID, channelRevision).Find(&slots).Error; err != nil { if err := store.DB.Preload("Channel.Cards").Preload("Channel.Groups").Where("account_id = ? AND revision > ?", account.ID, channelRevision).Find(&slots).Error; err != nil {
ErrResponse(w, http.StatusInternalServerError, err) ErrResponse(w, http.StatusInternalServerError, err)
return return
} }
} else { } else {
if err := store.DB.Preload("Channel").Where("account_id = ? AND channel_id != 0", account.ID).Find(&slots).Error; err != nil { if err := store.DB.Preload("Channel.Cards").Preload("Channel.Groups").Where("account_id = ? AND channel_id != 0", account.ID).Find(&slots).Error; err != nil {
ErrResponse(w, http.StatusInternalServerError, err) ErrResponse(w, http.StatusInternalServerError, err)
return return
} }
@ -73,9 +73,13 @@ func GetChannels(w http.ResponseWriter, r *http.Request) {
for _, slot := range slots { for _, slot := range slots {
if !typesSet || hasChannelType(types, slot.Channel) { if !typesSet || hasChannelType(types, slot.Channel) {
if channelRevisionSet {
response = append(response, getChannelRevisionModel(&slot, true))
} else if slot.Channel != nil {
response = append(response, getChannelModel(&slot, true, true)) response = append(response, getChannelModel(&slot, true, true))
} }
} }
}
w.Header().Set("Channel-Revision", strconv.FormatInt(account.ChannelRevision, 10)) w.Header().Set("Channel-Revision", strconv.FormatInt(account.ChannelRevision, 10))
@ -97,12 +101,12 @@ func GetChannels(w http.ResponseWriter, r *http.Request) {
account := &card.Account account := &card.Account
var slots []store.ChannelSlot var slots []store.ChannelSlot
if channelRevisionSet { if channelRevisionSet {
if err := store.DB.Preload("Channel.Groups.Cards").Where("account_id = ? AND revision > ?", account.ID, channelRevision).Find(&slots).Error; err != nil { if err := store.DB.Preload("Channel.Cards").Preload("Channel.Groups.Cards").Where("account_id = ? AND revision > ?", account.ID, channelRevision).Find(&slots).Error; err != nil {
ErrResponse(w, http.StatusInternalServerError, err) ErrResponse(w, http.StatusInternalServerError, err)
return return
} }
} else { } else {
if err := store.DB.Preload("Channel.Groups.Cards").Where("account_id = ? AND channel_id != 0", account.ID).Find(&slots).Error; err != nil { if err := store.DB.Preload("Channel.Cards").Preload("Channel.Groups.Cards").Where("account_id = ? AND channel_id != 0", account.ID).Find(&slots).Error; err != nil {
ErrResponse(w, http.StatusInternalServerError, err) ErrResponse(w, http.StatusInternalServerError, err)
return return
} }
@ -111,7 +115,11 @@ func GetChannels(w http.ResponseWriter, r *http.Request) {
for _, slot := range slots { for _, slot := range slots {
shared := isChannelShared(card.Guid, slot.Channel) shared := isChannelShared(card.Guid, slot.Channel)
if !typesSet || hasChannelType(types, slot.Channel) { if !typesSet || hasChannelType(types, slot.Channel) {
response = append(response, getChannelModel(&slot, shared, false)) if channelRevisionSet {
response = append(response, getChannelRevisionModel(&slot, shared))
} else if shared {
response = append(response, getChannelModel(&slot, true, false))
}
} }
} }

View File

@ -0,0 +1,83 @@
package databag
import (
"errors"
"net/http"
"gorm.io/gorm"
"github.com/gorilla/mux"
"databag/internal/store"
)
func SetChannelCard(w http.ResponseWriter, r *http.Request) {
account, code, err := BearerAppToken(r, false);
if err != nil {
ErrResponse(w, code, err)
return
}
// scan parameters
params := mux.Vars(r)
channelId := params["channelId"]
cardId := params["cardId"]
// load referenced channel
var channelSlot store.ChannelSlot
if err := store.DB.Preload("Channel").Where("account_id = ? AND channel_slot_id = ?", account.ID, channelId).First(&channelSlot).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
ErrResponse(w, http.StatusInternalServerError, err)
} else {
ErrResponse(w, http.StatusNotFound, err)
}
return
}
if channelSlot.Channel == nil {
ErrResponse(w, http.StatusNotFound, errors.New("channel has been deleted"))
return
}
// load referenced card
var cardSlot store.CardSlot
if err := store.DB.Preload("Card.CardSlot").Where("account_id = ? AND card_slot_id = ?", account.ID, cardId).First(&cardSlot).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
ErrResponse(w, http.StatusInternalServerError, err)
} else {
ErrResponse(w, http.StatusNotFound, err)
}
return
}
if cardSlot.Card == nil {
ErrResponse(w, http.StatusNotFound, errors.New("card has been deleted"))
return
}
// save and update contact revision
err = store.DB.Transaction(func(tx *gorm.DB) error {
if res := tx.Model(&channelSlot.Channel).Association("Cards").Append(cardSlot.Card); res != nil {
return res
}
if res := tx.Model(&channelSlot.Channel).Update("detail_revision", account.ChannelRevision + 1).Error; res != nil {
return res
}
if res := tx.Model(&channelSlot).Update("revision", account.ChannelRevision + 1).Error; res != nil {
return res
}
if res := tx.Model(&account).Update("channel_revision", account.ChannelRevision + 1).Error; res != nil {
return res
}
return nil
})
if err != nil {
ErrResponse(w, http.StatusInternalServerError, err)
return
}
// notify contacts of content change
SetStatus(account)
for _, card := range channelSlot.Channel.Cards {
SetContactChannelNotification(account, &card)
}
WriteResponse(w, getChannelModel(&channelSlot, true, true));
}

View File

@ -115,7 +115,7 @@ func getGroupModel(slot *store.GroupSlot) *Group {
} }
} }
func getArticleModel(slot *store.ArticleSlot, showData bool, showGroups bool) *Article { func getArticleModel(slot *store.ArticleSlot, showData bool, showList bool) *Article {
if !showData || slot.Article == nil { if !showData || slot.Article == nil {
return &Article{ return &Article{
Id: slot.ArticleSlotId, Id: slot.ArticleSlotId,
@ -123,13 +123,13 @@ func getArticleModel(slot *store.ArticleSlot, showData bool, showGroups bool) *A
} }
} }
var articleGroups *ArticleGroups var articleGroups *IdList
if showGroups { if showList {
var groups []string; var groups []string;
for _, group := range slot.Article.Groups { for _, group := range slot.Article.Groups {
groups = append(groups, group.GroupSlot.GroupSlotId) groups = append(groups, group.GroupSlot.GroupSlotId)
} }
articleGroups = &ArticleGroups{ Groups: groups } articleGroups = &IdList{ Ids: groups }
} }
return &Article{ return &Article{
@ -145,7 +145,7 @@ func getArticleModel(slot *store.ArticleSlot, showData bool, showGroups bool) *A
} }
} }
func getChannelRevisionModel(slot *store.ChannelSlot, showData bool, showGroups bool) *Channel { func getChannelRevisionModel(slot *store.ChannelSlot, showData bool) *Channel {
if !showData || slot.Channel == nil { if !showData || slot.Channel == nil {
return &Channel{ return &Channel{
@ -163,7 +163,7 @@ func getChannelRevisionModel(slot *store.ChannelSlot, showData bool, showGroups
} }
} }
func getChannelModel(slot *store.ChannelSlot, showData bool, showGroups bool) *Channel { func getChannelModel(slot *store.ChannelSlot, showData bool, showList bool) *Channel {
if !showData || slot.Channel == nil { if !showData || slot.Channel == nil {
return &Channel{ return &Channel{
@ -172,19 +172,28 @@ func getChannelModel(slot *store.ChannelSlot, showData bool, showGroups bool) *C
} }
} }
var channelGroups *ChannelGroups var channelGroups *IdList
if showGroups { if showList {
var groups []string; var groups []string;
for _, group := range slot.Channel.Groups { for _, group := range slot.Channel.Groups {
groups = append(groups, group.GroupSlot.GroupSlotId) groups = append(groups, group.GroupSlot.GroupSlotId)
} }
channelGroups = &ChannelGroups{ Groups: groups } channelGroups = &IdList{ Ids: groups }
} }
var cards []string var channelCards *IdList
if showList {
var cards []string;
for _, card := range slot.Channel.Cards { for _, card := range slot.Channel.Cards {
cards = append(cards, card.CardSlot.CardSlotId) cards = append(cards, card.CardSlot.CardSlotId)
} }
channelCards = &IdList{ Ids: cards }
}
members := []string{}
for _, card := range slot.Channel.Cards {
members = append(members, card.Guid)
}
return &Channel{ return &Channel{
Id: slot.ChannelSlotId, Id: slot.ChannelSlotId,
@ -197,7 +206,8 @@ func getChannelModel(slot *store.ChannelSlot, showData bool, showGroups bool) *C
Created: slot.Channel.Created, Created: slot.Channel.Created,
Updated: slot.Channel.Updated, Updated: slot.Channel.Updated,
Groups: channelGroups, Groups: channelGroups,
Cards: cards, Cards: channelCards,
Members: members,
}, },
}, },
} }

View File

@ -70,7 +70,7 @@ type ArticleData struct {
Updated int64 `json:"updated"` Updated int64 `json:"updated"`
Groups *ArticleGroups `json:"groups,omitempty"` Groups *IdList `json:"groups,omitempty"`
} }
type ArticleGroups struct { type ArticleGroups struct {
@ -171,14 +171,11 @@ type ChannelDetail struct {
Updated int64 `json:"updated"` Updated int64 `json:"updated"`
Groups *ChannelGroups `json:"groups,omitempty"` Groups *IdList `json:"groups,omitempty"`
Cards []string `json:"cards"` Cards *IdList `json:"groups,omitempty"`
}
type ChannelGroups struct { Members []string `json:"cards"`
Groups []string `json:"groups"`
} }
type Claim struct { type Claim struct {
@ -287,6 +284,11 @@ type Identity struct {
Node string `json:"node"` Node string `json:"node"`
} }
type IdList struct {
Ids []string `json:"ids"`
}
type NodeConfig struct { type NodeConfig struct {
Domain string `json:"domain"` Domain string `json:"domain"`

View File

@ -545,10 +545,10 @@ var routes = Routes{
}, },
Route{ Route{
"GetChannelDetail", "GetChannel",
strings.ToUpper("Get"), strings.ToUpper("Get"),
"/content/channels/{channelId}/detail", "/content/channels/{channelId}",
GetChannelDetail, GetChannel,
}, },
Route{ Route{

View File

@ -6,9 +6,76 @@ import (
) )
func TestTopicShare(t *testing.T) { func TestTopicShare(t *testing.T) {
var subject *Subject
var channel *Channel
var channels *[]Channel
aRevision := make(map[string][]string)
bRevision := make(map[string][]string)
cRevision := make(map[string][]string)
var revision string
params := make(map[string]string)
var detailRevision int64
// setup testing group // setup testing group
set, err := AddTestGroup("topicshare") set, err := AddTestGroup("topicshare")
assert.NoError(t, err) assert.NoError(t, err)
PrintMsg(set)
// add new channel
channel = &Channel{}
subject = &Subject{ Data: "channeldata", DataType: "channeldatatype" }
assert.NoError(t, ApiTestMsg(AddChannel, "POST", "/content/channels",
nil, subject, APP_TOKENAPP, set.A.Token, channel, nil))
// retrieve channels
channels = &[]Channel{}
assert.NoError(t, ApiTestMsg(GetChannels, "GET", "/content/channels",
nil, nil, APP_TOKENAPP, set.A.Token, channels, &aRevision))
assert.Equal(t, 1, len(*channels))
assert.NotNil(t, (*channels)[0].Data)
detailRevision = (*channels)[0].Data.DetailRevision
channels = &[]Channel{}
assert.NoError(t, ApiTestMsg(GetChannels, "GET", "/content/channels",
nil, nil, APP_TOKENCONTACT, set.B.A.Token, channels, &bRevision))
assert.Equal(t, 0, len(*channels))
channels = &[]Channel{}
assert.NoError(t, ApiTestMsg(GetChannels, "GET", "/content/channels",
nil, nil, APP_TOKENCONTACT, set.C.A.Token, channels, &cRevision))
assert.Equal(t, 0, len(*channels))
// assign channel to B
params["channelId"] = channel.Id
params["cardId"] = set.A.B.CardId
assert.NoError(t, ApiTestMsg(SetChannelCard, "PUT", "/content/channels/{channelId}/cards/{cardId}",
&params, nil, APP_TOKENAPP, set.A.Token, nil, nil))
// check shared channel
channels = &[]Channel{}
revision = "?channelRevision=" + aRevision["Channel-Revision"][0]
assert.NoError(t, ApiTestMsg(GetChannels, "GET", "/content/channels" + revision,
nil, nil, APP_TOKENAPP, set.A.Token, channels, &aRevision))
assert.Equal(t, 1, len(*channels))
assert.NotNil(t, (*channels)[0].Data)
assert.NotEqual(t, detailRevision, (*channels)[0].Data.DetailRevision)
channels = &[]Channel{}
revision = "?channelRevision=" + bRevision["Channel-Revision"][0] + "&viewRevision=" + bRevision["View-Revision"][0]
assert.NoError(t, ApiTestMsg(GetChannels, "GET", "/content/channels" + revision,
nil, nil, APP_TOKENCONTACT, set.B.A.Token, channels, &bRevision))
assert.Equal(t, 1, len(*channels))
assert.NotNil(t, (*channels)[0].Data)
channels = &[]Channel{}
revision = "?channelRevision=" + cRevision["Channel-Revision"][0] + "&viewRevision=" + cRevision["View-Revision"][0]
assert.NoError(t, ApiTestMsg(GetChannels, "GET", "/content/channels" + revision,
nil, nil, APP_TOKENCONTACT, set.C.A.Token, channels, &cRevision))
assert.Equal(t, 1, len(*channels))
assert.Nil(t, (*channels)[0].Data)
// get discovered channel
channel = &Channel{}
assert.NoError(t, ApiTestMsg(GetChannel, "GET", "/content/channels/{channelId}",
&params, nil, APP_TOKENCONTACT, set.B.A.Token, channel, nil))
assert.Equal(t, "channeldatatype", channel.Data.ChannelDetail.DataType)
assert.Equal(t, 1, len(channel.Data.ChannelDetail.Members))
assert.Equal(t, set.B.Guid, channel.Data.ChannelDetail.Members[0])
} }