diff --git a/cmd/server/main.go b/cmd/server/main.go index 4a216fc5..2257c9bd 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -61,7 +61,6 @@ func setGlobalLogger(debug bool) { config.Encoding = "json" } else { config = zap.NewProductionConfig() - config.DisableStacktrace = true } logger, _ := config.Build() diff --git a/internal/pkg/github/deployment.go b/internal/pkg/github/deployment.go index 5a5529b0..0f52e09c 100644 --- a/internal/pkg/github/deployment.go +++ b/internal/pkg/github/deployment.go @@ -75,8 +75,9 @@ func (g *Github) GetConfig(ctx context.Context, u *ent.User, r *ent.Repo) (*vo.C Repositories. GetContents(ctx, r.Namespace, r.Name, r.ConfigPath, &github.RepositoryContentGetOptions{}) if res.StatusCode == http.StatusNotFound { - return nil, e.NewError( - e.ErrorCodeConfigNotFound, + return nil, e.NewErrorWithMessage( + e.ErrorCodeNotFound, + "The configuration file is not found.", err, ) } else if err != nil { diff --git a/internal/pkg/github/repos.go b/internal/pkg/github/repos.go index 6382a3e9..7419c055 100644 --- a/internal/pkg/github/repos.go +++ b/internal/pkg/github/repos.go @@ -73,15 +73,9 @@ func (g *Github) GetCommit(ctx context.Context, u *ent.User, r *ent.Repo, sha st GetCommit(ctx, r.Namespace, r.Name, sha) // Github returns Unprocessable entity if the commit is not found. if res.StatusCode == http.StatusNotFound || res.StatusCode == http.StatusUnprocessableEntity { - return nil, e.NewError( - e.ErrorCodeRefNotFound, - err, - ) + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The commit is not found.", err) } else if err != nil { - return nil, e.NewError( - e.ErrorCodeInternalError, - err, - ) + return nil, e.NewError(e.ErrorCodeInternalError, err) } return mapGithubCommitToCommit(cm), nil @@ -97,10 +91,7 @@ func (g *Github) ListCommitStatuses(ctx context.Context, u *ent.User, r *ent.Rep PerPage: 100, }) if err != nil { - return nil, e.NewError( - e.ErrorCodeInternalError, - err, - ) + return nil, err } for _, rs := range cs.Statuses { @@ -115,10 +106,7 @@ func (g *Github) ListCommitStatuses(ctx context.Context, u *ent.User, r *ent.Rep }) // check-runs secures the commit is exist. if res.StatusCode == http.StatusUnprocessableEntity { - return nil, e.NewError( - e.ErrorCodeRefNotFound, - err, - ) + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The commit is not found.", err) } else if err != nil { return nil, e.NewError( e.ErrorCodeInternalError, @@ -143,10 +131,7 @@ func (g *Github) ListBranches(ctx context.Context, u *ent.User, r *ent.Repo, pag }, }) if err != nil { - return nil, e.NewError( - e.ErrorCodeInternalError, - err, - ) + return nil, e.NewError(e.ErrorCodeInternalError, err) } branches := []*vo.Branch{} @@ -162,15 +147,9 @@ func (g *Github) GetBranch(ctx context.Context, u *ent.User, r *ent.Repo, branch Repositories. GetBranch(ctx, r.Namespace, r.Name, branch) if res.StatusCode == http.StatusNotFound { - return nil, e.NewError( - e.ErrorCodeRefNotFound, - err, - ) + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The branch is not found.", err) } else if err != nil { - return nil, e.NewError( - e.ErrorCodeInternalError, - err, - ) + return nil, e.NewError(e.ErrorCodeInternalError, err) } return mapGithubBranchToBranch(b), nil @@ -249,10 +228,7 @@ func (g *Github) GetTag(ctx context.Context, u *ent.User, r *ent.Repo, tag strin } if q.Repository.Refs.TotalCount == 0 { - return nil, e.NewError( - e.ErrorCodeRefNotFound, - nil, - ) + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The tag is not found.", nil) } n := q.Repository.Refs.Nodes[0] @@ -278,7 +254,7 @@ func (g *Github) CreateWebhook(ctx context.Context, u *ent.User, r *ent.Repo, c Active: github.Bool(true), }) if err != nil { - return -1, err + return -1, e.NewErrorWithMessage(e.ErrorCodeInternalError, "It has failed to create a webhook.", err) } return *h.ID, nil diff --git a/internal/pkg/github/sync.go b/internal/pkg/github/sync.go index b21501e3..efad4d2e 100644 --- a/internal/pkg/github/sync.go +++ b/internal/pkg/github/sync.go @@ -4,6 +4,7 @@ import ( "context" "github.com/gitploy-io/gitploy/ent" + "github.com/gitploy-io/gitploy/pkg/e" "github.com/gitploy-io/gitploy/vo" "github.com/google/go-github/v32/github" ) @@ -11,7 +12,7 @@ import ( func (g *Github) ListRemoteRepos(ctx context.Context, u *ent.User) ([]*vo.RemoteRepo, error) { grs, err := g.listRemoteRepos(ctx, u) if err != nil { - return nil, err + return nil, e.NewError(e.ErrorCodeInternalError, err) } remotes := make([]*vo.RemoteRepo, 0) diff --git a/internal/pkg/store/approval.go b/internal/pkg/store/approval.go index 9eb21611..1a8532f1 100644 --- a/internal/pkg/store/approval.go +++ b/internal/pkg/store/approval.go @@ -2,12 +2,14 @@ package store import ( "context" + "fmt" "time" "entgo.io/ent/dialect/sql" "github.com/gitploy-io/gitploy/ent" "github.com/gitploy-io/gitploy/ent/approval" "github.com/gitploy-io/gitploy/ent/predicate" + "github.com/gitploy-io/gitploy/pkg/e" ) func (s *Store) SearchApprovals(ctx context.Context, u *ent.User, ss []approval.Status, from time.Time, to time.Time, page, perPage int) ([]*ent.Approval, error) { @@ -59,7 +61,7 @@ func (s *Store) ListApprovals(ctx context.Context, d *ent.Deployment) ([]*ent.Ap } func (s *Store) FindApprovalByID(ctx context.Context, id int) (*ent.Approval, error) { - return s.c.Approval. + ap, err := s.c.Approval. Query(). Where( approval.IDEQ(id), @@ -71,10 +73,17 @@ func (s *Store) FindApprovalByID(ctx context.Context, id int) (*ent.Approval, er WithUser() }). First(ctx) + if ent.IsNotFound(err) { + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The approval is not found.", err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return ap, nil } func (s *Store) FindApprovalOfUser(ctx context.Context, d *ent.Deployment, u *ent.User) (*ent.Approval, error) { - return s.c.Approval. + ap, err := s.c.Approval. Query(). Where( approval.And( @@ -89,21 +98,48 @@ func (s *Store) FindApprovalOfUser(ctx context.Context, d *ent.Deployment, u *en WithUser() }). First(ctx) + if ent.IsNotFound(err) { + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The user's approval is not found.", err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return ap, nil } func (s *Store) CreateApproval(ctx context.Context, a *ent.Approval) (*ent.Approval, error) { - return s.c.Approval. + ap, err := s.c.Approval. Create(). SetUserID(a.UserID). SetDeploymentID(a.DeploymentID). Save(ctx) + if ent.IsValidationError(err) { + return nil, e.NewErrorWithMessage( + e.ErrorCodeUnprocessableEntity, + fmt.Sprintf("Failed to create a approval. The value of \"%s\" field is invalid.", err.(*ent.ValidationError).Name), + err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return ap, nil } func (s *Store) UpdateApproval(ctx context.Context, a *ent.Approval) (*ent.Approval, error) { - return s.c.Approval. + ap, err := s.c.Approval. UpdateOne(a). SetStatus(a.Status). Save(ctx) + if ent.IsValidationError(err) { + return nil, e.NewErrorWithMessage( + e.ErrorCodeUnprocessableEntity, + fmt.Sprintf("Failed to update the approval. The value of \"%s\" field is invalid.", err.(*ent.ValidationError).Name), + err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return ap, nil } func (s *Store) DeleteApproval(ctx context.Context, a *ent.Approval) error { diff --git a/internal/pkg/store/approval_test.go b/internal/pkg/store/approval_test.go index c1204461..45a41f0c 100644 --- a/internal/pkg/store/approval_test.go +++ b/internal/pkg/store/approval_test.go @@ -9,6 +9,7 @@ import ( "github.com/gitploy-io/gitploy/ent/approval" "github.com/gitploy-io/gitploy/ent/enttest" "github.com/gitploy-io/gitploy/ent/migrate" + "github.com/gitploy-io/gitploy/pkg/e" ) func TestStore_SearchApprovals(t *testing.T) { @@ -79,3 +80,61 @@ func TestStore_SearchApprovals(t *testing.T) { }) } + +func TestStore_UpdateApproval(t *testing.T) { + t.Run("Return unprocessable_entity error when the status is invalid.", func(t *testing.T) { + ctx := context.Background() + + client := enttest.Open(t, + "sqlite3", + "file:ent?mode=memory&cache=shared&_fk=1", + enttest.WithMigrateOptions(migrate.WithForeignKeys(false)), + ) + defer client.Close() + + a := client.Approval. + Create(). + SetUserID(1). + SetDeploymentID(1). + SaveX(ctx) + + store := NewStore(client) + + t.Log("Update the approval with the invalid value.") + a.Status = approval.Status("VALUE") + _, err := store.UpdateApproval(ctx, a) + if !e.HasErrorCode(err, e.ErrorCodeUnprocessableEntity) { + t.Fatalf("UpdateApproval return error = %v, wanted ErrorCodeUnprocessableEntity", err) + } + }) + + t.Run("Return the updated approval.", func(t *testing.T) { + ctx := context.Background() + + client := enttest.Open(t, + "sqlite3", + "file:ent?mode=memory&cache=shared&_fk=1", + enttest.WithMigrateOptions(migrate.WithForeignKeys(false)), + ) + defer client.Close() + + a := client.Approval. + Create(). + SetUserID(1). + SetDeploymentID(1). + SaveX(ctx) + + store := NewStore(client) + + t.Log("Update the approval ") + a.Status = approval.StatusApproved + a, err := store.UpdateApproval(ctx, a) + if err != nil { + t.Fatalf("UpdateApproval returns an error: %v", err) + } + + if a.Status != approval.StatusApproved { + t.Fatalf("UpdateApproval status = %v, wanted %v", a.Status, approval.StatusApproved) + } + }) +} diff --git a/internal/pkg/store/deployment.go b/internal/pkg/store/deployment.go index c0c31eac..20244dec 100644 --- a/internal/pkg/store/deployment.go +++ b/internal/pkg/store/deployment.go @@ -2,6 +2,7 @@ package store import ( "context" + "fmt" "time" "entgo.io/ent/dialect/sql" @@ -17,10 +18,7 @@ func (s *Store) CountDeployments(ctx context.Context) (int, error) { Query(). Count(ctx) if err != nil { - return 0, e.NewError( - e.ErrorCodeInternalError, - err, - ) + return 0, e.NewError(e.ErrorCodeInternalError, err) } return cnt, nil @@ -138,7 +136,7 @@ func (s *Store) ListDeploymentsOfRepo(ctx context.Context, r *ent.Repo, env stri } func (s *Store) FindDeploymentByID(ctx context.Context, id int) (*ent.Deployment, error) { - return s.c.Deployment. + d, err := s.c.Deployment. Query(). Where( deployment.IDEQ(id), @@ -146,7 +144,14 @@ func (s *Store) FindDeploymentByID(ctx context.Context, id int) (*ent.Deployment WithRepo(). WithUser(). WithDeploymentStatuses(). - First(ctx) + Only(ctx) + if ent.IsNotFound(err) { + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The deployment is not found.", err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return d, nil } func (s *Store) FindDeploymentOfRepoByNumber(ctx context.Context, r *ent.Repo, number int) (*ent.Deployment, error) { @@ -161,16 +166,18 @@ func (s *Store) FindDeploymentOfRepoByNumber(ctx context.Context, r *ent.Repo, n WithRepo(). WithUser(). WithDeploymentStatuses(). - First(ctx) + Only(ctx) if ent.IsNotFound(err) { return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The deployment is not found.", err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) } return d, nil } func (s *Store) FindDeploymentByUID(ctx context.Context, uid int64) (*ent.Deployment, error) { - return s.c.Deployment. + d, err := s.c.Deployment. Query(). Where( deployment.UIDEQ(uid), @@ -178,7 +185,14 @@ func (s *Store) FindDeploymentByUID(ctx context.Context, uid int64) (*ent.Deploy WithRepo(). WithUser(). WithDeploymentStatuses(). - First(ctx) + Only(ctx) + if ent.IsNotFound(err) { + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The deployment is not found.", err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return d, nil } func (s *Store) GetNextDeploymentNumberOfRepo(ctx context.Context, r *ent.Repo) (int, error) { @@ -195,7 +209,7 @@ func (s *Store) GetNextDeploymentNumberOfRepo(ctx context.Context, r *ent.Repo) } func (s *Store) FindPrevSuccessDeployment(ctx context.Context, d *ent.Deployment) (*ent.Deployment, error) { - return s.c.Deployment. + d, err := s.c.Deployment. Query(). Where( deployment.And( @@ -207,11 +221,19 @@ func (s *Store) FindPrevSuccessDeployment(ctx context.Context, d *ent.Deployment ). Order(ent.Desc(deployment.FieldCreatedAt)). First(ctx) + if ent.IsNotFound(err) { + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The deployment is not found.", err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return d, nil } // CreateDeployment create a new deployment, and // it updates the 'latest_deployed_at' field of the repository. func (s *Store) CreateDeployment(ctx context.Context, d *ent.Deployment) (*ent.Deployment, error) { + // TODO: Group by a transaction d, err := s.c.Deployment.Create(). SetNumber(d.Number). SetType(d.Type). @@ -229,15 +251,14 @@ func (s *Store) CreateDeployment(ctx context.Context, d *ent.Deployment) (*ent.D SetRepoID(d.RepoID). Save(ctx) if ent.IsConstraintError(err) { - return nil, e.NewError( - e.ErrorCodeDeploymentConflict, - err, - ) + return nil, e.NewError(e.ErrorCodeDeploymentConflict, err) + } else if ent.IsValidationError(err) { + return nil, e.NewErrorWithMessage( + e.ErrorCodeUnprocessableEntity, + fmt.Sprintf("Failed to create a deployment. The value of \"%s\" field is invalid.", err.(*ent.ValidationError).Name), + err) } else if err != nil { - return nil, e.NewError( - e.ErrorCodeInternalError, - err, - ) + return nil, e.NewError(e.ErrorCodeInternalError, err) } s.c.Repo. @@ -262,11 +283,13 @@ func (s *Store) UpdateDeployment(ctx context.Context, d *ent.Deployment) (*ent.D SetRequiredApprovalCount(d.RequiredApprovalCount). SetStatus(d.Status). Save(ctx) - if err != nil { - return nil, e.NewError( - e.ErrorCodeInternalError, - err, - ) + if ent.IsValidationError(err) { + return nil, e.NewErrorWithMessage( + e.ErrorCodeUnprocessableEntity, + fmt.Sprintf("Failed to update a deployment. The value of \"%s\" field is invalid.", err.(*ent.ValidationError).Name), + err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) } return d, nil diff --git a/internal/pkg/store/deploymentstatus.go b/internal/pkg/store/deploymentstatus.go index ce986869..794b3917 100644 --- a/internal/pkg/store/deploymentstatus.go +++ b/internal/pkg/store/deploymentstatus.go @@ -2,22 +2,34 @@ package store import ( "context" + "fmt" "github.com/gitploy-io/gitploy/ent" + "github.com/gitploy-io/gitploy/pkg/e" ) func (s *Store) CreateDeploymentStatus(ctx context.Context, ds *ent.DeploymentStatus) (*ent.DeploymentStatus, error) { - return s.c.DeploymentStatus. + ret, err := s.c.DeploymentStatus. Create(). SetStatus(ds.Status). SetDescription(ds.Description). SetLogURL(ds.LogURL). SetDeploymentID(ds.DeploymentID). Save(ctx) + if ent.IsConstraintError(err) { + return nil, e.NewErrorWithMessage( + e.ErrorCodeUnprocessableEntity, + fmt.Sprintf("Failed to create a deployment status. The value of \"%s\" field is invalid.", err.(*ent.ValidationError).Name), + err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return ret, nil } func (s *Store) SyncDeploymentStatus(ctx context.Context, ds *ent.DeploymentStatus) (*ent.DeploymentStatus, error) { - return s.c.DeploymentStatus. + ret, err := s.c.DeploymentStatus. Create(). SetStatus(ds.Status). SetDescription(ds.Description). @@ -26,4 +38,14 @@ func (s *Store) SyncDeploymentStatus(ctx context.Context, ds *ent.DeploymentStat SetCreatedAt(ds.CreatedAt). SetUpdatedAt(ds.UpdatedAt). Save(ctx) + if ent.IsConstraintError(err) { + return nil, e.NewErrorWithMessage( + e.ErrorCodeUnprocessableEntity, + fmt.Sprintf("Failed to sync the deployment status. The value of \"%s\" field is invalid.", err.(*ent.ValidationError).Name), + err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return ret, nil } diff --git a/internal/pkg/store/lock.go b/internal/pkg/store/lock.go index a3860cb8..68446b72 100644 --- a/internal/pkg/store/lock.go +++ b/internal/pkg/store/lock.go @@ -2,6 +2,7 @@ package store import ( "context" + "fmt" "time" "github.com/gitploy-io/gitploy/ent" @@ -21,16 +22,21 @@ func (s *Store) ListExpiredLocksLessThanTime(ctx context.Context, t time.Time) ( } func (s *Store) ListLocksOfRepo(ctx context.Context, r *ent.Repo) ([]*ent.Lock, error) { - return s.c.Lock. + ls, err := s.c.Lock. Query(). Where(lock.RepoID(r.ID)). WithUser(). WithRepo(). All(ctx) + if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return ls, nil } func (s *Store) FindLockOfRepoByEnv(ctx context.Context, r *ent.Repo, env string) (*ent.Lock, error) { - return s.c.Lock. + l, err := s.c.Lock. Query(). Where( lock.And( @@ -41,6 +47,13 @@ func (s *Store) FindLockOfRepoByEnv(ctx context.Context, r *ent.Repo, env string WithUser(). WithRepo(). Only(ctx) + if ent.IsNotFound(err) { + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The lock is not found.", err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return l, nil } func (s *Store) HasLockOfRepoForEnv(ctx context.Context, r *ent.Repo, env string) (bool, error) { @@ -56,17 +69,14 @@ func (s *Store) HasLockOfRepoForEnv(ctx context.Context, r *ent.Repo, env string WithRepo(). Count(ctx) if err != nil { - return false, e.NewError( - e.ErrorCodeInternalError, - err, - ) + return false, e.NewError(e.ErrorCodeInternalError, err) } return cnt > 0, nil } func (s *Store) FindLockByID(ctx context.Context, id int) (*ent.Lock, error) { - return s.c.Lock. + l, err := s.c.Lock. Query(). Where( lock.IDEQ(id), @@ -74,23 +84,50 @@ func (s *Store) FindLockByID(ctx context.Context, id int) (*ent.Lock, error) { WithUser(). WithRepo(). Only(ctx) + if ent.IsNotFound(err) { + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The lock is not found.", err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return l, nil } func (s *Store) CreateLock(ctx context.Context, l *ent.Lock) (*ent.Lock, error) { - return s.c.Lock. + l, err := s.c.Lock. Create(). SetEnv(l.Env). SetNillableExpiredAt(l.ExpiredAt). SetRepoID(l.RepoID). SetUserID(l.UserID). Save(ctx) + if ent.IsValidationError(err) { + return nil, e.NewErrorWithMessage( + e.ErrorCodeUnprocessableEntity, + fmt.Sprintf("Failed to create a lock. The value of \"%s\" field is invalid.", err.(*ent.ValidationError).Name), + err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return l, nil } func (s *Store) UpdateLock(ctx context.Context, l *ent.Lock) (*ent.Lock, error) { - return s.c.Lock. + l, err := s.c.Lock. UpdateOne(l). SetNillableExpiredAt(l.ExpiredAt). Save(ctx) + if ent.IsValidationError(err) { + return nil, e.NewErrorWithMessage( + e.ErrorCodeUnprocessableEntity, + fmt.Sprintf("Failed to update the lock. The value of \"%s\" field is invalid.", err.(*ent.ValidationError).Name), + err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return l, nil } func (s *Store) DeleteLock(ctx context.Context, l *ent.Lock) error { diff --git a/internal/pkg/store/perm.go b/internal/pkg/store/perm.go index 1807d351..66a41cf0 100644 --- a/internal/pkg/store/perm.go +++ b/internal/pkg/store/perm.go @@ -2,16 +2,18 @@ package store import ( "context" + "fmt" "time" "github.com/gitploy-io/gitploy/ent" "github.com/gitploy-io/gitploy/ent/perm" "github.com/gitploy-io/gitploy/ent/repo" "github.com/gitploy-io/gitploy/ent/user" + "github.com/gitploy-io/gitploy/pkg/e" ) func (s *Store) ListPermsOfRepo(ctx context.Context, r *ent.Repo, q string, page, perPage int) ([]*ent.Perm, error) { - return s.c.Perm. + perms, err := s.c.Perm. Query(). Where( perm.And( @@ -24,10 +26,15 @@ func (s *Store) ListPermsOfRepo(ctx context.Context, r *ent.Repo, q string, page Limit(perPage). Offset(offset(page, perPage)). All(ctx) + if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return perms, nil } func (s *Store) FindPermOfRepo(ctx context.Context, r *ent.Repo, u *ent.User) (*ent.Perm, error) { - return s.c.Perm. + p, err := s.c.Perm. Query(). Where( perm.And( @@ -38,24 +45,51 @@ func (s *Store) FindPermOfRepo(ctx context.Context, r *ent.Repo, u *ent.User) (* WithRepo(). WithUser(). Only(ctx) + if ent.IsNotFound(err) { + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The user has no permission for the repository.", err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return p, nil } func (s *Store) CreatePerm(ctx context.Context, p *ent.Perm) (*ent.Perm, error) { - return s.c.Perm. + perm, err := s.c.Perm. Create(). SetRepoPerm(p.RepoPerm). SetSyncedAt(p.SyncedAt). SetUserID(p.UserID). SetRepoID(p.RepoID). Save(ctx) + if ent.IsValidationError(err) { + return nil, e.NewErrorWithMessage( + e.ErrorCodeUnprocessableEntity, + fmt.Sprintf("The value of \"%s\" field is invalid.", err.(*ent.ValidationError).Name), + err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return perm, nil } func (s *Store) UpdatePerm(ctx context.Context, p *ent.Perm) (*ent.Perm, error) { - return s.c.Perm. + perm, err := s.c.Perm. UpdateOne(p). SetRepoPerm(p.RepoPerm). SetSyncedAt(p.SyncedAt). Save(ctx) + if ent.IsValidationError(err) { + return nil, e.NewErrorWithMessage( + e.ErrorCodeUnprocessableEntity, + fmt.Sprintf("The value of \"%s\" field is invalid.", err.(*ent.ValidationError).Name), + err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return perm, nil } func (s *Store) DeletePermsOfUserLessThanSyncedAt(ctx context.Context, u *ent.User, t time.Time) (int, error) { @@ -82,7 +116,7 @@ func (s *Store) DeletePermsOfUserLessThanSyncedAt(ctx context.Context, u *ent.Us Exec(ctx) return err }); err != nil { - return 0, err + return 0, e.NewError(e.ErrorCodeInternalError, err) } return cnt, nil diff --git a/internal/pkg/store/repo.go b/internal/pkg/store/repo.go index fcaf6261..775f32f8 100644 --- a/internal/pkg/store/repo.go +++ b/internal/pkg/store/repo.go @@ -2,26 +2,38 @@ package store import ( "context" + "fmt" "entgo.io/ent/dialect/sql" "github.com/gitploy-io/gitploy/ent" "github.com/gitploy-io/gitploy/ent/deployment" "github.com/gitploy-io/gitploy/ent/perm" "github.com/gitploy-io/gitploy/ent/repo" + "github.com/gitploy-io/gitploy/pkg/e" "github.com/gitploy-io/gitploy/vo" ) func (s *Store) CountActiveRepos(ctx context.Context) (int, error) { - return s.c.Repo. + cnt, err := s.c.Repo. Query(). Where(repo.ActiveEQ(true)). Count(ctx) + if err != nil { + return 0, e.NewError(e.ErrorCodeInternalError, err) + } + + return cnt, nil } func (s *Store) CountRepos(ctx context.Context) (int, error) { - return s.c.Repo. + cnt, err := s.c.Repo. Query(). Count(ctx) + if err != nil { + return 0, e.NewError(e.ErrorCodeInternalError, err) + } + + return cnt, nil } func (s *Store) ListReposOfUser(ctx context.Context, u *ent.User, q, namespace, name string, sorted bool, page, perPage int) ([]*ent.Repo, error) { @@ -63,7 +75,7 @@ func (s *Store) ListReposOfUser(ctx context.Context, u *ent.User, q, namespace, repos, err := qry.All(ctx) if err != nil { - return nil, err + return nil, e.NewError(e.ErrorCodeInternalError, err) } for _, r := range repos { @@ -74,7 +86,7 @@ func (s *Store) ListReposOfUser(ctx context.Context, u *ent.User, q, namespace, WithUser(). All(ctx) if err != nil { - return nil, err + return nil, e.NewError(e.ErrorCodeInternalError, err) } r.Edges.Deployments = deployments @@ -83,11 +95,18 @@ func (s *Store) ListReposOfUser(ctx context.Context, u *ent.User, q, namespace, } func (s *Store) FindRepoByID(ctx context.Context, id int64) (*ent.Repo, error) { - return s.c.Repo.Get(ctx, id) + r, err := s.c.Repo.Get(ctx, id) + if ent.IsNotFound(err) { + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The repository is not found.", err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return r, nil } func (s *Store) FindRepoOfUserByID(ctx context.Context, u *ent.User, id int64) (*ent.Repo, error) { - return s.c.Repo. + r, err := s.c.Repo. Query(). Where(func(s *sql.Selector) { t := sql.Table(perm.Table) @@ -100,6 +119,13 @@ func (s *Store) FindRepoOfUserByID(ctx context.Context, u *ent.User, id int64) ( repo.IDEQ(id), ). Only(ctx) + if ent.IsNotFound(err) { + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The repository is not found.", err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return r, nil } func (s *Store) FindRepoOfUserByNamespaceName(ctx context.Context, u *ent.User, namespace, name string) (*ent.Repo, error) { @@ -119,42 +145,84 @@ func (s *Store) FindRepoOfUserByNamespaceName(ctx context.Context, u *ent.User, ), ). Only(ctx) - if err != nil { - return nil, err + if ent.IsNotFound(err) { + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The repository is not found.", err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) } return r, nil } func (s *Store) SyncRepo(ctx context.Context, r *vo.RemoteRepo) (*ent.Repo, error) { - return s.c.Repo. + ret, err := s.c.Repo. Create(). SetID(r.ID). SetNamespace(r.Namespace). SetName(r.Name). SetDescription(r.Description). Save(ctx) + if ent.IsValidationError(err) { + return nil, e.NewErrorWithMessage( + e.ErrorCodeUnprocessableEntity, + fmt.Sprintf("The value of \"%s\" field is invalid.", err.(*ent.ValidationError).Name), + err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return ret, nil } func (s *Store) UpdateRepo(ctx context.Context, r *ent.Repo) (*ent.Repo, error) { - return s.c.Repo. + ret, err := s.c.Repo. UpdateOne(r). SetConfigPath(r.ConfigPath). Save(ctx) + if ent.IsValidationError(err) { + return nil, e.NewErrorWithMessage( + e.ErrorCodeUnprocessableEntity, + fmt.Sprintf("The value of \"%s\" field is invalid.", err.(*ent.ValidationError).Name), + err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return ret, nil } func (s *Store) Activate(ctx context.Context, r *ent.Repo) (*ent.Repo, error) { - return s.c.Repo. + ret, err := s.c.Repo. UpdateOne(r). SetActive(true). SetWebhookID(r.WebhookID). Save(ctx) + if ent.IsValidationError(err) { + return nil, e.NewErrorWithMessage( + e.ErrorCodeUnprocessableEntity, + fmt.Sprintf("The value of \"%s\" field is invalid.", err.(*ent.ValidationError).Name), + err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return ret, nil } func (s *Store) Deactivate(ctx context.Context, r *ent.Repo) (*ent.Repo, error) { - return s.c.Repo. + ret, err := s.c.Repo. UpdateOne(r). SetActive(false). SetWebhookID(0). Save(ctx) + if ent.IsValidationError(err) { + return nil, e.NewErrorWithMessage( + e.ErrorCodeUnprocessableEntity, + fmt.Sprintf("The value of \"%s\" field is invalid.", err.(*ent.ValidationError).Name), + err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return ret, nil } diff --git a/internal/pkg/store/user.go b/internal/pkg/store/user.go index 385e487a..aa9f27c4 100644 --- a/internal/pkg/store/user.go +++ b/internal/pkg/store/user.go @@ -13,10 +13,7 @@ func (s *Store) CountUsers(ctx context.Context) (int, error) { Query(). Count(ctx) if err != nil { - return 0, e.NewError( - e.ErrorCodeInternalError, - err, - ) + return 0, e.NewError(e.ErrorCodeInternalError, err) } return cnt, nil @@ -40,33 +37,54 @@ func (s *Store) ListUsers(ctx context.Context, login string, page, perPage int) // // Note that add new indexes if you need to more edges. func (s *Store) FindUserByID(ctx context.Context, id int64) (*ent.User, error) { - return s.c.User. + u, err := s.c.User. Query(). Where( user.IDEQ(id), ). WithChatUser(). First(ctx) + if ent.IsNotFound(err) { + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The user is not found.", err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return u, nil } func (s *Store) FindUserByHash(ctx context.Context, hash string) (*ent.User, error) { - return s.c.User. + u, err := s.c.User. Query(). Where( user.HashEQ(hash), ). WithChatUser(). Only(ctx) + if ent.IsNotFound(err) { + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The user is not found.", err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return u, nil } func (s *Store) FindUserByLogin(ctx context.Context, login string) (*ent.User, error) { - return s.c.User. + u, err := s.c.User. Query(). Where( user.LoginEQ(login), ). WithChatUser(). Only(ctx) + if ent.IsNotFound(err) { + return nil, e.NewErrorWithMessage(e.ErrorCodeNotFound, "The user is not found.", err) + } else if err != nil { + return nil, e.NewError(e.ErrorCodeInternalError, err) + } + + return u, nil } func (s *Store) CreateUser(ctx context.Context, u *ent.User) (*ent.User, error) { diff --git a/internal/server/api/shared/middleware.go b/internal/server/api/shared/middleware.go index 4a1fdca8..e10c2472 100644 --- a/internal/server/api/shared/middleware.go +++ b/internal/server/api/shared/middleware.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" gb "github.com/gitploy-io/gitploy/internal/server/global" + "github.com/gitploy-io/gitploy/pkg/e" ) const ( @@ -30,7 +31,7 @@ func (m *Middleware) IsLicenseExpired() gin.HandlerFunc { lic, err := m.i.GetLicense(ctx) if err != nil { - gb.AbortWithErrorResponse(c, http.StatusInternalServerError, "It has failed to get the license.") + gb.AbortWithStatusAndError(c, http.StatusInternalServerError, err) return } @@ -39,14 +40,22 @@ func (m *Middleware) IsLicenseExpired() gin.HandlerFunc { } if lic.IsOverLimit() { - gb.AbortWithErrorResponse(c, http.StatusPaymentRequired, "The member count is over the limit.") + gb.AbortWithStatusAndError( + c, + http.StatusPaymentRequired, + e.NewErrorWithMessage(e.ErrorCodeLicenseRequired, "The license is over the limit.", nil), + ) return } if lic.IsStandard() && lic.IsExpired() { now := time.Now() if lic.ExpiredAt.Add(extraDuration).Before(now) { - gb.AbortWithErrorResponse(c, http.StatusPaymentRequired, "The license is expired.") + gb.AbortWithStatusAndError( + c, + http.StatusPaymentRequired, + e.NewErrorWithMessage(e.ErrorCodeLicenseRequired, "The license is expired.", nil), + ) return } } diff --git a/internal/server/api/v1/license/license.go b/internal/server/api/v1/license/license.go index 58f1cab9..aff2e327 100644 --- a/internal/server/api/v1/license/license.go +++ b/internal/server/api/v1/license/license.go @@ -27,7 +27,7 @@ func (l *Licenser) GetLicense(c *gin.Context) { lic, err := l.i.GetLicense(ctx) if err != nil { - gb.LogWithError(l.log, "It has failed to get the license.", err) + l.log.Check(gb.GetZapLogLevel(err), "It has failed to get the license.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } diff --git a/internal/server/api/v1/repos/approval.go b/internal/server/api/v1/repos/approval.go index 1c268acf..db2dbabd 100644 --- a/internal/server/api/v1/repos/approval.go +++ b/internal/server/api/v1/repos/approval.go @@ -8,6 +8,7 @@ package repos import ( "net/http" + "strconv" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" @@ -17,6 +18,7 @@ import ( "github.com/gitploy-io/gitploy/ent/approval" "github.com/gitploy-io/gitploy/ent/event" gb "github.com/gitploy-io/gitploy/internal/server/global" + "github.com/gitploy-io/gitploy/pkg/e" ) type ( @@ -40,20 +42,16 @@ func (r *Repo) ListApprovals(c *gin.Context) { re := vr.(*ent.Repo) d, err := r.i.FindDeploymentOfRepoByNumber(ctx, re, atoi(number)) - if ent.IsNotFound(err) { - r.log.Warn("The deployment is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusNotFound, "The deployment is not found.") - return - } else if err != nil { - r.log.Error("failed to get the deployment.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to get the deployment.") + if err != nil { + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the deployment.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } as, err := r.i.ListApprovals(ctx, d) if err != nil { - r.log.Error("failed to list approvals.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to list approvals.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to list approvals.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -68,13 +66,9 @@ func (r *Repo) GetApproval(c *gin.Context) { ) ap, err := r.i.FindApprovalByID(ctx, atoi(aid)) - if ent.IsNotFound(err) { - r.log.Warn("The approval is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusNotFound, "The apporval is not found.") - return - } else if err != nil { - r.log.Error("It has failed to get the approval.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to get the approval.") + if err != nil { + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the approval.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -85,34 +79,35 @@ func (r *Repo) GetMyApproval(c *gin.Context) { ctx := c.Request.Context() var ( - number = c.Param("number") + number int + err error ) + if number, err = strconv.Atoi(c.Param("number")); err != nil { + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The number must be number.", nil), + ) + return + } + vu, _ := c.Get(gb.KeyUser) u := vu.(*ent.User) vr, _ := c.Get(KeyRepo) re := vr.(*ent.Repo) - d, err := r.i.FindDeploymentOfRepoByNumber(ctx, re, atoi(number)) - if ent.IsNotFound(err) { - r.log.Warn("The deployment is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusNotFound, "The deployment is not found.") - return - } else if err != nil { - r.log.Error("failed to get the deployment.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to get the deployment.") + d, err := r.i.FindDeploymentOfRepoByNumber(ctx, re, number) + if err != nil { + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the deployment.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } a, err := r.i.FindApprovalOfUser(ctx, d, u) - if ent.IsNotFound(err) { - // r.log.Warn("The approval is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusNotFound, "The approval is not found.") - return - } else if err != nil { - r.log.Error("failed to get the approval.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to get the approval.") + if err != nil { + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the user's approval.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -123,54 +118,64 @@ func (r *Repo) CreateApproval(c *gin.Context) { ctx := c.Request.Context() var ( - number = c.Param("number") + number int + err error ) + if number, err = strconv.Atoi(c.Param("number")); err != nil { + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The number must be number.", nil), + ) + return + } + p := &approvalPostPayload{} if err := c.ShouldBindBodyWith(p, binding.JSON); err != nil { r.log.Warn("failed to bind the payload.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusBadRequest, "It has failed to bind the payload.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "It has failed to bind the playload.", nil), + ) return } vr, _ := c.Get(KeyRepo) re := vr.(*ent.Repo) - d, err := r.i.FindDeploymentOfRepoByNumber(ctx, re, atoi(number)) - if ent.IsNotFound(err) { - r.log.Warn("The deployment is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusNotFound, "The deployment is not found.") - return - } else if err != nil { - r.log.Error("failed to get the deployment.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to get the deployment.") + d, err := r.i.FindDeploymentOfRepoByNumber(ctx, re, number) + if err != nil { + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the deployment.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } + // TODO: Migrate the business logic into the interactor. user, err := r.i.FindUserByID(ctx, p.UserID) - if ent.IsNotFound(err) { - r.log.Warn("The approver is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusUnprocessableEntity, "The approver is not found.") - return - } else if err != nil { - r.log.Error("failed to get the approver.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to get the approver.") + if err != nil { + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the user.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } - if _, err := r.i.FindPermOfRepo(ctx, re, user); ent.IsNotFound(err) { + _, err = r.i.FindPermOfRepo(ctx, re, user) + if e.HasErrorCode(err, e.ErrorCodeNotFound) { r.log.Warn("The approver has no permission for the repository.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusUnprocessableEntity, "The approver has no permission for the repository.") + // Override the HTTP status. + gb.ResponseWithStatusAndError(c, http.StatusUnprocessableEntity, err) return } else if err != nil { - r.log.Error("It has failed to get the perm.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to get the perm.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the perm of approver.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } if d.Edges.User != nil && user.ID == d.Edges.User.ID { - r.log.Warn("The deployer can not be the approver.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusUnprocessableEntity, "The deployer can not be the approver.") + r.log.Warn("Failed to create a new approval.", zap.Error(err)) + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeUnprocessableEntity, "The deployer can not be the approver.", nil), + ) return } @@ -178,13 +183,9 @@ func (r *Repo) CreateApproval(c *gin.Context) { UserID: user.ID, DeploymentID: d.ID, }) - if ent.IsConstraintError(err) { - r.log.Warn("The approval to user is already exist.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusUnprocessableEntity, "The approval to user is already exist.") - return - } else if err != nil { - r.log.Error("It has failed to request a approval.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to request a approval.") + if err != nil { + r.log.Check(gb.GetZapLogLevel(err), "Failed to create a new approval.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -193,7 +194,7 @@ func (r *Repo) CreateApproval(c *gin.Context) { Type: event.TypeCreated, ApprovalID: ap.ID, }); err != nil { - r.log.Error("It has failed to create the event.", zap.Error(err)) + r.log.Error("Failed to create the event.", zap.Error(err)) } // Get the approval with edges @@ -208,13 +209,25 @@ func (r *Repo) UpdateMyApproval(c *gin.Context) { ctx := c.Request.Context() var ( - number = c.Param("number") + number int + err error ) + if number, err = strconv.Atoi(c.Param("number")); err != nil { + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The number must be number.", nil), + ) + return + } + p := &approvalPatchPayload{} if err := c.ShouldBindBodyWith(p, binding.JSON); err != nil { r.log.Warn("failed to bind the payload.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusBadRequest, "It has failed to bind the payload.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "It has failed to bind the payload.", nil), + ) return } @@ -224,33 +237,25 @@ func (r *Repo) UpdateMyApproval(c *gin.Context) { vr, _ := c.Get(KeyRepo) re := vr.(*ent.Repo) - d, err := r.i.FindDeploymentOfRepoByNumber(ctx, re, atoi(number)) - if ent.IsNotFound(err) { - r.log.Warn("The deployment is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusNotFound, "The deployment is not found.") - return - } else if err != nil { - r.log.Error("failed to get the deployment.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to get the deployment.") + d, err := r.i.FindDeploymentOfRepoByNumber(ctx, re, number) + if err != nil { + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the deployment.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } a, err := r.i.FindApprovalOfUser(ctx, d, u) - if ent.IsNotFound(err) { - r.log.Warn("The approval is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusNotFound, "The approval is not found.") - return - } else if err != nil { - r.log.Error("failed to get the approval.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to get the approval.") + if err != nil { + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the user's approval.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } if p.Status != string(a.Status) { a.Status = approval.Status(p.Status) if a, err = r.i.UpdateApproval(ctx, a); err != nil { - r.log.Error("failed to update the approval.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to update the approval.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to update the approval.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -259,7 +264,7 @@ func (r *Repo) UpdateMyApproval(c *gin.Context) { Type: event.TypeUpdated, ApprovalID: a.ID, }); err != nil { - r.log.Error("It has failed to create the event.", zap.Error(err)) + r.log.Error("Failed to create the event.", zap.Error(err)) } } @@ -275,30 +280,35 @@ func (r *Repo) DeleteApproval(c *gin.Context) { ctx := c.Request.Context() var ( - aid = c.Param("aid") + aid int + err error ) - ap, err := r.i.FindApprovalByID(ctx, atoi(aid)) - if ent.IsNotFound(err) { - r.log.Warn("The approval is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusNotFound, "The apporval is not found.") + if aid, err = strconv.Atoi(c.Param("aid")); err != nil { + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The number must be number.", nil), + ) return - } else if err != nil { - r.log.Error("It has failed to get the approval.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to get the approval.") + } + + ap, err := r.i.FindApprovalByID(ctx, aid) + if err != nil { + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the approval.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } if err := r.i.DeleteApproval(ctx, ap); err != nil { - r.log.Error("It has failed to delete the approval.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to delete the approval.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to delete the approval.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } if _, err := r.i.CreateEvent(ctx, &ent.Event{ Kind: event.KindApproval, Type: event.TypeDeleted, - DeletedID: atoi(aid), + DeletedID: aid, }); err != nil { r.log.Error("It has failed to create a new event.", zap.Error(err)) } diff --git a/internal/server/api/v1/repos/approval_oss.go b/internal/server/api/v1/repos/approval_oss.go index f91c4482..c23dcbc8 100644 --- a/internal/server/api/v1/repos/approval_oss.go +++ b/internal/server/api/v1/repos/approval_oss.go @@ -9,6 +9,7 @@ import ( "github.com/gitploy-io/gitploy/ent" gb "github.com/gitploy-io/gitploy/internal/server/global" + "github.com/gitploy-io/gitploy/pkg/e" ) func (r *Repo) ListApprovals(c *gin.Context) { @@ -24,13 +25,22 @@ func (r *Repo) GetMyApproval(c *gin.Context) { } func (r *Repo) CreateApproval(c *gin.Context) { - gb.ErrorResponse(c, http.StatusPaymentRequired, "It is limited to the community edition.") + gb.ResponseWithError( + c, + e.NewError(e.ErrorCodeLicenseRequired, nil), + ) } func (r *Repo) UpdateMyApproval(c *gin.Context) { - gb.ErrorResponse(c, http.StatusPaymentRequired, "It is limited to the community edition.") + gb.ResponseWithError( + c, + e.NewError(e.ErrorCodeLicenseRequired, nil), + ) } func (r *Repo) DeleteApproval(c *gin.Context) { - gb.ErrorResponse(c, http.StatusPaymentRequired, "It is limited to the community edition.") + gb.ResponseWithError( + c, + e.NewError(e.ErrorCodeLicenseRequired, nil), + ) } diff --git a/internal/server/api/v1/repos/branch.go b/internal/server/api/v1/repos/branch.go index a3c05846..9683490a 100644 --- a/internal/server/api/v1/repos/branch.go +++ b/internal/server/api/v1/repos/branch.go @@ -11,11 +11,12 @@ import ( ) func (r *Repo) ListBranches(c *gin.Context) { + ctx := c.Request.Context() + var ( page = c.DefaultQuery("page", "1") perPage = c.DefaultQuery("per_page", "30") ) - ctx := c.Request.Context() uv, _ := c.Get(gb.KeyUser) u := uv.(*ent.User) @@ -25,8 +26,8 @@ func (r *Repo) ListBranches(c *gin.Context) { branches, err := r.i.ListBranches(ctx, u, repo, atoi(page), atoi(perPage)) if err != nil { - r.log.Error("failed to list branches.", zap.String("repo", repo.Name), zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to list branches.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to list branches.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -47,7 +48,7 @@ func (r *Repo) GetBranch(c *gin.Context) { b, err := r.i.GetBranch(ctx, u, repo, branch) if err != nil { - gb.LogWithError(r.log, "It has failed to get the branch.", err) + r.log.Check(gb.GetZapLogLevel(err), "Failed to get the branch.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } diff --git a/internal/server/api/v1/repos/comment.go b/internal/server/api/v1/repos/comment.go index 7bcb5984..3875490b 100644 --- a/internal/server/api/v1/repos/comment.go +++ b/internal/server/api/v1/repos/comment.go @@ -37,7 +37,7 @@ func (r *Repo) ListComments(c *gin.Context) { if number, err = strconv.Atoi(c.Param("number")); err != nil { gb.ResponseWithError( c, - e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The number must be integer.", nil), + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The number must be number.", nil), ) return } @@ -47,14 +47,14 @@ func (r *Repo) ListComments(c *gin.Context) { d, err := r.i.FindDeploymentOfRepoByNumber(ctx, re, number) if err != nil { - gb.LogWithError(r.log, "Failed to find the deployment.", err) + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the deployment.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } cmts, err := r.i.ListCommentsOfDeployment(ctx, d) if err != nil { - gb.LogWithError(r.log, "Failed to list comments.", err) + r.log.Check(gb.GetZapLogLevel(err), "Failed to list comments.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } @@ -72,14 +72,14 @@ func (r *Repo) GetComment(c *gin.Context) { if id, err = strconv.Atoi(c.Param("id")); err != nil { gb.ResponseWithError( c, - e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The id must be integer.", nil), + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The id must be number.", nil), ) return } cmt, err := r.i.FindCommentByID(ctx, id) if err != nil { - gb.LogWithError(r.log, "Failed to find the comment.", err) + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the comment.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } @@ -99,7 +99,7 @@ func (r *Repo) CreateComment(c *gin.Context) { r.log.Warn("Failed to parse 'number'.", zap.Error(err)) gb.ResponseWithError( c, - e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The number must be integer.", err), + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The number must be number.", err), ) return } @@ -122,7 +122,7 @@ func (r *Repo) CreateComment(c *gin.Context) { d, err := r.i.FindDeploymentOfRepoByNumber(ctx, re, number) if err != nil { - gb.LogWithError(r.log, "Failed to find the deployment.", err) + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the deployment.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } @@ -134,7 +134,7 @@ func (r *Repo) CreateComment(c *gin.Context) { DeploymentID: d.ID, }) if err != nil { - gb.LogWithError(r.log, "Failed to create a new comment.", err) + r.log.Check(gb.GetZapLogLevel(err), "Failed to create a new comment.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } diff --git a/internal/server/api/v1/repos/comment_oss.go b/internal/server/api/v1/repos/comment_oss.go index 82955b2f..b7ae275c 100644 --- a/internal/server/api/v1/repos/comment_oss.go +++ b/internal/server/api/v1/repos/comment_oss.go @@ -9,6 +9,7 @@ import ( "github.com/gitploy-io/gitploy/ent" gb "github.com/gitploy-io/gitploy/internal/server/global" + "github.com/gitploy-io/gitploy/pkg/e" ) func (r *Repo) ListComments(c *gin.Context) { @@ -20,5 +21,8 @@ func (r *Repo) GetComment(c *gin.Context) { } func (r *Repo) CreateComment(c *gin.Context) { - gb.ErrorResponse(c, http.StatusPaymentRequired, "It is limited to the community edition.") + gb.ResponseWithError( + c, + e.NewError(e.ErrorCodeLicenseRequired, nil), + ) } diff --git a/internal/server/api/v1/repos/commit.go b/internal/server/api/v1/repos/commit.go index 6eaa5ed1..6a467f8b 100644 --- a/internal/server/api/v1/repos/commit.go +++ b/internal/server/api/v1/repos/commit.go @@ -27,8 +27,8 @@ func (r *Repo) ListCommits(c *gin.Context) { commits, err := r.i.ListCommits(ctx, u, repo, branch, atoi(page), atoi(perPage)) if err != nil { - r.log.Error("failed to list commits.", zap.String("repo", repo.Name), zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to list commits.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to list commits.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -50,7 +50,7 @@ func (r *Repo) GetCommit(c *gin.Context) { commit, err := r.i.GetCommit(ctx, u, repo, sha) if err != nil { - gb.LogWithError(r.log, "It has failed to get the commit.", err) + r.log.Check(gb.GetZapLogLevel(err), "Failed to get the commit.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } @@ -73,7 +73,7 @@ func (r *Repo) ListStatuses(c *gin.Context) { ss, err := r.i.ListCommitStatuses(ctx, u, repo, sha) if err != nil { - gb.LogWithError(r.log, "It has failed to list commit statuses.", err) + r.log.Check(gb.GetZapLogLevel(err), "Failed to list commit statuses.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } diff --git a/internal/server/api/v1/repos/deployment.go b/internal/server/api/v1/repos/deployment.go index e4910f92..9696fc29 100644 --- a/internal/server/api/v1/repos/deployment.go +++ b/internal/server/api/v1/repos/deployment.go @@ -43,8 +43,8 @@ func (r *Repo) ListDeployments(c *gin.Context) { ds, err := r.i.ListDeploymentsOfRepo(ctx, re, env, status, atoi(page), atoi(perPage)) if err != nil { - r.log.Error("failed to list deployments.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to list deployments.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to list deployments.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -62,12 +62,10 @@ func (r *Repo) GetDeploymentByNumber(c *gin.Context) { ctx := c.Request.Context() d, err := r.i.FindDeploymentOfRepoByNumber(ctx, re, atoi(number)) - if ent.IsNotFound(err) { - r.log.Warn("the deployment is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusNotFound, "The deployment is not found.") - } else if err != nil { - r.log.Error("failed to find deployment.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusNotFound, "It has failed to find the deployment.") + if err != nil { + r.log.Check(gb.GetZapLogLevel(err), "Failed to get the deployments.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) + return } gb.Response(c, http.StatusOK, d) @@ -78,7 +76,10 @@ func (r *Repo) CreateDeployment(c *gin.Context) { p := &deploymentPostPayload{} if err := c.ShouldBindBodyWith(p, binding.JSON); err != nil { - gb.ErrorResponse(c, http.StatusBadRequest, "It has failed to bind the payload.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "It has failed to bind the payload.", nil), + ) return } @@ -89,27 +90,30 @@ func (r *Repo) CreateDeployment(c *gin.Context) { re := vr.(*ent.Repo) cf, err := r.i.GetConfig(ctx, u, re) - if e.HasErrorCode(err, e.ErrorCodeConfigNotFound) { - gb.LogWithError(r.log, "The configuration file is not found.", err) + if e.HasErrorCode(err, e.ErrorCodeNotFound) { + r.log.Check(gb.GetZapLogLevel(err), "The configuration file is not found.").Write(zap.Error(err)) // To override the HTTP status 422. gb.ResponseWithStatusAndError(c, http.StatusUnprocessableEntity, err) return } else if err != nil { - gb.LogWithError(r.log, "It has failed to get the configuration.", err) + r.log.Check(gb.GetZapLogLevel(err), "It has failed to get the configuration.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } var env *vo.Env if env = cf.GetEnv(p.Env); env == nil { - r.log.Warn("The environment is not defined in the configuration.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusUnprocessableEntity, "The environment is not defined in the configuration.") + r.log.Warn("The environment is not defined in the configuration.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeConfigParseError, "The environment is not defiend in the configuration.", nil), + ) return } if err := env.Eval(&vo.EvalValues{}); err != nil { - r.log.Warn("It has failed to eval variables in the config.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusUnprocessableEntity, "It has failed to eval variables in the config.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to evaluate variables in the configuration.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -122,7 +126,7 @@ func (r *Repo) CreateDeployment(c *gin.Context) { cf.GetEnv(p.Env), ) if err != nil { - gb.LogWithError(r.log, "It has failed to deploy.", err) + r.log.Check(gb.GetZapLogLevel(err), "Failed to deploy.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } @@ -154,7 +158,10 @@ func (r *Repo) UpdateDeployment(c *gin.Context) { p := &deploymentPatchPayload{} if err := c.ShouldBindBodyWith(p, binding.JSON); err != nil { - gb.ErrorResponse(c, http.StatusBadRequest, "It has failed to bind the payload.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "It has failed to bind the payload.", nil), + ) return } @@ -165,39 +172,43 @@ func (r *Repo) UpdateDeployment(c *gin.Context) { re := vr.(*ent.Repo) d, err := r.i.FindDeploymentOfRepoByNumber(ctx, re, atoi(number)) - if ent.IsNotFound(err) { - r.log.Warn("the deployment is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusNotFound, "The deployment is not found.") + if err != nil { + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the deployments.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) + return } cf, err := r.i.GetConfig(ctx, u, re) - if e.HasErrorCode(err, e.ErrorCodeConfigNotFound) { - gb.LogWithError(r.log, "The configuration file is not found.", err) + if e.HasErrorCode(err, e.ErrorCodeNotFound) { + r.log.Check(gb.GetZapLogLevel(err), "The configuration file is not found.").Write(zap.Error(err)) // To override the HTTP status 422. gb.ResponseWithStatusAndError(c, http.StatusUnprocessableEntity, err) return } else if err != nil { - gb.LogWithError(r.log, "It has failed to get the configuration.", err) + r.log.Check(gb.GetZapLogLevel(err), "It has failed to get the configuration.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } var env *vo.Env if env = cf.GetEnv(d.Env); env == nil { - r.log.Warn("The environment is not defined in the config.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusUnprocessableEntity, "The environment is not defined in the config.") + r.log.Warn("The environment is not defined in the configuration.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeConfigParseError, "The environment is not defiend in the configuration.", nil), + ) return } if err := env.Eval(&vo.EvalValues{IsRollback: d.IsRollback}); err != nil { - r.log.Warn("It has failed to eval variables in the config.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusUnprocessableEntity, "It has failed to eval variables in the config.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to evaluate variables in the configuration.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } if p.Status == string(deployment.StatusCreated) && d.Status == deployment.StatusWaiting { if d, err = r.i.DeployToRemote(ctx, u, re, d, env); err != nil { - gb.LogWithError(r.log, "It has failed to deploy to the remote.", err) + r.log.Check(gb.GetZapLogLevel(err), "It has failed to deploy to the remote.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } @@ -233,34 +244,37 @@ func (r *Repo) RollbackDeployment(c *gin.Context) { re := vr.(*ent.Repo) d, err := r.i.FindDeploymentOfRepoByNumber(ctx, re, atoi(number)) - if ent.IsNotFound(err) { - r.log.Warn("the deployment is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusNotFound, "The deployment is not found.") + if err != nil { + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the deployments.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } cf, err := r.i.GetConfig(ctx, u, re) - if e.HasErrorCode(err, e.ErrorCodeConfigNotFound) { - gb.LogWithError(r.log, "The configuration file is not found.", err) + if e.HasErrorCode(err, e.ErrorCodeNotFound) { + r.log.Check(gb.GetZapLogLevel(err), "The configuration file is not found.").Write(zap.Error(err)) // To override the HTTP status 422. gb.ResponseWithStatusAndError(c, http.StatusUnprocessableEntity, err) return } else if err != nil { - gb.LogWithError(r.log, "It has failed to get the configuration.", err) + r.log.Check(gb.GetZapLogLevel(err), "It has failed to get the configuration.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } var env *vo.Env if env = cf.GetEnv(d.Env); env == nil { - r.log.Warn("The environment is not defined in the configuration.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusUnprocessableEntity, "The environment is not defined in the configuration.") + r.log.Warn("The environment is not defined in the configuration.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeConfigParseError, "The environment is not defiend in the configuration.", nil), + ) return } if err := env.Eval(&vo.EvalValues{IsRollback: true}); err != nil { - r.log.Warn("It has failed to eval variables in the config.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusUnprocessableEntity, "It has failed to eval variables in the config.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to evaluate variables in the configuration.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -274,7 +288,7 @@ func (r *Repo) RollbackDeployment(c *gin.Context) { cf.GetEnv(d.Env), ) if err != nil { - gb.LogWithError(r.log, "It has failed to deploy.", err) + r.log.Check(gb.GetZapLogLevel(err), "Failed to deploy.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } @@ -311,23 +325,20 @@ func (r *Repo) ListDeploymentChanges(c *gin.Context) { re := vr.(*ent.Repo) d, err := r.i.FindDeploymentOfRepoByNumber(ctx, re, atoi(number)) - if ent.IsNotFound(err) { - r.log.Warn("The deployment is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusNotFound, "The deployment is not found.") - return - } else if err != nil { - r.log.Error("It has failed to find the deployment.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to find the deployment.") + if err != nil { + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the deployments.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } ld, err := r.i.FindPrevSuccessDeployment(ctx, d) - if ent.IsNotFound(err) { + if e.HasErrorCode(err, e.ErrorCodeNotFound) { + r.log.Debug("The previous deployment is not found.") gb.Response(c, http.StatusOK, []*vo.Commit{}) return } else if err != nil { - r.log.Error("It has failed to find the comparable deployment.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to find the comparable deployment.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the deployments.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -336,7 +347,7 @@ func (r *Repo) ListDeploymentChanges(c *gin.Context) { if sha == "" { sha, err = r.getCommitSha(ctx, u, re, d.Type, d.Ref) if err != nil { - gb.LogWithError(r.log, "It has failed to get the commit SHA.", err) + r.log.Check(gb.GetZapLogLevel(err), "It has failed to get the commit SHA.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } @@ -344,8 +355,8 @@ func (r *Repo) ListDeploymentChanges(c *gin.Context) { commits, _, err := r.i.CompareCommits(ctx, u, re, ld.Sha, sha, atoi(page), atoi(perPage)) if err != nil { - r.log.Error("It has failed to compare two commits.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to compare two commits.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to compare two commits.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -391,7 +402,7 @@ func (r *Repo) GetConfig(c *gin.Context) { config, err := r.i.GetConfig(ctx, u, re) if err != nil { - gb.LogWithError(r.log, "It has failed to get the configuration.", err) + r.log.Check(gb.GetZapLogLevel(err), "Failed to get the configuration.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } diff --git a/internal/server/api/v1/repos/lock.go b/internal/server/api/v1/repos/lock.go index 190a5b91..3dd05725 100644 --- a/internal/server/api/v1/repos/lock.go +++ b/internal/server/api/v1/repos/lock.go @@ -7,7 +7,6 @@ package repos import ( - "fmt" "net/http" "strconv" "time" @@ -42,8 +41,8 @@ func (r *Repo) ListLocks(c *gin.Context) { locks, err := r.i.ListLocksOfRepo(ctx, re) if err != nil { - r.log.Error("It has failed to list locks.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to list locks.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to list locks.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -56,7 +55,10 @@ func (r *Repo) CreateLock(c *gin.Context) { p := &lockPostPayload{} if err := c.ShouldBindBodyWith(p, binding.JSON); err != nil { r.log.Error("It has failed to bind the payload.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusBadRequest, "It has failed to bind the payload.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "It has failed to bind the payload.", err), + ) return } @@ -64,13 +66,16 @@ func (r *Repo) CreateLock(c *gin.Context) { expiredAt *time.Time ) if p.ExpiredAt != nil { - e, err := time.Parse(time.RFC3339, *p.ExpiredAt) + exp, err := time.Parse(time.RFC3339, *p.ExpiredAt) if err != nil { - gb.ErrorResponse(c, http.StatusBadRequest, "Invalid format of \"expired_at\" parameter, RFC3339 format only.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "Invalid format of \"expired_at\" parameter, RFC3339 format only.", err), + ) return } - expiredAt = pointer.ToTime(e.UTC()) + expiredAt = pointer.ToTime(exp.UTC()) } vr, _ := c.Get(KeyRepo) @@ -80,30 +85,37 @@ func (r *Repo) CreateLock(c *gin.Context) { u := vu.(*ent.User) cfg, err := r.i.GetConfig(ctx, u, re) - if e.HasErrorCode(err, e.ErrorCodeConfigNotFound) { - gb.LogWithError(r.log, "The configuration file is not found.", err) + if e.HasErrorCode(err, e.ErrorCodeNotFound) { + r.log.Check(gb.GetZapLogLevel(err), "The configuration file is not found.").Write(zap.Error(err)) // To override the HTTP status 422. gb.ResponseWithStatusAndError(c, http.StatusUnprocessableEntity, err) return } else if err != nil { - gb.LogWithError(r.log, "It has failed to get the configuration.", err) + r.log.Check(gb.GetZapLogLevel(err), "It has failed to get the configuration.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } if !cfg.HasEnv(p.Env) { - r.log.Warn("The env is not found.", zap.String("env", p.Env)) - gb.ErrorResponse(c, http.StatusUnprocessableEntity, fmt.Sprintf("The '%s' env is not found.", p.Env)) + r.log.Warn("The environment is not defined in the configuration.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeConfigParseError, "The environment is not defiend in the configuration.", nil), + ) return } + // TODO: migrate the business logic into the interactor. if ok, err := r.i.HasLockOfRepoForEnv(ctx, re, p.Env); ok { r.log.Warn("The lock already exist.", zap.String("env", p.Env)) - gb.ErrorResponse(c, http.StatusUnprocessableEntity, "The lock already exist.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeUnprocessableEntity, "The lock already exist.", err), + ) return } else if err != nil { - r.log.Error("It has failed to check the lock.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to check the lock.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to check the lock.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -115,8 +127,8 @@ func (r *Repo) CreateLock(c *gin.Context) { RepoID: re.ID, }) if err != nil { - r.log.Error("It has failed to lock the env.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to lock the env.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to create a new lock.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -132,42 +144,45 @@ func (r *Repo) UpdateLock(c *gin.Context) { ctx := c.Request.Context() var ( - sid = c.Param("lockID") + id int + err error ) - id, err := strconv.Atoi(sid) - if err != nil { - r.log.Error("The lock ID must to be number.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusBadRequest, "The lock ID must to be number.") + if id, err = strconv.Atoi(c.Param("lockID")); err != nil { + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The ID must be number.", nil), + ) return } p := &lockPatchPayload{} if err := c.ShouldBindBodyWith(p, binding.JSON); err != nil { - r.log.Error("It has failed to bind the payload.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusBadRequest, "It has failed to bind the payload.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "It has failed to bind the payload.", nil), + ) return } var expiredAt *time.Time if p.ExpiredAt != nil { - e, err := time.Parse(time.RFC3339, *p.ExpiredAt) + exp, err := time.Parse(time.RFC3339, *p.ExpiredAt) if err != nil { - gb.ErrorResponse(c, http.StatusBadRequest, "Invalid format of \"expired_at\" parameter, RFC3339 format only.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "Invalid format of \"expired_at\" parameter, RFC3339 format only.", err), + ) return } - expiredAt = pointer.ToTime(e.UTC()) + expiredAt = pointer.ToTime(exp.UTC()) } l, err := r.i.FindLockByID(ctx, id) - if ent.IsNotFound(err) { - r.log.Warn("The lock is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusNotFound, "The lock is not found.") - return - } else if err != nil { - r.log.Error("It has failed to find the lock.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to find the lock.") + if err != nil { + r.log.Check(gb.GetZapLogLevel(err), "The lock is not found.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -177,8 +192,8 @@ func (r *Repo) UpdateLock(c *gin.Context) { } if _, err := r.i.UpdateLock(ctx, l); err != nil { - r.log.Error("It has failed to update the lock.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to update the lock.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to update the lock.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -193,30 +208,28 @@ func (r *Repo) DeleteLock(c *gin.Context) { ctx := c.Request.Context() var ( - sid = c.Param("lockID") + id int + err error ) - id, err := strconv.Atoi(sid) - if err != nil { - r.log.Error("The lock ID must to be number.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusBadRequest, "The lock ID must to be number.") + if id, err = strconv.Atoi(c.Param("lockID")); err != nil { + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The ID must be number.", nil), + ) return } l, err := r.i.FindLockByID(ctx, id) - if ent.IsNotFound(err) { - r.log.Warn("The lock is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusNotFound, "The lock is not found.") - return - } else if err != nil { - r.log.Error("It has failed to find the lock.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to find the lock.") + if err != nil { + r.log.Check(gb.GetZapLogLevel(err), "Failed to find the lock.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } if err := r.i.DeleteLock(ctx, l); err != nil { - r.log.Error("It has failed to delete the lock.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to delete the lock.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to delete the lock.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } diff --git a/internal/server/api/v1/repos/lock_oss.go b/internal/server/api/v1/repos/lock_oss.go index e604aca3..d5b0de90 100644 --- a/internal/server/api/v1/repos/lock_oss.go +++ b/internal/server/api/v1/repos/lock_oss.go @@ -9,6 +9,7 @@ import ( "github.com/gitploy-io/gitploy/ent" gb "github.com/gitploy-io/gitploy/internal/server/global" + "github.com/gitploy-io/gitploy/pkg/e" ) func (r *Repo) ListLocks(c *gin.Context) { @@ -16,13 +17,22 @@ func (r *Repo) ListLocks(c *gin.Context) { } func (r *Repo) CreateLock(c *gin.Context) { - gb.ErrorResponse(c, http.StatusPaymentRequired, "It is limited to the community edition.") + gb.ResponseWithError( + c, + e.NewError(e.ErrorCodeLicenseRequired, nil), + ) } func (r *Repo) UpdateLock(c *gin.Context) { - gb.ErrorResponse(c, http.StatusPaymentRequired, "It is limited to the community edition.") + gb.ResponseWithError( + c, + e.NewError(e.ErrorCodeLicenseRequired, nil), + ) } func (r *Repo) DeleteLock(c *gin.Context) { - gb.ErrorResponse(c, http.StatusPaymentRequired, "It is limited to the community edition.") + gb.ResponseWithError( + c, + e.NewError(e.ErrorCodeLicenseRequired, nil), + ) } diff --git a/internal/server/api/v1/repos/middleware.go b/internal/server/api/v1/repos/middleware.go index 06052f45..68a5f942 100644 --- a/internal/server/api/v1/repos/middleware.go +++ b/internal/server/api/v1/repos/middleware.go @@ -7,6 +7,7 @@ import ( "github.com/gitploy-io/gitploy/ent" "github.com/gitploy-io/gitploy/ent/perm" gb "github.com/gitploy-io/gitploy/internal/server/global" + "github.com/gitploy-io/gitploy/pkg/e" "go.uber.org/zap" ) @@ -41,24 +42,20 @@ func (rm *RepoMiddleware) RepoReadPerm() gin.HandlerFunc { u := v.(*ent.User) r, err := rm.i.FindRepoOfUserByNamespaceName(ctx, u, namespace, name) - if ent.IsNotFound(err) { - rm.log.Warn("The repository is not found.", zap.String("repo", namespace+"/"+name), zap.Error(err)) - gb.AbortWithErrorResponse(c, http.StatusNotFound, "The repository is not found.") - return - } else if err != nil { - rm.log.Error("It has failed to get the repository.", zap.String("repo", namespace+"/"+name), zap.Error(err)) - gb.AbortWithErrorResponse(c, http.StatusInternalServerError, "It has failed to get the repository.") + if err != nil { + rm.log.Check(gb.GetZapLogLevel(err), "Failed to find the repository.").Write(zap.Error(err)) + gb.AbortWithError(c, err) return } _, err = rm.i.FindPermOfRepo(ctx, r, u) - if ent.IsNotFound(err) { - rm.log.Warn("It is denied to access the repository.", zap.String("repo", namespace+"/"+name), zap.Error(err)) - gb.AbortWithErrorResponse(c, http.StatusForbidden, "It is denied to access the repository.") + if e.HasErrorCode(err, e.ErrorCodeNotFound) { + rm.log.Check(gb.GetZapLogLevel(err), "It is denied to acess the repository.").Write(zap.Error(err)) + gb.AbortWithStatusAndError(c, http.StatusForbidden, err) return } else if err != nil { - rm.log.Error("It has failed to get the permission.", zap.String("repo", namespace+"/"+name), zap.Error(err)) - gb.AbortWithErrorResponse(c, http.StatusInternalServerError, "It has failed to get the permission.") + rm.log.Check(gb.GetZapLogLevel(err), "Failed to find the permission.").Write(zap.Error(err)) + gb.AbortWithError(c, err) return } @@ -79,30 +76,30 @@ func (rm *RepoMiddleware) RepoWritePerm() gin.HandlerFunc { u := v.(*ent.User) r, err := rm.i.FindRepoOfUserByNamespaceName(ctx, u, namespace, name) - if ent.IsNotFound(err) { - rm.log.Warn("The repository is not found.", zap.String("repo", namespace+"/"+name), zap.Error(err)) - gb.AbortWithErrorResponse(c, http.StatusNotFound, "The repository is not found.") - return - } else if err != nil { - rm.log.Error("It has failed to get the repository.", zap.String("repo", namespace+"/"+name), zap.Error(err)) - gb.AbortWithErrorResponse(c, http.StatusInternalServerError, "It has failed to get the repository.") + if err != nil { + rm.log.Check(gb.GetZapLogLevel(err), "Failed to find the repository.").Write(zap.Error(err)) + gb.AbortWithError(c, err) return } p, err := rm.i.FindPermOfRepo(ctx, r, u) - if ent.IsNotFound(err) { - rm.log.Warn("It is denied to access the repository.", zap.String("repo", namespace+"/"+name), zap.Error(err)) - gb.AbortWithErrorResponse(c, http.StatusForbidden, "It is denied to access the repository.") + if e.HasErrorCode(err, e.ErrorCodeNotFound) { + rm.log.Check(gb.GetZapLogLevel(err), "It is denied to acess the repository.").Write(zap.Error(err)) + gb.AbortWithStatusAndError(c, http.StatusForbidden, err) return } else if err != nil { - rm.log.Error("It has failed to get the repository.", zap.String("repo", namespace+"/"+name), zap.Error(err)) - gb.AbortWithErrorResponse(c, http.StatusInternalServerError, "It has failed to get the permission.") + rm.log.Check(gb.GetZapLogLevel(err), "Failed to find the permission.").Write(zap.Error(err)) + gb.AbortWithError(c, err) return } if !(p.RepoPerm == perm.RepoPermWrite || p.RepoPerm == perm.RepoPermAdmin) { rm.log.Warn("The access is forbidden. Only write permission can access.", zap.String("repo", namespace+"/"+name)) - gb.AbortWithErrorResponse(c, http.StatusForbidden, "Only write permission can access.") + gb.AbortWithStatusAndError( + c, + http.StatusForbidden, + e.NewErrorWithMessage(e.ErrorPermissionRequired, "Only write permission can access.", nil), + ) return } @@ -123,30 +120,30 @@ func (rm *RepoMiddleware) RepoAdminPerm() gin.HandlerFunc { u := v.(*ent.User) r, err := rm.i.FindRepoOfUserByNamespaceName(ctx, u, namespace, name) - if ent.IsNotFound(err) { - rm.log.Warn("The repository is not found.", zap.String("repo", namespace+"/"+name), zap.Error(err)) - gb.AbortWithErrorResponse(c, http.StatusNotFound, "The repository is not found.") - return - } else if err != nil { - rm.log.Error("It has failed to get the repository.", zap.String("repo", namespace+"/"+name), zap.Error(err)) - gb.AbortWithErrorResponse(c, http.StatusInternalServerError, "It has failed to get the repository.") + if err != nil { + rm.log.Check(gb.GetZapLogLevel(err), "Failed to find the repository.").Write(zap.Error(err)) + gb.AbortWithError(c, err) return } p, err := rm.i.FindPermOfRepo(ctx, r, u) - if ent.IsNotFound(err) { - rm.log.Warn("It is denied to access the repo.", zap.String("repo", namespace+"/"+name), zap.Error(err)) - gb.AbortWithErrorResponse(c, http.StatusForbidden, "It is denied to access the repo.") + if e.HasErrorCode(err, e.ErrorCodeNotFound) { + rm.log.Check(gb.GetZapLogLevel(err), "It is denied to acess the repository.").Write(zap.Error(err)) + gb.AbortWithStatusAndError(c, http.StatusForbidden, err) return } else if err != nil { - rm.log.Error("It has failed to get the permission.", zap.String("repo", namespace+"/"+name), zap.Error(err)) - gb.AbortWithErrorResponse(c, http.StatusInternalServerError, "It has failed to get the permission.") + rm.log.Check(gb.GetZapLogLevel(err), "Failed to find the permission.").Write(zap.Error(err)) + gb.AbortWithError(c, err) return } if p.RepoPerm != perm.RepoPermAdmin { rm.log.Warn("The access is forbidden. Only admin permission can access.", zap.String("repo", namespace+"/"+name)) - gb.AbortWithErrorResponse(c, http.StatusForbidden, "Only admin permission can access.") + gb.AbortWithStatusAndError( + c, + http.StatusForbidden, + e.NewErrorWithMessage(e.ErrorPermissionRequired, "Only admin permission can access.", nil), + ) return } diff --git a/internal/server/api/v1/repos/perm.go b/internal/server/api/v1/repos/perm.go index 43f8c0dc..6f6afe04 100644 --- a/internal/server/api/v1/repos/perm.go +++ b/internal/server/api/v1/repos/perm.go @@ -28,8 +28,8 @@ func (r *Repo) ListPerms(c *gin.Context) { perms, err := r.i.ListPermsOfRepo(ctx, re, q, page, perPage) if err != nil { - r.log.Error("failed to get permissions.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to get permissions.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to list permissions.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } diff --git a/internal/server/api/v1/repos/repo.go b/internal/server/api/v1/repos/repo.go index 12179cc0..e8142379 100644 --- a/internal/server/api/v1/repos/repo.go +++ b/internal/server/api/v1/repos/repo.go @@ -8,6 +8,7 @@ import ( "github.com/gin-gonic/gin/binding" "github.com/gitploy-io/gitploy/ent" gb "github.com/gitploy-io/gitploy/internal/server/global" + "github.com/gitploy-io/gitploy/pkg/e" "github.com/gitploy-io/gitploy/vo" "go.uber.org/zap" ) @@ -56,15 +57,17 @@ func (r *Repo) ListRepos(c *gin.Context) { sorted, err := strconv.ParseBool(sort) if err != nil { - r.log.Error("invalid sort value.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusBadRequest, "It was invalid request.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The \"sort\" field must be boolean.", err), + ) return } repos, err := r.i.ListReposOfUser(ctx, u, q, namespace, name, sorted, atoi(page), atoi(perPage)) if err != nil { - r.log.Error("failed to list repositories.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to list repositories.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to list repositories.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -83,7 +86,10 @@ func (r *Repo) UpdateRepo(c *gin.Context) { p := &repoPatchPayload{} var err error if err := c.ShouldBindBodyWith(p, binding.JSON); err != nil { - gb.ErrorResponse(c, http.StatusBadRequest, "It has failed to bind the body") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "It has failed to bind the body.", err), + ) return } @@ -97,14 +103,14 @@ func (r *Repo) UpdateRepo(c *gin.Context) { Secret: r.WebhookSecret, InsecureSSL: r.WebhookSSL, }); err != nil { - r.log.Error("It has failed to activate the repo.", zap.Error(err), zap.String("url", r.WebhookURL)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to activate the repository.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to activate the repository.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } } else if !*p.Active && re.Active { if re, err = r.i.DeactivateRepo(ctx, u, re); err != nil { - r.log.Error("failed to deactivate the repo.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to deactivate the repository.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to deactivate the repository.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } } @@ -115,8 +121,8 @@ func (r *Repo) UpdateRepo(c *gin.Context) { re.ConfigPath = *p.ConfigPath if re, err = r.i.UpdateRepo(ctx, re); err != nil { - r.log.Error("failed to update the repo", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to update the repository.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to update the repository.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } } diff --git a/internal/server/api/v1/repos/tag.go b/internal/server/api/v1/repos/tag.go index b2734de3..706affa0 100644 --- a/internal/server/api/v1/repos/tag.go +++ b/internal/server/api/v1/repos/tag.go @@ -25,8 +25,8 @@ func (r *Repo) ListTags(c *gin.Context) { tags, err := r.i.ListTags(ctx, u, repo, atoi(page), atoi(perPage)) if err != nil { - r.log.Error("failed to list tags.", zap.String("repo", repo.Name), zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to list tags.") + r.log.Check(gb.GetZapLogLevel(err), "Failed to list tags.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -47,7 +47,7 @@ func (r *Repo) GetTag(c *gin.Context) { t, err := r.i.GetTag(ctx, u, repo, tag) if err != nil { - gb.LogWithError(r.log, "It has failed to get the tag.", err) + r.log.Check(gb.GetZapLogLevel(err), "Failed to get the tag.").Write(zap.Error(err)) gb.ResponseWithError(c, err) return } diff --git a/internal/server/api/v1/search/search.go b/internal/server/api/v1/search/search.go index f8d0e596..8070c7fe 100644 --- a/internal/server/api/v1/search/search.go +++ b/internal/server/api/v1/search/search.go @@ -13,6 +13,7 @@ import ( "github.com/gitploy-io/gitploy/ent/approval" "github.com/gitploy-io/gitploy/ent/deployment" gb "github.com/gitploy-io/gitploy/internal/server/global" + "github.com/gitploy-io/gitploy/pkg/e" ) const ( @@ -63,27 +64,42 @@ func (s *Search) SearchDeployments(c *gin.Context) { } if o, err = strconv.ParseBool(owned); err != nil { - gb.ErrorResponse(c, http.StatusBadRequest, "Invalid format of \"owned\" parameter.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The owned must be boolean.", err), + ) return } if f, err = time.Parse(time.RFC3339, from); err != nil { - gb.ErrorResponse(c, http.StatusBadRequest, "Invalid format of \"from\" parameter, RFC3339 format only.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "Invalid format of \"from\" parameter, RFC3339 format only.", err), + ) return } if t, err = time.Parse(time.RFC3339, to); err != nil { - gb.ErrorResponse(c, http.StatusBadRequest, "Invalid format of \"to\" parameter, RFC3339 format only.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "Invalid format of \"to\" parameter, RFC3339 format only.", err), + ) return } if p, err = strconv.Atoi(page); err != nil { - gb.ErrorResponse(c, http.StatusBadRequest, "Invalid format of \"page\" parameter.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "Invalid format of \"page\" parameter.", err), + ) return } if pp, err = strconv.Atoi(perPage); err != nil { - gb.ErrorResponse(c, http.StatusBadRequest, "Invalid format of \"per_page\" parameter.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "Invalid format of \"per_page\" parameter.", err), + ) return } @@ -96,8 +112,8 @@ func (s *Search) SearchDeployments(c *gin.Context) { u := v.(*ent.User) if ds, err = s.i.SearchDeployments(ctx, u, ss, o, f.UTC(), t.UTC(), p, pp); err != nil { - s.log.Error("It has failed to search deployments.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to search deployments.") + s.log.Check(gb.GetZapLogLevel(err), "Failed to search deployments.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -132,22 +148,34 @@ func (s *Search) SearchApprovals(c *gin.Context) { } if f, err = time.Parse(time.RFC3339, from); err != nil { - gb.ErrorResponse(c, http.StatusBadRequest, "Invalid format of \"from\" parameter, RFC3339 format only.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "Invalid format of \"from\" parameter.", err), + ) return } if t, err = time.Parse(time.RFC3339, to); err != nil { - gb.ErrorResponse(c, http.StatusBadRequest, "Invalid format of \"to\" parameter, RFC3339 format only.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "Invalid format of \"to\" parameter.", err), + ) return } if p, err = strconv.Atoi(page); err != nil { - gb.ErrorResponse(c, http.StatusBadRequest, "Invalid format of \"page\" parameter.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "Invalid format of \"page\" parameter.", err), + ) return } if pp, err = strconv.Atoi(perPage); err != nil { - gb.ErrorResponse(c, http.StatusBadRequest, "Invalid format of \"per_page\" parameter.") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "Invalid format of \"per_page\" parameter.", err), + ) return } @@ -160,8 +188,8 @@ func (s *Search) SearchApprovals(c *gin.Context) { u := v.(*ent.User) if ds, err = s.i.SearchApprovals(ctx, u, ss, f.UTC(), t.UTC(), p, pp); err != nil { - s.log.Error("It has failed to search deployments.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to search deployments.") + s.log.Check(gb.GetZapLogLevel(err), "Failed to search deployments.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } diff --git a/internal/server/api/v1/stream/events.go b/internal/server/api/v1/stream/events.go index 65292172..681daf4b 100644 --- a/internal/server/api/v1/stream/events.go +++ b/internal/server/api/v1/stream/events.go @@ -10,7 +10,6 @@ import ( "context" "fmt" "math/rand" - "net/http" "time" "github.com/gin-contrib/sse" @@ -54,8 +53,8 @@ func (s *Stream) GetEvents(c *gin.Context) { events <- e } if err := s.i.SubscribeEvent(sub); err != nil { - s.log.Error("failed to subscribe notification events", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to connect.") + s.log.Check(gb.GetZapLogLevel(err), "Failed to subscribe notification events").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } diff --git a/internal/server/api/v1/sync/syncher.go b/internal/server/api/v1/sync/syncher.go index 26b466d8..ed3d487d 100644 --- a/internal/server/api/v1/sync/syncher.go +++ b/internal/server/api/v1/sync/syncher.go @@ -38,8 +38,8 @@ func (s *Syncher) Sync(c *gin.Context) { remotes, err := s.i.ListRemoteRepos(ctx, u) if err != nil { - s.log.Error("It has failed to list remote repositories.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to list remote repositories.") + s.log.Check(gb.GetZapLogLevel(err), "Failed to list remote repositories.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -62,8 +62,8 @@ func (s *Syncher) Sync(c *gin.Context) { // Delete staled perms. var cnt int if cnt, err = s.i.DeletePermsOfUserLessThanSyncedAt(ctx, u, syncTime); err != nil { - s.log.Error("It has failed to delete staled repositories.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to delete staled repositories.") + s.log.Check(gb.GetZapLogLevel(err), "Failed to delete staled repositories.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } s.log.Debug(fmt.Sprintf("Delete %d staled perms.", cnt)) diff --git a/internal/server/api/v1/users/user.go b/internal/server/api/v1/users/user.go index 10265900..fd0826d3 100644 --- a/internal/server/api/v1/users/user.go +++ b/internal/server/api/v1/users/user.go @@ -10,6 +10,7 @@ import ( "github.com/gitploy-io/gitploy/ent" gb "github.com/gitploy-io/gitploy/internal/server/global" + "github.com/gitploy-io/gitploy/pkg/e" "github.com/gitploy-io/gitploy/vo" ) @@ -42,17 +43,23 @@ func (u *User) ListUsers(c *gin.Context) { ) if p, err = strconv.Atoi(c.DefaultQuery("page", "1")); err != nil { - gb.ErrorResponse(c, http.StatusBadRequest, "Invalid format \"page\".") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The page must be number.", err), + ) } if pp, err = strconv.Atoi(c.DefaultQuery("per_page", "30")); err != nil { - gb.ErrorResponse(c, http.StatusBadRequest, "Invalid format \"per_page\".") + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The per_page must be number.", err), + ) } us, err := u.i.ListUsers(ctx, q, p, pp) if err != nil { - u.log.Error("It has failed to list users.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to list users.") + u.log.Check(gb.GetZapLogLevel(err), "Failed to list users.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -68,34 +75,36 @@ func (u *User) UpdateUser(c *gin.Context) { ) if id, err = strconv.ParseInt(c.Param("id"), 10, 64); err != nil { - u.log.Error("Invalid ID of user.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusBadRequest, "Invalid ID of user.") + u.log.Warn("The id must be number.", zap.Error(err)) + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The id must be number.", err), + ) return } p := &userPatchPayload{} if err := c.ShouldBindBodyWith(p, binding.JSON); err != nil { - u.log.Error("It has failed to binding the payload.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusBadRequest, "It has failed to binding the payload.") + u.log.Warn("It has failed to binding the payload.", zap.Error(err)) + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "It has failed to binding the payload.", err), + ) return } du, err := u.i.FindUserByID(ctx, id) - if ent.IsNotFound(err) { - u.log.Warn("The deleting user is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusUnprocessableEntity, "The deleting user is not found.") - return - } else if err != nil { - u.log.Error("It has failed to get the user.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to get the user.") + if err != nil { + u.log.Check(gb.GetZapLogLevel(err), "Failed to find the user.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } if p.Admin != nil { du.Admin = *p.Admin if du, err = u.i.UpdateUser(ctx, du); err != nil { - u.log.Error("It has failed to patch the user.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to delete the user.") + u.log.Check(gb.GetZapLogLevel(err), "Failed to update the user.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } } @@ -112,25 +121,24 @@ func (u *User) DeleteUser(c *gin.Context) { ) if id, err = strconv.ParseInt(c.Param("id"), 10, 64); err != nil { - u.log.Error("Invalid ID of user.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusBadRequest, "Invalid ID of user.") + u.log.Warn("The id must be number.", zap.Error(err)) + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "The id must be number.", err), + ) return } du, err := u.i.FindUserByID(ctx, id) - if ent.IsNotFound(err) { - u.log.Warn("The deleting user is not found.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusUnprocessableEntity, "The deleting user is not found.") - return - } else if err != nil { - u.log.Error("It has failed to get the user.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to get the user.") + if err != nil { + u.log.Check(gb.GetZapLogLevel(err), "Failed to find the user.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } if err := u.i.DeleteUser(ctx, du); err != nil { - u.log.Error("It has failed to delete the user.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to delete the user.") + u.log.Check(gb.GetZapLogLevel(err), "Failed to delete the user.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -145,8 +153,8 @@ func (u *User) GetMyUser(c *gin.Context) { uv, err := u.i.FindUserByID(ctx, uv.ID) if err != nil { - u.log.Error("failed to find the user.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to find the user.") + u.log.Check(gb.GetZapLogLevel(err), "Failed to find the user.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } @@ -165,8 +173,8 @@ func (u *User) GetRateLimit(c *gin.Context) { ) if rl, err = u.i.GetRateLimit(ctx, uv); err != nil { - u.log.Error("It has failed to get the rate-limit.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to get the rate-limit.") + u.log.Check(gb.GetZapLogLevel(err), "Failed to get the rate-limit.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } diff --git a/internal/server/global/helper.go b/internal/server/global/helper.go new file mode 100644 index 00000000..5e88308f --- /dev/null +++ b/internal/server/global/helper.go @@ -0,0 +1,19 @@ +package global + +import ( + "go.uber.org/zap/zapcore" + + "github.com/gitploy-io/gitploy/pkg/e" +) + +func GetZapLogLevel(err error) zapcore.Level { + if !e.IsError(err) { + return zapcore.ErrorLevel + } + + if err.(*e.Error).Code == e.ErrorCodeInternalError { + return zapcore.ErrorLevel + } + + return zapcore.WarnLevel +} diff --git a/internal/server/global/http.go b/internal/server/global/http.go index 8823e10b..fed8676b 100644 --- a/internal/server/global/http.go +++ b/internal/server/global/http.go @@ -11,12 +11,6 @@ func Response(c *gin.Context, httpCode int, data interface{}) { c.JSON(httpCode, data) } -func ErrorResponse(c *gin.Context, httpCode int, message string) { - c.JSON(httpCode, map[string]string{ - "message": message, - }) -} - func ResponseWithError(c *gin.Context, err error) { if ge, ok := err.(*e.Error); ok { c.JSON(e.GetHttpCode(ge.Code), map[string]string{ @@ -32,7 +26,7 @@ func ResponseWithError(c *gin.Context, err error) { }) } -// ResponseWithStatusAndError +// ResponseWithStatusAndError overrides the HTTP status. func ResponseWithStatusAndError(c *gin.Context, status int, err error) { if ge, ok := err.(*e.Error); ok { c.JSON(status, map[string]string{ @@ -48,8 +42,33 @@ func ResponseWithStatusAndError(c *gin.Context, status int, err error) { }) } -func AbortWithErrorResponse(c *gin.Context, httpCode int, message string) { - c.AbortWithStatusJSON(httpCode, map[string]string{ - "message": message, +func AbortWithError(c *gin.Context, err error) { + if ge, ok := err.(*e.Error); ok { + c.AbortWithStatusJSON(e.GetHttpCode(ge.Code), map[string]string{ + "code": string(ge.Code), + "message": ge.Message, + }) + return + } + + c.AbortWithStatusJSON(http.StatusInternalServerError, map[string]string{ + "code": string(e.ErrorCodeInternalError), + "message": err.Error(), + }) +} + +// AbortWithStatusAndError overrides the HTTP status. +func AbortWithStatusAndError(c *gin.Context, status int, err error) { + if ge, ok := err.(*e.Error); ok { + c.AbortWithStatusJSON(status, map[string]string{ + "code": string(ge.Code), + "message": ge.Message, + }) + return + } + + c.AbortWithStatusJSON(http.StatusInternalServerError, map[string]string{ + "code": string(e.ErrorCodeInternalError), + "message": err.Error(), }) } diff --git a/internal/server/global/log.go b/internal/server/global/log.go deleted file mode 100644 index e8b174ca..00000000 --- a/internal/server/global/log.go +++ /dev/null @@ -1,23 +0,0 @@ -package global - -import ( - "go.uber.org/zap" - - "github.com/gitploy-io/gitploy/pkg/e" -) - -// LogWithError handles the level of log by the error code. -func LogWithError(logger *zap.Logger, message string, err error) { - ge, ok := err.(*e.Error) - if !ok { - logger.Error(message, zap.Error(err)) - return - } - - if ge.Code == e.ErrorCodeInternalError { - logger.Error(message, zap.Error(err)) - return - } - - logger.Warn(message, zap.Error(err)) -} diff --git a/internal/server/hooks/hook.go b/internal/server/hooks/hook.go index c28ef2d0..79ce7cae 100644 --- a/internal/server/hooks/hook.go +++ b/internal/server/hooks/hook.go @@ -12,6 +12,7 @@ import ( "github.com/gitploy-io/gitploy/ent/deployment" "github.com/gitploy-io/gitploy/ent/event" gb "github.com/gitploy-io/gitploy/internal/server/global" + "github.com/gitploy-io/gitploy/pkg/e" ) type ( @@ -51,7 +52,10 @@ func (h *Hooks) HandleHook(c *gin.Context) { return } - gb.ErrorResponse(c, http.StatusBadRequest, "It is invalid request.") + gb.ResponseWithError( + c, + e.NewError(e.ErrorCodeInvalidRequest, nil), + ) } func (h *Hooks) handleGithubHook(c *gin.Context) { @@ -62,10 +66,13 @@ func (h *Hooks) handleGithubHook(c *gin.Context) { return } - e := &github.DeploymentStatusEvent{} - if err := c.ShouldBindBodyWith(e, binding.JSON); err != nil { - h.log.Error("failed to bind the payload.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusBadRequest, "It is invalid request.") + evt := &github.DeploymentStatusEvent{} + if err := c.ShouldBindBodyWith(evt, binding.JSON); err != nil { + h.log.Warn("failed to bind the payload.", zap.Error(err)) + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "It has failed to bind the payload.", err), + ) return } @@ -80,34 +87,37 @@ func (h *Hooks) handleGithubHook(c *gin.Context) { sig := c.GetHeader(headerGithubSignature) if err := github.ValidateSignature(sig, payload, []byte(secret)); err != nil { - h.log.Error("failed to validate the signature.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusBadRequest, "It has failed to validate the signature.") + h.log.Warn("Failed to validate the signature.", zap.Error(err)) + gb.ResponseWithError( + c, + e.NewErrorWithMessage(e.ErrorCodeInvalidRequest, "It has failed to validate the signature.", err), + ) return } } // Convert event to the deployment status. - ds := mapGithubDeploymentStatus(e) + ds := mapGithubDeploymentStatus(evt) - uid := *e.Deployment.ID + uid := *evt.Deployment.ID d, err := h.i.FindDeploymentByUID(ctx, uid) if err != nil { - h.log.Error("It has failed to find the deployment by UID.", zap.Int64("deployment_uid", uid), zap.Error(err)) - gb.ErrorResponse(c, http.StatusBadRequest, "It has failed to find the deployment by UID.") + h.log.Check(gb.GetZapLogLevel(err), "Failed to find the deployment by UID.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } ds.DeploymentID = d.ID if ds, err = h.i.SyncDeploymentStatus(ctx, ds); err != nil { - h.log.Error("It has failed to create a new the deployment status.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to create a new the deployment status.") + h.log.Check(gb.GetZapLogLevel(err), "Failed to create a new the deployment status.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } d.Status = mapGithubState(ds.Status) if _, err := h.i.UpdateDeployment(ctx, d); err != nil { - h.log.Error("It has failed to update the deployment status.", zap.Error(err)) - gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to update the deployment status.") + h.log.Check(gb.GetZapLogLevel(err), "Failed to update the deployment.").Write(zap.Error(err)) + gb.ResponseWithError(c, err) return } diff --git a/internal/server/slack/deploy.go b/internal/server/slack/deploy.go index 69066550..674358b4 100644 --- a/internal/server/slack/deploy.go +++ b/internal/server/slack/deploy.go @@ -262,7 +262,7 @@ func (s *Slack) interactDeploy(c *gin.Context) { // Validate the entity is processible. _, err := s.getCommitSha(ctx, cu.Edges.User, cb.Edges.Repo, sm.Type, sm.Ref) - if e.HasErrorCode(err, e.ErrorCodeRefNotFound) { + if e.HasErrorCode(err, e.ErrorCodeNotFound) { c.JSON(http.StatusOK, buildErrorsPayload(map[string]string{ blockRef: "The reference is not found.", })) diff --git a/pkg/e/code.go b/pkg/e/code.go index d7e5532c..f96a4795 100644 --- a/pkg/e/code.go +++ b/pkg/e/code.go @@ -6,9 +6,6 @@ import ( ) const ( - // ErrorCodeConfigNotFound is that the configuration file is not found. - // TODO: migrate into ErrorCodeNotFound - ErrorCodeConfigNotFound ErrorCode = "config_not_found" // ErrorCodeConfigParseError is that an error occurs when it parse the file. ErrorCodeConfigParseError ErrorCode = "config_parse_error" @@ -23,17 +20,17 @@ const ( // ErrorCodeDeploymentUndeployable is that the merge conflict occurs or a commit status has failed. ErrorCodeDeploymentUndeployable ErrorCode = "deployment_undeployable" - // ErrorCodeLicenseDecode is that the license. + // ErrorCodeLicenseDecode is the error when the license is decoded. ErrorCodeLicenseDecode ErrorCode = "license_decode" - - // ErrorCodeRefNotFound is that the ref is not found. - // TODO: migrate into ErrorCodeNotFound - ErrorCodeRefNotFound ErrorCode = "ref_not_found" + // ErrorCodeLicenseRequired is that the license is required. + ErrorCodeLicenseRequired ErrorCode = "license_required" // General purpose error codes. - ErrorCodeInvalidRequest ErrorCode = "invalid_request" - ErrorCodeNotFound ErrorCode = "not_found" - ErrorCodeInternalError ErrorCode = "internal_error" + ErrorCodeInvalidRequest ErrorCode = "invalid_request" + ErrorPermissionRequired ErrorCode = "permission_required" + ErrorCodeNotFound ErrorCode = "not_found" + ErrorCodeUnprocessableEntity ErrorCode = "unprocessable_entity" + ErrorCodeInternalError ErrorCode = "internal_error" ) type ( @@ -64,7 +61,7 @@ func NewErrorWithMessage(code ErrorCode, message string, wrap error) *Error { } func (e *Error) Error() string { - return fmt.Sprintf("code: %s, message: %s, wrap: %s", e.Code, GetMessage(e.Code), e.Wrap) + return fmt.Sprintf("code: %s, message: %s, wrap: %s", e.Code, e.Message, e.Wrap) } func (e *Error) Unwrap() error { diff --git a/pkg/e/trans.go b/pkg/e/trans.go index cbd87564..d1b42a85 100644 --- a/pkg/e/trans.go +++ b/pkg/e/trans.go @@ -3,7 +3,6 @@ package e import "net/http" var messages = map[ErrorCode]string{ - ErrorCodeConfigNotFound: "The configuration file is not found.", ErrorCodeConfigParseError: "The configuration is invalid.", ErrorCodeDeploymentConflict: "The conflict occurs, please retry.", ErrorCodeDeploymentInvalid: "The validation has failed.", @@ -11,9 +10,11 @@ var messages = map[ErrorCode]string{ ErrorCodeDeploymentUnapproved: "The deployment is not approved", ErrorCodeDeploymentUndeployable: "There is merge conflict or a commit status check failed.", ErrorCodeLicenseDecode: "Decoding the license is failed.", - ErrorCodeRefNotFound: "The reference is not found.", + ErrorCodeLicenseRequired: "The license is required.", ErrorCodeInvalidRequest: "Invalid request parameter.", + ErrorPermissionRequired: "The permission is required", ErrorCodeNotFound: "It is not found.", + ErrorCodeUnprocessableEntity: "Invalid request payload.", ErrorCodeInternalError: "Server internal error.", } @@ -27,7 +28,6 @@ func GetMessage(code ErrorCode) string { } var httpCodes = map[ErrorCode]int{ - ErrorCodeConfigNotFound: http.StatusNotFound, ErrorCodeConfigParseError: http.StatusUnprocessableEntity, ErrorCodeDeploymentConflict: http.StatusUnprocessableEntity, ErrorCodeDeploymentInvalid: http.StatusUnprocessableEntity, @@ -35,9 +35,11 @@ var httpCodes = map[ErrorCode]int{ ErrorCodeDeploymentUnapproved: http.StatusUnprocessableEntity, ErrorCodeDeploymentUndeployable: http.StatusUnprocessableEntity, ErrorCodeLicenseDecode: http.StatusUnprocessableEntity, - ErrorCodeRefNotFound: http.StatusNotFound, + ErrorCodeLicenseRequired: http.StatusPaymentRequired, ErrorCodeInvalidRequest: http.StatusBadRequest, ErrorCodeNotFound: http.StatusNotFound, + ErrorPermissionRequired: http.StatusForbidden, + ErrorCodeUnprocessableEntity: http.StatusUnprocessableEntity, ErrorCodeInternalError: http.StatusInternalServerError, } diff --git a/vo/config.go b/vo/config.go index 44b7fffb..7784b982 100644 --- a/vo/config.go +++ b/vo/config.go @@ -2,11 +2,12 @@ package vo import ( "encoding/json" - "fmt" "strconv" "github.com/drone/envsubst" "gopkg.in/yaml.v3" + + eutil "github.com/gitploy-io/gitploy/pkg/e" ) type ( @@ -94,7 +95,7 @@ func (e *Env) IsApprovalEabled() bool { func (e *Env) Eval(v *EvalValues) error { byts, err := json.Marshal(e) if err != nil { - return fmt.Errorf("failed to marshal the env: %w", err) + return eutil.NewError(eutil.ErrorCodeConfigParseError, err) } // Evaluates variables @@ -124,11 +125,11 @@ func (e *Env) Eval(v *EvalValues) error { evalued, err := envsubst.Eval(string(byts), mapper) if err != nil { - return fmt.Errorf("failed to eval variables: %w", err) + return eutil.NewError(eutil.ErrorCodeConfigParseError, err) } if err := json.Unmarshal([]byte(evalued), e); err != nil { - return fmt.Errorf("failed to unmarshal to the env: %w", err) + return eutil.NewError(eutil.ErrorCodeConfigParseError, err) } return nil