Refactor Chats Service (#48)

This commit is contained in:
ben-toogood
2021-01-27 11:43:09 +00:00
committed by GitHub
parent 6ab2b2d9fa
commit 54c5994faf
30 changed files with 1855 additions and 82 deletions

27
test/chat/Makefile Normal file
View File

@@ -0,0 +1,27 @@
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 --openapi_out=. --proto_path=. --micro_out=. --go_out=:. proto/chat.proto
.PHONY: docs
docs:
protoc --openapi_out=. --proto_path=. --micro_out=. --go_out=:. proto/chat.proto
@redoc-cli bundle api-chat.json
.PHONY: build
build:
go build -o chat *.go
.PHONY: test
test:
go test -v ./... -cover
.PHONY: docker
docker:
docker build . -t chat:latest

87
test/chat/README.md Normal file
View File

@@ -0,0 +1,87 @@
Real time messaging API which enables Chat services to be embedded anywhere
# Chat Service
The Chat service is a programmable instant messaging API service which can be used in any application to immediately create conversations.
## Create a chat
### cURL
```bash
> curl 'https://api.m3o.com/chat/New' \
-H 'micro-namespace: $yourNamespace' \
-H 'authorization: Bearer $yourToken' \
-d '{"user_ids":["JohnBarry"]}';
{
"chat_id": "3c9ea66c-d516-45d4-abe8-082089e18b27"
}
```
### CLI
```bash
> micro chat new --user_ids=JohnBarry
{
"chat_id": "3c9ea66c-d516-45d4-abe8-082089e18b27"
}
```
## Send a message to the chat
### cURL
```bash
> curl 'https://api.m3o.com/chat/Send' \
-H 'micro-namespace: $yourNamespace' \
-H 'authorization: Bearer $yourToken' \
-d '{"user_id": "John", "subject": "Hello", "text": "Hey Barry"}';
{}
```
### CLI
```bash
> micro chat send --chat_id=bed4f0f0-da12-46d2-90d2-17ae1714a214 --user_id=John --subject=Hello --text='Hey Barry'
{}
```
## View the chat history
### cURL
```bash
> curl 'https://api.m3o.com/chat/Send' \
-H 'micro-namespace: $yourNamespace' \
-H 'authorization: Bearer $yourToken' \
-d '{"chat_id": "bed4f0f0-da12-46d2-90d2-17ae1714a214"}';
{
"messages": [
{
"id": "a61284a8-f471-4734-9192-640d89762e98",
"client_id": "6ba0d2a6-96fa-47d8-8f6f-7f75b4cc8b3e",
"chat_id": "bed4f0f0-da12-46d2-90d2-17ae1714a214",
"user_id": "John",
"subject": "Hello",
"text": "Hey Barry"
}
]
}
```
### CLI
```bash
> micro chat history --chat_id=bed4f0f0-da12-46d2-90d2-17ae1714a214
{
"messages": [
{
"id": "a61284a8-f471-4734-9192-640d89762e98",
"client_id": "6ba0d2a6-96fa-47d8-8f6f-7f75b4cc8b3e",
"chat_id": "bed4f0f0-da12-46d2-90d2-17ae1714a214",
"user_id": "John",
"subject": "Hello",
"text": "Hey Barry"
}
]
}
```

121
test/chat/client/main.go Normal file
View File

@@ -0,0 +1,121 @@
// Package main is a client for the chat service to demonstrate how it would work for a client. To
// run the client, first launch the chat service by running `micro run ./chat` from the top level of
// this repo. Then run `micro run ./chat/client` and `micro logs -f client` to follow the logs of
// the client.
package main
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"github.com/micro/micro/v3/service"
"github.com/micro/micro/v3/service/context/metadata"
"github.com/micro/micro/v3/service/logger"
chat "github.com/micro/services/chat/proto"
)
var (
userOneID = "user-one-" + uuid.New().String()
userTwoID = "user-two-" + uuid.New().String()
)
func main() {
// create a chat service client
srv := service.New()
cli := chat.NewChatService("chat", srv.Client())
// create a chat for our users
userIDs := []string{userOneID, userTwoID}
nRsp, err := cli.New(context.TODO(), &chat.NewRequest{UserIds: userIDs})
if err != nil {
logger.Fatalf("Error creating the chat: %v", err)
}
chatID := nRsp.GetChatId()
logger.Infof("Chat Created. ID: %v", chatID)
// list the number messages in the chat history
hRsp, err := cli.History(context.TODO(), &chat.HistoryRequest{ChatId: chatID})
if err != nil {
logger.Fatalf("Error getting the chat history: %v", err)
}
logger.Infof("Chat has %v message(s)", len(hRsp.Messages))
// create a channel to handle errors
errChan := make(chan error)
// run user one
go func() {
ctx := metadata.NewContext(context.TODO(), metadata.Metadata{
"user-id": userOneID, "chat-id": chatID,
})
stream, err := cli.Connect(ctx)
if err != nil {
errChan <- err
return
}
for i := 1; true; i++ {
// send a message to the chat
err = stream.Send(&chat.Message{
ClientId: uuid.New().String(),
SentAt: time.Now().Unix(),
Subject: "Message from user one",
Text: fmt.Sprintf("Message #%v", i),
})
if err != nil {
errChan <- err
return
}
logger.Infof("User one sent message")
// wait for user two to respond
msg, err := stream.Recv()
if err != nil {
errChan <- err
return
}
logger.Infof("User one recieved message %v from %v", msg.Text, msg.UserId)
time.Sleep(time.Second)
}
}()
// run user two
go func() {
ctx := metadata.NewContext(context.TODO(), metadata.Metadata{
"user-id": userTwoID, "chat-id": chatID,
})
stream, err := cli.Connect(ctx)
if err != nil {
errChan <- err
return
}
for i := 1; true; i++ {
// send a response to the chat
err = stream.Send(&chat.Message{
ClientId: uuid.New().String(),
SentAt: time.Now().Unix(),
Subject: "Response from user two",
Text: fmt.Sprintf("Response #%v", i),
})
if err != nil {
errChan <- err
return
}
logger.Infof("User two sent message")
// wait for a message from user one
msg, err := stream.Recv()
if err != nil {
errChan <- err
return
}
logger.Infof("User two recieved message %v from %v", msg.Text, msg.UserId)
time.Sleep(time.Second)
}
}()
logger.Fatal(<-errChan)
}

View File

@@ -0,0 +1,312 @@
package handler
import (
"context"
"sort"
"strings"
"time"
"github.com/google/uuid"
"github.com/micro/micro/v3/service/context/metadata"
"github.com/micro/micro/v3/service/errors"
"github.com/micro/micro/v3/service/events"
"github.com/micro/micro/v3/service/logger"
"github.com/micro/micro/v3/service/store"
"google.golang.org/protobuf/types/known/timestamppb"
// it's standard to import the services own proto under the alias pb
pb "github.com/micro/services/chat/proto"
)
const (
chatStoreKeyPrefix = "chats/"
chatEventKeyPrefix = "chats/"
messageStoreKeyPrefix = "messages/"
)
// Chat satisfies the ChatHandler interface. You can see this inteface defined in chat.pb.micro.go
type Chat struct{}
// New creates a chat for a group of users. The RPC is idempotent so if it's called multiple times
// for the same users, the same response will be returned. It's good practice to design APIs as
// idempotent since this enables safe retries.
func (c *Chat) New(ctx context.Context, req *pb.NewRequest, rsp *pb.NewResponse) error {
// in a real world application we would authorize the request to ensure the authenticated user
// is part of the chat they're attempting to create. We could do this by getting the user id from
// auth.AccountFromContext(ctx) and then validating the presence of their id in req.UserIds. If
// the user is not part of the request then we'd return a Forbidden error, which the micro api
// would transform to a 403 status code.
// validate the request
if len(req.UserIds) == 0 {
// Return a bad request error to the client, the first argument is a unique id which the client
// can check for. The second argument is a human readable description. Returning the correct type
// of error is important as it's used by the network to know if a request should be retried. Only
// 500 (InternalServerError) and 408 (Timeout) errors are retried.
return errors.BadRequest("chat.New.MissingUserIDs", "One or more user IDs are required")
}
// construct a key to identify the chat, we'll do this by sorting the user ids alphabetically and
// then joining them. When a service calls the store, the data returned will be automatically scoped
// to the service however it's still advised to use a prefix when writing data since this allows
// other types of keys to be written in the future. We'll make a copy of the req.UserIds object as
// it's a good practice to not mutate the request object.
sortedIDs := make([]string, len(req.UserIds))
copy(sortedIDs, req.UserIds)
sort.Strings(sortedIDs)
// key to lookup the chat in the store using, e.g. "chat/usera-userb-userc"
key := chatStoreKeyPrefix + strings.Join(sortedIDs, "_")
// read from the store to check if a chat with these users already exists
recs, err := store.Read(key)
if err == nil {
// if an error wasn't returned, at least one record was found. The value returned by the store
// is the bytes representation of the chat id. We'll convert this back into a string and return
// it to the client.
rsp.ChatId = string(recs[0].Value)
return nil
} else if err != store.ErrNotFound {
// if no records were found then we'd expect to get a store.ErrNotFound error returned. If this
// wasn't the case, the service could've experienced an issue connecting to the store so we should
// log the error and return an InternalServerError to the client, indicating the request should
// be retried
logger.Errorf("Error reading from the store. Key: %v. Error: %v", key, err)
return errors.InternalServerError("chat.New.Unknown", "Error reading from the store")
}
// no chat id was returned so we'll generate one, write it to the store and then return it to the
// client
chatID := uuid.New().String()
if err := store.Write(&store.Record{Key: key, Value: []byte(chatID)}); err != nil {
logger.Errorf("Error writing to the store. Key: %v. Error: %v", key, err)
return errors.InternalServerError("chat.New.Unknown", "Error writing to the store")
}
if err := store.Write(&store.Record{Key: chatStoreKeyPrefix + chatID}); err != nil {
logger.Errorf("Error writing to the store. Key: %v. Error: %v", chatStoreKeyPrefix+chatID, err)
return errors.InternalServerError("chat.New.Unknown", "Error writing to the store")
}
// The chat was successfully created so we'll log the event and then return the id to the client.
// Note that we'll use logger.Infof here vs the Errorf above.
logger.Infof("New chat created with ID %v", chatID)
rsp.ChatId = chatID
return nil
}
// History returns the historical messages in a chat
func (c *Chat) History(ctx context.Context, req *pb.HistoryRequest, rsp *pb.HistoryResponse) error {
// as per the New function, in a real world application we would authorize the request to ensure
// the authenticated user is part of the chat they're attempting to read the history of
// validate the request
if len(req.ChatId) == 0 {
return errors.BadRequest("chat.History.MissingChatID", "ChatID is missing")
}
// lookup the chat from the store to ensure it's valid
if _, err := store.Read(chatStoreKeyPrefix + req.ChatId); err == store.ErrNotFound {
return errors.BadRequest("chat.History.InvalidChatID", "Chat not found with this ID")
} else if err != nil {
logger.Errorf("Error reading from the store. Chat ID: %v. Error: %v", req.ChatId, err)
return errors.InternalServerError("chat.History.Unknown", "Error reading from the store")
}
// lookup the historical messages for the chat using the event store. lots of packages in micro
// support options, in this case we'll pass the ReadLimit option to restrict the number of messages
// we'll load from the events store.
messages, err := events.Read(chatEventKeyPrefix+req.ChatId, events.ReadLimit(50))
if err != nil {
logger.Errorf("Error reading from the event store. Chat ID: %v. Error: %v", req.ChatId, err)
return errors.InternalServerError("chat.History.Unknown", "Error reading from the event store")
}
// we've loaded the messages from the event store. next we need to serialize them and return them
// to the client. The message is stored in the event payload, to retrieve it we need to unmarshal
// the event into a message struct.
rsp.Messages = make([]*pb.Message, len(messages))
for i, ev := range messages {
var msg pb.Message
if err := ev.Unmarshal(&msg); err != nil {
logger.Errorf("Error unmarshaling event: %v", err)
return errors.InternalServerError("chat.History.Unknown", "Error unmarshaling event")
}
rsp.Messages[i] = &msg
}
return nil
}
// Send a single message to the chat, designed for ease of use via the API / CLI
func (c *Chat) Send(ctx context.Context, req *pb.SendRequest, rsp *pb.SendResponse) error {
// validate the request
if len(req.ChatId) == 0 {
return errors.BadRequest("chat.Send.MissingChatID", "ChatID is missing")
}
if len(req.UserId) == 0 {
return errors.BadRequest("chat.Send.MissingUserID", "UserID is missing")
}
if len(req.Text) == 0 {
return errors.BadRequest("chat.Send.MissingText", "Text is missing")
}
// construct the message
msg := &pb.Message{
Id: uuid.New().String(),
ClientId: req.ClientId,
ChatId: req.ChatId,
UserId: req.UserId,
Subject: req.Subject,
Text: req.Text,
SentAt: timestamppb.New(time.Now()),
}
// default the client id if not provided
if len(msg.ClientId) == 0 {
msg.ClientId = uuid.New().String()
}
// create the message
if err := c.createMessage(msg); err != nil {
return err
}
// return the response
rsp.Message = msg
return nil
}
// Connect to a chat using a bidirectional stream enabling the client to send and recieve messages
// over a single RPC. When a message is sent on the stream, it will be added to the chat history
// and sent to the other connected users. When opening the connection, the client should provide
// the chat_id and user_id in the context so the server knows which messages to stream.
func (c *Chat) Connect(ctx context.Context, stream pb.Chat_ConnectStream) error {
// the client passed the chat id and user id in the request context. we'll load that information
// now and validate it. If any information is missing we'll return a BadRequest error to the client
userID, ok := metadata.Get(ctx, "user-id")
if !ok {
return errors.BadRequest("chat.Connect.MissingUserID", "UserID missing in context")
}
chatID, ok := metadata.Get(ctx, "chat-id")
if !ok {
return errors.BadRequest("chat.Connect.MissingChatID", "ChatId missing in context")
}
// lookup the chat from the store to ensure it's valid
if _, err := store.Read(chatStoreKeyPrefix + chatID); err == store.ErrNotFound {
return errors.BadRequest("chat.Connect.InvalidChatID", "Chat not found with this ID")
} else if err != nil {
logger.Errorf("Error reading from the store. Chat ID: %v. Error: %v", chatID, err)
return errors.InternalServerError("chat.Connect.Unknown", "Error reading from the store")
}
// as per the New and Connect functions, at this point in a real world application we would
// authorize the request to ensure the authenticated user is part of the chat they're attempting
// to read the history of
// create a new context which can be cancelled, in the case either the consumer of publisher errors
// we don't want one to keep running in the background
cancelCtx, cancel := context.WithCancel(ctx)
defer cancel()
// create a channel to send errors on, because the subscriber / publisher will run in seperate go-
// routines, they need a way of returning errors to the client
errChan := make(chan error)
// create an event stream to consume messages posted by other users into the chat. we'll use the
// user id as a queue to ensure each user recieves the message
evStream, err := events.Consume(chatEventKeyPrefix+chatID, events.WithGroup(userID))
if err != nil {
logger.Errorf("Error streaming events. Chat ID: %v. Error: %v", chatID, err)
return errors.InternalServerError("chat.Connect.Unknown", "Error connecting to the event stream")
}
go func() {
for {
select {
case <-cancelCtx.Done():
// the context has been cancelled or timed out, stop subscribing to new messages
return
case ev := <-evStream:
// recieved a message, unmarshal it into a message struct. if an error occurs log it and
// cancel the context
var msg pb.Message
if err := ev.Unmarshal(&msg); err != nil {
logger.Errorf("Error unmarshaling message. ChatID: %v. Error: %v", chatID, err)
errChan <- err
return
}
// ignore any messages published by the current user
if msg.UserId == userID {
continue
}
// publish the message to the stream
if err := stream.Send(&msg); err != nil {
logger.Errorf("Error sending message to stream. ChatID: %v. Message ID: %v. Error: %v", chatID, msg.Id, err)
errChan <- err
return
}
}
}
}()
// transform the stream.Recv into a channel which can be used in the select statement below
msgChan := make(chan *pb.Message)
go func() {
for {
msg, err := stream.Recv()
if err != nil {
errChan <- err
close(msgChan)
return
}
msgChan <- msg
}
}()
for {
select {
case <-cancelCtx.Done():
// the context has been cancelled or timed out, stop subscribing to new messages
return nil
case err := <-errChan:
// an error occured in another goroutine, terminate the stream
return err
case msg := <-msgChan:
// set the defaults
msg.UserId = userID
msg.ChatId = chatID
// create the message
if err := c.createMessage(msg); err != nil {
return err
}
}
}
}
// createMessage is a helper function which creates a message in the event stream. It handles the
// logic for ensuring client id is unique.
func (c *Chat) createMessage(msg *pb.Message) error {
// a message was recieved from the client. validate it hasn't been recieved before
if _, err := store.Read(messageStoreKeyPrefix + msg.ClientId); err == nil {
// the message has already been processed
return nil
} else if err != store.ErrNotFound {
// an unexpected error occured
return err
}
// send the message to the event stream
if err := events.Publish(chatEventKeyPrefix+msg.ChatId, msg); err != nil {
return err
}
// record the messages client id
if err := store.Write(&store.Record{Key: messageStoreKeyPrefix + msg.ClientId}); err != nil {
return err
}
return nil
}

25
test/chat/main.go Normal file
View File

@@ -0,0 +1,25 @@
package main
import (
"github.com/micro/micro/v3/service"
"github.com/micro/micro/v3/service/logger"
"github.com/micro/services/chat/handler"
pb "github.com/micro/services/chat/proto"
)
func main() {
// Create the service
srv := service.New(
service.Name("chat"),
service.Version("latest"),
)
// Register the handler against the server
pb.RegisterChatHandler(srv.Server(), new(handler.Chat))
// Run the service
if err := srv.Run(); err != nil {
logger.Fatal(err)
}
}

1
test/chat/micro.mu Normal file
View File

@@ -0,0 +1 @@
service chat

670
test/chat/proto/chat.pb.go Normal file
View File

@@ -0,0 +1,670 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.23.0
// protoc v3.13.0
// source: proto/chat.proto
package chat
import (
proto "github.com/golang/protobuf/proto"
timestamp "github.com/golang/protobuf/ptypes/timestamp"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is a compile-time assertion that a sufficiently up-to-date version
// of the legacy proto package is being used.
const _ = proto.ProtoPackageIsVersion4
// NewRequest contains the infromation needed to create a new chat
type NewRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
UserIds []string `protobuf:"bytes,1,rep,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"`
}
func (x *NewRequest) Reset() {
*x = NewRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_chat_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NewRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NewRequest) ProtoMessage() {}
func (x *NewRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_chat_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NewRequest.ProtoReflect.Descriptor instead.
func (*NewRequest) Descriptor() ([]byte, []int) {
return file_proto_chat_proto_rawDescGZIP(), []int{0}
}
func (x *NewRequest) GetUserIds() []string {
if x != nil {
return x.UserIds
}
return nil
}
// NewResponse contains the chat id for the users
type NewResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ChatId string `protobuf:"bytes,1,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"`
}
func (x *NewResponse) Reset() {
*x = NewResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_chat_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NewResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NewResponse) ProtoMessage() {}
func (x *NewResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_chat_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NewResponse.ProtoReflect.Descriptor instead.
func (*NewResponse) Descriptor() ([]byte, []int) {
return file_proto_chat_proto_rawDescGZIP(), []int{1}
}
func (x *NewResponse) GetChatId() string {
if x != nil {
return x.ChatId
}
return ""
}
// HistoryRequest contains the id of the chat we want the history for. This RPC will return all
// historical messages, however in a real life application we'd introduce some form of pagination
// here, only loading the older messages when required.
type HistoryRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ChatId string `protobuf:"bytes,1,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"`
}
func (x *HistoryRequest) Reset() {
*x = HistoryRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_chat_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HistoryRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HistoryRequest) ProtoMessage() {}
func (x *HistoryRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_chat_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HistoryRequest.ProtoReflect.Descriptor instead.
func (*HistoryRequest) Descriptor() ([]byte, []int) {
return file_proto_chat_proto_rawDescGZIP(), []int{2}
}
func (x *HistoryRequest) GetChatId() string {
if x != nil {
return x.ChatId
}
return ""
}
// HistoryResponse contains the historical messages in a chat
type HistoryResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Messages []*Message `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"`
}
func (x *HistoryResponse) Reset() {
*x = HistoryResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_chat_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HistoryResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HistoryResponse) ProtoMessage() {}
func (x *HistoryResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_chat_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HistoryResponse.ProtoReflect.Descriptor instead.
func (*HistoryResponse) Descriptor() ([]byte, []int) {
return file_proto_chat_proto_rawDescGZIP(), []int{3}
}
func (x *HistoryResponse) GetMessages() []*Message {
if x != nil {
return x.Messages
}
return nil
}
// SendRequest contains a single message to send to a chat
type SendRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// a client side id, should be validated by the server to make the request retry safe
ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
// id of the chat the message is being sent to / from
ChatId string `protobuf:"bytes,2,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"`
// id of the user who sent the message
UserId string `protobuf:"bytes,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
// subject of the message
Subject string `protobuf:"bytes,4,opt,name=subject,proto3" json:"subject,omitempty"`
// text of the message
Text string `protobuf:"bytes,5,opt,name=text,proto3" json:"text,omitempty"`
}
func (x *SendRequest) Reset() {
*x = SendRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_chat_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendRequest) ProtoMessage() {}
func (x *SendRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_chat_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendRequest.ProtoReflect.Descriptor instead.
func (*SendRequest) Descriptor() ([]byte, []int) {
return file_proto_chat_proto_rawDescGZIP(), []int{4}
}
func (x *SendRequest) GetClientId() string {
if x != nil {
return x.ClientId
}
return ""
}
func (x *SendRequest) GetChatId() string {
if x != nil {
return x.ChatId
}
return ""
}
func (x *SendRequest) GetUserId() string {
if x != nil {
return x.UserId
}
return ""
}
func (x *SendRequest) GetSubject() string {
if x != nil {
return x.Subject
}
return ""
}
func (x *SendRequest) GetText() string {
if x != nil {
return x.Text
}
return ""
}
// SendResponse is a blank message returned when a message is successfully created
type SendResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Message *Message `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
}
func (x *SendResponse) Reset() {
*x = SendResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_chat_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendResponse) ProtoMessage() {}
func (x *SendResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_chat_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendResponse.ProtoReflect.Descriptor instead.
func (*SendResponse) Descriptor() ([]byte, []int) {
return file_proto_chat_proto_rawDescGZIP(), []int{5}
}
func (x *SendResponse) GetMessage() *Message {
if x != nil {
return x.Message
}
return nil
}
// Message sent to a chat
type Message struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// id of the message, allocated by the server
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
// a client side id, should be validated by the server to make the request retry safe
ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
// id of the chat the message is being sent to / from
ChatId string `protobuf:"bytes,3,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"`
// id of the user who sent the message
UserId string `protobuf:"bytes,4,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
// time time the message was sent in unix format
SentAt *timestamp.Timestamp `protobuf:"bytes,5,opt,name=sent_at,json=sentAt,proto3" json:"sent_at,omitempty"`
// subject of the message
Subject string `protobuf:"bytes,6,opt,name=subject,proto3" json:"subject,omitempty"`
// text of the message
Text string `protobuf:"bytes,7,opt,name=text,proto3" json:"text,omitempty"`
}
func (x *Message) Reset() {
*x = Message{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_chat_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Message) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Message) ProtoMessage() {}
func (x *Message) ProtoReflect() protoreflect.Message {
mi := &file_proto_chat_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Message.ProtoReflect.Descriptor instead.
func (*Message) Descriptor() ([]byte, []int) {
return file_proto_chat_proto_rawDescGZIP(), []int{6}
}
func (x *Message) GetId() string {
if x != nil {
return x.Id
}
return ""
}
func (x *Message) GetClientId() string {
if x != nil {
return x.ClientId
}
return ""
}
func (x *Message) GetChatId() string {
if x != nil {
return x.ChatId
}
return ""
}
func (x *Message) GetUserId() string {
if x != nil {
return x.UserId
}
return ""
}
func (x *Message) GetSentAt() *timestamp.Timestamp {
if x != nil {
return x.SentAt
}
return nil
}
func (x *Message) GetSubject() string {
if x != nil {
return x.Subject
}
return ""
}
func (x *Message) GetText() string {
if x != nil {
return x.Text
}
return ""
}
var File_proto_chat_proto protoreflect.FileDescriptor
var file_proto_chat_proto_rawDesc = []byte{
0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x04, 0x63, 0x68, 0x61, 0x74, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x27, 0x0a, 0x0a, 0x4e, 0x65, 0x77,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f,
0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49,
0x64, 0x73, 0x22, 0x26, 0x0a, 0x0b, 0x4e, 0x65, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x06, 0x63, 0x68, 0x61, 0x74, 0x49, 0x64, 0x22, 0x29, 0x0a, 0x0e, 0x48, 0x69,
0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07,
0x63, 0x68, 0x61, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63,
0x68, 0x61, 0x74, 0x49, 0x64, 0x22, 0x3c, 0x0a, 0x0f, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x63, 0x68, 0x61,
0x74, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x73, 0x22, 0x8a, 0x01, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64,
0x12, 0x17, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x06, 0x63, 0x68, 0x61, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65,
0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72,
0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20,
0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04,
0x74, 0x65, 0x78, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74,
0x22, 0x37, 0x0a, 0x0c, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x27, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x0d, 0x2e, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xcb, 0x01, 0x0a, 0x07, 0x4d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f,
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x68, 0x61, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75,
0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73,
0x65, 0x72, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x07, 0x73, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x74, 0x18,
0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
0x70, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x74, 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62,
0x6a, 0x65, 0x63, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a,
0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x32, 0xc6, 0x01, 0x0a, 0x04, 0x43, 0x68, 0x61, 0x74,
0x12, 0x2a, 0x0a, 0x03, 0x4e, 0x65, 0x77, 0x12, 0x10, 0x2e, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x4e,
0x65, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x63, 0x68, 0x61, 0x74,
0x2e, 0x4e, 0x65, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x07,
0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x14, 0x2e, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x48,
0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e,
0x63, 0x68, 0x61, 0x74, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x11, 0x2e, 0x63,
0x68, 0x61, 0x74, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x12, 0x2e, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x12, 0x0d,
0x2e, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x0d, 0x2e,
0x63, 0x68, 0x61, 0x74, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x28, 0x01, 0x30, 0x01,
0x42, 0x0c, 0x5a, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x63, 0x68, 0x61, 0x74, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proto_chat_proto_rawDescOnce sync.Once
file_proto_chat_proto_rawDescData = file_proto_chat_proto_rawDesc
)
func file_proto_chat_proto_rawDescGZIP() []byte {
file_proto_chat_proto_rawDescOnce.Do(func() {
file_proto_chat_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_chat_proto_rawDescData)
})
return file_proto_chat_proto_rawDescData
}
var file_proto_chat_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_proto_chat_proto_goTypes = []interface{}{
(*NewRequest)(nil), // 0: chat.NewRequest
(*NewResponse)(nil), // 1: chat.NewResponse
(*HistoryRequest)(nil), // 2: chat.HistoryRequest
(*HistoryResponse)(nil), // 3: chat.HistoryResponse
(*SendRequest)(nil), // 4: chat.SendRequest
(*SendResponse)(nil), // 5: chat.SendResponse
(*Message)(nil), // 6: chat.Message
(*timestamp.Timestamp)(nil), // 7: google.protobuf.Timestamp
}
var file_proto_chat_proto_depIdxs = []int32{
6, // 0: chat.HistoryResponse.messages:type_name -> chat.Message
6, // 1: chat.SendResponse.message:type_name -> chat.Message
7, // 2: chat.Message.sent_at:type_name -> google.protobuf.Timestamp
0, // 3: chat.Chat.New:input_type -> chat.NewRequest
2, // 4: chat.Chat.History:input_type -> chat.HistoryRequest
4, // 5: chat.Chat.Send:input_type -> chat.SendRequest
6, // 6: chat.Chat.Connect:input_type -> chat.Message
1, // 7: chat.Chat.New:output_type -> chat.NewResponse
3, // 8: chat.Chat.History:output_type -> chat.HistoryResponse
5, // 9: chat.Chat.Send:output_type -> chat.SendResponse
6, // 10: chat.Chat.Connect:output_type -> chat.Message
7, // [7:11] is the sub-list for method output_type
3, // [3:7] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_proto_chat_proto_init() }
func file_proto_chat_proto_init() {
if File_proto_chat_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proto_chat_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NewRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_chat_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NewResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_chat_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HistoryRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_chat_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HistoryResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_chat_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SendRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_chat_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SendResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_chat_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Message); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_chat_proto_rawDesc,
NumEnums: 0,
NumMessages: 7,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_proto_chat_proto_goTypes,
DependencyIndexes: file_proto_chat_proto_depIdxs,
MessageInfos: file_proto_chat_proto_msgTypes,
}.Build()
File_proto_chat_proto = out.File
file_proto_chat_proto_rawDesc = nil
file_proto_chat_proto_goTypes = nil
file_proto_chat_proto_depIdxs = nil
}

View File

@@ -0,0 +1,245 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: proto/chat.proto
package chat
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
_ "github.com/golang/protobuf/ptypes/timestamp"
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 Chat service
func NewChatEndpoints() []*api.Endpoint {
return []*api.Endpoint{}
}
// Client API for Chat service
type ChatService interface {
// New creates a chat for a group of users. The RPC is idempotent so if it's called multiple times
// for the same users, the same response will be returned. It's good practice to design APIs as
// idempotent since this enables safe retries.
New(ctx context.Context, in *NewRequest, opts ...client.CallOption) (*NewResponse, error)
// History returns the historical messages in a chat
History(ctx context.Context, in *HistoryRequest, opts ...client.CallOption) (*HistoryResponse, error)
// Send a single message to the chat
Send(ctx context.Context, in *SendRequest, opts ...client.CallOption) (*SendResponse, error)
// Connect to a chat using a bidirectional stream enabling the client to send and recieve messages
// over a single RPC. When a message is sent on the stream, it will be added to the chat history
// and sent to the other connected users. When opening the connection, the client should provide
// the chat_id and user_id in the context so the server knows which messages to stream.
Connect(ctx context.Context, opts ...client.CallOption) (Chat_ConnectService, error)
}
type chatService struct {
c client.Client
name string
}
func NewChatService(name string, c client.Client) ChatService {
return &chatService{
c: c,
name: name,
}
}
func (c *chatService) New(ctx context.Context, in *NewRequest, opts ...client.CallOption) (*NewResponse, error) {
req := c.c.NewRequest(c.name, "Chat.New", in)
out := new(NewResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *chatService) History(ctx context.Context, in *HistoryRequest, opts ...client.CallOption) (*HistoryResponse, error) {
req := c.c.NewRequest(c.name, "Chat.History", in)
out := new(HistoryResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *chatService) Send(ctx context.Context, in *SendRequest, opts ...client.CallOption) (*SendResponse, error) {
req := c.c.NewRequest(c.name, "Chat.Send", in)
out := new(SendResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *chatService) Connect(ctx context.Context, opts ...client.CallOption) (Chat_ConnectService, error) {
req := c.c.NewRequest(c.name, "Chat.Connect", &Message{})
stream, err := c.c.Stream(ctx, req, opts...)
if err != nil {
return nil, err
}
return &chatServiceConnect{stream}, nil
}
type Chat_ConnectService interface {
Context() context.Context
SendMsg(interface{}) error
RecvMsg(interface{}) error
Close() error
Send(*Message) error
Recv() (*Message, error)
}
type chatServiceConnect struct {
stream client.Stream
}
func (x *chatServiceConnect) Close() error {
return x.stream.Close()
}
func (x *chatServiceConnect) Context() context.Context {
return x.stream.Context()
}
func (x *chatServiceConnect) SendMsg(m interface{}) error {
return x.stream.Send(m)
}
func (x *chatServiceConnect) RecvMsg(m interface{}) error {
return x.stream.Recv(m)
}
func (x *chatServiceConnect) Send(m *Message) error {
return x.stream.Send(m)
}
func (x *chatServiceConnect) Recv() (*Message, error) {
m := new(Message)
err := x.stream.Recv(m)
if err != nil {
return nil, err
}
return m, nil
}
// Server API for Chat service
type ChatHandler interface {
// New creates a chat for a group of users. The RPC is idempotent so if it's called multiple times
// for the same users, the same response will be returned. It's good practice to design APIs as
// idempotent since this enables safe retries.
New(context.Context, *NewRequest, *NewResponse) error
// History returns the historical messages in a chat
History(context.Context, *HistoryRequest, *HistoryResponse) error
// Send a single message to the chat
Send(context.Context, *SendRequest, *SendResponse) error
// Connect to a chat using a bidirectional stream enabling the client to send and recieve messages
// over a single RPC. When a message is sent on the stream, it will be added to the chat history
// and sent to the other connected users. When opening the connection, the client should provide
// the chat_id and user_id in the context so the server knows which messages to stream.
Connect(context.Context, Chat_ConnectStream) error
}
func RegisterChatHandler(s server.Server, hdlr ChatHandler, opts ...server.HandlerOption) error {
type chat interface {
New(ctx context.Context, in *NewRequest, out *NewResponse) error
History(ctx context.Context, in *HistoryRequest, out *HistoryResponse) error
Send(ctx context.Context, in *SendRequest, out *SendResponse) error
Connect(ctx context.Context, stream server.Stream) error
}
type Chat struct {
chat
}
h := &chatHandler{hdlr}
return s.Handle(s.NewHandler(&Chat{h}, opts...))
}
type chatHandler struct {
ChatHandler
}
func (h *chatHandler) New(ctx context.Context, in *NewRequest, out *NewResponse) error {
return h.ChatHandler.New(ctx, in, out)
}
func (h *chatHandler) History(ctx context.Context, in *HistoryRequest, out *HistoryResponse) error {
return h.ChatHandler.History(ctx, in, out)
}
func (h *chatHandler) Send(ctx context.Context, in *SendRequest, out *SendResponse) error {
return h.ChatHandler.Send(ctx, in, out)
}
func (h *chatHandler) Connect(ctx context.Context, stream server.Stream) error {
return h.ChatHandler.Connect(ctx, &chatConnectStream{stream})
}
type Chat_ConnectStream interface {
Context() context.Context
SendMsg(interface{}) error
RecvMsg(interface{}) error
Close() error
Send(*Message) error
Recv() (*Message, error)
}
type chatConnectStream struct {
stream server.Stream
}
func (x *chatConnectStream) Close() error {
return x.stream.Close()
}
func (x *chatConnectStream) Context() context.Context {
return x.stream.Context()
}
func (x *chatConnectStream) SendMsg(m interface{}) error {
return x.stream.Send(m)
}
func (x *chatConnectStream) RecvMsg(m interface{}) error {
return x.stream.Recv(m)
}
func (x *chatConnectStream) Send(m *Message) error {
return x.stream.Send(m)
}
func (x *chatConnectStream) Recv() (*Message, error) {
m := new(Message)
if err := x.stream.Recv(m); err != nil {
return nil, err
}
return m, nil
}

View File

@@ -0,0 +1,80 @@
syntax = "proto3";
package chat;
option go_package = "proto;chat";
import "google/protobuf/timestamp.proto";
service Chat {
// New creates a chat for a group of users. The RPC is idempotent so if it's called multiple times
// for the same users, the same response will be returned. It's good practice to design APIs as
// idempotent since this enables safe retries.
rpc New(NewRequest) returns (NewResponse);
// History returns the historical messages in a chat
rpc History(HistoryRequest) returns (HistoryResponse);
// Send a single message to the chat
rpc Send(SendRequest) returns (SendResponse);
// Connect to a chat using a bidirectional stream enabling the client to send and recieve messages
// over a single RPC. When a message is sent on the stream, it will be added to the chat history
// and sent to the other connected users. When opening the connection, the client should provide
// the chat_id and user_id in the context so the server knows which messages to stream.
rpc Connect(stream Message) returns (stream Message);
}
// NewRequest contains the infromation needed to create a new chat
message NewRequest {
repeated string user_ids = 1;
}
// NewResponse contains the chat id for the users
message NewResponse {
string chat_id = 1;
}
// HistoryRequest contains the id of the chat we want the history for. This RPC will return all
// historical messages, however in a real life application we'd introduce some form of pagination
// here, only loading the older messages when required.
message HistoryRequest {
string chat_id = 1;
}
// HistoryResponse contains the historical messages in a chat
message HistoryResponse {
repeated Message messages = 1;
}
// SendRequest contains a single message to send to a chat
message SendRequest {
// a client side id, should be validated by the server to make the request retry safe
string client_id = 1;
// id of the chat the message is being sent to / from
string chat_id = 2;
// id of the user who sent the message
string user_id = 3;
// subject of the message
string subject = 4;
// text of the message
string text = 5;
}
// SendResponse is a blank message returned when a message is successfully created
message SendResponse {
Message message = 1;
}
// Message sent to a chat
message Message {
// id of the message, allocated by the server
string id = 1;
// a client side id, should be validated by the server to make the request retry safe
string client_id = 2;
// id of the chat the message is being sent to / from
string chat_id = 3;
// id of the user who sent the message
string user_id = 4;
// time time the message was sent in unix format
google.protobuf.Timestamp sent_at = 5;
// subject of the message
string subject = 6;
// text of the message
string text = 7;
}