testing get channel topics

This commit is contained in:
Roland Osborne 2022-02-18 12:21:15 -08:00
parent 898ad9f4ac
commit 73bedbccdc
10 changed files with 349 additions and 142 deletions

View File

@ -1894,6 +1894,35 @@ paths:
$ref: '#/components/schemas/Subject'
/content/channels/{channelId}:
get:
tags:
- content
description: Get details of channel.
operationId: get-channel
security:
- bearerAuth: []
parameters:
- name: channelId
in: path
description: specified channel id
required: true
schema:
type: string
responses:
'200':
description: success
content:
application/json:
schema:
$ref: '#/components/schemas/Channel'
'401':
description: invalid password
'404':
description: channel not found
'410':
description: account disabled
'500':
description: internal server error
delete:
tags:
- content
@ -1920,37 +1949,6 @@ paths:
'500':
description: internal server error
/content/channels/{channelId}/detail:
get:
tags:
- content
description: Get detail object of channel.
operationId: get-channel-detail
security:
- bearerAuth: []
parameters:
- name: channelId
in: path
description: specified channel id
required: true
schema:
type: string
responses:
'200':
description: success
content:
application/json:
schema:
$ref: '#/components/schemas/ChannelDetail'
'401':
description: invalid password
'404':
description: channel not found
'410':
description: account disabled
'500':
description: internal server error
/content/channels/{channelId}/subject/{field}:
get:
tags:
@ -2247,6 +2245,41 @@ paths:
$ref: '#/components/schemas/Subject'
/content/channels/{channelId}/topics/{topicId}:
get:
tags:
- content
description: Get full object of topic.
operationId: get-channel-topic
security:
- bearerAuth: []
parameters:
- name: channelId
in: path
description: specified channel id
required: true
schema:
type: string
- name: topicId
in: path
description: specified topic id
required: true
schema:
type: string
responses:
'200':
description: success
content:
application/json:
schema:
$ref: '#/components/schemas/Topic'
'401':
description: invalid password
'404':
description: channel not found
'410':
description: account disabled
'500':
description: internal server error
delete:
tags:
- content
@ -2303,6 +2336,10 @@ paths:
responses:
'200':
description: success
content:
application/json:
schema:
$ref: '#/components/schemas/TopicDetail'
'401':
description: invalid password
'404':
@ -2312,12 +2349,12 @@ paths:
'500':
description: internal server error
/content/channels/{channelId}/topics/{topicId}/size:
/content/channels/{channelId}/topics/{topicId}/count:
get:
tags:
- content
description: Get detail object of topic.
operationId: get-channel-topic-size
description: Get count of associated taghs.
operationId: get-channel-topic-count
security:
- bearerAuth: []
parameters:
@ -2336,6 +2373,10 @@ paths:
responses:
'200':
description: success
content:
application/json:
schema:
$ref: '#/components/schemas/TagCount'
'401':
description: invalid password
'404':
@ -3236,7 +3277,7 @@ components:
topicDetail:
$ref: '#/components/schemas/TopicDetail'
topicTags::
$ref: '#/components/schemas/TopicTags'
$ref: '#/components/schemas/TagCount'
TopicDetail:
type: object
@ -3264,16 +3305,16 @@ components:
type: string
enum: [ unconfirmed, confirmed, complete, error ]
TopicTags:
TagCount:
type: object
required:
- tagCount
- tagUpdated
- count
- updated
properties:
tagCount:
count:
type: integer
format: int32
tagUpdated:
updated:
type: integer
format: int64

View File

@ -1,81 +1,34 @@
package databag
import (
"errors"
"net/http"
"gorm.io/gorm"
"github.com/gorilla/mux"
"github.com/google/uuid"
"databag/internal/store"
)
func AddChannelTopic(w http.ResponseWriter, r *http.Request) {
// scan parameters
params := mux.Vars(r)
channelId := params["channelId"]
var subject Subject
if err := ParseRequest(r, w, &subject); err != nil {
ErrResponse(w, http.StatusBadRequest, err)
return
}
var guid string
var act *store.Account
tokenType := r.Header.Get("TokenType")
if tokenType == APP_TOKENAPP {
account, code, err := BearerAppToken(r, false);
channelSlot, guid, err, code := getChannelSlot(r, false)
if err != nil {
ErrResponse(w, code, err)
return
}
act = account
guid = account.Guid
} else if tokenType == APP_TOKENCONTACT {
card, code, err := BearerContactToken(r, true)
if err != nil {
ErrResponse(w, code, err)
return
}
act = &card.Account
guid = card.Guid
} else {
ErrResponse(w, http.StatusBadRequest, errors.New("unknown token type"))
return
}
// load channel
var channelSlot store.ChannelSlot
if err := store.DB.Preload("Channel.Cards").Preload("Channel.Groups.Cards").Where("account_id = ? AND channel_slot_id = ?", act.ID, channelId).First(&channelSlot).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
ErrResponse(w, http.StatusNotFound, err)
} else {
ErrResponse(w, http.StatusInternalServerError, err)
}
return
}
if channelSlot.Channel == nil {
ErrResponse(w, http.StatusNotFound, errors.New("referenced empty channel"))
return
}
// check if a member
if tokenType == APP_TOKENCONTACT {
if !isMember(guid, channelSlot.Channel.Cards) {
ErrResponse(w, http.StatusUnauthorized, errors.New("not a member of channel"))
return
}
}
act := &channelSlot.Account
topicSlot := &store.TopicSlot{}
err := store.DB.Transaction(func(tx *gorm.DB) error {
err = store.DB.Transaction(func(tx *gorm.DB) error {
// add new record
topic := &store.Topic{}
topic.Data = subject.Data
topic.DataType = subject.DataType
topic.Channel = channelSlot.Channel
topic.TagCount = 0
topic.Guid = guid
topic.DetailRevision = act.ChannelRevision + 1
@ -86,6 +39,7 @@ func AddChannelTopic(w http.ResponseWriter, r *http.Request) {
}
topicSlot.TopicSlotId = uuid.New().String()
topicSlot.AccountID = act.ID
topicSlot.ChannelID = channelSlot.Channel.ID
topicSlot.TopicID = topic.ID
topicSlot.Revision = act.ChannelRevision + 1
topicSlot.Topic = topic
@ -97,7 +51,7 @@ func AddChannelTopic(w http.ResponseWriter, r *http.Request) {
if res := tx.Model(&channelSlot).Update("revision", act.ChannelRevision + 1).Error; res != nil {
return res
}
if res := tx.Model(&act).Update("channel_revision", act.ChannelRevision + 1).Error; res != nil {
if res := tx.Model(act).Update("channel_revision", act.ChannelRevision + 1).Error; res != nil {
return res
}
return nil
@ -122,15 +76,7 @@ func AddChannelTopic(w http.ResponseWriter, r *http.Request) {
for _, card := range cards {
SetContactChannelNotification(act, &card)
}
WriteResponse(w, getTopicModel(topicSlot, true, true))
WriteResponse(w, getTopicModel(topicSlot))
}
func isMember(guid string, cards []store.Card) bool {
for _, card := range cards {
if guid == card.Guid {
return true
}
}
return false
}

View File

@ -33,21 +33,6 @@ func GetChannelAssets(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func GetChannelSize(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
}
func GetChannelTopicDetail(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
}
func GetChannelTopicSize(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
}
func GetChannelTopicSubjectField(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)

View File

@ -0,0 +1,122 @@
package databag
import (
"errors"
"net/http"
"gorm.io/gorm"
"github.com/gorilla/mux"
"databag/internal/store"
)
func GetChannelTopic(w http.ResponseWriter, r *http.Request) {
// scan parameters
params := mux.Vars(r)
topicId := params["topicId"]
var subject Subject
if err := ParseRequest(r, w, &subject); err != nil {
ErrResponse(w, http.StatusBadRequest, err)
return
}
channelSlot, _, err, code := getChannelSlot(r, false)
if err != nil {
ErrResponse(w, code, err)
return
}
// load topic
var topicSlot store.TopicSlot
if err = store.DB.Where("channel_id = ? AND topic_slot_id = ?", channelSlot.Channel.ID, topicId).First(&topicSlot).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
code = http.StatusNotFound
} else {
code = http.StatusInternalServerError
}
return
}
WriteResponse(w, getTopicModel(&topicSlot))
}
func isMember(guid string, cards []store.Card) bool {
for _, card := range cards {
if guid == card.Guid {
return true
}
}
return false
}
func isViewer(guid string, groups []store.Group) bool {
for _, group := range groups {
for _, card := range group.Cards {
if guid == card.Guid {
return true
}
}
}
return false
}
func getChannelSlot(r *http.Request, member bool) (slot store.ChannelSlot, guid string, err error, code int) {
// scan parameters
params := mux.Vars(r)
channelId := params["channelId"]
// validate contact access
var account *store.Account
tokenType := r.Header.Get("TokenType")
if tokenType == APP_TOKENAPP {
account, code, err = BearerAppToken(r, false);
if err != nil {
return
}
guid = account.Guid
} else if tokenType == APP_TOKENCONTACT {
var card *store.Card
card, code, err = BearerContactToken(r, true)
if err != nil {
return
}
account = &card.Account
guid = card.Guid
} else {
err = errors.New("unknown token type")
code = http.StatusBadRequest
return
}
// load channel
if err = store.DB.Preload("Account").Preload("Channel.Cards").Preload("Channel.Groups.Cards").Where("account_id = ? AND channel_slot_id = ?", account.ID, channelId).First(&slot).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
code = http.StatusNotFound
} else {
code = http.StatusInternalServerError
}
return
}
if slot.Channel == nil {
err = errors.New("referenced empty channel")
code = http.StatusNotFound
return
}
// validate access to channel
if tokenType == APP_TOKENCONTACT {
if member && !isMember(guid, slot.Channel.Cards) {
err = errors.New("contact is not a channel member")
code = http.StatusUnauthorized
return
} else if !isViewer(guid, slot.Channel.Groups) && !isMember(guid, slot.Channel.Cards) {
err = errors.New("contact is not a channel viewer")
code = http.StatusUnauthorized
return
}
}
return
}

View File

@ -0,0 +1,42 @@
package databag
import (
"errors"
"net/http"
"gorm.io/gorm"
"github.com/gorilla/mux"
"databag/internal/store"
)
func GetChannelTopicCount(w http.ResponseWriter, r *http.Request) {
// scan parameters
params := mux.Vars(r)
topicId := params["topicId"]
var subject Subject
if err := ParseRequest(r, w, &subject); err != nil {
ErrResponse(w, http.StatusBadRequest, err)
return
}
channelSlot, _, err, code := getChannelSlot(r, false)
if err != nil {
ErrResponse(w, code, err)
return
}
// load topic
var topicSlot store.TopicSlot
if err = store.DB.Where("channel_id = ? AND topic_slot_id = ?", channelSlot.Channel.ID, topicId).First(&topicSlot).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
code = http.StatusNotFound
} else {
code = http.StatusInternalServerError
}
return
}
WriteResponse(w, getTopicCountModel(&topicSlot))
}

View File

@ -0,0 +1,42 @@
package databag
import (
"errors"
"net/http"
"gorm.io/gorm"
"github.com/gorilla/mux"
"databag/internal/store"
)
func GetChannelTopicDetail(w http.ResponseWriter, r *http.Request) {
// scan parameters
params := mux.Vars(r)
topicId := params["topicId"]
var subject Subject
if err := ParseRequest(r, w, &subject); err != nil {
ErrResponse(w, http.StatusBadRequest, err)
return
}
channelSlot, _, err, code := getChannelSlot(r, false)
if err != nil {
ErrResponse(w, code, err)
return
}
// load topic
var topicSlot store.TopicSlot
if err = store.DB.Where("channel_id = ? AND topic_slot_id = ?", channelSlot.Channel.ID, topicId).First(&topicSlot).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
code = http.StatusNotFound
} else {
code = http.StatusInternalServerError
}
return
}
WriteResponse(w, getTopicDetailModel(&topicSlot))
}

View File

@ -213,8 +213,6 @@ func getChannelModel(slot *store.ChannelSlot, showData bool, showList bool) *Cha
}
}
func getTopicRevisionModel(slot *store.TopicSlot, showData bool) *Topic {
if !showData || slot.Topic == nil {
@ -234,9 +232,37 @@ func getTopicRevisionModel(slot *store.TopicSlot, showData bool) *Topic {
}
}
func getTopicModel(slot *store.TopicSlot, showData bool, showList bool) *Topic {
func getTopicDetailModel(slot *store.TopicSlot) *TopicDetail {
if !showData || slot.Topic == nil {
if slot.Topic == nil {
return nil
}
return &TopicDetail{
Guid: slot.Topic.Guid,
DataType: slot.Topic.DataType,
Data: slot.Topic.Data,
Created: slot.Topic.Created,
Updated: slot.Topic.Updated,
Status: slot.Topic.Status,
}
}
func getTopicCountModel(slot *store.TopicSlot) *TagCount {
if slot.Topic == nil {
return nil
}
return &TagCount{
Count: slot.Topic.TagCount,
Updated: slot.Topic.TagUpdated,
}
}
func getTopicModel(slot *store.TopicSlot) *Topic {
if slot.Topic == nil {
return &Topic{
Id: slot.TopicSlotId,
Revision: slot.Revision,
@ -248,22 +274,11 @@ func getTopicModel(slot *store.TopicSlot, showData bool, showList bool) *Topic {
Revision: slot.Revision,
Data: &TopicData {
DetailRevision: slot.Topic.DetailRevision,
TopicDetail: &TopicDetail{
Guid: slot.Topic.Guid,
DataType: slot.Topic.DataType,
Data: slot.Topic.Data,
Created: slot.Topic.Created,
Updated: slot.Topic.Updated,
Status: slot.Topic.Status,
},
TopicDetail: getTopicDetailModel(slot),
TagRevision: slot.Topic.TagRevision,
TopicTags: &TopicTags{
TagCount: slot.Topic.TagCount,
TagUpdated: slot.Topic.TagUpdated,
},
TopicTags: getTopicCountModel(slot),
},
}
}

View File

@ -368,6 +368,13 @@ type Tag struct {
Data *TagData `json:"data"`
}
type TagCount struct {
Count int32 `json:"count"`
Updated int64 `json:"updated"`
}
type TagData struct {
Guid string `json:"guid"`
@ -398,7 +405,7 @@ type TopicData struct {
TopicDetail *TopicDetail `json:"topicDetail,omitempty"`
TopicTags *TopicTags `json:"topicTags:,omitempty"`
TopicTags *TagCount `json:"topicTags:,omitempty"`
}
type TopicDetail struct {

View File

@ -558,6 +558,13 @@ var routes = Routes{
GetChannelSubjectField,
},
Route{
"GetChannelTopic",
strings.ToUpper("Get"),
"/content/channels/{channelId}/topics/{topicId}",
GetChannelTopic,
},
Route{
"GetChannelTopicDetail",
strings.ToUpper("Get"),
@ -566,10 +573,10 @@ var routes = Routes{
},
Route{
"GetChannelTopicSize",
"GetChannelTopicCount",
strings.ToUpper("Get"),
"/content/channels/{channelId}/topics/{topicId}/size",
GetChannelTopicSize,
"/content/channels/{channelId}/topics/{topicId}/count",
GetChannelTopicCount,
},
Route{

View File

@ -206,17 +206,18 @@ type Channel struct {
type TopicSlot struct {
ID uint
TopicSlotId string `gorm:"not null;index:topicslot,unique"`
AccountID uint `gorm:"not null;index:topicslot,unique"`
TopicSlotId string `gorm:"not null;index:topicaccount,unique;index:topicchannel,unique"`
AccountID uint `gorm:"not null;index:topicaccount,unique"`
ChannelID uint `gorm:"not null;index:topicchannel,unique"`
Revision int64 `gorm:"not null"`
TopicID uint `gorm:"not null;default:0"`
Topic *Topic
Channel *Channel
Account Account
}
type Topic struct {
ID uint `gorm:"primaryKey;not null;unique;autoIncrement"`
ChannelID uint
DetailRevision int64 `gorm:"not null"`
Guid string
DataType string `gorm:"index"`
@ -227,7 +228,6 @@ type Topic struct {
TagCount int32 `gorm:"not null"`
TagUpdated int64 `gorm:"autoUpdateTime"`
TagRevision int64 `gorm:"not null"`
Channel *Channel
Assets []Asset
Tags []Tag
}