Polishing posts and tags (#11)

* Polishing posts

* Parent -> Resource
This commit is contained in:
Janos Dobronszki
2020-10-16 11:20:10 +02:00
committed by GitHub
parent 69674f4c95
commit 0110816efb
8 changed files with 224 additions and 120 deletions

View File

@@ -39,8 +39,8 @@ type Posts struct {
}
func (p *Posts) Save(ctx context.Context, req *posts.SaveRequest, rsp *posts.SaveResponse) error {
if len(req.Id) == 0 || len(req.Title) == 0 || len(req.Content) == 0 {
return errors.BadRequest("posts.save.input-check", "Id, title or content is missing")
if len(req.Id) == 0 {
return errors.BadRequest("posts.save.input-check", "Id is missing")
}
// read by post
@@ -71,27 +71,48 @@ func (p *Posts) Save(ctx context.Context, req *posts.SaveRequest, rsp *posts.Sav
if err != nil {
return errors.InternalServerError("posts.save.unmarshal", "Failed to unmarshal old post: %v", err.Error())
}
post := &Post{
ID: req.Id,
Title: req.Title,
Content: req.Content,
Slug: postSlug,
Tags: req.Tags,
Title: oldPost.Title,
Content: oldPost.Content,
Slug: oldPost.Slug,
Tags: oldPost.Tags,
CreateTimestamp: oldPost.CreateTimestamp,
UpdateTimestamp: time.Now().Unix(),
}
if len(req.Title) > 0 {
post.Title = req.Title
post.Slug = slug.Make(post.Title)
}
if len(req.Slug) > 0 {
post.Slug = req.Slug
}
if len(req.Content) > 0 {
post.Content = req.Content
}
if len(req.Tags) > 0 {
// Handle the special case of deletion
if len(req.Tags) == 0 && req.Tags[0] == "" {
post.Tags = []string{}
} else {
post.Tags = req.Tags
}
}
// Check if slug exists
recordsBySlug, err := store.Read(fmt.Sprintf("%v:%v", slugPrefix, postSlug))
if err != nil && err != store.ErrNotFound {
return errors.InternalServerError("posts.save.store-read", "Failed to read post by slug: %v", err.Error())
}
otherSlugPost := &Post{}
err = json.Unmarshal(record.Value, otherSlugPost)
if err != nil {
return errors.InternalServerError("posts.save.slug-unmarshal", "Error unmarshaling other post with same slug: %v", err.Error())
}
if len(recordsBySlug) > 0 && oldPost.ID != otherSlugPost.ID {
if len(recordsBySlug) > 0 {
otherSlugPost := &Post{}
err = json.Unmarshal(recordsBySlug[0].Value, otherSlugPost)
if oldPost.ID != otherSlugPost.ID {
if err != nil {
return errors.InternalServerError("posts.save.slug-unmarshal", "Error unmarshaling other post with same slug: %v", err.Error())
}
}
return errors.BadRequest("posts.save.slug-check", "An other post with this slug already exists")
}
@@ -135,9 +156,9 @@ func (p *Posts) savePost(ctx context.Context, oldPost, post *Post) error {
if oldPost == nil {
for _, tagName := range post.Tags {
_, err := p.Tags.Add(ctx, &tags.AddRequest{
ParentID: post.ID,
Type: tagType,
Title: tagName,
ResourceID: post.ID,
Type: tagType,
Title: tagName,
})
if err != nil {
return err
@@ -161,9 +182,9 @@ func (p *Posts) diffTags(ctx context.Context, parentID string, oldTagNames, newT
_, stillThere := newTags[i]
if !stillThere {
_, err := p.Tags.Remove(ctx, &tags.RemoveRequest{
ParentID: parentID,
Type: tagType,
Title: i,
ResourceID: parentID,
Type: tagType,
Title: i,
})
if err != nil {
logger.Errorf("Error decreasing count for tag '%v' with type '%v' for parent '%v'", i, tagType, parentID)
@@ -174,9 +195,9 @@ func (p *Posts) diffTags(ctx context.Context, parentID string, oldTagNames, newT
_, newlyAdded := oldTags[i]
if newlyAdded {
_, err := p.Tags.Add(ctx, &tags.AddRequest{
ParentID: parentID,
Type: tagType,
Title: i,
ResourceID: parentID,
Type: tagType,
Title: i,
})
if err != nil {
logger.Errorf("Error increasing count for tag '%v' with type '%v' for parent '%v': %v", i, tagType, parentID, err)
@@ -193,6 +214,10 @@ func (p *Posts) Query(ctx context.Context, req *pb.QueryRequest, rsp *pb.QueryRe
key := fmt.Sprintf("%v:%v", slugPrefix, req.Slug)
logger.Infof("Reading post by slug: %v", req.Slug)
records, err = store.Read("", store.Prefix(key))
} else if len(req.Id) > 0 {
key := fmt.Sprintf("%v:%v", idPrefix, req.Id)
logger.Infof("Reading post by id: %v", req.Id)
records, err = store.Read("", store.Prefix(key))
} else {
key := fmt.Sprintf("%v:", timeStampPrefix)
var limit uint

View File

@@ -115,10 +115,13 @@ func (m *Post) GetTags() []string {
return nil
}
// Query posts. Acts as a listing when no id or slug provided.
// Gets a single post by id or slug if any of them provided.
type QueryRequest struct {
Slug string `protobuf:"bytes,1,opt,name=slug,proto3" json:"slug,omitempty"`
Offset int64 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`
Limit int64 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"`
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Slug string `protobuf:"bytes,2,opt,name=slug,proto3" json:"slug,omitempty"`
Offset int64 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"`
Limit int64 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -149,6 +152,13 @@ func (m *QueryRequest) XXX_DiscardUnknown() {
var xxx_messageInfo_QueryRequest proto.InternalMessageInfo
func (m *QueryRequest) GetId() string {
if m != nil {
return m.Id
}
return ""
}
func (m *QueryRequest) GetSlug() string {
if m != nil {
return m.Slug
@@ -210,11 +220,13 @@ func (m *QueryResponse) GetPosts() []*Post {
}
type SaveRequest struct {
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"`
Slug string `protobuf:"bytes,3,opt,name=slug,proto3" json:"slug,omitempty"`
Content string `protobuf:"bytes,4,opt,name=content,proto3" json:"content,omitempty"`
Timestamp int64 `protobuf:"varint,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"`
Slug string `protobuf:"bytes,3,opt,name=slug,proto3" json:"slug,omitempty"`
Content string `protobuf:"bytes,4,opt,name=content,proto3" json:"content,omitempty"`
Timestamp int64 `protobuf:"varint,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
// When updating a post and wanting to delete all tags,
// send a list of tags with only one member being an empty string [""]
Tags []string `protobuf:"bytes,6,rep,name=tags,proto3" json:"tags,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
@@ -412,28 +424,28 @@ func init() {
}
var fileDescriptor_a1e4efc789192621 = []byte{
// 363 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x52, 0x5d, 0x4e, 0xf3, 0x30,
// 365 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x52, 0x5d, 0x4e, 0xf3, 0x30,
0x10, 0xfc, 0xd2, 0xfc, 0xf4, 0xeb, 0xf6, 0x47, 0x68, 0x5b, 0xc0, 0xaa, 0x10, 0x94, 0x3c, 0xf5,
0xa9, 0x88, 0x82, 0xc4, 0x05, 0x38, 0x40, 0x09, 0x27, 0x08, 0xd4, 0x2d, 0x91, 0xd2, 0x26, 0xc4,
0x0e, 0x12, 0xe7, 0xe0, 0x14, 0x5c, 0x81, 0xd3, 0x61, 0xaf, 0xed, 0x92, 0x56, 0xe2, 0x91, 0x97,
0x68, 0x67, 0x36, 0x1e, 0xcf, 0x4c, 0x02, 0xa7, 0x65, 0x55, 0xc8, 0xe2, 0xaa, 0x2c, 0x84, 0x14,
0xe6, 0x39, 0x23, 0x06, 0x43, 0x02, 0xf1, 0x97, 0x07, 0xc1, 0x42, 0x4d, 0x38, 0x80, 0x56, 0xb6,
0x64, 0xde, 0xc4, 0x9b, 0x76, 0x12, 0x35, 0xe1, 0x08, 0x42, 0x99, 0xc9, 0x9c, 0xb3, 0x16, 0x51,
0x06, 0x20, 0x42, 0x20, 0xf2, 0x7a, 0xcd, 0x7c, 0x22, 0x69, 0x46, 0x06, 0xed, 0xe7, 0x62, 0x2b,
0xf9, 0x56, 0xb2, 0x80, 0x68, 0x07, 0x69, 0x53, 0xf1, 0x54, 0xf2, 0x25, 0x0b, 0xd5, 0xc6, 0x4f,
0x1c, 0xd4, 0x9b, 0xba, 0x5c, 0xd2, 0x26, 0x32, 0x1b, 0x0b, 0xf1, 0x04, 0xa2, 0xb4, 0x96, 0x2f,
0x45, 0xc5, 0xda, 0x24, 0x66, 0x91, 0xbe, 0x59, 0xa6, 0x6b, 0xc1, 0xfe, 0x4f, 0x7c, 0x7d, 0xb3,
0x9e, 0xe3, 0x05, 0xf4, 0x1e, 0x6a, 0x5e, 0xbd, 0x27, 0xfc, 0xb5, 0xe6, 0x2a, 0x83, 0x73, 0xe7,
0x35, 0xdc, 0x29, 0xbd, 0x62, 0xb5, 0x12, 0x5c, 0x52, 0x10, 0x3f, 0xb1, 0x48, 0xe7, 0xcb, 0xb3,
0x4d, 0x26, 0x29, 0x8a, 0x9f, 0x18, 0x10, 0xcf, 0xa1, 0x6f, 0x15, 0x45, 0x59, 0x6c, 0x05, 0xc7,
0x4b, 0x30, 0x45, 0x29, 0x4d, 0x7f, 0xda, 0x9d, 0x77, 0x67, 0xa6, 0x43, 0x5d, 0x59, 0x62, 0x2b,
0xfc, 0xf0, 0xa0, 0xfb, 0x98, 0xbe, 0x71, 0xe7, 0xe2, 0x2f, 0x9a, 0x3c, 0x83, 0x8e, 0xcc, 0x36,
0x4a, 0x3d, 0xdd, 0x94, 0xb6, 0xcb, 0x1f, 0x62, 0xd7, 0x4d, 0xd4, 0xe8, 0xe6, 0x1c, 0x7a, 0xc6,
0x94, 0x0d, 0x72, 0xe0, 0x2a, 0xbe, 0x80, 0xfe, 0x3d, 0xcf, 0xb9, 0xfc, 0xcd, 0x76, 0x7c, 0x04,
0x03, 0xf7, 0x82, 0x91, 0x98, 0x7f, 0x7a, 0x10, 0xea, 0xe0, 0x02, 0x6f, 0x21, 0xa4, 0x9a, 0x70,
0x68, 0xfb, 0x68, 0x7e, 0x86, 0xf1, 0x68, 0x9f, 0x34, 0xa7, 0xe3, 0x7f, 0x78, 0x0d, 0x81, 0xb6,
0x84, 0x68, 0xf7, 0x8d, 0xd2, 0xc6, 0xc3, 0x3d, 0x6e, 0x77, 0xe4, 0x0e, 0x22, 0x63, 0x02, 0x9d,
0xe8, 0x9e, 0xe9, 0xf1, 0xf1, 0x01, 0xeb, 0x0e, 0x3e, 0x45, 0xf4, 0x97, 0xdf, 0x7c, 0x07, 0x00,
0x00, 0xff, 0xff, 0xed, 0x03, 0x8f, 0x37, 0x00, 0x03, 0x00, 0x00,
0xa9, 0x88, 0x82, 0xc4, 0x05, 0x38, 0x00, 0x84, 0x0b, 0x10, 0xa8, 0x5b, 0x22, 0xa5, 0x75, 0x88,
0x1d, 0x24, 0xce, 0xc1, 0x29, 0xb8, 0x02, 0xa7, 0x23, 0x5e, 0x3b, 0x25, 0xa9, 0xe8, 0x1b, 0x2f,
0xd1, 0xce, 0x6c, 0x76, 0x77, 0x66, 0x12, 0x38, 0xce, 0x72, 0xa1, 0xc4, 0x45, 0x26, 0xa4, 0x92,
0xe6, 0x39, 0x23, 0x06, 0x7d, 0x02, 0xe1, 0x97, 0x03, 0xde, 0x5d, 0x59, 0xe1, 0x00, 0x5a, 0xc9,
0x82, 0x39, 0x13, 0x67, 0xda, 0x89, 0xca, 0x0a, 0x47, 0xe0, 0xab, 0x44, 0xa5, 0x9c, 0xb5, 0x88,
0x32, 0x00, 0x11, 0x3c, 0x99, 0x16, 0x2b, 0xe6, 0x12, 0x49, 0x35, 0x32, 0x68, 0x3f, 0x8b, 0x8d,
0xe2, 0x1b, 0xc5, 0x3c, 0xa2, 0x2b, 0x48, 0x9d, 0x9c, 0xc7, 0x8a, 0x2f, 0x98, 0x5f, 0x76, 0xdc,
0xa8, 0x82, 0xba, 0x53, 0x64, 0x0b, 0xea, 0x04, 0xa6, 0x63, 0x21, 0x1e, 0x41, 0x10, 0x17, 0xea,
0x45, 0xe4, 0xac, 0x4d, 0xcb, 0x2c, 0xd2, 0x97, 0x55, 0xbc, 0x92, 0xec, 0xff, 0xc4, 0xd5, 0x97,
0x75, 0x1d, 0x3e, 0x42, 0xef, 0xbe, 0xe0, 0xf9, 0x7b, 0xc4, 0x5f, 0x0b, 0xfe, 0x8b, 0x87, 0x4a,
0x6d, 0xab, 0xa6, 0xb6, 0xdc, 0x2f, 0x96, 0x4b, 0xc9, 0x15, 0x79, 0x70, 0x23, 0x8b, 0xb4, 0xdf,
0x34, 0x59, 0x27, 0xc6, 0x83, 0x1b, 0x19, 0x10, 0xce, 0xa1, 0x6f, 0x2f, 0xc8, 0x4c, 0x6c, 0x24,
0xc7, 0x73, 0x30, 0xc1, 0x95, 0x57, 0xdc, 0x69, 0x77, 0xde, 0x9d, 0x99, 0x4c, 0x75, 0x84, 0x91,
0x8d, 0xf4, 0xc3, 0x81, 0xee, 0x43, 0xfc, 0xc6, 0xf7, 0xa9, 0xfa, 0x8b, 0x64, 0x4f, 0xa0, 0xa3,
0x92, 0x75, 0xb9, 0x3d, 0x5e, 0x67, 0x36, 0xdb, 0x1f, 0x62, 0x9b, 0x55, 0x50, 0xcb, 0xea, 0x14,
0x7a, 0x46, 0x94, 0x35, 0xb2, 0xa3, 0x2a, 0x3c, 0x83, 0xfe, 0x2d, 0x4f, 0xb9, 0xda, 0x27, 0x3b,
0x3c, 0x80, 0x41, 0xf5, 0x82, 0x59, 0x31, 0xff, 0x74, 0xc0, 0xd7, 0xc6, 0x25, 0x5e, 0x83, 0x4f,
0x31, 0xe1, 0xd0, 0xe6, 0x51, 0xff, 0x2c, 0xe3, 0x51, 0x93, 0x34, 0xd3, 0xe1, 0x3f, 0xbc, 0x04,
0x4f, 0x4b, 0x42, 0xb4, 0xfd, 0x5a, 0x68, 0xe3, 0x61, 0x83, 0xdb, 0x8e, 0xdc, 0x40, 0x60, 0x44,
0x60, 0xb5, 0xb4, 0x21, 0x7a, 0x7c, 0xb8, 0xc3, 0x56, 0x83, 0x4f, 0x01, 0xfd, 0xf5, 0x57, 0xdf,
0x01, 0x00, 0x00, 0xff, 0xff, 0x05, 0x88, 0xa9, 0x15, 0x10, 0x03, 0x00, 0x00,
}

View File

@@ -20,10 +20,14 @@ message Post {
repeated string tags = 8;
}
// Query posts. Acts as a listing when no id or slug provided.
// Gets a single post by id or slug if any of them provided.
message QueryRequest {
string slug = 1;
int64 offset = 2;
int64 limit = 3;
string id = 1;
string slug = 2;
string tag = 3;
int64 offset = 4;
int64 limit = 5;
}
message QueryResponse {
@@ -36,6 +40,8 @@ message SaveRequest {
string slug = 3;
string content = 4;
int64 timestamp = 5;
// When updating a post and wanting to delete all tags,
// send a list of tags with only one member being an empty string [""]
repeated string tags = 6;
}

View File

@@ -15,9 +15,10 @@ import (
const (
slugPrefix = "bySlug"
parentPrefix = "byParent"
resourcePrefix = "byResource"
typePrefix = "byType"
tagCountPrefix = "tagCount"
childrenByTag = "childrenByTag"
)
type Tag struct {
@@ -30,14 +31,14 @@ type Tag struct {
type Tags struct{}
func (t *Tags) Add(ctx context.Context, req *pb.AddRequest, rsp *pb.AddResponse) error {
if len(req.ParentID) == 0 || len(req.Type) == 0 {
return errors.BadRequest("tags.increasecount.input-check", "parent id and type is required")
if len(req.ResourceID) == 0 || len(req.Type) == 0 {
return errors.BadRequest("tags.increasecount.input-check", "resource id and type is required")
}
tagSlug := slug.Make(req.GetTitle())
key := fmt.Sprintf("%v:%v", slugPrefix, tagSlug)
// read by parent ID + slug, the record is identical in boths places anyway
// read by resource ID + slug, the record is identical in boths places anyway
records, err := store.Read(key)
if err != nil && err != store.ErrNotFound {
return err
@@ -62,7 +63,7 @@ func (t *Tags) Add(ctx context.Context, req *pb.AddRequest, rsp *pb.AddResponse)
// increase tag count
err = store.Write(&store.Record{
Key: fmt.Sprintf("%v:%v:%v", tagCountPrefix, tag.Slug, req.GetParentID()),
Key: fmt.Sprintf("%v:%v:%v", tagCountPrefix, tag.Slug, req.GetResourceID()),
Value: nil,
})
if err != nil {
@@ -85,7 +86,7 @@ func (t *Tags) Add(ctx context.Context, req *pb.AddRequest, rsp *pb.AddResponse)
return err
}
err = store.Write(&store.Record{
Key: fmt.Sprintf("%v:%v:%v", parentPrefix, req.GetParentID(), tag.Slug),
Key: fmt.Sprintf("%v:%v:%v", resourcePrefix, req.GetResourceID(), tag.Slug),
Value: tagJSON,
})
if err != nil {
@@ -105,7 +106,7 @@ func (t *Tags) saveTag(tag *Tag) error {
return err
}
// write parentId:slug to enable prefix listing based on type
// write resourceId:slug to enable prefix listing based on type
err = store.Write(&store.Record{
Key: key,
Value: bytes,
@@ -120,15 +121,15 @@ func (t *Tags) saveTag(tag *Tag) error {
}
func (t *Tags) Remove(ctx context.Context, req *pb.RemoveRequest, rsp *pb.RemoveResponse) error {
if len(req.ParentID) == 0 || len(req.Type) == 0 {
return errors.BadRequest("tags.decreaseecount.input-check", "parent id and type is required")
if len(req.ResourceID) == 0 || len(req.Type) == 0 {
return errors.BadRequest("tags.decreaseecount.input-check", "resource id and type is required")
}
tagSlug := slug.Make(req.GetTitle())
parentKey := fmt.Sprintf("%v:%v:%v", parentPrefix, req.GetParentID(), tagSlug)
resourceKey := fmt.Sprintf("%v:%v:%v", resourcePrefix, req.GetResourceID(), tagSlug)
// read by parent ID + slug, the record is identical in boths places anyway
records, err := store.Read(parentKey)
// read by resource ID + slug, the record is identical in boths places anyway
records, err := store.Read(resourceKey)
if err != nil && err != store.ErrNotFound {
return err
}
@@ -146,7 +147,7 @@ func (t *Tags) Remove(ctx context.Context, req *pb.RemoveRequest, rsp *pb.Remove
}
// decrease tag count
err = store.Delete(fmt.Sprintf("%v:%v:%v", tagCountPrefix, tag.Slug, req.GetParentID()))
err = store.Delete(fmt.Sprintf("%v:%v:%v", tagCountPrefix, tag.Slug, req.GetResourceID()))
if err != nil {
return err
}
@@ -163,12 +164,12 @@ func (t *Tags) Remove(ctx context.Context, req *pb.RemoveRequest, rsp *pb.Remove
func (t *Tags) List(ctx context.Context, req *pb.ListRequest, rsp *pb.ListResponse) error {
logger.Info("Received Tags.List request")
key := ""
if len(req.ParentID) > 0 {
key = fmt.Sprintf("%v:%v", parentPrefix, req.ParentID)
if len(req.ResourceID) > 0 {
key = fmt.Sprintf("%v:%v", resourcePrefix, req.ResourceID)
} else if len(req.Type) > 0 {
key = fmt.Sprintf("%v:%v", typePrefix, req.Type)
} else {
return errors.BadRequest("tags.list.input-check", "parent id or type is required")
return errors.BadRequest("tags.list.input-check", "resource id or type is required")
}
records, err := store.Read("", store.Prefix(key))
@@ -200,10 +201,10 @@ func (t *Tags) Update(ctx context.Context, req *pb.UpdateRequest, rsp *pb.Update
}
tagSlug := slug.Make(req.GetTitle())
parentID := fmt.Sprintf("%v:%v", slugPrefix, tagSlug)
resourceID := fmt.Sprintf("%v:%v", slugPrefix, tagSlug)
// read by parent ID + slug, the record is identical in boths places anyway
records, err := store.Read(parentID)
// read by resource ID + slug, the record is identical in boths places anyway
records, err := store.Read(resourceID)
if err != nil {
return err
}

View File

@@ -21,7 +21,7 @@ var _ = math.Inf
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Tag struct {
// Type is useful for namespacing and listing across parents,
// Type is useful for namespacing and listing across resources,
// ie. list tags for posts, customers etc.
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
Slug string `protobuf:"bytes,2,opt,name=slug,proto3" json:"slug,omitempty"`
@@ -94,9 +94,10 @@ func (m *Tag) GetCount() int64 {
}
type AddRequest struct {
ParentID string `protobuf:"bytes,1,opt,name=parentID,proto3" json:"parentID,omitempty"`
ResourceID string `protobuf:"bytes,1,opt,name=resourceID,proto3" json:"resourceID,omitempty"`
Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"`
Title string `protobuf:"bytes,3,opt,name=title,proto3" json:"title,omitempty"`
ResourceCreated int64 `protobuf:"varint,4,opt,name=resourceCreated,proto3" json:"resourceCreated,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -127,9 +128,9 @@ func (m *AddRequest) XXX_DiscardUnknown() {
var xxx_messageInfo_AddRequest proto.InternalMessageInfo
func (m *AddRequest) GetParentID() string {
func (m *AddRequest) GetResourceID() string {
if m != nil {
return m.ParentID
return m.ResourceID
}
return ""
}
@@ -148,6 +149,13 @@ func (m *AddRequest) GetTitle() string {
return ""
}
func (m *AddRequest) GetResourceCreated() int64 {
if m != nil {
return m.ResourceCreated
}
return 0
}
type AddResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
@@ -180,7 +188,7 @@ func (m *AddResponse) XXX_DiscardUnknown() {
var xxx_messageInfo_AddResponse proto.InternalMessageInfo
type RemoveRequest struct {
ParentID string `protobuf:"bytes,1,opt,name=parentID,proto3" json:"parentID,omitempty"`
ResourceID string `protobuf:"bytes,1,opt,name=resourceID,proto3" json:"resourceID,omitempty"`
Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"`
Title string `protobuf:"bytes,3,opt,name=title,proto3" json:"title,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
@@ -213,9 +221,9 @@ func (m *RemoveRequest) XXX_DiscardUnknown() {
var xxx_messageInfo_RemoveRequest proto.InternalMessageInfo
func (m *RemoveRequest) GetParentID() string {
func (m *RemoveRequest) GetResourceID() string {
if m != nil {
return m.ParentID
return m.ResourceID
}
return ""
}
@@ -351,10 +359,10 @@ func (m *UpdateResponse) XXX_DiscardUnknown() {
var xxx_messageInfo_UpdateResponse proto.InternalMessageInfo
// ListRequest: list either by parent id or type.
// ListRequest: list either by resource id or type.
// Optionally filter by min or max count.
type ListRequest struct {
ParentID string `protobuf:"bytes,1,opt,name=parentID,proto3" json:"parentID,omitempty"`
ResourceID string `protobuf:"bytes,1,opt,name=resourceID,proto3" json:"resourceID,omitempty"`
Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"`
MinCount int64 `protobuf:"varint,3,opt,name=minCount,proto3" json:"minCount,omitempty"`
MaxCount int64 `protobuf:"varint,4,opt,name=maxCount,proto3" json:"maxCount,omitempty"`
@@ -388,9 +396,9 @@ func (m *ListRequest) XXX_DiscardUnknown() {
var xxx_messageInfo_ListRequest proto.InternalMessageInfo
func (m *ListRequest) GetParentID() string {
func (m *ListRequest) GetResourceID() string {
if m != nil {
return m.ParentID
return m.ResourceID
}
return ""
}
@@ -472,28 +480,29 @@ func init() {
}
var fileDescriptor_53c4e9b325a9c45b = []byte{
// 357 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x53, 0x41, 0x4b, 0xf3, 0x40,
0x10, 0x6d, 0xbb, 0x69, 0xf9, 0x3a, 0xf9, 0x2a, 0x75, 0xec, 0x21, 0x04, 0x84, 0x92, 0x53, 0x0f,
0xda, 0x42, 0xc5, 0x1f, 0x20, 0x7a, 0x11, 0x3c, 0x05, 0x7b, 0xf2, 0x14, 0x9b, 0xa5, 0x04, 0xda,
0x24, 0x76, 0x37, 0xa2, 0xf8, 0x33, 0xfd, 0x43, 0x66, 0x67, 0x37, 0xeb, 0x46, 0x8b, 0x07, 0xf1,
0x36, 0xf3, 0x26, 0x3b, 0xef, 0xed, 0x7b, 0x1b, 0x18, 0x97, 0xfb, 0x42, 0x16, 0x0b, 0x99, 0x6c,
0xc4, 0x9c, 0x4a, 0xf4, 0x54, 0x1d, 0xbd, 0x01, 0xbb, 0x4f, 0x36, 0x88, 0xe0, 0xc9, 0xd7, 0x92,
0x07, 0xdd, 0x69, 0x77, 0x36, 0x8c, 0xa9, 0x56, 0x98, 0xd8, 0x56, 0x9b, 0xa0, 0xa7, 0x31, 0x55,
0xe3, 0x04, 0xfa, 0x32, 0x93, 0x5b, 0x1e, 0x30, 0x02, 0x75, 0x83, 0x53, 0xf0, 0x53, 0x2e, 0xd6,
0xfb, 0xac, 0x94, 0x59, 0x91, 0x07, 0x1e, 0xcd, 0x5c, 0x48, 0x9d, 0x5b, 0x17, 0x55, 0x2e, 0x83,
0x7e, 0x3d, 0x63, 0xb1, 0x6e, 0xa2, 0x18, 0xe0, 0x2a, 0x4d, 0x63, 0xfe, 0x54, 0x71, 0x21, 0x31,
0x84, 0x7f, 0x65, 0xb2, 0xe7, 0xb9, 0xbc, 0xbd, 0x31, 0x3a, 0x6c, 0x6f, 0xf5, 0xf5, 0x1c, 0x7d,
0x07, 0xb5, 0x44, 0x23, 0xf0, 0x69, 0xa7, 0x28, 0x8b, 0x5c, 0xf0, 0x68, 0x05, 0xa3, 0x98, 0xef,
0x8a, 0x67, 0xfe, 0xb7, 0x2c, 0x63, 0x38, 0x6a, 0xd6, 0x1a, 0xa2, 0x07, 0x18, 0xad, 0xca, 0x34,
0x91, 0x96, 0xe8, 0x90, 0xa5, 0x76, 0x59, 0xef, 0x07, 0xfb, 0xd8, 0x37, 0xfb, 0x14, 0x5d, 0xb3,
0xdc, 0xd0, 0x55, 0xe0, 0xdf, 0x65, 0x42, 0xfe, 0xf6, 0x56, 0xf5, 0xf7, 0xbb, 0x2c, 0xbf, 0xa6,
0x48, 0x18, 0x45, 0x62, 0x7b, 0x9a, 0x25, 0x2f, 0x7a, 0xe6, 0x99, 0x99, 0xe9, 0xa3, 0x73, 0xf8,
0xaf, 0x69, 0xb5, 0x0c, 0x3c, 0x05, 0x7a, 0x46, 0x35, 0x27, 0x9b, 0xf9, 0xcb, 0xe1, 0x9c, 0xde,
0x57, 0xfd, 0xa0, 0x62, 0x82, 0x97, 0xef, 0x5d, 0xf0, 0xea, 0x4e, 0xe0, 0x19, 0xb0, 0x3a, 0x15,
0x1c, 0xeb, 0x0f, 0x3e, 0x43, 0x0f, 0x8f, 0x1d, 0xc4, 0x5c, 0xad, 0x83, 0x97, 0x30, 0xd0, 0xee,
0xe2, 0x89, 0x1e, 0xb7, 0x22, 0x0c, 0x27, 0x6d, 0xd0, 0x1e, 0x5b, 0x80, 0xa7, 0xc4, 0xa1, 0xd9,
0xe9, 0xf8, 0x13, 0xa2, 0x0b, 0xb9, 0x3c, 0xda, 0xd6, 0x86, 0xa7, 0x95, 0x60, 0xc3, 0xf3, 0xc5,
0xf9, 0xce, 0xe3, 0x80, 0x7e, 0xa0, 0x8b, 0x8f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xdd, 0x86, 0xfc,
0x24, 0x54, 0x03, 0x00, 0x00,
// 377 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x53, 0x41, 0x4f, 0xf2, 0x40,
0x10, 0xa5, 0x6c, 0x21, 0x1f, 0xd3, 0x0f, 0xc5, 0x91, 0x43, 0xd3, 0x44, 0x43, 0x7a, 0xe2, 0xa0,
0x90, 0x60, 0xfc, 0x01, 0x06, 0x2f, 0x26, 0x9e, 0x1a, 0x3d, 0x18, 0x4f, 0x95, 0x6e, 0x48, 0x13,
0x68, 0x6b, 0x77, 0x6b, 0x24, 0x5e, 0xfc, 0x8f, 0xfe, 0x21, 0xdb, 0xd9, 0x6d, 0x5d, 0x90, 0x78,
0xd1, 0xdb, 0xcc, 0x9b, 0xce, 0xbc, 0xb7, 0x6f, 0xa6, 0x30, 0xc8, 0xf2, 0x54, 0xa6, 0x53, 0x19,
0x2e, 0xc5, 0x84, 0x42, 0xb4, 0xab, 0xd8, 0x7f, 0x03, 0x76, 0x17, 0x2e, 0x11, 0xc1, 0x96, 0x9b,
0x8c, 0xbb, 0xd6, 0xc8, 0x1a, 0xf7, 0x02, 0x8a, 0x2b, 0x4c, 0xac, 0x8a, 0xa5, 0xdb, 0x56, 0x58,
0x15, 0xe3, 0x10, 0x3a, 0x32, 0x96, 0x2b, 0xee, 0x32, 0x02, 0x55, 0x82, 0x23, 0x70, 0x22, 0x2e,
0x16, 0x79, 0x9c, 0xc9, 0x38, 0x4d, 0x5c, 0x9b, 0x6a, 0x26, 0x54, 0xf5, 0x2d, 0xd2, 0x22, 0x91,
0x6e, 0xa7, 0xac, 0xb1, 0x40, 0x25, 0xfe, 0xbb, 0x05, 0x70, 0x15, 0x45, 0x01, 0x7f, 0x2e, 0xb8,
0x90, 0x78, 0x0a, 0x90, 0x73, 0x91, 0x16, 0xf9, 0x82, 0xdf, 0x5c, 0x6b, 0x29, 0x06, 0xd2, 0x88,
0x6c, 0x1b, 0x22, 0xf7, 0x0b, 0x1a, 0xc3, 0x61, 0xdd, 0x37, 0xcf, 0x79, 0x28, 0x79, 0x44, 0xa2,
0x58, 0xb0, 0x0b, 0xfb, 0x7d, 0x70, 0x48, 0x81, 0xc8, 0xd2, 0x44, 0x70, 0xff, 0x01, 0xfa, 0x01,
0x5f, 0xa7, 0x2f, 0xfc, 0xcf, 0x35, 0xf9, 0x03, 0x38, 0xa8, 0x47, 0x6b, 0xb2, 0x47, 0xe8, 0xdf,
0x67, 0x51, 0x29, 0xa3, 0x26, 0xdb, 0xb7, 0x85, 0x66, 0x58, 0xfb, 0x07, 0xc7, 0xd9, 0x37, 0xc7,
0x2b, 0xba, 0x7a, 0xb8, 0xa6, 0xdb, 0x80, 0x73, 0x1b, 0x0b, 0xf9, 0x9b, 0x97, 0x79, 0xf0, 0x6f,
0x1d, 0x27, 0x73, 0xda, 0x24, 0x23, 0x43, 0x9b, 0x9c, 0x6a, 0xe1, 0xab, 0xaa, 0xd9, 0xba, 0xa6,
0x73, 0xff, 0x1c, 0xfe, 0x2b, 0x6a, 0x25, 0x05, 0x4f, 0x80, 0xae, 0xaf, 0x64, 0x65, 0x63, 0x67,
0xd6, 0x9b, 0xd0, 0x59, 0x96, 0x77, 0x18, 0x10, 0x3c, 0xfb, 0xb0, 0xc0, 0x2e, 0x33, 0x81, 0x67,
0xc0, 0xca, 0xed, 0xe0, 0x40, 0x7d, 0xf0, 0x75, 0x2a, 0xde, 0x91, 0x81, 0xe8, 0xe7, 0xb5, 0xf0,
0x12, 0xba, 0xca, 0x61, 0x3c, 0x56, 0xe5, 0xad, 0x55, 0x7a, 0xc3, 0x6d, 0xb0, 0x69, 0x9b, 0x82,
0x5d, 0x89, 0x43, 0x3d, 0xd3, 0xf0, 0xc8, 0x43, 0x13, 0x32, 0x79, 0x94, 0xb5, 0x35, 0xcf, 0xd6,
0x16, 0x6b, 0x9e, 0x1d, 0xf7, 0x5b, 0x4f, 0x5d, 0xfa, 0xef, 0x2e, 0x3e, 0x03, 0x00, 0x00, 0xff,
0xff, 0x9a, 0x72, 0x97, 0xbb, 0x8b, 0x03, 0x00, 0x00,
}

View File

@@ -42,9 +42,9 @@ func NewTagsEndpoints() []*api.Endpoint {
// Client API for Tags service
type TagsService interface {
// Add a tag to a parent
// Add a tag to a resource
Add(ctx context.Context, in *AddRequest, opts ...client.CallOption) (*AddResponse, error)
// Remove a tag from a parent
// Remove a tag from a resource
Remove(ctx context.Context, in *RemoveRequest, opts ...client.CallOption) (*RemoveResponse, error)
// List tags by
List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error)
@@ -107,9 +107,9 @@ func (c *tagsService) Update(ctx context.Context, in *UpdateRequest, opts ...cli
// Server API for Tags service
type TagsHandler interface {
// Add a tag to a parent
// Add a tag to a resource
Add(context.Context, *AddRequest, *AddResponse) error
// Remove a tag from a parent
// Remove a tag from a resource
Remove(context.Context, *RemoveRequest, *RemoveResponse) error
// List tags by
List(context.Context, *ListRequest, *ListResponse) error

View File

@@ -3,9 +3,9 @@ syntax = "proto3";
package tags;
service Tags {
// Add a tag to a parent
// Add a tag to a resource
rpc Add(AddRequest) returns (AddResponse) {}
// Remove a tag from a parent
// Remove a tag from a resource
rpc Remove(RemoveRequest) returns (RemoveResponse) {}
// List tags by
rpc List(ListRequest) returns (ListResponse) {}
@@ -14,7 +14,7 @@ service Tags {
}
message Tag {
// Type is useful for namespacing and listing across parents,
// Type is useful for namespacing and listing across resources,
// ie. list tags for posts, customers etc.
string type = 1;
string slug = 2;
@@ -24,15 +24,16 @@ message Tag {
}
message AddRequest {
string parentID = 1;
string resourceID = 1;
string type = 2;
string title = 3;
int64 resourceCreated = 4;
}
message AddResponse{}
message RemoveRequest {
string parentID = 1;
string resourceID = 1;
string type = 2;
string title = 3;
}
@@ -47,10 +48,10 @@ message UpdateRequest {
message UpdateResponse{}
// ListRequest: list either by parent id or type.
// ListRequest: list either by resource id or type.
// Optionally filter by min or max count.
message ListRequest{
string parentID = 1;
string resourceID = 1;
string type = 2;
int64 minCount = 3;
int64 maxCount = 4;

View File

@@ -236,4 +236,54 @@ func testPosts(t *test.T) {
t.Fatal(tagsActual.Tags[1], string(outp1), string(outp2))
return
}
// test updating fields fields and removing tags
outp, err = cmd.Exec("posts", "--id=2", "--title=Hi2", "--tags=a", "save")
if err != nil {
t.Fatal(string(outp))
return
}
outp, err = cmd.Exec("tags", "list", "--type=post-tag")
json.Unmarshal(outp, &tagsActual)
if len(tagsActual.Tags) == 0 {
outp1, _ := cmd.Exec("logs", "tags")
t.Fatal(string(append(outp, outp1...)))
return
}
if len(tagsActual.Tags) != 2 {
t.Fatal(tagsActual.Tags)
return
}
for _, tag := range tagsActual.Tags {
if tag.Title == "b" {
if tag.Count != "1" {
t.Fatal("Tag b should have a count 1")
return
}
}
if tag.Title == "a" {
if tag.Count != "2" {
t.Fatal("Tag b should have a count 2")
return
}
}
}
outp, err = cmd.Exec("posts", "--id=2", "query")
if err != nil {
t.Fatal(string(outp))
return
}
json.Unmarshal(outp, &actual)
if len(actual.Posts) == 0 {
t.Fatal(string(outp))
return
}
if actual.Posts[0].Title != "Hi2" ||
actual.Posts[0].Content != "Hi there1" ||
actual.Posts[0].Slug != "hi2" || len(actual.Posts[0].Tags) != 1 {
t.Fatal(actual)
return
}
}