separating view and content notification

This commit is contained in:
Roland Osborne 2022-01-28 13:39:31 -08:00
parent e51197a3e5
commit b4b2c2c651
19 changed files with 267 additions and 57 deletions

View File

@ -1519,6 +1519,30 @@ paths:
type: integer
format: int64
/contact/view/revision:
put:
tags:
- contact
description: Set view revision for contact. This is intend to be invoked automatically anytime a contact updates their content or sharing. Access granted to contact tokens.
operationId: set-view-revision
security:
- bearerAuth: []
responses:
'200':
description: revision set
'401':
description: not authorized
'410':
description: account disabled
'500':
description: internal server error
requestBody:
content:
application/json:
schema:
type: integer
format: int64
/content/articleBlocks/view:
get:
tags:
@ -4121,8 +4145,9 @@ components:
- cardId
- profileRevision
- dataRevision
- remoteProfile
- remoteContent
- notifiedProfile
- notifiedContent
- notifiedView
properties:
cardId:
type: string
@ -4132,10 +4157,13 @@ components:
dataRevision:
type: integer
format: int64
remoteProfile:
notifiedProfile:
type: integer
format: int64
remoteContent:
notifiedContent:
type: integer
format: int64
notifiedView:
type: integer
format: int64
@ -4186,8 +4214,9 @@ components:
- cardId
- cardProfile
- cardData
- profileRevision
- contentRevision
- notifiedProfile
- notifiedContent
- notifiedView
properties:
cardId:
type: string
@ -4195,10 +4224,13 @@ components:
$ref: '#/components/schemas/CardProfile'
cardData:
$ref: '#/components/schemas/CardData'
profileRevision:
notifiedProfile:
type: integer
format: int64
contentRevision:
notifiedContent:
type: integer
format: int64
notifiedView:
type: integer
format: int64
@ -4484,12 +4516,14 @@ components:
required:
- contact
- token
- contentRevision
properties:
contact:
type: string
token:
type: string
viewRevision:
type: integer
format: int64
contentRevision:
type: integer
format: int64

View File

@ -1,6 +1,7 @@
package databag
import (
"errors"
"net/http"
"gorm.io/gorm"
"github.com/google/uuid"
@ -33,6 +34,25 @@ func AddArticle(w http.ResponseWriter, r *http.Request) {
return
}
var articleBlock store.ArticleBlock
if err := store.DB.Preload("Articles").Where("account_id = ?", account.ID).Last(&articleBlock).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
if err := addArticleBlock(account, &articleBlock); err != nil {
ErrResponse(w, http.StatusInternalServerError, err)
return
}
} else {
ErrResponse(w, http.StatusInternalServerError, err)
return
}
}
if len(articleBlock.Articles) >= APP_ARTICLEBLOCKSIZE {
if err := addArticleBlock(account, &articleBlock); err != nil {
ErrResponse(w, http.StatusInternalServerError, err)
return
}
}
article := &store.Article{
ArticleId: uuid.New().String(),
AccountID: account.ID,
@ -45,10 +65,13 @@ func AddArticle(w http.ResponseWriter, r *http.Request) {
Labels: labels,
}
err = store.DB.Transaction(func(tx *gorm.DB) error {
if res := store.DB.Save(article).Error; res != nil {
if res := tx.Save(article).Error; res != nil {
return res
}
if res := store.DB.Model(&account).Update("content_revision", account.ContentRevision + 1).Error; res != nil {
if res := tx.Model(&articleBlock).Update("revision", account.ContentRevision + 1).Error; res != nil {
return res
}
if res := tx.Model(&account).Update("content_revision", account.ContentRevision + 1).Error; res != nil {
return res
}
return nil
@ -58,7 +81,24 @@ func AddArticle(w http.ResponseWriter, r *http.Request) {
return
}
articleEntry := &ArticleEntry{
BlockId: articleBlock.ArticleBlockId,
BlockRevision: articleBlock.Revision,
Article: getArticleModel(article, 0),
}
SetStatus(account)
SetContentNotification(account)
WriteResponse(w, getArticleModel(article, 0))
WriteResponse(w, articleEntry)
}
func addArticleBlock(account *store.Account, articleBlock *store.ArticleBlock) error {
articleBlock.ArticleBlockId = uuid.New().String()
articleBlock.AccountID = account.ID
articleBlock.Revision = account.ContentRevision
if err := store.DB.Save(articleBlock).Error; err != nil {
return err
}
return nil
}

View File

@ -29,10 +29,10 @@ func AddGroup(w http.ResponseWriter, r *http.Request) {
Data: subject.Data,
}
err = store.DB.Transaction(func(tx *gorm.DB) error {
if res := store.DB.Save(group).Error; res != nil {
if res := tx.Save(group).Error; res != nil {
return res
}
if res := store.DB.Model(&account).Update("group_revision", account.GroupRevision + 1).Error; res != nil {
if res := tx.Model(&account).Update("group_revision", account.GroupRevision + 1).Error; res != nil {
return res
}
return nil

View File

@ -9,8 +9,9 @@ type cardView struct {
CardId string
ProfileRevision int64
DataRevision int64
RemoteProfile int64
RemoteContent int64
NotifiedProfile int64
NotifiedContent int64
NotifiedView int64
}
func GetCardView(w http.ResponseWriter, r *http.Request) {

View File

@ -31,8 +31,8 @@ func RemoveGroup(w http.ResponseWriter, r *http.Request) {
return
}
WriteResponse(w, nil)
SetStatus(account)
SetContentNotification(account)
SetViewNotification(account)
WriteResponse(w, nil)
}

View File

@ -70,9 +70,9 @@ func SetCardGroup(w http.ResponseWriter, r *http.Request) {
for _, group := range card.Groups {
cardData.Groups = append(cardData.Groups, group.GroupId)
}
WriteResponse(w, cardData)
SetContactContentNotification(account, &card)
SetContactViewNotification(account, &card)
SetStatus(account)
WriteResponse(w, cardData)
}

View File

@ -32,7 +32,7 @@ func NotifyContentRevision(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("remote_content", revision).Error; res != nil {
if res := tx.Model(card).Where("id = ?", card.ID).Update("notified_content", revision).Error; res != nil {
return res
}
if res := tx.Model(act).Where("id = ?", act.ID).Update("card_revision", act.CardRevision + 1).Error; res != nil {

View File

@ -61,8 +61,9 @@ func SetOpenMessage(w http.ResponseWriter, r *http.Request) {
card.Version = connect.Version
card.Node = connect.Node
card.ProfileRevision = connect.ProfileRevision
card.RemoteContent = connect.ContentRevision
card.RemoteProfile = connect.ProfileRevision
card.NotifiedProfile = connect.ProfileRevision
card.NotifiedContent = connect.ContentRevision
card.NotifiedView = connect.ViewRevision
card.Status = APP_CARDPENDING
card.DataRevision = 1
card.OutToken = connect.Token
@ -86,11 +87,14 @@ func SetOpenMessage(w http.ResponseWriter, r *http.Request) {
card.Node = connect.Node
card.ProfileRevision = connect.ProfileRevision
}
if connect.ContentRevision > card.RemoteContent {
card.RemoteContent = connect.ContentRevision
if connect.ContentRevision > card.NotifiedContent {
card.NotifiedContent = connect.ContentRevision
}
if connect.ProfileRevision > card.RemoteProfile {
card.RemoteProfile = connect.ProfileRevision
if connect.ViewRevision > card.NotifiedView {
card.NotifiedView = connect.ViewRevision
}
if connect.ProfileRevision > card.NotifiedProfile {
card.NotifiedProfile = connect.ProfileRevision
}
if card.Status == APP_CARDCONFIRMED {
card.Status = APP_CARDREQUESTED

View File

@ -30,10 +30,9 @@ func SetProfileRevision(w http.ResponseWriter, r *http.Request) {
func NotifyProfileRevision(card *store.Card, revision int64) error {
card.RemoteProfile = revision
act := &card.Account
err := store.DB.Transaction(func(tx *gorm.DB) error {
if res := tx.Model(card).Where("id = ?", card.ID).Update("remote_profile", revision).Error; res != nil {
if res := tx.Model(card).Where("id = ?", card.ID).Update("notified_profile", revision).Error; res != nil {
return res
}
if res := tx.Model(act).Where("id = ?", act.ID).Update("card_revision", act.CardRevision+1).Error; res != nil {

View File

@ -0,0 +1,49 @@
package databag
import (
"net/http"
"gorm.io/gorm"
"databag/internal/store"
)
func SetViewRevision(w http.ResponseWriter, r *http.Request) {
card, code, err := BearerContactToken(r)
if err != nil {
ErrResponse(w, code, err)
return
}
var revision int64
if err := ParseRequest(r, w, &revision); err != nil {
ErrResponse(w, http.StatusBadRequest, err)
return
}
if err := NotifyProfileRevision(card, revision); err != nil {
ErrResponse(w, http.StatusInternalServerError, err)
return
}
WriteResponse(w, nil)
}
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_view", revision).Error; res != nil {
return res
}
if res := tx.Model(act).Where("id = ?", act.ID).Update("card_revision", act.CardRevision + 1).Error; res != nil {
return res
}
return nil
})
if err != nil {
return err
}
SetStatus(act)
return nil
}

View File

@ -20,8 +20,9 @@ const APP_CARDCONFIRMED = "confirmed"
const APP_CARDREQUESTED = "requested"
const APP_CARDCONNECTING = "connecting"
const APP_CARDCONNECTED = "connected"
const APP_MODULEPROFILE = "profile"
const APP_MODULECONTENT = "content"
const APP_NOTIFYPROFILE = "profile"
const APP_NOTIFYCONTENT = "content"
const APP_NOTIFYVIEW = "view"
const APP_TOKENAPP = "app"
const APP_TOKENCONTACT = "contact"
const APP_NOTIFYBUFFER = 4096
@ -29,6 +30,7 @@ const APP_ARTICLEUNCONFIRMED = "unconfirmed"
const APP_ARTICLECONFIRMED = "confirmed"
const APP_ARTICLEINCOMPLETE = "incomplete"
const APP_ARTICLEERROR = "error"
const APP_ARTICLEBLOCKSIZE = 128
func AppCardStatus(status string) bool {
if status == APP_CARDPENDING {

View File

@ -14,8 +14,9 @@ func getCardModel(card *store.Card) *Card {
return &Card{
CardId: card.CardId,
ProfileRevision: card.RemoteProfile,
ContentRevision: card.RemoteContent,
NotifiedProfile: card.NotifiedProfile,
NotifiedContent: card.NotifiedContent,
NotifiedView: card.NotifiedView,
CardProfile: &CardProfile{
Guid: card.Guid,
Handle: card.Username,

View File

@ -88,8 +88,9 @@ type Card struct {
CardId string `json:"cardId"`
CardProfile *CardProfile `json:"cardProfile"`
CardData *CardData `json:"cardData"`
ProfileRevision int64 `json:"profileRevision"`
ContentRevision int64 `json:"contentRevision"`
NotifiedProfile int64 `json:"notifiedProfile"`
NotifiedContent int64 `json:"notifiedContent"`
NotifiedView int64 `json:"notifiedView"`
}
type CardData struct {
@ -116,8 +117,9 @@ type CardView struct {
CardId string `json:"cardId"`
ProfileRevision int64 `json:"profileRevision"`
DataRevision int64 `json:"dataRevision"`
RemoteProfile int64 `json:"remoteProfile"`
RemoteContent int64 `json:"remoteContent"`
NotifiedProfile int64 `json:"notifiedProfile"`
NotifiedContent int64 `json:"notifiedContent"`
NotifiedView int64 `json:"notifiedView"`
}
type ContentArticlesBody struct {
@ -329,6 +331,7 @@ type Identity struct {
type Connect struct {
Contact string `json:"contact"`
Token string `json:"token"`
ViewRevision int64 `json:"viewRevision"`
ContentRevision int64 `json:"contentRevision"`
ProfileRevision int64 `json:"profileRevision,omitempty"`
Handle string `json:"handle,omitempty"`

View File

@ -56,14 +56,18 @@ func SendLocalNotification(notification *store.Notification) {
return
}
if notification.Module == APP_MODULEPROFILE {
if notification.Module == APP_NOTIFYPROFILE {
if err := NotifyProfileRevision(&card, notification.Revision); err != nil {
ErrMsg(err)
}
} else if notification.Module == APP_MODULECONTENT {
} else if notification.Module == APP_NOTIFYCONTENT {
if err := NotifyContentRevision(&card, notification.Revision); err != nil {
ErrMsg(err)
}
} else if notification.Module == APP_NOTIFYVIEW {
if err := NotifyViewRevision(&card, notification.Revision); err != nil {
ErrMsg(err)
}
} else {
LogMsg("unknown notification type")
}
@ -88,7 +92,7 @@ func SetProfileNotification(account *store.Account) {
for _, card := range cards {
notification := &store.Notification{
Node: card.Node,
Module: APP_MODULEPROFILE,
Module: APP_NOTIFYPROFILE,
Token: card.OutToken,
Revision: account.ProfileRevision,
}
@ -106,7 +110,6 @@ func SetProfileNotification(account *store.Account) {
// notify all cards of content change
// account.Content incremented by adding, updating, removing article
// account.View incremented by removing a group or label or adding or removing a group with label
func SetContentNotification(account *store.Account) {
// select all connected cards
@ -121,9 +124,9 @@ func SetContentNotification(account *store.Account) {
for _, card := range cards {
notification := &store.Notification{
Node: card.Node,
Module: APP_MODULECONTENT,
Module: APP_NOTIFYCONTENT,
Token: card.OutToken,
Revision: account.ViewRevision + account.ContentRevision + card.ViewRevision,
Revision: account.ContentRevision,
}
if err := tx.Save(notification).Error; err != nil {
return err
@ -148,9 +151,65 @@ func SetContactContentNotification(account *store.Account, card *store.Card) {
// add new notification for card
notification := &store.Notification{
Node: card.Node,
Module: APP_MODULECONTENT,
Module: APP_NOTIFYCONTENT,
Token: card.OutToken,
Revision: account.ViewRevision + account.ContentRevision + card.ViewRevision,
Revision: account.ContentRevision,
}
if res := store.DB.Save(notification).Error; res != nil {
ErrMsg(res)
} else {
notify <- notification
}
}
// notify all cards of view change
// account.View incremented by removing a group or label or adding or removing a group with label
func SetViewNotification(account *store.Account) {
// select all connected cards
var cards []store.Card
if err := store.DB.Where("account_id = ? AND status = ?", account.Guid, APP_CARDCONNECTED).Find(&cards).Error; err != nil {
ErrMsg(err)
return
}
// add new notification for each card
err := store.DB.Transaction(func(tx *gorm.DB) error {
for _, card := range cards {
notification := &store.Notification{
Node: card.Node,
Module: APP_NOTIFYVIEW,
Token: card.OutToken,
Revision: account.ViewRevision + card.ViewRevision,
}
if err := tx.Save(notification).Error; err != nil {
return err
}
notify <- notification
}
return nil
})
if err != nil {
ErrMsg(err)
}
}
// notify single card of content change
// card.View incremented by adding or removing card from group or label
func SetContactViewNotification(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_NOTIFYVIEW,
Token: card.OutToken,
Revision: account.ViewRevision + card.ViewRevision,
}
if res := store.DB.Save(notification).Error; res != nil {

View File

@ -397,6 +397,13 @@ var routes = Routes{
SetOpenMessage,
},
Route{
"SetViewRevision",
strings.ToUpper("Put"),
"/contact/view/revision",
SetViewRevision,
},
Route{
"SetProfileRevision",
strings.ToUpper("Put"),

View File

@ -13,6 +13,7 @@ func AutoMigrate(db *gorm.DB) {
db.AutoMigrate(&Card{});
db.AutoMigrate(&Asset{});
db.AutoMigrate(&Article{});
db.AutoMigrate(&ArticleBlock{});
db.AutoMigrate(&ArticleAsset{});
db.AutoMigrate(&ArticleTag{});
db.AutoMigrate(&Dialogue{});
@ -141,8 +142,9 @@ type Card struct {
Created int64 `gorm:"autoCreateTime"`
Updated int64 `gorm:"autoUpdateTime"`
ViewRevision int64 `gorm:"not null"`
RemoteProfile int64
RemoteContent int64
NotifiedView int64
NotifiedContent int64
NotifiedProfile int64
Groups []Group `gorm:"many2many:card_groups;"`
Account Account `gorm:"references:Guid"`
}
@ -162,10 +164,19 @@ type Asset struct {
Account Account
}
type ArticleBlock struct {
ID uint `gorm:"primaryKey;not null;unique;autoIncrement"`
ArticleBlockId string `gorm:"not null;index:articleblock,unique"`
AccountID uint `gorm:"not null;index:articleblock,unique"`
Revision int64 `gorm:"not null"`
Articles []Article
}
type Article struct {
ID uint `gorm:"primaryKey;not null;unique;autoIncrement"`
ArticleId string `gorm:"not null;index:article,unique"`
AccountID uint `gorm:"not null;index:article,unique"`
ArticleBlockID uint `gorm:"not null;index:articleblock"`
Revision int64 `gorm:"not null"`
DataType string `gorm:"index"`
Data string

View File

@ -9,7 +9,7 @@ func TestAddArticle(t *testing.T) {
var set *TestGroup
var err error
var rev *Revision
var article Article
var articleEntry ArticleEntry
var contentRevision int64
// setup testing group
@ -22,7 +22,7 @@ func TestAddArticle(t *testing.T) {
// create article
articleAccess := &ArticleAccess{ Groups: []string{set.A.B.GroupId} }
assert.NoError(t, SendEndpointTest(AddArticle, nil, articleAccess, set.A.Token, &article))
assert.NoError(t, SendEndpointTest(AddArticle, nil, articleAccess, set.A.Token, &articleEntry))
// check revisions
rev = GetTestRevision(set.A.Revisions)

View File

@ -18,7 +18,7 @@ func TestGroupContact(t *testing.T) {
var cardData CardData
var contactRevision int64
var card Card
var contactCardRevision int64
var contactViewRevision int64
var wsA *websocket.Conn
var wsB *websocket.Conn
var err error
@ -62,7 +62,7 @@ func TestGroupContact(t *testing.T) {
SetBearerAuth(r, b)
GetCard(w, r)
assert.NoError(t, ReadResponse(w, &card))
contactCardRevision = card.ContentRevision
contactViewRevision = card.NotifiedView
// set contact group
r, w, _ = NewRequest("PUT", "/contact/cards/{cardId}/groups/{groupId}", nil)
@ -102,8 +102,8 @@ func TestGroupContact(t *testing.T) {
SetBearerAuth(r, b)
GetCard(w, r)
assert.NoError(t, ReadResponse(w, &card))
assert.NotEqual(t, contactCardRevision, card.ContentRevision)
contactCardRevision = card.ContentRevision
assert.NotEqual(t, contactViewRevision, card.NotifiedView)
contactViewRevision = card.NotifiedView
// show group view
r, w, _ = NewRequest("GET", "/share/groups", nil)
@ -166,7 +166,7 @@ func TestGroupContact(t *testing.T) {
SetBearerAuth(r, b)
GetCard(w, r)
assert.NoError(t, ReadResponse(w, &card))
assert.NotEqual(t, contactCardRevision, card.ContentRevision)
assert.NotEqual(t, contactViewRevision, card.NotifiedView)
// show group view
r, w, _ = NewRequest("GET", "/share/groups", nil)

View File

@ -26,7 +26,7 @@ func TestProfileNotification(t *testing.T) {
GetCardView(w, r)
assert.NoError(t, ReadResponse(w, &views))
assert.Equal(t, len(views), 1)
profileRevision := views[0].RemoteProfile
profileRevision := views[0].NotifiedProfile
// app connects websocket
ws, err = StatusConnection(a, &revision);
@ -55,5 +55,5 @@ func TestProfileNotification(t *testing.T) {
GetCardView(w, r)
assert.NoError(t, ReadResponse(w, &views))
assert.Equal(t, len(views), 1)
assert.NotEqual(t, profileRevision, views[0].RemoteProfile)
assert.NotEqual(t, profileRevision, views[0].NotifiedProfile)
}