diff --git a/net/server/internal/api_addCard.go b/net/server/internal/api_addCard.go index c7e87f9e..3e8cb5d8 100644 --- a/net/server/internal/api_addCard.go +++ b/net/server/internal/api_addCard.go @@ -33,7 +33,7 @@ func AddCard(w http.ResponseWriter, r *http.Request) { slot := &store.CardSlot{} var card store.Card - if err := store.DB.Preload("Card.Groups").Where("account_id = ? AND guid = ?", account.ID, guid).First(&card).Error; err != nil { + if err := store.DB.Preload("CardSlot").Preload("Groups").Where("account_id = ? AND guid = ?", account.Guid, guid).First(&card).Error; err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { ErrResponse(w, http.StatusInternalServerError, err) return @@ -110,7 +110,6 @@ func AddCard(w http.ResponseWriter, r *http.Request) { } } } else { - if identity.Revision > card.ProfileRevision { // update card data @@ -122,35 +121,33 @@ func AddCard(w http.ResponseWriter, r *http.Request) { card.Version = identity.Version card.Node = identity.Node card.ProfileRevision = identity.Revision - - // save contact card - err = store.DB.Transaction(func(tx *gorm.DB) error { - if res := tx.Save(&card).Error; res != nil { - return res - } - if res := store.DB.Where("account_id = ? AND card_id = ?", account.ID, card.ID).First(&slot).Error; res != nil { - if !errors.Is(res, gorm.ErrRecordNotFound) { - return nil - } - slot = &store.CardSlot{ - CardSlotId: uuid.New().String(), - AccountID: account.ID, - Revision: account.CardRevision + 1, - CardID: card.ID, - Card: &card, - } - } else { - slot.Revision = account.CardRevision + 1 - } - if res := tx.Preload("Card").Save(slot).Error; res != nil { - return res - } - if res := tx.Model(&account).Update("card_revision", account.CardRevision + 1).Error; res != nil { - return res - } - return nil - }) } + + // save contact card + err = store.DB.Transaction(func(tx *gorm.DB) error { + slot = &card.CardSlot + if res := tx.Save(&card).Error; res != nil { + return res + } + if slot == nil { + slot = &store.CardSlot{ + CardSlotId: uuid.New().String(), + AccountID: account.ID, + Revision: account.CardRevision + 1, + CardID: card.ID, + Card: &card, + } + } else { + slot.Revision = account.CardRevision + 1 + } + if res := tx.Preload("Card").Save(slot).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 { diff --git a/net/server/internal/api_getArticles.go b/net/server/internal/api_getArticles.go index e8c28e67..4343d034 100644 --- a/net/server/internal/api_getArticles.go +++ b/net/server/internal/api_getArticles.go @@ -11,6 +11,7 @@ func GetArticles(w http.ResponseWriter, r *http.Request) { var res error var viewRevision int64 var contentRevision int64 + var revisionSet bool view := r.FormValue("viewRevision") if view != "" { @@ -25,6 +26,7 @@ func GetArticles(w http.ResponseWriter, r *http.Request) { ErrResponse(w, http.StatusBadRequest, res) return } + revisionSet = true } tokenType := r.Header.Get("TokenType") @@ -55,7 +57,12 @@ func GetArticles(w http.ResponseWriter, r *http.Request) { } if viewRevision != card.ViewRevision + card.Account.ViewRevision { - contentRevision = 0 + if revisionSet { + ErrResponse(w, http.StatusGone, errors.New("article view unavailable")) + return + } else { + w.Header().Set("View-Revision", strconv.FormatInt(card.ViewRevision + card.Account.ViewRevision, 10)) + } } var articles []store.ArticleSlot @@ -65,7 +72,11 @@ func GetArticles(w http.ResponseWriter, r *http.Request) { } for _, article := range articles { - response = append(response, getArticleModel(&article, true, isShared(&article, card.Guid))) + if isShared(&article, card.Guid) { + response = append(response, getArticleModel(&article, true, true)) + } else if revisionSet { + response = append(response, getArticleModel(&article, true, false)) + } } } else { ErrResponse(w, http.StatusBadRequest, errors.New("invalid token type")) diff --git a/net/server/internal/api_getOpenMessage.go b/net/server/internal/api_getOpenMessage.go index da3f31d8..f5f91ec7 100644 --- a/net/server/internal/api_getOpenMessage.go +++ b/net/server/internal/api_getOpenMessage.go @@ -42,6 +42,8 @@ func GetOpenMessage(w http.ResponseWriter, r *http.Request) { Token: slot.Card.InToken, ContentRevision: account.ContentRevision + account.ViewRevision + slot.Card.ViewRevision, ProfileRevision: account.ProfileRevision, + ViewRevision: account.ViewRevision + slot.Card.ViewRevision, + LabelRevision: account.LabelRevision, Handle: account.Username, Name: detail.Name, Description: detail.Description, diff --git a/net/server/internal/api_setCardStatus.go b/net/server/internal/api_setCardStatus.go index f4c1911d..f17cf69a 100644 --- a/net/server/internal/api_setCardStatus.go +++ b/net/server/internal/api_setCardStatus.go @@ -2,6 +2,7 @@ package databag import ( "errors" + "strconv" "net/http" "encoding/hex" "gorm.io/gorm" @@ -11,6 +12,7 @@ import ( ) func SetCardStatus(w http.ResponseWriter, r *http.Request) { + var res error account, code, err := BearerAppToken(r, false); if err != nil { @@ -23,6 +25,40 @@ func SetCardStatus(w http.ResponseWriter, r *http.Request) { cardId := params["cardId"] token := r.FormValue("token") + // scan revisions + var viewRevision int64 + view := r.FormValue("viewRevision") + if view != "" { + if viewRevision, res = strconv.ParseInt(view, 10, 64); res != nil { + ErrResponse(w, http.StatusBadRequest, res) + return + } + } + var contentRevision int64 + content := r.FormValue("contentRevision") + if content != "" { + if contentRevision, res = strconv.ParseInt(content, 10, 64); res != nil { + ErrResponse(w, http.StatusBadRequest, res) + return + } + } + var labelRevision int64 + label := r.FormValue("labelRevision") + if label != "" { + if labelRevision, res = strconv.ParseInt(label, 10, 64); res != nil { + ErrResponse(w, http.StatusBadRequest, res) + return + } + } + var profileRevision int64 + profile := r.FormValue("profileRevision") + if profile != "" { + if profileRevision, res = strconv.ParseInt(profile, 10, 64); res != nil { + ErrResponse(w, http.StatusBadRequest, res) + return + } + } + var status string if err := ParseRequest(r, w, &status); err != nil { ErrResponse(w, http.StatusBadRequest, err) @@ -68,6 +104,10 @@ func SetCardStatus(w http.ResponseWriter, r *http.Request) { } } slot.Card.Status = status + slot.Card.NotifiedView = viewRevision + slot.Card.NotifiedContent = contentRevision + slot.Card.NotifiedLabel = labelRevision + slot.Card.NotifiedProfile = profileRevision // save and update contact revision err = store.DB.Transaction(func(tx *gorm.DB) error { diff --git a/net/server/internal/api_setOpenMessage.go b/net/server/internal/api_setOpenMessage.go index 34461afb..bc3b3ae3 100644 --- a/net/server/internal/api_setOpenMessage.go +++ b/net/server/internal/api_setOpenMessage.go @@ -69,6 +69,7 @@ func SetOpenMessage(w http.ResponseWriter, r *http.Request) { card.NotifiedProfile = connect.ProfileRevision card.NotifiedContent = connect.ContentRevision card.NotifiedView = connect.ViewRevision + card.NotifiedLabel = connect.LabelRevision card.OutToken = connect.Token card.InToken = hex.EncodeToString(data) card.AccountID = account.Guid @@ -140,6 +141,9 @@ func SetOpenMessage(w http.ResponseWriter, r *http.Request) { if connect.ViewRevision > card.NotifiedView { card.NotifiedView = connect.ViewRevision } + if connect.LabelRevision > card.NotifiedLabel { + card.NotifiedLabel = connect.LabelRevision + } if connect.ProfileRevision > card.NotifiedProfile { card.NotifiedProfile = connect.ProfileRevision } @@ -187,7 +191,15 @@ func SetOpenMessage(w http.ResponseWriter, r *http.Request) { status := &ContactStatus{ Token: slot.Card.InToken, Status: slot.Card.Status, + ViewRevision: slot.Card.ViewRevision + account.ViewRevision, + LabelRevision: account.LabelRevision, + ProfileRevision: account.ProfileRevision, + ContentRevision: account.ContentRevision, } + //SetContactProfileNotification(&account, slot.Card) + //SetContactContentNotification(&account, slot.Card) + //SetContactViewNotification(&account, slot.Card) + //SetContactLabelNotification(&account, slot.Card) SetStatus(&account) WriteResponse(w, &status) } diff --git a/net/server/internal/authUtil.go b/net/server/internal/authUtil.go index 5f1e204f..89945426 100644 --- a/net/server/internal/authUtil.go +++ b/net/server/internal/authUtil.go @@ -159,6 +159,9 @@ func BearerContactToken(r *http.Request, detail bool) (*store.Card, int, error) if card.Account.Disabled { return nil, http.StatusGone, errors.New("account is inactive") } + if card.Status != APP_CARDCONNECTING && card.Status != APP_CARDCONNECTED { + return nil, http.StatusUnauthorized, errors.New("invalid connection state") + } return &card, http.StatusOK, nil } diff --git a/net/server/internal/httpUtil.go b/net/server/internal/httpUtil.go index 700458f1..f3978842 100644 --- a/net/server/internal/httpUtil.go +++ b/net/server/internal/httpUtil.go @@ -20,6 +20,7 @@ func WriteResponse(w http.ResponseWriter, v interface{}) { log.Printf("%s:%d %s", strings.TrimPrefix(file, p), line, err.Error()) w.WriteHeader(http.StatusInternalServerError) } else { + w.Header().Set("Content-Type", "application/json") w.Write(body); } } diff --git a/net/server/internal/models.go b/net/server/internal/models.go index 9a287f34..581e18c4 100644 --- a/net/server/internal/models.go +++ b/net/server/internal/models.go @@ -261,6 +261,10 @@ type Tunnel struct { type ContactStatus struct { Token string `json:"token,omitempty"` Status string `json:"status"` + ViewRevision int64 `json:"viewRevision"` + LabelRevision int64 `json:"labelRevision"` + ContentRevision int64 `json:"contentRevision"` + ProfileRevision int64 `json:"profileRevision,omitempty"` } type DataMessage struct { @@ -293,6 +297,7 @@ type Connect struct { Contact string `json:"contact"` Token string `json:"token"` ViewRevision int64 `json:"viewRevision"` + LabelRevision int64 `json:"labelRevision"` ContentRevision int64 `json:"contentRevision"` ProfileRevision int64 `json:"profileRevision,omitempty"` Handle string `json:"handle,omitempty"` diff --git a/net/server/internal/notify.go b/net/server/internal/notify.go index c8f2f67a..84063e93 100644 --- a/net/server/internal/notify.go +++ b/net/server/internal/notify.go @@ -112,6 +112,29 @@ func SetProfileNotification(account *store.Account) { } } +// notify single card of profile revision +func SetContactProfileNotification(account *store.Account, card *store.Card) { + + if card.Status != APP_CARDCONNECTED { + return + } + + // add new notification for card + notification := &store.Notification{ + Node: card.Node, + Module: APP_NOTIFYPROFILE, + Token: card.OutToken, + Revision: account.ProfileRevision, + } + + if res := store.DB.Save(notification).Error; res != nil { + ErrMsg(res) + } else { + notify <- notification + } +} + + // notify all cards of content change // account.Content incremented by adding, updating, removing article & setting or clearning group or label from article func SetContentNotification(account *store.Account) { diff --git a/net/server/internal/store/schema.go b/net/server/internal/store/schema.go index 1df6a705..f0083799 100644 --- a/net/server/internal/store/schema.go +++ b/net/server/internal/store/schema.go @@ -175,14 +175,14 @@ type Card struct { Version string `gorm:"not null"` Node string `gorm:"not null"` ProfileRevision int64 `gorm:"not null"` - DetailRevision int64 `gorm:"not null"` + DetailRevision int64 `gorm:"not null;default:1"` Status string `gorm:"not null"` InToken string `gorm:"not null;index:cardguid,unique"` OutToken string Notes string Created int64 `gorm:"autoCreateTime"` Updated int64 `gorm:"autoUpdateTime"` - ViewRevision int64 `gorm:"not null"` + ViewRevision int64 `gorm:"not null;default:1"` NotifiedView int64 NotifiedContent int64 NotifiedLabel int64 diff --git a/net/server/internal/testUtil.go b/net/server/internal/testUtil.go index bced9970..36457ac5 100644 --- a/net/server/internal/testUtil.go +++ b/net/server/internal/testUtil.go @@ -3,6 +3,7 @@ package databag import ( "errors" "strings" + "strconv" "time" "net/url" "net/http" @@ -383,7 +384,11 @@ func OpenTestCard(account string, cardId string) (err error) { // update status if connected if contactStatus.Status == APP_CARDCONNECTED { - if r, w, err = NewRequest("PUT", "/contact/cards/{cardId}/status?token=" + contactStatus.Token, APP_CARDCONNECTED); err != nil { + view := "viewRevision=" + strconv.FormatInt(contactStatus.ViewRevision, 10) + content := "contentRevision=" + strconv.FormatInt(contactStatus.ContentRevision, 10) + label := "labelRevision=" + strconv.FormatInt(contactStatus.LabelRevision, 10) + profile := "profileRevision=" + strconv.FormatInt(contactStatus.ProfileRevision, 10) + if r, w, err = NewRequest("PUT", "/contact/cards/{cardId}/status?token=" + contactStatus.Token + "&" + view + "&" + content + "&" + label + "&" + profile, APP_CARDCONNECTED); err != nil { return } r = mux.SetURLVars(r, vars) diff --git a/net/server/internal/ucAddArticle_test.go b/net/server/internal/ucAddArticle_test.go index 11dacf25..83dfafb7 100644 --- a/net/server/internal/ucAddArticle_test.go +++ b/net/server/internal/ucAddArticle_test.go @@ -2,6 +2,7 @@ package databag import ( "testing" + "strconv" "github.com/stretchr/testify/assert" ) @@ -9,9 +10,11 @@ func TestAddArticle(t *testing.T) { var set *TestGroup var err error var rev *Revision + var ver *Revision var article Article var articles *[]Article var articleAccess *ArticleAccess + var cards []Card // setup testing group set, err = AddTestGroup("addarticle") @@ -28,6 +31,12 @@ func TestAddArticle(t *testing.T) { assert.NoError(t, SendEndpointTest(RemoveArticle, "DELETE", "/content/articls/" + article.ArticleId, &map[string]string{"articleId": article.ArticleId }, nil, APP_TOKENAPP, set.A.Token, nil)) + ver = GetTestRevision(set.B.Revisions) + assert.NoError(t, SendEndpointTest(GetCards, "GET", "/contact/cards?cardRevision=" + strconv.FormatInt(rev.Card, 10), nil, nil, APP_TOKENAPP, set.B.Token, &cards)) + assert.NotEqual(t, ver.Card, rev.Card) + assert.Equal(t, 1, len(cards)) + rev = ver + articles = &[]Article{} assert.NoError(t, SendEndpointTest(GetArticles, "GET", "/content/articles", nil, nil, APP_TOKENAPP, set.A.Token, articles)) assert.Equal(t, 2, len(*articles)) @@ -36,16 +45,26 @@ func TestAddArticle(t *testing.T) { articles = &[]Article{} assert.NoError(t, SendEndpointTest(GetArticles, "GET", "/content/articles", nil, nil, APP_TOKENCONTACT, set.B.A.Token, articles)) - assert.Equal(t, 2, len(*articles)) - assert.True(t, (*articles)[0].ArticleData != nil || (*articles)[1].ArticleData != nil) - assert.True(t, (*articles)[0].ArticleData == nil || (*articles)[1].ArticleData == nil) + assert.Equal(t, 1, len(*articles)) + assert.True(t, (*articles)[0].ArticleData != nil) articles = &[]Article{} assert.NoError(t, SendEndpointTest(GetArticles, "GET", "/content/articles", nil, nil, APP_TOKENCONTACT, set.C.A.Token, articles)) - assert.Equal(t, 2, len(*articles)) - assert.True(t, (*articles)[0].ArticleData == nil && (*articles)[1].ArticleData == nil) + assert.Equal(t, 0, len(*articles)) - // view article - assert.NotEqual(t, GetTestRevision(set.B.Revisions).Card, rev) + r, w, ret := NewRequest("GET", "/content/articles", nil) + assert.NoError(t, ret) + r.Header.Add("TokenType", APP_TOKENCONTACT) + SetBearerAuth(r, set.B.A.Token) + GetArticles(w, r) + resp := w.Result() + var view int64 + view, err = strconv.ParseInt(resp.Header["View-Revision"][0], 10, 64) + assert.NoError(t, err) + assert.Equal(t, view, cards[0].CardData.NotifiedView) + + articles = &[]Article{} + assert.NoError(t, SendEndpointTest(GetArticles, "GET", "/content/articles?contentRevision=0&viewRevision=" + strconv.FormatInt(cards[0].CardData.NotifiedView, 10), nil, nil, APP_TOKENCONTACT, set.B.A.Token, articles)) + assert.Equal(t, 2, len(*articles)) }