Rename streams to threads (#55)

This commit is contained in:
Asim Aslam
2021-01-29 14:11:04 +00:00
committed by GitHub
parent bdc63a699c
commit 253c153706
32 changed files with 1047 additions and 1597 deletions

2
threads/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
streams

3
threads/Dockerfile Normal file
View File

@@ -0,0 +1,3 @@
FROM alpine
ADD threads /threads
ENTRYPOINT [ "/threads" ]

22
threads/Makefile Normal file
View File

@@ -0,0 +1,22 @@
GOPATH:=$(shell go env GOPATH)
.PHONY: init
init:
go get -u github.com/golang/protobuf/proto
go get -u github.com/golang/protobuf/protoc-gen-go
go get github.com/micro/micro/v3/cmd/protoc-gen-micro
.PHONY: proto
proto:
protoc --proto_path=. --micro_out=. --go_out=:. proto/threads.proto
.PHONY: build
build:
go build -o threads *.go
.PHONY: test
test:
go test -v ./... -cover
.PHONY: docker
docker:
docker build . -t threads:latest

27
threads/README.md Normal file
View File

@@ -0,0 +1,27 @@
Threads conversations
# Threads Service
Threads provides threaded conversations as a service grouped by topics.
## Usage
Generated with
```
micro new threads
```
## Usage
Generate the proto code
```
make proto
```
Run the service
```
micro run .
```

3
threads/generate.go Normal file
View File

@@ -0,0 +1,3 @@
package main
//go:generate make proto

View File

@@ -0,0 +1,37 @@
package handler
import (
"context"
"github.com/google/uuid"
"github.com/micro/micro/v3/service/errors"
"github.com/micro/micro/v3/service/logger"
pb "github.com/micro/services/threads/proto"
)
// Create a conversation
func (s *Threads) CreateConversation(ctx context.Context, req *pb.CreateConversationRequest, rsp *pb.CreateConversationResponse) error {
// validate the request
if len(req.GroupId) == 0 {
return ErrMissingGroupID
}
if len(req.Topic) == 0 {
return ErrMissingTopic
}
// write the conversation to the database
conv := &Conversation{
ID: uuid.New().String(),
Topic: req.Topic,
GroupID: req.GroupId,
CreatedAt: s.Time(),
}
if err := s.DB.Create(conv).Error; err != nil {
logger.Errorf("Error creating conversation: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
}
// serialize the response
rsp.Conversation = conv.Serialize()
return nil
}

View File

@@ -0,0 +1,60 @@
package handler_test
import (
"context"
"testing"
"github.com/micro/services/threads/handler"
pb "github.com/micro/services/threads/proto"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
func TestCreateConversation(t *testing.T) {
tt := []struct {
Name string
GroupID string
Topic string
Error error
}{
{
Name: "MissingGroupID",
Topic: "HelloWorld",
Error: handler.ErrMissingGroupID,
},
{
Name: "MissingTopic",
GroupID: uuid.New().String(),
Error: handler.ErrMissingTopic,
},
{
Name: "Valid",
GroupID: uuid.New().String(),
Topic: "HelloWorld",
},
}
h := testHandler(t)
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
var rsp pb.CreateConversationResponse
err := h.CreateConversation(context.TODO(), &pb.CreateConversationRequest{
Topic: tc.Topic, GroupId: tc.GroupID,
}, &rsp)
assert.Equal(t, tc.Error, err)
if tc.Error != nil {
assert.Nil(t, rsp.Conversation)
return
}
assertConversationsMatch(t, &pb.Conversation{
CreatedAt: timestamppb.New(h.Time()),
GroupId: tc.GroupID,
Topic: tc.Topic,
}, rsp.Conversation)
})
}
}

View File

@@ -0,0 +1,53 @@
package handler
import (
"context"
"github.com/google/uuid"
"github.com/micro/micro/v3/service/errors"
"github.com/micro/micro/v3/service/logger"
pb "github.com/micro/services/threads/proto"
"gorm.io/gorm"
)
// Create a message within a conversation
func (s *Threads) CreateMessage(ctx context.Context, req *pb.CreateMessageRequest, rsp *pb.CreateMessageResponse) error {
// validate the request
if len(req.AuthorId) == 0 {
return ErrMissingAuthorID
}
if len(req.ConversationId) == 0 {
return ErrMissingConversationID
}
if len(req.Text) == 0 {
return ErrMissingText
}
return s.DB.Transaction(func(tx *gorm.DB) error {
// lookup the conversation
var conv Conversation
if err := tx.Where(&Conversation{ID: req.ConversationId}).First(&conv).Error; err == gorm.ErrRecordNotFound {
return ErrNotFound
} else if err != nil {
logger.Errorf("Error reading conversation: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
}
// create the message
msg := &Message{
ID: uuid.New().String(),
SentAt: s.Time(),
Text: req.Text,
AuthorID: req.AuthorId,
ConversationID: req.ConversationId,
}
if err := tx.Create(msg).Error; err != nil {
logger.Errorf("Error creating message: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
}
// serialize the response
rsp.Message = msg.Serialize()
return nil
})
}

View File

@@ -0,0 +1,89 @@
package handler_test
import (
"context"
"testing"
"github.com/micro/services/threads/handler"
pb "github.com/micro/services/threads/proto"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
func TestCreateMessage(t *testing.T) {
h := testHandler(t)
// seed some data
var cRsp pb.CreateConversationResponse
err := h.CreateConversation(context.TODO(), &pb.CreateConversationRequest{
Topic: "HelloWorld", GroupId: uuid.New().String(),
}, &cRsp)
if err != nil {
t.Fatalf("Error creating conversation: %v", err)
return
}
tt := []struct {
Name string
AuthorID string
ConversationID string
Text string
Error error
}{
{
Name: "MissingConversationID",
Text: "HelloWorld",
AuthorID: uuid.New().String(),
Error: handler.ErrMissingConversationID,
},
{
Name: "MissingAuthorID",
ConversationID: uuid.New().String(),
Text: "HelloWorld",
Error: handler.ErrMissingAuthorID,
},
{
Name: "MissingText",
ConversationID: uuid.New().String(),
AuthorID: uuid.New().String(),
Error: handler.ErrMissingText,
},
{
Name: "ConversationNotFound",
ConversationID: uuid.New().String(),
AuthorID: uuid.New().String(),
Text: "HelloWorld",
Error: handler.ErrNotFound,
},
{
Name: "Valid",
ConversationID: cRsp.Conversation.Id,
AuthorID: uuid.New().String(),
Text: "HelloWorld",
},
}
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
var rsp pb.CreateMessageResponse
err := h.CreateMessage(context.TODO(), &pb.CreateMessageRequest{
Text: tc.Text, ConversationId: tc.ConversationID, AuthorId: tc.AuthorID,
}, &rsp)
assert.Equal(t, tc.Error, err)
if tc.Error != nil {
assert.Nil(t, rsp.Message)
return
}
assertMessagesMatch(t, &pb.Message{
AuthorId: tc.AuthorID,
ConversationId: tc.ConversationID,
SentAt: timestamppb.New(h.Time()),
Text: tc.Text,
}, rsp.Message)
})
}
}

View File

@@ -0,0 +1,34 @@
package handler
import (
"context"
"github.com/micro/micro/v3/service/errors"
"github.com/micro/micro/v3/service/logger"
pb "github.com/micro/services/threads/proto"
"gorm.io/gorm"
)
// Delete a conversation and all the messages within
func (s *Threads) DeleteConversation(ctx context.Context, req *pb.DeleteConversationRequest, rsp *pb.DeleteConversationResponse) error {
// validate the request
if len(req.Id) == 0 {
return ErrMissingID
}
return s.DB.Transaction(func(tx *gorm.DB) error {
// delete all the messages
if err := tx.Where(&Message{ConversationID: req.Id}).Delete(&Message{}).Error; err != nil {
logger.Errorf("Error deleting messages: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
}
// delete the conversation
if err := tx.Where(&Conversation{ID: req.Id}).Delete(&Conversation{}).Error; err != nil {
logger.Errorf("Error deleting conversation: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
}
return nil
})
}

View File

@@ -0,0 +1,50 @@
package handler_test
import (
"context"
"testing"
"github.com/micro/services/threads/handler"
pb "github.com/micro/services/threads/proto"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
func TestDeleteConversation(t *testing.T) {
h := testHandler(t)
// seed some data
var cRsp pb.CreateConversationResponse
err := h.CreateConversation(context.TODO(), &pb.CreateConversationRequest{
Topic: "HelloWorld", GroupId: uuid.New().String(),
}, &cRsp)
if err != nil {
t.Fatalf("Error creating conversation: %v", err)
return
}
t.Run("MissingID", func(t *testing.T) {
err := h.DeleteConversation(context.TODO(), &pb.DeleteConversationRequest{}, &pb.DeleteConversationResponse{})
assert.Equal(t, handler.ErrMissingID, err)
})
t.Run("Valid", func(t *testing.T) {
err := h.DeleteConversation(context.TODO(), &pb.DeleteConversationRequest{
Id: cRsp.Conversation.Id,
}, &pb.DeleteConversationResponse{})
assert.NoError(t, err)
err = h.ReadConversation(context.TODO(), &pb.ReadConversationRequest{
Id: cRsp.Conversation.Id,
}, &pb.ReadConversationResponse{})
assert.Equal(t, handler.ErrNotFound, err)
})
t.Run("Retry", func(t *testing.T) {
err := h.DeleteConversation(context.TODO(), &pb.DeleteConversationRequest{
Id: cRsp.Conversation.Id,
}, &pb.DeleteConversationResponse{})
assert.NoError(t, err)
})
}

View File

@@ -0,0 +1,31 @@
package handler
import (
"context"
"github.com/micro/micro/v3/service/errors"
"github.com/micro/micro/v3/service/logger"
pb "github.com/micro/services/threads/proto"
)
// List all the conversations for a group
func (s *Threads) ListConversations(ctx context.Context, req *pb.ListConversationsRequest, rsp *pb.ListConversationsResponse) error {
// validate the request
if len(req.GroupId) == 0 {
return ErrMissingGroupID
}
// query the database
var convs []Conversation
if err := s.DB.Where(&Conversation{GroupID: req.GroupId}).Find(&convs).Error; err != nil {
logger.Errorf("Error reading conversation: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
}
// serialize the response
rsp.Conversations = make([]*pb.Conversation, len(convs))
for i, c := range convs {
rsp.Conversations[i] = c.Serialize()
}
return nil
}

View File

@@ -0,0 +1,55 @@
package handler_test
import (
"context"
"testing"
"github.com/google/uuid"
"github.com/micro/services/threads/handler"
pb "github.com/micro/services/threads/proto"
"github.com/stretchr/testify/assert"
)
func TestListConversations(t *testing.T) {
h := testHandler(t)
// seed some data
var cRsp1 pb.CreateConversationResponse
err := h.CreateConversation(context.TODO(), &pb.CreateConversationRequest{
Topic: "HelloWorld", GroupId: uuid.New().String(),
}, &cRsp1)
if err != nil {
t.Fatalf("Error creating conversation: %v", err)
return
}
var cRsp2 pb.CreateConversationResponse
err = h.CreateConversation(context.TODO(), &pb.CreateConversationRequest{
Topic: "FooBar", GroupId: uuid.New().String(),
}, &cRsp2)
if err != nil {
t.Fatalf("Error creating conversation: %v", err)
return
}
t.Run("MissingGroupID", func(t *testing.T) {
var rsp pb.ListConversationsResponse
err := h.ListConversations(context.TODO(), &pb.ListConversationsRequest{}, &rsp)
assert.Equal(t, handler.ErrMissingGroupID, err)
assert.Nil(t, rsp.Conversations)
})
t.Run("Valid", func(t *testing.T) {
var rsp pb.ListConversationsResponse
err := h.ListConversations(context.TODO(), &pb.ListConversationsRequest{
GroupId: cRsp1.Conversation.GroupId,
}, &rsp)
assert.NoError(t, err)
if len(rsp.Conversations) != 1 {
t.Fatalf("Expected 1 conversation to be returned, got %v", len(rsp.Conversations))
return
}
assertConversationsMatch(t, cRsp1.Conversation, rsp.Conversations[0])
})
}

View File

@@ -0,0 +1,45 @@
package handler
import (
"context"
"github.com/micro/micro/v3/service/errors"
"github.com/micro/micro/v3/service/logger"
pb "github.com/micro/services/threads/proto"
)
const DefaultLimit = 25
// List the messages within a conversation in reverse chronological order, using sent_before to
// offset as older messages need to be loaded
func (s *Threads) ListMessages(ctx context.Context, req *pb.ListMessagesRequest, rsp *pb.ListMessagesResponse) error {
// validate the request
if len(req.ConversationId) == 0 {
return ErrMissingConversationID
}
// construct the query
q := s.DB.Where(&Message{ConversationID: req.ConversationId}).Order("sent_at DESC")
if req.SentBefore != nil {
q = q.Where("sent_at < ?", req.SentBefore.AsTime())
}
if req.Limit != nil {
q.Limit(int(req.Limit.Value))
} else {
q.Limit(DefaultLimit)
}
// execute the query
var msgs []Message
if err := q.Find(&msgs).Error; err != nil {
logger.Errorf("Error reading messages: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
}
// serialize the response
rsp.Messages = make([]*pb.Message, len(msgs))
for i, m := range msgs {
rsp.Messages[i] = m.Serialize()
}
return nil
}

View File

@@ -0,0 +1,116 @@
package handler_test
import (
"context"
"sort"
"strconv"
"testing"
"time"
"github.com/google/uuid"
"github.com/micro/services/threads/handler"
pb "github.com/micro/services/threads/proto"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/wrapperspb"
)
func TestListMessages(t *testing.T) {
h := testHandler(t)
h.Time = time.Now
// seed some data
var convRsp pb.CreateConversationResponse
err := h.CreateConversation(context.TODO(), &pb.CreateConversationRequest{
Topic: "TestListMessages", GroupId: uuid.New().String(),
}, &convRsp)
assert.NoError(t, err)
if err != nil {
return
}
msgs := make([]*pb.Message, 50)
for i := 0; i < len(msgs); i++ {
var rsp pb.CreateMessageResponse
err := h.CreateMessage(context.TODO(), &pb.CreateMessageRequest{
ConversationId: convRsp.Conversation.Id,
AuthorId: uuid.New().String(),
Text: strconv.Itoa(i),
}, &rsp)
assert.NoError(t, err)
msgs[i] = rsp.Message
}
t.Run("MissingConversationID", func(t *testing.T) {
var rsp pb.ListMessagesResponse
err := h.ListMessages(context.TODO(), &pb.ListMessagesRequest{}, &rsp)
assert.Equal(t, handler.ErrMissingConversationID, err)
assert.Nil(t, rsp.Messages)
})
t.Run("NoOffset", func(t *testing.T) {
var rsp pb.ListMessagesResponse
err := h.ListMessages(context.TODO(), &pb.ListMessagesRequest{
ConversationId: convRsp.Conversation.Id,
}, &rsp)
assert.NoError(t, err)
if len(rsp.Messages) != handler.DefaultLimit {
t.Fatalf("Expected %v messages but got %v", handler.DefaultLimit, len(rsp.Messages))
return
}
expected := msgs[25:]
sortMessages(rsp.Messages)
for i, msg := range rsp.Messages {
assertMessagesMatch(t, expected[i], msg)
}
})
t.Run("LimitSet", func(t *testing.T) {
var rsp pb.ListMessagesResponse
err := h.ListMessages(context.TODO(), &pb.ListMessagesRequest{
ConversationId: convRsp.Conversation.Id,
Limit: &wrapperspb.Int32Value{Value: 10},
}, &rsp)
assert.NoError(t, err)
if len(rsp.Messages) != 10 {
t.Fatalf("Expected %v messages but got %v", 10, len(rsp.Messages))
return
}
expected := msgs[40:]
sortMessages(rsp.Messages)
for i, msg := range rsp.Messages {
assertMessagesMatch(t, expected[i], msg)
}
})
t.Run("OffsetAndLimit", func(t *testing.T) {
var rsp pb.ListMessagesResponse
err := h.ListMessages(context.TODO(), &pb.ListMessagesRequest{
ConversationId: convRsp.Conversation.Id,
Limit: &wrapperspb.Int32Value{Value: 5},
SentBefore: msgs[20].SentAt,
}, &rsp)
assert.NoError(t, err)
if len(rsp.Messages) != 5 {
t.Fatalf("Expected %v messages but got %v", 5, len(rsp.Messages))
return
}
expected := msgs[15:20]
sortMessages(rsp.Messages)
for i, msg := range rsp.Messages {
assertMessagesMatch(t, expected[i], msg)
}
})
}
// sortMessages by the time they were sent
func sortMessages(msgs []*pb.Message) {
sort.Slice(msgs, func(i, j int) bool {
if msgs[i].SentAt == nil || msgs[j].SentAt == nil {
return true
}
return msgs[i].SentAt.AsTime().Before(msgs[j].SentAt.AsTime())
})
}

View File

@@ -0,0 +1,38 @@
package handler
import (
"context"
"gorm.io/gorm"
"github.com/micro/micro/v3/service/errors"
"github.com/micro/micro/v3/service/logger"
pb "github.com/micro/services/threads/proto"
)
// Read a conversation using its ID, can filter using group ID if provided
func (s *Threads) ReadConversation(ctx context.Context, req *pb.ReadConversationRequest, rsp *pb.ReadConversationResponse) error {
// validate the request
if len(req.Id) == 0 {
return ErrMissingID
}
// construct the query
q := Conversation{ID: req.Id}
if req.GroupId != nil {
q.GroupID = req.GroupId.Value
}
// execute the query
var conv Conversation
if err := s.DB.Where(&q).First(&conv).Error; err == gorm.ErrRecordNotFound {
return ErrNotFound
} else if err != nil {
logger.Errorf("Error reading conversation: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
}
// serialize the response
rsp.Conversation = conv.Serialize()
return nil
}

View File

@@ -0,0 +1,70 @@
package handler_test
import (
"context"
"testing"
"github.com/google/uuid"
"github.com/micro/services/threads/handler"
pb "github.com/micro/services/threads/proto"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/wrapperspb"
)
func TestReadConversation(t *testing.T) {
h := testHandler(t)
// seed some data
var cRsp pb.CreateConversationResponse
err := h.CreateConversation(context.TODO(), &pb.CreateConversationRequest{
Topic: "HelloWorld", GroupId: uuid.New().String(),
}, &cRsp)
if err != nil {
t.Fatalf("Error creating conversation: %v", err)
return
}
tt := []struct {
Name string
ID string
GroupID *wrapperspb.StringValue
Error error
Result *pb.Conversation
}{
{
Name: "MissingID",
Error: handler.ErrMissingID,
},
{
Name: "IncorrectID",
ID: uuid.New().String(),
Error: handler.ErrNotFound,
},
{
Name: "FoundUsingIDOnly",
ID: cRsp.Conversation.Id,
Result: cRsp.Conversation,
},
{
Name: "IncorrectGroupID",
ID: cRsp.Conversation.Id,
Error: handler.ErrNotFound,
GroupID: &wrapperspb.StringValue{Value: uuid.New().String()},
},
}
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
var rsp pb.ReadConversationResponse
err := h.ReadConversation(context.TODO(), &pb.ReadConversationRequest{
Id: tc.ID, GroupId: tc.GroupID,
}, &rsp)
assert.Equal(t, tc.Error, err)
if tc.Result == nil {
assert.Nil(t, rsp.Conversation)
} else {
assertConversationsMatch(t, tc.Result, rsp.Conversation)
}
})
}
}

View File

@@ -0,0 +1,49 @@
package handler
import (
"context"
"github.com/micro/micro/v3/service/errors"
"github.com/micro/micro/v3/service/logger"
pb "github.com/micro/services/threads/proto"
"gorm.io/gorm"
)
// RecentMessages returns the most recent messages in a group of conversations. By default the
// most messages retrieved per conversation is 25, however this can be overriden using the
// limit_per_conversation option
func (s *Threads) RecentMessages(ctx context.Context, req *pb.RecentMessagesRequest, rsp *pb.RecentMessagesResponse) error {
// validate the request
if len(req.ConversationIds) == 0 {
return ErrMissingConversationIDs
}
limit := DefaultLimit
if req.LimitPerConversation != nil {
limit = int(req.LimitPerConversation.Value)
}
// query the database
var msgs []Message
err := s.DB.Transaction(func(tx *gorm.DB) error {
for _, id := range req.ConversationIds {
var cms []Message
if err := tx.Where(&Message{ConversationID: id}).Order("sent_at DESC").Limit(limit).Find(&cms).Error; err != nil {
return err
}
msgs = append(msgs, cms...)
}
return nil
})
if err != nil {
logger.Errorf("Error reading messages: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
}
// serialize the response
rsp.Messages = make([]*pb.Message, len(msgs))
for i, m := range msgs {
rsp.Messages[i] = m.Serialize()
}
return nil
}

View File

@@ -0,0 +1,101 @@
package handler_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/google/uuid"
"github.com/micro/services/threads/handler"
pb "github.com/micro/services/threads/proto"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/wrapperspb"
)
func TestRecentMessages(t *testing.T) {
h := testHandler(t)
h.Time = time.Now
// seed some data
ids := make([]string, 3)
convos := make(map[string][]*pb.Message, 3)
for i := 0; i < 3; i++ {
var convRsp pb.CreateConversationResponse
err := h.CreateConversation(context.TODO(), &pb.CreateConversationRequest{
Topic: "TestRecentMessages", GroupId: uuid.New().String(),
}, &convRsp)
assert.NoError(t, err)
if err != nil {
return
}
convos[convRsp.Conversation.Id] = make([]*pb.Message, 50)
ids[i] = convRsp.Conversation.Id
for j := 0; j < 50; j++ {
var rsp pb.CreateMessageResponse
err := h.CreateMessage(context.TODO(), &pb.CreateMessageRequest{
ConversationId: convRsp.Conversation.Id,
AuthorId: uuid.New().String(),
Text: fmt.Sprintf("Conversation %v, Message %v", i, j),
}, &rsp)
assert.NoError(t, err)
convos[convRsp.Conversation.Id][j] = rsp.Message
}
}
t.Run("MissingConversationIDs", func(t *testing.T) {
var rsp pb.RecentMessagesResponse
err := h.RecentMessages(context.TODO(), &pb.RecentMessagesRequest{}, &rsp)
assert.Equal(t, handler.ErrMissingConversationIDs, err)
assert.Nil(t, rsp.Messages)
})
t.Run("LimitSet", func(t *testing.T) {
var rsp pb.RecentMessagesResponse
err := h.RecentMessages(context.TODO(), &pb.RecentMessagesRequest{
ConversationIds: ids,
LimitPerConversation: &wrapperspb.Int32Value{Value: 10},
}, &rsp)
assert.NoError(t, err)
if len(rsp.Messages) != 30 {
t.Fatalf("Expected %v messages but got %v", 30, len(rsp.Messages))
return
}
var expected []*pb.Message
for _, msgs := range convos {
expected = append(expected, msgs[40:]...)
}
sortMessages(expected)
sortMessages(rsp.Messages)
for i, msg := range rsp.Messages {
assertMessagesMatch(t, expected[i], msg)
}
})
t.Run("NoLimitSet", func(t *testing.T) {
reducedIDs := ids[:2]
var rsp pb.RecentMessagesResponse
err := h.RecentMessages(context.TODO(), &pb.RecentMessagesRequest{
ConversationIds: reducedIDs,
}, &rsp)
assert.NoError(t, err)
if len(rsp.Messages) != 50 {
t.Fatalf("Expected %v messages but got %v", 50, len(rsp.Messages))
return
}
var expected []*pb.Message
for _, id := range reducedIDs {
expected = append(expected, convos[id][25:]...)
}
sortMessages(expected)
sortMessages(rsp.Messages)
for i, msg := range rsp.Messages {
assertMessagesMatch(t, expected[i], msg)
}
})
}

View File

@@ -0,0 +1,60 @@
package handler
import (
"time"
"github.com/micro/micro/v3/service/errors"
pb "github.com/micro/services/threads/proto"
"google.golang.org/protobuf/types/known/timestamppb"
"gorm.io/gorm"
)
var (
ErrMissingID = errors.BadRequest("MISSING_ID", "Missing ID")
ErrMissingGroupID = errors.BadRequest("MISSING_GROUP_ID", "Missing GroupID")
ErrMissingTopic = errors.BadRequest("MISSING_TOPIC", "Missing Topic")
ErrMissingAuthorID = errors.BadRequest("MISSING_AUTHOR_ID", "Missing Author ID")
ErrMissingText = errors.BadRequest("MISSING_TEXT", "Missing text")
ErrMissingConversationID = errors.BadRequest("MISSING_CONVERSATION_ID", "Missing Conversation ID")
ErrMissingConversationIDs = errors.BadRequest("MISSING_CONVERSATION_IDS", "One or more Conversation IDs are required")
ErrNotFound = errors.NotFound("NOT_FOUND", "Conversation not found")
)
type Threads struct {
DB *gorm.DB
Time func() time.Time
}
type Message struct {
ID string
AuthorID string
ConversationID string
Text string
SentAt time.Time
}
func (m *Message) Serialize() *pb.Message {
return &pb.Message{
Id: m.ID,
AuthorId: m.AuthorID,
ConversationId: m.ConversationID,
Text: m.Text,
SentAt: timestamppb.New(m.SentAt),
}
}
type Conversation struct {
ID string
GroupID string
Topic string
CreatedAt time.Time
}
func (c *Conversation) Serialize() *pb.Conversation {
return &pb.Conversation{
Id: c.ID,
GroupId: c.GroupID,
Topic: c.Topic,
CreatedAt: timestamppb.New(c.CreatedAt),
}
}

View File

@@ -0,0 +1,84 @@
package handler_test
import (
"testing"
"time"
"github.com/micro/services/threads/handler"
pb "github.com/micro/services/threads/proto"
"github.com/stretchr/testify/assert"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func testHandler(t *testing.T) *handler.Threads {
// connect to the database
db, err := gorm.Open(postgres.Open("postgresql://postgres@localhost:5432/threads?sslmode=disable"), &gorm.Config{})
if err != nil {
t.Fatalf("Error connecting to database: %v", err)
}
// migrate the database
if err := db.AutoMigrate(&handler.Conversation{}, &handler.Message{}); err != nil {
t.Fatalf("Error migrating database: %v", err)
}
// clean any data from a previous run
if err := db.Exec("TRUNCATE TABLE conversations, messages CASCADE").Error; err != nil {
t.Fatalf("Error cleaning database: %v", err)
}
return &handler.Threads{DB: db, Time: func() time.Time { return time.Unix(1611327673, 0) }}
}
func assertConversationsMatch(t *testing.T, exp, act *pb.Conversation) {
if act == nil {
t.Errorf("Conversation not returned")
return
}
// adapt this check so we can reuse the func in testing create, where we don't know the exact id
// which will be generated
if len(exp.Id) > 0 {
assert.Equal(t, exp.Id, act.Id)
} else {
assert.NotEmpty(t, act.Id)
}
assert.Equal(t, exp.Topic, act.Topic)
assert.Equal(t, exp.GroupId, act.GroupId)
if act.CreatedAt == nil {
t.Errorf("CreatedAt not set")
return
}
assert.True(t, exp.CreatedAt.AsTime().Equal(act.CreatedAt.AsTime()))
}
func assertMessagesMatch(t *testing.T, exp, act *pb.Message) {
if act == nil {
t.Errorf("Message not returned")
return
}
// adapt this check so we can reuse the func in testing create, where we don't know the exact id
// which will be generated
if len(exp.Id) > 0 {
assert.Equal(t, exp.Id, act.Id)
} else {
assert.NotEmpty(t, act.Id)
}
assert.Equal(t, exp.Text, act.Text)
assert.Equal(t, exp.AuthorId, act.AuthorId)
assert.Equal(t, exp.ConversationId, act.ConversationId)
if act.SentAt == nil {
t.Errorf("SentAt not set")
return
}
assert.True(t, exp.SentAt.AsTime().Equal(act.SentAt.AsTime()))
}

View File

@@ -0,0 +1,41 @@
package handler
import (
"context"
"github.com/micro/micro/v3/service/errors"
"github.com/micro/micro/v3/service/logger"
pb "github.com/micro/services/threads/proto"
"gorm.io/gorm"
)
// Update a conversations topic
func (s *Threads) UpdateConversation(ctx context.Context, req *pb.UpdateConversationRequest, rsp *pb.UpdateConversationResponse) error {
// validate the request
if len(req.Id) == 0 {
return ErrMissingID
}
if len(req.Topic) == 0 {
return ErrMissingTopic
}
// lookup the conversation
var conv Conversation
if err := s.DB.Where(&Conversation{ID: req.Id}).First(&conv).Error; err == gorm.ErrRecordNotFound {
return ErrNotFound
} else if err != nil {
logger.Errorf("Error reading conversation: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
}
// update the conversation
conv.Topic = req.Topic
if err := s.DB.Save(&conv).Error; err != nil {
logger.Errorf("Error updating conversation: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
}
// serialize the result
rsp.Conversation = conv.Serialize()
return nil
}

View File

@@ -0,0 +1,66 @@
package handler_test
import (
"context"
"testing"
"github.com/google/uuid"
"github.com/micro/services/threads/handler"
pb "github.com/micro/services/threads/proto"
"github.com/stretchr/testify/assert"
)
func TestUpdateConversation(t *testing.T) {
h := testHandler(t)
// seed some data
var cRsp pb.CreateConversationResponse
err := h.CreateConversation(context.TODO(), &pb.CreateConversationRequest{
Topic: "HelloWorld", GroupId: uuid.New().String(),
}, &cRsp)
if err != nil {
t.Fatalf("Error creating conversation: %v", err)
return
}
t.Run("MissingID", func(t *testing.T) {
err := h.UpdateConversation(context.TODO(), &pb.UpdateConversationRequest{
Topic: "NewTopic",
}, &pb.UpdateConversationResponse{})
assert.Equal(t, handler.ErrMissingID, err)
})
t.Run("MissingTopic", func(t *testing.T) {
err := h.UpdateConversation(context.TODO(), &pb.UpdateConversationRequest{
Id: uuid.New().String(),
}, &pb.UpdateConversationResponse{})
assert.Equal(t, handler.ErrMissingTopic, err)
})
t.Run("InvalidID", func(t *testing.T) {
err := h.UpdateConversation(context.TODO(), &pb.UpdateConversationRequest{
Id: uuid.New().String(),
Topic: "NewTopic",
}, &pb.UpdateConversationResponse{})
assert.Equal(t, handler.ErrNotFound, err)
})
t.Run("Valid", func(t *testing.T) {
err := h.UpdateConversation(context.TODO(), &pb.UpdateConversationRequest{
Id: cRsp.Conversation.Id,
Topic: "NewTopic",
}, &pb.UpdateConversationResponse{})
assert.NoError(t, err)
var rsp pb.ReadConversationResponse
err = h.ReadConversation(context.TODO(), &pb.ReadConversationRequest{
Id: cRsp.Conversation.Id,
}, &rsp)
assert.NoError(t, err)
if rsp.Conversation == nil {
t.Fatal("No conversation returned")
return
}
assert.Equal(t, "NewTopic", rsp.Conversation.Topic)
})
}

46
threads/main.go Normal file
View File

@@ -0,0 +1,46 @@
package main
import (
"time"
"github.com/micro/services/threads/handler"
pb "github.com/micro/services/threads/proto"
"github.com/micro/micro/v3/service"
"github.com/micro/micro/v3/service/config"
"github.com/micro/micro/v3/service/logger"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var dbAddress = "postgresql://postgres@localhost:5432/threads?sslmode=disable"
func main() {
// Create service
srv := service.New(
service.Name("threads"),
service.Version("latest"),
)
// Connect to the database
cfg, err := config.Get("threads.database")
if err != nil {
logger.Fatalf("Error loading config: %v", err)
}
addr := cfg.String(dbAddress)
db, err := gorm.Open(postgres.Open(addr), &gorm.Config{})
if err != nil {
logger.Fatalf("Error connecting to database: %v", err)
}
if err := db.AutoMigrate(&handler.Conversation{}, &handler.Message{}); err != nil {
logger.Fatalf("Error migrating database: %v", err)
}
// Register handler
pb.RegisterThreadsHandler(srv.Server(), &handler.Threads{DB: db, Time: time.Now})
// Run service
if err := srv.Run(); err != nil {
logger.Fatal(err)
}
}

1
threads/micro.mu Normal file
View File

@@ -0,0 +1 @@
service threads

911
threads/proto/threads.pb.go Normal file
View File

@@ -0,0 +1,911 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: proto/threads.proto
package threads
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
timestamp "github.com/golang/protobuf/ptypes/timestamp"
wrappers "github.com/golang/protobuf/ptypes/wrappers"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Conversation struct {
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
GroupId string `protobuf:"bytes,2,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"`
Topic string `protobuf:"bytes,3,opt,name=topic,proto3" json:"topic,omitempty"`
CreatedAt *timestamp.Timestamp `protobuf:"bytes,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Conversation) Reset() { *m = Conversation{} }
func (m *Conversation) String() string { return proto.CompactTextString(m) }
func (*Conversation) ProtoMessage() {}
func (*Conversation) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{0}
}
func (m *Conversation) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Conversation.Unmarshal(m, b)
}
func (m *Conversation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Conversation.Marshal(b, m, deterministic)
}
func (m *Conversation) XXX_Merge(src proto.Message) {
xxx_messageInfo_Conversation.Merge(m, src)
}
func (m *Conversation) XXX_Size() int {
return xxx_messageInfo_Conversation.Size(m)
}
func (m *Conversation) XXX_DiscardUnknown() {
xxx_messageInfo_Conversation.DiscardUnknown(m)
}
var xxx_messageInfo_Conversation proto.InternalMessageInfo
func (m *Conversation) GetId() string {
if m != nil {
return m.Id
}
return ""
}
func (m *Conversation) GetGroupId() string {
if m != nil {
return m.GroupId
}
return ""
}
func (m *Conversation) GetTopic() string {
if m != nil {
return m.Topic
}
return ""
}
func (m *Conversation) GetCreatedAt() *timestamp.Timestamp {
if m != nil {
return m.CreatedAt
}
return nil
}
type Message struct {
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
AuthorId string `protobuf:"bytes,2,opt,name=author_id,json=authorId,proto3" json:"author_id,omitempty"`
ConversationId string `protobuf:"bytes,3,opt,name=conversation_id,json=conversationId,proto3" json:"conversation_id,omitempty"`
Text string `protobuf:"bytes,4,opt,name=text,proto3" json:"text,omitempty"`
SentAt *timestamp.Timestamp `protobuf:"bytes,5,opt,name=sent_at,json=sentAt,proto3" json:"sent_at,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Message) Reset() { *m = Message{} }
func (m *Message) String() string { return proto.CompactTextString(m) }
func (*Message) ProtoMessage() {}
func (*Message) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{1}
}
func (m *Message) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Message.Unmarshal(m, b)
}
func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Message.Marshal(b, m, deterministic)
}
func (m *Message) XXX_Merge(src proto.Message) {
xxx_messageInfo_Message.Merge(m, src)
}
func (m *Message) XXX_Size() int {
return xxx_messageInfo_Message.Size(m)
}
func (m *Message) XXX_DiscardUnknown() {
xxx_messageInfo_Message.DiscardUnknown(m)
}
var xxx_messageInfo_Message proto.InternalMessageInfo
func (m *Message) GetId() string {
if m != nil {
return m.Id
}
return ""
}
func (m *Message) GetAuthorId() string {
if m != nil {
return m.AuthorId
}
return ""
}
func (m *Message) GetConversationId() string {
if m != nil {
return m.ConversationId
}
return ""
}
func (m *Message) GetText() string {
if m != nil {
return m.Text
}
return ""
}
func (m *Message) GetSentAt() *timestamp.Timestamp {
if m != nil {
return m.SentAt
}
return nil
}
type CreateConversationRequest struct {
GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"`
Topic string `protobuf:"bytes,2,opt,name=topic,proto3" json:"topic,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CreateConversationRequest) Reset() { *m = CreateConversationRequest{} }
func (m *CreateConversationRequest) String() string { return proto.CompactTextString(m) }
func (*CreateConversationRequest) ProtoMessage() {}
func (*CreateConversationRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{2}
}
func (m *CreateConversationRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CreateConversationRequest.Unmarshal(m, b)
}
func (m *CreateConversationRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CreateConversationRequest.Marshal(b, m, deterministic)
}
func (m *CreateConversationRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_CreateConversationRequest.Merge(m, src)
}
func (m *CreateConversationRequest) XXX_Size() int {
return xxx_messageInfo_CreateConversationRequest.Size(m)
}
func (m *CreateConversationRequest) XXX_DiscardUnknown() {
xxx_messageInfo_CreateConversationRequest.DiscardUnknown(m)
}
var xxx_messageInfo_CreateConversationRequest proto.InternalMessageInfo
func (m *CreateConversationRequest) GetGroupId() string {
if m != nil {
return m.GroupId
}
return ""
}
func (m *CreateConversationRequest) GetTopic() string {
if m != nil {
return m.Topic
}
return ""
}
type CreateConversationResponse struct {
Conversation *Conversation `protobuf:"bytes,1,opt,name=conversation,proto3" json:"conversation,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CreateConversationResponse) Reset() { *m = CreateConversationResponse{} }
func (m *CreateConversationResponse) String() string { return proto.CompactTextString(m) }
func (*CreateConversationResponse) ProtoMessage() {}
func (*CreateConversationResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{3}
}
func (m *CreateConversationResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CreateConversationResponse.Unmarshal(m, b)
}
func (m *CreateConversationResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CreateConversationResponse.Marshal(b, m, deterministic)
}
func (m *CreateConversationResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_CreateConversationResponse.Merge(m, src)
}
func (m *CreateConversationResponse) XXX_Size() int {
return xxx_messageInfo_CreateConversationResponse.Size(m)
}
func (m *CreateConversationResponse) XXX_DiscardUnknown() {
xxx_messageInfo_CreateConversationResponse.DiscardUnknown(m)
}
var xxx_messageInfo_CreateConversationResponse proto.InternalMessageInfo
func (m *CreateConversationResponse) GetConversation() *Conversation {
if m != nil {
return m.Conversation
}
return nil
}
type ReadConversationRequest struct {
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
GroupId *wrappers.StringValue `protobuf:"bytes,2,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ReadConversationRequest) Reset() { *m = ReadConversationRequest{} }
func (m *ReadConversationRequest) String() string { return proto.CompactTextString(m) }
func (*ReadConversationRequest) ProtoMessage() {}
func (*ReadConversationRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{4}
}
func (m *ReadConversationRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ReadConversationRequest.Unmarshal(m, b)
}
func (m *ReadConversationRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ReadConversationRequest.Marshal(b, m, deterministic)
}
func (m *ReadConversationRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ReadConversationRequest.Merge(m, src)
}
func (m *ReadConversationRequest) XXX_Size() int {
return xxx_messageInfo_ReadConversationRequest.Size(m)
}
func (m *ReadConversationRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ReadConversationRequest.DiscardUnknown(m)
}
var xxx_messageInfo_ReadConversationRequest proto.InternalMessageInfo
func (m *ReadConversationRequest) GetId() string {
if m != nil {
return m.Id
}
return ""
}
func (m *ReadConversationRequest) GetGroupId() *wrappers.StringValue {
if m != nil {
return m.GroupId
}
return nil
}
type ReadConversationResponse struct {
Conversation *Conversation `protobuf:"bytes,1,opt,name=conversation,proto3" json:"conversation,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ReadConversationResponse) Reset() { *m = ReadConversationResponse{} }
func (m *ReadConversationResponse) String() string { return proto.CompactTextString(m) }
func (*ReadConversationResponse) ProtoMessage() {}
func (*ReadConversationResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{5}
}
func (m *ReadConversationResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ReadConversationResponse.Unmarshal(m, b)
}
func (m *ReadConversationResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ReadConversationResponse.Marshal(b, m, deterministic)
}
func (m *ReadConversationResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_ReadConversationResponse.Merge(m, src)
}
func (m *ReadConversationResponse) XXX_Size() int {
return xxx_messageInfo_ReadConversationResponse.Size(m)
}
func (m *ReadConversationResponse) XXX_DiscardUnknown() {
xxx_messageInfo_ReadConversationResponse.DiscardUnknown(m)
}
var xxx_messageInfo_ReadConversationResponse proto.InternalMessageInfo
func (m *ReadConversationResponse) GetConversation() *Conversation {
if m != nil {
return m.Conversation
}
return nil
}
type ListConversationsRequest struct {
GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ListConversationsRequest) Reset() { *m = ListConversationsRequest{} }
func (m *ListConversationsRequest) String() string { return proto.CompactTextString(m) }
func (*ListConversationsRequest) ProtoMessage() {}
func (*ListConversationsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{6}
}
func (m *ListConversationsRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ListConversationsRequest.Unmarshal(m, b)
}
func (m *ListConversationsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ListConversationsRequest.Marshal(b, m, deterministic)
}
func (m *ListConversationsRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ListConversationsRequest.Merge(m, src)
}
func (m *ListConversationsRequest) XXX_Size() int {
return xxx_messageInfo_ListConversationsRequest.Size(m)
}
func (m *ListConversationsRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ListConversationsRequest.DiscardUnknown(m)
}
var xxx_messageInfo_ListConversationsRequest proto.InternalMessageInfo
func (m *ListConversationsRequest) GetGroupId() string {
if m != nil {
return m.GroupId
}
return ""
}
type ListConversationsResponse struct {
Conversations []*Conversation `protobuf:"bytes,1,rep,name=conversations,proto3" json:"conversations,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ListConversationsResponse) Reset() { *m = ListConversationsResponse{} }
func (m *ListConversationsResponse) String() string { return proto.CompactTextString(m) }
func (*ListConversationsResponse) ProtoMessage() {}
func (*ListConversationsResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{7}
}
func (m *ListConversationsResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ListConversationsResponse.Unmarshal(m, b)
}
func (m *ListConversationsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ListConversationsResponse.Marshal(b, m, deterministic)
}
func (m *ListConversationsResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_ListConversationsResponse.Merge(m, src)
}
func (m *ListConversationsResponse) XXX_Size() int {
return xxx_messageInfo_ListConversationsResponse.Size(m)
}
func (m *ListConversationsResponse) XXX_DiscardUnknown() {
xxx_messageInfo_ListConversationsResponse.DiscardUnknown(m)
}
var xxx_messageInfo_ListConversationsResponse proto.InternalMessageInfo
func (m *ListConversationsResponse) GetConversations() []*Conversation {
if m != nil {
return m.Conversations
}
return nil
}
type UpdateConversationRequest struct {
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Topic string `protobuf:"bytes,2,opt,name=topic,proto3" json:"topic,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *UpdateConversationRequest) Reset() { *m = UpdateConversationRequest{} }
func (m *UpdateConversationRequest) String() string { return proto.CompactTextString(m) }
func (*UpdateConversationRequest) ProtoMessage() {}
func (*UpdateConversationRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{8}
}
func (m *UpdateConversationRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_UpdateConversationRequest.Unmarshal(m, b)
}
func (m *UpdateConversationRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_UpdateConversationRequest.Marshal(b, m, deterministic)
}
func (m *UpdateConversationRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_UpdateConversationRequest.Merge(m, src)
}
func (m *UpdateConversationRequest) XXX_Size() int {
return xxx_messageInfo_UpdateConversationRequest.Size(m)
}
func (m *UpdateConversationRequest) XXX_DiscardUnknown() {
xxx_messageInfo_UpdateConversationRequest.DiscardUnknown(m)
}
var xxx_messageInfo_UpdateConversationRequest proto.InternalMessageInfo
func (m *UpdateConversationRequest) GetId() string {
if m != nil {
return m.Id
}
return ""
}
func (m *UpdateConversationRequest) GetTopic() string {
if m != nil {
return m.Topic
}
return ""
}
type UpdateConversationResponse struct {
Conversation *Conversation `protobuf:"bytes,1,opt,name=conversation,proto3" json:"conversation,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *UpdateConversationResponse) Reset() { *m = UpdateConversationResponse{} }
func (m *UpdateConversationResponse) String() string { return proto.CompactTextString(m) }
func (*UpdateConversationResponse) ProtoMessage() {}
func (*UpdateConversationResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{9}
}
func (m *UpdateConversationResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_UpdateConversationResponse.Unmarshal(m, b)
}
func (m *UpdateConversationResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_UpdateConversationResponse.Marshal(b, m, deterministic)
}
func (m *UpdateConversationResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_UpdateConversationResponse.Merge(m, src)
}
func (m *UpdateConversationResponse) XXX_Size() int {
return xxx_messageInfo_UpdateConversationResponse.Size(m)
}
func (m *UpdateConversationResponse) XXX_DiscardUnknown() {
xxx_messageInfo_UpdateConversationResponse.DiscardUnknown(m)
}
var xxx_messageInfo_UpdateConversationResponse proto.InternalMessageInfo
func (m *UpdateConversationResponse) GetConversation() *Conversation {
if m != nil {
return m.Conversation
}
return nil
}
type DeleteConversationRequest struct {
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *DeleteConversationRequest) Reset() { *m = DeleteConversationRequest{} }
func (m *DeleteConversationRequest) String() string { return proto.CompactTextString(m) }
func (*DeleteConversationRequest) ProtoMessage() {}
func (*DeleteConversationRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{10}
}
func (m *DeleteConversationRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DeleteConversationRequest.Unmarshal(m, b)
}
func (m *DeleteConversationRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_DeleteConversationRequest.Marshal(b, m, deterministic)
}
func (m *DeleteConversationRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_DeleteConversationRequest.Merge(m, src)
}
func (m *DeleteConversationRequest) XXX_Size() int {
return xxx_messageInfo_DeleteConversationRequest.Size(m)
}
func (m *DeleteConversationRequest) XXX_DiscardUnknown() {
xxx_messageInfo_DeleteConversationRequest.DiscardUnknown(m)
}
var xxx_messageInfo_DeleteConversationRequest proto.InternalMessageInfo
func (m *DeleteConversationRequest) GetId() string {
if m != nil {
return m.Id
}
return ""
}
type DeleteConversationResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *DeleteConversationResponse) Reset() { *m = DeleteConversationResponse{} }
func (m *DeleteConversationResponse) String() string { return proto.CompactTextString(m) }
func (*DeleteConversationResponse) ProtoMessage() {}
func (*DeleteConversationResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{11}
}
func (m *DeleteConversationResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DeleteConversationResponse.Unmarshal(m, b)
}
func (m *DeleteConversationResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_DeleteConversationResponse.Marshal(b, m, deterministic)
}
func (m *DeleteConversationResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_DeleteConversationResponse.Merge(m, src)
}
func (m *DeleteConversationResponse) XXX_Size() int {
return xxx_messageInfo_DeleteConversationResponse.Size(m)
}
func (m *DeleteConversationResponse) XXX_DiscardUnknown() {
xxx_messageInfo_DeleteConversationResponse.DiscardUnknown(m)
}
var xxx_messageInfo_DeleteConversationResponse proto.InternalMessageInfo
type CreateMessageRequest struct {
ConversationId string `protobuf:"bytes,1,opt,name=conversation_id,json=conversationId,proto3" json:"conversation_id,omitempty"`
AuthorId string `protobuf:"bytes,2,opt,name=author_id,json=authorId,proto3" json:"author_id,omitempty"`
Text string `protobuf:"bytes,3,opt,name=text,proto3" json:"text,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CreateMessageRequest) Reset() { *m = CreateMessageRequest{} }
func (m *CreateMessageRequest) String() string { return proto.CompactTextString(m) }
func (*CreateMessageRequest) ProtoMessage() {}
func (*CreateMessageRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{12}
}
func (m *CreateMessageRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CreateMessageRequest.Unmarshal(m, b)
}
func (m *CreateMessageRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CreateMessageRequest.Marshal(b, m, deterministic)
}
func (m *CreateMessageRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_CreateMessageRequest.Merge(m, src)
}
func (m *CreateMessageRequest) XXX_Size() int {
return xxx_messageInfo_CreateMessageRequest.Size(m)
}
func (m *CreateMessageRequest) XXX_DiscardUnknown() {
xxx_messageInfo_CreateMessageRequest.DiscardUnknown(m)
}
var xxx_messageInfo_CreateMessageRequest proto.InternalMessageInfo
func (m *CreateMessageRequest) GetConversationId() string {
if m != nil {
return m.ConversationId
}
return ""
}
func (m *CreateMessageRequest) GetAuthorId() string {
if m != nil {
return m.AuthorId
}
return ""
}
func (m *CreateMessageRequest) GetText() string {
if m != nil {
return m.Text
}
return ""
}
type CreateMessageResponse struct {
Message *Message `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CreateMessageResponse) Reset() { *m = CreateMessageResponse{} }
func (m *CreateMessageResponse) String() string { return proto.CompactTextString(m) }
func (*CreateMessageResponse) ProtoMessage() {}
func (*CreateMessageResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{13}
}
func (m *CreateMessageResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CreateMessageResponse.Unmarshal(m, b)
}
func (m *CreateMessageResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CreateMessageResponse.Marshal(b, m, deterministic)
}
func (m *CreateMessageResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_CreateMessageResponse.Merge(m, src)
}
func (m *CreateMessageResponse) XXX_Size() int {
return xxx_messageInfo_CreateMessageResponse.Size(m)
}
func (m *CreateMessageResponse) XXX_DiscardUnknown() {
xxx_messageInfo_CreateMessageResponse.DiscardUnknown(m)
}
var xxx_messageInfo_CreateMessageResponse proto.InternalMessageInfo
func (m *CreateMessageResponse) GetMessage() *Message {
if m != nil {
return m.Message
}
return nil
}
type ListMessagesRequest struct {
ConversationId string `protobuf:"bytes,1,opt,name=conversation_id,json=conversationId,proto3" json:"conversation_id,omitempty"`
SentBefore *timestamp.Timestamp `protobuf:"bytes,2,opt,name=sent_before,json=sentBefore,proto3" json:"sent_before,omitempty"`
Limit *wrappers.Int32Value `protobuf:"bytes,3,opt,name=limit,proto3" json:"limit,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ListMessagesRequest) Reset() { *m = ListMessagesRequest{} }
func (m *ListMessagesRequest) String() string { return proto.CompactTextString(m) }
func (*ListMessagesRequest) ProtoMessage() {}
func (*ListMessagesRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{14}
}
func (m *ListMessagesRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ListMessagesRequest.Unmarshal(m, b)
}
func (m *ListMessagesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ListMessagesRequest.Marshal(b, m, deterministic)
}
func (m *ListMessagesRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ListMessagesRequest.Merge(m, src)
}
func (m *ListMessagesRequest) XXX_Size() int {
return xxx_messageInfo_ListMessagesRequest.Size(m)
}
func (m *ListMessagesRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ListMessagesRequest.DiscardUnknown(m)
}
var xxx_messageInfo_ListMessagesRequest proto.InternalMessageInfo
func (m *ListMessagesRequest) GetConversationId() string {
if m != nil {
return m.ConversationId
}
return ""
}
func (m *ListMessagesRequest) GetSentBefore() *timestamp.Timestamp {
if m != nil {
return m.SentBefore
}
return nil
}
func (m *ListMessagesRequest) GetLimit() *wrappers.Int32Value {
if m != nil {
return m.Limit
}
return nil
}
type ListMessagesResponse struct {
Messages []*Message `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ListMessagesResponse) Reset() { *m = ListMessagesResponse{} }
func (m *ListMessagesResponse) String() string { return proto.CompactTextString(m) }
func (*ListMessagesResponse) ProtoMessage() {}
func (*ListMessagesResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{15}
}
func (m *ListMessagesResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ListMessagesResponse.Unmarshal(m, b)
}
func (m *ListMessagesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ListMessagesResponse.Marshal(b, m, deterministic)
}
func (m *ListMessagesResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_ListMessagesResponse.Merge(m, src)
}
func (m *ListMessagesResponse) XXX_Size() int {
return xxx_messageInfo_ListMessagesResponse.Size(m)
}
func (m *ListMessagesResponse) XXX_DiscardUnknown() {
xxx_messageInfo_ListMessagesResponse.DiscardUnknown(m)
}
var xxx_messageInfo_ListMessagesResponse proto.InternalMessageInfo
func (m *ListMessagesResponse) GetMessages() []*Message {
if m != nil {
return m.Messages
}
return nil
}
type RecentMessagesRequest struct {
ConversationIds []string `protobuf:"bytes,1,rep,name=conversation_ids,json=conversationIds,proto3" json:"conversation_ids,omitempty"`
LimitPerConversation *wrappers.Int32Value `protobuf:"bytes,2,opt,name=limit_per_conversation,json=limitPerConversation,proto3" json:"limit_per_conversation,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RecentMessagesRequest) Reset() { *m = RecentMessagesRequest{} }
func (m *RecentMessagesRequest) String() string { return proto.CompactTextString(m) }
func (*RecentMessagesRequest) ProtoMessage() {}
func (*RecentMessagesRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{16}
}
func (m *RecentMessagesRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RecentMessagesRequest.Unmarshal(m, b)
}
func (m *RecentMessagesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RecentMessagesRequest.Marshal(b, m, deterministic)
}
func (m *RecentMessagesRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_RecentMessagesRequest.Merge(m, src)
}
func (m *RecentMessagesRequest) XXX_Size() int {
return xxx_messageInfo_RecentMessagesRequest.Size(m)
}
func (m *RecentMessagesRequest) XXX_DiscardUnknown() {
xxx_messageInfo_RecentMessagesRequest.DiscardUnknown(m)
}
var xxx_messageInfo_RecentMessagesRequest proto.InternalMessageInfo
func (m *RecentMessagesRequest) GetConversationIds() []string {
if m != nil {
return m.ConversationIds
}
return nil
}
func (m *RecentMessagesRequest) GetLimitPerConversation() *wrappers.Int32Value {
if m != nil {
return m.LimitPerConversation
}
return nil
}
type RecentMessagesResponse struct {
Messages []*Message `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RecentMessagesResponse) Reset() { *m = RecentMessagesResponse{} }
func (m *RecentMessagesResponse) String() string { return proto.CompactTextString(m) }
func (*RecentMessagesResponse) ProtoMessage() {}
func (*RecentMessagesResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_78fcd0275ccaea0d, []int{17}
}
func (m *RecentMessagesResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RecentMessagesResponse.Unmarshal(m, b)
}
func (m *RecentMessagesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RecentMessagesResponse.Marshal(b, m, deterministic)
}
func (m *RecentMessagesResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_RecentMessagesResponse.Merge(m, src)
}
func (m *RecentMessagesResponse) XXX_Size() int {
return xxx_messageInfo_RecentMessagesResponse.Size(m)
}
func (m *RecentMessagesResponse) XXX_DiscardUnknown() {
xxx_messageInfo_RecentMessagesResponse.DiscardUnknown(m)
}
var xxx_messageInfo_RecentMessagesResponse proto.InternalMessageInfo
func (m *RecentMessagesResponse) GetMessages() []*Message {
if m != nil {
return m.Messages
}
return nil
}
func init() {
proto.RegisterType((*Conversation)(nil), "threads.Conversation")
proto.RegisterType((*Message)(nil), "threads.Message")
proto.RegisterType((*CreateConversationRequest)(nil), "threads.CreateConversationRequest")
proto.RegisterType((*CreateConversationResponse)(nil), "threads.CreateConversationResponse")
proto.RegisterType((*ReadConversationRequest)(nil), "threads.ReadConversationRequest")
proto.RegisterType((*ReadConversationResponse)(nil), "threads.ReadConversationResponse")
proto.RegisterType((*ListConversationsRequest)(nil), "threads.ListConversationsRequest")
proto.RegisterType((*ListConversationsResponse)(nil), "threads.ListConversationsResponse")
proto.RegisterType((*UpdateConversationRequest)(nil), "threads.UpdateConversationRequest")
proto.RegisterType((*UpdateConversationResponse)(nil), "threads.UpdateConversationResponse")
proto.RegisterType((*DeleteConversationRequest)(nil), "threads.DeleteConversationRequest")
proto.RegisterType((*DeleteConversationResponse)(nil), "threads.DeleteConversationResponse")
proto.RegisterType((*CreateMessageRequest)(nil), "threads.CreateMessageRequest")
proto.RegisterType((*CreateMessageResponse)(nil), "threads.CreateMessageResponse")
proto.RegisterType((*ListMessagesRequest)(nil), "threads.ListMessagesRequest")
proto.RegisterType((*ListMessagesResponse)(nil), "threads.ListMessagesResponse")
proto.RegisterType((*RecentMessagesRequest)(nil), "threads.RecentMessagesRequest")
proto.RegisterType((*RecentMessagesResponse)(nil), "threads.RecentMessagesResponse")
}
func init() { proto.RegisterFile("proto/threads.proto", fileDescriptor_78fcd0275ccaea0d) }
var fileDescriptor_78fcd0275ccaea0d = []byte{
// 749 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0xdb, 0x6e, 0xd3, 0x4a,
0x14, 0x95, 0xd3, 0xa6, 0x69, 0x76, 0x7a, 0x3b, 0xd3, 0xb4, 0xc7, 0x71, 0x6f, 0x39, 0x73, 0x1e,
0x4e, 0x0f, 0xa0, 0x54, 0xa4, 0x42, 0xa8, 0xea, 0x53, 0x2f, 0x42, 0x8a, 0x28, 0x88, 0x9a, 0x96,
0xa2, 0x4a, 0x55, 0xe4, 0xc4, 0xd3, 0xd4, 0x52, 0x62, 0x1b, 0xcf, 0x04, 0xf8, 0x06, 0xde, 0xf9,
0x07, 0x9e, 0xf8, 0x11, 0x7e, 0x0a, 0x65, 0x66, 0x9c, 0x8c, 0x2f, 0x93, 0x02, 0x7d, 0xf3, 0xcc,
0xac, 0xd9, 0x5e, 0x6b, 0xcf, 0x5e, 0x0b, 0x56, 0xc3, 0x28, 0x60, 0xc1, 0x1e, 0xbb, 0x8b, 0x88,
0xe3, 0xd2, 0x06, 0x5f, 0xa1, 0x92, 0x5c, 0x5a, 0x3b, 0xbd, 0x20, 0xe8, 0xf5, 0xc9, 0x1e, 0xdf,
0xee, 0x0c, 0x6f, 0xf7, 0x98, 0x37, 0x20, 0x94, 0x39, 0x83, 0x50, 0x20, 0xad, 0xed, 0x34, 0xe0,
0x53, 0xe4, 0x84, 0x21, 0x89, 0x64, 0x25, 0xfc, 0xc5, 0x80, 0x85, 0x93, 0xc0, 0xff, 0x48, 0x22,
0xea, 0x30, 0x2f, 0xf0, 0xd1, 0x12, 0x14, 0x3c, 0xd7, 0x34, 0xea, 0xc6, 0x6e, 0xd9, 0x2e, 0x78,
0x2e, 0xaa, 0xc1, 0x7c, 0x2f, 0x0a, 0x86, 0x61, 0xdb, 0x73, 0xcd, 0x02, 0xdf, 0x2d, 0xf1, 0x75,
0xcb, 0x45, 0x55, 0x28, 0xb2, 0x20, 0xf4, 0xba, 0xe6, 0x0c, 0xdf, 0x17, 0x0b, 0x74, 0x00, 0xd0,
0x8d, 0x88, 0xc3, 0x88, 0xdb, 0x76, 0x98, 0x39, 0x5b, 0x37, 0x76, 0x2b, 0x4d, 0xab, 0x21, 0x68,
0x34, 0x62, 0x1a, 0x8d, 0x8b, 0x98, 0xa7, 0x5d, 0x96, 0xe8, 0x23, 0x86, 0xbf, 0x19, 0x50, 0x7a,
0x45, 0x28, 0x75, 0x7a, 0x24, 0xc3, 0x63, 0x03, 0xca, 0xce, 0x90, 0xdd, 0x05, 0xd1, 0x84, 0xc8,
0xbc, 0xd8, 0x68, 0xb9, 0xe8, 0x3f, 0x58, 0xee, 0x2a, 0x22, 0x46, 0x10, 0xc1, 0x69, 0x49, 0xdd,
0x6e, 0xb9, 0x08, 0xc1, 0x2c, 0x23, 0x9f, 0x05, 0xad, 0xb2, 0xcd, 0xbf, 0xd1, 0x3e, 0x94, 0x28,
0xf1, 0xd9, 0x88, 0x6d, 0xf1, 0x5e, 0xb6, 0x73, 0x23, 0xe8, 0x11, 0xc3, 0x67, 0x50, 0x3b, 0xe1,
0xbc, 0xd5, 0xe6, 0xd9, 0xe4, 0xc3, 0x90, 0x50, 0x96, 0xe8, 0x99, 0xa1, 0xe9, 0x59, 0x41, 0xe9,
0x19, 0xbe, 0x02, 0x2b, 0xaf, 0x1a, 0x0d, 0x03, 0x9f, 0x12, 0x74, 0x00, 0x0b, 0xaa, 0x0c, 0x5e,
0xb2, 0xd2, 0x5c, 0x6b, 0xc4, 0x33, 0x91, 0xb8, 0x94, 0x80, 0xe2, 0x0e, 0xfc, 0x6d, 0x13, 0xc7,
0xcd, 0x23, 0x99, 0x6e, 0xf0, 0xf3, 0xd4, 0x43, 0x57, 0x9a, 0x9b, 0x99, 0x3e, 0xbc, 0x65, 0x91,
0xe7, 0xf7, 0xde, 0x39, 0xfd, 0x21, 0x19, 0x4b, 0xc2, 0x97, 0x60, 0x66, 0xff, 0xf1, 0x70, 0xea,
0xcf, 0xc0, 0x3c, 0xf3, 0x28, 0x53, 0x11, 0xf4, 0xfe, 0x06, 0xe3, 0xf7, 0x50, 0xcb, 0xb9, 0x26,
0xe9, 0x1c, 0xc2, 0xa2, 0xfa, 0x0f, 0x6a, 0x1a, 0xf5, 0x19, 0x3d, 0x9f, 0x24, 0x16, 0x1f, 0x41,
0xed, 0x32, 0x74, 0x35, 0x4f, 0x9e, 0xee, 0xa6, 0xf6, 0x9d, 0xf3, 0x4a, 0x3c, 0xbc, 0x59, 0x8f,
0xa1, 0x76, 0x4a, 0xfa, 0xe4, 0x97, 0xb8, 0xe1, 0x4d, 0xb0, 0xf2, 0xc0, 0x82, 0x05, 0x0e, 0xa1,
0x2a, 0x66, 0x51, 0x3a, 0x31, 0xae, 0x92, 0xe3, 0x31, 0x23, 0xd7, 0x63, 0x53, 0x9d, 0x1a, 0x1b,
0x70, 0x66, 0x62, 0x40, 0x7c, 0x02, 0x6b, 0xa9, 0x3f, 0xca, 0x86, 0x3c, 0x82, 0xd2, 0x40, 0x6c,
0xc9, 0x5e, 0xac, 0x8c, 0x7b, 0x11, 0x43, 0x63, 0x00, 0xfe, 0x6e, 0xc0, 0xea, 0xe8, 0xe1, 0xe5,
0x01, 0xfd, 0x6d, 0xda, 0x87, 0x50, 0xe1, 0x31, 0xd0, 0x21, 0xb7, 0x41, 0x44, 0xa4, 0x05, 0xa6,
0x45, 0x01, 0x8c, 0xe0, 0xc7, 0x1c, 0x8d, 0x9e, 0x42, 0xb1, 0xef, 0x0d, 0x3c, 0xa1, 0xab, 0xd2,
0xdc, 0xc8, 0x5c, 0x6b, 0xf9, 0x6c, 0xbf, 0x29, 0x8c, 0x23, 0x90, 0xf8, 0x14, 0xaa, 0x49, 0xbe,
0x52, 0xf4, 0x13, 0x98, 0x97, 0x9a, 0xe2, 0xf1, 0xcc, 0xaa, 0x1e, 0x23, 0xf0, 0x57, 0x03, 0xd6,
0x6c, 0xd2, 0x25, 0x7e, 0x46, 0xf8, 0xff, 0xb0, 0x92, 0x12, 0x2e, 0xea, 0x95, 0xed, 0xe5, 0xa4,
0x72, 0x8a, 0xce, 0x61, 0x9d, 0x73, 0x6a, 0x87, 0x24, 0x6a, 0x27, 0x46, 0xb0, 0x70, 0xbf, 0x9c,
0x2a, 0xbf, 0xfa, 0x86, 0x44, 0xea, 0x34, 0xe1, 0x17, 0xb0, 0x9e, 0xa6, 0xf5, 0x27, 0xfa, 0x9a,
0x3f, 0x8a, 0x50, 0xba, 0x10, 0xa7, 0xe8, 0x06, 0x50, 0x36, 0x25, 0x11, 0x9e, 0xf8, 0x43, 0x17,
0xc8, 0xd6, 0xbf, 0x53, 0x31, 0x92, 0xd8, 0x15, 0xac, 0xa4, 0x73, 0x0c, 0xd5, 0xc7, 0x17, 0x35,
0x31, 0x6a, 0xfd, 0x33, 0x05, 0x21, 0x0b, 0xdf, 0x00, 0xca, 0xba, 0x5e, 0xe1, 0xad, 0x4d, 0x15,
0x85, 0xf7, 0x94, 0xd8, 0xb8, 0x01, 0x94, 0xb5, 0xb3, 0x52, 0x5e, 0x1b, 0x0c, 0x4a, 0x79, 0x7d,
0x1e, 0xa0, 0x6b, 0xf8, 0x2b, 0x13, 0xa8, 0x68, 0xa2, 0x5a, 0x97, 0xd1, 0x16, 0x9e, 0x06, 0x91,
0xb5, 0x5f, 0xc3, 0x62, 0xc2, 0xf9, 0x68, 0x2b, 0xf5, 0x50, 0xc9, 0x0c, 0xb2, 0xb6, 0x75, 0xc7,
0xb2, 0xde, 0x4b, 0x58, 0x50, 0x3d, 0x85, 0x36, 0x13, 0x1c, 0x52, 0x0e, 0xb1, 0xb6, 0x34, 0xa7,
0xb2, 0xd8, 0x39, 0x2c, 0x25, 0x47, 0x18, 0x6d, 0x2b, 0x6f, 0x9d, 0x63, 0x39, 0x6b, 0x47, 0x7b,
0x2e, 0x4a, 0x1e, 0x2f, 0x5f, 0x2f, 0x72, 0x0b, 0x1d, 0x4a, 0x5c, 0x67, 0x8e, 0x2f, 0xf7, 0x7f,
0x06, 0x00, 0x00, 0xff, 0xff, 0x8c, 0xab, 0xc6, 0xc8, 0xe6, 0x09, 0x00, 0x00,
}

View File

@@ -0,0 +1,236 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: proto/threads.proto
package threads
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
_ "github.com/golang/protobuf/ptypes/timestamp"
_ "github.com/golang/protobuf/ptypes/wrappers"
math "math"
)
import (
context "context"
api "github.com/micro/micro/v3/service/api"
client "github.com/micro/micro/v3/service/client"
server "github.com/micro/micro/v3/service/server"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Reference imports to suppress errors if they are not otherwise used.
var _ api.Endpoint
var _ context.Context
var _ client.Option
var _ server.Option
// Api Endpoints for Threads service
func NewThreadsEndpoints() []*api.Endpoint {
return []*api.Endpoint{}
}
// Client API for Threads service
type ThreadsService interface {
// Create a conversation
CreateConversation(ctx context.Context, in *CreateConversationRequest, opts ...client.CallOption) (*CreateConversationResponse, error)
// Read a conversation using its ID, can filter using group ID if provided
ReadConversation(ctx context.Context, in *ReadConversationRequest, opts ...client.CallOption) (*ReadConversationResponse, error)
// Update a conversations topic
UpdateConversation(ctx context.Context, in *UpdateConversationRequest, opts ...client.CallOption) (*UpdateConversationResponse, error)
// Delete a conversation and all the messages within
DeleteConversation(ctx context.Context, in *DeleteConversationRequest, opts ...client.CallOption) (*DeleteConversationResponse, error)
// List all the conversations for a group
ListConversations(ctx context.Context, in *ListConversationsRequest, opts ...client.CallOption) (*ListConversationsResponse, error)
// Create a message within a conversation
CreateMessage(ctx context.Context, in *CreateMessageRequest, opts ...client.CallOption) (*CreateMessageResponse, error)
// List the messages within a conversation in reverse chronological order, using sent_before to
// offset as older messages need to be loaded
ListMessages(ctx context.Context, in *ListMessagesRequest, opts ...client.CallOption) (*ListMessagesResponse, error)
// RecentMessages returns the most recent messages in a group of conversations. By default the
// most messages retrieved per conversation is 25, however this can be overriden using the
// limit_per_conversation option
RecentMessages(ctx context.Context, in *RecentMessagesRequest, opts ...client.CallOption) (*RecentMessagesResponse, error)
}
type threadsService struct {
c client.Client
name string
}
func NewThreadsService(name string, c client.Client) ThreadsService {
return &threadsService{
c: c,
name: name,
}
}
func (c *threadsService) CreateConversation(ctx context.Context, in *CreateConversationRequest, opts ...client.CallOption) (*CreateConversationResponse, error) {
req := c.c.NewRequest(c.name, "Threads.CreateConversation", in)
out := new(CreateConversationResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *threadsService) ReadConversation(ctx context.Context, in *ReadConversationRequest, opts ...client.CallOption) (*ReadConversationResponse, error) {
req := c.c.NewRequest(c.name, "Threads.ReadConversation", in)
out := new(ReadConversationResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *threadsService) UpdateConversation(ctx context.Context, in *UpdateConversationRequest, opts ...client.CallOption) (*UpdateConversationResponse, error) {
req := c.c.NewRequest(c.name, "Threads.UpdateConversation", in)
out := new(UpdateConversationResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *threadsService) DeleteConversation(ctx context.Context, in *DeleteConversationRequest, opts ...client.CallOption) (*DeleteConversationResponse, error) {
req := c.c.NewRequest(c.name, "Threads.DeleteConversation", in)
out := new(DeleteConversationResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *threadsService) ListConversations(ctx context.Context, in *ListConversationsRequest, opts ...client.CallOption) (*ListConversationsResponse, error) {
req := c.c.NewRequest(c.name, "Threads.ListConversations", in)
out := new(ListConversationsResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *threadsService) CreateMessage(ctx context.Context, in *CreateMessageRequest, opts ...client.CallOption) (*CreateMessageResponse, error) {
req := c.c.NewRequest(c.name, "Threads.CreateMessage", in)
out := new(CreateMessageResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *threadsService) ListMessages(ctx context.Context, in *ListMessagesRequest, opts ...client.CallOption) (*ListMessagesResponse, error) {
req := c.c.NewRequest(c.name, "Threads.ListMessages", in)
out := new(ListMessagesResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *threadsService) RecentMessages(ctx context.Context, in *RecentMessagesRequest, opts ...client.CallOption) (*RecentMessagesResponse, error) {
req := c.c.NewRequest(c.name, "Threads.RecentMessages", in)
out := new(RecentMessagesResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Threads service
type ThreadsHandler interface {
// Create a conversation
CreateConversation(context.Context, *CreateConversationRequest, *CreateConversationResponse) error
// Read a conversation using its ID, can filter using group ID if provided
ReadConversation(context.Context, *ReadConversationRequest, *ReadConversationResponse) error
// Update a conversations topic
UpdateConversation(context.Context, *UpdateConversationRequest, *UpdateConversationResponse) error
// Delete a conversation and all the messages within
DeleteConversation(context.Context, *DeleteConversationRequest, *DeleteConversationResponse) error
// List all the conversations for a group
ListConversations(context.Context, *ListConversationsRequest, *ListConversationsResponse) error
// Create a message within a conversation
CreateMessage(context.Context, *CreateMessageRequest, *CreateMessageResponse) error
// List the messages within a conversation in reverse chronological order, using sent_before to
// offset as older messages need to be loaded
ListMessages(context.Context, *ListMessagesRequest, *ListMessagesResponse) error
// RecentMessages returns the most recent messages in a group of conversations. By default the
// most messages retrieved per conversation is 25, however this can be overriden using the
// limit_per_conversation option
RecentMessages(context.Context, *RecentMessagesRequest, *RecentMessagesResponse) error
}
func RegisterThreadsHandler(s server.Server, hdlr ThreadsHandler, opts ...server.HandlerOption) error {
type threads interface {
CreateConversation(ctx context.Context, in *CreateConversationRequest, out *CreateConversationResponse) error
ReadConversation(ctx context.Context, in *ReadConversationRequest, out *ReadConversationResponse) error
UpdateConversation(ctx context.Context, in *UpdateConversationRequest, out *UpdateConversationResponse) error
DeleteConversation(ctx context.Context, in *DeleteConversationRequest, out *DeleteConversationResponse) error
ListConversations(ctx context.Context, in *ListConversationsRequest, out *ListConversationsResponse) error
CreateMessage(ctx context.Context, in *CreateMessageRequest, out *CreateMessageResponse) error
ListMessages(ctx context.Context, in *ListMessagesRequest, out *ListMessagesResponse) error
RecentMessages(ctx context.Context, in *RecentMessagesRequest, out *RecentMessagesResponse) error
}
type Threads struct {
threads
}
h := &threadsHandler{hdlr}
return s.Handle(s.NewHandler(&Threads{h}, opts...))
}
type threadsHandler struct {
ThreadsHandler
}
func (h *threadsHandler) CreateConversation(ctx context.Context, in *CreateConversationRequest, out *CreateConversationResponse) error {
return h.ThreadsHandler.CreateConversation(ctx, in, out)
}
func (h *threadsHandler) ReadConversation(ctx context.Context, in *ReadConversationRequest, out *ReadConversationResponse) error {
return h.ThreadsHandler.ReadConversation(ctx, in, out)
}
func (h *threadsHandler) UpdateConversation(ctx context.Context, in *UpdateConversationRequest, out *UpdateConversationResponse) error {
return h.ThreadsHandler.UpdateConversation(ctx, in, out)
}
func (h *threadsHandler) DeleteConversation(ctx context.Context, in *DeleteConversationRequest, out *DeleteConversationResponse) error {
return h.ThreadsHandler.DeleteConversation(ctx, in, out)
}
func (h *threadsHandler) ListConversations(ctx context.Context, in *ListConversationsRequest, out *ListConversationsResponse) error {
return h.ThreadsHandler.ListConversations(ctx, in, out)
}
func (h *threadsHandler) CreateMessage(ctx context.Context, in *CreateMessageRequest, out *CreateMessageResponse) error {
return h.ThreadsHandler.CreateMessage(ctx, in, out)
}
func (h *threadsHandler) ListMessages(ctx context.Context, in *ListMessagesRequest, out *ListMessagesResponse) error {
return h.ThreadsHandler.ListMessages(ctx, in, out)
}
func (h *threadsHandler) RecentMessages(ctx context.Context, in *RecentMessagesRequest, out *RecentMessagesResponse) error {
return h.ThreadsHandler.RecentMessages(ctx, in, out)
}

113
threads/proto/threads.proto Normal file
View File

@@ -0,0 +1,113 @@
syntax = "proto3";
package threads;
option go_package = "proto;threads";
import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto";
service Threads {
// Create a conversation
rpc CreateConversation(CreateConversationRequest) returns (CreateConversationResponse);
// Read a conversation using its ID, can filter using group ID if provided
rpc ReadConversation(ReadConversationRequest) returns (ReadConversationResponse);
// Update a conversations topic
rpc UpdateConversation(UpdateConversationRequest) returns (UpdateConversationResponse);
// Delete a conversation and all the messages within
rpc DeleteConversation(DeleteConversationRequest) returns (DeleteConversationResponse);
// List all the conversations for a group
rpc ListConversations(ListConversationsRequest) returns (ListConversationsResponse);
// Create a message within a conversation
rpc CreateMessage(CreateMessageRequest) returns (CreateMessageResponse);
// List the messages within a conversation in reverse chronological order, using sent_before to
// offset as older messages need to be loaded
rpc ListMessages(ListMessagesRequest) returns (ListMessagesResponse);
// RecentMessages returns the most recent messages in a group of conversations. By default the
// most messages retrieved per conversation is 25, however this can be overriden using the
// limit_per_conversation option
rpc RecentMessages(RecentMessagesRequest) returns (RecentMessagesResponse);
}
message Conversation {
string id = 1;
string group_id = 2;
string topic = 3;
google.protobuf.Timestamp created_at = 4;
}
message Message {
string id = 1;
string author_id = 2;
string conversation_id = 3;
string text = 4;
google.protobuf.Timestamp sent_at = 5;
}
message CreateConversationRequest {
string group_id = 1;
string topic = 2;
}
message CreateConversationResponse {
Conversation conversation = 1;
}
message ReadConversationRequest {
string id = 1;
google.protobuf.StringValue group_id = 2;
}
message ReadConversationResponse {
Conversation conversation = 1;
}
message ListConversationsRequest {
string group_id = 1;
}
message ListConversationsResponse {
repeated Conversation conversations = 1;
}
message UpdateConversationRequest {
string id = 1;
string topic = 2;
}
message UpdateConversationResponse {
Conversation conversation = 1;
}
message DeleteConversationRequest {
string id = 1;
}
message DeleteConversationResponse {}
message CreateMessageRequest {
string conversation_id = 1;
string author_id = 2;
string text = 3;
}
message CreateMessageResponse {
Message message = 1;
}
message ListMessagesRequest {
string conversation_id = 1;
google.protobuf.Timestamp sent_before = 2;
google.protobuf.Int32Value limit = 3;
}
message ListMessagesResponse {
repeated Message messages = 1;
}
message RecentMessagesRequest {
repeated string conversation_ids = 1;
google.protobuf.Int32Value limit_per_conversation = 2;
}
message RecentMessagesResponse {
repeated Message messages = 1;
}