diff --git a/net/server/internal/api_setCardStatus.go b/net/server/internal/api_setCardStatus.go index 52a8af97..bad4f355 100644 --- a/net/server/internal/api_setCardStatus.go +++ b/net/server/internal/api_setCardStatus.go @@ -48,19 +48,21 @@ func SetCardStatus(w http.ResponseWriter, r *http.Request) { } // update card - card.Status = status card.DataRevision += 1 if token != "" { card.OutToken = token } if status == APP_CARDCONNECTING { - data, err := securerandom.Bytes(32) - if err != nil { - ErrResponse(w, http.StatusInternalServerError, err) - return + if card.Status != APP_CARDCONNECTING && card.Status != APP_CARDCONNECTED { + data, err := securerandom.Bytes(32) + if err != nil { + ErrResponse(w, http.StatusInternalServerError, err) + return + } + card.InToken = hex.EncodeToString(data) } - card.InToken = hex.EncodeToString(data) } + card.Status = status // save and update contact revision err = store.DB.Transaction(func(tx *gorm.DB) error { diff --git a/net/server/internal/api_setContentRevision.go b/net/server/internal/api_setContentRevision.go index ae23f8ff..f5be23fb 100644 --- a/net/server/internal/api_setContentRevision.go +++ b/net/server/internal/api_setContentRevision.go @@ -2,13 +2,48 @@ package databag import ( "net/http" + "gorm.io/gorm" + "databag/internal/store" ) func SetContentRevision(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) + + 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 NotifyContentRevision(token string, revision int64) error { +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 { + 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 } + diff --git a/net/server/internal/api_setProfileRevision.go b/net/server/internal/api_setProfileRevision.go index 5c5f65be..de94c469 100644 --- a/net/server/internal/api_setProfileRevision.go +++ b/net/server/internal/api_setProfileRevision.go @@ -2,14 +2,49 @@ package databag import ( "net/http" + "gorm.io/gorm" + "databag/internal/store" ) func SetProfileRevision(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) + + 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 NotifyProfileRevision(token string, revision int64) error { +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 { + 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 } diff --git a/net/server/internal/authUtil.go b/net/server/internal/authUtil.go index da9eeed8..10c14508 100644 --- a/net/server/internal/authUtil.go +++ b/net/server/internal/authUtil.go @@ -114,6 +114,28 @@ func BearerAppToken(r *http.Request, detail bool) (*store.Account, int, error) { return &app.Account, http.StatusOK, nil } +func BearerContactToken(r *http.Request) (*store.Card, int, error) { + + // parse bearer authentication + auth := r.Header.Get("Authorization") + token := strings.TrimSpace(strings.TrimPrefix(auth, "Bearer")) + + // find token record + var card store.Card + if err := store.DB.Preload("Account").Where("InToken = ?", token).First(&card).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, http.StatusNotFound, err + } else { + return nil, http.StatusInternalServerError, err + } + } + if card.Account.Disabled { + return nil, http.StatusGone, errors.New("account is inactive") + } + + return &card, http.StatusOK, nil +} + func BasicCredentials(r *http.Request) (string, []byte, error) { var username string diff --git a/net/server/internal/notify.go b/net/server/internal/notify.go index 4e3c8af7..7c4f746c 100644 --- a/net/server/internal/notify.go +++ b/net/server/internal/notify.go @@ -1,6 +1,7 @@ package databag import ( + "errors" "gorm.io/gorm" "databag/internal/store" ) @@ -43,12 +44,24 @@ func SendNotifications() { } func SendLocalNotification(notification *store.Notification) { + + // pull reference account + var card store.Card + if err := store.DB.Preload("Account").Where("in_token = ?", notification.Token).First(&card).Error; err != nil { + ErrMsg(err) + return + } + if card.Account.Disabled { + ErrMsg(errors.New("account is inactive")) + return + } + if notification.Module == APP_MODULEPROFILE { - if err := NotifyProfileRevision(notification.Token, notification.Revision); err != nil { + if err := NotifyProfileRevision(&card, notification.Revision); err != nil { ErrMsg(err) } } else if notification.Module == APP_MODULECONTENT { - if err := NotifyContentRevision(notification.Token, notification.Revision); err != nil { + if err := NotifyContentRevision(&card, notification.Revision); err != nil { ErrMsg(err) } } else { diff --git a/net/server/internal/ucProfileNotification_test.go b/net/server/internal/ucProfileNotification_test.go index 52b04bdd..0ba19e4f 100644 --- a/net/server/internal/ucProfileNotification_test.go +++ b/net/server/internal/ucProfileNotification_test.go @@ -2,30 +2,65 @@ package databag import ( "testing" + "encoding/json" + "github.com/gorilla/websocket" + "github.com/stretchr/testify/assert" ) func TestProfileNotification(t *testing.T) { + var views []CardView + var revision Revision + var data []byte // start notifcation thread go SendNotifications() + // connect contacts access := AddTestContacts(t, "profilenotification", 2); - contact := ConnectTestContacts(t, access[0], access[1]) + ConnectTestContacts(t, access[0], access[1]) - PrintMsg(access) - PrintMsg(contact) + // get views list of cards + r, w, _ := NewRequest("GET", "/contact/cards/view", nil) + SetBearerAuth(r, access[0]) + GetCardView(w, r) + assert.NoError(t, ReadResponse(w, &views)) + assert.Equal(t, len(views), 1) + profileRevision := views[0].RemoteProfile - // connect revision websocket for A + // app connects websocket + ws := getTestWebsocket() + announce := Announce{ AppToken: access[0] } + data, _ = json.Marshal(&announce) + ws.WriteMessage(websocket.TextMessage, data) - // get profile revision of B + // receive revision + _, data, _ = ws.ReadMessage() + assert.NoError(t, json.Unmarshal(data, &revision)) + cardRevision := revision.Card - // update profile of B + // update B profile + profileData := ProfileData{ + Name: "Namer", + Location: "San Francisco", + Description: "databaggerr", + }; + r, w, _ = NewRequest("PUT", "/profile/data", &profileData) + SetBearerAuth(r, access[1]) + SetProfile(w, r) + assert.NoError(t, ReadResponse(w, nil)) - // read revision message + // receive revision + _, data, _ = ws.ReadMessage() + assert.NoError(t, json.Unmarshal(data, &revision)) + assert.NotEqual(t, cardRevision, revision.Card) - // check card increment - - // check B profile incremented + // get views list of cards + r, w, _ = NewRequest("GET", "/contact/cards/view", nil) + SetBearerAuth(r, access[0]) + GetCardView(w, r) + assert.NoError(t, ReadResponse(w, &views)) + assert.Equal(t, len(views), 1) + assert.NotEqual(t, profileRevision, views[0].RemoteProfile) // stop notification thread ExitNotifications()