add lists

This commit is contained in:
Asim Aslam
2022-02-20 11:57:07 +00:00
parent ea95c4954d
commit 3104133e38
9 changed files with 1850 additions and 0 deletions

29
lists/Makefile Normal file
View File

@@ -0,0 +1,29 @@
GOPATH:=$(shell go env GOPATH)
.PHONY: api
api:
protoc --openapi_out=. --proto_path=. proto/lists.proto
.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: proto
proto:
protoc --proto_path=. --micro_out=. --go_out=:. proto/lists.proto
.PHONY: build
build:
go build -o lists *.go
.PHONY: test
test:
go test -v ./... -cover
.PHONY: docker
docker:
docker build . -t lists:latest

6
lists/README.md Normal file
View File

@@ -0,0 +1,6 @@
Makes a list
# Lists Service
Make lists for anything. Shopping, todos, mail, waitlists, absolutely anything.

111
lists/examples.json Normal file
View File

@@ -0,0 +1,111 @@
{
"create": [{
"title": "Create a list",
"description": "Create a simple text based list",
"run_check": false,
"request": {
"title": "New List",
"text": "This is my list"
},
"response": {
"list": {
"id": "63c0cdf8-2121-11ec-a881-0242e36f037a",
"created": "2021-09-29T13:33:03+01:00",
"updated": "2021-09-29T13:33:03+01:00",
"title": "New List",
"text": "This is my list"
}
}
}],
"read": [{
"title": "Read a list",
"description": "Read a list by its ID",
"run_check": false,
"request": {
"id": "63c0cdf8-2121-11ec-a881-0242e36f037a"
},
"response": {
"list": {
"id": "63c0cdf8-2121-11ec-a881-0242e36f037a",
"created": "2021-09-29T13:33:03+01:00",
"updated": "2021-09-29T13:33:03+01:00",
"title": "New List",
"text": "This is my list"
}
}
}],
"list": [{
"title": "List all lists",
"description": "List all your available lists",
"run_check": false,
"request": {},
"response": {
"lists": [
{
"id": "63c0cdf8-2121-11ec-a881-0242e36f037a",
"created": "2021-09-29T13:33:03+01:00",
"updated": "2021-09-29T13:33:03+01:00",
"title": "New List",
"text": "This is my list"
}
]
}
}],
"update": [{
"title": "Update a List",
"description": "Update the list title and text",
"run_check": false,
"request": {
"list": {
"id": "63c0cdf8-2121-11ec-a881-0242e36f037a",
"title": "Update List",
"text": "Updated list text"
}
},
"response": {
"list": {
"id": "63c0cdf8-2121-11ec-a881-0242e36f037a",
"created": "2021-09-29T13:33:03+01:00",
"updated": "2021-09-29T13:50:20+01:00",
"title": "Update List",
"text": "Updated list text"
}
}
}],
"delete": [{
"title": "Delete a List",
"description": "Delete a list by id",
"run_check": false,
"request": {
"id": "63c0cdf8-2121-11ec-a881-0242e36f037a"
},
"response": {
"list": {
"id": "63c0cdf8-2121-11ec-a881-0242e36f037a",
"created": "2021-09-29T13:33:03+01:00",
"updated": "2021-09-29T13:50:20+01:00",
"title": "Update List",
"text": "Updated list text"
}
}
}],
"events": [{
"title": "Subscribe to events",
"description": "Subscribe to list change events",
"run_check": false,
"request": {
"id": "63c0cdf8-2121-11ec-a881-0242e36f037a"
},
"response": {
"event": "deleted",
"list": {
"id": "63c0cdf8-2121-11ec-a881-0242e36f037a",
"created": "2021-09-29T13:33:03+01:00",
"updated": "2021-09-29T13:50:20+01:00",
"title": "Update List",
"text": "Updated list text"
}
}
}]
}

323
lists/handler/lists.go Normal file
View File

@@ -0,0 +1,323 @@
package handler
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/google/uuid"
"github.com/micro/micro/v3/service/client"
"github.com/micro/micro/v3/service/errors"
"github.com/micro/micro/v3/service/logger"
"github.com/micro/micro/v3/service/store"
pb "github.com/micro/services/lists/proto"
streamPb "github.com/micro/services/mq/proto"
pauth "github.com/micro/services/pkg/auth"
adminpb "github.com/micro/services/pkg/service/proto"
"github.com/micro/services/pkg/tenant"
"google.golang.org/protobuf/types/known/structpb"
)
// New returns an initialized Lists
func New(c client.Client) *Lists {
return &Lists{
Stream: streamPb.NewMqService("mq", c),
}
}
// Lists implements the lists proto definition
type Lists struct {
Stream streamPb.MqService
}
func newMessage(ev map[string]interface{}) *structpb.Struct {
st := new(structpb.Struct)
b, _ := json.Marshal(ev)
json.Unmarshal(b, st)
return st
}
// Create inserts a new list in the store
func (h *Lists) Create(ctx context.Context, req *pb.CreateRequest, rsp *pb.CreateResponse) error {
if len(req.Name) == 0 && len(req.Items) == 0 {
return errors.BadRequest("lists.create", "missing name and text")
}
tnt, ok := tenant.FromContext(ctx)
if !ok {
tnt = "default"
}
// generate a key (uuid v4)
id, err := uuid.NewUUID()
if err != nil {
return err
}
t := time.Now().Format(time.RFC3339)
// set the generated fields on the list
list := &pb.List{
Id: id.String(),
Created: t,
Updated: t,
Name: req.Name,
Items: req.Items,
}
key := fmt.Sprintf("%s:%s", tnt, id)
rec := store.NewRecord(key, list)
if err = store.Write(rec); err != nil {
return errors.InternalServerError("lists.created", "failed to create list")
}
// return the list in the response
rsp.List = list
h.Stream.Publish(ctx, &streamPb.PublishRequest{
Topic: "lists",
Message: newMessage(map[string]interface{}{
"event": "create",
"list": list,
}),
})
return nil
}
func (h *Lists) Read(ctx context.Context, req *pb.ReadRequest, rsp *pb.ReadResponse) error {
if len(req.Id) == 0 {
return errors.BadRequest("lists.read", "Missing List ID")
}
tnt, ok := tenant.FromContext(ctx)
if !ok {
tnt = "default"
}
key := fmt.Sprintf("%s:%s", tnt, req.Id)
// read the specific list
recs, err := store.Read(key)
if err == store.ErrNotFound {
return errors.NotFound("lists.read", "List not found")
} else if err != nil {
return errors.InternalServerError("lists.read", "Error reading from store: %v", err.Error())
}
// Decode the list
var list *pb.List
if err := recs[0].Decode(&list); err != nil {
return errors.InternalServerError("lists.update", "Error unmarshaling JSON: %v", err.Error())
}
// return the list
rsp.List = list
return nil
}
// Update is a unary API which updates a list in the store
func (h *Lists) Update(ctx context.Context, req *pb.UpdateRequest, rsp *pb.UpdateResponse) error {
// Validate the request
if req.List == nil {
return errors.BadRequest("lists.update", "Missing List")
}
if len(req.List.Id) == 0 {
return errors.BadRequest("lists.update", "Missing List ID")
}
tnt, ok := tenant.FromContext(ctx)
if !ok {
tnt = "default"
}
key := fmt.Sprintf("%s:%s", tnt, req.List.Id)
// read the specific list
recs, err := store.Read(key)
if err == store.ErrNotFound {
return errors.NotFound("lists.update", "List not found")
} else if err != nil {
return errors.InternalServerError("lists.update", "Error reading from store: %v", err.Error())
}
// Decode the list
var list *pb.List
if err := recs[0].Decode(&list); err != nil {
return errors.InternalServerError("lists.update", "Error unmarshaling JSON: %v", err.Error())
}
// Update the lists name and text
list.Name = req.List.Name
list.Items = req.List.Items
list.Updated = time.Now().Format(time.RFC3339)
rec := store.NewRecord(key, list)
// Write the updated list to the store
if err = store.Write(rec); err != nil {
return errors.InternalServerError("lists.update", "Error writing to store: %v", err.Error())
}
h.Stream.Publish(ctx, &streamPb.PublishRequest{
Topic: "lists",
Message: newMessage(map[string]interface{}{
"event": "update",
"list": list,
}),
})
rsp.List = list
return nil
}
func (h *Lists) Events(ctx context.Context, req *pb.EventsRequest, stream pb.Lists_EventsStream) error {
backendStream, err := h.Stream.Subscribe(ctx, &streamPb.SubscribeRequest{
Topic: "lists",
})
if err != nil {
return errors.InternalServerError("lists.subscribe", "Failed to subscribe to lists")
}
for {
select {
case <-ctx.Done():
return nil
default:
}
// receive messages from the stream
msg, err := backendStream.Recv()
if err != nil {
return nil
}
v, err := msg.Message.MarshalJSON()
if err != nil {
continue
}
rsp := new(pb.EventsResponse)
if err := json.Unmarshal(v, rsp); err != nil {
continue
}
list := rsp.List
// filter if necessary by id
if len(req.Id) > 0 && list.Id != req.Id {
continue
}
// send back the event to the client
if err := stream.Send(rsp); err != nil {
return nil
}
}
return nil
}
// Delete removes the list from the store, looking up using ID
func (h *Lists) Delete(ctx context.Context, req *pb.DeleteRequest, rsp *pb.DeleteResponse) error {
// Validate the request
if len(req.Id) == 0 {
return errors.BadRequest("lists.delete", "Missing List ID")
}
tnt, ok := tenant.FromContext(ctx)
if !ok {
tnt = "default"
}
key := fmt.Sprintf("%s:%s", tnt, req.Id)
// read the specific list
recs, err := store.Read(key)
if err == store.ErrNotFound {
return nil
} else if err != nil {
return errors.InternalServerError("lists.delete", "Error reading from store: %v", err.Error())
}
// Decode the list
var list *pb.List
if err := recs[0].Decode(&list); err != nil {
return errors.InternalServerError("lists.delete", "Error unmarshaling JSON: %v", err.Error())
}
// now delete it
if err := store.Delete(key); err != nil && err != store.ErrNotFound {
return errors.InternalServerError("lists.delete", "Failed to delete list")
}
h.Stream.Publish(ctx, &streamPb.PublishRequest{
Topic: "lists",
Message: newMessage(map[string]interface{}{
"event": "delete",
"list": list,
}),
})
rsp.List = list
return nil
}
// List returns all of the lists in the store
func (h *Lists) List(ctx context.Context, req *pb.ListRequest, rsp *pb.ListResponse) error {
tnt, ok := tenant.FromContext(ctx)
if !ok {
tnt = "default"
}
key := fmt.Sprintf("%s:", tnt)
// Retrieve all of the records in the store
recs, err := store.Read(key, store.ReadPrefix())
if err != nil {
return errors.InternalServerError("lists.list", "Error reading from store: %v", err.Error())
}
// Initialize the response lists slice
rsp.Lists = make([]*pb.List, len(recs))
// Unmarshal the lists into the response
for i, r := range recs {
if err := r.Decode(&rsp.Lists[i]); err != nil {
return errors.InternalServerError("lists.list", "Error decoding list: %v", err.Error())
}
}
return nil
}
func (h *Lists) DeleteData(ctx context.Context, request *adminpb.DeleteDataRequest, response *adminpb.DeleteDataResponse) error {
method := "admin.DeleteData"
_, err := pauth.VerifyMicroAdmin(ctx, method)
if err != nil {
return err
}
if len(request.TenantId) < 10 { // deliberate length check so we don't delete all the things
return errors.BadRequest(method, "Missing tenant ID")
}
keys, err := store.List(store.ListPrefix(request.TenantId))
if err != nil {
return err
}
for _, k := range keys {
if err := store.Delete(k); err != nil {
return err
}
}
logger.Infof("Deleted %d keys for %s", len(keys), request.TenantId)
return nil
}

30
lists/main.go Normal file
View File

@@ -0,0 +1,30 @@
package main
import (
"github.com/micro/micro/v3/service"
log "github.com/micro/micro/v3/service/logger"
"github.com/micro/services/lists/handler"
pb "github.com/micro/services/lists/proto"
admin "github.com/micro/services/pkg/service/proto"
)
func main() {
// New Service
srv := service.New(
service.Name("lists"),
service.Version("latest"),
)
// Initialise service
srv.Init()
h := handler.New(srv.Client())
// Register Handler
pb.RegisterListsHandler(srv.Server(), h)
admin.RegisterAdminHandler(srv.Server(), h)
// Run service
if err := srv.Run(); err != nil {
log.Fatal(err)
}
}

1001
lists/proto/lists.pb.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,253 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: proto/lists.proto
package lists
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 Lists service
func NewListsEndpoints() []*api.Endpoint {
return []*api.Endpoint{}
}
// Client API for Lists service
type ListsService interface {
List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error)
Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error)
Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error)
Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error)
Update(ctx context.Context, in *UpdateRequest, opts ...client.CallOption) (*UpdateResponse, error)
Events(ctx context.Context, in *EventsRequest, opts ...client.CallOption) (Lists_EventsService, error)
}
type listsService struct {
c client.Client
name string
}
func NewListsService(name string, c client.Client) ListsService {
return &listsService{
c: c,
name: name,
}
}
func (c *listsService) List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error) {
req := c.c.NewRequest(c.name, "Lists.List", in)
out := new(ListResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *listsService) Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error) {
req := c.c.NewRequest(c.name, "Lists.Create", in)
out := new(CreateResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *listsService) Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error) {
req := c.c.NewRequest(c.name, "Lists.Read", in)
out := new(ReadResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *listsService) Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error) {
req := c.c.NewRequest(c.name, "Lists.Delete", in)
out := new(DeleteResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *listsService) Update(ctx context.Context, in *UpdateRequest, opts ...client.CallOption) (*UpdateResponse, error) {
req := c.c.NewRequest(c.name, "Lists.Update", in)
out := new(UpdateResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *listsService) Events(ctx context.Context, in *EventsRequest, opts ...client.CallOption) (Lists_EventsService, error) {
req := c.c.NewRequest(c.name, "Lists.Events", &EventsRequest{})
stream, err := c.c.Stream(ctx, req, opts...)
if err != nil {
return nil, err
}
if err := stream.Send(in); err != nil {
return nil, err
}
return &listsServiceEvents{stream}, nil
}
type Lists_EventsService interface {
Context() context.Context
SendMsg(interface{}) error
RecvMsg(interface{}) error
Close() error
Recv() (*EventsResponse, error)
}
type listsServiceEvents struct {
stream client.Stream
}
func (x *listsServiceEvents) Close() error {
return x.stream.Close()
}
func (x *listsServiceEvents) Context() context.Context {
return x.stream.Context()
}
func (x *listsServiceEvents) SendMsg(m interface{}) error {
return x.stream.Send(m)
}
func (x *listsServiceEvents) RecvMsg(m interface{}) error {
return x.stream.Recv(m)
}
func (x *listsServiceEvents) Recv() (*EventsResponse, error) {
m := new(EventsResponse)
err := x.stream.Recv(m)
if err != nil {
return nil, err
}
return m, nil
}
// Server API for Lists service
type ListsHandler interface {
List(context.Context, *ListRequest, *ListResponse) error
Create(context.Context, *CreateRequest, *CreateResponse) error
Read(context.Context, *ReadRequest, *ReadResponse) error
Delete(context.Context, *DeleteRequest, *DeleteResponse) error
Update(context.Context, *UpdateRequest, *UpdateResponse) error
Events(context.Context, *EventsRequest, Lists_EventsStream) error
}
func RegisterListsHandler(s server.Server, hdlr ListsHandler, opts ...server.HandlerOption) error {
type lists interface {
List(ctx context.Context, in *ListRequest, out *ListResponse) error
Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error
Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error
Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error
Update(ctx context.Context, in *UpdateRequest, out *UpdateResponse) error
Events(ctx context.Context, stream server.Stream) error
}
type Lists struct {
lists
}
h := &listsHandler{hdlr}
return s.Handle(s.NewHandler(&Lists{h}, opts...))
}
type listsHandler struct {
ListsHandler
}
func (h *listsHandler) List(ctx context.Context, in *ListRequest, out *ListResponse) error {
return h.ListsHandler.List(ctx, in, out)
}
func (h *listsHandler) Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error {
return h.ListsHandler.Create(ctx, in, out)
}
func (h *listsHandler) Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error {
return h.ListsHandler.Read(ctx, in, out)
}
func (h *listsHandler) Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error {
return h.ListsHandler.Delete(ctx, in, out)
}
func (h *listsHandler) Update(ctx context.Context, in *UpdateRequest, out *UpdateResponse) error {
return h.ListsHandler.Update(ctx, in, out)
}
func (h *listsHandler) Events(ctx context.Context, stream server.Stream) error {
m := new(EventsRequest)
if err := stream.Recv(m); err != nil {
return err
}
return h.ListsHandler.Events(ctx, m, &listsEventsStream{stream})
}
type Lists_EventsStream interface {
Context() context.Context
SendMsg(interface{}) error
RecvMsg(interface{}) error
Close() error
Send(*EventsResponse) error
}
type listsEventsStream struct {
stream server.Stream
}
func (x *listsEventsStream) Close() error {
return x.stream.Close()
}
func (x *listsEventsStream) Context() context.Context {
return x.stream.Context()
}
func (x *listsEventsStream) SendMsg(m interface{}) error {
return x.stream.Send(m)
}
func (x *listsEventsStream) RecvMsg(m interface{}) error {
return x.stream.Recv(m)
}
func (x *listsEventsStream) Send(m *EventsResponse) error {
return x.stream.Send(m)
}

91
lists/proto/lists.proto Normal file
View File

@@ -0,0 +1,91 @@
syntax = "proto3";
package lists;
option go_package = "./proto;lists";
service Lists {
rpc List(ListRequest) returns (ListResponse);
rpc Create(CreateRequest) returns (CreateResponse);
rpc Read(ReadRequest) returns (ReadResponse);
rpc Delete(DeleteRequest) returns (DeleteResponse);
rpc Update(UpdateRequest) returns (UpdateResponse);
rpc Events(EventsRequest) returns (stream EventsResponse);
}
message List {
// unique id for the list, generated if not specified
string id = 1;
// time at which the list was created
string created = 2;
// time at which the list was updated
string updated = 3;
// name of the list
string name = 4;
// items within the list
repeated string items = 5;
}
// Create a new list
message CreateRequest {
// list name
string name = 1;
// list items
repeated string items = 2;
}
message CreateResponse {
// The created list
List list = 1;
}
// Read a list
message ReadRequest {
// the list id
string id = 1;
}
message ReadResponse {
// The list
List list = 1;
}
// Update a list
message UpdateRequest {
List list = 1;
}
message UpdateResponse {
List list = 1;
}
// Delete a list
message DeleteRequest {
// specify the id of the list
string id = 1;
}
message DeleteResponse {
List list = 1;
}
// List all the lists
message ListRequest {}
message ListResponse {
// the list of lists
repeated List lists = 1;
}
// Subscribe to lists events
message EventsRequest {
// optionally specify a list id
string id = 1;
}
message EventsResponse {
// the event which occured; create, delete, update
string event = 1;
// the list which the operation occured on
List list = 2;
}

6
lists/publicapi.json Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "lists",
"icon": "✔️",
"category": "storage",
"display_name": "Lists"
}