mirror of
https://github.com/kevin-DL/services.git
synced 2026-01-11 19:04:35 +00:00
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:
2
contact/.gitignore
vendored
Normal file
2
contact/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
contact
|
||||||
3
contact/Dockerfile
Normal file
3
contact/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
FROM alpine
|
||||||
|
ADD contact /contact
|
||||||
|
ENTRYPOINT [ "/contact" ]
|
||||||
28
contact/Makefile
Normal file
28
contact/Makefile
Normal 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
5
contact/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Store your contacts
|
||||||
|
|
||||||
|
# Contact Service
|
||||||
|
|
||||||
|
This is the Contact service
|
||||||
119
contact/domain/contact.go
Normal file
119
contact/domain/contact.go
Normal 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
387
contact/examples.json
Normal 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
3
contact/generate.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
//go:generate make proto
|
||||||
127
contact/handler/contact.go
Normal file
127
contact/handler/contact.go
Normal 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
30
contact/main.go
Normal 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
1
contact/micro.mu
Normal file
@@ -0,0 +1 @@
|
|||||||
|
service contact
|
||||||
1475
contact/proto/contact.pb.go
Normal file
1475
contact/proto/contact.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
161
contact/proto/contact.pb.micro.go
Normal file
161
contact/proto/contact.pb.micro.go
Normal 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
151
contact/proto/contact.proto
Normal 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
6
contact/publicapi.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "contact",
|
||||||
|
"icon": "👥",
|
||||||
|
"category": "communication",
|
||||||
|
"display_name": "Contacts"
|
||||||
|
}
|
||||||
1
go.mod
1
go.mod
@@ -64,7 +64,6 @@ require (
|
|||||||
googlemaps.github.io/maps v1.3.1
|
googlemaps.github.io/maps v1.3.1
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
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
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
|
||||||
gorm.io/datatypes v1.0.1
|
gorm.io/datatypes v1.0.1
|
||||||
gorm.io/driver/postgres v1.0.8
|
gorm.io/driver/postgres v1.0.8
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -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-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/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/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 h1:LtErHRofRQf5damrjb5AkpvmklaIhx3anLJ9sN4fMrI=
|
||||||
github.com/micro/micro/v3 v3.8.1-0.20211216122745-2e7245423520/go.mod h1:gjFa8T2ouD6BvorPTVPXLWtrRJwSKT5KxUNuu23Kkts=
|
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=
|
github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
|
|||||||
Reference in New Issue
Block a user