diff --git a/net/server/internal/api_content.go b/net/server/internal/api_content.go index 3b33e19c..ca7e0bea 100644 --- a/net/server/internal/api_content.go +++ b/net/server/internal/api_content.go @@ -48,11 +48,6 @@ func GetChannelAssets(w http.ResponseWriter, r *http.Request) { 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) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusOK) @@ -113,11 +108,6 @@ func RemoveChannelTopicTag(w http.ResponseWriter, r *http.Request) { 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) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusOK) diff --git a/net/server/internal/api_getArticleSubjectField.go b/net/server/internal/api_getArticleSubjectField.go index 224bd28a..fc8665ca 100644 --- a/net/server/internal/api_getArticleSubjectField.go +++ b/net/server/internal/api_getArticleSubjectField.go @@ -19,6 +19,7 @@ func GetArticleSubjectField(w http.ResponseWriter, r *http.Request) { field := params["field"] elements := strings.Split(field, ".") + var guid string var act *store.Account tokenType := r.Header.Get("TokenType") if tokenType == APP_TOKENAPP { @@ -35,6 +36,7 @@ func GetArticleSubjectField(w http.ResponseWriter, r *http.Request) { return } act = &card.Account + guid = card.Guid } else { ErrResponse(w, http.StatusBadRequest, errors.New("unknown token type")) return @@ -42,7 +44,7 @@ func GetArticleSubjectField(w http.ResponseWriter, r *http.Request) { // load article 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) { ErrResponse(w, http.StatusNotFound, err) } else { @@ -55,6 +57,12 @@ func GetArticleSubjectField(w http.ResponseWriter, r *http.Request) { 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 strData := fastjson.GetString([]byte(slot.Article.Data), elements...) binData, err := base64.StdEncoding.DecodeString(strData) diff --git a/net/server/internal/api_getChannel.go b/net/server/internal/api_getChannel.go new file mode 100644 index 00000000..6660eee4 --- /dev/null +++ b/net/server/internal/api_getChannel.go @@ -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)) + } +} + diff --git a/net/server/internal/api_getChannels.go b/net/server/internal/api_getChannels.go index 78097b33..6c7aa67b 100644 --- a/net/server/internal/api_getChannels.go +++ b/net/server/internal/api_getChannels.go @@ -60,12 +60,12 @@ func GetChannels(w http.ResponseWriter, r *http.Request) { var slots []store.ChannelSlot 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) return } } 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) return } @@ -73,7 +73,11 @@ func GetChannels(w http.ResponseWriter, r *http.Request) { for _, slot := range slots { if !typesSet || hasChannelType(types, slot.Channel) { - response = append(response, getChannelModel(&slot, true, true)) + if channelRevisionSet { + response = append(response, getChannelRevisionModel(&slot, true)) + } else if slot.Channel != nil { + response = append(response, getChannelModel(&slot, true, true)) + } } } @@ -97,12 +101,12 @@ func GetChannels(w http.ResponseWriter, r *http.Request) { account := &card.Account var slots []store.ChannelSlot 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) return } } 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) return } @@ -111,7 +115,11 @@ func GetChannels(w http.ResponseWriter, r *http.Request) { for _, slot := range slots { shared := isChannelShared(card.Guid, 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)) + } } } diff --git a/net/server/internal/api_setChannelCard.go b/net/server/internal/api_setChannelCard.go new file mode 100644 index 00000000..c38032a3 --- /dev/null +++ b/net/server/internal/api_setChannelCard.go @@ -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)); +} + diff --git a/net/server/internal/modelUtil.go b/net/server/internal/modelUtil.go index 956adb97..a772ed9b 100644 --- a/net/server/internal/modelUtil.go +++ b/net/server/internal/modelUtil.go @@ -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 { return &Article{ Id: slot.ArticleSlotId, @@ -123,13 +123,13 @@ func getArticleModel(slot *store.ArticleSlot, showData bool, showGroups bool) *A } } - var articleGroups *ArticleGroups - if showGroups { + var articleGroups *IdList + if showList { var groups []string; for _, group := range slot.Article.Groups { groups = append(groups, group.GroupSlot.GroupSlotId) } - articleGroups = &ArticleGroups{ Groups: groups } + articleGroups = &IdList{ Ids: groups } } 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 { 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 { return &Channel{ @@ -172,18 +172,27 @@ func getChannelModel(slot *store.ChannelSlot, showData bool, showGroups bool) *C } } - var channelGroups *ChannelGroups - if showGroups { + var channelGroups *IdList + if showList { var groups []string; for _, group := range slot.Channel.Groups { 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 { + cards = append(cards, card.CardSlot.CardSlotId) + } + channelCards = &IdList{ Ids: cards } + } + + members := []string{} for _, card := range slot.Channel.Cards { - cards = append(cards, card.CardSlot.CardSlotId) + members = append(members, card.Guid) } return &Channel{ @@ -197,7 +206,8 @@ func getChannelModel(slot *store.ChannelSlot, showData bool, showGroups bool) *C Created: slot.Channel.Created, Updated: slot.Channel.Updated, Groups: channelGroups, - Cards: cards, + Cards: channelCards, + Members: members, }, }, } diff --git a/net/server/internal/models.go b/net/server/internal/models.go index 8a28d119..6047195f 100644 --- a/net/server/internal/models.go +++ b/net/server/internal/models.go @@ -70,7 +70,7 @@ type ArticleData struct { Updated int64 `json:"updated"` - Groups *ArticleGroups `json:"groups,omitempty"` + Groups *IdList `json:"groups,omitempty"` } type ArticleGroups struct { @@ -171,14 +171,11 @@ type ChannelDetail struct { 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 { - - Groups []string `json:"groups"` + Members []string `json:"cards"` } type Claim struct { @@ -287,6 +284,11 @@ type Identity struct { Node string `json:"node"` } +type IdList struct { + + Ids []string `json:"ids"` +} + type NodeConfig struct { Domain string `json:"domain"` diff --git a/net/server/internal/routers.go b/net/server/internal/routers.go index e87eec00..b20450dc 100644 --- a/net/server/internal/routers.go +++ b/net/server/internal/routers.go @@ -545,10 +545,10 @@ var routes = Routes{ }, Route{ - "GetChannelDetail", + "GetChannel", strings.ToUpper("Get"), - "/content/channels/{channelId}/detail", - GetChannelDetail, + "/content/channels/{channelId}", + GetChannel, }, Route{ diff --git a/net/server/internal/ucTopicShare_test.go b/net/server/internal/ucTopicShare_test.go index 2d008e4c..ecf311bf 100644 --- a/net/server/internal/ucTopicShare_test.go +++ b/net/server/internal/ucTopicShare_test.go @@ -6,9 +6,76 @@ import ( ) 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 set, err := AddTestGroup("topicshare") 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}", + ¶ms, 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}", + ¶ms, 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]) } + +