removing block concept from articla api

This commit is contained in:
Roland Osborne 2022-02-01 10:03:04 -08:00
parent a713fd2174
commit b1e0a17506
12 changed files with 156 additions and 318 deletions

View File

@ -1543,12 +1543,12 @@ paths:
type: integer type: integer
format: int64 format: int64
/content/articleBlocks: /content/articles:
get: get:
tags: tags:
- content - 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. 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-article-block-view operationId: get-articles
security: security:
- bearerAuth: [] - bearerAuth: []
parameters: parameters:
@ -1579,74 +1579,6 @@ paths:
description: account disabled description: account disabled
'500': '500':
description: internal server error 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: post:
tags: tags:
- content - content
@ -4642,3 +4574,4 @@ components:
scheme: bearer scheme: bearer

View File

@ -1,6 +1,7 @@
package databag package databag
import ( import (
"time"
"errors" "errors"
"net/http" "net/http"
"gorm.io/gorm" "gorm.io/gorm"
@ -22,8 +23,8 @@ func AddArticle(w http.ResponseWriter, r *http.Request) {
return return
} }
var groups []store.Group var groups []store.Label
if err := store.DB.Where("group_id IN ?", articleAccess.Groups).Find(&groups).Error; err != nil { 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) ErrResponse(w, http.StatusInternalServerError, err)
return return
} }
@ -34,47 +35,43 @@ func AddArticle(w http.ResponseWriter, r *http.Request) {
return 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{ // save data and apply transaction
ArticleId: uuid.New().String(), var article *store.Article
ArticleBlockID: articleBlock.ID,
AccountID: account.ID,
Revision: 1,
Status: APP_ARTICLEUNCONFIRMED,
Expires: 0,
TagUpdated: 0,
TagRevision: 0,
Groups: groups,
Labels: labels,
ArticleBlock: articleBlock,
}
err = store.DB.Transaction(func(tx *gorm.DB) error { 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 { if ret := store.DB.Model(article).Update("article_data_id", articleData.ID).Error; ret != nil {
return res return ret;
}
if ret := store.DB.Preload("ArticleData.Labels.Groups").Where("id = ?", article.ID).First(article).Error; ret != nil {
return ret;
} }
return nil return nil
}) })
@ -83,23 +80,9 @@ func AddArticle(w http.ResponseWriter, r *http.Request) {
return return
} }
articleEntry := &ArticleEntry{
BlockId: articleBlock.ArticleBlockId,
Article: getArticleModel(article, 0),
}
SetStatus(account)
SetContentNotification(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
}

View File

@ -21,17 +21,41 @@ func AddGroup(w http.ResponseWriter, r *http.Request) {
return return
} }
group := &store.Group{ var group *store.Group
GroupId: uuid.New().String(),
AccountID: account.ID,
Revision: 0,
DataType: subject.DataType,
Data: subject.Data,
}
err = store.DB.Transaction(func(tx *gorm.DB) error { 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 { if res := tx.Save(group).Error; res != nil {
return res 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 { if res := tx.Model(&account).Update("group_revision", account.GroupRevision + 1).Error; res != nil {
return res return res
} }

View File

@ -88,11 +88,6 @@ func GetArticleTags(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) 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) { func GetArticles(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK) 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.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }

View File

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

View File

@ -1,6 +1,7 @@
package databag package databag
import ( import (
"errors"
"net/http" "net/http"
"gorm.io/gorm" "gorm.io/gorm"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -17,10 +18,27 @@ func RemoveGroup(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r) params := mux.Vars(r)
groupId := params["groupId"] 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 { 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 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 { if res := tx.Model(&account).Updates(store.Account{ViewRevision: account.ViewRevision + 1, GroupRevision: account.GroupRevision + 1}).Error; res != nil {
return res return res
} }

View File

@ -51,32 +51,40 @@ func getGroupModel(group *store.Group) *Group {
func getArticleModel(article *store.Article, tagCount int32) *Article { func getArticleModel(article *store.Article, tagCount int32) *Article {
// populate group id list if article.ArticleData == nil {
var groups []string; return &Article{
for _, group := range article.Groups { ArticleId: article.ArticleId,
groups = append(groups, group.GroupId) Revision: article.Revision,
} }
} else {
// populate label id list // populate id list
var labels []string; var groups []string;
for _, label := range article.Labels { var labels []string;
labels = append(labels, label.LabelId) 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{ return &Article{
ArticleId: article.ArticleId, ArticleId: article.ArticleId,
ArticleBlockId: article.ArticleBlock.ArticleBlockId, Revision: article.Revision,
Revision: article.Revision, ArticleData: &ArticleData{
DataType: article.DataType, DataType: article.ArticleData.DataType,
Data: article.Data, Data: article.ArticleData.Data,
Created: article.Created, Status: article.ArticleData.Status,
Updated: article.Updated, Labels: labels,
Status: article.Status, Groups: groups,
Labels: labels, TagCount: tagCount,
Groups: groups, Created: article.ArticleData.Created,
TagCount: tagCount, Updated: article.ArticleData.Updated,
TagUpdated: article.TagUpdated, TagUpdated: article.ArticleData.TagUpdated,
TagRevision: article.TagRevision, TagRevision: article.ArticleData.TagRevision,
},
}
} }
} }

View File

@ -43,15 +43,13 @@ type Subject struct {
Data string `json:"data"` Data string `json:"data"`
} }
type ArticleEntry struct { type Article struct {
BlockId string `json:"blockId"` ArticleId string `json:"article_id"`
Article *Article `json:"article"` Revision int64 `json:"revision"`
ArticleData *ArticleData `json:"articleData"`
} }
type Article struct { type ArticleData struct {
ArticleId string `json:"articleId"`
ArticleBlockId string `json:"articleBlockId"`
Revision int64 `json:"revision"`
DataType string `json:"type"` DataType string `json:"type"`
Data string `json:"data"` Data string `json:"data"`
Created int64 `json:"created"` Created int64 `json:"created"`

View File

@ -397,13 +397,6 @@ var routes = Routes{
SetOpenMessage, SetOpenMessage,
}, },
Route{
"SetViewRevision",
strings.ToUpper("Put"),
"/contact/view/revision",
SetViewRevision,
},
Route{ Route{
"SetProfileRevision", "SetProfileRevision",
strings.ToUpper("Put"), strings.ToUpper("Put"),
@ -411,6 +404,13 @@ var routes = Routes{
SetProfileRevision, SetProfileRevision,
}, },
Route{
"SetViewRevision",
strings.ToUpper("Put"),
"/contact/view/revision",
SetViewRevision,
},
Route{ Route{
"AddArticle", "AddArticle",
strings.ToUpper("Post"), strings.ToUpper("Post"),
@ -481,13 +481,6 @@ var routes = Routes{
GetArticleAssets, GetArticleAssets,
}, },
Route{
"GetArticleBlocks",
strings.ToUpper("Get"),
"/content/articleBlocks",
GetArticleBlocks,
},
Route{ Route{
"GetArticleSubjectField", "GetArticleSubjectField",
strings.ToUpper("Get"), strings.ToUpper("Get"),
@ -530,17 +523,10 @@ var routes = Routes{
GetArticleTags, GetArticleTags,
}, },
Route{
"GetArticleViews",
strings.ToUpper("Get"),
"/content/articleBlocks/{blockId}/view",
GetArticleViews,
},
Route{ Route{
"GetArticles", "GetArticles",
strings.ToUpper("Get"), strings.ToUpper("Get"),
"/content/articleBlocks/{blockId}", "/content/articles",
GetArticles, GetArticles,
}, },

View File

@ -13,7 +13,7 @@ func AutoMigrate(db *gorm.DB) {
db.AutoMigrate(&Card{}); db.AutoMigrate(&Card{});
db.AutoMigrate(&Asset{}); db.AutoMigrate(&Asset{});
db.AutoMigrate(&Article{}); db.AutoMigrate(&Article{});
db.AutoMigrate(&ArticleBlock{}); db.AutoMigrate(&ArticleData{});
db.AutoMigrate(&ArticleAsset{}); db.AutoMigrate(&ArticleAsset{});
db.AutoMigrate(&ArticleTag{}); db.AutoMigrate(&ArticleTag{});
db.AutoMigrate(&Dialogue{}); db.AutoMigrate(&Dialogue{});
@ -100,11 +100,13 @@ type Group struct {
ID uint `gorm:"primaryKey;not null;unique;autoIncrement"` ID uint `gorm:"primaryKey;not null;unique;autoIncrement"`
GroupId string `gorm:"not null;index:group,unqiue"` GroupId string `gorm:"not null;index:group,unqiue"`
AccountID uint `gorm:"not null;index:group,unique"` AccountID uint `gorm:"not null;index:group,unique"`
LabelID uint `gorm:"not null;index:direct"`
Revision int64 `gorm:"not null"` Revision int64 `gorm:"not null"`
DataType string `gorm:"index"` DataType string `gorm:"index"`
Data string Data string
Created int64 `gorm:"autoCreateTime"` Created int64 `gorm:"autoCreateTime"`
Updated int64 `gorm:"autoUpdateTime"` Updated int64 `gorm:"autoUpdateTime"`
Label Label //reference to label for direct assignment to articles
Account Account Account Account
} }
@ -117,6 +119,7 @@ type Label struct {
Data string Data string
Created int64 `gorm:"autoCreateTime"` Created int64 `gorm:"autoCreateTime"`
Updated int64 `gorm:"autoUpdateTime"` Updated int64 `gorm:"autoUpdateTime"`
Direct bool //special label indicating direct assignment of a group
Groups []Group `gorm:"many2many:label_groups;"` Groups []Group `gorm:"many2many:label_groups;"`
Account Account Account Account
} }
@ -164,20 +167,19 @@ type Asset struct {
Account Account 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 { type Article struct {
ID uint `gorm:"primaryKey;not null;unique;autoIncrement"` ID uint `gorm:"primaryKey;not null;unique;autoIncrement"`
ArticleId string `gorm:"not null;index:article,unique"` ArticleId string `gorm:"not null;index:article,unique"`
AccountID uint `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"` 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"` DataType string `gorm:"index"`
Data string Data string
Status string `gorm:"not null;index"` Status string `gorm:"not null;index"`
@ -186,10 +188,7 @@ type Article struct {
Updated int64 `gorm:"autoUpdateTime"` Updated int64 `gorm:"autoUpdateTime"`
TagUpdated int64 `gorm:"not null"` TagUpdated int64 `gorm:"not null"`
TagRevision int64 `gorm:"not null"` TagRevision int64 `gorm:"not null"`
Groups []Group `gorm:"many2many:article_groups;"`
Labels []Label `gorm:"many2many:article_labels;"` Labels []Label `gorm:"many2many:article_labels;"`
Account Account
ArticleBlock ArticleBlock
} }
type ArticleAsset struct { type ArticleAsset struct {

View File

@ -9,9 +9,7 @@ func TestAddArticle(t *testing.T) {
var set *TestGroup var set *TestGroup
var err error var err error
var rev *Revision var rev *Revision
var articleEntry ArticleEntry var article Article
var contentRevision int64
var ids []string
// setup testing group // setup testing group
set, err = AddTestGroup("addarticle") set, err = AddTestGroup("addarticle")
@ -19,20 +17,15 @@ func TestAddArticle(t *testing.T) {
// initial revision // initial revision
rev = GetTestRevision(set.A.Revisions) rev = GetTestRevision(set.A.Revisions)
contentRevision = rev.Content
// create article // create article
articleAccess := &ArticleAccess{ Groups: []string{set.A.B.GroupId} } articleAccess := &ArticleAccess{ Groups: []string{set.A.B.GroupId} }
assert.NoError(t, SendEndpointTest(AddArticle, "POST", "/content/articles", nil, articleAccess, set.A.Token, &articleEntry)) assert.NoError(t, SendEndpointTest(AddArticle, "POST", "/content/articles", nil, articleAccess, set.A.Token, &article))
PrintMsg(articleEntry); PrintMsg(article);
// check revisions // check revisions
rev = GetTestRevision(set.A.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 // view article

View File

@ -138,6 +138,8 @@ func TestGroupContact(t *testing.T) {
vars["groupId"] = group.GroupId vars["groupId"] = group.GroupId
r = mux.SetURLVars(r, vars) r = mux.SetURLVars(r, vars)
SetBearerAuth(r, a) SetBearerAuth(r, a)
PrintMsg(group.GroupId)
PrintMsg("REMOVED")
RemoveGroup(w, r) RemoveGroup(w, r)
assert.NoError(t, ReadResponse(w, &group)) assert.NoError(t, ReadResponse(w, &group))