Add Contact API (#340)

* feat: add basic files of contact

* chore: code review
1. delete redundant suffix
2. use tab replace whitespace in pb file

* chore: add some comments and check post data validation

* chore: add some comments

* chore: add publicapi.json and examples.json

* chore: update README.md

* fix: code review conversations
This commit is contained in:
zhaoyang
2021-12-21 17:59:45 +08:00
committed by GitHub
parent 0fa23b753f
commit 5f6de1fd18
16 changed files with 2498 additions and 3 deletions

2
contact/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
contact

3
contact/Dockerfile Normal file
View File

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

28
contact/Makefile Normal file
View File

@@ -0,0 +1,28 @@
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
go get github.com/micro/micro/v3/cmd/protoc-gen-openapi
.PHONY: api
api:
protoc --openapi_out=. --proto_path=. proto/contact.proto
.PHONY: proto
proto:
protoc --proto_path=. --micro_out=. --go_out=:. proto/contact.proto
.PHONY: build
build:
go build -o contact *.go
.PHONY: test
test:
go test -v ./... -cover
.PHONY: docker
docker:
docker build . -t contact:latest

5
contact/README.md Normal file
View File

@@ -0,0 +1,5 @@
Store your contacts
# Contact Service
This is the Contact service

119
contact/domain/contact.go Normal file
View File

@@ -0,0 +1,119 @@
package domain
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/micro/micro/v3/service/store"
"github.com/pkg/errors"
pb "github.com/micro/services/contact/proto"
)
type Contact interface {
Create(ctx context.Context, info *pb.ContactInfo) error
Update(ctx context.Context, id string, info *pb.ContactInfo) error
Read(ctx context.Context, id string) (result *pb.ContactInfo, err error)
Delete(ctx context.Context, id string) error
List(ctx context.Context, offset, limit uint) (result []*pb.ContactInfo, err error)
}
type contact struct {
store store.Store
}
func NewContactDomain(s store.Store) *contact {
return &contact{
store: s,
}
}
// contactIdPrefix return the contact prefix of the store key
func contactIdPrefix() string {
return "contact/id/"
}
// contactIdPrefix return the store key of one contact
func contactIdKey(id string) string {
return fmt.Sprintf("%s%s", contactIdPrefix(), id)
}
// Create a contact
func (c *contact) Create(ctx context.Context, info *pb.ContactInfo) error {
info.CreatedAt = time.Now().Format(time.RFC3339)
info.UpdatedAt = time.Now().Format(time.RFC3339)
val, err := json.Marshal(info)
if err != nil {
return err
}
return store.Write(&store.Record{
Key: contactIdKey(info.Id),
Value: val,
})
}
// Update contact information by id
func (c *contact) Update(ctx context.Context, id string, info *pb.ContactInfo) error {
info.UpdatedAt = time.Now().Format(time.RFC3339)
val, err := json.Marshal(info)
if err != nil {
return err
}
return store.Write(&store.Record{
Key: contactIdKey(id),
Value: val,
})
}
// Read one contact by id
func (c *contact) Read(ctx context.Context, id string) (*pb.ContactInfo, error) {
records, err := c.store.Read(contactIdKey(id))
if err != nil {
return nil, err
}
if len(records) == 0 {
return nil, errors.New("not found")
}
info := &pb.ContactInfo{}
if err := json.Unmarshal(records[0].Value, info); err != nil {
return nil, err
}
return info, err
}
// Delete one contact by id
func (c *contact) Delete(ctx context.Context, id string) error {
return c.store.Delete(contactIdKey(id))
}
// List contacts by offset and limit
func (c *contact) List(ctx context.Context, offset, limit uint) (result []*pb.ContactInfo, err error) {
records, err := c.store.Read(contactIdPrefix(),
store.ReadPrefix(),
store.ReadOffset(offset),
store.ReadLimit(limit))
if err != nil {
return nil, err
}
if len(records) == 0 {
return nil, errors.New("not found")
}
for _, rec := range records {
cinfo := &pb.ContactInfo{}
json.Unmarshal(rec.Value, cinfo)
result = append(result, cinfo)
}
return result, err
}

387
contact/examples.json Normal file
View File

@@ -0,0 +1,387 @@
{
"create": [
{
"title": "Create a contact",
"run_check": false,
"description": "Create a contact with post data",
"request": {
"name": "joe",
"phones": [
{
"label": "home",
"number": "010-12345678"
},
{
"label": "work",
"number": "010-87654321"
}
],
"emails": [
{
"label": "home",
"address": "home@example.com"
},
{
"label": "work",
"address": "work@example.com"
}
],
"links": [
{
"label": "blog",
"url": "https://blog.joe.me"
}
],
"birthday": "1995-01-01",
"addresses": [
{
"label": "company address",
"address": "https://company.of.joe.com"
}
],
"social_medias": [
{
"label": "twitter",
"username": "joe-twitter"
},
{
"label": "facebook",
"username": "joe-facebook"
}
],
"note": "this person is very important"
},
"response": {
"contact": {
"id": "42e48a3c-6221-11ec-96d2-acde48001122",
"name": "joe",
"phones": [
{
"label": "home",
"number": "010-12345678"
},
{
"label": "work",
"number": "010-87654321"
}
],
"emails": [
{
"label": "home",
"address": "home@example.com"
},
{
"label": "work",
"address": "work@example.com"
}
],
"links": [
{
"label": "blog",
"url": "https://blog.joe.me"
}
],
"birthday": "1995-01-01",
"addresses": [
{
"label": "company address",
"address": "https://company.of.joe.com"
}
],
"social_medias": [
{
"label": "twitter",
"username": "joe-twitter"
},
{
"label": "facebook",
"username": "joe-facebook"
}
],
"note": "this person is very important",
"created_at": "2021-12-21T17:28:15+08:00",
"updated_at": "2021-12-21T17:31:21+08:00"
}
}
}
],
"update": [
{
"title": "Update a contact",
"run_check": false,
"description": "Update a contact with post data",
"request": {
"id": "42e48a3c-6221-11ec-96d2-acde48001122",
"name": "joe",
"phones": [
{
"label": "home",
"number": "010-12345678"
},
{
"label": "work",
"number": "010-87654321"
}
],
"emails": [
{
"label": "home",
"address": "home@example.com"
},
{
"label": "work",
"address": "work@example.com"
}
],
"links": [
{
"label": "blog",
"url": "https://blog.joe.me"
}
],
"birthday": "1995-01-01",
"addresses": [
{
"label": "company address",
"address": "https://company.of.joe.com"
}
],
"social_medias": [
{
"label": "twitter",
"username": "joe-twitter"
},
{
"label": "facebook",
"username": "joe-facebook"
}
],
"note": "this person is very important"
},
"response": {
"contact": {
"id": "42e48a3c-6221-11ec-96d2-acde48001122",
"name": "joe",
"phones": [
{
"label": "home",
"number": "010-12345678"
},
{
"label": "work",
"number": "010-87654321"
}
],
"emails": [
{
"label": "home",
"address": "home@example.com"
},
{
"label": "work",
"address": "work@example.com"
}
],
"links": [
{
"label": "blog",
"url": "https://blog.joe.me"
}
],
"birthday": "1995-01-01",
"addresses": [
{
"label": "company address",
"address": "https://company.of.joe.com"
}
],
"social_medias": [
{
"label": "twitter",
"username": "joe-twitter"
},
{
"label": "facebook",
"username": "joe-facebook"
}
],
"note": "this person is very important",
"created_at": "2021-12-21T17:28:15+08:00",
"updated_at": "2021-12-21T17:31:21+08:00"
}
}
}
],
"read": [
{
"title": "Get a contact",
"run_check": false,
"description": "Get a contact by id",
"request": {
"id": "42e48a3c-6221-11ec-96d2-acde48001122"
},
"response": {
"contact": {
"id": "42e48a3c-6221-11ec-96d2-acde48001122",
"name": "joe",
"phones": [
{
"label": "home",
"number": "010-12345678"
},
{
"label": "work",
"number": "010-87654321"
}
],
"emails": [
{
"label": "home",
"address": "home@example.com"
},
{
"label": "work",
"address": "work@example.com"
}
],
"links": [
{
"label": "blog",
"url": "https://blog.joe.me"
}
],
"birthday": "1995-01-01",
"addresses": [
{
"label": "company address",
"address": "https://company.of.joe.com"
}
],
"social_medias": [
{
"label": "twitter",
"username": "joe-twitter"
},
{
"label": "facebook",
"username": "joe-facebook"
}
],
"note": "this person is very important",
"created_at": "2021-12-21T17:28:15+08:00",
"updated_at": "2021-12-21T17:31:21+08:00"
}
}
}
],
"delete": [
{
"title": "Delete a contact",
"run_check": false,
"description": "Delete a contact by id",
"request": {
"id": "42e48a3c-6221-11ec-96d2-acde48001122"
},
"response": {
}
}
],
"list": [
{
"title": "List contacts with default offset and limit, default limit is 20",
"run_check": false,
"description": "List contacts",
"request": {
},
"response": {
"contacts": [
{
"id": "7765a308-6222-11ec-96d2-acde48001122",
"name": "contact-1",
"phones": [
{
"label": "home",
"number": "010-1234567"
}
],
"emails": [],
"links": [],
"birthday": "",
"addresses": [],
"social_medias": [],
"note": "",
"created_at": "2021-12-21T17:28:15+08:00",
"updated_at": "2021-12-21T17:31:21+08:00"
},
{
"id": "7a7c1806-6222-11ec-96d2-acde48001122",
"name": "contact-2",
"phones": [
{
"label": "home",
"number": "010-1234567"
}
],
"emails": [],
"links": [],
"birthday": "",
"addresses": [],
"social_medias": [],
"note": "",
"created_at": "2021-12-21T17:28:15+08:00",
"updated_at": "2021-12-21T17:31:21+08:00"
},
{
"id": "7d4a8e28-6222-11ec-96d2-acde48001122",
"name": "contact-3",
"phones": [
{
"label": "home",
"number": "010-1234567"
}
],
"emails": [],
"links": [],
"birthday": "",
"addresses": [],
"social_medias": [],
"note": "",
"created_at": "2021-12-21T17:28:15+08:00",
"updated_at": "2021-12-21T17:31:21+08:00"
}
]
}
},
{
"title": "List contacts with specific offset and limit",
"run_check": false,
"description": "List contacts",
"request": {
"offset": 1,
"limit": 1
},
"response": {
"contacts": [
{
"id": "7a7c1806-6222-11ec-96d2-acde48001122",
"name": "contact-2",
"phones": [
{
"label": "home",
"number": "010-1234567"
}
],
"emails": [],
"links": [],
"birthday": "",
"addresses": [],
"social_medias": [],
"note": "",
"created_at": "2021-12-21T17:28:15+08:00",
"updated_at": "2021-12-21T17:31:21+08:00"
}
]
}
}
]
}

3
contact/generate.go Normal file
View File

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

127
contact/handler/contact.go Normal file
View File

@@ -0,0 +1,127 @@
package handler
import (
"context"
"strings"
"github.com/google/uuid"
"github.com/micro/micro/v3/service/errors"
"github.com/micro/services/contact/domain"
pb "github.com/micro/services/contact/proto"
)
type contact struct {
contact domain.Contact
}
func NewContact(c domain.Contact) *contact {
return &contact{
contact: c,
}
}
// Create a contact
func (c *contact) Create(ctx context.Context, req *pb.CreateRequest, rsp *pb.CreateResponse) error {
req.Name = strings.TrimSpace(req.Name)
if len(req.Name) == 0 {
return errors.BadRequest("contact.create", "contact name is required")
}
uid, err := uuid.NewUUID()
if err != nil {
return errors.InternalServerError("contact.create", "generate contact id error: %v", err)
}
info := &pb.ContactInfo{
Id: uid.String(),
Name: req.Name,
Phones: req.Phones,
Emails: req.Emails,
Links: req.Links,
Birthday: req.Birthday,
Addresses: req.Addresses,
SocialMedias: req.SocialMedias,
Note: req.Note,
}
if err := c.contact.Create(ctx, info); err != nil {
return errors.InternalServerError("contact.create", "create contact error: %v", err)
}
rsp.Contact = info
return nil
}
// Update information of the contact submitted
func (c *contact) Update(ctx context.Context, req *pb.UpdateRequest, rsp *pb.UpdateResponse) error {
req.Name = strings.TrimSpace(req.Name)
if len(req.Name) == 0 {
return errors.BadRequest("contact.create", "contact name is required")
}
old, err := c.contact.Read(ctx, req.Id)
if err != nil {
return errors.InternalServerError("contact.update", "get contact info error: %v", err)
}
info := &pb.ContactInfo{
Id: req.Id,
Name: req.Name,
Phones: req.Phones,
Emails: req.Emails,
Links: req.Links,
Birthday: req.Birthday,
Addresses: req.Addresses,
SocialMedias: req.SocialMedias,
Note: req.Note,
CreatedAt: old.CreatedAt,
}
if err := c.contact.Update(ctx, req.Id, info); err != nil {
return errors.InternalServerError("contact.update", "update contact error: %v", err)
}
rsp.Contact = info
return nil
}
// Read a contact by id
func (c *contact) Read(ctx context.Context, req *pb.ReadRequest, rsp *pb.ReadResponse) error {
info, err := c.contact.Read(ctx, req.Id)
if err != nil {
return errors.InternalServerError("contact.read", "get contact info error: %v", err)
}
rsp.Contact = info
return nil
}
// Delete contact by id
func (c *contact) Delete(ctx context.Context, req *pb.DeleteRequest, _ *pb.DeleteResponse) error {
err := c.contact.Delete(ctx, req.Id)
if err != nil {
return errors.InternalServerError("contact.delete", "delete contact error: %v", err)
}
return nil
}
// List contacts with offset and limit
func (c *contact) List(ctx context.Context, req *pb.ListRequest, rsp *pb.ListResponse) error {
if req.Limit == 0 {
req.Limit = 30
}
list, err := c.contact.List(ctx, uint(req.Offset), uint(req.Limit))
if err != nil {
return errors.InternalServerError("contact.list", "get contact info error: %v", err)
}
rsp.Contacts = list
return nil
}

30
contact/main.go Normal file
View File

@@ -0,0 +1,30 @@
package main
import (
"github.com/micro/micro/v3/service/store"
"github.com/micro/services/contact/domain"
"github.com/micro/services/contact/handler"
pb "github.com/micro/services/contact/proto"
"github.com/micro/micro/v3/service"
"github.com/micro/micro/v3/service/logger"
)
func main() {
// Create service
srv := service.New(
service.Name("contact"),
service.Version("latest"),
)
contactDomain := domain.NewContactDomain(store.DefaultStore)
// Register handler
pb.RegisterContactHandler(srv.Server(), handler.NewContact(contactDomain))
// Run service
if err := srv.Run(); err != nil {
logger.Fatal(err)
}
}

1
contact/micro.mu Normal file
View File

@@ -0,0 +1 @@
service contact

1475
contact/proto/contact.pb.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,161 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: proto/contact.proto
package contact
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 Contact service
func NewContactEndpoints() []*api.Endpoint {
return []*api.Endpoint{}
}
// Client API for Contact service
type ContactService interface {
Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error)
Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error)
Update(ctx context.Context, in *UpdateRequest, opts ...client.CallOption) (*UpdateResponse, error)
Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error)
List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error)
}
type contactService struct {
c client.Client
name string
}
func NewContactService(name string, c client.Client) ContactService {
return &contactService{
c: c,
name: name,
}
}
func (c *contactService) Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error) {
req := c.c.NewRequest(c.name, "Contact.Create", in)
out := new(CreateResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *contactService) Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error) {
req := c.c.NewRequest(c.name, "Contact.Read", in)
out := new(ReadResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *contactService) Update(ctx context.Context, in *UpdateRequest, opts ...client.CallOption) (*UpdateResponse, error) {
req := c.c.NewRequest(c.name, "Contact.Update", in)
out := new(UpdateResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *contactService) Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error) {
req := c.c.NewRequest(c.name, "Contact.Delete", in)
out := new(DeleteResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *contactService) List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error) {
req := c.c.NewRequest(c.name, "Contact.List", in)
out := new(ListResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Contact service
type ContactHandler interface {
Create(context.Context, *CreateRequest, *CreateResponse) error
Read(context.Context, *ReadRequest, *ReadResponse) error
Update(context.Context, *UpdateRequest, *UpdateResponse) error
Delete(context.Context, *DeleteRequest, *DeleteResponse) error
List(context.Context, *ListRequest, *ListResponse) error
}
func RegisterContactHandler(s server.Server, hdlr ContactHandler, opts ...server.HandlerOption) error {
type contact 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
}
type Contact struct {
contact
}
h := &contactHandler{hdlr}
return s.Handle(s.NewHandler(&Contact{h}, opts...))
}
type contactHandler struct {
ContactHandler
}
func (h *contactHandler) Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error {
return h.ContactHandler.Create(ctx, in, out)
}
func (h *contactHandler) Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error {
return h.ContactHandler.Read(ctx, in, out)
}
func (h *contactHandler) Update(ctx context.Context, in *UpdateRequest, out *UpdateResponse) error {
return h.ContactHandler.Update(ctx, in, out)
}
func (h *contactHandler) Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error {
return h.ContactHandler.Delete(ctx, in, out)
}
func (h *contactHandler) List(ctx context.Context, in *ListRequest, out *ListResponse) error {
return h.ContactHandler.List(ctx, in, out)
}

151
contact/proto/contact.proto Normal file
View File

@@ -0,0 +1,151 @@
syntax = "proto3";
package contact;
option go_package = "./proto;contact";
service Contact {
rpc Create(CreateRequest) returns (CreateResponse) {}
rpc Read(ReadRequest) returns (ReadResponse) {}
rpc Update(UpdateRequest) returns (UpdateResponse) {}
rpc Delete(DeleteRequest) returns (DeleteResponse) {}
rpc List(ListRequest) returns (ListResponse) {}
// TODO: wait for the search API
// rpc Search(Request) returns (Response) {}
}
message Phone {
// the label of the phone number
string label = 1;
// phone number
string number = 2;
}
message Email {
// the label of the email
string label = 1;
// the email address
string address = 2;
}
message Link {
// the label of the link
string label = 1;
// the url of the contact
string url = 2;
}
message Address {
// the label of the address
string label = 1;
// the address
string address = 2;
}
message SocialMedia {
// the label of the social
string label = 1;
// the username of social media
string username = 2;
}
message ContactInfo {
// contact id
string id = 1;
// the contact name
string name = 2;
// the phone numbers
repeated Phone phones = 3;
// the emails
repeated Email emails = 4;
// the contact links
repeated Link links = 5;
// the birthday
string birthday = 6;
// the address
repeated Address addresses = 7;
// the social media username
repeated SocialMedia social_medias = 8;
// note of the contact
string note = 9;
// create date string in RFC3339
string created_at = 10;
// update date string in RFC3339
string updated_at = 11;
}
message CreateRequest {
// required, the name of the contact
string name = 1;
// optional, phone numbers
repeated Phone phones = 2;
// optional, emails
repeated Email emails = 3;
// optional, links
repeated Link links = 4;
// optional, birthday
string birthday = 5;
// optional, address
repeated Address addresses = 6;
// optional, social media
repeated SocialMedia social_medias = 7;
// optional, note of the contact
string note = 8;
}
message CreateResponse {
ContactInfo contact = 1;
}
message ReadRequest {
string id = 1;
}
message ReadResponse {
ContactInfo contact = 1;
}
message DeleteRequest {
// the id of the contact
string id = 1;
}
message DeleteResponse {
}
message UpdateRequest {
// required, the contact id
string id = 1;
// required, the name
string name = 2;
// optional, phone number
repeated Phone phones = 3;
// optional, emails
repeated Email emails = 4;
// optional, links
repeated Link links = 5;
// optional, birthday
string birthday = 6;
// optional, addresses
repeated Address addresses = 7;
// optional, social media
repeated SocialMedia social_medias = 8;
// optional, note
string note = 9;
}
message UpdateResponse {
ContactInfo contact = 1;
}
message ListRequest {
// optional
uint32 offset = 1;
// optional, default is 30
uint32 limit = 2;
}
message ListResponse {
repeated ContactInfo contacts = 1;
}

6
contact/publicapi.json Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "contact",
"icon": "👥",
"category": "communication",
"display_name": "Contacts"
}

1
go.mod
View File

@@ -64,7 +64,6 @@ require (
googlemaps.github.io/maps v1.3.1
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
gorm.io/datatypes v1.0.1
gorm.io/driver/postgres v1.0.8

2
go.sum
View File

@@ -493,8 +493,6 @@ github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KK
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/micro/micro/v3 v3.8.0 h1:RTH2835RJ4/aqLZGMjGCIf7HroCmYlJh2KRHHuSL/AE=
github.com/micro/micro/v3 v3.8.0/go.mod h1:gjFa8T2ouD6BvorPTVPXLWtrRJwSKT5KxUNuu23Kkts=
github.com/micro/micro/v3 v3.8.1-0.20211216122745-2e7245423520 h1:LtErHRofRQf5damrjb5AkpvmklaIhx3anLJ9sN4fMrI=
github.com/micro/micro/v3 v3.8.1-0.20211216122745-2e7245423520/go.mod h1:gjFa8T2ouD6BvorPTVPXLWtrRJwSKT5KxUNuu23Kkts=
github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=