diff --git a/doc/api.oa3 b/doc/api.oa3 index c531218b..4da3e0c6 100644 --- a/doc/api.oa3 +++ b/doc/api.oa3 @@ -2045,12 +2045,12 @@ paths: application/json: schema: $ref: '#/components/schemas/Subject' - + /content/channels/{channelId}/groups/{groupId}: put: tags: - content - description: Set group for channels. Access granted to app tokens for account holder. + description: Set group for read access to channel. Access granted to app tokens for account holder. operationId: set-channel-group security: - bearerAuth: [] @@ -2085,7 +2085,7 @@ paths: delete: tags: - content - description: Clear sharing group for channel. Access granted to app tokens for account holder. + description: Clear read access to channel for group. Access granted to app tokens for account holder. operationId: clear-channel-group security: - bearerAuth: [] @@ -2118,6 +2118,78 @@ paths: '500': description: internal server error + /content/channels/{channelId}/cards/{cardId}: + put: + tags: + - content + description: Set card for write access to channel. Access granted to app tokens for account holder. + operationId: set-channel-card + security: + - bearerAuth: [] + parameters: + - name: channelId + in: path + description: specified channel id + required: true + schema: + type: string + - name: cardId + in: path + description: specified card id + required: true + schema: + type: string + responses: + '200': + description: success + content: + application/json: + schema: + $ref: '#/components/schemas/Channel' + '401': + description: permission denied + '404': + description: card or group not found + '410': + description: account disabled + '500': + description: internal server error + delete: + tags: + - content + description: Clear write access to channel for card. Access granted to app tokens for account holder. + operationId: clear-channel-card + security: + - bearerAuth: [] + parameters: + - name: channelId + in: path + description: specified channel id + required: true + schema: + type: string + - name: cardId + in: path + description: specified card id + required: true + schema: + type: string + responses: + '200': + description: success + content: + application/json: + schema: + $ref: '#/components/schemas/Channel' + '401': + description: permission denied + '404': + description: card or group not found + '410': + description: account disabled + '500': + description: internal server error + /content/channels/{channelId}/topics: get: tags: @@ -3141,18 +3213,12 @@ components: updated: type: integer format: int64 - viewers: + groups: + $ref: '#/components/schemas/ChannelGroups' + cards: type: array items: type: string - viewerGroups: - $ref: '#/components/schemas/ChannelGroups' - members: - type: array - items: - type: string - memberGroups: - $ref: '#/components/schemas/ChannelGroups' ChannelSize: type: object @@ -3520,3 +3586,4 @@ components: scheme: bearer + diff --git a/net/server/internal/api_clearCardGroup.go b/net/server/internal/api_clearCardGroup.go new file mode 100644 index 00000000..e83fd870 --- /dev/null +++ b/net/server/internal/api_clearCardGroup.go @@ -0,0 +1,84 @@ +package databag + +import ( + "errors" + "net/http" + "gorm.io/gorm" + "github.com/gorilla/mux" + "databag/internal/store" +) + +func ClearCardGroup(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) + cardId := params["cardId"] + groupId := params["groupId"] + + // load referenced card + var cardSlot store.CardSlot + if err := store.DB.Preload("Card").Where("account_id = ? AND card_slot_id = ?", account.ID, cardId).First(&cardSlot).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + ErrResponse(w, http.StatusNotFound, err) + } else { + ErrResponse(w, http.StatusInternalServerError, err) + } + return + } + if cardSlot.Card == nil { + ErrResponse(w, http.StatusNotFound, errors.New("card has been deleted")) + return + } + + // load referenced group + var groupSlot store.GroupSlot + if err := store.DB.Preload("Group").Where("account_id = ? AND group_slot_id = ?", account.ID, groupId).First(&groupSlot).Error; err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + ErrResponse(w, http.StatusInternalServerError, err) + } else { + ErrResponse(w, http.StatusNotFound, err) + } + return + } + if groupSlot.Group == nil { + ErrResponse(w, http.StatusNotFound, errors.New("referenced deleted group")) + return + } + + // save and update revision + cardSlot.Revision = account.CardRevision + 1 + err = store.DB.Transaction(func(tx *gorm.DB) error { + if res := tx.Model(&cardSlot.Card).Association("Groups").Delete(groupSlot.Group); res != nil { + return res + } + if res := tx.Model(&cardSlot.Card).Update("detail_revision", cardSlot.Card.DetailRevision + 1).Error; res != nil { + return res + } + if res := tx.Model(&cardSlot.Card).Update("view_revision", cardSlot.Card.ViewRevision + 1).Error; res != nil { + return res + } + if res := tx.Model(&cardSlot).Update("revision", account.CardRevision + 1).Error; res != nil { + return res + } + if res := tx.Model(&account).Update("card_revision", account.CardRevision + 1).Error; res != nil { + return res + } + return nil + }) + if err != nil { + ErrResponse(w, http.StatusInternalServerError, err) + return + } + + SetContactViewNotification(account, cardSlot.Card) + SetStatus(account) + WriteResponse(w, getCardModel(&cardSlot)) +} + + diff --git a/net/server/internal/api_contact.go b/net/server/internal/api_contact.go index 06708f96..841ed0b2 100644 --- a/net/server/internal/api_contact.go +++ b/net/server/internal/api_contact.go @@ -13,11 +13,6 @@ import ( "net/http" ) -func ClearCardGroup(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) -} - func GetCloseMessage(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_content.go b/net/server/internal/api_content.go index 2c6e66f1..4babd62b 100644 --- a/net/server/internal/api_content.go +++ b/net/server/internal/api_content.go @@ -33,6 +33,11 @@ func AddChannelTopicTag(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } +func ClearChannelCard(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) +} + func ClearChannelGroup(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusOK) @@ -118,6 +123,11 @@ 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_getCard.go b/net/server/internal/api_getCard.go index 2a122b23..1945b34a 100644 --- a/net/server/internal/api_getCard.go +++ b/net/server/internal/api_getCard.go @@ -18,7 +18,7 @@ func GetCard(w http.ResponseWriter, r *http.Request) { cardId := mux.Vars(r)["cardId"] var slot store.CardSlot - if err := store.DB.Preload("Card.Groups").Where("account_id = ? AND card_slot_id = ?", account.ID, cardId).First(&slot).Error; err != nil { + if err := store.DB.Preload("Card.Groups.GroupSlot").Where("account_id = ? AND card_slot_id = ?", account.ID, cardId).First(&slot).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { ErrResponse(w, http.StatusNotFound, err) } else { diff --git a/net/server/internal/api_setArticleRevision.go b/net/server/internal/api_setArticleRevision.go index 8a38ad9d..dbff352b 100644 --- a/net/server/internal/api_setArticleRevision.go +++ b/net/server/internal/api_setArticleRevision.go @@ -32,7 +32,7 @@ func NotifyArticleRevision(card *store.Card, revision int64) error { act := &card.Account err := store.DB.Transaction(func(tx *gorm.DB) error { - if res := tx.Model(card).Where("id = ?", card.ID).Update("notified_profile", revision).Error; res != nil { + if res := tx.Model(card).Where("id = ?", card.ID).Update("notified_article", revision).Error; res != nil { return res } if res := tx.Model(&card.CardSlot).Where("id = ?", card.CardSlot.ID).Update("revision", act.CardRevision+1).Error; res != nil { diff --git a/net/server/internal/api_setCardGroup.go b/net/server/internal/api_setCardGroup.go index e8055f10..2e61d68a 100644 --- a/net/server/internal/api_setCardGroup.go +++ b/net/server/internal/api_setCardGroup.go @@ -51,19 +51,22 @@ func SetCardGroup(w http.ResponseWriter, r *http.Request) { } // save and update revision - slot.Card.Groups = append(slot.Card.Groups, *groupSlot.Group) - slot.Card.ViewRevision += 1 - slot.Revision = account.CardRevision + 1 err = store.DB.Transaction(func(tx *gorm.DB) error { + if res := tx.Model(&slot.Card).Association("Groups").Append(groupSlot.Group); res != nil { + return res + } + if res := tx.Model(&slot.Card).Update("detail_revision", slot.Card.DetailRevision + 1).Error; res != nil { + return res + } + if res := tx.Model(&slot.Card).Update("view_revision", slot.Card.ViewRevision + 1).Error; res != nil { + return res + } + if res := tx.Model(&slot).Update("revision", account.CardRevision + 1).Error; res != nil { + return res + } if res := tx.Model(&account).Update("card_revision", account.CardRevision + 1).Error; res != nil { return res } - if res := tx.Save(&slot.Card).Error; res != nil { - return res - } - if res := tx.Preload("CardData.Groups").Save(&slot).Error; res != nil { - return res - } return nil }) if err != nil { diff --git a/net/server/internal/api_setChannelRevision.go b/net/server/internal/api_setChannelRevision.go index c4ed953f..1fd9ca93 100644 --- a/net/server/internal/api_setChannelRevision.go +++ b/net/server/internal/api_setChannelRevision.go @@ -32,7 +32,7 @@ func NotifyChannelRevision(card *store.Card, revision int64) error { act := &card.Account err := store.DB.Transaction(func(tx *gorm.DB) error { - if res := tx.Model(card).Where("id = ?", card.ID).Update("notified_profile", revision).Error; res != nil { + if res := tx.Model(card).Where("id = ?", card.ID).Update("notified_channel", revision).Error; res != nil { return res } if res := tx.Model(&card.CardSlot).Where("id = ?", card.CardSlot.ID).Update("revision", act.CardRevision+1).Error; res != nil { diff --git a/net/server/internal/api_setViewRevision.go b/net/server/internal/api_setViewRevision.go index 0b0f4765..d6791729 100644 --- a/net/server/internal/api_setViewRevision.go +++ b/net/server/internal/api_setViewRevision.go @@ -32,7 +32,7 @@ func NotifyViewRevision(card *store.Card, revision int64) error { act := &card.Account err := store.DB.Transaction(func(tx *gorm.DB) error { - if res := tx.Model(card).Where("id = ?", card.ID).Update("notified_profile", revision).Error; res != nil { + if res := tx.Model(card).Where("id = ?", card.ID).Update("notified_view", revision).Error; res != nil { return res } if res := tx.Model(&card.CardSlot).Where("id = ?", card.CardSlot.ID).Update("revision", act.CardRevision+1).Error; res != nil { diff --git a/net/server/internal/models.go b/net/server/internal/models.go index f4141222..8ec9c11d 100644 --- a/net/server/internal/models.go +++ b/net/server/internal/models.go @@ -177,13 +177,9 @@ type ChannelDetail struct { Updated int64 `json:"updated"` - Viewers []string `json:"viewers,omitempty"` + Groups *ChannelGroups `json:"groups,omitempty"` - ViewerGroups *ChannelGroups `json:"viewerGroups,omitempty"` - - Members []string `json:"members,omitempty"` - - MemberGroups *ChannelGroups `json:"memberGroups,omitempty"` + Cards []string `json:"cards"` } type ChannelGroups struct { diff --git a/net/server/internal/routers.go b/net/server/internal/routers.go index 970380ff..54d9fee5 100644 --- a/net/server/internal/routers.go +++ b/net/server/internal/routers.go @@ -145,13 +145,6 @@ var routes = Routes{ GetAccountUsername, }, - Route{ - "GetCard", - strings.ToUpper("Get"), - "/contact/cards/{cardId}", - GetCard, - }, - Route{ "GetPublicStatus", strings.ToUpper("Get"), @@ -523,6 +516,13 @@ var routes = Routes{ AddChannelTopicTag, }, + Route{ + "ClearChannelCard", + strings.ToUpper("Delete"), + "/content/channels/{channelId}/cards/{cardId}", + ClearChannelCard, + }, + Route{ "ClearChannelGroup", strings.ToUpper("Delete"), @@ -642,6 +642,13 @@ var routes = Routes{ RemoveChannelTopicTag, }, + Route{ + "SetChannelCard", + strings.ToUpper("Put"), + "/content/channels/{channelId}/cards/{cardId}", + SetChannelCard, + }, + Route{ "SetChannelConfirmed", strings.ToUpper("Put"), @@ -719,3 +726,4 @@ var routes = Routes{ Status, }, } + diff --git a/net/server/internal/ucContactSync_test.go b/net/server/internal/ucContactSync_test.go index c0739f69..cf33ea6a 100644 --- a/net/server/internal/ucContactSync_test.go +++ b/net/server/internal/ucContactSync_test.go @@ -10,7 +10,7 @@ import ( func TestContactSync(t *testing.T) { var profile Profile var msg DataMessage - var card Card + var card *Card param := map[string]string{} var img []byte var data []byte @@ -21,6 +21,7 @@ func TestContactSync(t *testing.T) { var detailRevision int64 var detail CardDetail var rev *Revision + var viewRevision int64 // setup testing group set, err := AddTestGroup("contactsync") @@ -72,7 +73,7 @@ func TestContactSync(t *testing.T) { // clear card notes GetTestRevision(set.B.Revisions) - assert.NoError(t, ApiTestMsg(ClearCardNotes, "DELETE", "/conact/cards/{cardId}/notes", + assert.NoError(t, ApiTestMsg(ClearCardNotes, "DELETE", "/contact/cards/{cardId}/notes", ¶m, nil, APP_TOKENAPP, set.B.Token, &detail, nil)) assert.NotEqual(t, rev.Card, GetTestRevision(set.B.Revisions).Card) cards = &[]Card{} @@ -81,4 +82,26 @@ func TestContactSync(t *testing.T) { assert.Equal(t, 1, len(*cards)) assert.NotEqual(t, detailRevision, (*cards)[0].Data.DetailRevision) + // remove card from group + card = &Card{} + param["cardId"] = set.B.A.CardId + assert.NoError(t, ApiTestMsg(GetCard, "GET", "/contact/cards/{cardId}", + ¶m, nil, APP_TOKENAPP, set.B.Token, card, nil)) + viewRevision = card.Data.NotifiedView + + card = &Card{} + param["cardId"] = set.A.B.CardId + param["groupId"] = set.A.B.GroupId + assert.NoError(t, ApiTestMsg(ClearCardGroup, "DELETE", "/contact/cards/{cardId}/groups/{groupId}", + ¶m, nil, APP_TOKENAPP, set.A.Token, card, nil)) + assert.Equal(t, 0, len(card.Data.CardDetail.Groups)) + assert.NotEqual(t, rev.Card, GetTestRevision(set.B.Revisions).Card) + + card = &Card{} + param["cardId"] = set.B.A.CardId + assert.NoError(t, ApiTestMsg(GetCard, "GET", "/contact/cards/{cardId}", + ¶m, nil, APP_TOKENAPP, set.B.Token, card, nil)) + assert.NotEqual(t, viewRevision, card.Data.NotifiedView) + PrintMsg(card) + }