mirror of
https://github.com/kevin-DL/services.git
synced 2026-01-11 19:04:35 +00:00
Groups Service (#40)
This commit is contained in:
2
groups/.gitignore
vendored
Normal file
2
groups/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
groups
|
||||
3
groups/Dockerfile
Normal file
3
groups/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
||||
FROM alpine
|
||||
ADD groups /groups
|
||||
ENTRYPOINT [ "/groups" ]
|
||||
22
groups/Makefile
Normal file
22
groups/Makefile
Normal 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
31
groups/README.md
Normal 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
2
groups/generate.go
Normal file
@@ -0,0 +1,2 @@
|
||||
package main
|
||||
//go:generate make proto
|
||||
209
groups/handler/handler.go
Normal file
209
groups/handler/handler.go
Normal 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
|
||||
}
|
||||
288
groups/handler/handler_test.go
Normal file
288
groups/handler/handler_test.go
Normal 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
46
groups/main.go
Normal 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
1
groups/micro.mu
Normal file
@@ -0,0 +1 @@
|
||||
service groups
|
||||
1096
groups/proto/groups.pb.go
Normal file
1096
groups/proto/groups.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
209
groups/proto/groups.pb.micro.go
Normal file
209
groups/proto/groups.pb.micro.go
Normal 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
81
groups/proto/groups.proto
Normal 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 {}
|
||||
Reference in New Issue
Block a user