mirror of
https://github.com/kevin-DL/services.git
synced 2026-01-11 19:04:35 +00:00
Refactor Chats Service (#48)
This commit is contained in:
27
test/chat/Makefile
Normal file
27
test/chat/Makefile
Normal 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
87
test/chat/README.md
Normal 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
121
test/chat/client/main.go
Normal 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)
|
||||
}
|
||||
312
test/chat/handler/handler.go
Normal file
312
test/chat/handler/handler.go
Normal 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
25
test/chat/main.go
Normal 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
1
test/chat/micro.mu
Normal file
@@ -0,0 +1 @@
|
||||
service chat
|
||||
670
test/chat/proto/chat.pb.go
Normal file
670
test/chat/proto/chat.pb.go
Normal 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
|
||||
}
|
||||
245
test/chat/proto/chat.pb.micro.go
Normal file
245
test/chat/proto/chat.pb.micro.go
Normal 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
|
||||
}
|
||||
80
test/chat/proto/chat.proto
Normal file
80
test/chat/proto/chat.proto
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user