From b1e0a175060f983d901320f0280180b5cc7224a6 Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Tue, 1 Feb 2022 10:03:04 -0800 Subject: [PATCH] removing block concept from articla api --- doc/api.oa3 | 77 +-------------- net/server/internal/api_addArticle.go | 93 ++++++++---------- net/server/internal/api_addGroup.go | 38 ++++++-- net/server/internal/api_content.go | 6 +- net/server/internal/api_getArticleBlocks.go | 102 -------------------- net/server/internal/api_removeGroup.go | 20 +++- net/server/internal/modelUtil.go | 56 ++++++----- net/server/internal/models.go | 12 +-- net/server/internal/routers.go | 30 ++---- net/server/internal/store/schema.go | 25 +++-- net/server/internal/ucAddArticle_test.go | 13 +-- net/server/internal/ucGroupContact_test.go | 2 + 12 files changed, 156 insertions(+), 318 deletions(-) delete mode 100644 net/server/internal/api_getArticleBlocks.go diff --git a/doc/api.oa3 b/doc/api.oa3 index 7e4705b7..14d66062 100644 --- a/doc/api.oa3 +++ b/doc/api.oa3 @@ -1543,12 +1543,12 @@ paths: type: integer format: int64 - /content/articleBlocks: + /content/articles: get: tags: - content - description: Get article blocks that should be updated based on revisions. Acess granted to account token or contact token. When the request is made with a contact token the account view revision will be added to the block revision. - operationId: get-article-block-view + description: Get article slots that should be updated based on revisions. Access granted to account token or contact token. When the request is made with a contact token the account view revision will be added to the block revision. + operationId: get-articles security: - bearerAuth: [] parameters: @@ -1579,74 +1579,6 @@ paths: description: account disabled '500': description: internal server error - - /content/articleBlocks/{blockId}: - get: - tags: - - content - description: Get the articles within specified block. Access granted for app token or contact token. All of the articles are returned for the app token, but only the shared articles are returned for the contact token. An article is shared by assigning a common group to an article or assigning a label to an article that has assigned a common group. - operationId: get-articles - security: - - bearerAuth: [] - parameters: - - name: blockId - in: path - description: specified group id - required: true - schema: - type: string - responses: - '200': - description: successful operation - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Article' - '401': - description: permission denied - '404': - description: block not found - '410': - description: account disabled - '500': - description: internal server error - - /content/articleBlocks/{blockId}/view: - get: - tags: - - content - description: Get the article views within specified block. Access granted for app token or contact token. All of the articles are returned for the app token, but only the shared articles are returned for the contact token. An article is shared by assigning a common group to an article or assigning a label to an article that has assigned a common group. - operationId: get-article-views - security: - - bearerAuth: [] - parameters: - - name: blockId - in: path - description: specified group id - required: true - schema: - type: string - responses: - '200': - description: successful operation - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/ArticleView' - '401': - description: permission denied - '404': - description: block not found - '410': - description: account disabled - '500': - description: internal server error - - /content/articles: post: tags: - content @@ -1672,7 +1604,7 @@ paths: application/json: schema: $ref: '#/components/schemas/ArticleAccess' - + /content/articles/{articleId}: get: tags: @@ -4642,3 +4574,4 @@ components: scheme: bearer + diff --git a/net/server/internal/api_addArticle.go b/net/server/internal/api_addArticle.go index e34ec054..7a9d35b2 100644 --- a/net/server/internal/api_addArticle.go +++ b/net/server/internal/api_addArticle.go @@ -1,6 +1,7 @@ package databag import ( + "time" "errors" "net/http" "gorm.io/gorm" @@ -22,8 +23,8 @@ func AddArticle(w http.ResponseWriter, r *http.Request) { return } - var groups []store.Group - if err := store.DB.Where("group_id IN ?", articleAccess.Groups).Find(&groups).Error; err != nil { + var groups []store.Label + if err := store.DB.Raw("select labels.* from labels inner join label_groups on labels.id = label_groups.label_id inner join groups on label_groups.group_id = groups.id where groups.group_id in ?", articleAccess.Groups).Scan(&groups).Error; err != nil { ErrResponse(w, http.StatusInternalServerError, err) return } @@ -34,47 +35,43 @@ 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(), - ArticleBlockID: articleBlock.ID, - AccountID: account.ID, - Revision: 1, - Status: APP_ARTICLEUNCONFIRMED, - Expires: 0, - TagUpdated: 0, - TagRevision: 0, - Groups: groups, - Labels: labels, - ArticleBlock: articleBlock, - } + // save data and apply transaction + var article *store.Article err = store.DB.Transaction(func(tx *gorm.DB) error { - if res := tx.Save(article).Error; res != nil { - return res + + articleData := &store.ArticleData{ + DataRevision: 1, + Status: APP_ARTICLEUNCONFIRMED, + TagUpdated: time.Now().Unix(), + TagRevision: 1, + Labels: append(groups, labels...), + }; + if res := store.DB.Save(articleData).Error; res != nil { + return res; } - if res := tx.Model(&articleBlock).Update("revision", account.ContentRevision + 1).Error; res != nil { - return res + + if res := store.DB.Where("article_data_id is null AND account_id = ?", account.ID).First(&article).Error; res != nil { + if errors.Is(res, gorm.ErrRecordNotFound) { + article = &store.Article{ + ArticleId: uuid.New().String(), + AccountID: account.ID, + Revision: 1, + ArticleDataID: articleData.ID, + ArticleData: articleData, + } + if ret := store.DB.Save(article).Error; ret != nil { + return ret; + } + } else { + return res + } } - if res := tx.Model(&account).Update("content_revision", account.ContentRevision + 1).Error; res != nil { - return res + if ret := store.DB.Model(article).Update("article_data_id", articleData.ID).Error; ret != nil { + return ret; + } + if ret := store.DB.Preload("ArticleData.Labels.Groups").Where("id = ?", article.ID).First(article).Error; ret != nil { + return ret; } return nil }) @@ -83,23 +80,9 @@ func AddArticle(w http.ResponseWriter, r *http.Request) { return } - articleEntry := &ArticleEntry{ - BlockId: articleBlock.ArticleBlockId, - Article: getArticleModel(article, 0), - } - - SetStatus(account) SetContentNotification(account) - WriteResponse(w, articleEntry) + SetStatus(account) + WriteResponse(w, getArticleModel(article, 0)) } -func addArticleBlock(account *store.Account, articleBlock *store.ArticleBlock) (err error) { - articleBlock.ArticleBlockId = uuid.New().String() - articleBlock.AccountID = account.ID - articleBlock.Revision = account.ContentRevision - if err = store.DB.Save(articleBlock).Error; err != nil { - return - } - return -} diff --git a/net/server/internal/api_addGroup.go b/net/server/internal/api_addGroup.go index d16c0cf3..98319a08 100644 --- a/net/server/internal/api_addGroup.go +++ b/net/server/internal/api_addGroup.go @@ -21,17 +21,41 @@ func AddGroup(w http.ResponseWriter, r *http.Request) { return } - group := &store.Group{ - GroupId: uuid.New().String(), - AccountID: account.ID, - Revision: 0, - DataType: subject.DataType, - Data: subject.Data, - } + var group *store.Group err = store.DB.Transaction(func(tx *gorm.DB) error { + + label := &store.Label{ + LabelId: uuid.New().String(), + AccountID: account.ID, + Revision: 1, + Direct: true, + } + if res := tx.Save(label).Error; res != nil { + return res + } + + group = &store.Group{ + GroupId: uuid.New().String(), + AccountID: account.ID, + LabelID: label.ID, + Revision: 1, + DataType: subject.DataType, + Data: subject.Data, + } if res := tx.Save(group).Error; res != nil { return res } + + label.Groups = []store.Group{*group} + if res := tx.Save(label).Error; res != nil { + return res + } + +PrintMsg("ADDED") +PrintMsg(group.GroupId) +PrintMsg(label.LabelId) +PrintMsg("***") + if res := tx.Model(&account).Update("group_revision", account.GroupRevision + 1).Error; res != nil { return res } diff --git a/net/server/internal/api_content.go b/net/server/internal/api_content.go index 7c5ed99d..7aee985a 100644 --- a/net/server/internal/api_content.go +++ b/net/server/internal/api_content.go @@ -88,11 +88,6 @@ func GetArticleTags(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } -func GetArticleViews(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) -} - func GetArticles(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusOK) @@ -157,3 +152,4 @@ func UpdateLabel(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_getArticleBlocks.go b/net/server/internal/api_getArticleBlocks.go deleted file mode 100644 index a89ec42a..00000000 --- a/net/server/internal/api_getArticleBlocks.go +++ /dev/null @@ -1,102 +0,0 @@ -package databag - -import ( - "strings" - "strconv" - "errors" - "net/http" - "gorm.io/gorm" - "databag/internal/store" -) - -func GetArticleBlocks(w http.ResponseWriter, r *http.Request) { - var err error - - var contentRevision int64 - if contentRevision, err = getArticleBlockRevision(r.FormValue("contentRevision")); err != nil { - ErrMsg(err) - } - - // extract token - tokenType := r.Header.Get("TokenType") - auth := r.Header.Get("Authorization") - token := strings.TrimSpace(strings.TrimPrefix(auth, "Bearer")) - target, access, err := ParseToken(token) - if err != nil { - ErrResponse(w, http.StatusBadRequest, errors.New("invalid bearer token")) - return - } - - // retrieve updated blocks - var blocks []store.ArticleBlock - if tokenType == APP_TOKENAPP { - var app store.App - if err := store.DB.Preload("Account").Where("account_id = ? AND token = ?", target, access).First(&app).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - ErrResponse(w, http.StatusNotFound, err) - } else { - ErrResponse(w, http.StatusInternalServerError, err) - } - return - } - - // retrieve block ids - if err = getAccountArticleBlocks(&app.Account, contentRevision, &blocks); err != nil { - ErrResponse(w, http.StatusInternalServerError, err) - return - } - } else if tokenType == APP_TOKENCONTACT { - var card store.Card - if err := store.DB.Preload("Account").Where("account_id = ? AND InToken = ?", target, access).First(&card).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - ErrResponse(w, http.StatusNotFound, err) - } else { - ErrResponse(w, http.StatusInternalServerError, err) - } - return - } - - var viewRevision int64 - if viewRevision, err = getArticleBlockRevision(r.FormValue("viewRevision")); err != nil { - ErrMsg(err) - } - - // retrieve block ids - if err = getCardArticleBlocks(&card, viewRevision, contentRevision, &blocks); err != nil { - ErrResponse(w, http.StatusInternalServerError, err) - return - } - } else { - ErrResponse(w, http.StatusBadRequest, errors.New("invalid token type")) - } - - var ids []string - for _, block := range blocks { - ids = append(ids, block.ArticleBlockId) - } - WriteResponse(w, ids) -} - -func getArticleBlockRevision(param string) (rev int64, err error) { - if param == "" { - return - } - rev, err = strconv.ParseInt(param, 10, 64) - return -} - -func getAccountArticleBlocks(act *store.Account, content int64, blocks *[]store.ArticleBlock) error { - return store.DB.Where("revision > ? AND account_id = ?", content, act.ID).Find(blocks).Error -} - -func getCardArticleBlocks(card *store.Card, view int64, content int64, blocks *[]store.ArticleBlock) error { - if view != card.ViewRevision + card.Account.ViewRevision { - return store.DB.Where("revision > ? && account_id = ?", content, card.Account.ID).Find(blocks).Error - } else { - return store.DB.Where("account_id = ?", card.Account.ID).Find(blocks).Error - } -} - - - - diff --git a/net/server/internal/api_removeGroup.go b/net/server/internal/api_removeGroup.go index aeb7c562..25f804ef 100644 --- a/net/server/internal/api_removeGroup.go +++ b/net/server/internal/api_removeGroup.go @@ -1,6 +1,7 @@ package databag import ( + "errors" "net/http" "gorm.io/gorm" "github.com/gorilla/mux" @@ -17,10 +18,27 @@ func RemoveGroup(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) groupId := params["groupId"] + var group store.Group + if err := store.DB.Preload("Label").Where("account_id = ? AND group_id = ?", account.ID, groupId).First(&group).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + ErrResponse(w, http.StatusNotFound, err) + } else { + ErrResponse(w, http.StatusInternalServerError, err) + } + return + } + err = store.DB.Transaction(func(tx *gorm.DB) error { - if res := tx.Where("account_id = ? AND group_id = ?", account.ID, groupId).Delete(&store.Group{}).Error; res != nil { + if res := tx.Delete(&group.Label).Error; res != nil { return res } + if res := tx.Delete(&group).Error; res != nil { + return res + } + if res := tx.Model(&group.Label).Association("Groups").Delete(&group); res != nil { + return res + } + if res := tx.Model(&account).Updates(store.Account{ViewRevision: account.ViewRevision + 1, GroupRevision: account.GroupRevision + 1}).Error; res != nil { return res } diff --git a/net/server/internal/modelUtil.go b/net/server/internal/modelUtil.go index d508c694..61922506 100644 --- a/net/server/internal/modelUtil.go +++ b/net/server/internal/modelUtil.go @@ -51,32 +51,40 @@ func getGroupModel(group *store.Group) *Group { func getArticleModel(article *store.Article, tagCount int32) *Article { - // populate group id list - var groups []string; - for _, group := range article.Groups { - groups = append(groups, group.GroupId) - } + if article.ArticleData == nil { + return &Article{ + ArticleId: article.ArticleId, + Revision: article.Revision, + } + } else { - // populate label id list - var labels []string; - for _, label := range article.Labels { - labels = append(labels, label.LabelId) - } + // populate id list + var groups []string; + var labels []string; + for _, label := range article.ArticleData.Labels { + if label.Direct && len(label.Groups) > 0 { + groups = append(groups, label.Groups[0].GroupId) + } else { + labels = append(labels, label.LabelId) + } + } - return &Article{ - ArticleId: article.ArticleId, - ArticleBlockId: article.ArticleBlock.ArticleBlockId, - Revision: article.Revision, - DataType: article.DataType, - Data: article.Data, - Created: article.Created, - Updated: article.Updated, - Status: article.Status, - Labels: labels, - Groups: groups, - TagCount: tagCount, - TagUpdated: article.TagUpdated, - TagRevision: article.TagRevision, + return &Article{ + ArticleId: article.ArticleId, + Revision: article.Revision, + ArticleData: &ArticleData{ + DataType: article.ArticleData.DataType, + Data: article.ArticleData.Data, + Status: article.ArticleData.Status, + Labels: labels, + Groups: groups, + TagCount: tagCount, + Created: article.ArticleData.Created, + Updated: article.ArticleData.Updated, + TagUpdated: article.ArticleData.TagUpdated, + TagRevision: article.ArticleData.TagRevision, + }, + } } } diff --git a/net/server/internal/models.go b/net/server/internal/models.go index 8ca35908..484d5f43 100644 --- a/net/server/internal/models.go +++ b/net/server/internal/models.go @@ -43,15 +43,13 @@ type Subject struct { Data string `json:"data"` } -type ArticleEntry struct { - BlockId string `json:"blockId"` - Article *Article `json:"article"` +type Article struct { + ArticleId string `json:"article_id"` + Revision int64 `json:"revision"` + ArticleData *ArticleData `json:"articleData"` } -type Article struct { - ArticleId string `json:"articleId"` - ArticleBlockId string `json:"articleBlockId"` - Revision int64 `json:"revision"` +type ArticleData struct { DataType string `json:"type"` Data string `json:"data"` Created int64 `json:"created"` diff --git a/net/server/internal/routers.go b/net/server/internal/routers.go index 788a5992..d312cd6e 100644 --- a/net/server/internal/routers.go +++ b/net/server/internal/routers.go @@ -397,13 +397,6 @@ var routes = Routes{ SetOpenMessage, }, - Route{ - "SetViewRevision", - strings.ToUpper("Put"), - "/contact/view/revision", - SetViewRevision, - }, - Route{ "SetProfileRevision", strings.ToUpper("Put"), @@ -411,6 +404,13 @@ var routes = Routes{ SetProfileRevision, }, + Route{ + "SetViewRevision", + strings.ToUpper("Put"), + "/contact/view/revision", + SetViewRevision, + }, + Route{ "AddArticle", strings.ToUpper("Post"), @@ -481,13 +481,6 @@ var routes = Routes{ GetArticleAssets, }, - Route{ - "GetArticleBlocks", - strings.ToUpper("Get"), - "/content/articleBlocks", - GetArticleBlocks, - }, - Route{ "GetArticleSubjectField", strings.ToUpper("Get"), @@ -530,17 +523,10 @@ var routes = Routes{ GetArticleTags, }, - Route{ - "GetArticleViews", - strings.ToUpper("Get"), - "/content/articleBlocks/{blockId}/view", - GetArticleViews, - }, - Route{ "GetArticles", strings.ToUpper("Get"), - "/content/articleBlocks/{blockId}", + "/content/articles", GetArticles, }, diff --git a/net/server/internal/store/schema.go b/net/server/internal/store/schema.go index b04f2042..4db1569f 100644 --- a/net/server/internal/store/schema.go +++ b/net/server/internal/store/schema.go @@ -13,7 +13,7 @@ func AutoMigrate(db *gorm.DB) { db.AutoMigrate(&Card{}); db.AutoMigrate(&Asset{}); db.AutoMigrate(&Article{}); - db.AutoMigrate(&ArticleBlock{}); + db.AutoMigrate(&ArticleData{}); db.AutoMigrate(&ArticleAsset{}); db.AutoMigrate(&ArticleTag{}); db.AutoMigrate(&Dialogue{}); @@ -100,11 +100,13 @@ type Group struct { ID uint `gorm:"primaryKey;not null;unique;autoIncrement"` GroupId string `gorm:"not null;index:group,unqiue"` AccountID uint `gorm:"not null;index:group,unique"` + LabelID uint `gorm:"not null;index:direct"` Revision int64 `gorm:"not null"` DataType string `gorm:"index"` Data string Created int64 `gorm:"autoCreateTime"` Updated int64 `gorm:"autoUpdateTime"` + Label Label //reference to label for direct assignment to articles Account Account } @@ -117,6 +119,7 @@ type Label struct { Data string Created int64 `gorm:"autoCreateTime"` Updated int64 `gorm:"autoUpdateTime"` + Direct bool //special label indicating direct assignment of a group Groups []Group `gorm:"many2many:label_groups;"` Account Account } @@ -164,20 +167,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:articleblockid"` Revision int64 `gorm:"not null"` + ArticleDataID uint + ArticleData *ArticleData + Account Account +} + +type ArticleData struct { + ID uint `gorm:"primaryKey;not null;unique;autoIncrement"` + DataRevision int64 `gorm:"not null"` DataType string `gorm:"index"` Data string Status string `gorm:"not null;index"` @@ -186,10 +188,7 @@ type Article struct { Updated int64 `gorm:"autoUpdateTime"` TagUpdated int64 `gorm:"not null"` TagRevision int64 `gorm:"not null"` - Groups []Group `gorm:"many2many:article_groups;"` Labels []Label `gorm:"many2many:article_labels;"` - Account Account - ArticleBlock ArticleBlock } type ArticleAsset struct { diff --git a/net/server/internal/ucAddArticle_test.go b/net/server/internal/ucAddArticle_test.go index 46777f96..e89326f8 100644 --- a/net/server/internal/ucAddArticle_test.go +++ b/net/server/internal/ucAddArticle_test.go @@ -9,9 +9,7 @@ func TestAddArticle(t *testing.T) { var set *TestGroup var err error var rev *Revision - var articleEntry ArticleEntry - var contentRevision int64 - var ids []string + var article Article // setup testing group set, err = AddTestGroup("addarticle") @@ -19,20 +17,15 @@ func TestAddArticle(t *testing.T) { // initial revision rev = GetTestRevision(set.A.Revisions) - contentRevision = rev.Content // create article articleAccess := &ArticleAccess{ Groups: []string{set.A.B.GroupId} } - assert.NoError(t, SendEndpointTest(AddArticle, "POST", "/content/articles", nil, articleAccess, set.A.Token, &articleEntry)) - PrintMsg(articleEntry); + assert.NoError(t, SendEndpointTest(AddArticle, "POST", "/content/articles", nil, articleAccess, set.A.Token, &article)) + PrintMsg(article); // check revisions rev = GetTestRevision(set.A.Revisions) - assert.Greater(t, rev.Content, contentRevision) - // view article blocks - assert.NoError(t, SendEndpointTest(GetArticleBlocks, "GET", "/content/articleBlocks", nil, nil, set.A.Token, &ids)) - PrintMsg(ids) // view article diff --git a/net/server/internal/ucGroupContact_test.go b/net/server/internal/ucGroupContact_test.go index 07fae335..8b787e7f 100644 --- a/net/server/internal/ucGroupContact_test.go +++ b/net/server/internal/ucGroupContact_test.go @@ -138,6 +138,8 @@ func TestGroupContact(t *testing.T) { vars["groupId"] = group.GroupId r = mux.SetURLVars(r, vars) SetBearerAuth(r, a) +PrintMsg(group.GroupId) +PrintMsg("REMOVED") RemoveGroup(w, r) assert.NoError(t, ReadResponse(w, &group))