mirror of
https://github.com/aykhans/slash-e.git
synced 2025-04-18 21:19:44 +00:00
feat: implement part of user service
This commit is contained in:
parent
dfe47b9b7e
commit
59a75c89eb
17
api/v2/common.go
Normal file
17
api/v2/common.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
apiv2pb "github.com/boojack/slash/proto/gen/api/v2"
|
||||||
|
"github.com/boojack/slash/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
func convertRowStatusFromStore(rowStatus store.RowStatus) apiv2pb.RowStatus {
|
||||||
|
switch rowStatus {
|
||||||
|
case store.Normal:
|
||||||
|
return apiv2pb.RowStatus_ACTIVE
|
||||||
|
case store.Archived:
|
||||||
|
return apiv2pb.RowStatus_ARCHIVED
|
||||||
|
default:
|
||||||
|
return apiv2pb.RowStatus_ROW_STATUS_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
193
api/v2/jwt.go
Normal file
193
api/v2/jwt.go
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/boojack/slash/api/auth"
|
||||||
|
"github.com/boojack/slash/store"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
var authenticationAllowlistMethods = map[string]bool{
|
||||||
|
"/memos.api.v2.UserService/GetUser": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAuthenticationAllowed returns whether the method is exempted from authentication.
|
||||||
|
func IsAuthenticationAllowed(fullMethodName string) bool {
|
||||||
|
if strings.HasPrefix(fullMethodName, "/grpc.reflection") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return authenticationAllowlistMethods[fullMethodName]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextKey is the key type of context value.
|
||||||
|
type ContextKey int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// The key name used to store user id in the context
|
||||||
|
// user id is extracted from the jwt token subject field.
|
||||||
|
UserIDContextKey ContextKey = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// GRPCAuthInterceptor is the auth interceptor for gRPC server.
|
||||||
|
type GRPCAuthInterceptor struct {
|
||||||
|
store *store.Store
|
||||||
|
secret string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGRPCAuthInterceptor returns a new API auth interceptor.
|
||||||
|
func NewGRPCAuthInterceptor(store *store.Store, secret string) *GRPCAuthInterceptor {
|
||||||
|
return &GRPCAuthInterceptor{
|
||||||
|
store: store,
|
||||||
|
secret: secret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthenticationInterceptor is the unary interceptor for gRPC API.
|
||||||
|
func (in *GRPCAuthInterceptor) AuthenticationInterceptor(ctx context.Context, request any, serverInfo *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||||
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, status.Errorf(codes.Unauthenticated, "failed to parse metadata from incoming context")
|
||||||
|
}
|
||||||
|
accessTokenStr, err := getTokenFromMetadata(md)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Unauthenticated, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, err := in.authenticate(ctx, accessTokenStr)
|
||||||
|
if err != nil {
|
||||||
|
if IsAuthenticationAllowed(serverInfo.FullMethod) {
|
||||||
|
return handler(ctx, request)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stores userID into context.
|
||||||
|
childCtx := context.WithValue(ctx, UserIDContextKey, userID)
|
||||||
|
return handler(childCtx, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *GRPCAuthInterceptor) authenticate(ctx context.Context, accessTokenStr string) (int, error) {
|
||||||
|
if accessTokenStr == "" {
|
||||||
|
return 0, status.Errorf(codes.Unauthenticated, "access token not found")
|
||||||
|
}
|
||||||
|
claims := &claimsMessage{}
|
||||||
|
_, err := jwt.ParseWithClaims(accessTokenStr, claims, func(t *jwt.Token) (any, error) {
|
||||||
|
if t.Method.Alg() != jwt.SigningMethodHS256.Name {
|
||||||
|
return nil, status.Errorf(codes.Unauthenticated, "unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
|
||||||
|
}
|
||||||
|
if kid, ok := t.Header["kid"].(string); ok {
|
||||||
|
if kid == "v1" {
|
||||||
|
return []byte(in.secret), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.Unauthenticated, "unexpected access token kid=%v", t.Header["kid"])
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, status.Errorf(codes.Unauthenticated, "Invalid or expired access token")
|
||||||
|
}
|
||||||
|
if !audienceContains(claims.Audience, auth.AccessTokenAudienceName) {
|
||||||
|
return 0, status.Errorf(codes.Unauthenticated,
|
||||||
|
"invalid access token, audience mismatch, got %q, expected %q. you may send request to the wrong environment",
|
||||||
|
claims.Audience,
|
||||||
|
auth.AccessTokenAudienceName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, err := strconv.Atoi(claims.Subject)
|
||||||
|
if err != nil {
|
||||||
|
return 0, status.Errorf(codes.Unauthenticated, "malformed ID %q in the access token", claims.Subject)
|
||||||
|
}
|
||||||
|
user, err := in.store.GetUser(ctx, &store.FindUser{
|
||||||
|
ID: &userID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, status.Errorf(codes.Unauthenticated, "failed to find user ID %q in the access token", userID)
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
return 0, status.Errorf(codes.Unauthenticated, "user ID %q not exists in the access token", userID)
|
||||||
|
}
|
||||||
|
if user.RowStatus == store.Archived {
|
||||||
|
return 0, status.Errorf(codes.Unauthenticated, "user ID %q has been deactivated by administrators", userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return userID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTokenFromMetadata(md metadata.MD) (string, error) {
|
||||||
|
authorizationHeaders := md.Get("Authorization")
|
||||||
|
if len(md.Get("Authorization")) > 0 {
|
||||||
|
authHeaderParts := strings.Fields(authorizationHeaders[0])
|
||||||
|
if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
|
||||||
|
return "", errors.Errorf("authorization header format must be Bearer {token}")
|
||||||
|
}
|
||||||
|
return authHeaderParts[1], nil
|
||||||
|
}
|
||||||
|
// check the HTTP cookie
|
||||||
|
var accessToken string
|
||||||
|
for _, t := range append(md.Get("grpcgateway-cookie"), md.Get("cookie")...) {
|
||||||
|
header := http.Header{}
|
||||||
|
header.Add("Cookie", t)
|
||||||
|
request := http.Request{Header: header}
|
||||||
|
if v, _ := request.Cookie(auth.AccessTokenCookieName); v != nil {
|
||||||
|
accessToken = v.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return accessToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func audienceContains(audience jwt.ClaimStrings, token string) bool {
|
||||||
|
for _, v := range audience {
|
||||||
|
if v == token {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type claimsMessage struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateAccessToken generates an access token for web.
|
||||||
|
func GenerateAccessToken(username string, userID int, secret string) (string, error) {
|
||||||
|
expirationTime := time.Now().Add(auth.AccessTokenDuration)
|
||||||
|
return generateToken(username, userID, auth.AccessTokenAudienceName, expirationTime, []byte(secret))
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateToken(username string, userID int, aud string, expirationTime time.Time, secret []byte) (string, error) {
|
||||||
|
// Create the JWT claims, which includes the username and expiry time.
|
||||||
|
claims := &claimsMessage{
|
||||||
|
Name: username,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
Audience: jwt.ClaimStrings{aud},
|
||||||
|
// In JWT, the expiry time is expressed as unix milliseconds.
|
||||||
|
ExpiresAt: jwt.NewNumericDate(expirationTime),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
Issuer: auth.Issuer,
|
||||||
|
Subject: strconv.Itoa(userID),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare the token with the HS256 algorithm used for signing, and the claims.
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
token.Header["kid"] = auth.KeyID
|
||||||
|
|
||||||
|
// Create the JWT string.
|
||||||
|
tokenString, err := token.SignedString(secret)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenString, nil
|
||||||
|
}
|
65
api/v2/user_service.go
Normal file
65
api/v2/user_service.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
apiv2pb "github.com/boojack/slash/proto/gen/api/v2"
|
||||||
|
"github.com/boojack/slash/store"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserService struct {
|
||||||
|
apiv2pb.UnimplementedUserServiceServer
|
||||||
|
|
||||||
|
Store *store.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserService creates a new UserService.
|
||||||
|
func NewUserService(store *store.Store) *UserService {
|
||||||
|
return &UserService{
|
||||||
|
Store: store,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserService) GetUser(ctx context.Context, request *apiv2pb.GetUserRequest) (*apiv2pb.GetUserResponse, error) {
|
||||||
|
id := int(request.Id)
|
||||||
|
user, err := s.Store.GetUser(ctx, &store.FindUser{
|
||||||
|
ID: &id,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "failed to list tags: %v", err)
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
userMessage := convertUserFromStore(user)
|
||||||
|
response := &apiv2pb.GetUserResponse{
|
||||||
|
User: userMessage,
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertUserFromStore(user *store.User) *apiv2pb.User {
|
||||||
|
return &apiv2pb.User{
|
||||||
|
Id: int32(user.ID),
|
||||||
|
RowStatus: convertRowStatusFromStore(user.RowStatus),
|
||||||
|
CreatedTs: user.CreatedTs,
|
||||||
|
UpdatedTs: user.UpdatedTs,
|
||||||
|
Role: convertUserRoleFromStore(user.Role),
|
||||||
|
Email: user.Email,
|
||||||
|
Nickname: user.Nickname,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertUserRoleFromStore(role store.Role) apiv2pb.Role {
|
||||||
|
switch role {
|
||||||
|
case store.RoleAdmin:
|
||||||
|
return apiv2pb.Role_ADMIN
|
||||||
|
case store.RoleUser:
|
||||||
|
return apiv2pb.Role_USER
|
||||||
|
default:
|
||||||
|
return apiv2pb.Role_ROLE_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
67
api/v2/v2.go
Normal file
67
api/v2/v2.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
apiv2pb "github.com/boojack/slash/proto/gen/api/v2"
|
||||||
|
"github.com/boojack/slash/server/profile"
|
||||||
|
"github.com/boojack/slash/store"
|
||||||
|
grpcRuntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
type APIV2Service struct {
|
||||||
|
Secret string
|
||||||
|
Profile *profile.Profile
|
||||||
|
Store *store.Store
|
||||||
|
|
||||||
|
grpcServer *grpc.Server
|
||||||
|
grpcServerPort int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAPIV2Service(secret string, profile *profile.Profile, store *store.Store, grpcServerPort int) *APIV2Service {
|
||||||
|
authProvider := NewGRPCAuthInterceptor(store, secret)
|
||||||
|
grpcServer := grpc.NewServer(
|
||||||
|
grpc.ChainUnaryInterceptor(
|
||||||
|
authProvider.AuthenticationInterceptor,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
apiv2pb.RegisterUserServiceServer(grpcServer, NewUserService(store))
|
||||||
|
|
||||||
|
return &APIV2Service{
|
||||||
|
Secret: secret,
|
||||||
|
Profile: profile,
|
||||||
|
Store: store,
|
||||||
|
grpcServer: grpcServer,
|
||||||
|
grpcServerPort: grpcServerPort,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *APIV2Service) GetGRPCServer() *grpc.Server {
|
||||||
|
return s.grpcServer
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterGateway registers the gRPC-Gateway with the given Echo instance.
|
||||||
|
func (s *APIV2Service) RegisterGateway(ctx context.Context, e *echo.Echo) error {
|
||||||
|
// 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", s.grpcServerPort),
|
||||||
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gwMux := grpcRuntime.NewServeMux()
|
||||||
|
if err := apiv2pb.RegisterUserServiceHandler(context.Background(), gwMux, conn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.Any("/api/v2/*", echo.WrapHandler(gwMux))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -10,8 +10,8 @@ option go_package = "gen/api/v2";
|
|||||||
|
|
||||||
service UserService {
|
service UserService {
|
||||||
rpc GetUser(GetUserRequest) returns (GetUserResponse) {
|
rpc GetUser(GetUserRequest) returns (GetUserResponse) {
|
||||||
option (google.api.http) = {get: "/api/v2/users/{email}"};
|
option (google.api.http) = {get: "/api/v2/users/{id}"};
|
||||||
option (google.api.method_signature) = "email";
|
option (google.api.method_signature) = "id";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ enum Role {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message GetUserRequest {
|
message GetUserRequest {
|
||||||
string email = 1;
|
int32 id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetUserResponse {
|
message GetUserResponse {
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
|
|
||||||
| Field | Type | Label | Description |
|
| Field | Type | Label | Description |
|
||||||
| ----- | ---- | ----- | ----------- |
|
| ----- | ---- | ----- | ----------- |
|
||||||
| email | [string](#string) | | |
|
| id | [int32](#int32) | | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ type GetUserRequest struct {
|
|||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"`
|
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetUserRequest) Reset() {
|
func (x *GetUserRequest) Reset() {
|
||||||
@ -205,11 +205,11 @@ func (*GetUserRequest) Descriptor() ([]byte, []int) {
|
|||||||
return file_api_v2_user_service_proto_rawDescGZIP(), []int{1}
|
return file_api_v2_user_service_proto_rawDescGZIP(), []int{1}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetUserRequest) GetEmail() string {
|
func (x *GetUserRequest) GetId() int32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Email
|
return x.Id
|
||||||
}
|
}
|
||||||
return ""
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUserResponse struct {
|
type GetUserResponse struct {
|
||||||
@ -283,36 +283,35 @@ var file_api_v2_user_service_proto_rawDesc = []byte{
|
|||||||
0x32, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05,
|
0x32, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05,
|
||||||
0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61,
|
0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61,
|
||||||
0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08,
|
0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x26,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x20,
|
||||||
0x0a, 0x0e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
0x0a, 0x0e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||||
0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64,
|
||||||
0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x39, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65,
|
0x22, 0x39, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||||
0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x75, 0x73, 0x65,
|
0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e,
|
0x0b, 0x32, 0x12, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32,
|
||||||
0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65,
|
0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x2a, 0x31, 0x0a, 0x04, 0x52,
|
||||||
0x72, 0x2a, 0x31, 0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x4f, 0x4c,
|
0x6f, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x4f, 0x4c, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50,
|
||||||
0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12,
|
0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x44, 0x4d,
|
||||||
0x09, 0x0a, 0x05, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x55, 0x53,
|
0x49, 0x4e, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x55, 0x53, 0x45, 0x52, 0x10, 0x02, 0x32, 0x76,
|
||||||
0x45, 0x52, 0x10, 0x02, 0x32, 0x7c, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76,
|
0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x67, 0x0a,
|
||||||
0x69, 0x63, 0x65, 0x12, 0x6d, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c,
|
0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68,
|
||||||
0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65,
|
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52,
|
||||||
0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x73,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61,
|
||||||
0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x55,
|
0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73,
|
||||||
0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0xda, 0x41, 0x05,
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0xda, 0x41, 0x02, 0x69, 0x64, 0x82, 0xd3, 0xe4, 0x93,
|
||||||
0x65, 0x6d, 0x61, 0x69, 0x6c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x61, 0x70,
|
0x02, 0x14, 0x12, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x75, 0x73, 0x65, 0x72,
|
||||||
0x69, 0x2f, 0x76, 0x32, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x65, 0x6d, 0x61, 0x69,
|
0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x42, 0xa7, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x73,
|
||||||
0x6c, 0x7d, 0x42, 0xa7, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68,
|
0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x42, 0x10, 0x55, 0x73, 0x65,
|
||||||
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x42, 0x10, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72,
|
0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a,
|
||||||
0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74,
|
0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6f, 0x6f, 0x6a,
|
||||||
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6f, 0x6f, 0x6a, 0x61, 0x63, 0x6b, 0x2f,
|
0x61, 0x63, 0x6b, 0x2f, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
|
||||||
0x73, 0x6c, 0x61, 0x73, 0x68, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f,
|
0x67, 0x65, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x3b, 0x61, 0x70, 0x69, 0x76, 0x32,
|
||||||
0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x3b, 0x61, 0x70, 0x69, 0x76, 0x32, 0xa2, 0x02, 0x03, 0x53,
|
0xa2, 0x02, 0x03, 0x53, 0x41, 0x58, 0xaa, 0x02, 0x0c, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x41,
|
||||||
0x41, 0x58, 0xaa, 0x02, 0x0c, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x56,
|
0x70, 0x69, 0x2e, 0x56, 0x32, 0xca, 0x02, 0x0c, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x5c, 0x41, 0x70,
|
||||||
0x32, 0xca, 0x02, 0x0c, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x32,
|
0x69, 0x5c, 0x56, 0x32, 0xe2, 0x02, 0x18, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x5c, 0x41, 0x70, 0x69,
|
||||||
0xe2, 0x02, 0x18, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x32, 0x5c,
|
0x5c, 0x56, 0x32, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea,
|
||||||
0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0e, 0x53, 0x6c,
|
0x02, 0x0e, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x56, 0x32,
|
||||||
0x61, 0x73, 0x68, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x56, 0x32, 0x62, 0x06, 0x70, 0x72,
|
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -42,14 +42,14 @@ func request_UserService_GetUser_0(ctx context.Context, marshaler runtime.Marsha
|
|||||||
_ = err
|
_ = err
|
||||||
)
|
)
|
||||||
|
|
||||||
val, ok = pathParams["email"]
|
val, ok = pathParams["id"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "email")
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||||
}
|
}
|
||||||
|
|
||||||
protoReq.Email, err = runtime.String(val)
|
protoReq.Id, err = runtime.Int32(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "email", err)
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := client.GetUser(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
msg, err := client.GetUser(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
@ -68,14 +68,14 @@ func local_request_UserService_GetUser_0(ctx context.Context, marshaler runtime.
|
|||||||
_ = err
|
_ = err
|
||||||
)
|
)
|
||||||
|
|
||||||
val, ok = pathParams["email"]
|
val, ok = pathParams["id"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "email")
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||||
}
|
}
|
||||||
|
|
||||||
protoReq.Email, err = runtime.String(val)
|
protoReq.Id, err = runtime.Int32(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "email", err)
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := server.GetUser(ctx, &protoReq)
|
msg, err := server.GetUser(ctx, &protoReq)
|
||||||
@ -97,7 +97,7 @@ func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
|
|||||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
var err error
|
var err error
|
||||||
var annotatedContext context.Context
|
var annotatedContext context.Context
|
||||||
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/slash.api.v2.UserService/GetUser", runtime.WithHTTPPathPattern("/api/v2/users/{email}"))
|
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/slash.api.v2.UserService/GetUser", runtime.WithHTTPPathPattern("/api/v2/users/{id}"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
return
|
return
|
||||||
@ -161,7 +161,7 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
|
|||||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
var err error
|
var err error
|
||||||
var annotatedContext context.Context
|
var annotatedContext context.Context
|
||||||
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/slash.api.v2.UserService/GetUser", runtime.WithHTTPPathPattern("/api/v2/users/{email}"))
|
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/slash.api.v2.UserService/GetUser", runtime.WithHTTPPathPattern("/api/v2/users/{id}"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
return
|
return
|
||||||
@ -181,7 +181,7 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
pattern_UserService_GetUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v2", "users", "email"}, ""))
|
pattern_UserService_GetUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v2", "users", "id"}, ""))
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -3,13 +3,14 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
apiv1 "github.com/boojack/slash/api/v1"
|
apiv1 "github.com/boojack/slash/api/v1"
|
||||||
|
apiv2 "github.com/boojack/slash/api/v2"
|
||||||
"github.com/boojack/slash/server/profile"
|
"github.com/boojack/slash/server/profile"
|
||||||
"github.com/boojack/slash/store"
|
"github.com/boojack/slash/store"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"github.com/labstack/echo/v4/middleware"
|
||||||
)
|
)
|
||||||
@ -19,6 +20,9 @@ type Server struct {
|
|||||||
|
|
||||||
Profile *profile.Profile
|
Profile *profile.Profile
|
||||||
Store *store.Store
|
Store *store.Store
|
||||||
|
|
||||||
|
// API services.
|
||||||
|
apiV2Service *apiv2.APIV2Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store) (*Server, error) {
|
func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store) (*Server, error) {
|
||||||
@ -66,10 +70,27 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
|
|||||||
apiV1Service := apiv1.NewAPIV1Service(profile, store)
|
apiV1Service := apiv1.NewAPIV1Service(profile, store)
|
||||||
apiV1Service.Start(rootGroup, secret)
|
apiV1Service.Start(rootGroup, secret)
|
||||||
|
|
||||||
|
s.apiV2Service = apiv2.NewAPIV2Service(secret, profile, store, s.Profile.Port+1)
|
||||||
|
// Register gRPC gateway as api v2.
|
||||||
|
if err := s.apiV2Service.RegisterGateway(ctx, e); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to register gRPC gateway: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Start(_ context.Context) error {
|
func (s *Server) Start(_ context.Context) error {
|
||||||
|
// 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.apiV2Service.GetGRPCServer().Serve(listen); err != nil {
|
||||||
|
println("grpc server listen error")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return s.e.Start(fmt.Sprintf(":%d", s.Profile.Port))
|
return s.e.Start(fmt.Sprintf(":%d", s.Profile.Port))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user