mirror of
https://github.com/kevin-DL/services.git
synced 2026-01-11 19:04:35 +00:00
remove the services we won't use
This commit is contained in:
2
chats/.gitignore
vendored
2
chats/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
|
||||
chats
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM alpine
|
||||
ADD chats /chats
|
||||
ENTRYPOINT [ "/chats" ]
|
||||
@@ -1,26 +0,0 @@
|
||||
|
||||
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/chats.proto
|
||||
|
||||
docs:
|
||||
protoc --openapi_out=. --proto_path=. --micro_out=. --go_out=. proto/chats.proto
|
||||
@redoc-cli bundle api-chats.json
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build -o chats *.go
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -v ./... -cover
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
docker build . -t chats:latest
|
||||
@@ -1,5 +0,0 @@
|
||||
Chats is a service for direct messaging
|
||||
|
||||
# Chats Service
|
||||
|
||||
The chats service enables direct messaging between one or more parties.
|
||||
@@ -1,3 +0,0 @@
|
||||
package main
|
||||
|
||||
//go:generate make proto
|
||||
@@ -1,126 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
pb "github.com/micro/services/chats/proto"
|
||||
"github.com/micro/services/pkg/tenant"
|
||||
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissingID = errors.BadRequest("MISSING_ID", "Missing ID")
|
||||
ErrMissingAuthorID = errors.BadRequest("MISSING_AUTHOR_ID", "Missing Author ID")
|
||||
ErrMissingText = errors.BadRequest("MISSING_TEXT", "Missing text")
|
||||
ErrMissingChatID = errors.BadRequest("MISSING_CHAT_ID", "Missing Chat ID")
|
||||
ErrMissingUserIDs = errors.BadRequest("MISSING_USER_IDs", "Two or more user IDs are required")
|
||||
ErrNotFound = errors.NotFound("NOT_FOUND", "Chat not found")
|
||||
)
|
||||
|
||||
type Chats struct {
|
||||
Time func() time.Time
|
||||
}
|
||||
|
||||
type Chat struct {
|
||||
ID string
|
||||
UserIDs []string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
ID string
|
||||
AuthorID string
|
||||
ChatID string
|
||||
Text string
|
||||
SentAt time.Time
|
||||
}
|
||||
|
||||
func ParseTime(v string) time.Time {
|
||||
t, err := time.Parse(time.RFC3339Nano, v)
|
||||
if err == nil {
|
||||
return t
|
||||
}
|
||||
t, err = time.Parse(time.RFC3339, v)
|
||||
if err == nil {
|
||||
return t
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (m *Message) Serialize() *pb.Message {
|
||||
return &pb.Message{
|
||||
Id: m.ID,
|
||||
AuthorId: m.AuthorID,
|
||||
ChatId: m.ChatID,
|
||||
Text: m.Text,
|
||||
SentAt: m.SentAt.Format(time.RFC3339Nano),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Chat) Index(ctx context.Context) string {
|
||||
sort.Strings(c.UserIDs)
|
||||
users := strings.Join(c.UserIDs, "-")
|
||||
|
||||
key := fmt.Sprintf("chatByUserIDs:%s", users)
|
||||
|
||||
t, ok := tenant.FromContext(ctx)
|
||||
if !ok {
|
||||
return key
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", t, key)
|
||||
}
|
||||
|
||||
func (c *Chat) Key(ctx context.Context) string {
|
||||
key := fmt.Sprintf("chat:%s", c.ID)
|
||||
|
||||
t, ok := tenant.FromContext(ctx)
|
||||
if !ok {
|
||||
return key
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", t, key)
|
||||
}
|
||||
|
||||
func (m *Message) Key(ctx context.Context) string {
|
||||
key := fmt.Sprintf("message:%s:%s", m.ChatID, m.ID)
|
||||
|
||||
t, ok := tenant.FromContext(ctx)
|
||||
if !ok {
|
||||
return key
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", t, key)
|
||||
}
|
||||
|
||||
func (m *Message) Index(ctx context.Context) string {
|
||||
key := fmt.Sprintf("messagesByChatID:%s", m.ChatID)
|
||||
|
||||
if !m.SentAt.IsZero() {
|
||||
key = fmt.Sprintf("%s:%d", key, m.SentAt.UnixNano())
|
||||
|
||||
if len(m.ID) > 0 {
|
||||
key = fmt.Sprintf("%s:%s", key, m.ID)
|
||||
}
|
||||
}
|
||||
|
||||
t, ok := tenant.FromContext(ctx)
|
||||
if !ok {
|
||||
return key
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", t, key)
|
||||
}
|
||||
|
||||
func (c *Chat) Serialize() *pb.Chat {
|
||||
return &pb.Chat{
|
||||
Id: c.ID,
|
||||
UserIds: c.UserIDs,
|
||||
CreatedAt: c.CreatedAt.Format(time.RFC3339Nano),
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/store"
|
||||
"github.com/micro/micro/v3/service/store/memory"
|
||||
"github.com/micro/services/chats/handler"
|
||||
pb "github.com/micro/services/chats/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testHandler(t *testing.T) *handler.Chats {
|
||||
store.DefaultStore = memory.NewStore()
|
||||
return &handler.Chats{Time: func() time.Time { return time.Unix(1611327673, 0) }}
|
||||
}
|
||||
|
||||
func assertChatsMatch(t *testing.T, exp, act *pb.Chat) {
|
||||
if act == nil {
|
||||
t.Errorf("Chat not returned")
|
||||
return
|
||||
}
|
||||
|
||||
// adapt this check so we can reuse the func in testing create, where we don't know the exact id
|
||||
// which will be generated
|
||||
if len(exp.Id) > 0 {
|
||||
assert.Equal(t, exp.Id, act.Id)
|
||||
} else {
|
||||
assert.NotEmpty(t, act.Id)
|
||||
}
|
||||
|
||||
assert.Equal(t, exp.UserIds, act.UserIds)
|
||||
|
||||
if len(act.CreatedAt) == 0 {
|
||||
t.Errorf("CreatedAt not set")
|
||||
return
|
||||
}
|
||||
|
||||
assert.True(t, exp.CreatedAt == act.CreatedAt)
|
||||
}
|
||||
|
||||
func assertMessagesMatch(t *testing.T, exp, act *pb.Message) {
|
||||
if act == nil {
|
||||
t.Errorf("Message not returned")
|
||||
return
|
||||
}
|
||||
|
||||
// adapt these checks so we can reuse the func in testing create, where we don't know the exact id /
|
||||
// idempotent_id which will be generated
|
||||
if len(exp.Id) > 0 {
|
||||
assert.Equal(t, exp.Id, act.Id)
|
||||
} else {
|
||||
assert.NotEmpty(t, act.Id)
|
||||
}
|
||||
assert.Equal(t, exp.Text, act.Text)
|
||||
assert.Equal(t, exp.AuthorId, act.AuthorId)
|
||||
assert.Equal(t, exp.ChatId, act.ChatId)
|
||||
|
||||
if len(act.SentAt) == 0 {
|
||||
t.Errorf("SentAt not set")
|
||||
return
|
||||
}
|
||||
|
||||
assert.True(t, exp.SentAt == act.SentAt)
|
||||
}
|
||||
|
||||
func microAccountCtx() context.Context {
|
||||
return auth.ContextWithAccount(context.TODO(), &auth.Account{
|
||||
Issuer: "micro",
|
||||
})
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
"github.com/micro/micro/v3/service/store"
|
||||
pb "github.com/micro/services/chats/proto"
|
||||
)
|
||||
|
||||
// Create a chat between two or more users, if a chat already exists for these users, the existing
|
||||
// chat will be returned
|
||||
func (c *Chats) CreateChat(ctx context.Context, req *pb.CreateChatRequest, rsp *pb.CreateChatResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.UserIds) < 2 {
|
||||
return ErrMissingUserIDs
|
||||
}
|
||||
|
||||
// sort the user ids
|
||||
sort.Strings(req.UserIds)
|
||||
|
||||
id := uuid.New().String()
|
||||
if len(req.Id) > 0 {
|
||||
id = req.Id
|
||||
}
|
||||
|
||||
// construct the chat
|
||||
chat := &Chat{
|
||||
ID: id,
|
||||
CreatedAt: time.Now(),
|
||||
UserIDs: req.UserIds,
|
||||
}
|
||||
|
||||
// read the chat by the unique composition of ids
|
||||
recs, err := store.Read(chat.Key(ctx), store.ReadLimit(1))
|
||||
if err == nil && len(recs) == 1 {
|
||||
// found an existing record
|
||||
recs[0].Decode(&chat)
|
||||
rsp.Chat = chat.Serialize()
|
||||
return nil
|
||||
}
|
||||
|
||||
// if not found check it exists by user index key
|
||||
if err == store.ErrNotFound {
|
||||
recs, err = store.Read(chat.Index(ctx), store.ReadLimit(1))
|
||||
if err == nil && len(recs) > 0 {
|
||||
recs[0].Decode(&chat)
|
||||
rsp.Chat = chat.Serialize()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ok otherwise we're creating an entirely new record
|
||||
newRec := store.NewRecord(chat.Key(ctx), chat)
|
||||
if err := store.Write(newRec); err != nil {
|
||||
logger.Errorf("Error creating chat: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
|
||||
}
|
||||
|
||||
// write the user composite key
|
||||
newRec = store.NewRecord(chat.Index(ctx), chat)
|
||||
if err := store.Write(newRec); err != nil {
|
||||
logger.Errorf("Error creating chat: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
|
||||
}
|
||||
|
||||
// return the record
|
||||
rsp.Chat = chat.Serialize()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/services/chats/handler"
|
||||
pb "github.com/micro/services/chats/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreateChat(t *testing.T) {
|
||||
userIDs := []string{uuid.New().String(), uuid.New().String()}
|
||||
|
||||
tt := []struct {
|
||||
Name string
|
||||
UserIDs []string
|
||||
Error error
|
||||
}{
|
||||
{
|
||||
Name: "NoUserIDs",
|
||||
Error: handler.ErrMissingUserIDs,
|
||||
},
|
||||
{
|
||||
Name: "OneUserID",
|
||||
UserIDs: userIDs[1:],
|
||||
Error: handler.ErrMissingUserIDs,
|
||||
},
|
||||
{
|
||||
Name: "Valid",
|
||||
UserIDs: userIDs,
|
||||
},
|
||||
{
|
||||
Name: "Repeat",
|
||||
UserIDs: userIDs,
|
||||
},
|
||||
}
|
||||
|
||||
var chat *pb.Chat
|
||||
h := testHandler(t)
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
var rsp pb.CreateChatResponse
|
||||
err := h.CreateChat(microAccountCtx(), &pb.CreateChatRequest{
|
||||
UserIds: tc.UserIDs,
|
||||
}, &rsp)
|
||||
|
||||
assert.Equal(t, tc.Error, err)
|
||||
if tc.Error != nil {
|
||||
return
|
||||
}
|
||||
|
||||
assert.NotNil(t, rsp.Chat)
|
||||
if chat == nil {
|
||||
chat = rsp.Chat
|
||||
} else {
|
||||
assertChatsMatch(t, chat, rsp.Chat)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
"github.com/micro/micro/v3/service/store"
|
||||
pb "github.com/micro/services/chats/proto"
|
||||
)
|
||||
|
||||
// Create a message within a chat
|
||||
func (c *Chats) SendMessage(ctx context.Context, req *pb.SendMessageRequest, rsp *pb.SendMessageResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.AuthorId) == 0 {
|
||||
return ErrMissingAuthorID
|
||||
}
|
||||
if len(req.ChatId) == 0 {
|
||||
return ErrMissingChatID
|
||||
}
|
||||
if len(req.Text) == 0 {
|
||||
return ErrMissingText
|
||||
}
|
||||
|
||||
chat := &Chat{
|
||||
ID: req.ChatId,
|
||||
}
|
||||
|
||||
recs, err := store.Read(chat.Key(ctx), store.ReadLimit(1))
|
||||
if err == store.ErrNotFound {
|
||||
return ErrNotFound
|
||||
} else if err != nil {
|
||||
logger.Errorf("Error reading chat: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
|
||||
}
|
||||
|
||||
// create the message
|
||||
msg := &Message{
|
||||
ID: req.Id,
|
||||
Text: req.Text,
|
||||
AuthorID: req.AuthorId,
|
||||
ChatID: req.ChatId,
|
||||
SentAt: c.Time(),
|
||||
}
|
||||
if len(msg.ID) == 0 {
|
||||
msg.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
// check if the message already exists
|
||||
recs, err = store.Read(msg.Key(ctx), store.ReadLimit(1))
|
||||
if err == nil && len(recs) == 1 {
|
||||
// return the existing message
|
||||
msg = &Message{}
|
||||
recs[0].Decode(&msg)
|
||||
rsp.Message = msg.Serialize()
|
||||
return nil
|
||||
}
|
||||
|
||||
// if there's an error then return
|
||||
if err != nil && err != store.ErrNotFound {
|
||||
logger.Errorf("Error creating message: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
|
||||
}
|
||||
|
||||
// otherwise write the record
|
||||
if err := store.Write(store.NewRecord(msg.Key(ctx), msg)); err != nil {
|
||||
logger.Errorf("Error creating message: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
|
||||
}
|
||||
|
||||
// write the time based index
|
||||
if err := store.Write(store.NewRecord(msg.Index(ctx), msg)); err == nil {
|
||||
rsp.Message = msg.Serialize()
|
||||
return nil
|
||||
} else if err != nil {
|
||||
logger.Errorf("Error creating message: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/micro/services/chats/handler"
|
||||
pb "github.com/micro/services/chats/proto"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSendMessage(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
// seed some data
|
||||
var cRsp pb.CreateChatResponse
|
||||
err := h.CreateChat(microAccountCtx(), &pb.CreateChatRequest{
|
||||
UserIds: []string{uuid.New().String(), uuid.New().String()},
|
||||
}, &cRsp)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating chat: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
iid := uuid.New().String()
|
||||
tt := []struct {
|
||||
Name string
|
||||
AuthorID string
|
||||
ChatID string
|
||||
Text string
|
||||
Error error
|
||||
ID string
|
||||
}{
|
||||
{
|
||||
Name: "MissingChatID",
|
||||
Text: "HelloWorld",
|
||||
AuthorID: uuid.New().String(),
|
||||
Error: handler.ErrMissingChatID,
|
||||
},
|
||||
{
|
||||
Name: "MissingAuthorID",
|
||||
ChatID: uuid.New().String(),
|
||||
Text: "HelloWorld",
|
||||
Error: handler.ErrMissingAuthorID,
|
||||
},
|
||||
{
|
||||
Name: "MissingText",
|
||||
ChatID: uuid.New().String(),
|
||||
AuthorID: uuid.New().String(),
|
||||
Error: handler.ErrMissingText,
|
||||
},
|
||||
{
|
||||
Name: "ChatNotFound",
|
||||
ChatID: uuid.New().String(),
|
||||
AuthorID: uuid.New().String(),
|
||||
Text: "HelloWorld",
|
||||
Error: handler.ErrNotFound,
|
||||
},
|
||||
{
|
||||
Name: "WithoutID",
|
||||
ChatID: cRsp.Chat.Id,
|
||||
AuthorID: uuid.New().String(),
|
||||
Text: "HelloWorld",
|
||||
},
|
||||
{
|
||||
Name: "WithID",
|
||||
ChatID: cRsp.Chat.Id,
|
||||
AuthorID: "johndoe",
|
||||
Text: "HelloWorld",
|
||||
ID: iid,
|
||||
},
|
||||
{
|
||||
Name: "RepeatID",
|
||||
ChatID: cRsp.Chat.Id,
|
||||
AuthorID: "johndoe",
|
||||
Text: "HelloWorld",
|
||||
ID: iid,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
var rsp pb.SendMessageResponse
|
||||
err := h.SendMessage(microAccountCtx(), &pb.SendMessageRequest{
|
||||
AuthorId: tc.AuthorID,
|
||||
ChatId: tc.ChatID,
|
||||
Text: tc.Text,
|
||||
Id: tc.ID,
|
||||
}, &rsp)
|
||||
|
||||
assert.Equal(t, tc.Error, err)
|
||||
if tc.Error != nil {
|
||||
assert.Nil(t, rsp.Message)
|
||||
return
|
||||
}
|
||||
|
||||
assertMessagesMatch(t, &pb.Message{
|
||||
AuthorId: tc.AuthorID,
|
||||
ChatId: tc.ChatID,
|
||||
SentAt: h.Time().Format(time.RFC3339Nano),
|
||||
Text: tc.Text,
|
||||
Id: tc.ID,
|
||||
}, rsp.Message)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
"github.com/micro/micro/v3/service/store"
|
||||
pb "github.com/micro/services/chats/proto"
|
||||
)
|
||||
|
||||
const DefaultLimit = 25
|
||||
|
||||
// List the messages within a chat in reverse chronological order, using sent_before to
|
||||
// offset as older messages need to be loaded
|
||||
func (c *Chats) ListMessages(ctx context.Context, req *pb.ListMessagesRequest, rsp *pb.ListMessagesResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.ChatId) == 0 {
|
||||
return ErrMissingChatID
|
||||
}
|
||||
|
||||
message := &Message{
|
||||
ChatID: req.ChatId,
|
||||
}
|
||||
|
||||
// default order is descending
|
||||
order := store.OrderDesc
|
||||
if req.Order == "asc" {
|
||||
order = store.OrderAsc
|
||||
}
|
||||
|
||||
opts := []store.ReadOption{
|
||||
store.ReadPrefix(),
|
||||
store.ReadOrder(order),
|
||||
}
|
||||
|
||||
if req.Limit > 0 {
|
||||
opts = append(opts, store.ReadLimit(uint(req.Limit)))
|
||||
} else {
|
||||
opts = append(opts, store.ReadLimit(uint(DefaultLimit)))
|
||||
}
|
||||
if req.Offset > 0 {
|
||||
opts = append(opts, store.ReadOffset(uint(req.Offset)))
|
||||
}
|
||||
|
||||
// read all the records with the chat ID suffix
|
||||
recs, err := store.Read(message.Index(ctx), opts...)
|
||||
if err != nil {
|
||||
logger.Errorf("Error reading messages: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
|
||||
}
|
||||
|
||||
// return all the messages
|
||||
for _, rec := range recs {
|
||||
m := &Message{}
|
||||
rec.Decode(&m)
|
||||
if len(m.ID) == 0 || m.ChatID != req.ChatId {
|
||||
continue
|
||||
}
|
||||
rsp.Messages = append(rsp.Messages, m.Serialize())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/services/chats/handler"
|
||||
pb "github.com/micro/services/chats/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestListMessages(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
h.Time = time.Now
|
||||
|
||||
// seed some data
|
||||
var chatRsp pb.CreateChatResponse
|
||||
err := h.CreateChat(microAccountCtx(), &pb.CreateChatRequest{
|
||||
UserIds: []string{uuid.New().String(), uuid.New().String()},
|
||||
}, &chatRsp)
|
||||
assert.NoError(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
msgs := make([]*pb.Message, 50)
|
||||
for i := 0; i < len(msgs); i++ {
|
||||
var rsp pb.SendMessageResponse
|
||||
err := h.SendMessage(microAccountCtx(), &pb.SendMessageRequest{
|
||||
ChatId: chatRsp.Chat.Id,
|
||||
AuthorId: uuid.New().String(),
|
||||
Text: strconv.Itoa(i),
|
||||
}, &rsp)
|
||||
assert.NoError(t, err)
|
||||
msgs[i] = rsp.Message
|
||||
}
|
||||
|
||||
t.Run("MissingChatID", func(t *testing.T) {
|
||||
var rsp pb.ListMessagesResponse
|
||||
err := h.ListMessages(microAccountCtx(), &pb.ListMessagesRequest{}, &rsp)
|
||||
assert.Equal(t, handler.ErrMissingChatID, err)
|
||||
assert.Nil(t, rsp.Messages)
|
||||
})
|
||||
|
||||
t.Run("NoOffset", func(t *testing.T) {
|
||||
var rsp pb.ListMessagesResponse
|
||||
err := h.ListMessages(microAccountCtx(), &pb.ListMessagesRequest{
|
||||
ChatId: chatRsp.Chat.Id,
|
||||
}, &rsp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
if len(rsp.Messages) != handler.DefaultLimit {
|
||||
t.Fatalf("Expected %v messages but got %v", handler.DefaultLimit, len(rsp.Messages))
|
||||
return
|
||||
}
|
||||
expected := msgs[25:]
|
||||
sortMessages(rsp.Messages)
|
||||
for i, msg := range rsp.Messages {
|
||||
assertMessagesMatch(t, expected[i], msg)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("LimitSet", func(t *testing.T) {
|
||||
var rsp pb.ListMessagesResponse
|
||||
err := h.ListMessages(microAccountCtx(), &pb.ListMessagesRequest{
|
||||
ChatId: chatRsp.Chat.Id,
|
||||
Limit: 10,
|
||||
}, &rsp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
if len(rsp.Messages) != 10 {
|
||||
t.Fatalf("Expected %v messages but got %v", 10, len(rsp.Messages))
|
||||
return
|
||||
}
|
||||
expected := msgs[40:]
|
||||
sortMessages(rsp.Messages)
|
||||
for i, msg := range rsp.Messages {
|
||||
assertMessagesMatch(t, expected[i], msg)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("OffsetAndLimit", func(t *testing.T) {
|
||||
var rsp pb.ListMessagesResponse
|
||||
err := h.ListMessages(microAccountCtx(), &pb.ListMessagesRequest{
|
||||
ChatId: chatRsp.Chat.Id,
|
||||
Limit: 5,
|
||||
Offset: 15,
|
||||
}, &rsp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
if len(rsp.Messages) != 5 {
|
||||
t.Fatalf("Expected %v messages but got %v", 5, len(rsp.Messages))
|
||||
return
|
||||
}
|
||||
expected := msgs[30:35]
|
||||
sortMessages(rsp.Messages)
|
||||
for i, msg := range rsp.Messages {
|
||||
assertMessagesMatch(t, expected[i], msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// sortMessages by the time they were sent
|
||||
func sortMessages(msgs []*pb.Message) {
|
||||
sort.Slice(msgs, func(i, j int) bool {
|
||||
if len(msgs[i].SentAt) == 0 || len(msgs[j].SentAt) == 0 {
|
||||
return true
|
||||
}
|
||||
return handler.ParseTime(msgs[i].SentAt).Before(handler.ParseTime(msgs[j].SentAt))
|
||||
})
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/micro/services/chats/handler"
|
||||
pb "github.com/micro/services/chats/proto"
|
||||
|
||||
"github.com/micro/micro/v3/service"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create service
|
||||
srv := service.New(
|
||||
service.Name("chats"),
|
||||
service.Version("latest"),
|
||||
)
|
||||
|
||||
h := &handler.Chats{Time: time.Now}
|
||||
// Register handler
|
||||
pb.RegisterChatsHandler(srv.Server(), h)
|
||||
|
||||
// Run service
|
||||
if err := srv.Run(); err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
service chats
|
||||
@@ -1,746 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.6
|
||||
// source: proto/chats.proto
|
||||
|
||||
package chats
|
||||
|
||||
import (
|
||||
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)
|
||||
)
|
||||
|
||||
type Chat struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// unique id of the chat
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
// list of users in the chat
|
||||
UserIds []string `protobuf:"bytes,2,rep,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"`
|
||||
// RFC3339Nano timestamp
|
||||
CreatedAt string `protobuf:"bytes,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Chat) Reset() {
|
||||
*x = Chat{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_chats_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Chat) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Chat) ProtoMessage() {}
|
||||
|
||||
func (x *Chat) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_chats_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 Chat.ProtoReflect.Descriptor instead.
|
||||
func (*Chat) Descriptor() ([]byte, []int) {
|
||||
return file_proto_chats_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Chat) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Chat) GetUserIds() []string {
|
||||
if x != nil {
|
||||
return x.UserIds
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Chat) GetCreatedAt() string {
|
||||
if x != nil {
|
||||
return x.CreatedAt
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// unique id of the message
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
// user id of the message
|
||||
AuthorId string `protobuf:"bytes,2,opt,name=author_id,json=authorId,proto3" json:"author_id,omitempty"`
|
||||
// chat id the message belongs to
|
||||
ChatId string `protobuf:"bytes,3,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"`
|
||||
// text within the message
|
||||
Text string `protobuf:"bytes,4,opt,name=text,proto3" json:"text,omitempty"`
|
||||
// RFC3339Nano timestamp
|
||||
SentAt string `protobuf:"bytes,5,opt,name=sent_at,json=sentAt,proto3" json:"sent_at,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Message) Reset() {
|
||||
*x = Message{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_chats_proto_msgTypes[1]
|
||||
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_chats_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 Message.ProtoReflect.Descriptor instead.
|
||||
func (*Message) Descriptor() ([]byte, []int) {
|
||||
return file_proto_chats_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Message) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Message) GetAuthorId() string {
|
||||
if x != nil {
|
||||
return x.AuthorId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Message) GetChatId() string {
|
||||
if x != nil {
|
||||
return x.ChatId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Message) GetText() string {
|
||||
if x != nil {
|
||||
return x.Text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Message) GetSentAt() string {
|
||||
if x != nil {
|
||||
return x.SentAt
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Create a new chat between mulitple users
|
||||
type CreateChatRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The chat ID
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
// List of users in the chat
|
||||
UserIds []string `protobuf:"bytes,2,rep,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"`
|
||||
}
|
||||
|
||||
func (x *CreateChatRequest) Reset() {
|
||||
*x = CreateChatRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_chats_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *CreateChatRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CreateChatRequest) ProtoMessage() {}
|
||||
|
||||
func (x *CreateChatRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_chats_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 CreateChatRequest.ProtoReflect.Descriptor instead.
|
||||
func (*CreateChatRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_chats_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *CreateChatRequest) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CreateChatRequest) GetUserIds() []string {
|
||||
if x != nil {
|
||||
return x.UserIds
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type CreateChatResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Chat *Chat `protobuf:"bytes,1,opt,name=chat,proto3" json:"chat,omitempty"`
|
||||
}
|
||||
|
||||
func (x *CreateChatResponse) Reset() {
|
||||
*x = CreateChatResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_chats_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *CreateChatResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CreateChatResponse) ProtoMessage() {}
|
||||
|
||||
func (x *CreateChatResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_chats_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 CreateChatResponse.ProtoReflect.Descriptor instead.
|
||||
func (*CreateChatResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_chats_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *CreateChatResponse) GetChat() *Chat {
|
||||
if x != nil {
|
||||
return x.Chat
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send a message to a chat room
|
||||
type SendMessageRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
ChatId string `protobuf:"bytes,2,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"`
|
||||
AuthorId string `protobuf:"bytes,3,opt,name=author_id,json=authorId,proto3" json:"author_id,omitempty"`
|
||||
Text string `protobuf:"bytes,4,opt,name=text,proto3" json:"text,omitempty"`
|
||||
}
|
||||
|
||||
func (x *SendMessageRequest) Reset() {
|
||||
*x = SendMessageRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_chats_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *SendMessageRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SendMessageRequest) ProtoMessage() {}
|
||||
|
||||
func (x *SendMessageRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_chats_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 SendMessageRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SendMessageRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_chats_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *SendMessageRequest) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SendMessageRequest) GetChatId() string {
|
||||
if x != nil {
|
||||
return x.ChatId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SendMessageRequest) GetAuthorId() string {
|
||||
if x != nil {
|
||||
return x.AuthorId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SendMessageRequest) GetText() string {
|
||||
if x != nil {
|
||||
return x.Text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type SendMessageResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Message *Message `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (x *SendMessageResponse) Reset() {
|
||||
*x = SendMessageResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_chats_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *SendMessageResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SendMessageResponse) ProtoMessage() {}
|
||||
|
||||
func (x *SendMessageResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_chats_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 SendMessageResponse.ProtoReflect.Descriptor instead.
|
||||
func (*SendMessageResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_chats_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *SendMessageResponse) GetMessage() *Message {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List messages within a chat
|
||||
type ListMessagesRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// unique id of the chat
|
||||
ChatId string `protobuf:"bytes,1,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"`
|
||||
// limit the number of messages
|
||||
Limit int64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"`
|
||||
// offset for the messages
|
||||
Offset int64 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"`
|
||||
// order "asc" or "desc" (defaults to reverse chronological)
|
||||
Order string `protobuf:"bytes,4,opt,name=order,proto3" json:"order,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ListMessagesRequest) Reset() {
|
||||
*x = ListMessagesRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_chats_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListMessagesRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListMessagesRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ListMessagesRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_chats_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 ListMessagesRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ListMessagesRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_chats_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *ListMessagesRequest) GetChatId() string {
|
||||
if x != nil {
|
||||
return x.ChatId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListMessagesRequest) GetLimit() int64 {
|
||||
if x != nil {
|
||||
return x.Limit
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ListMessagesRequest) GetOffset() int64 {
|
||||
if x != nil {
|
||||
return x.Offset
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ListMessagesRequest) GetOrder() string {
|
||||
if x != nil {
|
||||
return x.Order
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ListMessagesResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Messages []*Message `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ListMessagesResponse) Reset() {
|
||||
*x = ListMessagesResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_chats_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListMessagesResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListMessagesResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ListMessagesResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_chats_proto_msgTypes[7]
|
||||
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 ListMessagesResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ListMessagesResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_chats_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *ListMessagesResponse) GetMessages() []*Message {
|
||||
if x != nil {
|
||||
return x.Messages
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_proto_chats_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_proto_chats_proto_rawDesc = []byte{
|
||||
0x0a, 0x11, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x68, 0x61, 0x74, 0x73, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x12, 0x05, 0x63, 0x68, 0x61, 0x74, 0x73, 0x22, 0x50, 0x0a, 0x04, 0x43, 0x68,
|
||||
0x61, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
|
||||
0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02,
|
||||
0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x1d, 0x0a,
|
||||
0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x7c, 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, 0x61, 0x75, 0x74, 0x68, 0x6f,
|
||||
0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68,
|
||||
0x6f, 0x72, 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, 0x12, 0x0a,
|
||||
0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78,
|
||||
0x74, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x74, 0x41, 0x74, 0x22, 0x3e, 0x0a, 0x11, 0x43, 0x72,
|
||||
0x65, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
|
||||
0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
|
||||
0x09, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, 0x35, 0x0a, 0x12, 0x43, 0x72,
|
||||
0x65, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x12, 0x1f, 0x0a, 0x04, 0x63, 0x68, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b,
|
||||
0x2e, 0x63, 0x68, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x68, 0x61, 0x74, 0x52, 0x04, 0x63, 0x68, 0x61,
|
||||
0x74, 0x22, 0x6e, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 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, 0x1b, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a,
|
||||
0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78,
|
||||
0x74, 0x22, 0x3f, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73,
|
||||
0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x68, 0x61, 0x74,
|
||||
0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61,
|
||||
0x67, 0x65, 0x22, 0x72, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
||||
0x65, 0x73, 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, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73,
|
||||
0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74,
|
||||
0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x42, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65,
|
||||
0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a,
|
||||
0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
|
||||
0x32, 0x0e, 0x2e, 0x63, 0x68, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||
0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x32, 0xd9, 0x01, 0x0a, 0x05, 0x43,
|
||||
0x68, 0x61, 0x74, 0x73, 0x12, 0x41, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x68,
|
||||
0x61, 0x74, 0x12, 0x18, 0x2e, 0x63, 0x68, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74,
|
||||
0x65, 0x43, 0x68, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63,
|
||||
0x68, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x74, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x4d,
|
||||
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x63, 0x68, 0x61, 0x74, 0x73, 0x2e, 0x53,
|
||||
0x65, 0x6e, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x1a, 0x2e, 0x63, 0x68, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x65,
|
||||
0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a,
|
||||
0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0x1a, 0x2e,
|
||||
0x63, 0x68, 0x61, 0x74, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
||||
0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x63, 0x68, 0x61, 0x74,
|
||||
0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x3b, 0x63, 0x68, 0x61, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_proto_chats_proto_rawDescOnce sync.Once
|
||||
file_proto_chats_proto_rawDescData = file_proto_chats_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_proto_chats_proto_rawDescGZIP() []byte {
|
||||
file_proto_chats_proto_rawDescOnce.Do(func() {
|
||||
file_proto_chats_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_chats_proto_rawDescData)
|
||||
})
|
||||
return file_proto_chats_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proto_chats_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
var file_proto_chats_proto_goTypes = []interface{}{
|
||||
(*Chat)(nil), // 0: chats.Chat
|
||||
(*Message)(nil), // 1: chats.Message
|
||||
(*CreateChatRequest)(nil), // 2: chats.CreateChatRequest
|
||||
(*CreateChatResponse)(nil), // 3: chats.CreateChatResponse
|
||||
(*SendMessageRequest)(nil), // 4: chats.SendMessageRequest
|
||||
(*SendMessageResponse)(nil), // 5: chats.SendMessageResponse
|
||||
(*ListMessagesRequest)(nil), // 6: chats.ListMessagesRequest
|
||||
(*ListMessagesResponse)(nil), // 7: chats.ListMessagesResponse
|
||||
}
|
||||
var file_proto_chats_proto_depIdxs = []int32{
|
||||
0, // 0: chats.CreateChatResponse.chat:type_name -> chats.Chat
|
||||
1, // 1: chats.SendMessageResponse.message:type_name -> chats.Message
|
||||
1, // 2: chats.ListMessagesResponse.messages:type_name -> chats.Message
|
||||
2, // 3: chats.Chats.CreateChat:input_type -> chats.CreateChatRequest
|
||||
4, // 4: chats.Chats.SendMessage:input_type -> chats.SendMessageRequest
|
||||
6, // 5: chats.Chats.ListMessages:input_type -> chats.ListMessagesRequest
|
||||
3, // 6: chats.Chats.CreateChat:output_type -> chats.CreateChatResponse
|
||||
5, // 7: chats.Chats.SendMessage:output_type -> chats.SendMessageResponse
|
||||
7, // 8: chats.Chats.ListMessages:output_type -> chats.ListMessagesResponse
|
||||
6, // [6:9] is the sub-list for method output_type
|
||||
3, // [3:6] 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_chats_proto_init() }
|
||||
func file_proto_chats_proto_init() {
|
||||
if File_proto_chats_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_proto_chats_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Chat); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_chats_proto_msgTypes[1].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
|
||||
}
|
||||
}
|
||||
file_proto_chats_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*CreateChatRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_chats_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*CreateChatResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_chats_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*SendMessageRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_chats_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*SendMessageResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_chats_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ListMessagesRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_chats_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ListMessagesResponse); 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_chats_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 8,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_proto_chats_proto_goTypes,
|
||||
DependencyIndexes: file_proto_chats_proto_depIdxs,
|
||||
MessageInfos: file_proto_chats_proto_msgTypes,
|
||||
}.Build()
|
||||
File_proto_chats_proto = out.File
|
||||
file_proto_chats_proto_rawDesc = nil
|
||||
file_proto_chats_proto_goTypes = nil
|
||||
file_proto_chats_proto_depIdxs = nil
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
// Code generated by protoc-gen-micro. DO NOT EDIT.
|
||||
// source: proto/chats.proto
|
||||
|
||||
package chats
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
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 Chats service
|
||||
|
||||
func NewChatsEndpoints() []*api.Endpoint {
|
||||
return []*api.Endpoint{}
|
||||
}
|
||||
|
||||
// Client API for Chats service
|
||||
|
||||
type ChatsService interface {
|
||||
// Create a chat between two or more users, if a chat already exists for these users, the existing
|
||||
// chat will be returned
|
||||
CreateChat(ctx context.Context, in *CreateChatRequest, opts ...client.CallOption) (*CreateChatResponse, error)
|
||||
// Create a message within a chat
|
||||
SendMessage(ctx context.Context, in *SendMessageRequest, opts ...client.CallOption) (*SendMessageResponse, error)
|
||||
// List the messages within a chat in reverse chronological order, using sent_before to
|
||||
// offset as older messages need to be loaded
|
||||
ListMessages(ctx context.Context, in *ListMessagesRequest, opts ...client.CallOption) (*ListMessagesResponse, error)
|
||||
}
|
||||
|
||||
type chatsService struct {
|
||||
c client.Client
|
||||
name string
|
||||
}
|
||||
|
||||
func NewChatsService(name string, c client.Client) ChatsService {
|
||||
return &chatsService{
|
||||
c: c,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *chatsService) CreateChat(ctx context.Context, in *CreateChatRequest, opts ...client.CallOption) (*CreateChatResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Chats.CreateChat", in)
|
||||
out := new(CreateChatResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *chatsService) SendMessage(ctx context.Context, in *SendMessageRequest, opts ...client.CallOption) (*SendMessageResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Chats.SendMessage", in)
|
||||
out := new(SendMessageResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *chatsService) ListMessages(ctx context.Context, in *ListMessagesRequest, opts ...client.CallOption) (*ListMessagesResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Chats.ListMessages", in)
|
||||
out := new(ListMessagesResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Chats service
|
||||
|
||||
type ChatsHandler interface {
|
||||
// Create a chat between two or more users, if a chat already exists for these users, the existing
|
||||
// chat will be returned
|
||||
CreateChat(context.Context, *CreateChatRequest, *CreateChatResponse) error
|
||||
// Create a message within a chat
|
||||
SendMessage(context.Context, *SendMessageRequest, *SendMessageResponse) error
|
||||
// List the messages within a chat in reverse chronological order, using sent_before to
|
||||
// offset as older messages need to be loaded
|
||||
ListMessages(context.Context, *ListMessagesRequest, *ListMessagesResponse) error
|
||||
}
|
||||
|
||||
func RegisterChatsHandler(s server.Server, hdlr ChatsHandler, opts ...server.HandlerOption) error {
|
||||
type chats interface {
|
||||
CreateChat(ctx context.Context, in *CreateChatRequest, out *CreateChatResponse) error
|
||||
SendMessage(ctx context.Context, in *SendMessageRequest, out *SendMessageResponse) error
|
||||
ListMessages(ctx context.Context, in *ListMessagesRequest, out *ListMessagesResponse) error
|
||||
}
|
||||
type Chats struct {
|
||||
chats
|
||||
}
|
||||
h := &chatsHandler{hdlr}
|
||||
return s.Handle(s.NewHandler(&Chats{h}, opts...))
|
||||
}
|
||||
|
||||
type chatsHandler struct {
|
||||
ChatsHandler
|
||||
}
|
||||
|
||||
func (h *chatsHandler) CreateChat(ctx context.Context, in *CreateChatRequest, out *CreateChatResponse) error {
|
||||
return h.ChatsHandler.CreateChat(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *chatsHandler) SendMessage(ctx context.Context, in *SendMessageRequest, out *SendMessageResponse) error {
|
||||
return h.ChatsHandler.SendMessage(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *chatsHandler) ListMessages(ctx context.Context, in *ListMessagesRequest, out *ListMessagesResponse) error {
|
||||
return h.ChatsHandler.ListMessages(ctx, in, out)
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package chats;
|
||||
|
||||
option go_package = "./proto;chats";
|
||||
|
||||
service Chats {
|
||||
// Create a chat between two or more users, if a chat already exists for these users, the existing
|
||||
// chat will be returned
|
||||
rpc CreateChat(CreateChatRequest) returns (CreateChatResponse);
|
||||
// Create a message within a chat
|
||||
rpc SendMessage(SendMessageRequest) returns (SendMessageResponse);
|
||||
// List the messages within a chat in reverse chronological order, using sent_before to
|
||||
// offset as older messages need to be loaded
|
||||
rpc ListMessages(ListMessagesRequest) returns (ListMessagesResponse);
|
||||
}
|
||||
|
||||
message Chat {
|
||||
// unique id of the chat
|
||||
string id = 1;
|
||||
// list of users in the chat
|
||||
repeated string user_ids = 2;
|
||||
// RFC3339 Nano timestamp e.g 2006-01-02T15:04:05.999999999Z07:00
|
||||
string created_at = 3;
|
||||
}
|
||||
|
||||
message Message {
|
||||
// unique id of the message
|
||||
string id = 1;
|
||||
// user id of the message
|
||||
string author_id = 2;
|
||||
// chat id the message belongs to
|
||||
string chat_id = 3;
|
||||
// text within the message
|
||||
string text = 4;
|
||||
// RFC3339 Nano timestamp e.g 2006-01-02T15:04:05.999999999Z07:00
|
||||
string sent_at = 5;
|
||||
}
|
||||
|
||||
// Create a new chat between mulitple users
|
||||
message CreateChatRequest {
|
||||
// The chat ID
|
||||
string id = 1;
|
||||
// List of users in the chat
|
||||
repeated string user_ids = 2;
|
||||
}
|
||||
|
||||
message CreateChatResponse {
|
||||
Chat chat = 1;
|
||||
}
|
||||
|
||||
// Send a message to a chat room
|
||||
message SendMessageRequest {
|
||||
string id = 1;
|
||||
string chat_id = 2;
|
||||
string author_id = 3;
|
||||
string text = 4;
|
||||
}
|
||||
|
||||
message SendMessageResponse {
|
||||
Message message = 1;
|
||||
}
|
||||
|
||||
// List messages within a chat
|
||||
message ListMessagesRequest {
|
||||
// unique id of the chat
|
||||
string chat_id = 1;
|
||||
// limit the number of messages
|
||||
int64 limit = 2;
|
||||
// offset for the messages
|
||||
int64 offset = 3;
|
||||
// order "asc" or "desc" (defaults to reverse chronological)
|
||||
string order = 4;
|
||||
}
|
||||
|
||||
message ListMessagesResponse {
|
||||
repeated Message messages = 1;
|
||||
}
|
||||
2
groups/.gitignore
vendored
2
groups/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
|
||||
groups
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM alpine
|
||||
ADD groups /groups
|
||||
ENTRYPOINT [ "/groups" ]
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
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/groups.proto
|
||||
|
||||
.PHONY: docs
|
||||
docs:
|
||||
protoc --openapi_out=. --proto_path=. --micro_out=. --go_out=:. proto/groups.proto
|
||||
@redoc-cli bundle api-groups.json
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build -o groups *.go
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -v ./... -cover
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
docker build . -t groups:latest
|
||||
@@ -1,6 +0,0 @@
|
||||
Manage group memberships
|
||||
|
||||
# Groups Service
|
||||
|
||||
The group service is a basic CRUD service for group membership management. Create groups, add members and lookup which groups a user is a member of.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
package main
|
||||
|
||||
//go:generate make proto
|
||||
@@ -1,375 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
"github.com/micro/micro/v3/service/store"
|
||||
pb "github.com/micro/services/groups/proto"
|
||||
"github.com/micro/services/pkg/tenant"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissingName = errors.BadRequest("MISSING_NAME", "Missing name")
|
||||
ErrMissingID = errors.BadRequest("MISSING_ID", "Missing ID")
|
||||
ErrMissingIDs = errors.BadRequest("MISSING_IDS", "One or more IDs are required")
|
||||
ErrMissingGroupID = errors.BadRequest("MISSING_GROUP_ID", "Missing Group ID")
|
||||
ErrMissingMemberID = errors.BadRequest("MISSING_MEMBER_ID", "Missing Member ID")
|
||||
ErrNotFound = errors.BadRequest("NOT_FOUND", "No group found with this ID")
|
||||
ErrStore = errors.InternalServerError("STORE_ERROR", "Error connecting to the store")
|
||||
)
|
||||
|
||||
type Group struct {
|
||||
ID string
|
||||
Name string
|
||||
Members []string
|
||||
}
|
||||
|
||||
type Member struct {
|
||||
ID string
|
||||
Group string
|
||||
}
|
||||
|
||||
func (g *Group) Key(ctx context.Context) string {
|
||||
key := fmt.Sprintf("group:%s", g.ID)
|
||||
|
||||
t, ok := tenant.FromContext(ctx)
|
||||
if !ok {
|
||||
return key
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", t, key)
|
||||
}
|
||||
|
||||
func (m *Member) Key(ctx context.Context) string {
|
||||
key := fmt.Sprintf("member:%s:%s", m.ID, m.Group)
|
||||
|
||||
t, ok := tenant.FromContext(ctx)
|
||||
if !ok {
|
||||
return key
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", t, key)
|
||||
|
||||
}
|
||||
|
||||
func (g *Group) Serialize() *pb.Group {
|
||||
memberIDs := make([]string, len(g.Members))
|
||||
for i, m := range g.Members {
|
||||
memberIDs[i] = m
|
||||
}
|
||||
return &pb.Group{Id: g.ID, Name: g.Name, MemberIds: memberIDs}
|
||||
}
|
||||
|
||||
type Groups struct{}
|
||||
|
||||
func (g *Groups) Create(ctx context.Context, req *pb.CreateRequest, rsp *pb.CreateResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.Name) == 0 {
|
||||
return ErrMissingName
|
||||
}
|
||||
|
||||
// create the group object
|
||||
group := &Group{ID: uuid.New().String(), Name: req.Name}
|
||||
|
||||
// write the group record
|
||||
if err := store.Write(store.NewRecord(group.Key(ctx), group)); err != nil {
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
// return the group
|
||||
rsp.Group = group.Serialize()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Groups) Read(ctx context.Context, req *pb.ReadRequest, rsp *pb.ReadResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.Ids) == 0 {
|
||||
return ErrMissingIDs
|
||||
}
|
||||
|
||||
// serialize the response
|
||||
rsp.Groups = make(map[string]*pb.Group)
|
||||
|
||||
for _, id := range req.Ids {
|
||||
group := &Group{
|
||||
ID: id,
|
||||
}
|
||||
recs, err := store.Read(group.Key(ctx), store.ReadLimit(1))
|
||||
if err != nil {
|
||||
return ErrStore
|
||||
}
|
||||
if len(recs) == 0 {
|
||||
continue
|
||||
}
|
||||
if err := recs[0].Decode(&group); err != nil {
|
||||
continue
|
||||
}
|
||||
rsp.Groups[group.ID] = group.Serialize()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Groups) Update(ctx context.Context, req *pb.UpdateRequest, rsp *pb.UpdateResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.Id) == 0 {
|
||||
return ErrMissingID
|
||||
}
|
||||
if len(req.Name) == 0 {
|
||||
return ErrMissingName
|
||||
}
|
||||
|
||||
group := &Group{ID: req.Id}
|
||||
|
||||
recs, err := store.Read(group.Key(ctx), store.ReadLimit(1))
|
||||
if err == store.ErrNotFound {
|
||||
return ErrNotFound
|
||||
} else if err != nil {
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
// decode the record
|
||||
recs[0].Decode(&group)
|
||||
|
||||
// set the name
|
||||
group.Name = req.Name
|
||||
|
||||
// save the record
|
||||
if err := store.Write(store.NewRecord(group.Key(ctx), group)); err != nil {
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
// serialize the response
|
||||
rsp.Group = group.Serialize()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Groups) Delete(ctx context.Context, req *pb.DeleteRequest, rsp *pb.DeleteResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.Id) == 0 {
|
||||
return ErrMissingID
|
||||
}
|
||||
|
||||
group := &Group{ID: req.Id}
|
||||
|
||||
// get the group
|
||||
recs, err := store.Read(group.Key(ctx), store.ReadLimit(1))
|
||||
if err == store.ErrNotFound {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
// decode the record
|
||||
recs[0].Decode(&group)
|
||||
|
||||
// delete the record
|
||||
if err := store.Delete(group.Key(ctx)); err == store.ErrNotFound {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
// delete all the members
|
||||
for _, memberId := range group.Members {
|
||||
m := &Member{
|
||||
ID: memberId,
|
||||
}
|
||||
// delete the member
|
||||
store.Delete(m.Key(ctx))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Groups) List(ctx context.Context, req *pb.ListRequest, rsp *pb.ListResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
|
||||
if len(req.MemberId) > 0 {
|
||||
// only list groups the user is a member of
|
||||
m := &Member{ID: req.MemberId}
|
||||
recs, err := store.Read(m.Key(ctx), store.ReadPrefix())
|
||||
if err != nil {
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
for _, rec := range recs {
|
||||
m := &Member{ID: req.MemberId}
|
||||
rec.Decode(&m)
|
||||
|
||||
// get the group
|
||||
group := &Group{ID: m.Group}
|
||||
|
||||
grecs, err := store.Read(group.Key(ctx), store.ReadLimit(1))
|
||||
if err != nil {
|
||||
return ErrStore
|
||||
}
|
||||
grecs[0].Decode(&group)
|
||||
|
||||
rsp.Groups = append(rsp.Groups, group.Serialize())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
group := &Group{}
|
||||
|
||||
// read all the prefixes
|
||||
recs, err := store.Read(group.Key(ctx), store.ReadPrefix())
|
||||
if err != nil {
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
// serialize and return response
|
||||
for _, rec := range recs {
|
||||
group := new(Group)
|
||||
rec.Decode(&group)
|
||||
rsp.Groups = append(rsp.Groups, group.Serialize())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Groups) AddMember(ctx context.Context, req *pb.AddMemberRequest, rsp *pb.AddMemberResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.GroupId) == 0 {
|
||||
return ErrMissingGroupID
|
||||
}
|
||||
if len(req.MemberId) == 0 {
|
||||
return ErrMissingMemberID
|
||||
}
|
||||
|
||||
// read the group
|
||||
group := &Group{ID: req.GroupId}
|
||||
|
||||
recs, err := store.Read(group.Key(ctx), store.ReadLimit(1))
|
||||
if err == store.ErrNotFound {
|
||||
return ErrNotFound
|
||||
} else if err != nil {
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
// decode the record
|
||||
recs[0].Decode(group)
|
||||
|
||||
var seen bool
|
||||
for _, member := range group.Members {
|
||||
if member == req.MemberId {
|
||||
seen = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// already a member
|
||||
if seen {
|
||||
return nil
|
||||
}
|
||||
|
||||
// add the member
|
||||
group.Members = append(group.Members, req.MemberId)
|
||||
|
||||
// save the record
|
||||
if err := store.Write(store.NewRecord(group.Key(ctx), group)); err != nil {
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
// add the member record
|
||||
|
||||
m := &Member{
|
||||
ID: req.MemberId,
|
||||
Group: group.ID,
|
||||
}
|
||||
|
||||
// write the record
|
||||
if err := store.Write(store.NewRecord(m.Key(ctx), m)); err != nil {
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Groups) RemoveMember(ctx context.Context, req *pb.RemoveMemberRequest, rsp *pb.RemoveMemberResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.GroupId) == 0 {
|
||||
return ErrMissingGroupID
|
||||
}
|
||||
if len(req.MemberId) == 0 {
|
||||
return ErrMissingMemberID
|
||||
}
|
||||
|
||||
// read the group
|
||||
group := &Group{ID: req.GroupId}
|
||||
|
||||
// read the gruop
|
||||
recs, err := store.Read(group.Key(ctx), store.ReadLimit(1))
|
||||
if err == store.ErrNotFound {
|
||||
return ErrNotFound
|
||||
} else if err != nil {
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
// decode the record
|
||||
recs[0].Decode(&group)
|
||||
|
||||
// new member id list
|
||||
var members []string
|
||||
|
||||
for _, member := range group.Members {
|
||||
if member == req.MemberId {
|
||||
continue
|
||||
}
|
||||
members = append(members, member)
|
||||
}
|
||||
|
||||
// update the member
|
||||
group.Members = members
|
||||
|
||||
// save the record
|
||||
if err := store.Write(store.NewRecord(group.Key(ctx), group)); err != nil {
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
// delete the member
|
||||
m := &Member{
|
||||
ID: req.MemberId,
|
||||
Group: group.ID,
|
||||
}
|
||||
if err := store.Delete(m.Key(ctx)); err != nil {
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,280 +0,0 @@
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/store"
|
||||
"github.com/micro/micro/v3/service/store/memory"
|
||||
"github.com/micro/services/groups/handler"
|
||||
pb "github.com/micro/services/groups/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testHandler(t *testing.T) *handler.Groups {
|
||||
store.DefaultStore = memory.NewStore()
|
||||
return &handler.Groups{}
|
||||
}
|
||||
func TestCreate(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
t.Run("MissingName", func(t *testing.T) {
|
||||
err := h.Create(microAccountCtx(), &pb.CreateRequest{}, &pb.CreateResponse{})
|
||||
assert.Equal(t, handler.ErrMissingName, err)
|
||||
})
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
err := h.Create(microAccountCtx(), &pb.CreateRequest{
|
||||
Name: "Doe Family Group",
|
||||
}, &pb.CreateResponse{})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
t.Run("MissingID", func(t *testing.T) {
|
||||
err := h.Update(microAccountCtx(), &pb.UpdateRequest{
|
||||
Name: "Doe Family Group",
|
||||
}, &pb.UpdateResponse{})
|
||||
assert.Equal(t, handler.ErrMissingID, err)
|
||||
})
|
||||
|
||||
t.Run("MissingName", func(t *testing.T) {
|
||||
err := h.Update(microAccountCtx(), &pb.UpdateRequest{
|
||||
Id: uuid.New().String(),
|
||||
}, &pb.UpdateResponse{})
|
||||
assert.Equal(t, handler.ErrMissingName, err)
|
||||
})
|
||||
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
err := h.Update(microAccountCtx(), &pb.UpdateRequest{
|
||||
Id: uuid.New().String(),
|
||||
Name: "Bar Family Group",
|
||||
}, &pb.UpdateResponse{})
|
||||
assert.Equal(t, handler.ErrNotFound, err)
|
||||
})
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
// create a demo group
|
||||
var cRsp pb.CreateResponse
|
||||
err := h.Create(microAccountCtx(), &pb.CreateRequest{
|
||||
Name: "Doe Family Group",
|
||||
}, &cRsp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = h.Update(microAccountCtx(), &pb.UpdateRequest{
|
||||
Id: cRsp.Group.Id,
|
||||
Name: "Bar Family Group",
|
||||
}, &pb.UpdateResponse{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var rRsp pb.ReadResponse
|
||||
err = h.Read(microAccountCtx(), &pb.ReadRequest{
|
||||
Ids: []string{cRsp.Group.Id},
|
||||
}, &rRsp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
g := rRsp.Groups[cRsp.Group.Id]
|
||||
if g == nil {
|
||||
t.Errorf("Group not returned")
|
||||
} else {
|
||||
assert.Equal(t, "Bar Family Group", g.Name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
t.Run("MissingID", func(t *testing.T) {
|
||||
err := h.Delete(microAccountCtx(), &pb.DeleteRequest{}, &pb.DeleteResponse{})
|
||||
assert.Equal(t, handler.ErrMissingID, err)
|
||||
})
|
||||
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
err := h.Delete(microAccountCtx(), &pb.DeleteRequest{
|
||||
Id: uuid.New().String(),
|
||||
}, &pb.DeleteResponse{})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
// create a demo group
|
||||
var cRsp pb.CreateResponse
|
||||
err := h.Create(microAccountCtx(), &pb.CreateRequest{
|
||||
Name: "Doe Family Group",
|
||||
}, &cRsp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
err := h.Delete(microAccountCtx(), &pb.DeleteRequest{
|
||||
Id: cRsp.Group.Id,
|
||||
}, &pb.DeleteResponse{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var rRsp pb.ReadResponse
|
||||
err = h.Read(microAccountCtx(), &pb.ReadRequest{
|
||||
Ids: []string{cRsp.Group.Id},
|
||||
}, &rRsp)
|
||||
assert.Nil(t, rRsp.Groups[cRsp.Group.Id])
|
||||
})
|
||||
}
|
||||
func TestList(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
// create two demo groups
|
||||
var cRsp1 pb.CreateResponse
|
||||
err := h.Create(microAccountCtx(), &pb.CreateRequest{
|
||||
Name: "Alpha Group",
|
||||
}, &cRsp1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var cRsp2 pb.CreateResponse
|
||||
err = h.Create(microAccountCtx(), &pb.CreateRequest{
|
||||
Name: "Bravo Group",
|
||||
}, &cRsp2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// add a member to the first group
|
||||
uid := uuid.New().String()
|
||||
err = h.AddMember(microAccountCtx(), &pb.AddMemberRequest{
|
||||
GroupId: cRsp1.Group.Id, MemberId: uid,
|
||||
}, &pb.AddMemberResponse{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("Unscoped", func(t *testing.T) {
|
||||
var rsp pb.ListResponse
|
||||
err = h.List(microAccountCtx(), &pb.ListRequest{}, &rsp)
|
||||
assert.NoError(t, err)
|
||||
assert.Lenf(t, rsp.Groups, 2, "Two groups should be returned")
|
||||
if len(rsp.Groups) != 2 {
|
||||
return
|
||||
}
|
||||
|
||||
sort.Slice(rsp.Groups, func(i, j int) bool {
|
||||
return rsp.Groups[i].Name < rsp.Groups[j].Name
|
||||
})
|
||||
assert.Equal(t, cRsp1.Group.Id, rsp.Groups[0].Id)
|
||||
assert.Equal(t, cRsp1.Group.Name, rsp.Groups[0].Name)
|
||||
assert.Len(t, rsp.Groups[0].MemberIds, 1)
|
||||
assert.Contains(t, rsp.Groups[0].MemberIds, uid)
|
||||
assert.Equal(t, cRsp2.Group.Id, rsp.Groups[1].Id)
|
||||
assert.Equal(t, cRsp2.Group.Name, rsp.Groups[1].Name)
|
||||
assert.Len(t, rsp.Groups[1].MemberIds, 0)
|
||||
})
|
||||
|
||||
t.Run("Scoped", func(t *testing.T) {
|
||||
var rsp pb.ListResponse
|
||||
err = h.List(microAccountCtx(), &pb.ListRequest{MemberId: uid}, &rsp)
|
||||
assert.NoError(t, err)
|
||||
assert.Lenf(t, rsp.Groups, 1, "One group should be returned")
|
||||
if len(rsp.Groups) != 1 {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, cRsp1.Group.Id, rsp.Groups[0].Id)
|
||||
assert.Equal(t, cRsp1.Group.Name, rsp.Groups[0].Name)
|
||||
assert.Len(t, rsp.Groups[0].MemberIds, 1)
|
||||
assert.Contains(t, rsp.Groups[0].MemberIds, uid)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAddMember(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
t.Run("MissingGroupID", func(t *testing.T) {
|
||||
err := h.AddMember(microAccountCtx(), &pb.AddMemberRequest{
|
||||
MemberId: uuid.New().String(),
|
||||
}, &pb.AddMemberResponse{})
|
||||
assert.Equal(t, handler.ErrMissingGroupID, err)
|
||||
})
|
||||
|
||||
t.Run("MissingMemberID", func(t *testing.T) {
|
||||
err := h.AddMember(microAccountCtx(), &pb.AddMemberRequest{
|
||||
GroupId: uuid.New().String(),
|
||||
}, &pb.AddMemberResponse{})
|
||||
assert.Equal(t, handler.ErrMissingMemberID, err)
|
||||
})
|
||||
|
||||
t.Run("GroupNotFound", func(t *testing.T) {
|
||||
err := h.AddMember(microAccountCtx(), &pb.AddMemberRequest{
|
||||
GroupId: uuid.New().String(),
|
||||
MemberId: uuid.New().String(),
|
||||
}, &pb.AddMemberResponse{})
|
||||
assert.Equal(t, handler.ErrNotFound, err)
|
||||
})
|
||||
|
||||
// create a test group
|
||||
var cRsp pb.CreateResponse
|
||||
err := h.Create(microAccountCtx(), &pb.CreateRequest{
|
||||
Name: "Alpha Group",
|
||||
}, &cRsp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
err := h.AddMember(microAccountCtx(), &pb.AddMemberRequest{
|
||||
GroupId: cRsp.Group.Id,
|
||||
MemberId: uuid.New().String(),
|
||||
}, &pb.AddMemberResponse{})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Retry", func(t *testing.T) {
|
||||
err := h.AddMember(microAccountCtx(), &pb.AddMemberRequest{
|
||||
GroupId: cRsp.Group.Id,
|
||||
MemberId: uuid.New().String(),
|
||||
}, &pb.AddMemberResponse{})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemoveMember(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
t.Run("MissingGroupID", func(t *testing.T) {
|
||||
err := h.RemoveMember(microAccountCtx(), &pb.RemoveMemberRequest{
|
||||
MemberId: uuid.New().String(),
|
||||
}, &pb.RemoveMemberResponse{})
|
||||
assert.Equal(t, handler.ErrMissingGroupID, err)
|
||||
})
|
||||
|
||||
t.Run("MissingMemberID", func(t *testing.T) {
|
||||
err := h.RemoveMember(microAccountCtx(), &pb.RemoveMemberRequest{
|
||||
GroupId: uuid.New().String(),
|
||||
}, &pb.RemoveMemberResponse{})
|
||||
assert.Equal(t, handler.ErrMissingMemberID, err)
|
||||
})
|
||||
|
||||
// create a test group
|
||||
var cRsp pb.CreateResponse
|
||||
err := h.Create(microAccountCtx(), &pb.CreateRequest{
|
||||
Name: "Alpha Group",
|
||||
}, &cRsp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
err := h.RemoveMember(microAccountCtx(), &pb.RemoveMemberRequest{
|
||||
GroupId: cRsp.Group.Id,
|
||||
MemberId: uuid.New().String(),
|
||||
}, &pb.RemoveMemberResponse{})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Retry", func(t *testing.T) {
|
||||
err := h.RemoveMember(microAccountCtx(), &pb.RemoveMemberRequest{
|
||||
GroupId: cRsp.Group.Id,
|
||||
MemberId: uuid.New().String(),
|
||||
}, &pb.RemoveMemberResponse{})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func microAccountCtx() context.Context {
|
||||
return auth.ContextWithAccount(context.TODO(), &auth.Account{
|
||||
Issuer: "micro",
|
||||
ID: "someID",
|
||||
})
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/micro/micro/v3/service"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
"github.com/micro/services/groups/handler"
|
||||
pb "github.com/micro/services/groups/proto"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create service
|
||||
srv := service.New(
|
||||
service.Name("groups"),
|
||||
service.Version("latest"),
|
||||
)
|
||||
|
||||
// Register handler
|
||||
pb.RegisterGroupsHandler(srv.Server(), new(handler.Groups))
|
||||
|
||||
// Run service
|
||||
if err := srv.Run(); err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
service groups
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,209 +0,0 @@
|
||||
// Code generated by protoc-gen-micro. DO NOT EDIT.
|
||||
// source: proto/groups.proto
|
||||
|
||||
package groups
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
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 Groups service
|
||||
|
||||
func NewGroupsEndpoints() []*api.Endpoint {
|
||||
return []*api.Endpoint{}
|
||||
}
|
||||
|
||||
// Client API for Groups service
|
||||
|
||||
type GroupsService interface {
|
||||
// Create a group
|
||||
Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error)
|
||||
// Read a group using ID
|
||||
Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error)
|
||||
// Update a groups name
|
||||
Update(ctx context.Context, in *UpdateRequest, opts ...client.CallOption) (*UpdateResponse, error)
|
||||
// Delete a group
|
||||
Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error)
|
||||
// List all groups
|
||||
List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error)
|
||||
// AddMember to a group
|
||||
AddMember(ctx context.Context, in *AddMemberRequest, opts ...client.CallOption) (*AddMemberResponse, error)
|
||||
// RemoveMember from a group
|
||||
RemoveMember(ctx context.Context, in *RemoveMemberRequest, opts ...client.CallOption) (*RemoveMemberResponse, error)
|
||||
}
|
||||
|
||||
type groupsService struct {
|
||||
c client.Client
|
||||
name string
|
||||
}
|
||||
|
||||
func NewGroupsService(name string, c client.Client) GroupsService {
|
||||
return &groupsService{
|
||||
c: c,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *groupsService) Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Groups.Create", in)
|
||||
out := new(CreateResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *groupsService) Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Groups.Read", in)
|
||||
out := new(ReadResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *groupsService) Update(ctx context.Context, in *UpdateRequest, opts ...client.CallOption) (*UpdateResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Groups.Update", in)
|
||||
out := new(UpdateResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *groupsService) Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Groups.Delete", in)
|
||||
out := new(DeleteResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *groupsService) List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Groups.List", in)
|
||||
out := new(ListResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *groupsService) AddMember(ctx context.Context, in *AddMemberRequest, opts ...client.CallOption) (*AddMemberResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Groups.AddMember", in)
|
||||
out := new(AddMemberResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *groupsService) RemoveMember(ctx context.Context, in *RemoveMemberRequest, opts ...client.CallOption) (*RemoveMemberResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Groups.RemoveMember", in)
|
||||
out := new(RemoveMemberResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Groups service
|
||||
|
||||
type GroupsHandler interface {
|
||||
// Create a group
|
||||
Create(context.Context, *CreateRequest, *CreateResponse) error
|
||||
// Read a group using ID
|
||||
Read(context.Context, *ReadRequest, *ReadResponse) error
|
||||
// Update a groups name
|
||||
Update(context.Context, *UpdateRequest, *UpdateResponse) error
|
||||
// Delete a group
|
||||
Delete(context.Context, *DeleteRequest, *DeleteResponse) error
|
||||
// List all groups
|
||||
List(context.Context, *ListRequest, *ListResponse) error
|
||||
// AddMember to a group
|
||||
AddMember(context.Context, *AddMemberRequest, *AddMemberResponse) error
|
||||
// RemoveMember from a group
|
||||
RemoveMember(context.Context, *RemoveMemberRequest, *RemoveMemberResponse) error
|
||||
}
|
||||
|
||||
func RegisterGroupsHandler(s server.Server, hdlr GroupsHandler, opts ...server.HandlerOption) error {
|
||||
type groups interface {
|
||||
Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error
|
||||
Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error
|
||||
Update(ctx context.Context, in *UpdateRequest, out *UpdateResponse) error
|
||||
Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error
|
||||
List(ctx context.Context, in *ListRequest, out *ListResponse) error
|
||||
AddMember(ctx context.Context, in *AddMemberRequest, out *AddMemberResponse) error
|
||||
RemoveMember(ctx context.Context, in *RemoveMemberRequest, out *RemoveMemberResponse) error
|
||||
}
|
||||
type Groups struct {
|
||||
groups
|
||||
}
|
||||
h := &groupsHandler{hdlr}
|
||||
return s.Handle(s.NewHandler(&Groups{h}, opts...))
|
||||
}
|
||||
|
||||
type groupsHandler struct {
|
||||
GroupsHandler
|
||||
}
|
||||
|
||||
func (h *groupsHandler) Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error {
|
||||
return h.GroupsHandler.Create(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *groupsHandler) Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error {
|
||||
return h.GroupsHandler.Read(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *groupsHandler) Update(ctx context.Context, in *UpdateRequest, out *UpdateResponse) error {
|
||||
return h.GroupsHandler.Update(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *groupsHandler) Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error {
|
||||
return h.GroupsHandler.Delete(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *groupsHandler) List(ctx context.Context, in *ListRequest, out *ListResponse) error {
|
||||
return h.GroupsHandler.List(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *groupsHandler) AddMember(ctx context.Context, in *AddMemberRequest, out *AddMemberResponse) error {
|
||||
return h.GroupsHandler.AddMember(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *groupsHandler) RemoveMember(ctx context.Context, in *RemoveMemberRequest, out *RemoveMemberResponse) error {
|
||||
return h.GroupsHandler.RemoveMember(ctx, in, out)
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package groups;
|
||||
option go_package = "./proto;groups";
|
||||
|
||||
service Groups {
|
||||
// Create a group
|
||||
rpc Create(CreateRequest) returns (CreateResponse);
|
||||
// Read a group using ID
|
||||
rpc Read(ReadRequest) returns (ReadResponse);
|
||||
// Update a groups name
|
||||
rpc Update(UpdateRequest) returns (UpdateResponse);
|
||||
// Delete a group
|
||||
rpc Delete(DeleteRequest) returns (DeleteResponse);
|
||||
// List all groups
|
||||
rpc List(ListRequest) returns (ListResponse);
|
||||
// AddMember to a group
|
||||
rpc AddMember(AddMemberRequest) returns (AddMemberResponse);
|
||||
// RemoveMember from a group
|
||||
rpc RemoveMember(RemoveMemberRequest) returns (RemoveMemberResponse);
|
||||
}
|
||||
|
||||
message Group {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
repeated string member_ids = 3;
|
||||
}
|
||||
|
||||
message CreateRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message CreateResponse {
|
||||
Group group = 1;
|
||||
}
|
||||
|
||||
message ReadRequest {
|
||||
repeated string ids = 1;
|
||||
}
|
||||
|
||||
message ReadResponse {
|
||||
map<string, Group> groups = 1;
|
||||
}
|
||||
|
||||
message UpdateRequest {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
}
|
||||
|
||||
message UpdateResponse {
|
||||
Group group = 1;
|
||||
}
|
||||
|
||||
message DeleteRequest {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message DeleteResponse {}
|
||||
|
||||
message ListRequest {
|
||||
// passing a member id will restrict the groups to that which the member is part of
|
||||
string member_id = 1;
|
||||
}
|
||||
|
||||
message ListResponse {
|
||||
repeated Group groups = 1;
|
||||
}
|
||||
|
||||
message AddMemberRequest {
|
||||
string group_id = 1;
|
||||
string member_id = 2;
|
||||
}
|
||||
|
||||
message AddMemberResponse {}
|
||||
|
||||
message RemoveMemberRequest {
|
||||
string group_id = 1;
|
||||
string member_id = 2;
|
||||
}
|
||||
|
||||
message RemoveMemberResponse {}
|
||||
2
invites/.gitignore
vendored
2
invites/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
|
||||
invites
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM alpine
|
||||
ADD invites /invites
|
||||
ENTRYPOINT [ "/invites" ]
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
GOPATH:=$(shell go env GOPATH)
|
||||
.PHONY: init
|
||||
init:
|
||||
go get -u github.com/golang/protobuf/proto
|
||||
go get -u github.com/golang/protobuf/protoc-gen-go
|
||||
go get github.com/micro/micro/v3/cmd/protoc-gen-micro
|
||||
.PHONY: proto
|
||||
proto:
|
||||
protoc --proto_path=. --micro_out=. --go_out=:. proto/invites.proto
|
||||
|
||||
.PHONY: docs
|
||||
docs:
|
||||
protoc --openapi_out=. --proto_path=. --micro_out=. --go_out=:. proto/invites.proto
|
||||
@redoc-cli bundle api-invites.json
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build -o invites *.go
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -v ./... -cover
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
docker build . -t invites:latest
|
||||
@@ -1,6 +0,0 @@
|
||||
Manage and send user and group invites
|
||||
|
||||
# Invites Service
|
||||
|
||||
The invites services allows you to create and manage invites for users and groups by providing invite codes.
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
|
||||
{
|
||||
"create": [{
|
||||
"title": "Create an invite",
|
||||
"description": "Create an invite for a user for a group",
|
||||
"request": {
|
||||
"group_id": "mygroup",
|
||||
"email": "john@doe.com"
|
||||
},
|
||||
"response": {
|
||||
"invite": {
|
||||
"id": "fb3a3552-3c7b-4a18-a1f8-08ab56940862",
|
||||
"group_id": "mygroup",
|
||||
"email": "john@doe.com",
|
||||
"code": "86285587"
|
||||
}
|
||||
}
|
||||
}],
|
||||
"read": [{
|
||||
"title": "Read an invite",
|
||||
"description": "Read an invite by code",
|
||||
"request": {
|
||||
"code": "86285587"
|
||||
},
|
||||
"response": {
|
||||
"invite": {
|
||||
"id": "fb3a3552-3c7b-4a18-a1f8-08ab56940862",
|
||||
"group_id": "mygroup",
|
||||
"email": "john@doe.com",
|
||||
"code": "86285587"
|
||||
}
|
||||
}
|
||||
}],
|
||||
"delete": [{
|
||||
"title": "Delete an invite",
|
||||
"description": "Delete an invite by id",
|
||||
"request": {
|
||||
"id": "fb3a3552-3c7b-4a18-a1f8-08ab56940862"
|
||||
},
|
||||
"response": {}
|
||||
}],
|
||||
"list": [{
|
||||
"title": "List invites",
|
||||
"description": "List invites for a group by id",
|
||||
"request": {
|
||||
"group_id": "mygroup"
|
||||
},
|
||||
"response": {
|
||||
"invites": [{
|
||||
"id": "fb3a3552-3c7b-4a18-a1f8-08ab56940862",
|
||||
"group_id": "myawesomegroup",
|
||||
"email": "john@doe.com",
|
||||
"code": "86285587"
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package main
|
||||
|
||||
//go:generate make proto
|
||||
@@ -1,347 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
"github.com/micro/micro/v3/service/store"
|
||||
pb "github.com/micro/services/invites/proto"
|
||||
"github.com/micro/services/pkg/tenant"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissingID = errors.BadRequest("MISSING_ID", "Missing ID")
|
||||
ErrMissingGroupID = errors.BadRequest("MISSING_GROUP_ID", "Missing GroupID")
|
||||
ErrInvalidEmail = errors.BadRequest("INVALID_EMAIL", "The email provided was invalid")
|
||||
ErrMissingEmail = errors.BadRequest("MISSING_EMAIL", "Missing Email")
|
||||
ErrMissingIDAndCode = errors.BadRequest("ID_OR_CODE_REQUIRED", "An email address code is required to read an invite")
|
||||
ErrMissingGroupIDAndEmail = errors.BadRequest("GROUP_ID_OR_EMAIL_REQUIRED", "An email address or group id is needed to list invites")
|
||||
ErrInviteNotFound = errors.NotFound("NOT_FOUND", "Invite not found")
|
||||
|
||||
emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||
)
|
||||
|
||||
type Invite struct {
|
||||
ID string
|
||||
Email string
|
||||
GroupID string
|
||||
Code string
|
||||
}
|
||||
|
||||
func (i *Invite) Serialize() *pb.Invite {
|
||||
return &pb.Invite{
|
||||
Id: i.ID,
|
||||
Email: i.Email,
|
||||
GroupId: i.GroupID,
|
||||
Code: i.Code,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Invite) Key(ctx context.Context) string {
|
||||
key := fmt.Sprintf("invite:%s:%s", i.ID, i.Code)
|
||||
|
||||
t, ok := tenant.FromContext(ctx)
|
||||
if !ok {
|
||||
return key
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", t, key)
|
||||
}
|
||||
|
||||
func (i *Invite) Index(ctx context.Context) string {
|
||||
key := fmt.Sprintf("group:%s:%s", i.GroupID, i.Email)
|
||||
|
||||
t, ok := tenant.FromContext(ctx)
|
||||
if !ok {
|
||||
return key
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", t, key)
|
||||
}
|
||||
|
||||
func (i *Invite) Marshal() []byte {
|
||||
b, _ := json.Marshal(i)
|
||||
return b
|
||||
}
|
||||
|
||||
func (i *Invite) Unmarshal(b []byte) error {
|
||||
return json.Unmarshal(b, &i)
|
||||
}
|
||||
|
||||
type Invites struct{}
|
||||
|
||||
// schema
|
||||
// Read: id/code
|
||||
// List: group/email
|
||||
|
||||
// Create an invite
|
||||
func (i *Invites) Create(ctx context.Context, req *pb.CreateRequest, rsp *pb.CreateResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.GroupId) == 0 {
|
||||
return ErrMissingGroupID
|
||||
}
|
||||
if len(req.Email) == 0 {
|
||||
return ErrMissingEmail
|
||||
}
|
||||
if !isEmailValid(req.Email) {
|
||||
return ErrInvalidEmail
|
||||
}
|
||||
|
||||
// construct the invite and write to the db
|
||||
invite := &Invite{
|
||||
ID: uuid.New().String(),
|
||||
Code: generateCode(),
|
||||
GroupID: req.GroupId,
|
||||
Email: strings.ToLower(req.Email),
|
||||
}
|
||||
|
||||
// id/val
|
||||
key := invite.Key(ctx)
|
||||
// get group key
|
||||
gkey := invite.Index(ctx)
|
||||
|
||||
// TODO: Use the micro/micro/v3/service/sync interface to lock
|
||||
|
||||
// write the first record
|
||||
if err := store.Write(store.NewRecord(key, invite)); err != nil {
|
||||
logger.Errorf("Error writing to the store: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
|
||||
}
|
||||
|
||||
// write the group record
|
||||
if err := store.Write(store.NewRecord(gkey, invite)); err != nil {
|
||||
logger.Errorf("Error writing to the store: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
|
||||
}
|
||||
|
||||
// serialize the response
|
||||
rsp.Invite = invite.Serialize()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read an invite using ID or code
|
||||
func (i *Invites) Read(ctx context.Context, req *pb.ReadRequest, rsp *pb.ReadResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.Id) == 0 && len(req.Code) == 0 {
|
||||
return ErrMissingIDAndCode
|
||||
}
|
||||
|
||||
var recs []*store.Record
|
||||
var err error
|
||||
|
||||
// create a pseudo invite
|
||||
invite := &Invite{
|
||||
ID: req.Id,
|
||||
Code: req.Code,
|
||||
}
|
||||
|
||||
if len(req.Id) > 0 && len(req.Code) > 0 {
|
||||
recs, err = store.Read(invite.Key(ctx), store.ReadLimit(1))
|
||||
} else if len(req.Id) > 0 {
|
||||
recs, err = store.Read(invite.Key(ctx), store.ReadLimit(1), store.ReadPrefix())
|
||||
} else if len(req.Code) > 0 {
|
||||
// create a code suffix key
|
||||
key := ":" + req.Code
|
||||
// read all the keys with the given code
|
||||
// TODO: potential race where if the code is not random
|
||||
// we read it for the wrong user e.g if two tenants generate the same code
|
||||
r, lerr := store.Read(key, store.ReadLimit(1), store.ReadSuffix())
|
||||
|
||||
// now scan for the prefix
|
||||
prefix := "invite:"
|
||||
|
||||
// additional prefix for the tenant
|
||||
if t, ok := tenant.FromContext(ctx); ok {
|
||||
prefix = t + "/" + prefix
|
||||
}
|
||||
|
||||
// scan for the key we're looking for
|
||||
for _, rec := range r {
|
||||
// skip the missing prefix
|
||||
if !strings.HasPrefix(rec.Key, prefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
// skip missing suffix
|
||||
if !strings.HasSuffix(rec.Key, key) {
|
||||
continue
|
||||
}
|
||||
|
||||
// save the record
|
||||
recs = append(recs, rec)
|
||||
break
|
||||
}
|
||||
|
||||
// set the error
|
||||
// TODO: maybe just process this
|
||||
err = lerr
|
||||
}
|
||||
|
||||
// check if there are any records
|
||||
if err == store.ErrNotFound || len(recs) == 0 {
|
||||
return ErrInviteNotFound
|
||||
}
|
||||
|
||||
// check the error
|
||||
if err != nil {
|
||||
logger.Errorf("Error reading from the store: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
|
||||
}
|
||||
|
||||
// unmarshal the invite
|
||||
invite.Unmarshal(recs[0].Value)
|
||||
|
||||
// serialize the response
|
||||
rsp.Invite = invite.Serialize()
|
||||
return nil
|
||||
}
|
||||
|
||||
// List invited for a group or specific email
|
||||
func (i *Invites) List(ctx context.Context, req *pb.ListRequest, rsp *pb.ListResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.Email) == 0 && len(req.GroupId) == 0 {
|
||||
return ErrMissingGroupIDAndEmail
|
||||
}
|
||||
|
||||
invite := &Invite{
|
||||
GroupID: req.GroupId,
|
||||
Email: req.Email,
|
||||
}
|
||||
|
||||
var recs []*store.Record
|
||||
var err error
|
||||
|
||||
if len(invite.GroupID) > 0 && len(invite.Email) > 0 {
|
||||
key := invite.Index(ctx)
|
||||
recs, err = store.Read(key, store.ReadLimit(1))
|
||||
} else if len(invite.GroupID) > 0 {
|
||||
key := invite.Index(ctx)
|
||||
recs, err = store.Read(key, store.ReadPrefix())
|
||||
} else if len(invite.Email) > 0 {
|
||||
// create a email suffix key
|
||||
key := ":" + invite.Email
|
||||
// read all the keys with the given code
|
||||
r, lerr := store.Read(key, store.ReadSuffix())
|
||||
|
||||
// now scan for the prefix
|
||||
prefix := "group:"
|
||||
|
||||
// additional prefix for the tenant
|
||||
if t, ok := tenant.FromContext(ctx); ok {
|
||||
prefix = t + "/" + prefix
|
||||
}
|
||||
|
||||
// scan for the key we're looking for
|
||||
for _, rec := range r {
|
||||
// skip the missing prefix
|
||||
if !strings.HasPrefix(rec.Key, prefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
// skip missing suffix
|
||||
if !strings.HasSuffix(rec.Key, key) {
|
||||
continue
|
||||
}
|
||||
|
||||
// save the record
|
||||
recs = append(recs, rec)
|
||||
}
|
||||
|
||||
// set the error
|
||||
// TODO: maybe just process this
|
||||
err = lerr
|
||||
}
|
||||
|
||||
// no records found
|
||||
if err == store.ErrNotFound {
|
||||
return nil
|
||||
}
|
||||
|
||||
// check the error
|
||||
if err != nil {
|
||||
logger.Errorf("Error reading from the store: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
|
||||
}
|
||||
|
||||
// return response
|
||||
for _, rec := range recs {
|
||||
invite := new(Invite)
|
||||
invite.Unmarshal(rec.Value)
|
||||
rsp.Invites = append(rsp.Invites, invite.Serialize())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete an invite
|
||||
func (i *Invites) Delete(ctx context.Context, req *pb.DeleteRequest, rsp *pb.DeleteResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.Id) == 0 {
|
||||
return ErrMissingID
|
||||
}
|
||||
|
||||
invite := &Invite{ID: req.Id}
|
||||
key := invite.Key(ctx)
|
||||
|
||||
// check for the existing invite value
|
||||
recs, err := store.Read(key, store.ReadLimit(1), store.ReadPrefix())
|
||||
if err == store.ErrNotFound || len(recs) == 0 {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
logger.Errorf("Error connecting to DB: %v", err)
|
||||
return errors.InternalServerError("DB_ERROR", "Error connecting to DB")
|
||||
}
|
||||
|
||||
// unmarshal the existing invite
|
||||
invite.Unmarshal(recs[0].Value)
|
||||
if invite.ID != req.Id {
|
||||
return nil
|
||||
}
|
||||
|
||||
// delete the record by id
|
||||
store.Delete(invite.Key(ctx))
|
||||
|
||||
// delete the record by group id
|
||||
store.Delete(invite.Index(ctx))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isEmailValid checks if the email provided passes the required structure and length.
|
||||
func isEmailValid(e string) bool {
|
||||
if len(e) < 3 && len(e) > 254 {
|
||||
return false
|
||||
}
|
||||
return emailRegex.MatchString(e)
|
||||
}
|
||||
|
||||
// generateCode generates a random 8 digit code
|
||||
func generateCode() string {
|
||||
v := rand.Intn(89999999) + 10000000
|
||||
return strconv.Itoa(v)
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/store"
|
||||
"github.com/micro/micro/v3/service/store/memory"
|
||||
"github.com/micro/services/invites/handler"
|
||||
pb "github.com/micro/services/invites/proto"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testHandler(t *testing.T) *handler.Invites {
|
||||
store.DefaultStore = memory.NewStore()
|
||||
return &handler.Invites{}
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
tt := []struct {
|
||||
Name string
|
||||
GroupID string
|
||||
Email string
|
||||
Error error
|
||||
}{
|
||||
{
|
||||
Name: "MissingGroupID",
|
||||
Email: "john@doe.com",
|
||||
Error: handler.ErrMissingGroupID,
|
||||
},
|
||||
{
|
||||
Name: "MissingEmail",
|
||||
GroupID: uuid.New().String(),
|
||||
Error: handler.ErrMissingEmail,
|
||||
},
|
||||
{
|
||||
Name: "InvalidEmail",
|
||||
GroupID: uuid.New().String(),
|
||||
Email: "foo.foo.foo",
|
||||
Error: handler.ErrInvalidEmail,
|
||||
},
|
||||
{
|
||||
Name: "Valid",
|
||||
GroupID: "thisisavalidgroupid",
|
||||
Email: "john@doe.com",
|
||||
},
|
||||
{
|
||||
Name: "Repeat",
|
||||
GroupID: "thisisavalidgroupid",
|
||||
Email: "john@doe.com",
|
||||
},
|
||||
}
|
||||
|
||||
h := testHandler(t)
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
var rsp pb.CreateResponse
|
||||
err := h.Create(microAccountCtx(), &pb.CreateRequest{
|
||||
GroupId: tc.GroupID, Email: tc.Email,
|
||||
}, &rsp)
|
||||
assert.Equal(t, tc.Error, err)
|
||||
|
||||
if tc.Error != nil {
|
||||
assert.Nil(t, rsp.Invite)
|
||||
return
|
||||
}
|
||||
|
||||
if rsp.Invite == nil {
|
||||
t.Fatalf("Invite was not returned")
|
||||
return
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, rsp.Invite.Id)
|
||||
assert.NotEmpty(t, rsp.Invite.Code)
|
||||
assert.Equal(t, tc.GroupID, rsp.Invite.GroupId)
|
||||
assert.Equal(t, tc.Email, rsp.Invite.Email)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
// seed some data
|
||||
var cRsp pb.CreateResponse
|
||||
err := h.Create(microAccountCtx(), &pb.CreateRequest{Email: "john@doe.com", GroupId: uuid.New().String()}, &cRsp)
|
||||
assert.NoError(t, err)
|
||||
if cRsp.Invite == nil {
|
||||
t.Fatal("No invite returned on create")
|
||||
return
|
||||
}
|
||||
|
||||
tt := []struct {
|
||||
Name string
|
||||
ID string
|
||||
Code string
|
||||
Error error
|
||||
Invite *pb.Invite
|
||||
}{
|
||||
{
|
||||
Name: "MissingIDAndCode",
|
||||
Error: handler.ErrMissingIDAndCode,
|
||||
},
|
||||
{
|
||||
Name: "NotFoundByID",
|
||||
ID: uuid.New().String(),
|
||||
Error: handler.ErrInviteNotFound,
|
||||
},
|
||||
{
|
||||
Name: "NotFoundByCode",
|
||||
Code: "12345678",
|
||||
Error: handler.ErrInviteNotFound,
|
||||
},
|
||||
{
|
||||
Name: "ValidID",
|
||||
ID: cRsp.Invite.Id,
|
||||
Invite: cRsp.Invite,
|
||||
},
|
||||
{
|
||||
Name: "ValidCode",
|
||||
Code: cRsp.Invite.Code,
|
||||
Invite: cRsp.Invite,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
var rsp pb.ReadResponse
|
||||
err := h.Read(microAccountCtx(), &pb.ReadRequest{Id: tc.ID, Code: tc.Code}, &rsp)
|
||||
assert.Equal(t, tc.Error, err)
|
||||
|
||||
if tc.Invite == nil {
|
||||
assert.Nil(t, rsp.Invite)
|
||||
} else {
|
||||
assertInvitesMatch(t, tc.Invite, rsp.Invite)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
// seed some data
|
||||
var cRsp pb.CreateResponse
|
||||
err := h.Create(microAccountCtx(), &pb.CreateRequest{Email: "john@doe.com", GroupId: uuid.New().String()}, &cRsp)
|
||||
assert.NoError(t, err)
|
||||
if cRsp.Invite == nil {
|
||||
t.Fatal("No invite returned on create")
|
||||
return
|
||||
}
|
||||
|
||||
tt := []struct {
|
||||
Name string
|
||||
GroupID string
|
||||
Email string
|
||||
Error error
|
||||
Invite *pb.Invite
|
||||
}{
|
||||
{
|
||||
Name: "MissingIDAndEmail",
|
||||
Error: handler.ErrMissingGroupIDAndEmail,
|
||||
},
|
||||
{
|
||||
Name: "NoResultsForEmail",
|
||||
Email: "foo@bar.com",
|
||||
},
|
||||
{
|
||||
Name: "NoResultsForGroupID",
|
||||
GroupID: uuid.New().String(),
|
||||
},
|
||||
{
|
||||
Name: "ValidGroupID",
|
||||
GroupID: cRsp.Invite.GroupId,
|
||||
Invite: cRsp.Invite,
|
||||
},
|
||||
{
|
||||
Name: "ValidEmail",
|
||||
Email: cRsp.Invite.Email,
|
||||
Invite: cRsp.Invite,
|
||||
},
|
||||
{
|
||||
Name: "EmailAndGroupID",
|
||||
Email: cRsp.Invite.Email,
|
||||
GroupID: uuid.New().String(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
var rsp pb.ListResponse
|
||||
err := h.List(microAccountCtx(), &pb.ListRequest{Email: tc.Email, GroupId: tc.GroupID}, &rsp)
|
||||
assert.Equal(t, tc.Error, err)
|
||||
|
||||
if tc.Invite == nil {
|
||||
assert.Empty(t, rsp.Invites)
|
||||
} else {
|
||||
if len(rsp.Invites) != 1 {
|
||||
t.Errorf("Incorrect number of invites returned, expected 1 but got %v", len(rsp.Invites))
|
||||
return
|
||||
}
|
||||
assertInvitesMatch(t, tc.Invite, rsp.Invites[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
t.Run("MissingID", func(t *testing.T) {
|
||||
err := h.Delete(microAccountCtx(), &pb.DeleteRequest{}, &pb.DeleteResponse{})
|
||||
assert.Equal(t, handler.ErrMissingID, err)
|
||||
})
|
||||
|
||||
// seed some data
|
||||
var cRsp pb.CreateResponse
|
||||
err := h.Create(microAccountCtx(), &pb.CreateRequest{Email: "john@doe.com", GroupId: uuid.New().String()}, &cRsp)
|
||||
assert.NoError(t, err)
|
||||
if cRsp.Invite == nil {
|
||||
t.Fatal("No invite returned on create")
|
||||
return
|
||||
}
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
err := h.Delete(microAccountCtx(), &pb.DeleteRequest{Id: cRsp.Invite.Id}, &pb.DeleteResponse{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = h.Read(microAccountCtx(), &pb.ReadRequest{Id: cRsp.Invite.Id}, &pb.ReadResponse{})
|
||||
assert.Equal(t, handler.ErrInviteNotFound, err)
|
||||
})
|
||||
|
||||
t.Run("Repeat", func(t *testing.T) {
|
||||
err := h.Delete(microAccountCtx(), &pb.DeleteRequest{Id: cRsp.Invite.Id}, &pb.DeleteResponse{})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func assertInvitesMatch(t *testing.T, exp, act *pb.Invite) {
|
||||
if act == nil {
|
||||
t.Errorf("No invite returned")
|
||||
return
|
||||
}
|
||||
assert.Equal(t, exp.Id, act.Id)
|
||||
assert.Equal(t, exp.Code, act.Code)
|
||||
assert.Equal(t, exp.Email, act.Email)
|
||||
assert.Equal(t, exp.GroupId, act.GroupId)
|
||||
}
|
||||
|
||||
func microAccountCtx() context.Context {
|
||||
return auth.ContextWithAccount(context.TODO(), &auth.Account{
|
||||
Issuer: "micro",
|
||||
ID: "someID",
|
||||
})
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/micro/services/invites/handler"
|
||||
pb "github.com/micro/services/invites/proto"
|
||||
|
||||
"github.com/micro/micro/v3/service"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create service
|
||||
srv := service.New(
|
||||
service.Name("invites"),
|
||||
service.Version("latest"),
|
||||
)
|
||||
|
||||
// Register handler
|
||||
pb.RegisterInvitesHandler(srv.Server(), new(handler.Invites))
|
||||
|
||||
// Run service
|
||||
if err := srv.Run(); err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
service invites
|
||||
@@ -1,716 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.6
|
||||
// source: proto/invites.proto
|
||||
|
||||
package invites
|
||||
|
||||
import (
|
||||
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)
|
||||
)
|
||||
|
||||
type Invite struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
GroupId string `protobuf:"bytes,2,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"`
|
||||
Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
|
||||
Code string `protobuf:"bytes,4,opt,name=code,proto3" json:"code,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Invite) Reset() {
|
||||
*x = Invite{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_invites_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Invite) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Invite) ProtoMessage() {}
|
||||
|
||||
func (x *Invite) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_invites_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 Invite.ProtoReflect.Descriptor instead.
|
||||
func (*Invite) Descriptor() ([]byte, []int) {
|
||||
return file_proto_invites_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Invite) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Invite) GetGroupId() string {
|
||||
if x != nil {
|
||||
return x.GroupId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Invite) GetEmail() string {
|
||||
if x != nil {
|
||||
return x.Email
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Invite) GetCode() string {
|
||||
if x != nil {
|
||||
return x.Code
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type CreateRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"`
|
||||
Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"`
|
||||
}
|
||||
|
||||
func (x *CreateRequest) Reset() {
|
||||
*x = CreateRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_invites_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *CreateRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CreateRequest) ProtoMessage() {}
|
||||
|
||||
func (x *CreateRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_invites_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 CreateRequest.ProtoReflect.Descriptor instead.
|
||||
func (*CreateRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_invites_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *CreateRequest) GetGroupId() string {
|
||||
if x != nil {
|
||||
return x.GroupId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CreateRequest) GetEmail() string {
|
||||
if x != nil {
|
||||
return x.Email
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type CreateResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Invite *Invite `protobuf:"bytes,1,opt,name=invite,proto3" json:"invite,omitempty"`
|
||||
}
|
||||
|
||||
func (x *CreateResponse) Reset() {
|
||||
*x = CreateResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_invites_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *CreateResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CreateResponse) ProtoMessage() {}
|
||||
|
||||
func (x *CreateResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_invites_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 CreateResponse.ProtoReflect.Descriptor instead.
|
||||
func (*CreateResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_invites_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *CreateResponse) GetInvite() *Invite {
|
||||
if x != nil {
|
||||
return x.Invite
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ReadRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Code string `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ReadRequest) Reset() {
|
||||
*x = ReadRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_invites_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ReadRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ReadRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ReadRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_invites_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 ReadRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ReadRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_invites_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *ReadRequest) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ReadRequest) GetCode() string {
|
||||
if x != nil {
|
||||
return x.Code
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ReadResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Invite *Invite `protobuf:"bytes,1,opt,name=invite,proto3" json:"invite,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ReadResponse) Reset() {
|
||||
*x = ReadResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_invites_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ReadResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ReadResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ReadResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_invites_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 ReadResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ReadResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_invites_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *ReadResponse) GetInvite() *Invite {
|
||||
if x != nil {
|
||||
return x.Invite
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ListRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"`
|
||||
Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ListRequest) Reset() {
|
||||
*x = ListRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_invites_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ListRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_invites_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 ListRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ListRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_invites_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *ListRequest) GetGroupId() string {
|
||||
if x != nil {
|
||||
return x.GroupId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListRequest) GetEmail() string {
|
||||
if x != nil {
|
||||
return x.Email
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ListResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Invites []*Invite `protobuf:"bytes,1,rep,name=invites,proto3" json:"invites,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ListResponse) Reset() {
|
||||
*x = ListResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_invites_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ListResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_invites_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 ListResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ListResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_invites_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *ListResponse) GetInvites() []*Invite {
|
||||
if x != nil {
|
||||
return x.Invites
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DeleteRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DeleteRequest) Reset() {
|
||||
*x = DeleteRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_invites_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DeleteRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DeleteRequest) ProtoMessage() {}
|
||||
|
||||
func (x *DeleteRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_invites_proto_msgTypes[7]
|
||||
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 DeleteRequest.ProtoReflect.Descriptor instead.
|
||||
func (*DeleteRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_invites_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *DeleteRequest) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type DeleteResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *DeleteResponse) Reset() {
|
||||
*x = DeleteResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_invites_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DeleteResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DeleteResponse) ProtoMessage() {}
|
||||
|
||||
func (x *DeleteResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_invites_proto_msgTypes[8]
|
||||
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 DeleteResponse.ProtoReflect.Descriptor instead.
|
||||
func (*DeleteResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_invites_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
var File_proto_invites_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_proto_invites_proto_rawDesc = []byte{
|
||||
0x0a, 0x13, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x73, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x73, 0x22, 0x5d,
|
||||
0x0a, 0x06, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75,
|
||||
0x70, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75,
|
||||
0x70, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64,
|
||||
0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x40, 0x0a,
|
||||
0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19,
|
||||
0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61,
|
||||
0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22,
|
||||
0x39, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x27, 0x0a, 0x06, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x0f, 0x2e, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x76, 0x69,
|
||||
0x74, 0x65, 0x52, 0x06, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x22, 0x31, 0x0a, 0x0b, 0x52, 0x65,
|
||||
0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x37, 0x0a,
|
||||
0x0c, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a,
|
||||
0x06, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e,
|
||||
0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x06,
|
||||
0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x22, 0x3e, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64,
|
||||
0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x39, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x07, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65,
|
||||
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65,
|
||||
0x73, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x07, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65,
|
||||
0x73, 0x22, 0x1f, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
|
||||
0x69, 0x64, 0x22, 0x10, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe9, 0x01, 0x0a, 0x07, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x73,
|
||||
0x12, 0x39, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x69, 0x6e, 0x76,
|
||||
0x69, 0x74, 0x65, 0x73, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x17, 0x2e, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x43, 0x72, 0x65,
|
||||
0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x52,
|
||||
0x65, 0x61, 0x64, 0x12, 0x14, 0x2e, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x52, 0x65,
|
||||
0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x69, 0x6e, 0x76, 0x69,
|
||||
0x74, 0x65, 0x73, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x12, 0x33, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x14, 0x2e, 0x69, 0x6e, 0x76, 0x69, 0x74,
|
||||
0x65, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15,
|
||||
0x2e, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12,
|
||||
0x16, 0x2e, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65,
|
||||
0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x42, 0x11, 0x5a, 0x0f, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x69, 0x6e, 0x76, 0x69,
|
||||
0x74, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_proto_invites_proto_rawDescOnce sync.Once
|
||||
file_proto_invites_proto_rawDescData = file_proto_invites_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_proto_invites_proto_rawDescGZIP() []byte {
|
||||
file_proto_invites_proto_rawDescOnce.Do(func() {
|
||||
file_proto_invites_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_invites_proto_rawDescData)
|
||||
})
|
||||
return file_proto_invites_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proto_invites_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
|
||||
var file_proto_invites_proto_goTypes = []interface{}{
|
||||
(*Invite)(nil), // 0: invites.Invite
|
||||
(*CreateRequest)(nil), // 1: invites.CreateRequest
|
||||
(*CreateResponse)(nil), // 2: invites.CreateResponse
|
||||
(*ReadRequest)(nil), // 3: invites.ReadRequest
|
||||
(*ReadResponse)(nil), // 4: invites.ReadResponse
|
||||
(*ListRequest)(nil), // 5: invites.ListRequest
|
||||
(*ListResponse)(nil), // 6: invites.ListResponse
|
||||
(*DeleteRequest)(nil), // 7: invites.DeleteRequest
|
||||
(*DeleteResponse)(nil), // 8: invites.DeleteResponse
|
||||
}
|
||||
var file_proto_invites_proto_depIdxs = []int32{
|
||||
0, // 0: invites.CreateResponse.invite:type_name -> invites.Invite
|
||||
0, // 1: invites.ReadResponse.invite:type_name -> invites.Invite
|
||||
0, // 2: invites.ListResponse.invites:type_name -> invites.Invite
|
||||
1, // 3: invites.Invites.Create:input_type -> invites.CreateRequest
|
||||
3, // 4: invites.Invites.Read:input_type -> invites.ReadRequest
|
||||
5, // 5: invites.Invites.List:input_type -> invites.ListRequest
|
||||
7, // 6: invites.Invites.Delete:input_type -> invites.DeleteRequest
|
||||
2, // 7: invites.Invites.Create:output_type -> invites.CreateResponse
|
||||
4, // 8: invites.Invites.Read:output_type -> invites.ReadResponse
|
||||
6, // 9: invites.Invites.List:output_type -> invites.ListResponse
|
||||
8, // 10: invites.Invites.Delete:output_type -> invites.DeleteResponse
|
||||
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_invites_proto_init() }
|
||||
func file_proto_invites_proto_init() {
|
||||
if File_proto_invites_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_proto_invites_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Invite); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_invites_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*CreateRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_invites_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*CreateResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_invites_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ReadRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_invites_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ReadResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_invites_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ListRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_invites_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ListResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_invites_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DeleteRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_invites_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DeleteResponse); 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_invites_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 9,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_proto_invites_proto_goTypes,
|
||||
DependencyIndexes: file_proto_invites_proto_depIdxs,
|
||||
MessageInfos: file_proto_invites_proto_msgTypes,
|
||||
}.Build()
|
||||
File_proto_invites_proto = out.File
|
||||
file_proto_invites_proto_rawDesc = nil
|
||||
file_proto_invites_proto_goTypes = nil
|
||||
file_proto_invites_proto_depIdxs = nil
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
// Code generated by protoc-gen-micro. DO NOT EDIT.
|
||||
// source: proto/invites.proto
|
||||
|
||||
package invites
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
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 Invites service
|
||||
|
||||
func NewInvitesEndpoints() []*api.Endpoint {
|
||||
return []*api.Endpoint{}
|
||||
}
|
||||
|
||||
// Client API for Invites service
|
||||
|
||||
type InvitesService interface {
|
||||
// Create an invite
|
||||
Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error)
|
||||
// Read an invite using ID or code
|
||||
Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error)
|
||||
// List invited for a group or specific email
|
||||
List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error)
|
||||
// Delete an invite
|
||||
Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error)
|
||||
}
|
||||
|
||||
type invitesService struct {
|
||||
c client.Client
|
||||
name string
|
||||
}
|
||||
|
||||
func NewInvitesService(name string, c client.Client) InvitesService {
|
||||
return &invitesService{
|
||||
c: c,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *invitesService) Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Invites.Create", in)
|
||||
out := new(CreateResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *invitesService) Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Invites.Read", in)
|
||||
out := new(ReadResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *invitesService) List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Invites.List", in)
|
||||
out := new(ListResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *invitesService) Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Invites.Delete", in)
|
||||
out := new(DeleteResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Invites service
|
||||
|
||||
type InvitesHandler interface {
|
||||
// Create an invite
|
||||
Create(context.Context, *CreateRequest, *CreateResponse) error
|
||||
// Read an invite using ID or code
|
||||
Read(context.Context, *ReadRequest, *ReadResponse) error
|
||||
// List invited for a group or specific email
|
||||
List(context.Context, *ListRequest, *ListResponse) error
|
||||
// Delete an invite
|
||||
Delete(context.Context, *DeleteRequest, *DeleteResponse) error
|
||||
}
|
||||
|
||||
func RegisterInvitesHandler(s server.Server, hdlr InvitesHandler, opts ...server.HandlerOption) error {
|
||||
type invites interface {
|
||||
Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error
|
||||
Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error
|
||||
List(ctx context.Context, in *ListRequest, out *ListResponse) error
|
||||
Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error
|
||||
}
|
||||
type Invites struct {
|
||||
invites
|
||||
}
|
||||
h := &invitesHandler{hdlr}
|
||||
return s.Handle(s.NewHandler(&Invites{h}, opts...))
|
||||
}
|
||||
|
||||
type invitesHandler struct {
|
||||
InvitesHandler
|
||||
}
|
||||
|
||||
func (h *invitesHandler) Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error {
|
||||
return h.InvitesHandler.Create(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *invitesHandler) Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error {
|
||||
return h.InvitesHandler.Read(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *invitesHandler) List(ctx context.Context, in *ListRequest, out *ListResponse) error {
|
||||
return h.InvitesHandler.List(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *invitesHandler) Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error {
|
||||
return h.InvitesHandler.Delete(ctx, in, out)
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package invites;
|
||||
option go_package = "./proto;invites";
|
||||
|
||||
service Invites {
|
||||
// Create an invite
|
||||
rpc Create(CreateRequest) returns (CreateResponse);
|
||||
// Read an invite using ID or code
|
||||
rpc Read(ReadRequest) returns (ReadResponse);
|
||||
// List invited for a group or specific email
|
||||
rpc List(ListRequest) returns (ListResponse);
|
||||
// Delete an invite
|
||||
rpc Delete(DeleteRequest) returns (DeleteResponse);
|
||||
}
|
||||
|
||||
message Invite {
|
||||
string id = 1;
|
||||
string group_id = 2;
|
||||
string email = 3;
|
||||
string code = 4;
|
||||
}
|
||||
|
||||
message CreateRequest {
|
||||
string group_id = 1;
|
||||
string email = 2;
|
||||
}
|
||||
|
||||
message CreateResponse {
|
||||
Invite invite = 1;
|
||||
}
|
||||
|
||||
message ReadRequest {
|
||||
string id = 1;
|
||||
string code = 2;
|
||||
}
|
||||
|
||||
message ReadResponse {
|
||||
Invite invite = 1;
|
||||
}
|
||||
|
||||
message ListRequest {
|
||||
string group_id = 1;
|
||||
string email = 2;
|
||||
}
|
||||
|
||||
message ListResponse {
|
||||
repeated Invite invites = 1;
|
||||
}
|
||||
|
||||
message DeleteRequest {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message DeleteResponse {}
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
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/mail.proto
|
||||
|
||||
.PHONY: docs
|
||||
docs:
|
||||
protoc --openapi_out=. --proto_path=. --micro_out=. --go_out=:. proto/mail.proto
|
||||
@redoc-cli bundle api-mail.json
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build -o mail *.go
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -v ./... -cover
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
docker build . -t mail:latest
|
||||
@@ -1,6 +0,0 @@
|
||||
An inbox for your messages
|
||||
|
||||
# Mail Service
|
||||
|
||||
The mail service is a simplified service for sending mail, much like email where the messages stay on the server.
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
"github.com/micro/micro/v3/service/store"
|
||||
pb "github.com/micro/services/mail/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
messagePrefix = "message"
|
||||
joinKey = "/"
|
||||
)
|
||||
|
||||
type Mail struct{}
|
||||
|
||||
// Send a message
|
||||
func (m *Mail) Send(ctx context.Context, req *pb.SendRequest, rsp *pb.SendResponse) error {
|
||||
// validate the request
|
||||
if len(req.To) == 0 {
|
||||
return errors.BadRequest("mail.Send.MissingTo", "Missing to")
|
||||
}
|
||||
if len(req.From) == 0 {
|
||||
return errors.BadRequest("mail.Send.MissingFrom", "Missing from")
|
||||
}
|
||||
if len(req.Text) == 0 {
|
||||
return errors.BadRequest("mail.Send.MissingText", "Missing text")
|
||||
}
|
||||
|
||||
// construct the message and marshal it to json
|
||||
msg := &pb.Message{
|
||||
Id: uuid.New().String(),
|
||||
To: req.To,
|
||||
From: req.From,
|
||||
Subject: req.Subject,
|
||||
Text: req.Text,
|
||||
SentAt: time.Now().Unix(),
|
||||
}
|
||||
bytes, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return errors.BadRequest("mail.Send.Unknown", "Error encoding message")
|
||||
}
|
||||
|
||||
// write the message to the store under the recipients key
|
||||
key := strings.Join([]string{messagePrefix, req.To, msg.Id}, joinKey)
|
||||
if err := store.Write(&store.Record{Key: key, Value: bytes}); err != nil {
|
||||
return errors.BadRequest("mail.Send.Unknown", "Error writing to the store")
|
||||
}
|
||||
|
||||
// write the message to the store under the id (so it can be looked up without needing to know
|
||||
// the users id)
|
||||
key = strings.Join([]string{messagePrefix, msg.Id}, joinKey)
|
||||
if err := store.Write(&store.Record{Key: key, Value: bytes}); err != nil {
|
||||
return errors.BadRequest("mail.Send.Unknown", "Error writing to the store")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List mail for a user
|
||||
func (m *Mail) List(ctx context.Context, req *pb.ListRequest, rsp *pb.ListResponse) error {
|
||||
// validate the request
|
||||
if len(req.User) == 0 {
|
||||
return errors.BadRequest("mail.List.MissingUser", "Missing user")
|
||||
}
|
||||
|
||||
// query the store for any mail sent to the user
|
||||
prefix := strings.Join([]string{messagePrefix, req.User}, joinKey)
|
||||
recs, err := store.Read(prefix, store.ReadPrefix())
|
||||
if err != nil {
|
||||
return errors.BadRequest("mail.List.Unknown", "Error reading from the store")
|
||||
}
|
||||
|
||||
// serialize the result
|
||||
rsp.Mail = make([]*pb.Message, len(recs))
|
||||
for i, r := range recs {
|
||||
var msg pb.Message
|
||||
if err := json.Unmarshal(r.Value, &msg); err != nil {
|
||||
return errors.BadRequest("mail.List.Unknown", "Error decoding message")
|
||||
}
|
||||
rsp.Mail[i] = &msg
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read a message
|
||||
func (m *Mail) Read(ctx context.Context, req *pb.ReadRequest, rsp *pb.ReadResponse) error {
|
||||
// validate the request
|
||||
if len(req.Id) == 0 {
|
||||
return errors.BadRequest("mail.Read.MissingUser", "Missing user")
|
||||
}
|
||||
|
||||
// query the store
|
||||
key := strings.Join([]string{messagePrefix, req.Id}, joinKey)
|
||||
recs, err := store.Read(key)
|
||||
if err == store.ErrNotFound {
|
||||
return errors.NotFound("message.Read.InvalidID", "Message not found with ID")
|
||||
} else if err != nil {
|
||||
return errors.BadRequest("mail.Read.Unknown", "Error reading from the store")
|
||||
}
|
||||
|
||||
// serialize the response
|
||||
var msg pb.Message
|
||||
if err := json.Unmarshal(recs[0].Value, &msg); err != nil {
|
||||
return errors.BadRequest("mail.Read.Unknown", "Error decoding message")
|
||||
}
|
||||
rsp.Message = &msg
|
||||
|
||||
return nil
|
||||
}
|
||||
25
mail/main.go
25
mail/main.go
@@ -1,25 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/micro/micro/v3/service"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
|
||||
"github.com/micro/services/mail/handler"
|
||||
pb "github.com/micro/services/mail/proto"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create the service
|
||||
srv := service.New(
|
||||
service.Name("mail"),
|
||||
service.Version("latest"),
|
||||
)
|
||||
|
||||
// Register the handler against the server
|
||||
pb.RegisterMailHandler(srv.Server(), new(handler.Mail))
|
||||
|
||||
// Run the service
|
||||
if err := srv.Run(); err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
service mail
|
||||
@@ -1,603 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.25.0
|
||||
// protoc v3.15.5
|
||||
// source: proto/mail.proto
|
||||
|
||||
package mail
|
||||
|
||||
import (
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
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
|
||||
|
||||
type Message struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
To string `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"`
|
||||
From string `protobuf:"bytes,3,opt,name=from,proto3" json:"from,omitempty"`
|
||||
Subject string `protobuf:"bytes,4,opt,name=subject,proto3" json:"subject,omitempty"`
|
||||
Text string `protobuf:"bytes,5,opt,name=text,proto3" json:"text,omitempty"`
|
||||
SentAt int64 `protobuf:"varint,6,opt,name=sent_at,json=sentAt,proto3" json:"sent_at,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Message) Reset() {
|
||||
*x = Message{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_mail_proto_msgTypes[0]
|
||||
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_mail_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 Message.ProtoReflect.Descriptor instead.
|
||||
func (*Message) Descriptor() ([]byte, []int) {
|
||||
return file_proto_mail_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Message) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Message) GetTo() string {
|
||||
if x != nil {
|
||||
return x.To
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Message) GetFrom() string {
|
||||
if x != nil {
|
||||
return x.From
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Message) GetSubject() string {
|
||||
if x != nil {
|
||||
return x.Subject
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Message) GetText() string {
|
||||
if x != nil {
|
||||
return x.Text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Message) GetSentAt() int64 {
|
||||
if x != nil {
|
||||
return x.SentAt
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type SendRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
To string `protobuf:"bytes,1,opt,name=to,proto3" json:"to,omitempty"`
|
||||
From string `protobuf:"bytes,2,opt,name=from,proto3" json:"from,omitempty"`
|
||||
Subject string `protobuf:"bytes,3,opt,name=subject,proto3" json:"subject,omitempty"`
|
||||
Text string `protobuf:"bytes,4,opt,name=text,proto3" json:"text,omitempty"`
|
||||
}
|
||||
|
||||
func (x *SendRequest) Reset() {
|
||||
*x = SendRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_mail_proto_msgTypes[1]
|
||||
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_mail_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 SendRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SendRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_mail_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *SendRequest) GetTo() string {
|
||||
if x != nil {
|
||||
return x.To
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SendRequest) GetFrom() string {
|
||||
if x != nil {
|
||||
return x.From
|
||||
}
|
||||
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 ""
|
||||
}
|
||||
|
||||
type SendResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *SendResponse) Reset() {
|
||||
*x = SendResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_mail_proto_msgTypes[2]
|
||||
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_mail_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 SendResponse.ProtoReflect.Descriptor instead.
|
||||
func (*SendResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_mail_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
type ListRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
User string `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ListRequest) Reset() {
|
||||
*x = ListRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_mail_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ListRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_mail_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 ListRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ListRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_mail_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *ListRequest) GetUser() string {
|
||||
if x != nil {
|
||||
return x.User
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ListResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Mail []*Message `protobuf:"bytes,1,rep,name=mail,proto3" json:"mail,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ListResponse) Reset() {
|
||||
*x = ListResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_mail_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ListResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_mail_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 ListResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ListResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_mail_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *ListResponse) GetMail() []*Message {
|
||||
if x != nil {
|
||||
return x.Mail
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ReadRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ReadRequest) Reset() {
|
||||
*x = ReadRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_mail_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ReadRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ReadRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ReadRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_mail_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 ReadRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ReadRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_mail_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *ReadRequest) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ReadResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Message *Message `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ReadResponse) Reset() {
|
||||
*x = ReadResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_mail_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ReadResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ReadResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ReadResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_mail_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 ReadResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ReadResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_mail_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *ReadResponse) GetMessage() *Message {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_proto_mail_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_proto_mail_proto_rawDesc = []byte{
|
||||
0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x12, 0x04, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x84, 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, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x02, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 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, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x6e, 0x74, 0x5f, 0x61,
|
||||
0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x74, 0x41, 0x74, 0x22,
|
||||
0x5f, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e,
|
||||
0x0a, 0x02, 0x74, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x12,
|
||||
0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72,
|
||||
0x6f, 0x6d, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04,
|
||||
0x74, 0x65, 0x78, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74,
|
||||
0x22, 0x0e, 0x0a, 0x0c, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x21, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||
0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75,
|
||||
0x73, 0x65, 0x72, 0x22, 0x31, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x04, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x0d, 0x2e, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||
0x52, 0x04, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x1d, 0x0a, 0x0b, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x37, 0x0a, 0x0c, 0x52, 0x65, 0x61, 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, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x4d, 0x65,
|
||||
0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x93,
|
||||
0x01, 0x0a, 0x04, 0x4d, 0x61, 0x69, 0x6c, 0x12, 0x2d, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12,
|
||||
0x11, 0x2e, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x11,
|
||||
0x2e, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x12, 0x2e, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x11, 0x2e,
|
||||
0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x12, 0x2e, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0c, 0x5a, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x6d, 0x61,
|
||||
0x69, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_proto_mail_proto_rawDescOnce sync.Once
|
||||
file_proto_mail_proto_rawDescData = file_proto_mail_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_proto_mail_proto_rawDescGZIP() []byte {
|
||||
file_proto_mail_proto_rawDescOnce.Do(func() {
|
||||
file_proto_mail_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_mail_proto_rawDescData)
|
||||
})
|
||||
return file_proto_mail_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proto_mail_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||
var file_proto_mail_proto_goTypes = []interface{}{
|
||||
(*Message)(nil), // 0: mail.Message
|
||||
(*SendRequest)(nil), // 1: mail.SendRequest
|
||||
(*SendResponse)(nil), // 2: mail.SendResponse
|
||||
(*ListRequest)(nil), // 3: mail.ListRequest
|
||||
(*ListResponse)(nil), // 4: mail.ListResponse
|
||||
(*ReadRequest)(nil), // 5: mail.ReadRequest
|
||||
(*ReadResponse)(nil), // 6: mail.ReadResponse
|
||||
}
|
||||
var file_proto_mail_proto_depIdxs = []int32{
|
||||
0, // 0: mail.ListResponse.mail:type_name -> mail.Message
|
||||
0, // 1: mail.ReadResponse.message:type_name -> mail.Message
|
||||
1, // 2: mail.Mail.Send:input_type -> mail.SendRequest
|
||||
3, // 3: mail.Mail.List:input_type -> mail.ListRequest
|
||||
5, // 4: mail.Mail.Read:input_type -> mail.ReadRequest
|
||||
2, // 5: mail.Mail.Send:output_type -> mail.SendResponse
|
||||
4, // 6: mail.Mail.List:output_type -> mail.ListResponse
|
||||
6, // 7: mail.Mail.Read:output_type -> mail.ReadResponse
|
||||
5, // [5:8] is the sub-list for method output_type
|
||||
2, // [2:5] is the sub-list for method input_type
|
||||
2, // [2:2] is the sub-list for extension type_name
|
||||
2, // [2:2] is the sub-list for extension extendee
|
||||
0, // [0:2] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_proto_mail_proto_init() }
|
||||
func file_proto_mail_proto_init() {
|
||||
if File_proto_mail_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_proto_mail_proto_msgTypes[0].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
|
||||
}
|
||||
}
|
||||
file_proto_mail_proto_msgTypes[1].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_mail_proto_msgTypes[2].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_mail_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ListRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_mail_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ListResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_mail_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ReadRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_mail_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ReadResponse); 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_mail_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 7,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_proto_mail_proto_goTypes,
|
||||
DependencyIndexes: file_proto_mail_proto_depIdxs,
|
||||
MessageInfos: file_proto_mail_proto_msgTypes,
|
||||
}.Build()
|
||||
File_proto_mail_proto = out.File
|
||||
file_proto_mail_proto_rawDesc = nil
|
||||
file_proto_mail_proto_goTypes = nil
|
||||
file_proto_mail_proto_depIdxs = nil
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
// Code generated by protoc-gen-micro. DO NOT EDIT.
|
||||
// source: proto/mail.proto
|
||||
|
||||
package mail
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
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 Mail service
|
||||
|
||||
func NewMailEndpoints() []*api.Endpoint {
|
||||
return []*api.Endpoint{}
|
||||
}
|
||||
|
||||
// Client API for Mail service
|
||||
|
||||
type MailService interface {
|
||||
Send(ctx context.Context, in *SendRequest, opts ...client.CallOption) (*SendResponse, error)
|
||||
List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error)
|
||||
Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error)
|
||||
}
|
||||
|
||||
type mailService struct {
|
||||
c client.Client
|
||||
name string
|
||||
}
|
||||
|
||||
func NewMailService(name string, c client.Client) MailService {
|
||||
return &mailService{
|
||||
c: c,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *mailService) Send(ctx context.Context, in *SendRequest, opts ...client.CallOption) (*SendResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Mail.Send", in)
|
||||
out := new(SendResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *mailService) List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Mail.List", in)
|
||||
out := new(ListResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *mailService) Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Mail.Read", in)
|
||||
out := new(ReadResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Mail service
|
||||
|
||||
type MailHandler interface {
|
||||
Send(context.Context, *SendRequest, *SendResponse) error
|
||||
List(context.Context, *ListRequest, *ListResponse) error
|
||||
Read(context.Context, *ReadRequest, *ReadResponse) error
|
||||
}
|
||||
|
||||
func RegisterMailHandler(s server.Server, hdlr MailHandler, opts ...server.HandlerOption) error {
|
||||
type mail interface {
|
||||
Send(ctx context.Context, in *SendRequest, out *SendResponse) error
|
||||
List(ctx context.Context, in *ListRequest, out *ListResponse) error
|
||||
Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error
|
||||
}
|
||||
type Mail struct {
|
||||
mail
|
||||
}
|
||||
h := &mailHandler{hdlr}
|
||||
return s.Handle(s.NewHandler(&Mail{h}, opts...))
|
||||
}
|
||||
|
||||
type mailHandler struct {
|
||||
MailHandler
|
||||
}
|
||||
|
||||
func (h *mailHandler) Send(ctx context.Context, in *SendRequest, out *SendResponse) error {
|
||||
return h.MailHandler.Send(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *mailHandler) List(ctx context.Context, in *ListRequest, out *ListResponse) error {
|
||||
return h.MailHandler.List(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *mailHandler) Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error {
|
||||
return h.MailHandler.Read(ctx, in, out)
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package mail;
|
||||
option go_package = "./proto;mail";
|
||||
|
||||
service Mail {
|
||||
rpc Send(SendRequest) returns (SendResponse);
|
||||
rpc List(ListRequest) returns (ListResponse);
|
||||
rpc Read(ReadRequest) returns (ReadResponse);
|
||||
}
|
||||
|
||||
message Message {
|
||||
string id = 1;
|
||||
string to = 2;
|
||||
string from = 3;
|
||||
string subject = 4;
|
||||
string text = 5;
|
||||
int64 sent_at = 6;
|
||||
}
|
||||
|
||||
message SendRequest {
|
||||
string to = 1;
|
||||
string from = 2;
|
||||
string subject = 3;
|
||||
string text = 4;
|
||||
}
|
||||
|
||||
message SendResponse {}
|
||||
|
||||
message ListRequest {
|
||||
string user = 1;
|
||||
}
|
||||
|
||||
message ListResponse {
|
||||
repeated Message mail = 1;
|
||||
}
|
||||
|
||||
message ReadRequest {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message ReadResponse {
|
||||
Message message = 1;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
The mail service is a simplified service for sending mail, much like email.
|
||||
|
||||
# Mail Service
|
||||
|
||||
## Send a message
|
||||
|
||||
### CLI
|
||||
|
||||
```bash
|
||||
> micro mail send --to=John --from=Barry --subject=HelloWorld --text="Hello John"
|
||||
```
|
||||
|
||||
## List the mail a user has received
|
||||
|
||||
### CLI
|
||||
|
||||
```bash
|
||||
> micro mail list --user=John
|
||||
{
|
||||
"mail": [
|
||||
{
|
||||
"id": "78efd836-ca51-4163-af43-65985f7c6587",
|
||||
"to": "John",
|
||||
"from": "Barry",
|
||||
"subject": "HelloWorld",
|
||||
"text": "Hello John",
|
||||
"sent_at": "1602777240"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Lookup an individual email by ID
|
||||
|
||||
### CLI
|
||||
|
||||
```bash
|
||||
> micro mail read --id=78efd836-ca51-4163-af43-65985f7c6587
|
||||
{
|
||||
"message": {
|
||||
"id": "78efd836-ca51-4163-af43-65985f7c6587",
|
||||
"to": "John",
|
||||
"from": "Barry",
|
||||
"subject": "HelloWorld",
|
||||
"text": "Hello John",
|
||||
"sent_at": "1602777240"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,6 +0,0 @@
|
||||
Store your notes and todos
|
||||
|
||||
# Notes Service
|
||||
|
||||
The notes service offers basic storage of notes and todo lists.
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
"github.com/micro/micro/v3/service/store"
|
||||
pb "github.com/micro/services/notes/proto"
|
||||
)
|
||||
|
||||
const storePrefix = "notes/"
|
||||
|
||||
// New returns an initialized notes handler
|
||||
func New() pb.NotesHandler {
|
||||
return new(handler)
|
||||
}
|
||||
|
||||
type handler struct{}
|
||||
|
||||
// List all the notes
|
||||
func (h *handler) List(ctx context.Context, req *pb.ListRequest, rsp *pb.ListResponse) error {
|
||||
// query the store
|
||||
recs, err := store.Read(storePrefix, store.ReadPrefix())
|
||||
if err != nil {
|
||||
logger.Errorf("Error reading notes from the store: %v", err)
|
||||
return errors.InternalServerError("notes.List.Unknown", "Error reading from the store")
|
||||
}
|
||||
|
||||
// serialize the response
|
||||
rsp.Notes = make([]*pb.Note, len(recs))
|
||||
for i, r := range recs {
|
||||
var note pb.Note
|
||||
if err := json.Unmarshal(r.Value, ¬e); err != nil {
|
||||
logger.Errorf("Error unmarshaling note: %v", err)
|
||||
return errors.InternalServerError("notes.List.Unknown", "Error unmarshaling note")
|
||||
}
|
||||
rsp.Notes[i] = ¬e
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a note
|
||||
func (h *handler) Create(ctx context.Context, req *pb.CreateRequest, rsp *pb.CreateResponse) error {
|
||||
// validate the request
|
||||
if len(req.Title) == 0 {
|
||||
return errors.BadRequest("notes.Create.MissingTitle", "Missing title")
|
||||
}
|
||||
|
||||
// construct the note
|
||||
note := &pb.Note{
|
||||
Id: uuid.New().String(),
|
||||
Created: time.Now().Unix(),
|
||||
Title: req.Title,
|
||||
Text: req.Text,
|
||||
}
|
||||
|
||||
// marshal the note to bytes
|
||||
bytes, err := json.Marshal(note)
|
||||
if err != nil {
|
||||
logger.Errorf("Error marshaling note: %v", err)
|
||||
return errors.InternalServerError("notes.Create.Unknown", "Error marshaling note")
|
||||
}
|
||||
|
||||
// write to the store
|
||||
key := storePrefix + note.Id
|
||||
if err := store.Write(&store.Record{Key: key, Value: bytes}); err != nil {
|
||||
logger.Errorf("Error writing to store: %v", err)
|
||||
return errors.InternalServerError("notes.Create.Unknown", "Error writing to store")
|
||||
}
|
||||
|
||||
// return the id
|
||||
rsp.Id = note.Id
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete a note
|
||||
func (h *handler) Delete(ctx context.Context, req *pb.DeleteRequest, rsp *pb.DeleteResponse) error {
|
||||
// validate the request
|
||||
if len(req.Id) == 0 {
|
||||
return errors.BadRequest("notes.Delete.MissingID", "Missing id")
|
||||
}
|
||||
|
||||
// delete the note from the store
|
||||
if err := store.Delete(storePrefix + req.Id); err == store.ErrNotFound {
|
||||
return errors.NotFound("notes.Delete.InvalidID", "Note not found with this ID")
|
||||
} else if err != nil {
|
||||
logger.Errorf("Error deleting from the store: %v", err)
|
||||
return errors.InternalServerError("notes.Delete.Unknown", "Error deleting from the store")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update a note
|
||||
func (h *handler) Update(ctx context.Context, req *pb.UpdateRequest, rsp *pb.UpdateResponse) error {
|
||||
// validate the request
|
||||
if len(req.Id) == 0 {
|
||||
return errors.BadRequest("notes.Update.MissingID", "Missing id")
|
||||
}
|
||||
if len(req.Title) == 0 {
|
||||
return errors.BadRequest("notes.Update.MissingTitle", "Missing title")
|
||||
}
|
||||
|
||||
// read the note from the store
|
||||
recs, err := store.Read(storePrefix + req.Id)
|
||||
if err == store.ErrNotFound {
|
||||
return errors.NotFound("notes.Update.InvalidID", "Note not found with this ID")
|
||||
} else if err != nil {
|
||||
logger.Errorf("Error reading from the store: %v", err)
|
||||
return errors.InternalServerError("notes.Update.Unknown", "Error reading from the store")
|
||||
}
|
||||
|
||||
// unmarshal the note
|
||||
var note pb.Note
|
||||
if err := json.Unmarshal(recs[0].Value, ¬e); err != nil {
|
||||
logger.Errorf("Error unmarshaling note: %v", err)
|
||||
return errors.InternalServerError("notes.Update.Unknown", "Error unmarshaling note")
|
||||
}
|
||||
|
||||
// assign the new title and text
|
||||
note.Title = req.Title
|
||||
note.Text = req.Text
|
||||
|
||||
// marshal the note to bytes
|
||||
bytes, err := json.Marshal(note)
|
||||
if err != nil {
|
||||
logger.Errorf("Error marshaling note: %v", err)
|
||||
return errors.InternalServerError("notes.Update.Unknown", "Error marshaling note")
|
||||
}
|
||||
|
||||
// write to the store
|
||||
key := storePrefix + note.Id
|
||||
if err := store.Write(&store.Record{Key: key, Value: bytes}); err != nil {
|
||||
logger.Errorf("Error writing to store: %v", err)
|
||||
return errors.InternalServerError("notes.Update.Unknown", "Error writing to store")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateStream updates a note every time an update is sent on the stream
|
||||
func (h *handler) UpdateStream(ctx context.Context, stream pb.Notes_UpdateStreamStream) error {
|
||||
for {
|
||||
uReq, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return errors.InternalServerError("notes.UpdateStream.Unknown", "Error reading from stream")
|
||||
}
|
||||
|
||||
if err := h.Update(ctx, uReq, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/micro/micro/v3/service"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
|
||||
"github.com/micro/services/notes/handler"
|
||||
pb "github.com/micro/services/notes/proto"
|
||||
)
|
||||
|
||||
func main() {
|
||||
srv := service.New(
|
||||
service.Name("notes"),
|
||||
service.Version("latest"),
|
||||
)
|
||||
|
||||
pb.RegisterNotesHandler(srv.Server(), handler.New())
|
||||
|
||||
if err := srv.Run(); err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
service notes
|
||||
@@ -1,436 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: notes/proto/notes.proto
|
||||
|
||||
package notes
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type Note struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Created int64 `protobuf:"varint,2,opt,name=created,proto3" json:"created,omitempty"`
|
||||
Title string `protobuf:"bytes,3,opt,name=title,proto3" json:"title,omitempty"`
|
||||
Text string `protobuf:"bytes,4,opt,name=text,proto3" json:"text,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Note) Reset() { *m = Note{} }
|
||||
func (m *Note) String() string { return proto.CompactTextString(m) }
|
||||
func (*Note) ProtoMessage() {}
|
||||
func (*Note) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7f19915b807268a5, []int{0}
|
||||
}
|
||||
|
||||
func (m *Note) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Note.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Note) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Note.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Note) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Note.Merge(m, src)
|
||||
}
|
||||
func (m *Note) XXX_Size() int {
|
||||
return xxx_messageInfo_Note.Size(m)
|
||||
}
|
||||
func (m *Note) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Note.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Note proto.InternalMessageInfo
|
||||
|
||||
func (m *Note) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Note) GetCreated() int64 {
|
||||
if m != nil {
|
||||
return m.Created
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Note) GetTitle() string {
|
||||
if m != nil {
|
||||
return m.Title
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Note) GetText() string {
|
||||
if m != nil {
|
||||
return m.Text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type CreateRequest struct {
|
||||
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
|
||||
Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *CreateRequest) Reset() { *m = CreateRequest{} }
|
||||
func (m *CreateRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*CreateRequest) ProtoMessage() {}
|
||||
func (*CreateRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7f19915b807268a5, []int{1}
|
||||
}
|
||||
|
||||
func (m *CreateRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_CreateRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *CreateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_CreateRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *CreateRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_CreateRequest.Merge(m, src)
|
||||
}
|
||||
func (m *CreateRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_CreateRequest.Size(m)
|
||||
}
|
||||
func (m *CreateRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_CreateRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_CreateRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *CreateRequest) GetTitle() string {
|
||||
if m != nil {
|
||||
return m.Title
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *CreateRequest) GetText() string {
|
||||
if m != nil {
|
||||
return m.Text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type CreateResponse struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *CreateResponse) Reset() { *m = CreateResponse{} }
|
||||
func (m *CreateResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*CreateResponse) ProtoMessage() {}
|
||||
func (*CreateResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7f19915b807268a5, []int{2}
|
||||
}
|
||||
|
||||
func (m *CreateResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_CreateResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *CreateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_CreateResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *CreateResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_CreateResponse.Merge(m, src)
|
||||
}
|
||||
func (m *CreateResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_CreateResponse.Size(m)
|
||||
}
|
||||
func (m *CreateResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_CreateResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_CreateResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *CreateResponse) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type UpdateRequest struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"`
|
||||
Text string `protobuf:"bytes,3,opt,name=text,proto3" json:"text,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *UpdateRequest) Reset() { *m = UpdateRequest{} }
|
||||
func (m *UpdateRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*UpdateRequest) ProtoMessage() {}
|
||||
func (*UpdateRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7f19915b807268a5, []int{3}
|
||||
}
|
||||
|
||||
func (m *UpdateRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_UpdateRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *UpdateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_UpdateRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *UpdateRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_UpdateRequest.Merge(m, src)
|
||||
}
|
||||
func (m *UpdateRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_UpdateRequest.Size(m)
|
||||
}
|
||||
func (m *UpdateRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_UpdateRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_UpdateRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *UpdateRequest) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *UpdateRequest) GetTitle() string {
|
||||
if m != nil {
|
||||
return m.Title
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *UpdateRequest) GetText() string {
|
||||
if m != nil {
|
||||
return m.Text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type UpdateResponse struct {
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *UpdateResponse) Reset() { *m = UpdateResponse{} }
|
||||
func (m *UpdateResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*UpdateResponse) ProtoMessage() {}
|
||||
func (*UpdateResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7f19915b807268a5, []int{4}
|
||||
}
|
||||
|
||||
func (m *UpdateResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_UpdateResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *UpdateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_UpdateResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *UpdateResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_UpdateResponse.Merge(m, src)
|
||||
}
|
||||
func (m *UpdateResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_UpdateResponse.Size(m)
|
||||
}
|
||||
func (m *UpdateResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_UpdateResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_UpdateResponse proto.InternalMessageInfo
|
||||
|
||||
type DeleteRequest struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *DeleteRequest) Reset() { *m = DeleteRequest{} }
|
||||
func (m *DeleteRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*DeleteRequest) ProtoMessage() {}
|
||||
func (*DeleteRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7f19915b807268a5, []int{5}
|
||||
}
|
||||
|
||||
func (m *DeleteRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_DeleteRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *DeleteRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_DeleteRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *DeleteRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_DeleteRequest.Merge(m, src)
|
||||
}
|
||||
func (m *DeleteRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_DeleteRequest.Size(m)
|
||||
}
|
||||
func (m *DeleteRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_DeleteRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_DeleteRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *DeleteRequest) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type DeleteResponse struct {
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *DeleteResponse) Reset() { *m = DeleteResponse{} }
|
||||
func (m *DeleteResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*DeleteResponse) ProtoMessage() {}
|
||||
func (*DeleteResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7f19915b807268a5, []int{6}
|
||||
}
|
||||
|
||||
func (m *DeleteResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_DeleteResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *DeleteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_DeleteResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *DeleteResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_DeleteResponse.Merge(m, src)
|
||||
}
|
||||
func (m *DeleteResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_DeleteResponse.Size(m)
|
||||
}
|
||||
func (m *DeleteResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_DeleteResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_DeleteResponse proto.InternalMessageInfo
|
||||
|
||||
type ListRequest struct {
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ListRequest) Reset() { *m = ListRequest{} }
|
||||
func (m *ListRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*ListRequest) ProtoMessage() {}
|
||||
func (*ListRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7f19915b807268a5, []int{7}
|
||||
}
|
||||
|
||||
func (m *ListRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_ListRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *ListRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_ListRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *ListRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ListRequest.Merge(m, src)
|
||||
}
|
||||
func (m *ListRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_ListRequest.Size(m)
|
||||
}
|
||||
func (m *ListRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ListRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ListRequest proto.InternalMessageInfo
|
||||
|
||||
type ListResponse struct {
|
||||
Notes []*Note `protobuf:"bytes,1,rep,name=notes,proto3" json:"notes,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ListResponse) Reset() { *m = ListResponse{} }
|
||||
func (m *ListResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*ListResponse) ProtoMessage() {}
|
||||
func (*ListResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7f19915b807268a5, []int{8}
|
||||
}
|
||||
|
||||
func (m *ListResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_ListResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *ListResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_ListResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *ListResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ListResponse.Merge(m, src)
|
||||
}
|
||||
func (m *ListResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_ListResponse.Size(m)
|
||||
}
|
||||
func (m *ListResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ListResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ListResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *ListResponse) GetNotes() []*Note {
|
||||
if m != nil {
|
||||
return m.Notes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Note)(nil), "notes.Note")
|
||||
proto.RegisterType((*CreateRequest)(nil), "notes.CreateRequest")
|
||||
proto.RegisterType((*CreateResponse)(nil), "notes.CreateResponse")
|
||||
proto.RegisterType((*UpdateRequest)(nil), "notes.UpdateRequest")
|
||||
proto.RegisterType((*UpdateResponse)(nil), "notes.UpdateResponse")
|
||||
proto.RegisterType((*DeleteRequest)(nil), "notes.DeleteRequest")
|
||||
proto.RegisterType((*DeleteResponse)(nil), "notes.DeleteResponse")
|
||||
proto.RegisterType((*ListRequest)(nil), "notes.ListRequest")
|
||||
proto.RegisterType((*ListResponse)(nil), "notes.ListResponse")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("notes/proto/notes.proto", fileDescriptor_7f19915b807268a5) }
|
||||
|
||||
var fileDescriptor_7f19915b807268a5 = []byte{
|
||||
// 344 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0xcf, 0x4a, 0xc3, 0x40,
|
||||
0x10, 0xc6, 0xc9, 0x9f, 0x56, 0x9c, 0x36, 0x45, 0xd6, 0x8a, 0xa1, 0x17, 0x63, 0x4e, 0x01, 0xb1,
|
||||
0xc1, 0x8a, 0x07, 0x11, 0x2f, 0xea, 0x45, 0x10, 0x0f, 0x11, 0x2f, 0xbd, 0xb5, 0xc9, 0xa0, 0x0b,
|
||||
0x6d, 0x37, 0x66, 0xa7, 0xe2, 0xc3, 0xf8, 0xb0, 0x92, 0xdd, 0x24, 0xcd, 0xd6, 0x7a, 0xf0, 0x36,
|
||||
0xdf, 0xec, 0xfc, 0x66, 0x76, 0xbe, 0x5d, 0x38, 0x5e, 0x09, 0x42, 0x19, 0xe7, 0x85, 0x20, 0x11,
|
||||
0xab, 0x78, 0xac, 0x62, 0xd6, 0x51, 0x22, 0x9c, 0x82, 0xfb, 0x2c, 0x08, 0xd9, 0x00, 0x6c, 0x9e,
|
||||
0xf9, 0x56, 0x60, 0x45, 0xfb, 0x89, 0xcd, 0x33, 0xe6, 0xc3, 0x5e, 0x5a, 0xe0, 0x8c, 0x30, 0xf3,
|
||||
0xed, 0xc0, 0x8a, 0x9c, 0xa4, 0x96, 0x6c, 0x08, 0x1d, 0xe2, 0xb4, 0x40, 0xdf, 0x51, 0xc5, 0x5a,
|
||||
0x30, 0x06, 0x2e, 0xe1, 0x17, 0xf9, 0xae, 0x4a, 0xaa, 0x38, 0xbc, 0x06, 0xef, 0x5e, 0x41, 0x09,
|
||||
0x7e, 0xac, 0x51, 0xd2, 0x06, 0xb5, 0x76, 0xa1, 0x76, 0x0b, 0x0d, 0x60, 0x50, 0xa3, 0x32, 0x17,
|
||||
0x2b, 0xf9, 0xeb, 0x82, 0xe1, 0x23, 0x78, 0xaf, 0x79, 0xd6, 0x6a, 0xbe, 0xbd, 0x41, 0x33, 0xcc,
|
||||
0xde, 0x35, 0xcc, 0x69, 0x0d, 0x3b, 0x80, 0x41, 0xdd, 0x4a, 0x0f, 0x0b, 0x4f, 0xc0, 0x7b, 0xc0,
|
||||
0x05, 0xfe, 0xd9, 0xbc, 0x44, 0xea, 0x82, 0x0a, 0xf1, 0xa0, 0xf7, 0xc4, 0x25, 0x55, 0x40, 0x78,
|
||||
0x01, 0x7d, 0x2d, 0xab, 0xeb, 0x9f, 0x82, 0x36, 0xdc, 0xb7, 0x02, 0x27, 0xea, 0x4d, 0x7a, 0x63,
|
||||
0xfd, 0x16, 0xa5, 0xf7, 0x89, 0x3e, 0x99, 0x7c, 0xdb, 0xd0, 0x29, 0xb5, 0x64, 0x31, 0xb8, 0x25,
|
||||
0xcc, 0x58, 0x55, 0xd5, 0x6a, 0x3c, 0x3a, 0x34, 0x72, 0x55, 0xf7, 0x2b, 0xe8, 0x6a, 0xbb, 0xd8,
|
||||
0xb0, 0x3a, 0x36, 0x8c, 0x1f, 0x1d, 0x6d, 0x65, 0x37, 0x98, 0xde, 0xa2, 0xc1, 0x8c, 0xad, 0x1b,
|
||||
0xcc, 0x5c, 0xb5, 0xc4, 0xb4, 0x5f, 0x0d, 0x66, 0xbc, 0x44, 0x83, 0x99, 0xa6, 0xb2, 0x5b, 0xe8,
|
||||
0xeb, 0xcc, 0x0b, 0x15, 0x38, 0x5b, 0xfe, 0x0b, 0x8e, 0xac, 0xbb, 0xf3, 0xe9, 0xd9, 0x1b, 0xa7,
|
||||
0xf7, 0xf5, 0x7c, 0x9c, 0x8a, 0x65, 0xbc, 0xe4, 0x69, 0x21, 0x62, 0x89, 0xc5, 0x27, 0x4f, 0x51,
|
||||
0xc6, 0xad, 0x5f, 0x7e, 0xa3, 0xe2, 0x79, 0x57, 0x89, 0xcb, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0x31, 0xee, 0xed, 0xda, 0x01, 0x03, 0x00, 0x00,
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
// Code generated by protoc-gen-micro. DO NOT EDIT.
|
||||
// source: notes/proto/notes.proto
|
||||
|
||||
package notes
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
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 Notes service
|
||||
|
||||
func NewNotesEndpoints() []*api.Endpoint {
|
||||
return []*api.Endpoint{}
|
||||
}
|
||||
|
||||
// Client API for Notes service
|
||||
|
||||
type NotesService interface {
|
||||
List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error)
|
||||
Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error)
|
||||
Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error)
|
||||
Update(ctx context.Context, in *UpdateRequest, opts ...client.CallOption) (*UpdateResponse, error)
|
||||
UpdateStream(ctx context.Context, opts ...client.CallOption) (Notes_UpdateStreamService, error)
|
||||
}
|
||||
|
||||
type notesService struct {
|
||||
c client.Client
|
||||
name string
|
||||
}
|
||||
|
||||
func NewNotesService(name string, c client.Client) NotesService {
|
||||
return ¬esService{
|
||||
c: c,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *notesService) List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Notes.List", in)
|
||||
out := new(ListResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *notesService) Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Notes.Create", in)
|
||||
out := new(CreateResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *notesService) Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Notes.Delete", in)
|
||||
out := new(DeleteResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *notesService) Update(ctx context.Context, in *UpdateRequest, opts ...client.CallOption) (*UpdateResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Notes.Update", in)
|
||||
out := new(UpdateResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *notesService) UpdateStream(ctx context.Context, opts ...client.CallOption) (Notes_UpdateStreamService, error) {
|
||||
req := c.c.NewRequest(c.name, "Notes.UpdateStream", &UpdateRequest{})
|
||||
stream, err := c.c.Stream(ctx, req, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ¬esServiceUpdateStream{stream}, nil
|
||||
}
|
||||
|
||||
type Notes_UpdateStreamService interface {
|
||||
Context() context.Context
|
||||
SendMsg(interface{}) error
|
||||
RecvMsg(interface{}) error
|
||||
CloseAndRecv() (*UpdateResponse, error)
|
||||
Send(*UpdateRequest) error
|
||||
}
|
||||
|
||||
type notesServiceUpdateStream struct {
|
||||
stream client.Stream
|
||||
}
|
||||
|
||||
func (x *notesServiceUpdateStream) CloseAndRecv() (*UpdateResponse, error) {
|
||||
if err := x.stream.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := new(UpdateResponse)
|
||||
err := x.RecvMsg(r)
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (x *notesServiceUpdateStream) Context() context.Context {
|
||||
return x.stream.Context()
|
||||
}
|
||||
|
||||
func (x *notesServiceUpdateStream) SendMsg(m interface{}) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *notesServiceUpdateStream) RecvMsg(m interface{}) error {
|
||||
return x.stream.Recv(m)
|
||||
}
|
||||
|
||||
func (x *notesServiceUpdateStream) Send(m *UpdateRequest) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
// Server API for Notes service
|
||||
|
||||
type NotesHandler interface {
|
||||
List(context.Context, *ListRequest, *ListResponse) error
|
||||
Create(context.Context, *CreateRequest, *CreateResponse) error
|
||||
Delete(context.Context, *DeleteRequest, *DeleteResponse) error
|
||||
Update(context.Context, *UpdateRequest, *UpdateResponse) error
|
||||
UpdateStream(context.Context, Notes_UpdateStreamStream) error
|
||||
}
|
||||
|
||||
func RegisterNotesHandler(s server.Server, hdlr NotesHandler, opts ...server.HandlerOption) error {
|
||||
type notes interface {
|
||||
List(ctx context.Context, in *ListRequest, out *ListResponse) error
|
||||
Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error
|
||||
Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error
|
||||
Update(ctx context.Context, in *UpdateRequest, out *UpdateResponse) error
|
||||
UpdateStream(ctx context.Context, stream server.Stream) error
|
||||
}
|
||||
type Notes struct {
|
||||
notes
|
||||
}
|
||||
h := ¬esHandler{hdlr}
|
||||
return s.Handle(s.NewHandler(&Notes{h}, opts...))
|
||||
}
|
||||
|
||||
type notesHandler struct {
|
||||
NotesHandler
|
||||
}
|
||||
|
||||
func (h *notesHandler) List(ctx context.Context, in *ListRequest, out *ListResponse) error {
|
||||
return h.NotesHandler.List(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *notesHandler) Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error {
|
||||
return h.NotesHandler.Create(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *notesHandler) Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error {
|
||||
return h.NotesHandler.Delete(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *notesHandler) Update(ctx context.Context, in *UpdateRequest, out *UpdateResponse) error {
|
||||
return h.NotesHandler.Update(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *notesHandler) UpdateStream(ctx context.Context, stream server.Stream) error {
|
||||
return h.NotesHandler.UpdateStream(ctx, ¬esUpdateStreamStream{stream})
|
||||
}
|
||||
|
||||
type Notes_UpdateStreamStream interface {
|
||||
Context() context.Context
|
||||
SendMsg(interface{}) error
|
||||
RecvMsg(interface{}) error
|
||||
SendAndClose(*UpdateResponse) error
|
||||
Recv() (*UpdateRequest, error)
|
||||
}
|
||||
|
||||
type notesUpdateStreamStream struct {
|
||||
stream server.Stream
|
||||
}
|
||||
|
||||
func (x *notesUpdateStreamStream) SendAndClose(in *UpdateResponse) error {
|
||||
if err := x.SendMsg(in); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.stream.Close()
|
||||
}
|
||||
|
||||
func (x *notesUpdateStreamStream) Context() context.Context {
|
||||
return x.stream.Context()
|
||||
}
|
||||
|
||||
func (x *notesUpdateStreamStream) SendMsg(m interface{}) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *notesUpdateStreamStream) RecvMsg(m interface{}) error {
|
||||
return x.stream.Recv(m)
|
||||
}
|
||||
|
||||
func (x *notesUpdateStreamStream) Recv() (*UpdateRequest, error) {
|
||||
m := new(UpdateRequest)
|
||||
if err := x.stream.Recv(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package notes;
|
||||
option go_package = "./proto;notes";
|
||||
|
||||
service Notes {
|
||||
rpc List(ListRequest) returns (ListResponse);
|
||||
rpc Create(CreateRequest) returns (CreateResponse);
|
||||
rpc Delete(DeleteRequest) returns (DeleteResponse);
|
||||
rpc Update(UpdateRequest) returns (UpdateResponse);
|
||||
rpc UpdateStream(stream UpdateRequest) returns (UpdateResponse);
|
||||
}
|
||||
|
||||
message Note {
|
||||
string id = 1;
|
||||
int64 created = 2;
|
||||
string title = 3;
|
||||
string text = 4;
|
||||
}
|
||||
|
||||
message CreateRequest {
|
||||
string title = 1;
|
||||
string text = 2;
|
||||
}
|
||||
|
||||
message CreateResponse {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message UpdateRequest {
|
||||
string id = 1;
|
||||
string title = 2;
|
||||
string text = 3;
|
||||
}
|
||||
|
||||
message UpdateResponse {}
|
||||
|
||||
message DeleteRequest {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message DeleteResponse {}
|
||||
|
||||
message ListRequest {}
|
||||
|
||||
message ListResponse {
|
||||
repeated Note notes = 1;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
Notes service is an RPC service which offers CRUD for notes. It demonstrates usage of the store, errors and logger pacakges.
|
||||
|
||||
# Notes Service
|
||||
|
||||
## Create a note
|
||||
|
||||
```bash
|
||||
micro notes create --title="HelloWorld" --text="MyFirstNote"
|
||||
{
|
||||
"id": "6d3fa5c0-6e79-4418-a72a-c1650efb65d2"
|
||||
}
|
||||
```
|
||||
|
||||
## Update a note
|
||||
|
||||
```bash
|
||||
micro notes update --id=6d3fa5c0-6e79-4418-a72a-c1650efb65d2 --title="HelloWorld" --text="MyFirstNote (v2)"
|
||||
{}
|
||||
```
|
||||
|
||||
## List notes
|
||||
|
||||
```bash
|
||||
micro notes list
|
||||
{
|
||||
"notes": [
|
||||
{
|
||||
"id": "6d3fa5c0-6e79-4418-a72a-c1650efb65d2",
|
||||
"created": "1602849877",
|
||||
"title": "HelloWorld",
|
||||
"text": "MyFirstNote (v2)"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Delete a note
|
||||
|
||||
```bash
|
||||
micro notes delete --id=6d3fa5c0-6e79-4418-a72a-c1650efb65d2
|
||||
{}
|
||||
```
|
||||
2
seen/.gitignore
vendored
2
seen/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
|
||||
seen
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM alpine
|
||||
ADD seen /seen
|
||||
ENTRYPOINT [ "/seen" ]
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
GOPATH:=$(shell go env GOPATH)
|
||||
.PHONY: init
|
||||
init:
|
||||
go get -u github.com/golang/protobuf/proto
|
||||
go get -u github.com/golang/protobuf/protoc-gen-go
|
||||
go get github.com/micro/micro/v3/cmd/protoc-gen-micro
|
||||
.PHONY: proto
|
||||
proto:
|
||||
protoc --proto_path=. --micro_out=. --go_out=:. proto/seen.proto
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build -o seen *.go
|
||||
|
||||
docs:
|
||||
protoc --openapi_out=. --proto_path=. --micro_out=. --go_out=:. proto/seen.proto
|
||||
@redoc-cli bundle api-seen.json
|
||||
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -v ./... -cover
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
docker build . -t seen:latest
|
||||
@@ -1,6 +0,0 @@
|
||||
Seen is a service to keep track of which resources a user has seen (read). For example, it can be used to keep track of what notifications have been seen by a user, or what messages they've read in a chat.
|
||||
|
||||
|
||||
# Seen Service
|
||||
|
||||
The seen service is a service to keep track of which resources a user has seen (read).
|
||||
@@ -1,221 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
"github.com/micro/micro/v3/service/store"
|
||||
"github.com/micro/services/pkg/tenant"
|
||||
pb "github.com/micro/services/seen/proto"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissingUserID = errors.BadRequest("MISSING_USER_ID", "Missing UserID")
|
||||
ErrMissingResourceID = errors.BadRequest("MISSING_RESOURCE_ID", "Missing ResourceID")
|
||||
ErrMissingResourceIDs = errors.BadRequest("MISSING_RESOURCE_IDS", "Missing ResourceIDs")
|
||||
ErrMissingResourceType = errors.BadRequest("MISSING_RESOURCE_TYPE", "Missing ResourceType")
|
||||
ErrStore = errors.InternalServerError("STORE_ERROR", "Error connecting to the store")
|
||||
)
|
||||
|
||||
type Seen struct{}
|
||||
|
||||
type Record struct {
|
||||
ID string
|
||||
UserID string
|
||||
ResourceID string
|
||||
ResourceType string
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
func (r *Record) Key(ctx context.Context) string {
|
||||
key := fmt.Sprintf("%s:%s:%s", r.UserID, r.ResourceType, r.ResourceID)
|
||||
|
||||
t, ok := tenant.FromContext(ctx)
|
||||
if !ok {
|
||||
return key
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", t, key)
|
||||
}
|
||||
|
||||
func (r *Record) Marshal() []byte {
|
||||
b, _ := json.Marshal(r)
|
||||
return b
|
||||
}
|
||||
|
||||
func (r *Record) Unmarshal(b []byte) error {
|
||||
return json.Unmarshal(b, &r)
|
||||
}
|
||||
|
||||
// Set a resource as seen by a user. If no timestamp is provided, the current time is used.
|
||||
func (s *Seen) Set(ctx context.Context, req *pb.SetRequest, rsp *pb.SetResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.UserId) == 0 {
|
||||
return ErrMissingUserID
|
||||
}
|
||||
if len(req.ResourceId) == 0 {
|
||||
return ErrMissingResourceID
|
||||
}
|
||||
if len(req.ResourceType) == 0 {
|
||||
return ErrMissingResourceType
|
||||
}
|
||||
|
||||
// default the timestamp
|
||||
if req.Timestamp == nil {
|
||||
req.Timestamp = timestamppb.New(time.Now())
|
||||
}
|
||||
|
||||
// find the resource
|
||||
instance := &Record{
|
||||
UserID: req.UserId,
|
||||
ResourceID: req.ResourceId,
|
||||
ResourceType: req.ResourceType,
|
||||
}
|
||||
|
||||
_, err := store.Read(instance.Key(ctx), store.ReadLimit(1))
|
||||
if err == store.ErrNotFound {
|
||||
instance.ID = uuid.New().String()
|
||||
} else if err != nil {
|
||||
logger.Errorf("Error with store: %v", err)
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
// update the resource
|
||||
instance.Timestamp = req.Timestamp.AsTime()
|
||||
|
||||
if err := store.Write(&store.Record{
|
||||
Key: instance.Key(ctx),
|
||||
Value: instance.Marshal(),
|
||||
}); err != nil {
|
||||
logger.Errorf("Error with store: %v", err)
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unset a resource as seen, used in cases where a user viewed a resource but wants to override
|
||||
// this so they remember to action it in the future, e.g. "Mark this as unread".
|
||||
func (s *Seen) Unset(ctx context.Context, req *pb.UnsetRequest, rsp *pb.UnsetResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.UserId) == 0 {
|
||||
return ErrMissingUserID
|
||||
}
|
||||
if len(req.ResourceId) == 0 {
|
||||
return ErrMissingResourceID
|
||||
}
|
||||
if len(req.ResourceType) == 0 {
|
||||
return ErrMissingResourceType
|
||||
}
|
||||
|
||||
instance := &Record{
|
||||
UserID: req.UserId,
|
||||
ResourceID: req.ResourceId,
|
||||
ResourceType: req.ResourceType,
|
||||
}
|
||||
|
||||
// delete the object from the store
|
||||
if err := store.Delete(instance.Key(ctx)); err != nil {
|
||||
logger.Errorf("Error with store: %v", err)
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read returns the timestamps at which various resources were seen by a user. If no timestamp
|
||||
// is returned for a given resource_id, it indicates that resource has not yet been seen by the
|
||||
// user.
|
||||
func (s *Seen) Read(ctx context.Context, req *pb.ReadRequest, rsp *pb.ReadResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.UserId) == 0 {
|
||||
return ErrMissingUserID
|
||||
}
|
||||
if len(req.ResourceIds) == 0 {
|
||||
return ErrMissingResourceIDs
|
||||
}
|
||||
if len(req.ResourceType) == 0 {
|
||||
return ErrMissingResourceType
|
||||
}
|
||||
|
||||
rec := &Record{
|
||||
UserID: req.UserId,
|
||||
ResourceType: req.ResourceType,
|
||||
}
|
||||
|
||||
var recs []*store.Record
|
||||
var err error
|
||||
|
||||
// get the records for the resource type
|
||||
if len(req.ResourceIds) == 1 {
|
||||
// read the key itself
|
||||
rec.ResourceID = req.ResourceIds[0]
|
||||
// gen key
|
||||
key := rec.Key(ctx)
|
||||
// get the record
|
||||
recs, err = store.Read(key, store.ReadLimit(1))
|
||||
} else {
|
||||
// create a key prefix
|
||||
key := rec.Key(ctx)
|
||||
// otherwise read the prefix
|
||||
recs, err = store.Read(key, store.ReadPrefix())
|
||||
}
|
||||
|
||||
if err == store.ErrNotFound {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
logger.Errorf("Error with store: %v", err)
|
||||
return ErrStore
|
||||
}
|
||||
|
||||
// make an id map
|
||||
ids := make(map[string]bool)
|
||||
|
||||
for _, id := range req.ResourceIds {
|
||||
ids[id] = true
|
||||
}
|
||||
|
||||
// make the map
|
||||
rsp.Timestamps = make(map[string]*timestamppb.Timestamp)
|
||||
|
||||
// range over records for the user/resource type
|
||||
// TODO: add some sort of filter query in store
|
||||
for _, rec := range recs {
|
||||
// get id
|
||||
parts := strings.Split(rec.Key, ":")
|
||||
id := parts[2]
|
||||
|
||||
fmt.Println("checking record", rec.Key, id)
|
||||
|
||||
if ok := ids[id]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// add the timestamp for the record
|
||||
r := new(Record)
|
||||
r.Unmarshal(rec.Value)
|
||||
rsp.Timestamps[id] = timestamppb.New(r.Timestamp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/store"
|
||||
"github.com/micro/micro/v3/service/store/memory"
|
||||
"github.com/micro/services/seen/handler"
|
||||
pb "github.com/micro/services/seen/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func testHandler(t *testing.T) *handler.Seen {
|
||||
store.DefaultStore = memory.NewStore()
|
||||
return &handler.Seen{}
|
||||
}
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
tt := []struct {
|
||||
Name string
|
||||
UserID string
|
||||
ResourceType string
|
||||
ResourceID string
|
||||
Timestamp *timestamppb.Timestamp
|
||||
Error error
|
||||
}{
|
||||
{
|
||||
Name: "MissingUserID",
|
||||
ResourceType: "message",
|
||||
ResourceID: uuid.New().String(),
|
||||
Error: handler.ErrMissingUserID,
|
||||
},
|
||||
{
|
||||
Name: "MissingResourceID",
|
||||
UserID: uuid.New().String(),
|
||||
ResourceType: "message",
|
||||
Error: handler.ErrMissingResourceID,
|
||||
},
|
||||
{
|
||||
Name: "MissingResourceType",
|
||||
UserID: uuid.New().String(),
|
||||
ResourceID: uuid.New().String(),
|
||||
Error: handler.ErrMissingResourceType,
|
||||
},
|
||||
{
|
||||
Name: "WithTimetamp",
|
||||
UserID: uuid.New().String(),
|
||||
ResourceID: uuid.New().String(),
|
||||
ResourceType: "message",
|
||||
Timestamp: timestamppb.New(time.Now().Add(time.Minute * -5)),
|
||||
},
|
||||
{
|
||||
Name: "WithoutTimetamp",
|
||||
UserID: uuid.New().String(),
|
||||
ResourceID: uuid.New().String(),
|
||||
ResourceType: "message",
|
||||
},
|
||||
{
|
||||
Name: "WithUpdatedTimetamp",
|
||||
UserID: uuid.New().String(),
|
||||
ResourceID: uuid.New().String(),
|
||||
ResourceType: "message",
|
||||
Timestamp: timestamppb.New(time.Now().Add(time.Minute * -3)),
|
||||
},
|
||||
}
|
||||
|
||||
h := testHandler(t)
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
err := h.Set(microAccountCtx(), &pb.SetRequest{
|
||||
UserId: tc.UserID,
|
||||
ResourceId: tc.ResourceID,
|
||||
ResourceType: tc.ResourceType,
|
||||
Timestamp: tc.Timestamp,
|
||||
}, &pb.SetResponse{})
|
||||
|
||||
assert.Equal(t, tc.Error, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestUnset(t *testing.T) {
|
||||
// seed some test data
|
||||
h := testHandler(t)
|
||||
seed := &pb.SetRequest{
|
||||
UserId: uuid.New().String(),
|
||||
ResourceId: uuid.New().String(),
|
||||
ResourceType: "message",
|
||||
}
|
||||
err := h.Set(microAccountCtx(), seed, &pb.SetResponse{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
tt := []struct {
|
||||
Name string
|
||||
UserID string
|
||||
ResourceType string
|
||||
ResourceID string
|
||||
Error error
|
||||
}{
|
||||
{
|
||||
Name: "MissingUserID",
|
||||
ResourceType: "message",
|
||||
ResourceID: uuid.New().String(),
|
||||
Error: handler.ErrMissingUserID,
|
||||
},
|
||||
{
|
||||
Name: "MissingResourceID",
|
||||
UserID: uuid.New().String(),
|
||||
ResourceType: "message",
|
||||
Error: handler.ErrMissingResourceID,
|
||||
},
|
||||
{
|
||||
Name: "MissingResourceType",
|
||||
UserID: uuid.New().String(),
|
||||
ResourceID: uuid.New().String(),
|
||||
Error: handler.ErrMissingResourceType,
|
||||
},
|
||||
{
|
||||
Name: "Exists",
|
||||
UserID: seed.UserId,
|
||||
ResourceID: seed.ResourceId,
|
||||
ResourceType: seed.ResourceType,
|
||||
},
|
||||
{
|
||||
Name: "Repeat",
|
||||
UserID: seed.UserId,
|
||||
ResourceID: seed.ResourceId,
|
||||
ResourceType: seed.ResourceType,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
err := h.Unset(microAccountCtx(), &pb.UnsetRequest{
|
||||
UserId: tc.UserID,
|
||||
ResourceId: tc.ResourceID,
|
||||
ResourceType: tc.ResourceType,
|
||||
}, &pb.UnsetResponse{})
|
||||
|
||||
assert.Equal(t, tc.Error, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
tn := time.Now()
|
||||
h := testHandler(t)
|
||||
|
||||
// seed some test data
|
||||
td := []struct {
|
||||
UserID string
|
||||
ResourceID string
|
||||
ResourceType string
|
||||
Timestamp *timestamppb.Timestamp
|
||||
}{
|
||||
{
|
||||
UserID: "user-1",
|
||||
ResourceID: "message-1",
|
||||
ResourceType: "message",
|
||||
Timestamp: timestamppb.New(tn.Add(time.Minute * -10)),
|
||||
},
|
||||
{
|
||||
UserID: "user-1",
|
||||
ResourceID: "message-1",
|
||||
ResourceType: "message",
|
||||
Timestamp: timestamppb.New(tn),
|
||||
},
|
||||
{
|
||||
UserID: "user-1",
|
||||
ResourceID: "message-2",
|
||||
ResourceType: "message",
|
||||
Timestamp: timestamppb.New(tn.Add(time.Minute * -10)),
|
||||
},
|
||||
{
|
||||
UserID: "user-1",
|
||||
ResourceID: "notification-1",
|
||||
ResourceType: "notification",
|
||||
Timestamp: timestamppb.New(tn.Add(time.Minute * -10)),
|
||||
},
|
||||
{
|
||||
UserID: "user-2",
|
||||
ResourceID: "message-3",
|
||||
ResourceType: "message",
|
||||
Timestamp: timestamppb.New(tn.Add(time.Minute * -10)),
|
||||
},
|
||||
}
|
||||
for _, d := range td {
|
||||
assert.NoError(t, h.Set(microAccountCtx(), &pb.SetRequest{
|
||||
UserId: d.UserID,
|
||||
ResourceId: d.ResourceID,
|
||||
ResourceType: d.ResourceType,
|
||||
Timestamp: d.Timestamp,
|
||||
}, &pb.SetResponse{}))
|
||||
}
|
||||
|
||||
// check only the requested values are returned
|
||||
var rsp pb.ReadResponse
|
||||
err := h.Read(microAccountCtx(), &pb.ReadRequest{
|
||||
UserId: "user-1",
|
||||
ResourceType: "message",
|
||||
ResourceIds: []string{"message-1", "message-2", "message-3"},
|
||||
}, &rsp)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, rsp.Timestamps, 2)
|
||||
|
||||
if v := rsp.Timestamps["message-1"]; v != nil {
|
||||
assert.Equal(t, microSecondTime(v.AsTime()), microSecondTime(tn))
|
||||
} else {
|
||||
t.Errorf("Expected a timestamp for message-1")
|
||||
}
|
||||
|
||||
if v := rsp.Timestamps["message-2"]; v != nil {
|
||||
assert.Equal(t, microSecondTime(v.AsTime()), microSecondTime(tn.Add(time.Minute*-10).UTC()))
|
||||
} else {
|
||||
t.Errorf("Expected a timestamp for message-2")
|
||||
}
|
||||
|
||||
// unsetting a resource should remove it from the list
|
||||
err = h.Unset(microAccountCtx(), &pb.UnsetRequest{
|
||||
UserId: "user-1",
|
||||
ResourceId: "message-2",
|
||||
ResourceType: "message",
|
||||
}, &pb.UnsetResponse{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
rsp = pb.ReadResponse{}
|
||||
err = h.Read(microAccountCtx(), &pb.ReadRequest{
|
||||
UserId: "user-1",
|
||||
ResourceType: "message",
|
||||
ResourceIds: []string{"message-1", "message-2", "message-3"},
|
||||
}, &rsp)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, rsp.Timestamps, 1)
|
||||
|
||||
}
|
||||
|
||||
// postgres has a resolution of 100microseconds so just test that it's accurate to the second
|
||||
func microSecondTime(tt time.Time) time.Time {
|
||||
return time.Unix(tt.Unix(), int64(tt.Nanosecond()-tt.Nanosecond()%1000)).UTC()
|
||||
}
|
||||
|
||||
func microAccountCtx() context.Context {
|
||||
return auth.ContextWithAccount(context.TODO(), &auth.Account{
|
||||
Issuer: "micro",
|
||||
})
|
||||
}
|
||||
24
seen/main.go
24
seen/main.go
@@ -1,24 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/micro/micro/v3/service"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
"github.com/micro/services/seen/handler"
|
||||
pb "github.com/micro/services/seen/proto"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create service
|
||||
srv := service.New(
|
||||
service.Name("seen"),
|
||||
service.Version("latest"),
|
||||
)
|
||||
|
||||
// Register handler
|
||||
pb.RegisterSeenHandler(srv.Server(), new(handler.Seen))
|
||||
|
||||
// Run service
|
||||
if err := srv.Run(); err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
service seen
|
||||
@@ -1,614 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.25.0
|
||||
// protoc v3.15.5
|
||||
// source: proto/seen.proto
|
||||
|
||||
package seen
|
||||
|
||||
import (
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
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
|
||||
|
||||
type Resource struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Resource) Reset() {
|
||||
*x = Resource{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_seen_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Resource) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Resource) ProtoMessage() {}
|
||||
|
||||
func (x *Resource) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_seen_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 Resource.ProtoReflect.Descriptor instead.
|
||||
func (*Resource) Descriptor() ([]byte, []int) {
|
||||
return file_proto_seen_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Resource) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Resource) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type SetRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
|
||||
ResourceType string `protobuf:"bytes,2,opt,name=resource_type,json=resourceType,proto3" json:"resource_type,omitempty"`
|
||||
ResourceId string `protobuf:"bytes,3,opt,name=resource_id,json=resourceId,proto3" json:"resource_id,omitempty"`
|
||||
Timestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||
}
|
||||
|
||||
func (x *SetRequest) Reset() {
|
||||
*x = SetRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_seen_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *SetRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SetRequest) ProtoMessage() {}
|
||||
|
||||
func (x *SetRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_seen_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 SetRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SetRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_seen_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *SetRequest) GetUserId() string {
|
||||
if x != nil {
|
||||
return x.UserId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SetRequest) GetResourceType() string {
|
||||
if x != nil {
|
||||
return x.ResourceType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SetRequest) GetResourceId() string {
|
||||
if x != nil {
|
||||
return x.ResourceId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SetRequest) GetTimestamp() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.Timestamp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SetResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *SetResponse) Reset() {
|
||||
*x = SetResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_seen_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *SetResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SetResponse) ProtoMessage() {}
|
||||
|
||||
func (x *SetResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_seen_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 SetResponse.ProtoReflect.Descriptor instead.
|
||||
func (*SetResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_seen_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
type UnsetRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
|
||||
ResourceType string `protobuf:"bytes,2,opt,name=resource_type,json=resourceType,proto3" json:"resource_type,omitempty"`
|
||||
ResourceId string `protobuf:"bytes,3,opt,name=resource_id,json=resourceId,proto3" json:"resource_id,omitempty"`
|
||||
}
|
||||
|
||||
func (x *UnsetRequest) Reset() {
|
||||
*x = UnsetRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_seen_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *UnsetRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*UnsetRequest) ProtoMessage() {}
|
||||
|
||||
func (x *UnsetRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_seen_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 UnsetRequest.ProtoReflect.Descriptor instead.
|
||||
func (*UnsetRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_seen_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *UnsetRequest) GetUserId() string {
|
||||
if x != nil {
|
||||
return x.UserId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UnsetRequest) GetResourceType() string {
|
||||
if x != nil {
|
||||
return x.ResourceType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UnsetRequest) GetResourceId() string {
|
||||
if x != nil {
|
||||
return x.ResourceId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type UnsetResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *UnsetResponse) Reset() {
|
||||
*x = UnsetResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_seen_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *UnsetResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*UnsetResponse) ProtoMessage() {}
|
||||
|
||||
func (x *UnsetResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_seen_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 UnsetResponse.ProtoReflect.Descriptor instead.
|
||||
func (*UnsetResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_seen_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
type ReadRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
|
||||
ResourceType string `protobuf:"bytes,2,opt,name=resource_type,json=resourceType,proto3" json:"resource_type,omitempty"`
|
||||
ResourceIds []string `protobuf:"bytes,3,rep,name=resource_ids,json=resourceIds,proto3" json:"resource_ids,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ReadRequest) Reset() {
|
||||
*x = ReadRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_seen_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ReadRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ReadRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ReadRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_seen_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 ReadRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ReadRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_seen_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *ReadRequest) GetUserId() string {
|
||||
if x != nil {
|
||||
return x.UserId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ReadRequest) GetResourceType() string {
|
||||
if x != nil {
|
||||
return x.ResourceType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ReadRequest) GetResourceIds() []string {
|
||||
if x != nil {
|
||||
return x.ResourceIds
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ReadResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Timestamps map[string]*timestamppb.Timestamp `protobuf:"bytes,1,rep,name=timestamps,proto3" json:"timestamps,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
}
|
||||
|
||||
func (x *ReadResponse) Reset() {
|
||||
*x = ReadResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_seen_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ReadResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ReadResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ReadResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_seen_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 ReadResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ReadResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_seen_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *ReadResponse) GetTimestamps() map[string]*timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.Timestamps
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_proto_seen_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_proto_seen_proto_rawDesc = []byte{
|
||||
0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x65, 0x65, 0x6e, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x12, 0x04, 0x73, 0x65, 0x65, 0x6e, 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, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73,
|
||||
0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0xa5, 0x01, 0x0a, 0x0a, 0x53, 0x65,
|
||||
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72,
|
||||
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49,
|
||||
0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79,
|
||||
0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
|
||||
0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
|
||||
0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x73,
|
||||
0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73,
|
||||
0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 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, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
|
||||
0x70, 0x22, 0x0d, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x6d, 0x0a, 0x0c, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73,
|
||||
0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f,
|
||||
0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x22,
|
||||
0x0f, 0x0a, 0x0d, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x6e, 0x0a, 0x0b, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||
0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f,
|
||||
0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a,
|
||||
0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20,
|
||||
0x03, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x73,
|
||||
0x22, 0xad, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x42, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73, 0x18,
|
||||
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x65, 0x65, 0x6e, 0x2e, 0x52, 0x65, 0x61,
|
||||
0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x73,
|
||||
0x74, 0x61, 0x6d, 0x70, 0x73, 0x1a, 0x59, 0x0a, 0x0f, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
||||
0x6d, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x18, 0x02, 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, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
|
||||
0x32, 0x93, 0x01, 0x0a, 0x04, 0x53, 0x65, 0x65, 0x6e, 0x12, 0x2a, 0x0a, 0x03, 0x53, 0x65, 0x74,
|
||||
0x12, 0x10, 0x2e, 0x73, 0x65, 0x65, 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x11, 0x2e, 0x73, 0x65, 0x65, 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x05, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x12, 0x12,
|
||||
0x2e, 0x73, 0x65, 0x65, 0x6e, 0x2e, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x13, 0x2e, 0x73, 0x65, 0x65, 0x6e, 0x2e, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12,
|
||||
0x11, 0x2e, 0x73, 0x65, 0x65, 0x6e, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x12, 0x2e, 0x73, 0x65, 0x65, 0x6e, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0c, 0x5a, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b,
|
||||
0x73, 0x65, 0x65, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_proto_seen_proto_rawDescOnce sync.Once
|
||||
file_proto_seen_proto_rawDescData = file_proto_seen_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_proto_seen_proto_rawDescGZIP() []byte {
|
||||
file_proto_seen_proto_rawDescOnce.Do(func() {
|
||||
file_proto_seen_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_seen_proto_rawDescData)
|
||||
})
|
||||
return file_proto_seen_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proto_seen_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
var file_proto_seen_proto_goTypes = []interface{}{
|
||||
(*Resource)(nil), // 0: seen.Resource
|
||||
(*SetRequest)(nil), // 1: seen.SetRequest
|
||||
(*SetResponse)(nil), // 2: seen.SetResponse
|
||||
(*UnsetRequest)(nil), // 3: seen.UnsetRequest
|
||||
(*UnsetResponse)(nil), // 4: seen.UnsetResponse
|
||||
(*ReadRequest)(nil), // 5: seen.ReadRequest
|
||||
(*ReadResponse)(nil), // 6: seen.ReadResponse
|
||||
nil, // 7: seen.ReadResponse.TimestampsEntry
|
||||
(*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp
|
||||
}
|
||||
var file_proto_seen_proto_depIdxs = []int32{
|
||||
8, // 0: seen.SetRequest.timestamp:type_name -> google.protobuf.Timestamp
|
||||
7, // 1: seen.ReadResponse.timestamps:type_name -> seen.ReadResponse.TimestampsEntry
|
||||
8, // 2: seen.ReadResponse.TimestampsEntry.value:type_name -> google.protobuf.Timestamp
|
||||
1, // 3: seen.Seen.Set:input_type -> seen.SetRequest
|
||||
3, // 4: seen.Seen.Unset:input_type -> seen.UnsetRequest
|
||||
5, // 5: seen.Seen.Read:input_type -> seen.ReadRequest
|
||||
2, // 6: seen.Seen.Set:output_type -> seen.SetResponse
|
||||
4, // 7: seen.Seen.Unset:output_type -> seen.UnsetResponse
|
||||
6, // 8: seen.Seen.Read:output_type -> seen.ReadResponse
|
||||
6, // [6:9] is the sub-list for method output_type
|
||||
3, // [3:6] 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_seen_proto_init() }
|
||||
func file_proto_seen_proto_init() {
|
||||
if File_proto_seen_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_proto_seen_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Resource); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_seen_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*SetRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_seen_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*SetResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_seen_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*UnsetRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_seen_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*UnsetResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_seen_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ReadRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_seen_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ReadResponse); 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_seen_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 8,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_proto_seen_proto_goTypes,
|
||||
DependencyIndexes: file_proto_seen_proto_depIdxs,
|
||||
MessageInfos: file_proto_seen_proto_msgTypes,
|
||||
}.Build()
|
||||
File_proto_seen_proto = out.File
|
||||
file_proto_seen_proto_rawDesc = nil
|
||||
file_proto_seen_proto_goTypes = nil
|
||||
file_proto_seen_proto_depIdxs = nil
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
// Code generated by protoc-gen-micro. DO NOT EDIT.
|
||||
// source: proto/seen.proto
|
||||
|
||||
package seen
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
_ "google.golang.org/protobuf/types/known/timestamppb"
|
||||
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 Seen service
|
||||
|
||||
func NewSeenEndpoints() []*api.Endpoint {
|
||||
return []*api.Endpoint{}
|
||||
}
|
||||
|
||||
// Client API for Seen service
|
||||
|
||||
type SeenService interface {
|
||||
// Set a resource as seen by a user. If no timestamp is provided, the current time is used.
|
||||
Set(ctx context.Context, in *SetRequest, opts ...client.CallOption) (*SetResponse, error)
|
||||
// Unset a resource as seen, used in cases where a user viewed a resource but wants to override
|
||||
// this so they remember to action it in the future, e.g. "Mark this as unread".
|
||||
Unset(ctx context.Context, in *UnsetRequest, opts ...client.CallOption) (*UnsetResponse, error)
|
||||
// Read returns the timestamps at which various resources were seen by a user. If no timestamp
|
||||
// is returned for a given resource_id, it indicates that resource has not yet been seen by the
|
||||
// user.
|
||||
Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error)
|
||||
}
|
||||
|
||||
type seenService struct {
|
||||
c client.Client
|
||||
name string
|
||||
}
|
||||
|
||||
func NewSeenService(name string, c client.Client) SeenService {
|
||||
return &seenService{
|
||||
c: c,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *seenService) Set(ctx context.Context, in *SetRequest, opts ...client.CallOption) (*SetResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Seen.Set", in)
|
||||
out := new(SetResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *seenService) Unset(ctx context.Context, in *UnsetRequest, opts ...client.CallOption) (*UnsetResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Seen.Unset", in)
|
||||
out := new(UnsetResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *seenService) Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Seen.Read", in)
|
||||
out := new(ReadResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Seen service
|
||||
|
||||
type SeenHandler interface {
|
||||
// Set a resource as seen by a user. If no timestamp is provided, the current time is used.
|
||||
Set(context.Context, *SetRequest, *SetResponse) error
|
||||
// Unset a resource as seen, used in cases where a user viewed a resource but wants to override
|
||||
// this so they remember to action it in the future, e.g. "Mark this as unread".
|
||||
Unset(context.Context, *UnsetRequest, *UnsetResponse) error
|
||||
// Read returns the timestamps at which various resources were seen by a user. If no timestamp
|
||||
// is returned for a given resource_id, it indicates that resource has not yet been seen by the
|
||||
// user.
|
||||
Read(context.Context, *ReadRequest, *ReadResponse) error
|
||||
}
|
||||
|
||||
func RegisterSeenHandler(s server.Server, hdlr SeenHandler, opts ...server.HandlerOption) error {
|
||||
type seen interface {
|
||||
Set(ctx context.Context, in *SetRequest, out *SetResponse) error
|
||||
Unset(ctx context.Context, in *UnsetRequest, out *UnsetResponse) error
|
||||
Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error
|
||||
}
|
||||
type Seen struct {
|
||||
seen
|
||||
}
|
||||
h := &seenHandler{hdlr}
|
||||
return s.Handle(s.NewHandler(&Seen{h}, opts...))
|
||||
}
|
||||
|
||||
type seenHandler struct {
|
||||
SeenHandler
|
||||
}
|
||||
|
||||
func (h *seenHandler) Set(ctx context.Context, in *SetRequest, out *SetResponse) error {
|
||||
return h.SeenHandler.Set(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *seenHandler) Unset(ctx context.Context, in *UnsetRequest, out *UnsetResponse) error {
|
||||
return h.SeenHandler.Unset(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *seenHandler) Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error {
|
||||
return h.SeenHandler.Read(ctx, in, out)
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package seen;
|
||||
option go_package = "./proto;seen";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
// Seen is a service to keep track of which resources a user has seen (read). For example, it can
|
||||
// be used to keep track of what notifications have been seen by a user, or what messages they've
|
||||
// read in a chat.
|
||||
service Seen {
|
||||
// Set a resource as seen by a user. If no timestamp is provided, the current time is used.
|
||||
rpc Set(SetRequest) returns (SetResponse);
|
||||
// Unset a resource as seen, used in cases where a user viewed a resource but wants to override
|
||||
// this so they remember to action it in the future, e.g. "Mark this as unread".
|
||||
rpc Unset(UnsetRequest) returns (UnsetResponse);
|
||||
// Read returns the timestamps at which various resources were seen by a user. If no timestamp
|
||||
// is returned for a given resource_id, it indicates that resource has not yet been seen by the
|
||||
// user.
|
||||
rpc Read(ReadRequest) returns (ReadResponse);
|
||||
}
|
||||
|
||||
message Resource {
|
||||
string type = 1;
|
||||
string id = 2;
|
||||
}
|
||||
|
||||
message SetRequest {
|
||||
string user_id = 1;
|
||||
string resource_type = 2;
|
||||
string resource_id = 3;
|
||||
google.protobuf.Timestamp timestamp = 4;
|
||||
}
|
||||
|
||||
message SetResponse {}
|
||||
|
||||
message UnsetRequest {
|
||||
string user_id = 1;
|
||||
string resource_type = 2;
|
||||
string resource_id = 3;
|
||||
}
|
||||
|
||||
message UnsetResponse {}
|
||||
|
||||
message ReadRequest {
|
||||
string user_id = 1;
|
||||
string resource_type = 2;
|
||||
repeated string resource_ids = 3;
|
||||
}
|
||||
|
||||
message ReadResponse {
|
||||
map<string, google.protobuf.Timestamp> timestamps = 1;
|
||||
}
|
||||
2
streams/.gitignore
vendored
2
streams/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
|
||||
streams
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
GOPATH:=$(shell go env GOPATH)
|
||||
.PHONY: init
|
||||
init:
|
||||
go get -u github.com/golang/protobuf/proto
|
||||
go get -u github.com/golang/protobuf/protoc-gen-go
|
||||
go get github.com/micro/micro/v3/cmd/protoc-gen-micro
|
||||
.PHONY: proto
|
||||
proto:
|
||||
protoc --proto_path=. --micro_out=. --go_out=:. proto/streams.proto
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build -o streams *.go
|
||||
|
||||
.PHONY: docs
|
||||
docs:
|
||||
protoc --openapi_out=. --proto_path=. --micro_out=. --go_out=:. proto/streams.proto
|
||||
@redoc-cli bundle api-streams.json
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -v ./... -cover
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
docker build . -t streams:latest
|
||||
@@ -1,7 +0,0 @@
|
||||
PubSub streaming and websockets
|
||||
|
||||
# Streams Service
|
||||
|
||||
The streams service provides an event stream, designed for sending messages from a server to mutliple
|
||||
clients connecting via Websockets. The Token RPC should be called to generate a token for each client,
|
||||
the clients should then subscribe using the Subscribe RPC.
|
||||
@@ -1,57 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
"github.com/micro/micro/v3/service/events"
|
||||
"github.com/micro/services/pkg/cache"
|
||||
"github.com/nats-io/nats-streaming-server/util"
|
||||
)
|
||||
|
||||
var (
|
||||
TokenTTL = time.Minute
|
||||
ErrMissingTopic = errors.BadRequest("MISSING_TOPIC", "Missing topic")
|
||||
ErrInvalidTopic = errors.BadRequest("MISSING_TOPIC", "Invalid topic")
|
||||
ErrMissingToken = errors.BadRequest("MISSING_TOKEN", "Missing token")
|
||||
ErrMissingMessage = errors.BadRequest("MISSING_MESSAGE", "Missing message")
|
||||
ErrInvalidToken = errors.Forbidden("INVALID_TOKEN", "Invalid token")
|
||||
ErrExpiredToken = errors.Forbidden("EXPIRED_TOKEN", "Token expired")
|
||||
ErrForbiddenTopic = errors.Forbidden("FORBIDDEN_TOPIC", "Token has not have permission to subscribe to this topic")
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Token string
|
||||
Topic string
|
||||
Account string
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
type Streams struct {
|
||||
Cache cache.Cache
|
||||
Events events.Stream
|
||||
Time func() time.Time
|
||||
}
|
||||
|
||||
func getAccount(acc *auth.Account) string {
|
||||
owner := acc.Metadata["apikey_owner"]
|
||||
if len(owner) == 0 {
|
||||
owner = acc.ID
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", acc.Issuer, owner)
|
||||
}
|
||||
|
||||
// fmtTopic returns a topic string with namespace prefix
|
||||
func fmtTopic(acc *auth.Account, topic string) string {
|
||||
return fmt.Sprintf("%s.%s", getAccount(acc), topic)
|
||||
}
|
||||
|
||||
// validateTopicInput validates that topic is alphanumeric
|
||||
func validateTopicInput(topic string) error {
|
||||
if !util.IsChannelNameValid(topic, false) {
|
||||
return ErrInvalidTopic
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/micro/micro/v3/service/events"
|
||||
"github.com/micro/micro/v3/service/store/memory"
|
||||
"github.com/micro/services/pkg/cache"
|
||||
"github.com/micro/services/streams/handler"
|
||||
)
|
||||
|
||||
func testHandler(t *testing.T) *handler.Streams {
|
||||
h := &handler.Streams{
|
||||
Cache: cache.New(memory.NewStore()),
|
||||
Events: new(eventsMock),
|
||||
Time: func() time.Time {
|
||||
return time.Unix(1612787045, 0)
|
||||
},
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
type eventsMock struct {
|
||||
PublishCount int
|
||||
PublishTopic string
|
||||
PublishMessage interface{}
|
||||
|
||||
ConsumeTopic string
|
||||
ConsumeChan <-chan events.Event
|
||||
}
|
||||
|
||||
func (e *eventsMock) Publish(topic string, msg interface{}, opts ...events.PublishOption) error {
|
||||
e.PublishCount++
|
||||
e.PublishTopic = topic
|
||||
e.PublishMessage = msg
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *eventsMock) Consume(topic string, opts ...events.ConsumeOption) (<-chan events.Event, error) {
|
||||
e.ConsumeTopic = topic
|
||||
return e.ConsumeChan, nil
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
pb "github.com/micro/services/streams/proto"
|
||||
)
|
||||
|
||||
func (s *Streams) Publish(ctx context.Context, req *pb.Message, rsp *pb.PublishResponse) error {
|
||||
// validate the request
|
||||
if len(req.Topic) == 0 {
|
||||
return ErrMissingTopic
|
||||
}
|
||||
if err := validateTopicInput(req.Topic); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(req.Message) == 0 {
|
||||
return ErrMissingMessage
|
||||
}
|
||||
|
||||
topic := req.Topic
|
||||
|
||||
// in the event we have an account we use multi-tenancy
|
||||
acc, ok := auth.AccountFromContext(ctx)
|
||||
if ok {
|
||||
topic = fmtTopic(acc, req.Topic)
|
||||
}
|
||||
|
||||
// publish the message
|
||||
logger.Infof("Publishing message to topic: %v", req.Topic)
|
||||
return s.Events.Publish(topic, req.Message)
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/services/streams/handler"
|
||||
pb "github.com/micro/services/streams/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPublish(t *testing.T) {
|
||||
msg := "{\"foo\":\"bar\"}"
|
||||
topic := strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||
|
||||
t.Run("MissingTopic", func(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "foo"})
|
||||
err := h.Publish(ctx, &pb.Message{Message: msg}, &pb.PublishResponse{})
|
||||
assert.Equal(t, handler.ErrMissingTopic, err)
|
||||
assert.Zero(t, h.Events.(*eventsMock).PublishCount)
|
||||
})
|
||||
|
||||
t.Run("MissingMessage", func(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "foo"})
|
||||
err := h.Publish(ctx, &pb.Message{Topic: topic}, &pb.PublishResponse{})
|
||||
assert.Equal(t, handler.ErrMissingMessage, err)
|
||||
assert.Zero(t, h.Events.(*eventsMock).PublishCount)
|
||||
})
|
||||
|
||||
t.Run("ValidMessage", func(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "foo", ID: "foo-id"})
|
||||
err := h.Publish(ctx, &pb.Message{
|
||||
Topic: topic, Message: msg,
|
||||
}, &pb.PublishResponse{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, h.Events.(*eventsMock).PublishCount)
|
||||
assert.Equal(t, msg, h.Events.(*eventsMock).PublishMessage)
|
||||
// topic is prefixed with acc issuer to implement multitenancy
|
||||
assert.Equal(t, "foo.foo-id."+topic, h.Events.(*eventsMock).PublishTopic)
|
||||
})
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"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"
|
||||
pb "github.com/micro/services/streams/proto"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func (s *Streams) Subscribe(ctx context.Context, req *pb.SubscribeRequest, stream pb.Streams_SubscribeStream) error {
|
||||
logger.Infof("Received subscribe request. Topic: '%v', Token: '%v'", req.Topic, req.Token)
|
||||
|
||||
// validate the request
|
||||
if len(req.Token) == 0 {
|
||||
return ErrMissingToken
|
||||
}
|
||||
if len(req.Topic) == 0 {
|
||||
return ErrMissingTopic
|
||||
}
|
||||
if err := validateTopicInput(req.Topic); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// find the token and check to see if it has expired
|
||||
var token Token
|
||||
if err := s.Cache.Get("token:"+req.Token, &token); err == store.ErrNotFound {
|
||||
return ErrInvalidToken
|
||||
} else if err != nil {
|
||||
logger.Errorf("Error reading token from store: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error reading token from database")
|
||||
}
|
||||
if token.ExpiresAt.Before(s.Time()) {
|
||||
return ErrExpiredToken
|
||||
}
|
||||
|
||||
// if the token was scoped to a channel, ensure the channel is the one being requested
|
||||
if len(token.Topic) > 0 && token.Topic != req.Topic {
|
||||
return ErrForbiddenTopic
|
||||
}
|
||||
|
||||
var topic string
|
||||
|
||||
// attempt to create a unique topic for the account
|
||||
acc, ok := auth.AccountFromContext(ctx)
|
||||
if ok {
|
||||
topic = fmtTopic(acc, req.Topic)
|
||||
} else if len(token.Account) > 0 {
|
||||
// use the account in the token if present
|
||||
topic = fmt.Sprintf("%s.%s", token.Account, token.Topic)
|
||||
} else {
|
||||
topic = req.Topic
|
||||
}
|
||||
|
||||
// start the subscription
|
||||
logger.Infof("Subscribing to %v via queue %v", req.Topic, token.Token)
|
||||
|
||||
evChan, err := s.Events.Consume(topic, events.WithGroup(token.Token))
|
||||
if err != nil {
|
||||
logger.Errorf("Error connecting to events stream: %v", err)
|
||||
return errors.InternalServerError("EVENTS_ERROR", "Error connecting to events stream")
|
||||
}
|
||||
|
||||
for {
|
||||
msg, ok := <-evChan
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Infof("Sending message to subscriber %v", token.Topic)
|
||||
pbMsg := &pb.Message{
|
||||
Topic: req.Topic, // use req.Topic not msg.Topic because topic is munged for multitenancy
|
||||
Message: string(msg.Payload),
|
||||
SentAt: timestamppb.New(msg.Timestamp),
|
||||
}
|
||||
|
||||
if err := stream.Send(pbMsg); err == io.EOF {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/events"
|
||||
"github.com/micro/services/streams/handler"
|
||||
pb "github.com/micro/services/streams/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSubscribe(t *testing.T) {
|
||||
t.Run("MissingToken", func(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
s := new(streamMock)
|
||||
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "foo"})
|
||||
err := h.Subscribe(ctx, &pb.SubscribeRequest{
|
||||
Topic: "helloworld",
|
||||
}, s)
|
||||
|
||||
assert.Equal(t, handler.ErrMissingToken, err)
|
||||
assert.Empty(t, s.Messages)
|
||||
})
|
||||
|
||||
t.Run("MissingTopic", func(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
s := new(streamMock)
|
||||
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "foo"})
|
||||
err := h.Subscribe(ctx, &pb.SubscribeRequest{
|
||||
Token: uuid.New().String(),
|
||||
}, s)
|
||||
|
||||
assert.Equal(t, handler.ErrMissingTopic, err)
|
||||
assert.Empty(t, s.Messages)
|
||||
})
|
||||
|
||||
t.Run("InvalidToken", func(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
s := new(streamMock)
|
||||
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "foo"})
|
||||
err := h.Subscribe(ctx, &pb.SubscribeRequest{
|
||||
Topic: "helloworld",
|
||||
Token: uuid.New().String(),
|
||||
}, s)
|
||||
|
||||
assert.Equal(t, handler.ErrInvalidToken, err)
|
||||
assert.Empty(t, s.Messages)
|
||||
})
|
||||
|
||||
t.Run("ExpiredToken", func(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
var tRsp pb.TokenResponse
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "foo"})
|
||||
err := h.Token(ctx, &pb.TokenRequest{
|
||||
Topic: "helloworld",
|
||||
}, &tRsp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ct := h.Time()
|
||||
h.Time = func() time.Time { return ct.Add(handler.TokenTTL * 2) }
|
||||
s := new(streamMock)
|
||||
err = h.Subscribe(ctx, &pb.SubscribeRequest{
|
||||
Topic: "helloworld",
|
||||
Token: tRsp.Token,
|
||||
}, s)
|
||||
|
||||
assert.Equal(t, handler.ErrExpiredToken, err)
|
||||
assert.Empty(t, s.Messages)
|
||||
})
|
||||
|
||||
t.Run("ForbiddenTopic", func(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
var tRsp pb.TokenResponse
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "foo"})
|
||||
err := h.Token(ctx, &pb.TokenRequest{
|
||||
Topic: "helloworldx",
|
||||
}, &tRsp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := new(streamMock)
|
||||
err = h.Subscribe(ctx, &pb.SubscribeRequest{
|
||||
Topic: "helloworld",
|
||||
Token: tRsp.Token,
|
||||
}, s)
|
||||
|
||||
assert.Equal(t, handler.ErrForbiddenTopic, err)
|
||||
assert.Empty(t, s.Messages)
|
||||
})
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
defer func() {
|
||||
if i := recover(); i != nil {
|
||||
t.Logf("%+v", i)
|
||||
}
|
||||
}()
|
||||
h := testHandler(t)
|
||||
c := make(chan events.Event)
|
||||
h.Events.(*eventsMock).ConsumeChan = c
|
||||
|
||||
var tRsp pb.TokenResponse
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{ID: "foo", Issuer: "my-ns"})
|
||||
err := h.Token(ctx, &pb.TokenRequest{
|
||||
Topic: "helloworld",
|
||||
}, &tRsp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := &streamMock{Messages: []*pb.Message{}}
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
var subsErr error
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
subsErr = h.Subscribe(ctx, &pb.SubscribeRequest{
|
||||
Topic: "helloworld",
|
||||
Token: tRsp.Token,
|
||||
}, s)
|
||||
}()
|
||||
|
||||
e1 := events.Event{
|
||||
ID: uuid.New().String(),
|
||||
Topic: "helloworld",
|
||||
Timestamp: h.Time().Add(time.Second * -2),
|
||||
Payload: []byte("abc"),
|
||||
}
|
||||
e2 := events.Event{
|
||||
ID: uuid.New().String(),
|
||||
Topic: "helloworld",
|
||||
Timestamp: h.Time().Add(time.Second * -1),
|
||||
Payload: []byte("123"),
|
||||
}
|
||||
|
||||
timeout := time.NewTimer(time.Millisecond * 100).C
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Fatal("Events not consumed from stream")
|
||||
return
|
||||
case c <- e1:
|
||||
t.Log("Event1 consumed")
|
||||
}
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Fatal("Events not consumed from stream")
|
||||
return
|
||||
case c <- e2:
|
||||
t.Log("Event2 consumed")
|
||||
}
|
||||
|
||||
close(c)
|
||||
wg.Wait()
|
||||
assert.NoError(t, subsErr)
|
||||
assert.Equal(t, "my-ns.foo.helloworld", h.Events.(*eventsMock).ConsumeTopic)
|
||||
|
||||
// sleep to wait for the subscribe loop to push the message to the stream
|
||||
//time.Sleep(1 * time.Second)
|
||||
if len(s.Messages) != 2 {
|
||||
t.Fatalf("Expected 2 messages, got %v", len(s.Messages))
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, e1.Topic, s.Messages[0].Topic)
|
||||
assert.Equal(t, string(e1.Payload), s.Messages[0].Message)
|
||||
assert.True(t, e1.Timestamp.Equal(s.Messages[0].SentAt.AsTime()))
|
||||
|
||||
assert.Equal(t, e2.Topic, s.Messages[1].Topic)
|
||||
assert.Equal(t, string(e2.Payload), s.Messages[1].Message)
|
||||
assert.True(t, e2.Timestamp.Equal(s.Messages[1].SentAt.AsTime()))
|
||||
})
|
||||
|
||||
/*
|
||||
t.Run("TokenForDifferentIssuer", func(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
var tRsp pb.TokenResponse
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "foo"})
|
||||
err := h.Token(ctx, &pb.TokenRequest{
|
||||
Topic: "tokfordiff",
|
||||
}, &tRsp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := new(streamMock)
|
||||
ctx = auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "bar"})
|
||||
err = h.Subscribe(ctx, &pb.SubscribeRequest{
|
||||
Topic: "tokfordiff",
|
||||
Token: tRsp.Token,
|
||||
}, s)
|
||||
assert.Equal(t, handler.ErrInvalidToken, err)
|
||||
assert.Empty(t, s.Messages)
|
||||
})
|
||||
*/
|
||||
t.Run("BadTopic", func(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
var tRsp pb.TokenResponse
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "foo"})
|
||||
err := h.Token(ctx, &pb.TokenRequest{}, &tRsp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := new(streamMock)
|
||||
ctx = auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "bar"})
|
||||
err = h.Subscribe(ctx, &pb.SubscribeRequest{
|
||||
Topic: "tok-for/diff",
|
||||
Token: tRsp.Token,
|
||||
}, s)
|
||||
assert.Equal(t, handler.ErrInvalidTopic, err)
|
||||
assert.Empty(t, s.Messages)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
type streamMock struct {
|
||||
Messages []*pb.Message
|
||||
pb.Streams_SubscribeStream
|
||||
}
|
||||
|
||||
func (x *streamMock) Send(m *pb.Message) error {
|
||||
x.Messages = append(x.Messages, m)
|
||||
return nil
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
pb "github.com/micro/services/streams/proto"
|
||||
)
|
||||
|
||||
func (s *Streams) Token(ctx context.Context, req *pb.TokenRequest, rsp *pb.TokenResponse) error {
|
||||
if len(req.Topic) > 0 {
|
||||
if err := validateTopicInput(req.Topic); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var account string
|
||||
if acc, ok := auth.AccountFromContext(ctx); ok {
|
||||
account = getAccount(acc)
|
||||
}
|
||||
|
||||
// construct the token and write it to the database
|
||||
t := Token{
|
||||
Token: uuid.New().String(),
|
||||
ExpiresAt: s.Time().Add(TokenTTL),
|
||||
Topic: req.Topic,
|
||||
Account: account,
|
||||
}
|
||||
|
||||
if err := s.Cache.Put("token:"+t.Token, t, t.ExpiresAt); err != nil {
|
||||
logger.Errorf("Error creating token in store: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error writing token to database")
|
||||
}
|
||||
|
||||
// return the token in the response
|
||||
rsp.Token = t.Token
|
||||
return nil
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/services/streams/handler"
|
||||
pb "github.com/micro/services/streams/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestToken(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
t.Run("WithoutTopic", func(t *testing.T) {
|
||||
var rsp pb.TokenResponse
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "foo"})
|
||||
err := h.Token(ctx, &pb.TokenRequest{}, &rsp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, rsp.Token)
|
||||
})
|
||||
|
||||
t.Run("WithTopic", func(t *testing.T) {
|
||||
var rsp pb.TokenResponse
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "foo"})
|
||||
err := h.Token(ctx, &pb.TokenRequest{Topic: "helloworld"}, &rsp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, rsp.Token)
|
||||
})
|
||||
|
||||
t.Run("WithBadTopic", func(t *testing.T) {
|
||||
var rsp pb.TokenResponse
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "foo"})
|
||||
err := h.Token(ctx, &pb.TokenRequest{Topic: "helloworld/1"}, &rsp)
|
||||
assert.Equal(t, handler.ErrInvalidTopic, err)
|
||||
})
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/micro/micro/v3/service"
|
||||
"github.com/micro/micro/v3/service/events"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
"github.com/micro/services/pkg/cache"
|
||||
"github.com/micro/services/streams/handler"
|
||||
pb "github.com/micro/services/streams/proto"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create service
|
||||
srv := service.New(
|
||||
service.Name("streams"),
|
||||
)
|
||||
|
||||
h := &handler.Streams{
|
||||
Cache: cache.DefaultCache,
|
||||
Events: events.DefaultStream,
|
||||
Time: time.Now,
|
||||
}
|
||||
|
||||
// Register handler
|
||||
pb.RegisterStreamsHandler(srv.Server(), h)
|
||||
|
||||
// Run service
|
||||
if err := srv.Run(); err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
service streams
|
||||
@@ -1,441 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.5
|
||||
// source: proto/streams.proto
|
||||
|
||||
package streams
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
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)
|
||||
)
|
||||
|
||||
type PublishResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *PublishResponse) Reset() {
|
||||
*x = PublishResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_streams_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *PublishResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PublishResponse) ProtoMessage() {}
|
||||
|
||||
func (x *PublishResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_streams_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 PublishResponse.ProtoReflect.Descriptor instead.
|
||||
func (*PublishResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_streams_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"`
|
||||
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
|
||||
SentAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=sent_at,json=sentAt,proto3" json:"sent_at,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Message) Reset() {
|
||||
*x = Message{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_streams_proto_msgTypes[1]
|
||||
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_streams_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 Message.ProtoReflect.Descriptor instead.
|
||||
func (*Message) Descriptor() ([]byte, []int) {
|
||||
return file_proto_streams_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Message) GetTopic() string {
|
||||
if x != nil {
|
||||
return x.Topic
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Message) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Message) GetSentAt() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.SentAt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SubscribeRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// topic the user wishes to subscribe to, required
|
||||
Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"`
|
||||
// tokens should be provided if the user is not proving an API key on the request (e.g. in cases
|
||||
// where the stream is being consumed directly from the frontend via websockets). tokens can be
|
||||
// generated using the Token RPC
|
||||
Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"`
|
||||
}
|
||||
|
||||
func (x *SubscribeRequest) Reset() {
|
||||
*x = SubscribeRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_streams_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *SubscribeRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SubscribeRequest) ProtoMessage() {}
|
||||
|
||||
func (x *SubscribeRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_streams_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 SubscribeRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SubscribeRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_streams_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *SubscribeRequest) GetTopic() string {
|
||||
if x != nil {
|
||||
return x.Topic
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SubscribeRequest) GetToken() string {
|
||||
if x != nil {
|
||||
return x.Token
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type TokenRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// the topic the token should be restricted to, if no topic is required the token can be used to
|
||||
// subscribe to any topic
|
||||
Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"`
|
||||
}
|
||||
|
||||
func (x *TokenRequest) Reset() {
|
||||
*x = TokenRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_streams_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *TokenRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*TokenRequest) ProtoMessage() {}
|
||||
|
||||
func (x *TokenRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_streams_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 TokenRequest.ProtoReflect.Descriptor instead.
|
||||
func (*TokenRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_streams_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *TokenRequest) GetTopic() string {
|
||||
if x != nil {
|
||||
return x.Topic
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type TokenResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
|
||||
}
|
||||
|
||||
func (x *TokenResponse) Reset() {
|
||||
*x = TokenResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_streams_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *TokenResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*TokenResponse) ProtoMessage() {}
|
||||
|
||||
func (x *TokenResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_streams_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 TokenResponse.ProtoReflect.Descriptor instead.
|
||||
func (*TokenResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_streams_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *TokenResponse) GetToken() string {
|
||||
if x != nil {
|
||||
return x.Token
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_proto_streams_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_proto_streams_proto_rawDesc = []byte{
|
||||
0x0a, 0x13, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 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,
|
||||
0x11, 0x0a, 0x0f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x6e, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f,
|
||||
0x70, 0x69, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x33, 0x0a,
|
||||
0x07, 0x73, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x74, 0x18, 0x03, 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, 0x22, 0x3e, 0x0a, 0x10, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x14, 0x0a, 0x05,
|
||||
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b,
|
||||
0x65, 0x6e, 0x22, 0x24, 0x0a, 0x0c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x22, 0x25, 0x0a, 0x0d, 0x54, 0x6f, 0x6b, 0x65,
|
||||
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b,
|
||||
0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x32,
|
||||
0xba, 0x01, 0x0a, 0x07, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x12, 0x37, 0x0a, 0x07, 0x50,
|
||||
0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x12, 0x10, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73,
|
||||
0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x18, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61,
|
||||
0x6d, 0x73, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x00, 0x12, 0x3c, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62,
|
||||
0x65, 0x12, 0x19, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x53, 0x75, 0x62, 0x73,
|
||||
0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x73,
|
||||
0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00,
|
||||
0x30, 0x01, 0x12, 0x38, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x15, 0x2e, 0x73, 0x74,
|
||||
0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x16, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x54, 0x6f, 0x6b,
|
||||
0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x11, 0x5a, 0x0f,
|
||||
0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x62,
|
||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_proto_streams_proto_rawDescOnce sync.Once
|
||||
file_proto_streams_proto_rawDescData = file_proto_streams_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_proto_streams_proto_rawDescGZIP() []byte {
|
||||
file_proto_streams_proto_rawDescOnce.Do(func() {
|
||||
file_proto_streams_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_streams_proto_rawDescData)
|
||||
})
|
||||
return file_proto_streams_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proto_streams_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||
var file_proto_streams_proto_goTypes = []interface{}{
|
||||
(*PublishResponse)(nil), // 0: streams.PublishResponse
|
||||
(*Message)(nil), // 1: streams.Message
|
||||
(*SubscribeRequest)(nil), // 2: streams.SubscribeRequest
|
||||
(*TokenRequest)(nil), // 3: streams.TokenRequest
|
||||
(*TokenResponse)(nil), // 4: streams.TokenResponse
|
||||
(*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp
|
||||
}
|
||||
var file_proto_streams_proto_depIdxs = []int32{
|
||||
5, // 0: streams.Message.sent_at:type_name -> google.protobuf.Timestamp
|
||||
1, // 1: streams.Streams.Publish:input_type -> streams.Message
|
||||
2, // 2: streams.Streams.Subscribe:input_type -> streams.SubscribeRequest
|
||||
3, // 3: streams.Streams.Token:input_type -> streams.TokenRequest
|
||||
0, // 4: streams.Streams.Publish:output_type -> streams.PublishResponse
|
||||
1, // 5: streams.Streams.Subscribe:output_type -> streams.Message
|
||||
4, // 6: streams.Streams.Token:output_type -> streams.TokenResponse
|
||||
4, // [4:7] is the sub-list for method output_type
|
||||
1, // [1:4] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_proto_streams_proto_init() }
|
||||
func file_proto_streams_proto_init() {
|
||||
if File_proto_streams_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_proto_streams_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*PublishResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_streams_proto_msgTypes[1].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
|
||||
}
|
||||
}
|
||||
file_proto_streams_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*SubscribeRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_streams_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*TokenRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_streams_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*TokenResponse); 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_streams_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 5,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_proto_streams_proto_goTypes,
|
||||
DependencyIndexes: file_proto_streams_proto_depIdxs,
|
||||
MessageInfos: file_proto_streams_proto_msgTypes,
|
||||
}.Build()
|
||||
File_proto_streams_proto = out.File
|
||||
file_proto_streams_proto_rawDesc = nil
|
||||
file_proto_streams_proto_goTypes = nil
|
||||
file_proto_streams_proto_depIdxs = nil
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
// Code generated by protoc-gen-micro. DO NOT EDIT.
|
||||
// source: proto/streams.proto
|
||||
|
||||
package streams
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
_ "google.golang.org/protobuf/types/known/timestamppb"
|
||||
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 Streams service
|
||||
|
||||
func NewStreamsEndpoints() []*api.Endpoint {
|
||||
return []*api.Endpoint{}
|
||||
}
|
||||
|
||||
// Client API for Streams service
|
||||
|
||||
type StreamsService interface {
|
||||
Publish(ctx context.Context, in *Message, opts ...client.CallOption) (*PublishResponse, error)
|
||||
Subscribe(ctx context.Context, in *SubscribeRequest, opts ...client.CallOption) (Streams_SubscribeService, error)
|
||||
Token(ctx context.Context, in *TokenRequest, opts ...client.CallOption) (*TokenResponse, error)
|
||||
}
|
||||
|
||||
type streamsService struct {
|
||||
c client.Client
|
||||
name string
|
||||
}
|
||||
|
||||
func NewStreamsService(name string, c client.Client) StreamsService {
|
||||
return &streamsService{
|
||||
c: c,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *streamsService) Publish(ctx context.Context, in *Message, opts ...client.CallOption) (*PublishResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Streams.Publish", in)
|
||||
out := new(PublishResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *streamsService) Subscribe(ctx context.Context, in *SubscribeRequest, opts ...client.CallOption) (Streams_SubscribeService, error) {
|
||||
req := c.c.NewRequest(c.name, "Streams.Subscribe", &SubscribeRequest{})
|
||||
stream, err := c.c.Stream(ctx, req, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := stream.Send(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &streamsServiceSubscribe{stream}, nil
|
||||
}
|
||||
|
||||
type Streams_SubscribeService interface {
|
||||
Context() context.Context
|
||||
SendMsg(interface{}) error
|
||||
RecvMsg(interface{}) error
|
||||
Close() error
|
||||
Recv() (*Message, error)
|
||||
}
|
||||
|
||||
type streamsServiceSubscribe struct {
|
||||
stream client.Stream
|
||||
}
|
||||
|
||||
func (x *streamsServiceSubscribe) Close() error {
|
||||
return x.stream.Close()
|
||||
}
|
||||
|
||||
func (x *streamsServiceSubscribe) Context() context.Context {
|
||||
return x.stream.Context()
|
||||
}
|
||||
|
||||
func (x *streamsServiceSubscribe) SendMsg(m interface{}) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *streamsServiceSubscribe) RecvMsg(m interface{}) error {
|
||||
return x.stream.Recv(m)
|
||||
}
|
||||
|
||||
func (x *streamsServiceSubscribe) Recv() (*Message, error) {
|
||||
m := new(Message)
|
||||
err := x.stream.Recv(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *streamsService) Token(ctx context.Context, in *TokenRequest, opts ...client.CallOption) (*TokenResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Streams.Token", in)
|
||||
out := new(TokenResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Streams service
|
||||
|
||||
type StreamsHandler interface {
|
||||
Publish(context.Context, *Message, *PublishResponse) error
|
||||
Subscribe(context.Context, *SubscribeRequest, Streams_SubscribeStream) error
|
||||
Token(context.Context, *TokenRequest, *TokenResponse) error
|
||||
}
|
||||
|
||||
func RegisterStreamsHandler(s server.Server, hdlr StreamsHandler, opts ...server.HandlerOption) error {
|
||||
type streams interface {
|
||||
Publish(ctx context.Context, in *Message, out *PublishResponse) error
|
||||
Subscribe(ctx context.Context, stream server.Stream) error
|
||||
Token(ctx context.Context, in *TokenRequest, out *TokenResponse) error
|
||||
}
|
||||
type Streams struct {
|
||||
streams
|
||||
}
|
||||
h := &streamsHandler{hdlr}
|
||||
return s.Handle(s.NewHandler(&Streams{h}, opts...))
|
||||
}
|
||||
|
||||
type streamsHandler struct {
|
||||
StreamsHandler
|
||||
}
|
||||
|
||||
func (h *streamsHandler) Publish(ctx context.Context, in *Message, out *PublishResponse) error {
|
||||
return h.StreamsHandler.Publish(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *streamsHandler) Subscribe(ctx context.Context, stream server.Stream) error {
|
||||
m := new(SubscribeRequest)
|
||||
if err := stream.Recv(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return h.StreamsHandler.Subscribe(ctx, m, &streamsSubscribeStream{stream})
|
||||
}
|
||||
|
||||
type Streams_SubscribeStream interface {
|
||||
Context() context.Context
|
||||
SendMsg(interface{}) error
|
||||
RecvMsg(interface{}) error
|
||||
Close() error
|
||||
Send(*Message) error
|
||||
}
|
||||
|
||||
type streamsSubscribeStream struct {
|
||||
stream server.Stream
|
||||
}
|
||||
|
||||
func (x *streamsSubscribeStream) Close() error {
|
||||
return x.stream.Close()
|
||||
}
|
||||
|
||||
func (x *streamsSubscribeStream) Context() context.Context {
|
||||
return x.stream.Context()
|
||||
}
|
||||
|
||||
func (x *streamsSubscribeStream) SendMsg(m interface{}) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *streamsSubscribeStream) RecvMsg(m interface{}) error {
|
||||
return x.stream.Recv(m)
|
||||
}
|
||||
|
||||
func (x *streamsSubscribeStream) Send(m *Message) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (h *streamsHandler) Token(ctx context.Context, in *TokenRequest, out *TokenResponse) error {
|
||||
return h.StreamsHandler.Token(ctx, in, out)
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package streams;
|
||||
option go_package = "./proto;streams";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
service Streams {
|
||||
rpc Publish(Message) returns (PublishResponse) {}
|
||||
rpc Subscribe(SubscribeRequest) returns (stream Message) {}
|
||||
rpc Token(TokenRequest) returns (TokenResponse) {}
|
||||
}
|
||||
|
||||
message PublishResponse {}
|
||||
|
||||
message Message {
|
||||
string topic = 1;
|
||||
string message = 2;
|
||||
google.protobuf.Timestamp sent_at = 3;
|
||||
}
|
||||
|
||||
message SubscribeRequest {
|
||||
// topic the user wishes to subscribe to, required
|
||||
string topic = 1;
|
||||
// tokens should be provided if the user is not proving an API key on the request (e.g. in cases
|
||||
// where the stream is being consumed directly from the frontend via websockets). tokens can be
|
||||
// generated using the Token RPC
|
||||
string token = 2;
|
||||
}
|
||||
|
||||
message TokenRequest {
|
||||
// the topic the token should be restricted to, if no topic is required the token can be used to
|
||||
// subscribe to any topic
|
||||
string topic = 1;
|
||||
}
|
||||
|
||||
message TokenResponse {
|
||||
string token = 1;
|
||||
}
|
||||
2
threads/.gitignore
vendored
2
threads/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
|
||||
streams
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM alpine
|
||||
ADD threads /threads
|
||||
ENTRYPOINT [ "/threads" ]
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
GOPATH:=$(shell go env GOPATH)
|
||||
.PHONY: init
|
||||
init:
|
||||
go get -u github.com/golang/protobuf/proto
|
||||
go get -u github.com/golang/protobuf/protoc-gen-go
|
||||
go get github.com/micro/micro/v3/cmd/protoc-gen-micro
|
||||
.PHONY: proto
|
||||
proto:
|
||||
protoc --proto_path=. --micro_out=. --go_out=:. proto/threads.proto
|
||||
|
||||
.PHONY: docs
|
||||
docs:
|
||||
protoc --openapi_out=. --proto_path=. --micro_out=. --go_out=:. proto/threads.proto
|
||||
@redoc-cli bundle api-threads.json
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build -o threads *.go
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -v ./... -cover
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
docker build . -t threads:latest
|
||||
@@ -1,6 +0,0 @@
|
||||
Threaded conversations
|
||||
|
||||
# Threads Service
|
||||
|
||||
Threads provides threaded conversations as a service grouped by topics.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
package main
|
||||
|
||||
//go:generate make proto
|
||||
@@ -1,74 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
"github.com/micro/services/pkg/model"
|
||||
pb "github.com/micro/services/threads/proto"
|
||||
)
|
||||
|
||||
// Create a message within a thread
|
||||
func (s *Threads) CreateMessage(ctx context.Context, req *pb.CreateMessageRequest, rsp *pb.CreateMessageResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.AuthorId) == 0 {
|
||||
return ErrMissingAuthorID
|
||||
}
|
||||
if len(req.ThreadId) == 0 {
|
||||
return ErrMissingThreadID
|
||||
}
|
||||
if len(req.Text) == 0 {
|
||||
return ErrMissingText
|
||||
}
|
||||
|
||||
// lookup the thread
|
||||
conv := Thread{ID: req.ThreadId}
|
||||
|
||||
if err := model.Read(ctx, &conv); err == model.ErrNotFound {
|
||||
return ErrNotFound
|
||||
} else if err != nil {
|
||||
logger.Errorf("Error reading thread: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
|
||||
}
|
||||
|
||||
// create the message
|
||||
msg := &Message{
|
||||
ID: req.Id,
|
||||
SentAt: s.Time(),
|
||||
Text: req.Text,
|
||||
AuthorID: req.AuthorId,
|
||||
ThreadID: req.ThreadId,
|
||||
}
|
||||
if len(msg.ID) == 0 {
|
||||
msg.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
if err := model.Create(ctx, msg); err == nil {
|
||||
rsp.Message = msg.Serialize()
|
||||
return nil
|
||||
} else if err != model.ErrAlreadyExists {
|
||||
logger.Errorf("Error creating message: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
|
||||
}
|
||||
|
||||
// a message already exists with this id
|
||||
existing := &Message{ID: msg.ID, ThreadID: req.ThreadId}
|
||||
|
||||
if err := model.Read(ctx, existing); err == model.ErrNotFound {
|
||||
return ErrNotFound
|
||||
} else if err != nil {
|
||||
logger.Errorf("Error creating message: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
|
||||
}
|
||||
|
||||
// return the message
|
||||
rsp.Message = existing.Serialize()
|
||||
return nil
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/micro/services/threads/handler"
|
||||
pb "github.com/micro/services/threads/proto"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreateMessage(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
// seed some data
|
||||
var cRsp pb.CreateThreadResponse
|
||||
err := h.CreateThread(microAccountCtx(), &pb.CreateThreadRequest{
|
||||
Topic: "HelloWorld", GroupId: uuid.New().String(),
|
||||
}, &cRsp)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating thread: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
iid := uuid.New().String()
|
||||
tt := []struct {
|
||||
Name string
|
||||
AuthorID string
|
||||
ThreadID string
|
||||
ID string
|
||||
Text string
|
||||
Error error
|
||||
}{
|
||||
{
|
||||
Name: "MissingThreadID",
|
||||
Text: "HelloWorld",
|
||||
AuthorID: uuid.New().String(),
|
||||
Error: handler.ErrMissingThreadID,
|
||||
},
|
||||
{
|
||||
Name: "MissingAuthorID",
|
||||
ThreadID: uuid.New().String(),
|
||||
Text: "HelloWorld",
|
||||
Error: handler.ErrMissingAuthorID,
|
||||
},
|
||||
{
|
||||
Name: "MissingText",
|
||||
ThreadID: uuid.New().String(),
|
||||
AuthorID: uuid.New().String(),
|
||||
Error: handler.ErrMissingText,
|
||||
},
|
||||
{
|
||||
Name: "ThreadNotFound",
|
||||
ThreadID: uuid.New().String(),
|
||||
AuthorID: uuid.New().String(),
|
||||
Text: "HelloWorld",
|
||||
Error: handler.ErrNotFound,
|
||||
},
|
||||
{
|
||||
Name: "NoID",
|
||||
ThreadID: cRsp.Thread.Id,
|
||||
AuthorID: uuid.New().String(),
|
||||
Text: "HelloWorld",
|
||||
},
|
||||
{
|
||||
Name: "WithID",
|
||||
ThreadID: cRsp.Thread.Id,
|
||||
Text: "HelloWorld",
|
||||
AuthorID: "johndoe",
|
||||
ID: iid,
|
||||
},
|
||||
{
|
||||
Name: "RepeatID",
|
||||
ThreadID: cRsp.Thread.Id,
|
||||
Text: "HelloWorld",
|
||||
AuthorID: "johndoe",
|
||||
ID: iid,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
var rsp pb.CreateMessageResponse
|
||||
err := h.CreateMessage(microAccountCtx(), &pb.CreateMessageRequest{
|
||||
AuthorId: tc.AuthorID,
|
||||
ThreadId: tc.ThreadID,
|
||||
Text: tc.Text,
|
||||
Id: tc.ID,
|
||||
}, &rsp)
|
||||
|
||||
assert.Equal(t, tc.Error, err)
|
||||
if tc.Error != nil {
|
||||
assert.Nil(t, rsp.Message)
|
||||
return
|
||||
}
|
||||
|
||||
assertMessagesMatch(t, &pb.Message{
|
||||
Id: tc.ID,
|
||||
AuthorId: tc.AuthorID,
|
||||
ThreadId: tc.ThreadID,
|
||||
SentAt: handler.FormatTime(h.Time()),
|
||||
Text: tc.Text,
|
||||
}, rsp.Message)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
"github.com/micro/services/pkg/model"
|
||||
pb "github.com/micro/services/threads/proto"
|
||||
)
|
||||
|
||||
// Create a thread
|
||||
func (s *Threads) CreateThread(ctx context.Context, req *pb.CreateThreadRequest, rsp *pb.CreateThreadResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.GroupId) == 0 {
|
||||
return ErrMissingGroupID
|
||||
}
|
||||
if len(req.Topic) == 0 {
|
||||
return ErrMissingTopic
|
||||
}
|
||||
|
||||
// write the thread to the database
|
||||
thread := &Thread{
|
||||
ID: uuid.New().String(),
|
||||
Topic: req.Topic,
|
||||
GroupID: req.GroupId,
|
||||
CreatedAt: s.Time(),
|
||||
}
|
||||
|
||||
// write the thread to the database
|
||||
if err := model.Create(ctx, thread); err != nil {
|
||||
logger.Errorf("Error creating thread: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// serialize the response
|
||||
rsp.Thread = thread.Serialize()
|
||||
return nil
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/micro/services/threads/handler"
|
||||
pb "github.com/micro/services/threads/proto"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreateThread(t *testing.T) {
|
||||
tt := []struct {
|
||||
Name string
|
||||
GroupID string
|
||||
Topic string
|
||||
Error error
|
||||
}{
|
||||
{
|
||||
Name: "MissingGroupID",
|
||||
Topic: "HelloWorld",
|
||||
Error: handler.ErrMissingGroupID,
|
||||
},
|
||||
{
|
||||
Name: "MissingTopic",
|
||||
GroupID: uuid.New().String(),
|
||||
Error: handler.ErrMissingTopic,
|
||||
},
|
||||
{
|
||||
Name: "Valid",
|
||||
GroupID: uuid.New().String(),
|
||||
Topic: "HelloWorld",
|
||||
},
|
||||
}
|
||||
|
||||
h := testHandler(t)
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
var rsp pb.CreateThreadResponse
|
||||
err := h.CreateThread(microAccountCtx(), &pb.CreateThreadRequest{
|
||||
Topic: tc.Topic, GroupId: tc.GroupID,
|
||||
}, &rsp)
|
||||
|
||||
assert.Equal(t, tc.Error, err)
|
||||
if tc.Error != nil {
|
||||
assert.Nil(t, rsp.Thread)
|
||||
return
|
||||
}
|
||||
|
||||
assertThreadsMatch(t, &pb.Thread{
|
||||
CreatedAt: handler.FormatTime(h.Time()),
|
||||
GroupId: tc.GroupID,
|
||||
Topic: tc.Topic,
|
||||
}, rsp.Thread)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
"github.com/micro/services/pkg/model"
|
||||
pb "github.com/micro/services/threads/proto"
|
||||
)
|
||||
|
||||
// Delete a thread and all the messages within
|
||||
func (s *Threads) DeleteThread(ctx context.Context, req *pb.DeleteThreadRequest, rsp *pb.DeleteThreadResponse) error {
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
// validate the request
|
||||
if len(req.Id) == 0 {
|
||||
return ErrMissingID
|
||||
}
|
||||
|
||||
thread := Thread{ID: req.Id}
|
||||
|
||||
// delete the thread
|
||||
if err := model.Delete(ctx, &thread); err != nil {
|
||||
logger.Errorf("Error deleting thread: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
|
||||
}
|
||||
|
||||
message := Message{ThreadID: req.Id}
|
||||
|
||||
// delete the messages
|
||||
if err := model.Delete(ctx, &message); err != nil {
|
||||
logger.Errorf("Error deleting messages: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to database")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/micro/services/threads/handler"
|
||||
pb "github.com/micro/services/threads/proto"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDeleteThread(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
|
||||
// seed some data
|
||||
var cRsp pb.CreateThreadResponse
|
||||
err := h.CreateThread(microAccountCtx(), &pb.CreateThreadRequest{
|
||||
Topic: "HelloWorld", GroupId: uuid.New().String(),
|
||||
}, &cRsp)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating thread: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Run("MissingID", func(t *testing.T) {
|
||||
err := h.DeleteThread(microAccountCtx(), &pb.DeleteThreadRequest{}, &pb.DeleteThreadResponse{})
|
||||
assert.Equal(t, handler.ErrMissingID, err)
|
||||
})
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
err := h.DeleteThread(microAccountCtx(), &pb.DeleteThreadRequest{
|
||||
Id: cRsp.Thread.Id,
|
||||
}, &pb.DeleteThreadResponse{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = h.ReadThread(microAccountCtx(), &pb.ReadThreadRequest{
|
||||
Id: cRsp.Thread.Id,
|
||||
}, &pb.ReadThreadResponse{})
|
||||
assert.Equal(t, handler.ErrNotFound, err)
|
||||
})
|
||||
|
||||
t.Run("Retry", func(t *testing.T) {
|
||||
err := h.DeleteThread(microAccountCtx(), &pb.DeleteThreadRequest{
|
||||
Id: cRsp.Thread.Id,
|
||||
}, &pb.DeleteThreadResponse{})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user