Unverified Commit 89ba2a65 authored by boojack's avatar boojack Committed by GitHub

feat: implement part of tag service (#2051)

* feat: add grpc gateway tempalte

* chore: update

* chore: move directory

* chore: update
parent 9cedb3cc
package v2
import (
"context"
"fmt"
grpcRuntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/labstack/echo/v4"
apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
// RegisterGateway registers the gRPC-Gateway with the given Echo instance.
func RegisterGateway(ctx context.Context, e *echo.Echo, grpcServerPort int) {
// Create a client connection to the gRPC Server we just started.
// This is where the gRPC-Gateway proxies the requests.
conn, err := grpc.DialContext(
ctx,
fmt.Sprintf(":%d", grpcServerPort),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
panic(err)
}
gwMux := grpcRuntime.NewServeMux()
err = apiv2pb.RegisterTagServiceHandler(context.Background(), gwMux, conn)
if err != nil {
panic(err)
}
e.Any("/api/v2/*", echo.WrapHandler(gwMux))
}
package v2
import (
"context"
apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
"github.com/usememos/memos/store"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type TagService struct {
apiv2pb.UnimplementedTagServiceServer
Store *store.Store
}
// NewTagService creates a new TagService.
func NewTagService(store *store.Store) *TagService {
return &TagService{
Store: store,
}
}
func (s *TagService) ListTags(ctx context.Context, request *apiv2pb.ListTagsRequest) (*apiv2pb.ListTagsResponse, error) {
tags, err := s.Store.ListTags(ctx, &store.FindTag{
CreatorID: int(request.CreatorId),
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list tags: %v", err)
}
response := &apiv2pb.ListTagsResponse{}
for _, tag := range tags {
response.Tags = append(response.Tags, convertTagFromStore(tag))
}
return response, nil
}
func convertTagFromStore(tag *store.Tag) *apiv2pb.Tag {
return &apiv2pb.Tag{
Name: tag.Name,
CreatorId: int32(tag.CreatorID),
}
}
package v2
......@@ -10,10 +10,10 @@ require (
github.com/aws/aws-sdk-go-v2/credentials v1.13.12
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.51
github.com/aws/aws-sdk-go-v2/service/s3 v1.30.3
github.com/bufbuild/connect-go v1.10.0
github.com/disintegration/imaging v1.6.2
github.com/google/uuid v1.3.0
github.com/gorilla/feeds v1.1.1
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2
github.com/labstack/echo/v4 v4.9.0
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.6.1
......@@ -21,12 +21,13 @@ require (
github.com/stretchr/testify v1.8.1
github.com/yuin/goldmark v1.5.4
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.1.0
golang.org/x/crypto v0.11.0
golang.org/x/exp v0.0.0-20230111222715-75897c7a292a
golang.org/x/mod v0.8.0
golang.org/x/net v0.7.0
golang.org/x/oauth2 v0.5.0
google.golang.org/grpc v1.52.0
golang.org/x/net v0.12.0
golang.org/x/oauth2 v0.10.0
google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e
google.golang.org/grpc v1.57.0
modernc.org/sqlite v1.24.0
)
......@@ -34,9 +35,11 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
golang.org/x/image v0.7.0 // indirect
golang.org/x/tools v0.6.0 // indirect
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230726155614-23370e0ffb3e // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect
......@@ -67,11 +70,10 @@ require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/labstack/gommon v0.3.1 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
......@@ -88,12 +90,11 @@ require (
github.com/valyala/fasttemplate v1.2.1 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/time v0.1.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.31.0
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
This diff is collapsed.
syntax = "proto3";
package memos.apiv2;
package memos.api.v2;
option go_package = "gen/apiv2";
import "google/api/annotations.proto";
option go_package = "gen/api/v2";
service TagService {
rpc ListTags(ListTagsRequest) returns (ListTagsResponse);
rpc ListTags(ListTagsRequest) returns (ListTagsResponse) {
option (google.api.http) = {get: "/api/v2/tags"};
}
}
message Tag {
......
......@@ -3,16 +3,18 @@ managed:
enabled: true
go_package_prefix:
default: github.com/usememos/memos/proto/gen
except:
- buf.build/googleapis/googleapis
plugins:
- plugin: buf.build/grpc/go:v1.3.0
- plugin: buf.build/protocolbuffers/go:v1.31.0
out: gen
opt:
- paths=source_relative
- plugin: buf.build/bufbuild/connect-go:v1.10.0
- plugin: buf.build/grpc/go:v1.3.0
out: gen
opt:
- paths=source_relative
- plugin: buf.build/protocolbuffers/go:v1.31.0
- plugin: buf.build/grpc-ecosystem/gateway:v2.16.1
out: gen
opt:
- paths=source_relative
# Generated by buf. DO NOT EDIT.
version: v1
deps:
- remote: buf.build
owner: googleapis
repository: googleapis
commit: 711e289f6a384c4caeebaff7c6931ade
digest: shake256:e08fb55dad7469f69df00304eed31427d2d1576e9aab31e6bf86642688e04caaf0372f15fe6974cf79432779a635b3ea401ca69c943976dc42749524e4c25d94
- remote: buf.build
owner: grpc-ecosystem
repository: grpc-gateway
commit: fed2dcdcfd694403ad51cd3c94957830
digest: shake256:ed076a21e3d772892fc465ced0e4dd50f9dba86fdd4473920eaa25efa4807644e8e021be423dcfcee74bf4242e7e57422393f9b1abb10acb18ea1a5df509bb19
......@@ -8,3 +8,7 @@ lint:
- DEFAULT
except:
- PACKAGE_DIRECTORY_MATCH
- PACKAGE_VERSION_SUFFIX
deps:
- buf.build/googleapis/googleapis
- buf.build/grpc-ecosystem/grpc-gateway
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: api/v2/tag_service.proto
/*
Package apiv2 is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package apiv2
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
var (
filter_TagService_ListTags_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_TagService_ListTags_0(ctx context.Context, marshaler runtime.Marshaler, client TagServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListTagsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_TagService_ListTags_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListTags(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_TagService_ListTags_0(ctx context.Context, marshaler runtime.Marshaler, server TagServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListTagsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_TagService_ListTags_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListTags(ctx, &protoReq)
return msg, metadata, err
}
// RegisterTagServiceHandlerServer registers the http handlers for service TagService to "mux".
// UnaryRPC :call TagServiceServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterTagServiceHandlerFromEndpoint instead.
func RegisterTagServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server TagServiceServer) error {
mux.Handle("GET", pattern_TagService_ListTags_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v2.TagService/ListTags", runtime.WithHTTPPathPattern("/api/v2/tags"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_TagService_ListTags_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_TagService_ListTags_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterTagServiceHandlerFromEndpoint is same as RegisterTagServiceHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterTagServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterTagServiceHandler(ctx, mux, conn)
}
// RegisterTagServiceHandler registers the http handlers for service TagService to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterTagServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterTagServiceHandlerClient(ctx, mux, NewTagServiceClient(conn))
}
// RegisterTagServiceHandlerClient registers the http handlers for service TagService
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "TagServiceClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "TagServiceClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "TagServiceClient" to call the correct interceptors.
func RegisterTagServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client TagServiceClient) error {
mux.Handle("GET", pattern_TagService_ListTags_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/memos.api.v2.TagService/ListTags", runtime.WithHTTPPathPattern("/api/v2/tags"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_TagService_ListTags_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_TagService_ListTags_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_TagService_ListTags_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v2", "tags"}, ""))
)
var (
forward_TagService_ListTags_0 = runtime.ForwardResponseMessage
)
......@@ -2,7 +2,7 @@
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc (unknown)
// source: apiv2/tag_service.proto
// source: api/v2/tag_service.proto
package apiv2
......@@ -19,7 +19,7 @@ import (
const _ = grpc.SupportPackageIsVersion7
const (
TagService_ListTags_FullMethodName = "/memos.apiv2.TagService/ListTags"
TagService_ListTags_FullMethodName = "/memos.api.v2.TagService/ListTags"
)
// TagServiceClient is the client API for TagService service.
......@@ -96,7 +96,7 @@ func _TagService_ListTags_Handler(srv interface{}, ctx context.Context, dec func
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var TagService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "memos.apiv2.TagService",
ServiceName: "memos.api.v2.TagService",
HandlerType: (*TagServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
......@@ -105,5 +105,5 @@ var TagService_ServiceDesc = grpc.ServiceDesc{
},
},
Streams: []grpc.StreamDesc{},
Metadata: "apiv2/tag_service.proto",
Metadata: "api/v2/tag_service.proto",
}
// Code generated by protoc-gen-connect-go. DO NOT EDIT.
//
// Source: apiv2/tag_service.proto
package apiv2connect
import (
context "context"
errors "errors"
connect_go "github.com/bufbuild/connect-go"
apiv2 "github.com/usememos/memos/proto/gen/apiv2"
http "net/http"
strings "strings"
)
// This is a compile-time assertion to ensure that this generated file and the connect package are
// compatible. If you get a compiler error that this constant is not defined, this code was
// generated with a version of connect newer than the one compiled into your binary. You can fix the
// problem by either regenerating this code with an older version of connect or updating the connect
// version compiled into your binary.
const _ = connect_go.IsAtLeastVersion0_1_0
const (
// TagServiceName is the fully-qualified name of the TagService service.
TagServiceName = "memos.apiv2.TagService"
)
// These constants are the fully-qualified names of the RPCs defined in this package. They're
// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
//
// Note that these are different from the fully-qualified method names used by
// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
// period.
const (
// TagServiceListTagsProcedure is the fully-qualified name of the TagService's ListTags RPC.
TagServiceListTagsProcedure = "/memos.apiv2.TagService/ListTags"
)
// TagServiceClient is a client for the memos.apiv2.TagService service.
type TagServiceClient interface {
ListTags(context.Context, *connect_go.Request[apiv2.ListTagsRequest]) (*connect_go.Response[apiv2.ListTagsResponse], error)
}
// NewTagServiceClient constructs a client for the memos.apiv2.TagService service. By default, it
// uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
// connect.WithGRPCWeb() options.
//
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
// http://api.acme.com or https://acme.com/grpc).
func NewTagServiceClient(httpClient connect_go.HTTPClient, baseURL string, opts ...connect_go.ClientOption) TagServiceClient {
baseURL = strings.TrimRight(baseURL, "/")
return &tagServiceClient{
listTags: connect_go.NewClient[apiv2.ListTagsRequest, apiv2.ListTagsResponse](
httpClient,
baseURL+TagServiceListTagsProcedure,
opts...,
),
}
}
// tagServiceClient implements TagServiceClient.
type tagServiceClient struct {
listTags *connect_go.Client[apiv2.ListTagsRequest, apiv2.ListTagsResponse]
}
// ListTags calls memos.apiv2.TagService.ListTags.
func (c *tagServiceClient) ListTags(ctx context.Context, req *connect_go.Request[apiv2.ListTagsRequest]) (*connect_go.Response[apiv2.ListTagsResponse], error) {
return c.listTags.CallUnary(ctx, req)
}
// TagServiceHandler is an implementation of the memos.apiv2.TagService service.
type TagServiceHandler interface {
ListTags(context.Context, *connect_go.Request[apiv2.ListTagsRequest]) (*connect_go.Response[apiv2.ListTagsResponse], error)
}
// NewTagServiceHandler builds an HTTP handler from the service implementation. It returns the path
// on which to mount the handler and the handler itself.
//
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
// and JSON codecs. They also support gzip compression.
func NewTagServiceHandler(svc TagServiceHandler, opts ...connect_go.HandlerOption) (string, http.Handler) {
tagServiceListTagsHandler := connect_go.NewUnaryHandler(
TagServiceListTagsProcedure,
svc.ListTags,
opts...,
)
return "/memos.apiv2.TagService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case TagServiceListTagsProcedure:
tagServiceListTagsHandler.ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
})
}
// UnimplementedTagServiceHandler returns CodeUnimplemented from all methods.
type UnimplementedTagServiceHandler struct{}
func (UnimplementedTagServiceHandler) ListTags(context.Context, *connect_go.Request[apiv2.ListTagsRequest]) (*connect_go.Response[apiv2.ListTagsResponse], error) {
return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("memos.apiv2.TagService.ListTags is not implemented"))
}
......@@ -4,23 +4,29 @@ import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"time"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/pkg/errors"
apiv1 "github.com/usememos/memos/api/v1"
apiv2 "github.com/usememos/memos/api/v2"
"github.com/usememos/memos/common/log"
"github.com/usememos/memos/common/util"
"github.com/usememos/memos/plugin/telegram"
apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
"github.com/usememos/memos/server/profile"
"github.com/usememos/memos/store"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"go.uber.org/zap"
"google.golang.org/grpc"
)
type Server struct {
e *echo.Echo
e *echo.Echo
grpcServer *grpc.Server
ID string
Secret string
......@@ -94,6 +100,13 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
apiV1Service := apiv1.NewAPIV1Service(s.Secret, profile, store)
apiV1Service.Register(rootGroup)
// Register gPRC server services.
s.grpcServer = grpc.NewServer()
apiv2pb.RegisterTagServiceServer(s.grpcServer, apiv2.NewTagService(store))
// Register gRPC gateway as api v2.
apiv2.RegisterGateway(ctx, e, s.Profile.Port+1)
return s, nil
}
......@@ -105,6 +118,17 @@ func (s *Server) Start(ctx context.Context) error {
go s.telegramBot.Start(ctx)
go autoBackup(ctx, s.Store)
// Start gRPC server.
listen, err := net.Listen("tcp", fmt.Sprintf(":%d", s.Profile.Port+1))
if err != nil {
return err
}
go func() {
if err := s.grpcServer.Serve(listen); err != nil {
log.Error("grpc server listen error", zap.Error(err))
}
}()
return s.e.Start(fmt.Sprintf(":%d", s.Profile.Port))
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment