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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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