Groups Service (#40)

This commit is contained in:
ben-toogood
2021-01-20 14:02:41 +00:00
committed by GitHub
parent 8d748ba76b
commit 2f2658da9a
12 changed files with 1990 additions and 0 deletions

2
groups/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
groups

3
groups/Dockerfile Normal file
View File

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

22
groups/Makefile Normal file
View File

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

31
groups/README.md Normal file
View File

@@ -0,0 +1,31 @@
# Groups Service
The group serivce is a basic CRUD service for groups. You can use it to create groups, add members and lookup which groups a user is a member of.
Example usage:
```bash
$ micro groups create --name=Micro
{
"group": {
"id": "e35562c9-b6f6-459a-b52d-7e6159465fd6",
"name": "Micro"
}
}
$ micro groups addMember --group_id=e35562c9-b6f6-459a-b52d-7e6159465fd6 --member_id=Asim
{}
$ micro groups list --member_id=Asim
{
"groups": [
{
"id": "e35562c9-b6f6-459a-b52d-7e6159465fd6",
"name": "Micro",
"member_ids": [
"Asim"
]
}
]
}
$ micro groups list --member_id=Boris
{}
```

2
groups/generate.go Normal file
View File

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

209
groups/handler/handler.go Normal file
View File

@@ -0,0 +1,209 @@
package handler
import (
"context"
"strings"
"github.com/google/uuid"
"github.com/micro/micro/v3/service/errors"
pb "github.com/micro/services/groups/proto"
"gorm.io/gorm"
)
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
Memberships []Membership
}
type Membership struct {
MemberID string `gorm:"uniqueIndex:idx_membership"`
GroupID string `gorm:"uniqueIndex:idx_membership"`
Group Group
}
func (g *Group) Serialize() *pb.Group {
memberIDs := make([]string, len(g.Memberships))
for i, m := range g.Memberships {
memberIDs[i] = m.MemberID
}
return &pb.Group{Id: g.ID, Name: g.Name, MemberIds: memberIDs}
}
type Groups struct {
DB *gorm.DB
}
func (g *Groups) Create(ctx context.Context, req *pb.CreateRequest, rsp *pb.CreateResponse) error {
// validate the request
if len(req.Name) == 0 {
return ErrMissingName
}
// create the group object
group := &Group{ID: uuid.New().String(), Name: req.Name}
if err := g.DB.Create(group).Error; 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 {
// validate the request
if len(req.Ids) == 0 {
return ErrMissingIDs
}
// query the database
var groups []Group
if err := g.DB.Model(&Group{}).Preload("Memberships").Where("id IN (?)", req.Ids).Find(&groups).Error; err != nil {
return ErrStore
}
// serialize the response
rsp.Groups = make(map[string]*pb.Group, len(groups))
for _, g := range groups {
rsp.Groups[g.ID] = g.Serialize()
}
return nil
}
func (g *Groups) Update(ctx context.Context, req *pb.UpdateRequest, rsp *pb.UpdateResponse) error {
// validate the request
if len(req.Id) == 0 {
return ErrMissingID
}
if len(req.Name) == 0 {
return ErrMissingName
}
return g.DB.Transaction(func(tx *gorm.DB) error {
// find the group
var group Group
if err := tx.Where(&Group{ID: req.Id}).First(&group).Error; err == gorm.ErrRecordNotFound {
return ErrNotFound
} else if err != nil {
return ErrStore
}
// update the group
group.Name = req.Name
if err := tx.Save(&group).Error; 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 {
// validate the request
if len(req.Id) == 0 {
return ErrMissingID
}
// delete from the database
if err := g.DB.Delete(&Group{ID: req.Id}).Error; err == gorm.ErrRecordNotFound {
return nil
} else if err != nil {
return ErrStore
}
return nil
}
func (g *Groups) List(ctx context.Context, req *pb.ListRequest, rsp *pb.ListResponse) error {
if len(req.MemberId) > 0 {
// only list groups the user is a member of
var ms []Membership
q := g.DB.Where(&Membership{MemberID: req.MemberId}).Preload("Group.Memberships")
if err := q.Find(&ms).Error; err != nil {
return err
}
rsp.Groups = make([]*pb.Group, len(ms))
for i, m := range ms {
rsp.Groups[i] = m.Group.Serialize()
}
return nil
}
// load all groups
var groups []Group
if err := g.DB.Model(&Group{}).Preload("Memberships").Find(&groups).Error; err != nil {
return ErrStore
}
// serialize the response
rsp.Groups = make([]*pb.Group, len(groups))
for i, g := range groups {
rsp.Groups[i] = g.Serialize()
}
return nil
}
func (g *Groups) AddMember(ctx context.Context, req *pb.AddMemberRequest, rsp *pb.AddMemberResponse) error {
// validate the request
if len(req.GroupId) == 0 {
return ErrMissingGroupID
}
if len(req.MemberId) == 0 {
return ErrMissingMemberID
}
return g.DB.Transaction(func(tx *gorm.DB) error {
// check the group exists
var group Group
if err := tx.Where(&Group{ID: req.GroupId}).First(&group).Error; err == gorm.ErrRecordNotFound {
return ErrNotFound
} else if err != nil {
return err
}
// create the membership
m := &Membership{MemberID: req.MemberId, GroupID: req.GroupId}
err := tx.Create(m).Error
// check for membership already existing (unique index violation)
if err != nil && strings.Contains(err.Error(), "fk_groups_memberships") {
return nil
} else if err != nil {
return ErrStore
}
return nil
})
}
func (g *Groups) RemoveMember(ctx context.Context, req *pb.RemoveMemberRequest, rsp *pb.RemoveMemberResponse) error {
// validate the request
if len(req.GroupId) == 0 {
return ErrMissingGroupID
}
if len(req.MemberId) == 0 {
return ErrMissingMemberID
}
// delete the membership
m := &Membership{MemberID: req.MemberId, GroupID: req.GroupId}
if err := g.DB.Where(m).Delete(m).Error; err != nil {
return ErrStore
}
return nil
}

View File

@@ -0,0 +1,288 @@
package handler_test
import (
"context"
"sort"
"testing"
"github.com/google/uuid"
"github.com/micro/services/groups/handler"
pb "github.com/micro/services/groups/proto"
"github.com/stretchr/testify/assert"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func testHandler(t *testing.T) *handler.Groups {
// connect to the database
db, err := gorm.Open(postgres.Open("postgresql://postgres@localhost:5432/groups?sslmode=disable"), &gorm.Config{})
if err != nil {
t.Fatalf("Error connecting to database: %v", err)
}
// migrate the database
if err := db.AutoMigrate(&handler.Group{}, &handler.Membership{}); err != nil {
t.Fatalf("Error migrating database: %v", err)
}
// clean any data from a previous run
if err := db.Exec("TRUNCATE TABLE groups CASCADE").Error; err != nil {
t.Fatalf("Error cleaning database: %v", err)
}
return &handler.Groups{DB: db}
}
func TestCreate(t *testing.T) {
h := testHandler(t)
t.Run("MissingName", func(t *testing.T) {
err := h.Create(context.TODO(), &pb.CreateRequest{}, &pb.CreateResponse{})
assert.Equal(t, handler.ErrMissingName, err)
})
t.Run("Valid", func(t *testing.T) {
err := h.Create(context.TODO(), &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(context.TODO(), &pb.UpdateRequest{
Name: "Doe Family Group",
}, &pb.UpdateResponse{})
assert.Equal(t, handler.ErrMissingID, err)
})
t.Run("MissingName", func(t *testing.T) {
err := h.Update(context.TODO(), &pb.UpdateRequest{
Id: uuid.New().String(),
}, &pb.UpdateResponse{})
assert.Equal(t, handler.ErrMissingName, err)
})
t.Run("NotFound", func(t *testing.T) {
err := h.Update(context.TODO(), &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(context.TODO(), &pb.CreateRequest{
Name: "Doe Family Group",
}, &cRsp)
assert.NoError(t, err)
err = h.Update(context.TODO(), &pb.UpdateRequest{
Id: cRsp.Group.Id,
Name: "Bar Family Group",
}, &pb.UpdateResponse{})
assert.NoError(t, err)
var rRsp pb.ReadResponse
err = h.Read(context.TODO(), &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(context.TODO(), &pb.DeleteRequest{}, &pb.DeleteResponse{})
assert.Equal(t, handler.ErrMissingID, err)
})
t.Run("NotFound", func(t *testing.T) {
err := h.Delete(context.TODO(), &pb.DeleteRequest{
Id: uuid.New().String(),
}, &pb.DeleteResponse{})
assert.NoError(t, err)
})
// create a demo group
var cRsp pb.CreateResponse
err := h.Create(context.TODO(), &pb.CreateRequest{
Name: "Doe Family Group",
}, &cRsp)
assert.NoError(t, err)
t.Run("Valid", func(t *testing.T) {
err := h.Delete(context.TODO(), &pb.DeleteRequest{
Id: cRsp.Group.Id,
}, &pb.DeleteResponse{})
assert.NoError(t, err)
var rRsp pb.ReadResponse
err = h.Read(context.TODO(), &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(context.TODO(), &pb.CreateRequest{
Name: "Alpha Group",
}, &cRsp1)
assert.NoError(t, err)
var cRsp2 pb.CreateResponse
err = h.Create(context.TODO(), &pb.CreateRequest{
Name: "Bravo Group",
}, &cRsp2)
assert.NoError(t, err)
// add a member to the first group
uid := uuid.New().String()
err = h.AddMember(context.TODO(), &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(context.TODO(), &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(context.TODO(), &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(context.TODO(), &pb.AddMemberRequest{
MemberId: uuid.New().String(),
}, &pb.AddMemberResponse{})
assert.Equal(t, handler.ErrMissingGroupID, err)
})
t.Run("MissingMemberID", func(t *testing.T) {
err := h.AddMember(context.TODO(), &pb.AddMemberRequest{
GroupId: uuid.New().String(),
}, &pb.AddMemberResponse{})
assert.Equal(t, handler.ErrMissingMemberID, err)
})
t.Run("GroupNotFound", func(t *testing.T) {
err := h.AddMember(context.TODO(), &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(context.TODO(), &pb.CreateRequest{
Name: "Alpha Group",
}, &cRsp)
assert.NoError(t, err)
t.Run("Valid", func(t *testing.T) {
err := h.AddMember(context.TODO(), &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(context.TODO(), &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(context.TODO(), &pb.RemoveMemberRequest{
MemberId: uuid.New().String(),
}, &pb.RemoveMemberResponse{})
assert.Equal(t, handler.ErrMissingGroupID, err)
})
t.Run("MissingMemberID", func(t *testing.T) {
err := h.RemoveMember(context.TODO(), &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(context.TODO(), &pb.CreateRequest{
Name: "Alpha Group",
}, &cRsp)
assert.NoError(t, err)
t.Run("Valid", func(t *testing.T) {
err := h.RemoveMember(context.TODO(), &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(context.TODO(), &pb.RemoveMemberRequest{
GroupId: cRsp.Group.Id,
MemberId: uuid.New().String(),
}, &pb.RemoveMemberResponse{})
assert.NoError(t, err)
})
}

46
groups/main.go Normal file
View File

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

1
groups/micro.mu Normal file
View File

@@ -0,0 +1 @@
service groups

1096
groups/proto/groups.pb.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,209 @@
// 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)
}

81
groups/proto/groups.proto Normal file
View File

@@ -0,0 +1,81 @@
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 {}