update notified revisions on card status update

This commit is contained in:
Roland Osborne 2022-02-04 00:20:57 -08:00
parent 6dfab23234
commit 7f625cd9bc
12 changed files with 160 additions and 42 deletions

View File

@ -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 {

View File

@ -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"))

View File

@ -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,

View File

@ -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 {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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);
}
}

View File

@ -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"`

View File

@ -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) {

View File

@ -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

View File

@ -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)

View File

@ -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))
}