From 7ac050fccb88bd57707239254e3992f97aff1a6a Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Mon, 14 Oct 2024 05:27:36 +0300 Subject: [PATCH 01/21] chore(jwt): got rid of the conflict of imports --- apps/server/internal/pkg/jwt/jwt.go | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/server/internal/pkg/jwt/jwt.go b/apps/server/internal/pkg/jwt/jwt.go index e1cd77a..456db23 100644 --- a/apps/server/internal/pkg/jwt/jwt.go +++ b/apps/server/internal/pkg/jwt/jwt.go @@ -62,12 +62,15 @@ func NewToken(opts Opts) (token string, err error) { return tokenString, nil } +// Claims wrap jwt.MapClaims to get rid of import conflict +type Claims jwt.MapClaims + // GetPayload undirected call `Validate` method, // so if u use this method, u don't need to call `Validate` again // // Throws: ErrTokenValidation, ErrTokenParse // if token expired throws ErrTokenExpired -func GetPayload(token string, secret string) (*jwt.MapClaims, error) { +func GetPayload(token string, secret string) (*Claims, error) { valid, err := Validate(token, secret) if err != nil { switch { @@ -84,14 +87,19 @@ func GetPayload(token string, secret string) (*jwt.MapClaims, error) { if !ok { return nil, ErrTokenParse } - return &claims, nil + + c := Claims(claims) + return &c, nil } +// Token wrap jwt.Token to get rid of import conflict +type Token *jwt.Token + // Validate provided token using secret // // Throws: ErrTokenParse, ErrTokenValidation, ErrTokenExpired -func Validate(token string, secret string) (*jwt.Token, error) { - parsed, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { +func Validate(given string, secret string) (Token, error) { + parsed, err := jwt.Parse(given, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, ErrTokenSigningValidation } @@ -102,10 +110,12 @@ func Validate(token string, secret string) (*jwt.Token, error) { return nil, ErrTokenParse } + token := Token(parsed) + switch { - case parsed.Valid: - return parsed, nil - case !parsed.Valid: + case token.Valid: + return token, nil + case !token.Valid: return nil, ErrTokenValidation case errors.Is(err, jwt.ErrTokenSignatureInvalid): return nil, ErrTokenParse From 187cdc64088d4d4430003858273e74c1e7332c2a Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Mon, 14 Oct 2024 05:29:28 +0300 Subject: [PATCH 02/21] feat(profile): impl `UploadAvatar` in profile service --- .../internal/repository/user_file/s3.go | 1 - .../repository/user_file/user_file.go | 1 + .../server/internal/service/profile/errors.go | 8 ++ .../internal/service/profile/profile.go | 15 ++++ .../internal/service/profile/service.go | 75 +++++++++++++++++++ 5 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 apps/server/internal/service/profile/errors.go create mode 100644 apps/server/internal/service/profile/profile.go create mode 100644 apps/server/internal/service/profile/service.go diff --git a/apps/server/internal/repository/user_file/s3.go b/apps/server/internal/repository/user_file/s3.go index 001b2e0..68f02e0 100644 --- a/apps/server/internal/repository/user_file/s3.go +++ b/apps/server/internal/repository/user_file/s3.go @@ -30,7 +30,6 @@ func New(opts Opts) *File { } } -// Create loads the file into S3 storage and returns information about the file. func (s File) Create(ctx context.Context, opts CreateUserFileOpts) (*UserFile, error) { file := opts.File filePath := strings.Join([]string{ diff --git a/apps/server/internal/repository/user_file/user_file.go b/apps/server/internal/repository/user_file/user_file.go index 599dd28..d4ad9bc 100644 --- a/apps/server/internal/repository/user_file/user_file.go +++ b/apps/server/internal/repository/user_file/user_file.go @@ -6,6 +6,7 @@ import ( ) type Repository interface { + // Create loads the file into S3 storage and returns information about the file. Create(ctx context.Context, opts CreateUserFileOpts) (*UserFile, error) } diff --git a/apps/server/internal/service/profile/errors.go b/apps/server/internal/service/profile/errors.go new file mode 100644 index 0000000..8e19be1 --- /dev/null +++ b/apps/server/internal/service/profile/errors.go @@ -0,0 +1,8 @@ +package profile + +import "errors" + +var ( + ErrWrongAvatarSize = errors.New("avatar size is larger than the allowed size") + ErrZeroAvatarSize = errors.New("given avatar has zero size") +) diff --git a/apps/server/internal/service/profile/profile.go b/apps/server/internal/service/profile/profile.go new file mode 100644 index 0000000..750fea5 --- /dev/null +++ b/apps/server/internal/service/profile/profile.go @@ -0,0 +1,15 @@ +package profile + +import ( + "bytes" + "context" + "github.com/google/uuid" +) + +type Service interface { + UploadAvatar(ctx context.Context, userID uuid.UUID, opts UploadAvatarOpts) error +} + +type UploadAvatarOpts struct { + avatar bytes.Buffer +} diff --git a/apps/server/internal/service/profile/service.go b/apps/server/internal/service/profile/service.go new file mode 100644 index 0000000..6b318c0 --- /dev/null +++ b/apps/server/internal/service/profile/service.go @@ -0,0 +1,75 @@ +package profile + +import ( + "context" + "github.com/go-faster/errors" + "github.com/google/uuid" + "github.com/taskemapp/server/apps/server/internal/repository/user" + "github.com/taskemapp/server/apps/server/internal/repository/user_file" + "go.uber.org/fx" + "go.uber.org/zap" + "mime" +) + +type Opts struct { + fx.In + UserFileRepo user_file.Repository + UserRepo user.Repository + Logger *zap.Logger +} + +type Profile struct { + userFileRepo user_file.Repository + userRepo user.Repository + logger *zap.Logger +} + +// New creates Profile +func New(opts Opts) *Profile { + return &Profile{ + userFileRepo: opts.UserFileRepo, + userRepo: opts.UserRepo, + logger: opts.Logger, + } +} + +// UploadAvatar for user with specified ID +// +// Maximum avatar size should be no more than 400 kb +func (p *Profile) UploadAvatar(ctx context.Context, userID uuid.UUID, opts UploadAvatarOpts) error { + if opts.avatar.Len() != 0 { + return errors.Wrap(ErrZeroAvatarSize, "upload avatar") + } + + fileSize := 400 + if opts.avatar.Len() != fileSize { + return errors.Wrap(ErrZeroAvatarSize, "upload avatar") + } + + u, err := p.userRepo.FindByID(ctx, userID) + if err != nil { + return errors.Wrap(err, "upload avatar") + } + + p.logger.Info("Upload avatar for user: ", zap.String("name", u.Name)) + + fileName := "avatar.webp" + f, err := p.userFileRepo.Create(ctx, user_file.CreateUserFileOpts{ + UserName: u.Name, + FileName: fileName, + File: opts.avatar, + MimeType: mime.TypeByExtension(".webp"), + }) + if err != nil { + return errors.Wrap(err, "upload avatar") + } + + p.logger.Info("Update avatar url for user: ", zap.String("name", u.Name)) + + _, err = p.userRepo.Update(ctx, userID, user.UpdateOpts{AvatarUrl: &f.CdnPath}) + if err != nil { + return errors.Wrap(err, "upload avatar") + } + + return nil +} From 7ed5328a1037ac4eb53ff4cfc3a7e228c43a6e9b Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Mon, 14 Oct 2024 05:46:40 +0300 Subject: [PATCH 03/21] chore(token): move documentaion for `Repository` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because earlier it was in the wrong place 😶 --- apps/server/internal/repository/token/redis.go | 4 ---- apps/server/internal/repository/token/token.go | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/server/internal/repository/token/redis.go b/apps/server/internal/repository/token/redis.go index 9051b50..b9a5898 100644 --- a/apps/server/internal/repository/token/redis.go +++ b/apps/server/internal/repository/token/redis.go @@ -27,8 +27,6 @@ func NewClient(opts Opts) (*Client, error) { }, nil } -// SetToken store auth token in `in memory storage`, where key defined as -// tokenType:userID func (rc *Client) SetToken(ctx context.Context, opts CreateOpts) error { val, err := rc.client.Set( ctx, @@ -46,8 +44,6 @@ func (rc *Client) SetToken(ctx context.Context, opts CreateOpts) error { return nil } -// GetToken get auth token from `in memory storage`, where key defined as -// tokenType:userID func (rc *Client) GetToken(ctx context.Context, key string) (string, error) { val, err := rc.client.Get(ctx, key).Result() if errors.Is(err, redis.Nil) { diff --git a/apps/server/internal/repository/token/token.go b/apps/server/internal/repository/token/token.go index 4bab92a..9457b13 100644 --- a/apps/server/internal/repository/token/token.go +++ b/apps/server/internal/repository/token/token.go @@ -7,7 +7,11 @@ import ( ) type Repository interface { + // GetToken get auth token from `in memory storage`, where key defined as + // tokenType:userID GetToken(ctx context.Context, key string) (string, error) + // SetToken store auth token in `in memory storage`, where key defined as + // tokenType:userID SetToken(ctx context.Context, opts CreateOpts) error DelToken(ctx context.Context, key string) error } From 980f447a6adbbb3492be36789dd3f6eb9c7065cf Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Mon, 14 Oct 2024 06:08:08 +0300 Subject: [PATCH 04/21] chore(auth): replaces complex get uid with simple `interceptor.GetUserID(ctx)` --- apps/server/internal/grpc/auth/server.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/apps/server/internal/grpc/auth/server.go b/apps/server/internal/grpc/auth/server.go index 42ec32d..afe43af 100644 --- a/apps/server/internal/grpc/auth/server.go +++ b/apps/server/internal/grpc/auth/server.go @@ -3,9 +3,8 @@ package auth import ( "context" "errors" - "github.com/google/uuid" "github.com/taskemapp/server/apps/server/internal/config" - "github.com/taskemapp/server/apps/server/internal/pkg/jwt" + "github.com/taskemapp/server/apps/server/internal/grpc/interceptor" "github.com/taskemapp/server/apps/server/internal/pkg/validation" "github.com/taskemapp/server/apps/server/internal/repository/token" "github.com/taskemapp/server/apps/server/internal/repository/user" @@ -127,16 +126,10 @@ func (s *Server) RefreshToken( ctx context.Context, req *v1.RefreshTokenRequest, ) (*v1.RefreshTokenResponse, error) { - payload, err := jwt.GetPayload(req.Token, s.config.TokenSecret) - if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) - } - - var uid uuid.UUID - uid, err = uuid.Parse((*payload)["uid"].(string)) + uid, err := interceptor.GetUserID(ctx) if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) + return nil, err } resp, err := s.auth.RefreshToken( @@ -148,7 +141,7 @@ func (s *Server) RefreshToken( ) if err != nil { - s.logger.Sugar().Warn(err) + s.logger.Error("refresh token failed", zap.Error(err)) return nil, status.Error(codes.InvalidArgument, err.Error()) } From a7e0d81c9e04c85837dae859e21cf52cfdb5b97e Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Mon, 14 Oct 2024 06:10:17 +0300 Subject: [PATCH 05/21] feat(intercaptor): adds methods that provide different types of id to context --- .../internal/grpc/interceptor/context.go | 57 +++++++++++++++++++ .../internal/grpc/interceptor/errors.go | 8 +++ 2 files changed, 65 insertions(+) create mode 100644 apps/server/internal/grpc/interceptor/context.go create mode 100644 apps/server/internal/grpc/interceptor/errors.go diff --git a/apps/server/internal/grpc/interceptor/context.go b/apps/server/internal/grpc/interceptor/context.go new file mode 100644 index 0000000..83567fd --- /dev/null +++ b/apps/server/internal/grpc/interceptor/context.go @@ -0,0 +1,57 @@ +package interceptor + +import ( + "context" + "errors" + "github.com/google/uuid" + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" + "github.com/taskemapp/server/apps/server/internal/pkg/jwt" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func provideUserID(ctx context.Context, payload jwt.Claims) (context.Context, error) { + uid, err := uuid.Parse(payload["uid"].(string)) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "Wrong token") + } + + ctx = logging.InjectFields( + ctx, + logging.Fields{"user_id", uid.String()}, + ) + + return context.WithValue(ctx, CtxKey{"uid"}, uid), nil +} + +// GetUserID from context, only throws ErrGetUserID if uid not found in context +func GetUserID(ctx context.Context) (uuid.UUID, error) { + if uid, ok := ctx.Value(CtxKey{"uid"}).(uuid.UUID); ok { + return uid, nil + } + + return uuid.Nil, ErrGetUserID +} + +func provideReqID(ctx context.Context) (context.Context, error) { + rid, err := uuid.NewV7() + if err != nil { + return nil, errors.New("failed to generate rid") + } + + ctx = logging.InjectFields( + ctx, + logging.Fields{"request_id", rid.String()}, + ) + + return context.WithValue(ctx, CtxKey{"rid"}, rid), nil +} + +// GetRequestID from context, only throws ErrRequestID if uid not found in context +func GetRequestID(ctx context.Context) (uuid.UUID, error) { + if uid, ok := ctx.Value(CtxKey{"uid"}).(uuid.UUID); ok { + return uid, nil + } + + return uuid.Nil, ErrRequestID +} diff --git a/apps/server/internal/grpc/interceptor/errors.go b/apps/server/internal/grpc/interceptor/errors.go new file mode 100644 index 0000000..2007e73 --- /dev/null +++ b/apps/server/internal/grpc/interceptor/errors.go @@ -0,0 +1,8 @@ +package interceptor + +import "errors" + +var ( + ErrGetUserID = errors.New("failed to get uid from context") + ErrRequestID = errors.New("failed to get rid from context") +) From 9dca80ad9d4a5c5074ff20d6b9152e8cb1587628 Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Mon, 14 Oct 2024 06:12:03 +0300 Subject: [PATCH 06/21] feat(interceptor): adds interceptor which provide request id --- .../internal/grpc/interceptor/request.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 apps/server/internal/grpc/interceptor/request.go diff --git a/apps/server/internal/grpc/interceptor/request.go b/apps/server/internal/grpc/interceptor/request.go new file mode 100644 index 0000000..83d7a6c --- /dev/null +++ b/apps/server/internal/grpc/interceptor/request.go @@ -0,0 +1,19 @@ +package interceptor + +import ( + "context" + "go.uber.org/zap" + "google.golang.org/grpc" +) + +// ProvideRID provide request id for every single request, +// request id need for logging +func (i *Interceptor) ProvideRID() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + ctx, err := provideReqID(ctx) + if err != nil { + i.logger.Warn("Failed to provide request id", zap.Error(err)) + } + return handler(ctx, req) + } +} From 6b2d7b540ac630bc95a48ebe738c99c821a10469 Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Mon, 14 Oct 2024 06:12:32 +0300 Subject: [PATCH 07/21] chore(interceptor): rename package --- .../interceptor.go | 6 +- .../{interceptors => interceptor}/matcher.go | 2 +- .../server/internal/grpc/interceptors/auth.go | 59 ------------------- 3 files changed, 6 insertions(+), 61 deletions(-) rename apps/server/internal/grpc/{interceptors => interceptor}/interceptor.go (80%) rename apps/server/internal/grpc/{interceptors => interceptor}/matcher.go (97%) delete mode 100644 apps/server/internal/grpc/interceptors/auth.go diff --git a/apps/server/internal/grpc/interceptors/interceptor.go b/apps/server/internal/grpc/interceptor/interceptor.go similarity index 80% rename from apps/server/internal/grpc/interceptors/interceptor.go rename to apps/server/internal/grpc/interceptor/interceptor.go index 5f7e83b..c5881d4 100644 --- a/apps/server/internal/grpc/interceptors/interceptor.go +++ b/apps/server/internal/grpc/interceptor/interceptor.go @@ -1,25 +1,29 @@ -package interceptors +package interceptor import ( "github.com/taskemapp/server/apps/server/internal/config" "github.com/taskemapp/server/apps/server/internal/repository/token" "go.uber.org/fx" + "go.uber.org/zap" ) type Opts struct { fx.In Config config.Config TokenRepo token.Repository + Logger zap.Logger } type Interceptor struct { c config.Config tokenRepo token.Repository + logger zap.Logger } func New(opts Opts) *Interceptor { return &Interceptor{ c: opts.Config, tokenRepo: opts.TokenRepo, + logger: opts.Logger, } } diff --git a/apps/server/internal/grpc/interceptors/matcher.go b/apps/server/internal/grpc/interceptor/matcher.go similarity index 97% rename from apps/server/internal/grpc/interceptors/matcher.go rename to apps/server/internal/grpc/interceptor/matcher.go index 2caaf5c..fb4cebf 100644 --- a/apps/server/internal/grpc/interceptors/matcher.go +++ b/apps/server/internal/grpc/interceptor/matcher.go @@ -1,4 +1,4 @@ -package interceptors +package interceptor import ( "context" diff --git a/apps/server/internal/grpc/interceptors/auth.go b/apps/server/internal/grpc/interceptors/auth.go deleted file mode 100644 index 47c2149..0000000 --- a/apps/server/internal/grpc/interceptors/auth.go +++ /dev/null @@ -1,59 +0,0 @@ -package interceptors - -import ( - "context" - "fmt" - "github.com/go-faster/errors" - "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" - "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" - "github.com/taskemapp/server/apps/server/internal/pkg/jwt" - "github.com/taskemapp/server/apps/server/internal/repository/token" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -type TokenKey struct{} -type TokenPayload struct{} - -// Auth get token from grpc request metadata -// -// Already throws formated grpc with status.Errorf -func (i *Interceptor) Auth(ctx context.Context) (context.Context, error) { - tokenMd, err := auth.AuthFromMD(ctx, "bearer") - if err != nil { - return nil, err - } - - claims, err := jwt.GetPayload(tokenMd, i.c.TokenSecret) - - if err != nil { - switch { - case errors.Is(err, jwt.ErrTokenExpired): - return nil, status.Errorf(codes.Unauthenticated, "Token expired") - case errors.Is(err, jwt.ErrTokenParse): - return nil, status.Errorf(codes.InvalidArgument, "Token parse error") - case errors.Is(err, jwt.ErrTokenValidation): - return nil, status.Errorf(codes.Unauthenticated, "Token validation error") - default: - return nil, status.Errorf(codes.Internal, "Internal error") - } - } - - payload := *claims - - _, err = i.tokenRepo.GetToken(ctx, fmt.Sprintf("%s:%s", payload["type"].(string), payload["uid"].(string))) - if err != nil { - switch { - case errors.Is(err, token.ErrNotFound): - return nil, status.Errorf(codes.Unauthenticated, "Wrong token") - default: - return nil, status.Errorf(codes.Internal, "Internal error") - } - } - - ctx = logging.InjectFields(ctx, logging.Fields{"auth.sub", payload}) - ctx = context.WithValue(ctx, TokenKey{}, tokenMd) - ctx = context.WithValue(ctx, TokenPayload{}, payload) - - return ctx, nil -} From e1988b9c7f97fb9478c22b0fc47a1e0c2a6b59b8 Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Mon, 14 Oct 2024 06:13:39 +0300 Subject: [PATCH 08/21] refactor(interceptor): imprv readability --- apps/server/internal/grpc/interceptor/auth.go | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 apps/server/internal/grpc/interceptor/auth.go diff --git a/apps/server/internal/grpc/interceptor/auth.go b/apps/server/internal/grpc/interceptor/auth.go new file mode 100644 index 0000000..3e16192 --- /dev/null +++ b/apps/server/internal/grpc/interceptor/auth.go @@ -0,0 +1,65 @@ +package interceptor + +import ( + "context" + "fmt" + "github.com/go-faster/errors" + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" + "github.com/taskemapp/server/apps/server/internal/pkg/jwt" + "github.com/taskemapp/server/apps/server/internal/repository/token" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type CtxKey struct { + key string +} + +// Auth get token from grpc request metadata +// +// Already throws formated grpc with status.Errorf +func (i *Interceptor) Auth(ctx context.Context) (context.Context, error) { + tokenMd, err := auth.AuthFromMD(ctx, "bearer") + if err != nil { + return nil, err + } + + claims, err := jwt.GetPayload(tokenMd, i.c.TokenSecret) + + ctx, err = matchTokenErr(ctx, err) + if err != nil { + return ctx, err + } + + payload := *claims + + _, err = i.tokenRepo.GetToken(ctx, fmt.Sprintf("%s:%s", payload["type"].(string), payload["uid"].(string))) + if err != nil { + switch { + case errors.Is(err, token.ErrNotFound): + return nil, status.Errorf(codes.InvalidArgument, "Wrong token provided") + default: + return nil, status.Errorf(codes.Internal, "Internal error") + } + } + + ctx, err = provideUserID(ctx, payload) + + return ctx, nil +} + +func matchTokenErr(ctx context.Context, err error) (context.Context, error) { + if err != nil { + switch { + case errors.Is(err, jwt.ErrTokenExpired): + return nil, status.Errorf(codes.Unauthenticated, "Token expired") + case errors.Is(err, jwt.ErrTokenParse): + return nil, status.Errorf(codes.InvalidArgument, "Token parse error") + case errors.Is(err, jwt.ErrTokenValidation): + return nil, status.Errorf(codes.Unauthenticated, "Token validation error") + default: + return nil, status.Errorf(codes.Internal, "Internal error") + } + } + return ctx, nil +} From b3682b7d18df317ab49afbd3035c1427c96c1452 Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Mon, 14 Oct 2024 06:16:31 +0300 Subject: [PATCH 09/21] fix(grpc): import issue --- apps/server/internal/app/grpc/grpc.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/server/internal/app/grpc/grpc.go b/apps/server/internal/app/grpc/grpc.go index 32675b7..ee91600 100644 --- a/apps/server/internal/app/grpc/grpc.go +++ b/apps/server/internal/app/grpc/grpc.go @@ -11,7 +11,7 @@ import ( "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/selector" "github.com/taskemapp/server/apps/server/internal/config" "github.com/taskemapp/server/apps/server/internal/grpc/auth" - "github.com/taskemapp/server/apps/server/internal/grpc/interceptors" + "github.com/taskemapp/server/apps/server/internal/grpc/interceptor" "github.com/taskemapp/server/apps/server/internal/grpc/team" v1 "github.com/taskemapp/server/apps/server/tools/gen/grpc/v1" "go.uber.org/fx" @@ -27,7 +27,7 @@ type Opts struct { AuthServer *auth.Server TeamServer *team.Server Log *zap.Logger - Ic *interceptors.Interceptor + Ic *interceptor.Interceptor } type App struct { @@ -53,6 +53,7 @@ func New(opts Opts) App { srv := grpc.NewServer( grpc.ChainUnaryInterceptor( selector.UnaryServerInterceptor(authMd.UnaryServerInterceptor(opts.Ic.Auth), selector.MatchFunc(opts.Ic.AuthMatcher)), + opts.Ic.ProvideRID(), recovery.UnaryServerInterceptor(recoveryOpts...), logging.UnaryServerInterceptor(interceptorLogger(opts.Log), logOpts...), ), From bf06b05ebe4936d32f9d7d2825b25138bdab252d Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Mon, 14 Oct 2024 06:16:58 +0300 Subject: [PATCH 10/21] fix(app): import issue --- apps/server/internal/app/app.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/server/internal/app/app.go b/apps/server/internal/app/app.go index 9249d44..d448d3c 100644 --- a/apps/server/internal/app/app.go +++ b/apps/server/internal/app/app.go @@ -17,7 +17,7 @@ import ( "github.com/taskemapp/server/apps/server/internal/app/task" "github.com/taskemapp/server/apps/server/internal/app/team" "github.com/taskemapp/server/apps/server/internal/config" - "github.com/taskemapp/server/apps/server/internal/grpc/interceptors" + "github.com/taskemapp/server/apps/server/internal/grpc/interceptor" "github.com/taskemapp/server/apps/server/internal/pkg/migrations" "github.com/taskemapp/server/apps/server/internal/pkg/s3" "github.com/taskemapp/server/libs/queue" @@ -54,7 +54,7 @@ var App = fx.Options( auth.App, team.App, task.App, - fx.Provide(interceptors.New), + fx.Provide(interceptor.New), fx.Provide(grpcsrv.New), fx.Invoke( From 983bac63ce808a95ecf6ddfedc9be8177e245b8c Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Mon, 14 Oct 2024 06:17:39 +0300 Subject: [PATCH 11/21] fix(profile): private field in opts -_- --- apps/server/internal/service/profile/profile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/internal/service/profile/profile.go b/apps/server/internal/service/profile/profile.go index 750fea5..85cf026 100644 --- a/apps/server/internal/service/profile/profile.go +++ b/apps/server/internal/service/profile/profile.go @@ -11,5 +11,5 @@ type Service interface { } type UploadAvatarOpts struct { - avatar bytes.Buffer + Avatar bytes.Buffer } From c5e0a672704ed14ff8c8b74084bbcdc449247f33 Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Mon, 14 Oct 2024 06:22:26 +0300 Subject: [PATCH 12/21] chore(team): replaces complex get uid with simple `interceptor.GetUserID` --- apps/server/internal/grpc/team/server.go | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/apps/server/internal/grpc/team/server.go b/apps/server/internal/grpc/team/server.go index f648c2c..deb74ad 100644 --- a/apps/server/internal/grpc/team/server.go +++ b/apps/server/internal/grpc/team/server.go @@ -2,12 +2,10 @@ package team import ( "context" - "github.com/golang-jwt/jwt/v5" - "github.com/google/uuid" "github.com/jackc/pgx/v5/pgconn" "github.com/pkg/errors" "github.com/taskemapp/server/apps/server/internal/config" - "github.com/taskemapp/server/apps/server/internal/grpc/interceptors" + "github.com/taskemapp/server/apps/server/internal/grpc/interceptor" "github.com/taskemapp/server/apps/server/internal/mapper" "github.com/taskemapp/server/apps/server/internal/repository/token" "github.com/taskemapp/server/apps/server/internal/service" @@ -46,10 +44,7 @@ func New(opts Opts) *Server { } func (t *Server) Get(ctx context.Context, request *v1.GetTeamRequest) (*v1.TeamResponse, error) { - payload := (ctx.Value(interceptors.TokenPayload{})).(jwt.MapClaims) - - var uid uuid.UUID - uid, err := uuid.Parse(payload["uid"].(string)) + uid, err := interceptor.GetUserID(ctx) if err != nil { return nil, err } @@ -62,18 +57,15 @@ func (t *Server) Get(ctx context.Context, request *v1.GetTeamRequest) (*v1.TeamR } func (t *Server) GetUserTeams(ctx context.Context, empty *emptypb.Empty) (*v1.GetAllTeamsResponse, error) { - payload := (ctx.Value(interceptors.TokenPayload{})).(jwt.MapClaims) - - var uid uuid.UUID - uid, err := uuid.Parse(payload["uid"].(string)) + uid, err := interceptor.GetUserID(ctx) if err != nil { return nil, err } - page := payload["page"].(int) + // TODO(ripls56): get page and limit from metadata res, err := t.team.GetUserTeams(ctx, uid, service.PaginationOpts{ - Page: page, + Page: 0, PerPage: 30, }) if err != nil { @@ -88,10 +80,7 @@ func (t *Server) GetAllCanJoin(ctx context.Context, empty *emptypb.Empty) (*v1.G } func (t *Server) Create(ctx context.Context, request *v1.CreateTeamRequest) (*v1.CreateTeamResponse, error) { - payload := (ctx.Value(interceptors.TokenPayload{})).(jwt.MapClaims) - - var uid uuid.UUID - uid, err := uuid.Parse(payload["uid"].(string)) + uid, err := interceptor.GetUserID(ctx) if err != nil { return nil, err } From fa9c0001a7959d6ad12ff1c36abd3094e0b62680 Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Mon, 14 Oct 2024 08:38:38 +0300 Subject: [PATCH 13/21] docs: add some temp readme info --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 43ec704..49fa3b1 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,31 @@ # Server for `taskem` -![codecov graph](https://codecov.io/gh/taskemapp/server/graphs/sunburst.svg?token=13F6QIQ717) +## Development + +### Requirements + +- libvips 8.3+ (8.8+ recommended) +- C compatible compiler such as gcc 4.6+ or clang 3.0+ +- Go 1.3+ + +### Installation + +1. [Download](https://www.msys2.org/) and install MSYS2. +2. After install open MSYS2 UCRT64 +3. Update MSYS2 + ```bash + pacman -Syuu + ``` +4. Install toolchain | gcc | pkg-config | vips + ```bash + pacman -S mingw-w64-ucrt-x86_64-toolchain + pacman -S mingw-w64-ucrt-x86_64-gcc + pacman -S pkg-config + pacman -S mingw-w64-ucrt-x86_64-libvips + ``` + After that add `C:/msys64/ucrt64/bin` to your `PATH` + +## Coverage + +![codecov graph](https://codecov.io/gh/taskemapp/server/graphs/sunburst.svg?token=13F6QIQ717) \ No newline at end of file From 3593f9e59e7a201ed9c410fd80890c66e7ec895f Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Tue, 22 Oct 2024 21:22:10 +0300 Subject: [PATCH 14/21] fix(Dockerfile): now work with libvips --- apps/server/Dockerfile | 86 +++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 56 deletions(-) diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile index 8d669a2..e61e7f3 100644 --- a/apps/server/Dockerfile +++ b/apps/server/Dockerfile @@ -1,84 +1,58 @@ # syntax=docker/dockerfile:1 -# Comments are provided throughout this file to help you get started. -# If you need more help, visit the Dockerfile reference guide at -# https://docs.docker.com/go/dockerfile-reference/ - -# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7 - ################################################################################ -# Create a stage for building the application. ARG GO_VERSION=1.23.0 + FROM --platform=$BUILDPLATFORM golang:${GO_VERSION} AS build WORKDIR /src ARG PROJECT_PATH=apps/server +ARG LIBVIPS_VERSION=8.15.5 LABEL org.opencontainers.image.source="https://github.com/taskemapp/server" -# Download dependencies as a separate step to take advantage of Docker's caching. -# Leverage a cache mount to /go/pkg/mod/ to speed up subsequent builds. -# Leverage bind mounts to go.sum and go.mod to avoid having to copy them into -# the container. RUN --mount=type=bind,source=${PROJECT_PATH}/go.sum,target=go.sum \ --mount=type=bind,source=${PROJECT_PATH}/go.mod,target=go.mod \ go mod download -x +RUN DEBIAN_FRONTEND=noninteractive \ + apt-get update && apt-get install -y \ + build-essential \ + pkg-config \ + libglib2.0-dev \ + libexpat1-dev \ + libjpeg62-turbo-dev \ + libtiff5-dev \ + libarchive-dev \ + meson \ + ninja-build && \ + rm -rf /var/lib/apt/lists/* + +RUN wget https://github.com/libvips/libvips/releases/download/v${LIBVIPS_VERSION}/vips-${LIBVIPS_VERSION}.tar.xz && \ + tar -xf vips-${LIBVIPS_VERSION}.tar.xz && \ + cd vips-${LIBVIPS_VERSION} && \ + meson setup build --prefix /usr/local --libdir lib && \ + meson compile -C build && \ + meson install -C build && \ + ldconfig && \ + cd .. && rm -rf vips-${LIBVIPS_VERSION}* -# This is the architecture you're building for, which is passed in by the builder. -# Placing it here allows the previous steps to be cached across architectures. ARG TARGETARCH -# Build the application. -# Leverage a cache mount to /go/pkg/mod/ to speed up subsequent builds. -# Leverage a bind mount to the current directory to avoid having to copy the -# source code into the container. RUN --mount=type=bind,source=./${PROJECT_PATH}/,target=. \ - CGO_ENABLED=0 GOARCH=$TARGETARCH go build -o /bin/server ./cmd/server + CGO_ENABLED=1 GOARCH=$TARGETARCH go build -o /bin/server ./cmd/server ################################################################################ -# Create a new stage for running the application that contains the minimal -# runtime dependencies for the application. This often uses a different base -# image from the build stage where the necessary files are copied from the build -# stage. -# -# The example below uses the alpine image as the foundation for running the app. -# By specifying the "latest" tag, it will also use whatever happens to be the -# most recent version of that image when you build your Dockerfile. If -# reproducability is important, consider using a versioned tag -# (e.g., alpine:3.17.2) or SHA (e.g., alpine@sha256:c41ab5c992deb4fe7e5da09f67a8804a46bd0592bfdf0b1847dde0e0889d2bff). -FROM alpine:latest AS final - -# Install any runtime dependencies that are needed to run your application. -# Leverage a cache mount to /var/cache/apk/ to speed up subsequent builds. -RUN --mount=type=cache,target=/var/cache/apk \ - apk --update add \ - ca-certificates \ - tzdata \ - && \ - update-ca-certificates - -# Create a non-privileged user that the app will run under. -# See https://docs.docker.com/go/dockerfile-user-best-practices/ -ARG UID=10001 -RUN adduser \ - --disabled-password \ - --gecos "" \ - --home "/nonexistent" \ - --shell "/sbin/nologin" \ - --no-create-home \ - --uid "${UID}" \ - appuser -USER appuser - -# Copy the executable from the "build" stage. +FROM gcr.io/distroless/base-debian12:nonroot AS final + +COPY --from=build /usr/local/lib /usr/local/lib + +ENV LD_LIBRARY_PATH="/usr/local/lib" + COPY --from=build /bin/server /bin/ -# Copy the migrations directory COPY migrations migrations -# Expose the port that the application listens on. EXPOSE 50051 -# What the container should run when it is started. ENTRYPOINT [ "/bin/server" ] From a4a0d22f6df439797e3fef71fc22e5fb869aac8c Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Wed, 23 Oct 2024 02:13:50 +0300 Subject: [PATCH 15/21] fix(Dockerfile): now work with libvips --- apps/server/Dockerfile | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile index e61e7f3..7f7abda 100644 --- a/apps/server/Dockerfile +++ b/apps/server/Dockerfile @@ -1,9 +1,8 @@ # syntax=docker/dockerfile:1 -################################################################################ ARG GO_VERSION=1.23.0 -FROM --platform=$BUILDPLATFORM golang:${GO_VERSION} AS build +FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-bookworm AS build WORKDIR /src ARG PROJECT_PATH=apps/server @@ -20,18 +19,19 @@ RUN DEBIAN_FRONTEND=noninteractive \ build-essential \ pkg-config \ libglib2.0-dev \ - libexpat1-dev \ libjpeg62-turbo-dev \ libtiff5-dev \ - libarchive-dev \ meson \ - ninja-build && \ + ninja-build \ + libwebp-dev \ + libarchive-dev \ + libexpat1-dev && \ rm -rf /var/lib/apt/lists/* RUN wget https://github.com/libvips/libvips/releases/download/v${LIBVIPS_VERSION}/vips-${LIBVIPS_VERSION}.tar.xz && \ tar -xf vips-${LIBVIPS_VERSION}.tar.xz && \ cd vips-${LIBVIPS_VERSION} && \ - meson setup build --prefix /usr/local --libdir lib && \ + meson setup build --prefix /usr/local --buildtype=release && \ meson compile -C build && \ meson install -C build && \ ldconfig && \ @@ -43,10 +43,38 @@ RUN --mount=type=bind,source=./${PROJECT_PATH}/,target=. \ CGO_ENABLED=1 GOARCH=$TARGETARCH go build -o /bin/server ./cmd/server ################################################################################ -FROM gcr.io/distroless/base-debian12:nonroot AS final +FROM debian:bookworm-slim AS final COPY --from=build /usr/local/lib /usr/local/lib +RUN DEBIAN_FRONTEND=noninteractive \ + apt-get update && \ + apt-get install --no-install-recommends -y \ + libglib2.0-0 \ + libjpeg62-turbo \ + libpng16-16 \ + libtiff6 \ + libwebp7 \ + libwebpmux3 \ + libwebpdemux2 \ + libarchive13 \ + libexpat1 && \ + apt-get autoremove -y && \ + apt-get autoclean && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + appuser +USER appuser + ENV LD_LIBRARY_PATH="/usr/local/lib" COPY --from=build /bin/server /bin/ From 009d050ce338eda8e2eee36eeb034cd1d498ca0f Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Wed, 23 Oct 2024 14:33:05 +0300 Subject: [PATCH 16/21] feat(profile): avatar image processing, and upload to s3 --- apps/server/go.mod | 10 ++-- apps/server/go.sum | 12 +++++ apps/server/internal/app/app.go | 5 +- apps/server/internal/app/grpc/grpc.go | 11 ++-- apps/server/internal/app/profile/app.go | 19 +++++++ .../internal/grpc/interceptor/interceptor.go | 4 +- apps/server/internal/grpc/profile/server.go | 51 +++++++++++++++++++ apps/server/internal/pkg/image/convert.go | 19 +++++++ apps/server/internal/pkg/util/contextutil.go | 1 + .../internal/service/profile/profile.go | 3 +- .../internal/service/profile/service.go | 22 +++++--- go.work.sum | 6 +++ protos | 2 +- 13 files changed, 143 insertions(+), 22 deletions(-) create mode 100644 apps/server/internal/app/profile/app.go create mode 100644 apps/server/internal/grpc/profile/server.go create mode 100644 apps/server/internal/pkg/image/convert.go create mode 100644 apps/server/internal/pkg/util/contextutil.go diff --git a/apps/server/go.mod b/apps/server/go.mod index f2006ee..12e07b2 100644 --- a/apps/server/go.mod +++ b/apps/server/go.mod @@ -37,6 +37,7 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/goccy/go-json v0.10.3 // indirect + github.com/h2non/bimg v1.1.9 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect @@ -50,11 +51,12 @@ require ( github.com/rs/xid v1.6.0 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect go.uber.org/dig v1.18.0 // indirect - golang.org/x/crypto v0.26.0 // indirect - golang.org/x/net v0.28.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.30.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/tools v0.26.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/apps/server/go.sum b/apps/server/go.sum index 6d5f419..672297c 100644 --- a/apps/server/go.sum +++ b/apps/server/go.sum @@ -34,6 +34,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= +github.com/h2non/bimg v1.1.9 h1:WH20Nxko9l/HFm4kZCA3Phbgu2cbHvYzxwxn9YROEGg= +github.com/h2non/bimg v1.1.9/go.mod h1:R3+UiYwkK4rQl6KVFTOFJHitgLbZXBZNFh2cv3AEbp8= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -123,6 +125,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -132,6 +136,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -149,6 +155,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -162,10 +170,14 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= diff --git a/apps/server/internal/app/app.go b/apps/server/internal/app/app.go index d448d3c..c23c761 100644 --- a/apps/server/internal/app/app.go +++ b/apps/server/internal/app/app.go @@ -3,8 +3,8 @@ package app import ( "context" "fmt" + "github.com/taskemapp/server/apps/server/internal/app/profile" "github.com/taskemapp/server/apps/server/internal/pkg/notifier" - "github.com/taskemapp/server/apps/server/internal/repository/user_file" "net/url" "github.com/go-redis/redis/v8" @@ -48,11 +48,10 @@ var App = fx.Options( fx.Provide(s3.NewConfig), fx.Provide(s3.New), - fx.Provide(fx.Annotate(user_file.New, fx.As(new(user_file.Repository)))), - //General app auth.App, team.App, + profile.App, task.App, fx.Provide(interceptor.New), fx.Provide(grpcsrv.New), diff --git a/apps/server/internal/app/grpc/grpc.go b/apps/server/internal/app/grpc/grpc.go index ee91600..78a2222 100644 --- a/apps/server/internal/app/grpc/grpc.go +++ b/apps/server/internal/app/grpc/grpc.go @@ -3,6 +3,7 @@ package grpc import ( "context" "fmt" + "github.com/taskemapp/server/apps/server/internal/grpc/profile" "net" authMd "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" @@ -24,10 +25,11 @@ import ( type Opts struct { fx.In - AuthServer *auth.Server - TeamServer *team.Server - Log *zap.Logger - Ic *interceptor.Interceptor + AuthServer *auth.Server + ProfileServer *profile.Server + TeamServer *team.Server + Log *zap.Logger + Ic *interceptor.Interceptor } type App struct { @@ -65,6 +67,7 @@ func New(opts Opts) App { ) v1.RegisterAuthServer(srv, opts.AuthServer) + v1.RegisterProfileServer(srv, opts.ProfileServer) v1.RegisterTeamServer(srv, opts.TeamServer) return App{Srv: srv} diff --git a/apps/server/internal/app/profile/app.go b/apps/server/internal/app/profile/app.go new file mode 100644 index 0000000..dcbf342 --- /dev/null +++ b/apps/server/internal/app/profile/app.go @@ -0,0 +1,19 @@ +package profile + +import ( + profilesrv "github.com/taskemapp/server/apps/server/internal/grpc/profile" + "github.com/taskemapp/server/apps/server/internal/repository/user_file" + "github.com/taskemapp/server/apps/server/internal/service/profile" + "go.uber.org/fx" +) + +var App = fx.Options( + fx.Provide( + fx.Annotate(user_file.New, fx.As(new(user_file.Repository))), + ), + + fx.Provide( + fx.Annotate(profile.New, fx.As(new(profile.Service))), + ), + fx.Provide(profilesrv.New), +) diff --git a/apps/server/internal/grpc/interceptor/interceptor.go b/apps/server/internal/grpc/interceptor/interceptor.go index c5881d4..b76a93e 100644 --- a/apps/server/internal/grpc/interceptor/interceptor.go +++ b/apps/server/internal/grpc/interceptor/interceptor.go @@ -11,13 +11,13 @@ type Opts struct { fx.In Config config.Config TokenRepo token.Repository - Logger zap.Logger + Logger *zap.Logger } type Interceptor struct { c config.Config tokenRepo token.Repository - logger zap.Logger + logger *zap.Logger } func New(opts Opts) *Interceptor { diff --git a/apps/server/internal/grpc/profile/server.go b/apps/server/internal/grpc/profile/server.go new file mode 100644 index 0000000..b1c7ff3 --- /dev/null +++ b/apps/server/internal/grpc/profile/server.go @@ -0,0 +1,51 @@ +package profile + +import ( + "context" + "github.com/taskemapp/server/apps/server/internal/grpc/interceptor" + "github.com/taskemapp/server/apps/server/internal/service/profile" + v1 "github.com/taskemapp/server/apps/server/tools/gen/grpc/v1" + "go.uber.org/fx" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" +) + +type Opts struct { + fx.In + Logger *zap.Logger + Profile profile.Service +} + +func New(opts Opts) *Server { + return &Server{profile: opts.Profile} +} + +type Server struct { + v1.UnimplementedProfileServer + profile profile.Service +} + +func (s *Server) AddOrUpdateAvatar(ctx context.Context, request *v1.AddOrUpdateAvatarRequest) (*emptypb.Empty, error) { + a := request.AvatarImage + + uid, err := interceptor.GetUserID(ctx) + + if err != nil { + return nil, status.Error(codes.Unauthenticated, "Wrong token provided") + } + + err = s.profile.UploadAvatar( + ctx, + uid, + profile.UploadAvatarOpts{ + Avatar: a, + }, + ) + if err != nil { + return nil, err + } + + return nil, err +} diff --git a/apps/server/internal/pkg/image/convert.go b/apps/server/internal/pkg/image/convert.go new file mode 100644 index 0000000..995625b --- /dev/null +++ b/apps/server/internal/pkg/image/convert.go @@ -0,0 +1,19 @@ +package image + +import ( + "github.com/go-faster/errors" + "github.com/h2non/bimg" +) + +func ConvertToWebp(b []byte) (i []byte, err error) { + n := bimg.NewImage(b) + t := n.Type() + if t != "webp" { + i, err = n.Convert(bimg.WEBP) + if err != nil { + return nil, errors.Wrap(err, "convert to webp") + } + } + + return i, nil +} diff --git a/apps/server/internal/pkg/util/contextutil.go b/apps/server/internal/pkg/util/contextutil.go new file mode 100644 index 0000000..c7d8682 --- /dev/null +++ b/apps/server/internal/pkg/util/contextutil.go @@ -0,0 +1 @@ +package util diff --git a/apps/server/internal/service/profile/profile.go b/apps/server/internal/service/profile/profile.go index 85cf026..8f86540 100644 --- a/apps/server/internal/service/profile/profile.go +++ b/apps/server/internal/service/profile/profile.go @@ -1,7 +1,6 @@ package profile import ( - "bytes" "context" "github.com/google/uuid" ) @@ -11,5 +10,5 @@ type Service interface { } type UploadAvatarOpts struct { - Avatar bytes.Buffer + Avatar []byte } diff --git a/apps/server/internal/service/profile/service.go b/apps/server/internal/service/profile/service.go index 6b318c0..2187961 100644 --- a/apps/server/internal/service/profile/service.go +++ b/apps/server/internal/service/profile/service.go @@ -1,9 +1,11 @@ package profile import ( + "bytes" "context" "github.com/go-faster/errors" "github.com/google/uuid" + "github.com/taskemapp/server/apps/server/internal/pkg/image" "github.com/taskemapp/server/apps/server/internal/repository/user" "github.com/taskemapp/server/apps/server/internal/repository/user_file" "go.uber.org/fx" @@ -35,15 +37,21 @@ func New(opts Opts) *Profile { // UploadAvatar for user with specified ID // -// Maximum avatar size should be no more than 400 kb +// Maximum avatar size should be no more than 1 mb func (p *Profile) UploadAvatar(ctx context.Context, userID uuid.UUID, opts UploadAvatarOpts) error { - if opts.avatar.Len() != 0 { + avatar := opts.Avatar + if len(avatar) != 0 { return errors.Wrap(ErrZeroAvatarSize, "upload avatar") } - fileSize := 400 - if opts.avatar.Len() != fileSize { - return errors.Wrap(ErrZeroAvatarSize, "upload avatar") + fileSize := 1024 * 1024 + if len(avatar) != fileSize { + return errors.Wrap(ErrWrongAvatarSize, "upload avatar") + } + + avatar, err := image.ConvertToWebp(avatar) + if err != nil { + return errors.Wrap(err, "upload avatar") } u, err := p.userRepo.FindByID(ctx, userID) @@ -54,10 +62,12 @@ func (p *Profile) UploadAvatar(ctx context.Context, userID uuid.UUID, opts Uploa p.logger.Info("Upload avatar for user: ", zap.String("name", u.Name)) fileName := "avatar.webp" + var buff bytes.Buffer + buff.Write(avatar) f, err := p.userFileRepo.Create(ctx, user_file.CreateUserFileOpts{ UserName: u.Name, FileName: fileName, - File: opts.avatar, + File: buff, MimeType: mime.TypeByExtension(".webp"), }) if err != nil { diff --git a/go.work.sum b/go.work.sum index 3cc1a6f..8e60127 100644 --- a/go.work.sum +++ b/go.work.sum @@ -101,8 +101,14 @@ golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88p golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= +golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= diff --git a/protos b/protos index 0b6b4b8..01f031f 160000 --- a/protos +++ b/protos @@ -1 +1 @@ -Subproject commit 0b6b4b85abf387dcba2dcc8c08db625364a4ef41 +Subproject commit 01f031f91c9f9add2ba37eb2799631b91c00a9aa From c875d801a610eff7e4b559a0fc0de0a47d01b5fd Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Sat, 26 Oct 2024 17:36:21 +0300 Subject: [PATCH 17/21] fix(repository): wrong map values in update --- apps/server/internal/repository/user/pgx.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/server/internal/repository/user/pgx.go b/apps/server/internal/repository/user/pgx.go index 3cb81f8..76f8403 100644 --- a/apps/server/internal/repository/user/pgx.go +++ b/apps/server/internal/repository/user/pgx.go @@ -26,11 +26,11 @@ type Pgx struct { logger *zap.Logger } -func NewPgx(opts Opts) (*Pgx, error) { +func NewPgx(opts Opts) *Pgx { return &Pgx{ pgx: opts.Pgx, logger: opts.Logger, - }, nil + } } var selectUserFields = []string{ @@ -53,11 +53,11 @@ func (p *Pgx) Update(ctx context.Context, userID uuid.UUID, opts UpdateOpts) (*U } if opts.Email != nil { - updateMap["email"] = *opts.DisplayName + updateMap["email"] = *opts.Email } if opts.AvatarUrl != nil { - updateMap["avatar_url"] = *opts.DisplayName + updateMap["avatar_url"] = *opts.AvatarUrl } if opts.IsVerified != nil { From 05351c8bc0d2dc7be83c9af3114b047bff782d81 Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Sat, 26 Oct 2024 17:37:12 +0300 Subject: [PATCH 18/21] fix(s3): didn't use values from cfg --- apps/server/internal/pkg/s3/s3.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/internal/pkg/s3/s3.go b/apps/server/internal/pkg/s3/s3.go index 1179ae4..7043c3a 100644 --- a/apps/server/internal/pkg/s3/s3.go +++ b/apps/server/internal/pkg/s3/s3.go @@ -14,7 +14,7 @@ import ( func New(cfg Config) (*minio.Client, error) { c, err := minio.New(cfg.Host, &minio.Options{ - Creds: credentials.NewStaticV4("root", "password", ""), + Creds: credentials.NewStaticV4(cfg.AccessToken, cfg.SecretToken, ""), Secure: cfg.Secure, }) if err != nil { From d71f6b13c7bfb926eae7c9e67a718fac1b5b2fa2 Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Sat, 26 Oct 2024 17:44:11 +0300 Subject: [PATCH 19/21] chore(intreceptor): change returned err msg --- apps/server/internal/grpc/interceptor/auth.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/server/internal/grpc/interceptor/auth.go b/apps/server/internal/grpc/interceptor/auth.go index 3e16192..3ab7a47 100644 --- a/apps/server/internal/grpc/interceptor/auth.go +++ b/apps/server/internal/grpc/interceptor/auth.go @@ -33,6 +33,7 @@ func (i *Interceptor) Auth(ctx context.Context) (context.Context, error) { payload := *claims + // TODO: переделать на стринг билдер _, err = i.tokenRepo.GetToken(ctx, fmt.Sprintf("%s:%s", payload["type"].(string), payload["uid"].(string))) if err != nil { switch { @@ -54,7 +55,7 @@ func matchTokenErr(ctx context.Context, err error) (context.Context, error) { case errors.Is(err, jwt.ErrTokenExpired): return nil, status.Errorf(codes.Unauthenticated, "Token expired") case errors.Is(err, jwt.ErrTokenParse): - return nil, status.Errorf(codes.InvalidArgument, "Token parse error") + return nil, status.Errorf(codes.InvalidArgument, "Wrong token signature") case errors.Is(err, jwt.ErrTokenValidation): return nil, status.Errorf(codes.Unauthenticated, "Token validation error") default: From f8989b6f8f5a3459dcad62ca8e58c5f1a6d7ea20 Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Sat, 26 Oct 2024 21:46:45 +0300 Subject: [PATCH 20/21] chore(profile): service image processing --- .../internal/service/profile/image/image.go | 6 ++++ .../service/profile/image/processing.go | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 apps/server/internal/service/profile/image/image.go create mode 100644 apps/server/internal/service/profile/image/processing.go diff --git a/apps/server/internal/service/profile/image/image.go b/apps/server/internal/service/profile/image/image.go new file mode 100644 index 0000000..8310cc3 --- /dev/null +++ b/apps/server/internal/service/profile/image/image.go @@ -0,0 +1,6 @@ +package image + +type Processing interface { + // ConvertToWebp from any valid img format + ConvertToWebp(b []byte) error +} diff --git a/apps/server/internal/service/profile/image/processing.go b/apps/server/internal/service/profile/image/processing.go new file mode 100644 index 0000000..2c84a1b --- /dev/null +++ b/apps/server/internal/service/profile/image/processing.go @@ -0,0 +1,29 @@ +package image + +import ( + "github.com/go-faster/errors" + "github.com/h2non/bimg" +) + +type ProfileProcessing struct { +} + +func NewProcessing() *ProfileProcessing { + return &ProfileProcessing{} +} + +// TODO(ripls56): не работает с png, над зависимости глянуть в докере + +func (p *ProfileProcessing) ConvertToWebp(b []byte) error { + n := bimg.NewImage(b) + t := n.Type() + if t != "webp" { + i, err := n.Convert(bimg.WEBP) + if err != nil { + return errors.Wrap(err, "convert to webp") + } + b = i + } + + return nil +} From 21daa49364de4b41e264c23af4fb2610b903f8ee Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Sat, 26 Oct 2024 21:47:45 +0300 Subject: [PATCH 21/21] chore: migrate to `fx.Module` --- apps/server/internal/app/app.go | 9 +--- apps/server/internal/app/grpc/grpc.go | 50 +++++++++++-------- apps/server/internal/app/profile/app.go | 19 ------- apps/server/internal/app/profilefx/app.go | 43 ++++++++++++++++ apps/server/internal/pkg/image/convert.go | 19 ------- .../internal/service/profile/service.go | 24 ++++++--- 6 files changed, 90 insertions(+), 74 deletions(-) delete mode 100644 apps/server/internal/app/profile/app.go create mode 100644 apps/server/internal/app/profilefx/app.go delete mode 100644 apps/server/internal/pkg/image/convert.go diff --git a/apps/server/internal/app/app.go b/apps/server/internal/app/app.go index c23c761..d1a6c9d 100644 --- a/apps/server/internal/app/app.go +++ b/apps/server/internal/app/app.go @@ -3,7 +3,7 @@ package app import ( "context" "fmt" - "github.com/taskemapp/server/apps/server/internal/app/profile" + "github.com/taskemapp/server/apps/server/internal/app/profilefx" "github.com/taskemapp/server/apps/server/internal/pkg/notifier" "net/url" @@ -44,21 +44,16 @@ var App = fx.Options( fx.Provide(queue.NewConfig), fx.Provide(fx.Annotate(queue.NewMQ, fx.As(new(queue.Queue)))), - //S3 - fx.Provide(s3.NewConfig), - fx.Provide(s3.New), - //General app auth.App, team.App, - profile.App, + profilefx.App, task.App, fx.Provide(interceptor.New), fx.Provide(grpcsrv.New), fx.Invoke( migrations.Invoke, - s3.Invoke, grpc.Invoke, ), ) diff --git a/apps/server/internal/app/grpc/grpc.go b/apps/server/internal/app/grpc/grpc.go index 78a2222..51c93f0 100644 --- a/apps/server/internal/app/grpc/grpc.go +++ b/apps/server/internal/app/grpc/grpc.go @@ -3,9 +3,7 @@ package grpc import ( "context" "fmt" - "github.com/taskemapp/server/apps/server/internal/grpc/profile" - "net" - + "github.com/go-faster/errors" authMd "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery" @@ -13,6 +11,7 @@ import ( "github.com/taskemapp/server/apps/server/internal/config" "github.com/taskemapp/server/apps/server/internal/grpc/auth" "github.com/taskemapp/server/apps/server/internal/grpc/interceptor" + "github.com/taskemapp/server/apps/server/internal/grpc/profile" "github.com/taskemapp/server/apps/server/internal/grpc/team" v1 "github.com/taskemapp/server/apps/server/tools/gen/grpc/v1" "go.uber.org/fx" @@ -21,6 +20,8 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/reflection" "google.golang.org/grpc/status" + "net" + "strconv" ) type Opts struct { @@ -33,7 +34,6 @@ type Opts struct { } type App struct { - fx.Out Srv *grpc.Server } @@ -69,37 +69,45 @@ func New(opts Opts) App { v1.RegisterAuthServer(srv, opts.AuthServer) v1.RegisterProfileServer(srv, opts.ProfileServer) v1.RegisterTeamServer(srv, opts.TeamServer) + reflection.Register(srv) return App{Srv: srv} } -func Invoke(lc fx.Lifecycle, log *zap.Logger, c config.Config, srv *grpc.Server) { - lc.Append( - fx.Hook{ - OnStart: func(ctx context.Context) error { - log.Sugar().Infof("Server starting on port %d", c.GrpcPort) +func (a App) Run(c config.Config) error { + l, err := net.Listen("tcp", fmt.Sprintf(":%d", c.GrpcPort)) + if err != nil { + return errors.Wrap(err, "run") + } + if err = a.Srv.Serve(l); err != nil { + return errors.Wrap(err, "run") + } - l, err := net.Listen("tcp", fmt.Sprintf(":%d", c.GrpcPort)) - if err != nil { - return err - } + return nil +} - reflection.Register(srv) +func (a App) GracefulStop() { + a.Srv.GracefulStop() +} +func Invoke(lc fx.Lifecycle, log *zap.Logger, c config.Config, app App) { + lc.Append( + fx.Hook{ + OnStart: func(ctx context.Context) error { go func() { - err = srv.Serve(l) - if err != nil { - log.Error(err.Error()) - return - } + err := app.Run(c) + log.Error("server stopped", zap.Error(err)) }() + log.Info("Server started on port", zap.String("addr", strconv.Itoa(c.GrpcPort))) + return nil }, OnStop: func(ctx context.Context) error { - log.Sugar().Info("Gracefully stopping grpc server") - srv.GracefulStop() + log.Info("Gracefully stopping grpc server") + app.GracefulStop() + log.Info("Server stopped") return nil }, }, diff --git a/apps/server/internal/app/profile/app.go b/apps/server/internal/app/profile/app.go deleted file mode 100644 index dcbf342..0000000 --- a/apps/server/internal/app/profile/app.go +++ /dev/null @@ -1,19 +0,0 @@ -package profile - -import ( - profilesrv "github.com/taskemapp/server/apps/server/internal/grpc/profile" - "github.com/taskemapp/server/apps/server/internal/repository/user_file" - "github.com/taskemapp/server/apps/server/internal/service/profile" - "go.uber.org/fx" -) - -var App = fx.Options( - fx.Provide( - fx.Annotate(user_file.New, fx.As(new(user_file.Repository))), - ), - - fx.Provide( - fx.Annotate(profile.New, fx.As(new(profile.Service))), - ), - fx.Provide(profilesrv.New), -) diff --git a/apps/server/internal/app/profilefx/app.go b/apps/server/internal/app/profilefx/app.go new file mode 100644 index 0000000..6927e6c --- /dev/null +++ b/apps/server/internal/app/profilefx/app.go @@ -0,0 +1,43 @@ +package profilefx + +import ( + profilesrv "github.com/taskemapp/server/apps/server/internal/grpc/profile" + "github.com/taskemapp/server/apps/server/internal/pkg/s3" + "github.com/taskemapp/server/apps/server/internal/repository/user_file" + "github.com/taskemapp/server/apps/server/internal/service/profile" + "github.com/taskemapp/server/apps/server/internal/service/profile/image" + "go.uber.org/fx" + "go.uber.org/zap" +) + +var App = fx.Options( + fx.Module("profile", + fx.Decorate( + func(l *zap.Logger) *zap.Logger { + return l.With(zap.String("scope", "profile")) + }, + ), + + fx.Provide( + fx.Private, + s3.NewConfig, + s3.New, + ), + + fx.Provide( + fx.Private, + //fx.Annotate(user.NewPgx, fx.As(new(user.Repository))), + fx.Annotate(user_file.New, fx.As(new(user_file.Repository))), + fx.Annotate(image.NewProcessing, fx.As(new(image.Processing))), + ), + + fx.Provide( + fx.Private, + fx.Annotate(profile.New, fx.As(new(profile.Service))), + ), + + fx.Invoke(s3.Invoke), + + fx.Provide(profilesrv.New), + ), +) diff --git a/apps/server/internal/pkg/image/convert.go b/apps/server/internal/pkg/image/convert.go deleted file mode 100644 index 995625b..0000000 --- a/apps/server/internal/pkg/image/convert.go +++ /dev/null @@ -1,19 +0,0 @@ -package image - -import ( - "github.com/go-faster/errors" - "github.com/h2non/bimg" -) - -func ConvertToWebp(b []byte) (i []byte, err error) { - n := bimg.NewImage(b) - t := n.Type() - if t != "webp" { - i, err = n.Convert(bimg.WEBP) - if err != nil { - return nil, errors.Wrap(err, "convert to webp") - } - } - - return i, nil -} diff --git a/apps/server/internal/service/profile/service.go b/apps/server/internal/service/profile/service.go index 2187961..1b7f93b 100644 --- a/apps/server/internal/service/profile/service.go +++ b/apps/server/internal/service/profile/service.go @@ -5,9 +5,9 @@ import ( "context" "github.com/go-faster/errors" "github.com/google/uuid" - "github.com/taskemapp/server/apps/server/internal/pkg/image" "github.com/taskemapp/server/apps/server/internal/repository/user" "github.com/taskemapp/server/apps/server/internal/repository/user_file" + "github.com/taskemapp/server/apps/server/internal/service/profile/image" "go.uber.org/fx" "go.uber.org/zap" "mime" @@ -18,20 +18,27 @@ type Opts struct { UserFileRepo user_file.Repository UserRepo user.Repository Logger *zap.Logger + Processing image.Processing } type Profile struct { userFileRepo user_file.Repository userRepo user.Repository logger *zap.Logger + processing image.Processing } // New creates Profile func New(opts Opts) *Profile { + u := opts.UserRepo + if u == nil { + panic("user repo didnt provided") + } return &Profile{ userFileRepo: opts.UserFileRepo, userRepo: opts.UserRepo, logger: opts.Logger, + processing: opts.Processing, } } @@ -40,26 +47,26 @@ func New(opts Opts) *Profile { // Maximum avatar size should be no more than 1 mb func (p *Profile) UploadAvatar(ctx context.Context, userID uuid.UUID, opts UploadAvatarOpts) error { avatar := opts.Avatar - if len(avatar) != 0 { + if len(avatar) == 0 { return errors.Wrap(ErrZeroAvatarSize, "upload avatar") } fileSize := 1024 * 1024 - if len(avatar) != fileSize { + if len(avatar) > fileSize { return errors.Wrap(ErrWrongAvatarSize, "upload avatar") } - avatar, err := image.ConvertToWebp(avatar) + err := p.processing.ConvertToWebp(avatar) if err != nil { return errors.Wrap(err, "upload avatar") } + p.logger.Info("image converted", zap.Int("len", len(avatar))) u, err := p.userRepo.FindByID(ctx, userID) if err != nil { return errors.Wrap(err, "upload avatar") } - - p.logger.Info("Upload avatar for user: ", zap.String("name", u.Name)) + p.logger.Info("user found", zap.Int("len", len(avatar))) fileName := "avatar.webp" var buff bytes.Buffer @@ -73,13 +80,14 @@ func (p *Profile) UploadAvatar(ctx context.Context, userID uuid.UUID, opts Uploa if err != nil { return errors.Wrap(err, "upload avatar") } - - p.logger.Info("Update avatar url for user: ", zap.String("name", u.Name)) + p.logger.Info("avatar uploaded for user") _, err = p.userRepo.Update(ctx, userID, user.UpdateOpts{AvatarUrl: &f.CdnPath}) if err != nil { return errors.Wrap(err, "upload avatar") } + p.logger.Info("avatar url updated") + return nil }