Add label feature (#2073)

* Address PR comments
* Add list label from ancestor scopes query param
* Add label feature
This commit is contained in:
Darko Draskovic 2024-07-25 14:57:56 +00:00 committed by Harness
parent d36bf54f99
commit 81fcd524c8
77 changed files with 5330 additions and 68 deletions

View File

@ -26,6 +26,7 @@ import (
"github.com/harness/gitness/app/services/codecomments" "github.com/harness/gitness/app/services/codecomments"
"github.com/harness/gitness/app/services/codeowners" "github.com/harness/gitness/app/services/codeowners"
"github.com/harness/gitness/app/services/importer" "github.com/harness/gitness/app/services/importer"
"github.com/harness/gitness/app/services/label"
locker "github.com/harness/gitness/app/services/locker" locker "github.com/harness/gitness/app/services/locker"
"github.com/harness/gitness/app/services/protection" "github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/services/pullreq" "github.com/harness/gitness/app/services/pullreq"
@ -65,6 +66,7 @@ type Controller struct {
codeOwners *codeowners.Service codeOwners *codeowners.Service
locker *locker.Locker locker *locker.Locker
importer *importer.PullReq importer *importer.PullReq
labelSvc *label.Service
} }
func NewController( func NewController(
@ -91,6 +93,7 @@ func NewController(
codeowners *codeowners.Service, codeowners *codeowners.Service,
locker *locker.Locker, locker *locker.Locker,
importer *importer.PullReq, importer *importer.PullReq,
labelSvc *label.Service,
) *Controller { ) *Controller {
return &Controller{ return &Controller{
tx: tx, tx: tx,
@ -116,6 +119,7 @@ func NewController(
codeOwners: codeowners, codeOwners: codeowners,
locker: locker, locker: locker,
importer: importer, importer: importer,
labelSvc: labelSvc,
} }
} }

View File

@ -0,0 +1,55 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pullreq
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// AssignLabel assigns a label to a pull request .
func (c *Controller) AssignLabel(
ctx context.Context,
session *auth.Session,
repoRef string,
pullreqNum int64,
in *types.PullReqCreateInput,
) (*types.PullReqLabel, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to target repo: %w", err)
}
if err := in.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate input: %w", err)
}
pullreq, err := c.pullreqStore.FindByNumber(ctx, repo.ID, pullreqNum)
if err != nil {
return nil, fmt.Errorf("failed to find pullreq: %w", err)
}
pullreqLabel, err := c.labelSvc.AssignToPullReq(
ctx, session.Principal.ID, pullreq.ID, repo.ID, repo.ParentID, in)
if err != nil {
return nil, fmt.Errorf("failed to create pullreq label: %w", err)
}
return pullreqLabel, nil
}

View File

@ -0,0 +1,51 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pullreq
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// ListLabels list labels assigned to a specified pullreq.
func (c *Controller) ListLabels(
ctx context.Context,
session *auth.Session,
repoRef string,
pullreqNum int64,
filter *types.AssignableLabelFilter,
) (*types.ScopesLabels, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to target repo: %w", err)
}
pullreq, err := c.pullreqStore.FindByNumber(ctx, repo.ID, pullreqNum)
if err != nil {
return nil, fmt.Errorf("failed to find pullreq: %w", err)
}
scopeLabelsMap, err := c.labelSvc.ListPullReqLabels(
ctx, repo, repo.ParentID, pullreq.ID, filter)
if err != nil {
return nil, fmt.Errorf("failed to list pullreq labels: %w", err)
}
return scopeLabelsMap, nil
}

View File

@ -0,0 +1,49 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pullreq
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
// UnassignLabel removes a label from a pull request.
func (c *Controller) UnassignLabel(
ctx context.Context,
session *auth.Session,
repoRef string,
pullreqNum int64,
labelID int64,
) error {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush)
if err != nil {
return fmt.Errorf("failed to acquire access to target repo: %w", err)
}
pullreq, err := c.pullreqStore.FindByNumber(ctx, repo.ID, pullreqNum)
if err != nil {
return fmt.Errorf("failed to find pullreq: %w", err)
}
if err := c.labelSvc.UnassignFromPullReq(
ctx, repo.ID, repo.ParentID, pullreq.ID, labelID); err != nil {
return fmt.Errorf("failed to delete pullreq label: %w", err)
}
return nil
}

View File

@ -20,6 +20,7 @@ import (
"github.com/harness/gitness/app/services/codecomments" "github.com/harness/gitness/app/services/codecomments"
"github.com/harness/gitness/app/services/codeowners" "github.com/harness/gitness/app/services/codeowners"
"github.com/harness/gitness/app/services/importer" "github.com/harness/gitness/app/services/importer"
"github.com/harness/gitness/app/services/label"
"github.com/harness/gitness/app/services/locker" "github.com/harness/gitness/app/services/locker"
"github.com/harness/gitness/app/services/protection" "github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/services/pullreq" "github.com/harness/gitness/app/services/pullreq"
@ -41,21 +42,24 @@ func ProvideController(tx dbtx.Transactor, urlProvider url.Provider, authorizer
pullReqStore store.PullReqStore, pullReqActivityStore store.PullReqActivityStore, pullReqStore store.PullReqStore, pullReqActivityStore store.PullReqActivityStore,
codeCommentsView store.CodeCommentView, codeCommentsView store.CodeCommentView,
pullReqReviewStore store.PullReqReviewStore, pullReqReviewerStore store.PullReqReviewerStore, pullReqReviewStore store.PullReqReviewStore, pullReqReviewerStore store.PullReqReviewerStore,
repoStore store.RepoStore, principalStore store.PrincipalStore, principalInfoCache store.PrincipalInfoCache, repoStore store.RepoStore,
principalStore store.PrincipalStore, principalInfoCache store.PrincipalInfoCache,
fileViewStore store.PullReqFileViewStore, membershipStore store.MembershipStore, fileViewStore store.PullReqFileViewStore, membershipStore store.MembershipStore,
checkStore store.CheckStore, checkStore store.CheckStore,
rpcClient git.Interface, eventReporter *pullreqevents.Reporter, codeCommentMigrator *codecomments.Migrator, rpcClient git.Interface, eventReporter *pullreqevents.Reporter, codeCommentMigrator *codecomments.Migrator,
pullreqService *pullreq.Service, ruleManager *protection.Manager, sseStreamer sse.Streamer, pullreqService *pullreq.Service, ruleManager *protection.Manager, sseStreamer sse.Streamer,
codeOwners *codeowners.Service, locker *locker.Locker, importer *importer.PullReq, codeOwners *codeowners.Service, locker *locker.Locker, importer *importer.PullReq,
labelSvc *label.Service,
) *Controller { ) *Controller {
return NewController(tx, urlProvider, authorizer, return NewController(tx, urlProvider, authorizer,
pullReqStore, pullReqActivityStore, pullReqStore, pullReqActivityStore,
codeCommentsView, codeCommentsView,
pullReqReviewStore, pullReqReviewerStore, pullReqReviewStore, pullReqReviewerStore,
repoStore, principalStore, principalInfoCache, repoStore,
principalStore, principalInfoCache,
fileViewStore, membershipStore, fileViewStore, membershipStore,
checkStore, checkStore,
rpcClient, eventReporter, rpcClient, eventReporter,
codeCommentMigrator, codeCommentMigrator,
pullreqService, ruleManager, sseStreamer, codeOwners, locker, importer) pullreqService, ruleManager, sseStreamer, codeOwners, locker, importer, labelSvc)
} }

View File

@ -30,6 +30,7 @@ import (
"github.com/harness/gitness/app/services/codeowners" "github.com/harness/gitness/app/services/codeowners"
"github.com/harness/gitness/app/services/importer" "github.com/harness/gitness/app/services/importer"
"github.com/harness/gitness/app/services/keywordsearch" "github.com/harness/gitness/app/services/keywordsearch"
"github.com/harness/gitness/app/services/label"
"github.com/harness/gitness/app/services/locker" "github.com/harness/gitness/app/services/locker"
"github.com/harness/gitness/app/services/protection" "github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/services/publicaccess" "github.com/harness/gitness/app/services/publicaccess"
@ -92,6 +93,7 @@ type Controller struct {
identifierCheck check.RepoIdentifier identifierCheck check.RepoIdentifier
repoCheck Check repoCheck Check
publicAccess publicaccess.Service publicAccess publicaccess.Service
labelSvc *label.Service
} }
func NewController( func NewController(
@ -119,6 +121,7 @@ func NewController(
identifierCheck check.RepoIdentifier, identifierCheck check.RepoIdentifier,
repoCheck Check, repoCheck Check,
publicAccess publicaccess.Service, publicAccess publicaccess.Service,
labelSvc *label.Service,
) *Controller { ) *Controller {
return &Controller{ return &Controller{
defaultBranch: config.Git.DefaultBranch, defaultBranch: config.Git.DefaultBranch,
@ -145,6 +148,7 @@ func NewController(
identifierCheck: identifierCheck, identifierCheck: identifierCheck,
repoCheck: repoCheck, repoCheck: repoCheck,
publicAccess: publicAccess, publicAccess: publicAccess,
labelSvc: labelSvc,
} }
} }

View File

@ -0,0 +1,49 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// DefineLabel defines a new label for the specified repository.
func (c *Controller) DefineLabel(
ctx context.Context,
session *auth.Session,
repoRef string,
in *types.DefineLabelInput,
) (*types.Label, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to repo: %w", err)
}
if err := in.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate input: %w", err)
}
label, err := c.labelSvc.Define(
ctx, session.Principal.ID, nil, &repo.ID, in)
if err != nil {
return nil, fmt.Errorf("failed to create repo label: %w", err)
}
return label, nil
}

View File

@ -0,0 +1,42 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
// DeleteLabel deletes a label for the specified repository.
func (c *Controller) DeleteLabel(
ctx context.Context,
session *auth.Session,
repoRef string,
key string,
) error {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return fmt.Errorf("failed to acquire access to repo: %w", err)
}
if err := c.labelSvc.Delete(ctx, nil, &repo.ID, key); err != nil {
return fmt.Errorf("failed to delete repo label: %w", err)
}
return nil
}

View File

@ -0,0 +1,44 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// ListLabels lists all labels defined for the specified repository.
func (c *Controller) ListLabels(
ctx context.Context,
session *auth.Session,
repoRef string,
filter *types.LabelFilter,
) ([]*types.Label, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to repo: %w", err)
}
labels, err := c.labelSvc.List(ctx, &repo.ParentID, &repo.ID, filter)
if err != nil {
return nil, fmt.Errorf("failed to list repo labels: %w", err)
}
return labels, nil
}

View File

@ -0,0 +1,49 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// SaveLabel creates or updates a label and possibly label values for the specified repository.
func (c *Controller) SaveLabel(
ctx context.Context,
session *auth.Session,
repoRef string,
in *types.SaveInput,
) (*types.LabelWithValues, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to repo: %w", err)
}
if err := in.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate input: %w", err)
}
labelWithValues, err := c.labelSvc.Save(
ctx, session.Principal.ID, nil, &repo.ID, in)
if err != nil {
return nil, fmt.Errorf("failed to save label: %w", err)
}
return labelWithValues, nil
}

View File

@ -0,0 +1,49 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// UpdateLabel updates a label for the specified repository.
func (c *Controller) UpdateLabel(
ctx context.Context,
session *auth.Session,
repoRef string,
key string,
in *types.UpdateLabelInput,
) (*types.Label, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to repo: %w", err)
}
if err := in.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate input: %w", err)
}
label, err := c.labelSvc.Update(ctx, session.Principal.ID, nil, &repo.ID, key, in)
if err != nil {
return nil, fmt.Errorf("failed to update repo label: %w", err)
}
return label, nil
}

View File

@ -0,0 +1,65 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// DefineLabelValue defines a new label value for the specified repository.
func (c *Controller) DefineLabelValue(
ctx context.Context,
session *auth.Session,
repoRef string,
key string,
in *types.DefineValueInput,
) (*types.LabelValue, error) {
repo, err := GetRepo(ctx, c.repoStore, repoRef, []enum.RepoState{enum.RepoStateActive})
if err != nil {
return nil, fmt.Errorf("failed to acquire access to repo: %w", err)
}
if err := in.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate input: %w", err)
}
label, err := c.labelSvc.Find(ctx, nil, &repo.ID, key)
if err != nil {
return nil, fmt.Errorf("failed to find repo label: %w", err)
}
permission := enum.PermissionRepoEdit
if label.Type == enum.LabelTypeDynamic {
permission = enum.PermissionRepoPush
}
if err = apiauth.CheckRepo(
ctx, c.authorizer, session, repo, permission); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}
value, err := c.labelSvc.DefineValue(ctx, session.Principal.ID, label.ID, in)
if err != nil {
return nil, fmt.Errorf("failed to create repo label value: %w", err)
}
return value, nil
}

View File

@ -0,0 +1,43 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
// DeleteLabelValue deletes a label value for the specified repository.
func (c *Controller) DeleteLabelValue(
ctx context.Context,
session *auth.Session,
repoRef string,
key string,
value string,
) error {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return fmt.Errorf("failed to acquire access to repo: %w", err)
}
if err := c.labelSvc.DeleteValue(ctx, nil, &repo.ID, key, value); err != nil {
return fmt.Errorf("failed to delete repo label value: %w", err)
}
return nil
}

View File

@ -0,0 +1,45 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// ListLabelValues lists all label values defined for the specified repository.
func (c *Controller) ListLabelValues(
ctx context.Context,
session *auth.Session,
repoRef string,
key string,
filter *types.ListQueryFilter,
) ([]*types.LabelValue, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to repo: %w", err)
}
values, err := c.labelSvc.ListValues(ctx, nil, &repo.ID, key, filter)
if err != nil {
return nil, fmt.Errorf("failed to list repo label values: %w", err)
}
return values, nil
}

View File

@ -0,0 +1,52 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// UpdateLabelValue updates a label value for the specified label and repository.
func (c *Controller) UpdateLabelValue(
ctx context.Context,
session *auth.Session,
repoRef string,
key string,
value string,
in *types.UpdateValueInput,
) (*types.LabelValue, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to repo: %w", err)
}
label, err := c.labelSvc.Find(ctx, nil, &repo.ID, key)
if err != nil {
return nil, fmt.Errorf("failed to find repo label: %w", err)
}
labelValue, err := c.labelSvc.UpdateValue(
ctx, session.Principal.ID, label.ID, value, in)
if err != nil {
return nil, fmt.Errorf("failed to update repo label value: %w", err)
}
return labelValue, nil
}

View File

@ -21,6 +21,7 @@ import (
"github.com/harness/gitness/app/services/codeowners" "github.com/harness/gitness/app/services/codeowners"
"github.com/harness/gitness/app/services/importer" "github.com/harness/gitness/app/services/importer"
"github.com/harness/gitness/app/services/keywordsearch" "github.com/harness/gitness/app/services/keywordsearch"
"github.com/harness/gitness/app/services/label"
"github.com/harness/gitness/app/services/locker" "github.com/harness/gitness/app/services/locker"
"github.com/harness/gitness/app/services/protection" "github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/services/publicaccess" "github.com/harness/gitness/app/services/publicaccess"
@ -67,13 +68,14 @@ func ProvideController(
identifierCheck check.RepoIdentifier, identifierCheck check.RepoIdentifier,
repoChecks Check, repoChecks Check,
publicAccess publicaccess.Service, publicAccess publicaccess.Service,
labelSvc *label.Service,
) *Controller { ) *Controller {
return NewController(config, tx, urlProvider, return NewController(config, tx, urlProvider,
authorizer, authorizer,
repoStore, spaceStore, pipelineStore, repoStore, spaceStore, pipelineStore,
principalStore, ruleStore, settings, principalInfoCache, protectionManager, rpcClient, importer, principalStore, ruleStore, settings, principalInfoCache, protectionManager, rpcClient, importer,
codeOwners, reporeporter, indexer, limiter, locker, auditService, mtxManager, identifierCheck, codeOwners, reporeporter, indexer, limiter, locker, auditService, mtxManager, identifierCheck,
repoChecks, publicAccess) repoChecks, publicAccess, labelSvc)
} }
func ProvideRepoCheck() Check { func ProvideRepoCheck() Check {

View File

@ -24,6 +24,7 @@ import (
"github.com/harness/gitness/app/services/exporter" "github.com/harness/gitness/app/services/exporter"
"github.com/harness/gitness/app/services/gitspace" "github.com/harness/gitness/app/services/gitspace"
"github.com/harness/gitness/app/services/importer" "github.com/harness/gitness/app/services/importer"
"github.com/harness/gitness/app/services/label"
"github.com/harness/gitness/app/services/publicaccess" "github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/sse" "github.com/harness/gitness/app/sse"
"github.com/harness/gitness/app/store" "github.com/harness/gitness/app/store"
@ -62,27 +63,30 @@ func (s SpaceOutput) MarshalJSON() ([]byte, error) {
type Controller struct { type Controller struct {
nestedSpacesEnabled bool nestedSpacesEnabled bool
tx dbtx.Transactor tx dbtx.Transactor
urlProvider url.Provider urlProvider url.Provider
sseStreamer sse.Streamer sseStreamer sse.Streamer
identifierCheck check.SpaceIdentifier identifierCheck check.SpaceIdentifier
authorizer authz.Authorizer authorizer authz.Authorizer
spacePathStore store.SpacePathStore spacePathStore store.SpacePathStore
pipelineStore store.PipelineStore pipelineStore store.PipelineStore
secretStore store.SecretStore secretStore store.SecretStore
connectorStore store.ConnectorStore connectorStore store.ConnectorStore
templateStore store.TemplateStore templateStore store.TemplateStore
spaceStore store.SpaceStore spaceStore store.SpaceStore
repoStore store.RepoStore repoStore store.RepoStore
principalStore store.PrincipalStore principalStore store.PrincipalStore
repoCtrl *repo.Controller repoCtrl *repo.Controller
membershipStore store.MembershipStore membershipStore store.MembershipStore
importer *importer.Repository importer *importer.Repository
exporter *exporter.Repository exporter *exporter.Repository
resourceLimiter limiter.ResourceLimiter resourceLimiter limiter.ResourceLimiter
publicAccess publicaccess.Service publicAccess publicaccess.Service
auditService audit.Service auditService audit.Service
gitspaceSvc *gitspace.Service gitspaceSvc *gitspace.Service
gitspaceConfigStore store.GitspaceConfigStore
gitspaceInstanceStore store.GitspaceInstanceStore
labelSvc *label.Service
} }
func NewController(config *types.Config, tx dbtx.Transactor, urlProvider url.Provider, func NewController(config *types.Config, tx dbtx.Transactor, urlProvider url.Provider,
@ -93,29 +97,34 @@ func NewController(config *types.Config, tx dbtx.Transactor, urlProvider url.Pro
membershipStore store.MembershipStore, importer *importer.Repository, exporter *exporter.Repository, membershipStore store.MembershipStore, importer *importer.Repository, exporter *exporter.Repository,
limiter limiter.ResourceLimiter, publicAccess publicaccess.Service, auditService audit.Service, limiter limiter.ResourceLimiter, publicAccess publicaccess.Service, auditService audit.Service,
gitspaceSvc *gitspace.Service, gitspaceSvc *gitspace.Service,
gitspaceStore store.GitspaceConfigStore, gitspaceInstanceStore store.GitspaceInstanceStore,
labelSvc *label.Service,
) *Controller { ) *Controller {
return &Controller{ return &Controller{
nestedSpacesEnabled: config.NestedSpacesEnabled, nestedSpacesEnabled: config.NestedSpacesEnabled,
tx: tx, tx: tx,
urlProvider: urlProvider, urlProvider: urlProvider,
sseStreamer: sseStreamer, sseStreamer: sseStreamer,
identifierCheck: identifierCheck, identifierCheck: identifierCheck,
authorizer: authorizer, authorizer: authorizer,
spacePathStore: spacePathStore, spacePathStore: spacePathStore,
pipelineStore: pipelineStore, pipelineStore: pipelineStore,
secretStore: secretStore, secretStore: secretStore,
connectorStore: connectorStore, connectorStore: connectorStore,
templateStore: templateStore, templateStore: templateStore,
spaceStore: spaceStore, spaceStore: spaceStore,
repoStore: repoStore, repoStore: repoStore,
principalStore: principalStore, principalStore: principalStore,
repoCtrl: repoCtrl, repoCtrl: repoCtrl,
membershipStore: membershipStore, membershipStore: membershipStore,
importer: importer, importer: importer,
exporter: exporter, exporter: exporter,
resourceLimiter: limiter, resourceLimiter: limiter,
publicAccess: publicAccess, publicAccess: publicAccess,
auditService: auditService, auditService: auditService,
gitspaceSvc: gitspaceSvc, gitspaceSvc: gitspaceSvc,
gitspaceConfigStore: gitspaceStore,
gitspaceInstanceStore: gitspaceInstanceStore,
labelSvc: labelSvc,
} }
} }

View File

@ -43,12 +43,12 @@ type ImportRepositoriesOutput struct {
DuplicateRepos []*repoctrl.RepositoryOutput `json:"duplicate_repos"` // repos which already exist in the space. DuplicateRepos []*repoctrl.RepositoryOutput `json:"duplicate_repos"` // repos which already exist in the space.
} }
// getSpaceCheckAuthRepoCreation checks whether the user has permissions to create repos // getSpaceCheckAuth checks whether the user has repo permissions permission.
// in the given space. func (c *Controller) getSpaceCheckAuth(
func (c *Controller) getSpaceCheckAuthRepoCreation(
ctx context.Context, ctx context.Context,
session *auth.Session, session *auth.Session,
spaceRef string, spaceRef string,
permission enum.Permission,
) (*types.Space, error) { ) (*types.Space, error) {
space, err := c.spaceStore.FindByRef(ctx, spaceRef) space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil { if err != nil {
@ -62,7 +62,7 @@ func (c *Controller) getSpaceCheckAuthRepoCreation(
Identifier: "", Identifier: "",
} }
err = apiauth.Check(ctx, c.authorizer, session, scope, resource, enum.PermissionRepoEdit) err = apiauth.Check(ctx, c.authorizer, session, scope, resource, permission)
if err != nil { if err != nil {
return nil, fmt.Errorf("auth check failed: %w", err) return nil, fmt.Errorf("auth check failed: %w", err)
} }
@ -80,7 +80,7 @@ func (c *Controller) ImportRepositories(
spaceRef string, spaceRef string,
in *ImportRepositoriesInput, in *ImportRepositoriesInput,
) (ImportRepositoriesOutput, error) { ) (ImportRepositoriesOutput, error) {
space, err := c.getSpaceCheckAuthRepoCreation(ctx, session, spaceRef) space, err := c.getSpaceCheckAuth(ctx, session, spaceRef, enum.PermissionRepoEdit)
if err != nil { if err != nil {
return ImportRepositoriesOutput{}, err return ImportRepositoriesOutput{}, err
} }

View File

@ -0,0 +1,49 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// DefineLabel defines a new label for the specified space.
func (c *Controller) DefineLabel(
ctx context.Context,
session *auth.Session,
spaceRef string,
in *types.DefineLabelInput,
) (*types.Label, error) {
space, err := c.getSpaceCheckAuth(ctx, session, spaceRef, enum.PermissionSpaceEdit)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
if err := in.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate input: %w", err)
}
label, err := c.labelSvc.Define(
ctx, session.Principal.ID, &space.ID, nil, in)
if err != nil {
return nil, fmt.Errorf("failed to create space label: %w", err)
}
return label, nil
}

View File

@ -0,0 +1,42 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
// DeleteLabel deletes a label for the specified space.
func (c *Controller) DeleteLabel(
ctx context.Context,
session *auth.Session,
spaceRef string,
key string,
) error {
space, err := c.getSpaceCheckAuth(ctx, session, spaceRef, enum.PermissionSpaceEdit)
if err != nil {
return fmt.Errorf("failed to acquire access to space: %w", err)
}
if err := c.labelSvc.Delete(ctx, &space.ID, nil, key); err != nil {
return fmt.Errorf("failed to delete space label: %w", err)
}
return nil
}

View File

@ -0,0 +1,44 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// ListLabels lists all labels defined for the specified space.
func (c *Controller) ListLabels(
ctx context.Context,
session *auth.Session,
spaceRef string,
filter *types.LabelFilter,
) ([]*types.Label, error) {
space, err := c.getSpaceCheckAuth(ctx, session, spaceRef, enum.PermissionSpaceView)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
labels, err := c.labelSvc.List(ctx, &space.ID, nil, filter)
if err != nil {
return nil, fmt.Errorf("failed to list space labels: %w", err)
}
return labels, nil
}

View File

@ -0,0 +1,49 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// SaveLabel defines a new label for the specified space.
func (c *Controller) SaveLabel(
ctx context.Context,
session *auth.Session,
spaceRef string,
in *types.SaveInput,
) (*types.LabelWithValues, error) {
space, err := c.getSpaceCheckAuth(ctx, session, spaceRef, enum.PermissionSpaceEdit)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
if err := in.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate input: %w", err)
}
labelWithValues, err := c.labelSvc.Save(
ctx, session.Principal.ID, &space.ID, nil, in)
if err != nil {
return nil, fmt.Errorf("failed to save label: %w", err)
}
return labelWithValues, nil
}

View File

@ -0,0 +1,49 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// UpdateLabel updates a label for the specified space.
func (c *Controller) UpdateLabel(
ctx context.Context,
session *auth.Session,
spaceRef string,
key string,
in *types.UpdateLabelInput,
) (*types.Label, error) {
space, err := c.getSpaceCheckAuth(ctx, session, spaceRef, enum.PermissionSpaceEdit)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
if err := in.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate input: %w", err)
}
label, err := c.labelSvc.Update(ctx, session.Principal.ID, &space.ID, nil, key, in)
if err != nil {
return nil, fmt.Errorf("failed to update space label: %w", err)
}
return label, nil
}

View File

@ -0,0 +1,55 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// DefineLabelValue defines a new label value for the specified space and label.
func (c *Controller) DefineLabelValue(
ctx context.Context,
session *auth.Session,
spaceRef string,
key string,
in *types.DefineValueInput,
) (*types.LabelValue, error) {
// TODO: permission check should be based on static vs dynamic label
space, err := c.getSpaceCheckAuth(ctx, session, spaceRef, enum.PermissionSpaceEdit)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
if err := in.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate input: %w", err)
}
label, err := c.labelSvc.Find(ctx, &space.ID, nil, key)
if err != nil {
return nil, fmt.Errorf("failed to find repo label: %w", err)
}
value, err := c.labelSvc.DefineValue(ctx, session.Principal.ID, label.ID, in)
if err != nil {
return nil, fmt.Errorf("failed to create space label value: %w", err)
}
return value, nil
}

View File

@ -0,0 +1,43 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
// DeleteLabelValue deletes a label value for the specified space.
func (c *Controller) DeleteLabelValue(
ctx context.Context,
session *auth.Session,
spaceRef string,
key string,
value string,
) error {
space, err := c.getSpaceCheckAuth(ctx, session, spaceRef, enum.PermissionSpaceEdit)
if err != nil {
return fmt.Errorf("failed to acquire access to space: %w", err)
}
if err := c.labelSvc.DeleteValue(ctx, &space.ID, nil, key, value); err != nil {
return fmt.Errorf("failed to delete space label value: %w", err)
}
return nil
}

View File

@ -0,0 +1,45 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// ListLabelValues lists all label values defined in the specified space.
func (c *Controller) ListLabelValues(
ctx context.Context,
session *auth.Session,
spaceRef string,
key string,
filter *types.ListQueryFilter,
) ([]*types.LabelValue, error) {
space, err := c.getSpaceCheckAuth(ctx, session, spaceRef, enum.PermissionSpaceView)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
values, err := c.labelSvc.ListValues(ctx, &space.ID, nil, key, filter)
if err != nil {
return nil, fmt.Errorf("failed to list space label values: %w", err)
}
return values, nil
}

View File

@ -0,0 +1,52 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// UpdateLabelValue updates a label value for the specified space and label.
func (c *Controller) UpdateLabelValue(
ctx context.Context,
session *auth.Session,
spaceRef string,
key string,
value string,
in *types.UpdateValueInput,
) (*types.LabelValue, error) {
space, err := c.getSpaceCheckAuth(ctx, session, spaceRef, enum.PermissionSpaceEdit)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
label, err := c.labelSvc.Find(ctx, &space.ID, nil, key)
if err != nil {
return nil, fmt.Errorf("failed to find space label: %w", err)
}
labelValue, err := c.labelSvc.UpdateValue(
ctx, session.Principal.ID, label.ID, value, in)
if err != nil {
return nil, fmt.Errorf("failed to update space label value: %w", err)
}
return labelValue, nil
}

View File

@ -21,6 +21,7 @@ import (
"github.com/harness/gitness/app/services/exporter" "github.com/harness/gitness/app/services/exporter"
"github.com/harness/gitness/app/services/gitspace" "github.com/harness/gitness/app/services/gitspace"
"github.com/harness/gitness/app/services/importer" "github.com/harness/gitness/app/services/importer"
"github.com/harness/gitness/app/services/label"
"github.com/harness/gitness/app/services/publicaccess" "github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/sse" "github.com/harness/gitness/app/sse"
"github.com/harness/gitness/app/store" "github.com/harness/gitness/app/store"
@ -46,6 +47,8 @@ func ProvideController(config *types.Config, tx dbtx.Transactor, urlProvider url
repoCtrl *repo.Controller, membershipStore store.MembershipStore, importer *importer.Repository, repoCtrl *repo.Controller, membershipStore store.MembershipStore, importer *importer.Repository,
exporter *exporter.Repository, limiter limiter.ResourceLimiter, publicAccess publicaccess.Service, exporter *exporter.Repository, limiter limiter.ResourceLimiter, publicAccess publicaccess.Service,
auditService audit.Service, gitspaceService *gitspace.Service, auditService audit.Service, gitspaceService *gitspace.Service,
gitspaceConfigStore store.GitspaceConfigStore, instanceStore store.GitspaceInstanceStore,
labelSvc *label.Service,
) *Controller { ) *Controller {
return NewController(config, tx, urlProvider, sseStreamer, identifierCheck, authorizer, return NewController(config, tx, urlProvider, sseStreamer, identifierCheck, authorizer,
spacePathStore, pipelineStore, secretStore, spacePathStore, pipelineStore, secretStore,
@ -53,5 +56,7 @@ func ProvideController(config *types.Config, tx dbtx.Transactor, urlProvider url
spaceStore, repoStore, principalStore, spaceStore, repoStore, principalStore,
repoCtrl, membershipStore, importer, repoCtrl, membershipStore, importer,
exporter, limiter, publicAccess, exporter, limiter, publicAccess,
auditService, gitspaceService) auditService, gitspaceService,
gitspaceConfigStore, instanceStore,
labelSvc)
} }

View File

@ -64,7 +64,7 @@ func NewController(authorizer authz.Authorizer,
func (c *Controller) getRepoCheckAccess(ctx context.Context, func (c *Controller) getRepoCheckAccess(ctx context.Context,
session *auth.Session, session *auth.Session,
repoRef string, repoRef string,
reqPermission enum.Permission, permission enum.Permission,
) (*types.Repository, error) { ) (*types.Repository, error) {
if repoRef == "" { if repoRef == "" {
return nil, usererror.BadRequest("A valid repository reference must be provided.") return nil, usererror.BadRequest("A valid repository reference must be provided.")
@ -75,7 +75,7 @@ func (c *Controller) getRepoCheckAccess(ctx context.Context,
return nil, fmt.Errorf("failed to find repo: %w", err) return nil, fmt.Errorf("failed to find repo: %w", err)
} }
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission); err != nil { if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, permission); err != nil {
return nil, fmt.Errorf("failed to verify authorization: %w", err) return nil, fmt.Errorf("failed to verify authorization: %w", err)
} }

View File

@ -0,0 +1,60 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pullreq
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/pullreq"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/types"
)
func HandleAssignLabel(pullreqCtrl *pullreq.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
pullreqNumber, err := request.GetPullReqNumberFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(types.PullReqCreateInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid request body: %s.", err)
return
}
label, err := pullreqCtrl.AssignLabel(
ctx, session, repoRef, pullreqNumber, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, label)
}
}

View File

@ -0,0 +1,56 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pullreq
import (
"net/http"
"github.com/harness/gitness/app/api/controller/pullreq"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
func HandleListLabels(pullreqCtrl *pullreq.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
pullreqNumber, err := request.GetPullReqNumberFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
filter, err := request.ParseAssignableLabelFilter(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
labels, err := pullreqCtrl.ListLabels(ctx, session, repoRef, pullreqNumber, filter)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, labels)
}
}

View File

@ -0,0 +1,56 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pullreq
import (
"net/http"
"github.com/harness/gitness/app/api/controller/pullreq"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
func HandleUnassignLabel(pullreqCtrl *pullreq.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
pullreqNumber, err := request.GetPullReqNumberFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
labelID, err := request.GetLabelIDFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
if err := pullreqCtrl.UnassignLabel(
ctx, session, repoRef, pullreqNumber, labelID); err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.DeleteSuccessful(w)
}
}

View File

@ -0,0 +1,53 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/types"
)
func HandleDefineLabel(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(types.DefineLabelInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid request body: %s.", err)
return
}
label, err := repoCtrl.DefineLabel(ctx, session, repoRef, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusCreated, label)
}
}

View File

@ -0,0 +1,50 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"net/http"
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
func HandleDeleteLabel(labelCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
key, err := request.GetLabelKeyFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
err = labelCtrl.DeleteLabel(ctx, session, repoRef, key)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.DeleteSuccessful(w)
}
}

View File

@ -0,0 +1,50 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"net/http"
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
func HandleListLabels(labelCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
filter, err := request.ParseLabelFilter(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
labels, err := labelCtrl.ListLabels(ctx, session, repoRef, filter)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, labels)
}
}

View File

@ -0,0 +1,53 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/types"
)
func HandleSaveLabel(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(types.SaveInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid request body: %s.", err)
return
}
label, err := repoCtrl.SaveLabel(ctx, session, repoRef, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, label)
}
}

View File

@ -0,0 +1,59 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/types"
)
func HandleUpdateLabel(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
key, err := request.GetLabelKeyFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(types.UpdateLabelInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid request body: %s.", err)
return
}
label, err := repoCtrl.UpdateLabel(ctx, session, repoRef, key, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, label)
}
}

View File

@ -0,0 +1,59 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/types"
)
func HandleDefineLabelValue(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(types.DefineValueInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid request body: %s.", err)
return
}
key, err := request.GetLabelKeyFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
label, err := repoCtrl.DefineLabelValue(ctx, session, repoRef, key, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusCreated, label)
}
}

View File

@ -0,0 +1,56 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"net/http"
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
func HandleDeleteLabelValue(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
key, err := request.GetLabelKeyFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
value, err := request.GetLabelValueFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
err = repoCtrl.DeleteLabelValue(ctx, session, repoRef, key, value)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.DeleteSuccessful(w)
}
}

View File

@ -0,0 +1,52 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"net/http"
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
func HandleListLabelValues(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
key, err := request.GetLabelKeyFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
filter := request.ParseListQueryFilterFromRequest(r)
labels, err := repoCtrl.ListLabelValues(ctx, session, repoRef, key, &filter)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, labels)
}
}

View File

@ -0,0 +1,66 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/types"
)
func HandleUpdateLabelValue(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
key, err := request.GetLabelKeyFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
value, err := request.GetLabelValueFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(types.UpdateValueInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid request body: %s.", err)
return
}
label, err := repoCtrl.UpdateLabelValue(
ctx, session, repoRef, key, value, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, label)
}
}

View File

@ -0,0 +1,53 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/space"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/types"
)
func HandleDefineLabel(labelCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(types.DefineLabelInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid request body: %s.", err)
return
}
label, err := labelCtrl.DefineLabel(ctx, session, spaceRef, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusCreated, label)
}
}

View File

@ -0,0 +1,50 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"net/http"
"github.com/harness/gitness/app/api/controller/space"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
func HandleDeleteLabel(labelCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
identifier, err := request.GetLabelKeyFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
err = labelCtrl.DeleteLabel(ctx, session, spaceRef, identifier)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.DeleteSuccessful(w)
}
}

View File

@ -0,0 +1,50 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"net/http"
"github.com/harness/gitness/app/api/controller/space"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
func HandleListLabels(labelCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
filter, err := request.ParseLabelFilter(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
labels, err := labelCtrl.ListLabels(ctx, session, spaceRef, filter)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, labels)
}
}

View File

@ -0,0 +1,53 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/space"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/types"
)
func HandleSaveLabel(labelCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(types.SaveInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid request body: %s.", err)
return
}
label, err := labelCtrl.SaveLabel(ctx, session, spaceRef, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, label)
}
}

View File

@ -0,0 +1,59 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/space"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/types"
)
func HandleUpdateLabel(labelCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
key, err := request.GetLabelKeyFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(types.UpdateLabelInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid request body: %s.", err)
return
}
label, err := labelCtrl.UpdateLabel(ctx, session, spaceRef, key, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, label)
}
}

View File

@ -0,0 +1,59 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/space"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/types"
)
func HandleDefineLabelValue(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(types.DefineValueInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid request body: %s.", err)
return
}
key, err := request.GetLabelKeyFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
label, err := spaceCtrl.DefineLabelValue(ctx, session, spaceRef, key, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusCreated, label)
}
}

View File

@ -0,0 +1,56 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"net/http"
"github.com/harness/gitness/app/api/controller/space"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
func HandleDeleteLabelValue(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
key, err := request.GetLabelKeyFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
value, err := request.GetLabelValueFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
err = spaceCtrl.DeleteLabelValue(ctx, session, spaceRef, key, value)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.DeleteSuccessful(w)
}
}

View File

@ -0,0 +1,53 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"net/http"
"github.com/harness/gitness/app/api/controller/space"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
func HandleListLabelValues(labelValueCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
key, err := request.GetLabelKeyFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
filter := request.ParseListQueryFilterFromRequest(r)
labels, err := labelValueCtrl.ListLabelValues(
ctx, session, spaceRef, key, &filter)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, labels)
}
}

View File

@ -0,0 +1,66 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/space"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/types"
)
func HandleUpdateLabelValue(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
key, err := request.GetLabelKeyFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
value, err := request.GetLabelValueFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(types.UpdateValueInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid request body: %s.", err)
return
}
label, err := spaceCtrl.UpdateLabelValue(
ctx, session, spaceRef, key, value, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, label)
}
}

View File

@ -142,6 +142,11 @@ type getPullReqChecksRequest struct {
pullReqRequest pullReqRequest
} }
type pullReqAssignLabelInput struct {
pullReqRequest
types.PullReqCreateInput
}
var queryParameterQueryPullRequest = openapi3.ParameterOrRef{ var queryParameterQueryPullRequest = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{ Parameter: &openapi3.Parameter{
Name: request.QueryParamQuery, Name: request.QueryParamQuery,
@ -314,6 +319,21 @@ var queryParameterBeforePullRequestActivity = openapi3.ParameterOrRef{
}, },
} }
var queryParameterAssignable = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamAssignable,
In: openapi3.ParameterInQuery,
Description: ptr.String("The result should contain all labels assignable to the pullreq."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeBoolean),
Default: ptrptr(false),
},
},
},
}
//nolint:funlen //nolint:funlen
func pullReqOperations(reflector *openapi3.Reflector) { func pullReqOperations(reflector *openapi3.Reflector) {
createPullReq := openapi3.Operation{} createPullReq := openapi3.Operation{}
@ -624,4 +644,45 @@ func pullReqOperations(reflector *openapi3.Reflector) {
panicOnErr(reflector.SetJSONResponse(&opChecks, new(usererror.Error), http.StatusForbidden)) panicOnErr(reflector.SetJSONResponse(&opChecks, new(usererror.Error), http.StatusForbidden))
panicOnErr(reflector.SetJSONResponse(&opChecks, new(usererror.Error), http.StatusNotFound)) panicOnErr(reflector.SetJSONResponse(&opChecks, new(usererror.Error), http.StatusNotFound))
panicOnErr(reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/pullreq/{pullreq_number}/checks", opChecks)) panicOnErr(reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/pullreq/{pullreq_number}/checks", opChecks))
opAssignLabel := openapi3.Operation{}
opAssignLabel.WithTags("pullreq")
opAssignLabel.WithMapOfAnything(map[string]interface{}{"operationId": "assignLabel"})
_ = reflector.SetRequest(&opAssignLabel, new(pullReqAssignLabelInput), http.MethodPut)
_ = reflector.SetJSONResponse(&opAssignLabel, new(types.PullReqLabel), http.StatusOK)
_ = reflector.SetJSONResponse(&opAssignLabel, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opAssignLabel, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opAssignLabel, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opAssignLabel, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPut,
"/repos/{repo_ref}/pullreq/{pullreq_number}/labels", opAssignLabel)
opListLabels := openapi3.Operation{}
opListLabels.WithTags("pullreq")
opListLabels.WithMapOfAnything(map[string]interface{}{"operationId": "listLabels"})
opListLabels.WithParameters(
QueryParameterPage, QueryParameterLimit, queryParameterAssignable, queryParameterQueryLabel)
_ = reflector.SetRequest(&opListLabels, new(pullReqRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opListLabels, new(types.ScopesLabels), http.StatusOK)
_ = reflector.SetJSONResponse(&opListLabels, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opListLabels, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opListLabels, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opListLabels, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodGet,
"/repos/{repo_ref}/pullreq/{pullreq_number}/labels", opListLabels)
opUnassignLabel := openapi3.Operation{}
opUnassignLabel.WithTags("pullreq")
opUnassignLabel.WithMapOfAnything(map[string]interface{}{"operationId": "unassignLabel"})
_ = reflector.SetRequest(&opUnassignLabel, struct {
pullReqRequest
LabelID int64 `path:"label_id"`
}{}, http.MethodDelete)
_ = reflector.SetJSONResponse(&opUnassignLabel, nil, http.StatusNoContent)
_ = reflector.SetJSONResponse(&opUnassignLabel, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opUnassignLabel, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opUnassignLabel, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opUnassignLabel, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodDelete,
"/repos/{repo_ref}/pullreq/{pullreq_number}/labels/{label_id}", opUnassignLabel)
} }

View File

@ -221,6 +221,18 @@ type archiveRequest struct {
Format string `path:"format" required:"true"` Format string `path:"format" required:"true"`
} }
type labelRequest struct {
Key string `json:"key"`
Description string `json:"description"`
Type enum.LabelType `json:"type"`
Color enum.LabelColor `json:"color"`
}
type labelValueRequest struct {
Value string `json:"value"`
Color enum.LabelColor `json:"color"`
}
var queryParameterGitRef = openapi3.ParameterOrRef{ var queryParameterGitRef = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{ Parameter: &openapi3.Parameter{
Name: request.QueryParamGitRef, Name: request.QueryParamGitRef,
@ -603,6 +615,35 @@ var queryParamArchiveCompression = openapi3.ParameterOrRef{
}, },
} }
var queryParameterInherited = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamInherited,
In: openapi3.ParameterInQuery,
Description: ptr.String("The result should inherit labels from parent parent spaces."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeBoolean),
Default: ptrptr(false),
},
},
},
}
var queryParameterQueryLabel = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamQuery,
In: openapi3.ParameterInQuery,
Description: ptr.String("The substring which is used to filter the labels by their key."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeString),
},
},
},
}
//nolint:funlen //nolint:funlen
func repoOperations(reflector *openapi3.Reflector) { func repoOperations(reflector *openapi3.Reflector) {
createRepository := openapi3.Operation{} createRepository := openapi3.Operation{}
@ -1168,4 +1209,157 @@ func repoOperations(reflector *openapi3.Reflector) {
_ = reflector.SetJSONResponse(&opSummary, new(usererror.Error), http.StatusForbidden) _ = reflector.SetJSONResponse(&opSummary, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opSummary, new(usererror.Error), http.StatusNotFound) _ = reflector.SetJSONResponse(&opSummary, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/summary", opSummary) _ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/summary", opSummary)
opDefineLabel := openapi3.Operation{}
opDefineLabel.WithTags("repository")
opDefineLabel.WithMapOfAnything(
map[string]interface{}{"operationId": "defineRepoLabel"})
_ = reflector.SetRequest(&opDefineLabel, &struct {
repoRequest
labelRequest
}{}, http.MethodPost)
_ = reflector.SetJSONResponse(&opDefineLabel, new(types.Label), http.StatusCreated)
_ = reflector.SetJSONResponse(&opDefineLabel, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opDefineLabel, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opDefineLabel, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opDefineLabel, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opDefineLabel, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPost, "/repos/{repo_ref}/labels", opDefineLabel)
opSaveLabel := openapi3.Operation{}
opSaveLabel.WithTags("repository")
opSaveLabel.WithMapOfAnything(
map[string]interface{}{"operationId": "saveRepoLabel"})
_ = reflector.SetRequest(&opSaveLabel, &struct {
repoRequest
types.SaveInput
}{}, http.MethodPut)
_ = reflector.SetJSONResponse(&opSaveLabel, new(types.LabelWithValues), http.StatusOK)
_ = reflector.SetJSONResponse(&opSaveLabel, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opSaveLabel, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opSaveLabel, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opSaveLabel, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opSaveLabel, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPut, "/repos/{repo_ref}/labels", opSaveLabel)
opListLabels := openapi3.Operation{}
opListLabels.WithTags("repository")
opListLabels.WithMapOfAnything(
map[string]interface{}{"operationId": "listRepoLabels"})
opListLabels.WithParameters(
QueryParameterPage, QueryParameterLimit, queryParameterInherited, queryParameterQueryLabel)
_ = reflector.SetRequest(&opListLabels, new(repoRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opListLabels, new([]*types.Label), http.StatusOK)
_ = reflector.SetJSONResponse(&opListLabels, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opListLabels, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opListLabels, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opListLabels, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opListLabels, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/labels", opListLabels)
opDeleteLabel := openapi3.Operation{}
opDeleteLabel.WithTags("repository")
opDeleteLabel.WithMapOfAnything(
map[string]interface{}{"operationId": "deleteRepoLabel"})
_ = reflector.SetRequest(&opDeleteLabel, &struct {
repoRequest
Key string `path:"key"`
}{}, http.MethodDelete)
_ = reflector.SetJSONResponse(&opDeleteLabel, nil, http.StatusNoContent)
_ = reflector.SetJSONResponse(&opDeleteLabel, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opDeleteLabel, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opDeleteLabel, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opDeleteLabel, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opDeleteLabel, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(
http.MethodDelete, "/repos/{repo_ref}/labels/{key}", opDeleteLabel)
opUpdateLabel := openapi3.Operation{}
opUpdateLabel.WithTags("repository")
opUpdateLabel.WithMapOfAnything(
map[string]interface{}{"operationId": "updateRepoLabel"})
_ = reflector.SetRequest(&opUpdateLabel, &struct {
repoRequest
labelRequest
Key string `path:"key"`
}{}, http.MethodPatch)
_ = reflector.SetJSONResponse(&opUpdateLabel, new(types.Label), http.StatusOK)
_ = reflector.SetJSONResponse(&opUpdateLabel, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opUpdateLabel, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opUpdateLabel, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opUpdateLabel, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opUpdateLabel, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPatch, "/repos/{repo_ref}/labels/{key}", opUpdateLabel)
opDefineLabelValue := openapi3.Operation{}
opDefineLabelValue.WithTags("repository")
opDefineLabelValue.WithMapOfAnything(
map[string]interface{}{"operationId": "defineRepoLabelValue"})
_ = reflector.SetRequest(&opDefineLabelValue, &struct {
repoRequest
labelValueRequest
Key string `path:"key"`
}{}, http.MethodPost)
_ = reflector.SetJSONResponse(&opDefineLabelValue, new(types.LabelValue), http.StatusCreated)
_ = reflector.SetJSONResponse(&opDefineLabelValue, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opDefineLabelValue, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opDefineLabelValue, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opDefineLabelValue, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opDefineLabelValue, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPost,
"/repos/{repo_ref}/labels/{key}/values", opDefineLabelValue)
opListLabelValues := openapi3.Operation{}
opListLabelValues.WithTags("repository")
opListLabelValues.WithMapOfAnything(
map[string]interface{}{"operationId": "listRepoLabelValues"})
_ = reflector.SetRequest(&opListLabelValues, &struct {
repoRequest
Key string `path:"key"`
}{}, http.MethodGet)
_ = reflector.SetJSONResponse(&opListLabelValues, new([]*types.LabelValue), http.StatusOK)
_ = reflector.SetJSONResponse(&opListLabelValues, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opListLabelValues, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opListLabelValues, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opListLabelValues, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opListLabelValues, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet,
"/repos/{repo_ref}/labels/{key}/values", opListLabelValues)
opDeleteLabelValue := openapi3.Operation{}
opDeleteLabelValue.WithTags("repository")
opDeleteLabelValue.WithMapOfAnything(
map[string]interface{}{"operationId": "deleteRepoLabelValue"})
_ = reflector.SetRequest(&opDeleteLabelValue, &struct {
repoRequest
Key string `path:"key"`
Value string `path:"value"`
}{}, http.MethodDelete)
_ = reflector.SetJSONResponse(&opDeleteLabelValue, nil, http.StatusNoContent)
_ = reflector.SetJSONResponse(&opDeleteLabelValue, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opDeleteLabelValue, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opDeleteLabelValue, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opDeleteLabelValue, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opDeleteLabelValue, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(
http.MethodDelete, "/repos/{repo_ref}/labels/{key}/values/{value}", opDeleteLabelValue)
opUpdateLabelValue := openapi3.Operation{}
opUpdateLabelValue.WithTags("repository")
opUpdateLabelValue.WithMapOfAnything(
map[string]interface{}{"operationId": "updateRepoLabelValue"})
_ = reflector.SetRequest(&opUpdateLabelValue, &struct {
repoRequest
labelValueRequest
Key string `path:"key"`
Value string `path:"value"`
}{}, http.MethodPatch)
_ = reflector.SetJSONResponse(&opUpdateLabelValue, new(types.LabelValue), http.StatusOK)
_ = reflector.SetJSONResponse(&opUpdateLabelValue, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opUpdateLabelValue, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opUpdateLabelValue, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opUpdateLabelValue, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opUpdateLabelValue, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPatch,
"/repos/{repo_ref}/labels/{key}/values/{value}", opUpdateLabelValue)
} }

View File

@ -446,4 +446,158 @@ func spaceOperations(reflector *openapi3.Reflector) {
_ = reflector.SetJSONResponse(&opMembershipList, new(usererror.Error), http.StatusForbidden) _ = reflector.SetJSONResponse(&opMembershipList, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opMembershipList, new(usererror.Error), http.StatusNotFound) _ = reflector.SetJSONResponse(&opMembershipList, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet, "/spaces/{space_ref}/members", opMembershipList) _ = reflector.Spec.AddOperation(http.MethodGet, "/spaces/{space_ref}/members", opMembershipList)
opDefineLabel := openapi3.Operation{}
opDefineLabel.WithTags("space")
opDefineLabel.WithMapOfAnything(
map[string]interface{}{"operationId": "defineSpaceLabel"})
_ = reflector.SetRequest(&opDefineLabel, &struct {
spaceRequest
labelRequest
}{}, http.MethodPost)
_ = reflector.SetJSONResponse(&opDefineLabel, new(types.Label), http.StatusCreated)
_ = reflector.SetJSONResponse(&opDefineLabel, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opDefineLabel, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opDefineLabel, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opDefineLabel, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opDefineLabel, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPost, "/spaces/{space_ref}/labels", opDefineLabel)
opSaveLabel := openapi3.Operation{}
opSaveLabel.WithTags("space")
opSaveLabel.WithMapOfAnything(
map[string]interface{}{"operationId": "saveSpaceLabel"})
_ = reflector.SetRequest(&opSaveLabel, &struct {
spaceRequest
types.SaveInput
}{}, http.MethodPut)
_ = reflector.SetJSONResponse(&opSaveLabel, new(types.LabelWithValues), http.StatusOK)
_ = reflector.SetJSONResponse(&opSaveLabel, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opSaveLabel, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opSaveLabel, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opSaveLabel, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opSaveLabel, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPut, "/spaces/{space_ref}/labels", opSaveLabel)
opListLabels := openapi3.Operation{}
opListLabels.WithTags("space")
opListLabels.WithMapOfAnything(
map[string]interface{}{"operationId": "listSpaceLabels"})
opListLabels.WithParameters(
QueryParameterPage, QueryParameterLimit, queryParameterInherited, queryParameterQueryLabel)
_ = reflector.SetRequest(&opListLabels, new(spaceRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opListLabels, new([]*types.Label), http.StatusOK)
_ = reflector.SetJSONResponse(&opListLabels, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opListLabels, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opListLabels, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opListLabels, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opListLabels, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet, "/spaces/{space_ref}/labels", opListLabels)
opDeleteLabel := openapi3.Operation{}
opDeleteLabel.WithTags("space")
opDeleteLabel.WithMapOfAnything(
map[string]interface{}{"operationId": "deleteSpaceLabel"})
_ = reflector.SetRequest(&opDeleteLabel, &struct {
spaceRequest
Key string `path:"key"`
}{}, http.MethodDelete)
_ = reflector.SetJSONResponse(&opDeleteLabel, nil, http.StatusNoContent)
_ = reflector.SetJSONResponse(&opDeleteLabel, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opDeleteLabel, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opDeleteLabel, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opDeleteLabel, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opDeleteLabel, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(
http.MethodDelete, "/spaces/{space_ref}/labels/{key}", opDeleteLabel)
opUpdateLabel := openapi3.Operation{}
opUpdateLabel.WithTags("space")
opUpdateLabel.WithMapOfAnything(
map[string]interface{}{"operationId": "updateSpaceLabel"})
_ = reflector.SetRequest(&opUpdateLabel, &struct {
spaceRequest
labelRequest
Key string `path:"key"`
}{}, http.MethodPatch)
_ = reflector.SetJSONResponse(&opUpdateLabel, new(types.Label), http.StatusOK)
_ = reflector.SetJSONResponse(&opUpdateLabel, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opUpdateLabel, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opUpdateLabel, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opUpdateLabel, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opUpdateLabel, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPatch,
"/spaces/{space_ref}/labels/{key}", opUpdateLabel)
opDefineLabelValue := openapi3.Operation{}
opDefineLabelValue.WithTags("space")
opDefineLabelValue.WithMapOfAnything(
map[string]interface{}{"operationId": "defineSpaceLabelValue"})
_ = reflector.SetRequest(&opDefineLabelValue, &struct {
spaceRequest
labelValueRequest
Key string `path:"key"`
}{}, http.MethodPost)
_ = reflector.SetJSONResponse(&opDefineLabelValue, new(types.LabelValue), http.StatusCreated)
_ = reflector.SetJSONResponse(&opDefineLabelValue, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opDefineLabelValue, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opDefineLabelValue, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opDefineLabelValue, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opDefineLabelValue, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPost,
"/spaces/{space_ref}/labels/{key}/values", opDefineLabelValue)
opListLabelValues := openapi3.Operation{}
opListLabelValues.WithTags("space")
opListLabelValues.WithMapOfAnything(
map[string]interface{}{"operationId": "listSpaceLabelValues"})
_ = reflector.SetRequest(&opListLabelValues, &struct {
spaceRequest
Key string `path:"key"`
}{}, http.MethodGet)
_ = reflector.SetJSONResponse(&opListLabelValues, new([]*types.LabelValue), http.StatusOK)
_ = reflector.SetJSONResponse(&opListLabelValues, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opListLabelValues, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opListLabelValues, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opListLabelValues, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opListLabelValues, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet,
"/spaces/{space_ref}/labels/{key}/values", opListLabelValues)
opDeleteLabelValue := openapi3.Operation{}
opDeleteLabelValue.WithTags("space")
opDeleteLabelValue.WithMapOfAnything(
map[string]interface{}{"operationId": "deleteSpaceLabelValue"})
_ = reflector.SetRequest(&opDeleteLabelValue, &struct {
spaceRequest
Key string `path:"key"`
Value string `path:"value"`
}{}, http.MethodDelete)
_ = reflector.SetJSONResponse(&opDeleteLabelValue, nil, http.StatusNoContent)
_ = reflector.SetJSONResponse(&opDeleteLabelValue, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opDeleteLabelValue, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opDeleteLabelValue, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opDeleteLabelValue, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opDeleteLabelValue, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(
http.MethodDelete, "/spaces/{space_ref}/labels/{key}/values/{value}", opDeleteLabelValue)
opUpdateLabelValue := openapi3.Operation{}
opUpdateLabelValue.WithTags("space")
opUpdateLabelValue.WithMapOfAnything(
map[string]interface{}{"operationId": "updateSpaceLabelValue"})
_ = reflector.SetRequest(&opUpdateLabelValue, &struct {
spaceRequest
labelValueRequest
Key string `path:"key"`
Value string `path:"value"`
}{}, http.MethodPatch)
_ = reflector.SetJSONResponse(&opUpdateLabelValue, new(types.LabelValue), http.StatusOK)
_ = reflector.SetJSONResponse(&opUpdateLabelValue, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opUpdateLabelValue, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opUpdateLabelValue, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opUpdateLabelValue, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opUpdateLabelValue, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPatch,
"/spaces/{space_ref}/labels/{key}/values/{value}", opUpdateLabelValue)
} }

View File

@ -50,6 +50,9 @@ const (
PerPageDefault = 30 PerPageDefault = 30
PerPageMax = 100 PerPageMax = 100
QueryParamInherited = "inherited"
QueryParamAssignable = "assignable"
// TODO: have shared constants across all services? // TODO: have shared constants across all services?
HeaderRequestID = "X-Request-Id" HeaderRequestID = "X-Request-Id"
HeaderUserAgent = "User-Agent" HeaderUserAgent = "User-Agent"
@ -155,6 +158,16 @@ func ParseRecursiveFromQuery(r *http.Request) (bool, error) {
return QueryParamAsBoolOrDefault(r, QueryParamRecursive, false) return QueryParamAsBoolOrDefault(r, QueryParamRecursive, false)
} }
// ParseInheritedFromQuery extracts the inherited option from the URL query.
func ParseInheritedFromQuery(r *http.Request) (bool, error) {
return QueryParamAsBoolOrDefault(r, QueryParamInherited, false)
}
// ParseAssignableFromQuery extracts the assignable option from the URL query.
func ParseAssignableFromQuery(r *http.Request) (bool, error) {
return QueryParamAsBoolOrDefault(r, QueryParamAssignable, false)
}
// GetDeletedAtFromQueryOrError gets the exact resource deletion timestamp from the query. // GetDeletedAtFromQueryOrError gets the exact resource deletion timestamp from the query.
func GetDeletedAtFromQueryOrError(r *http.Request) (int64, error) { func GetDeletedAtFromQueryOrError(r *http.Request) (int64, error) {
return QueryParamAsPositiveInt64OrError(r, QueryParamDeletedAt) return QueryParamAsPositiveInt64OrError(r, QueryParamDeletedAt)

67
app/api/request/label.go Normal file
View File

@ -0,0 +1,67 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package request
import (
"net/http"
"github.com/harness/gitness/types"
)
const (
PathParamLabelKey = "label_key"
PathParamLabelValue = "label_value"
PathParamLabelID = "label_id"
)
func GetLabelKeyFromPath(r *http.Request) (string, error) {
return EncodedPathParamOrError(r, PathParamLabelKey)
}
func GetLabelValueFromPath(r *http.Request) (string, error) {
return EncodedPathParamOrError(r, PathParamLabelValue)
}
func GetLabelIDFromPath(r *http.Request) (int64, error) {
return PathParamAsPositiveInt64(r, PathParamLabelID)
}
// ParseLabelFilter extracts the label filter from the url.
func ParseLabelFilter(r *http.Request) (*types.LabelFilter, error) {
// inherited is used to list labels from parent scopes
inherited, err := ParseInheritedFromQuery(r)
if err != nil {
return nil, err
}
return &types.LabelFilter{
Inherited: inherited,
ListQueryFilter: ParseListQueryFilterFromRequest(r),
}, nil
}
// ParseAssignableLabelFilter extracts the assignable label filter from the url.
func ParseAssignableLabelFilter(r *http.Request) (*types.AssignableLabelFilter, error) {
// assignable is used to list all labels assignable to pullreq
assignable, err := ParseAssignableFromQuery(r)
if err != nil {
return nil, err
}
return &types.AssignableLabelFilter{
Assignable: assignable,
ListQueryFilter: ParseListQueryFilterFromRequest(r),
}, nil
}

View File

@ -18,6 +18,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"github.com/harness/gitness/app/api/usererror" "github.com/harness/gitness/app/api/usererror"
@ -72,6 +73,24 @@ func PathParamOrError(r *http.Request, paramName string) (string, error) {
return val, nil return val, nil
} }
// EncodedPathParamOrError tries to retrieve the parameter from the request and
// returns the parameter if it exists and is not empty, otherwise returns an error,
// then it tries to URL decode parameter value,
// and returns decoded value, or an error on decoding failure.
func EncodedPathParamOrError(r *http.Request, paramName string) (string, error) {
val, err := PathParamOrError(r, paramName)
if err != nil {
return "", err
}
decoded, err := url.PathUnescape(val)
if err != nil {
return "", usererror.BadRequestf("Value %s for param %s has incorrect encoding", val, paramName)
}
return decoded, nil
}
// PathParamOrEmpty retrieves the path parameter or returns an empty string otherwise. // PathParamOrEmpty retrieves the path parameter or returns an empty string otherwise.
func PathParamOrEmpty(r *http.Request, paramName string) string { func PathParamOrEmpty(r *http.Request, paramName string) string {
val, ok := PathParam(r, paramName) val, ok := PathParam(r, paramName)

View File

@ -91,8 +91,8 @@ func Translate(ctx context.Context, err error) *Error {
log.Ctx(ctx).Warn().Err(appError.Err).Msgf("Application error translation is omitting internal details.") log.Ctx(ctx).Warn().Err(appError.Err).Msgf("Application error translation is omitting internal details.")
} }
return NewWithPayload(httpStatusCode( return NewWithPayload(
appError.Status), httpStatusCode(appError.Status),
appError.Message, appError.Message,
appError.Details, appError.Details,
) )

View File

@ -227,7 +227,12 @@ func setupRoutesV1WithAuth(r chi.Router,
} }
// nolint: revive // it's the app context, it shouldn't be the first argument // nolint: revive // it's the app context, it shouldn't be the first argument
func setupSpaces(r chi.Router, appCtx context.Context, spaceCtrl *space.Controller) { func setupSpaces(
r chi.Router,
appCtx context.Context,
spaceCtrl *space.Controller,
) {
r.Route("/spaces", func(r chi.Router) { r.Route("/spaces", func(r chi.Router) {
// Create takes path and parentId via body, not uri // Create takes path and parentId via body, not uri
r.Post("/", handlerspace.HandleCreate(spaceCtrl)) r.Post("/", handlerspace.HandleCreate(spaceCtrl))
@ -264,6 +269,26 @@ func setupSpaces(r chi.Router, appCtx context.Context, spaceCtrl *space.Controll
r.Patch("/", handlerspace.HandleMembershipUpdate(spaceCtrl)) r.Patch("/", handlerspace.HandleMembershipUpdate(spaceCtrl))
}) })
}) })
r.Route("/labels", func(r chi.Router) {
r.Post("/", handlerspace.HandleDefineLabel(spaceCtrl))
r.Get("/", handlerspace.HandleListLabels(spaceCtrl))
r.Put("/", handlerspace.HandleSaveLabel(spaceCtrl))
r.Route(fmt.Sprintf("/{%s}", request.PathParamLabelKey), func(r chi.Router) {
r.Delete("/", handlerspace.HandleDeleteLabel(spaceCtrl))
r.Patch("/", handlerspace.HandleUpdateLabel(spaceCtrl))
r.Route("/values", func(r chi.Router) {
r.Post("/", handlerspace.HandleDefineLabelValue(spaceCtrl))
r.Get("/", handlerspace.HandleListLabelValues(spaceCtrl))
r.Route(fmt.Sprintf("/{%s}", request.PathParamLabelValue), func(r chi.Router) {
r.Delete("/", handlerspace.HandleDeleteLabelValue(spaceCtrl))
r.Patch("/", handlerspace.HandleUpdateLabelValue(spaceCtrl))
})
})
})
})
}) })
}) })
} }
@ -385,6 +410,26 @@ func setupRepos(r chi.Router,
SetupUploads(r, uploadCtrl) SetupUploads(r, uploadCtrl)
SetupRules(r, repoCtrl) SetupRules(r, repoCtrl)
r.Route("/labels", func(r chi.Router) {
r.Post("/", handlerrepo.HandleDefineLabel(repoCtrl))
r.Get("/", handlerrepo.HandleListLabels(repoCtrl))
r.Put("/", handlerrepo.HandleSaveLabel(repoCtrl))
r.Route(fmt.Sprintf("/{%s}", request.PathParamLabelKey), func(r chi.Router) {
r.Delete("/", handlerrepo.HandleDeleteLabel(repoCtrl))
r.Patch("/", handlerrepo.HandleUpdateLabel(repoCtrl))
r.Route("/values", func(r chi.Router) {
r.Post("/", handlerrepo.HandleDefineLabelValue(repoCtrl))
r.Get("/", handlerrepo.HandleListLabelValues(repoCtrl))
r.Route(fmt.Sprintf("/{%s}", request.PathParamLabelValue), func(r chi.Router) {
r.Delete("/", handlerrepo.HandleDeleteLabelValue(repoCtrl))
r.Patch("/", handlerrepo.HandleUpdateLabelValue(repoCtrl))
})
})
})
})
}) })
}) })
} }
@ -565,6 +610,14 @@ func SetupPullReq(r chi.Router, pullreqCtrl *pullreq.Controller) {
r.Get("/diff", handlerpullreq.HandleDiff(pullreqCtrl)) r.Get("/diff", handlerpullreq.HandleDiff(pullreqCtrl))
r.Post("/diff", handlerpullreq.HandleDiff(pullreqCtrl)) r.Post("/diff", handlerpullreq.HandleDiff(pullreqCtrl))
r.Get("/checks", handlerpullreq.HandleCheckList(pullreqCtrl)) r.Get("/checks", handlerpullreq.HandleCheckList(pullreqCtrl))
r.Route("/labels", func(r chi.Router) {
r.Put("/", handlerpullreq.HandleAssignLabel(pullreqCtrl))
r.Get("/", handlerpullreq.HandleListLabels(pullreqCtrl))
r.Route(fmt.Sprintf("/{%s}", request.PathParamLabelID), func(r chi.Router) {
r.Delete("/", handlerpullreq.HandleUnassignLabel(pullreqCtrl))
})
})
}) })
}) })
} }

305
app/services/label/label.go Normal file
View File

@ -0,0 +1,305 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package label
import (
"context"
"fmt"
"sort"
"time"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/store"
"github.com/harness/gitness/types"
)
func (s *Service) Define(
ctx context.Context,
principalID int64,
spaceID, repoID *int64,
in *types.DefineLabelInput,
) (*types.Label, error) {
var scope int64
if spaceID != nil {
spaceIDs, err := s.spaceStore.GetAncestorIDs(ctx, *spaceID)
if err != nil {
return nil, fmt.Errorf("failed to get space ids hierarchy: %w", err)
}
scope = int64(len(spaceIDs))
}
label := newLabel(principalID, spaceID, repoID, scope, in)
if err := s.labelStore.Define(ctx, label); err != nil {
return nil, err
}
return label, nil
}
func (s *Service) Update(
ctx context.Context,
principalID int64,
spaceID, repoID *int64,
key string,
in *types.UpdateLabelInput,
) (*types.Label, error) {
label, err := s.labelStore.Find(ctx, spaceID, repoID, key)
if err != nil {
return nil, fmt.Errorf("failed to find repo label: %w", err)
}
return s.update(ctx, principalID, label, in)
}
func (s *Service) update(
ctx context.Context,
principalID int64,
label *types.Label,
in *types.UpdateLabelInput,
) (*types.Label, error) {
label, hasChanges := applyChanges(principalID, label, in)
if !hasChanges {
return label, nil
}
err := s.labelStore.Update(ctx, label)
if err != nil {
return nil, fmt.Errorf("failed to update label: %w", err)
}
return label, nil
}
//nolint:gocognit
func (s *Service) Save(
ctx context.Context,
principalID int64,
spaceID, repoID *int64,
in *types.SaveInput,
) (*types.LabelWithValues, error) {
var label *types.Label
var valuesToReturn []*types.LabelValue
var err error
err = s.tx.WithTx(ctx, func(ctx context.Context) error {
label, err = s.labelStore.FindByID(ctx, in.Label.ID)
if err != nil {
if !errors.Is(err, store.ErrResourceNotFound) {
return err
}
label, err = s.Define(ctx, principalID, spaceID, repoID, &in.Label.DefineLabelInput)
if err != nil {
return err
}
} else {
label, err = s.update(ctx, principalID, label, &types.UpdateLabelInput{
Key: &in.Label.Key,
Type: &label.Type,
Description: &label.Description,
Color: &in.Label.Color,
})
if err != nil {
return err
}
}
existingValues, err := s.labelValueStore.List(ctx, label.ID, &types.ListQueryFilter{})
if err != nil {
return err
}
existingValuesMap := make(map[int64]*types.LabelValue, len(existingValues))
for _, value := range existingValues {
existingValuesMap[value.ID] = value
}
var valuesToCreate []*types.SaveLabelValueInput
valuesToUpdate := make(map[int64]*types.SaveLabelValueInput)
var valuesToDelete []string
for _, value := range in.Values {
if _, ok := existingValuesMap[value.ID]; ok {
valuesToUpdate[value.ID] = value
} else {
valuesToCreate = append(valuesToCreate, value)
}
}
for _, value := range existingValues {
if _, ok := valuesToUpdate[value.ID]; !ok {
valuesToDelete = append(valuesToDelete, value.Value)
}
}
valuesToReturn = make([]*types.LabelValue, len(valuesToCreate)+len(valuesToUpdate))
for i, value := range valuesToCreate {
valuesToReturn[i] = newLabelValue(principalID, label.ID, &value.DefineValueInput)
if err = s.labelValueStore.Define(ctx, valuesToReturn[i]); err != nil {
return err
}
}
i := len(valuesToCreate)
for _, value := range valuesToUpdate {
if valuesToReturn[i], err = s.updateValue(ctx, principalID, existingValuesMap[value.ID], &types.UpdateValueInput{
Value: &value.Value,
Color: &value.Color,
}); err != nil {
return err
}
i++
}
if err = s.labelValueStore.DeleteMany(ctx, label.ID, valuesToDelete); err != nil {
return err
}
if label.ValueCount, err = s.labelStore.IncrementValueCount(
ctx, label.ID, len(valuesToCreate)-len(valuesToDelete)); err != nil {
return err
}
sort.Slice(valuesToReturn, func(i, j int) bool {
return valuesToReturn[i].Value < valuesToReturn[j].Value
})
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to save label: %w", err)
}
return &types.LabelWithValues{
Label: *label,
Values: valuesToReturn,
}, nil
}
func (s *Service) Find(
ctx context.Context,
spaceID, repoID *int64,
key string,
) (*types.Label, error) {
return s.labelStore.Find(ctx, spaceID, repoID, key)
}
func (s *Service) List(
ctx context.Context,
spaceID, repoID *int64,
filter *types.LabelFilter,
) ([]*types.Label, error) {
if filter.Inherited {
return s.listInScopes(ctx, spaceID, repoID, filter)
}
return s.list(ctx, spaceID, repoID, filter)
}
func (s *Service) list(
ctx context.Context,
spaceID, repoID *int64,
filter *types.LabelFilter,
) ([]*types.Label, error) {
if repoID != nil {
return s.labelStore.List(ctx, nil, repoID, filter)
}
return s.labelStore.List(ctx, spaceID, nil, filter)
}
func (s *Service) listInScopes(
ctx context.Context,
spaceID, repoID *int64,
filter *types.LabelFilter,
) ([]*types.Label, error) {
var spaceIDs []int64
var repoIDVal int64
var err error
if repoID != nil {
spaceIDs, err = s.spaceStore.GetAncestorIDs(ctx, *spaceID)
if err != nil {
return nil, err
}
repoIDVal = *repoID
} else {
spaceIDs, err = s.spaceStore.GetAncestorIDs(ctx, *spaceID)
if err != nil {
return nil, err
}
}
return s.labelStore.ListInScopes(ctx, repoIDVal, spaceIDs, filter)
}
func (s *Service) Delete(
ctx context.Context,
spaceID, repoID *int64,
key string,
) error {
return s.labelStore.Delete(ctx, spaceID, repoID, key)
}
func newLabel(
principalID int64,
spaceID, repoID *int64,
scope int64,
in *types.DefineLabelInput,
) *types.Label {
now := time.Now().UnixMilli()
return &types.Label{
RepoID: repoID,
SpaceID: spaceID,
Scope: scope,
Key: in.Key,
Type: in.Type,
Description: in.Description,
Color: in.Color,
Created: now,
Updated: now,
CreatedBy: principalID,
UpdatedBy: principalID,
}
}
func applyChanges(principalID int64, label *types.Label, in *types.UpdateLabelInput) (*types.Label, bool) {
hasChanges := false
if label.UpdatedBy != principalID {
hasChanges = true
label.UpdatedBy = principalID
}
if in.Key != nil && label.Key != *in.Key {
hasChanges = true
label.Key = *in.Key
}
if in.Description != nil && label.Description != *in.Description {
hasChanges = true
label.Description = *in.Description
}
if in.Color != nil && label.Color != *in.Color {
hasChanges = true
label.Color = *in.Color
}
if in.Type != nil && label.Type != *in.Type {
hasChanges = true
label.Type = *in.Type
}
if hasChanges {
label.Updated = time.Now().UnixMilli()
}
return label, hasChanges
}

View File

@ -0,0 +1,274 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package label
import (
"context"
"fmt"
"slices"
"sort"
"time"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"golang.org/x/exp/maps"
)
func (s *Service) AssignToPullReq(
ctx context.Context,
principalID int64,
pullreqID int64,
repoID int64,
repoParentID int64,
in *types.PullReqCreateInput,
) (*types.PullReqLabel, error) {
label, err := s.labelStore.FindByID(ctx, in.LabelID)
if err != nil {
return nil, fmt.Errorf("failed to find label by id: %w", err)
}
if label.SpaceID != nil {
spaceIDs, err := s.spaceStore.GetAncestorIDs(ctx, repoParentID)
if err != nil {
return nil, fmt.Errorf("failed to get parent space ids: %w", err)
}
if ok := slices.Contains(spaceIDs, *label.SpaceID); !ok {
return nil, errors.NotFound(
"label %d is not defined in current space tree path", label.ID)
}
} else if label.RepoID != nil && *label.RepoID != repoID {
return nil, errors.InvalidArgument(
"label %d is not defined in current repo", label.ID)
}
pullreqLabel := newPullReqLabel(pullreqID, principalID, in)
if in.ValueID != nil {
labelValue, err := s.labelValueStore.FindByID(ctx, *in.ValueID)
if err != nil {
return nil, fmt.Errorf("failed to find label value by id: %w", err)
}
if label.ID != labelValue.LabelID {
return nil, errors.InvalidArgument("label value is not associated with label")
}
}
if in.Value != "" {
valueID, err := s.getOrDefineValue(ctx, principalID, label, in.Value)
if err != nil {
return nil, err
}
pullreqLabel.ValueID = &valueID
}
err = s.pullReqLabelAssignmentStore.Assign(ctx, pullreqLabel)
if err != nil {
return nil, fmt.Errorf("failed to assign label to pullreq: %w", err)
}
return pullreqLabel, nil
}
func (s *Service) getOrDefineValue(
ctx context.Context,
principalID int64,
label *types.Label,
value string,
) (int64, error) {
if label.Type != enum.LabelTypeDynamic {
return 0, errors.InvalidArgument("label doesn't allow new value assignment")
}
labelValue, err := s.labelValueStore.FindByLabelID(ctx, label.ID, value)
if err == nil {
return labelValue.ID, nil
}
if !errors.Is(err, store.ErrResourceNotFound) {
return 0, fmt.Errorf("failed to find label value: %w", err)
}
labelValue, err = s.DefineValue(
ctx,
principalID,
label.ID,
&types.DefineValueInput{
Value: value,
Color: label.Color,
},
)
if err != nil {
return 0, fmt.Errorf("failed to create label value: %w", err)
}
return labelValue.ID, nil
}
func (s *Service) UnassignFromPullReq(
ctx context.Context, repoID, repoParentID, pullreqID, labelID int64,
) error {
label, err := s.labelStore.FindByID(ctx, labelID)
if err != nil {
return fmt.Errorf("failed to find label by id: %w", err)
}
if label.RepoID != nil && *label.RepoID != repoID {
return errors.InvalidArgument(
"label %d is not defined in current repo", label.ID)
} else if label.SpaceID != nil {
spaceIDs, err := s.spaceStore.GetAncestorIDs(ctx, repoParentID)
if err != nil {
return fmt.Errorf("failed to get parent space ids: %w", err)
}
if ok := slices.Contains(spaceIDs, *label.SpaceID); !ok {
return errors.NotFound(
"label %d is not defined in current space tree path", label.ID)
}
}
return s.pullReqLabelAssignmentStore.Unassign(ctx, pullreqID, labelID)
}
func (s *Service) ListPullReqLabels(
ctx context.Context,
repo *types.Repository,
spaceID int64,
pullreqID int64,
filter *types.AssignableLabelFilter,
) (*types.ScopesLabels, error) {
spaces, err := s.spaceStore.GetHierarchy(ctx, spaceID)
if err != nil {
return nil, fmt.Errorf("failed to get space hierarchy: %w", err)
}
spaceIDs := make([]int64, len(spaces))
for i, space := range spaces {
spaceIDs[i] = space.ID
}
scopeLabelsMap := make(map[int64]*types.ScopeData)
pullreqAssignments, err := s.pullReqLabelAssignmentStore.ListAssigned(
ctx, pullreqID)
if err != nil {
return nil, fmt.Errorf("failed to list labels assigned to pullreq: %w", err)
}
if !filter.Assignable {
sortedAssignments := maps.Values(pullreqAssignments)
sort.Slice(sortedAssignments, func(i, j int) bool {
if sortedAssignments[i].Key != sortedAssignments[j].Key {
return sortedAssignments[i].Key < sortedAssignments[j].Key
}
return sortedAssignments[i].Scope < sortedAssignments[j].Scope
})
populateScopeLabelsMap(sortedAssignments, scopeLabelsMap, repo, spaces)
return createScopeLabels(sortedAssignments, scopeLabelsMap), nil
}
labelInfos, err := s.labelStore.ListInfosInScopes(ctx, repo.ID, spaceIDs, filter)
if err != nil {
return nil, fmt.Errorf("failed to list repo and spaces label infos: %w", err)
}
labelIDs := make([]int64, len(labelInfos))
for i, labelInfo := range labelInfos {
labelIDs[i] = labelInfo.ID
}
valueInfos, err := s.labelValueStore.ListInfosByLabelIDs(ctx, labelIDs)
if err != nil {
return nil, fmt.Errorf("failed to list label value infos by label ids: %w", err)
}
allAssignments := make([]*types.LabelAssignment, len(labelInfos))
for i, labelInfo := range labelInfos {
assignment, ok := pullreqAssignments[labelInfo.ID]
if !ok {
assignment = &types.LabelAssignment{
LabelInfo: *labelInfo,
}
}
assignment.LabelInfo.Assigned = &ok
allAssignments[i] = assignment
allAssignments[i].Values = valueInfos[labelInfo.ID]
}
populateScopeLabelsMap(allAssignments, scopeLabelsMap, repo, spaces)
return createScopeLabels(allAssignments, scopeLabelsMap), nil
}
func populateScopeLabelsMap(
assignments []*types.LabelAssignment,
scopeLabelsMap map[int64]*types.ScopeData,
repo *types.Repository,
spaces []*types.Space,
) {
for _, assignment := range assignments {
_, ok := scopeLabelsMap[assignment.Scope]
if ok {
continue
}
scopeLabelsMap[assignment.Scope] = &types.ScopeData{Scope: assignment.Scope}
if assignment.Scope == 0 {
scopeLabelsMap[assignment.Scope].Repo = repo
} else {
for _, space := range spaces {
if space.ID == *assignment.SpaceID {
scopeLabelsMap[assignment.Scope].Space = space
}
}
}
}
}
func createScopeLabels(
assignments []*types.LabelAssignment,
scopeLabelsMap map[int64]*types.ScopeData,
) *types.ScopesLabels {
scopeData := make([]*types.ScopeData, len(scopeLabelsMap))
for i, scopeLabel := range maps.Values(scopeLabelsMap) {
scopeData[i] = scopeLabel
}
sort.Slice(scopeData, func(i, j int) bool {
return scopeData[i].Scope < scopeData[j].Scope
})
return &types.ScopesLabels{
LabelData: assignments,
ScopeData: scopeData,
}
}
func newPullReqLabel(
pullreqID int64,
principalID int64,
in *types.PullReqCreateInput,
) *types.PullReqLabel {
now := time.Now().UnixMilli()
return &types.PullReqLabel{
PullReqID: pullreqID,
LabelID: in.LabelID,
ValueID: in.ValueID,
Created: now,
Updated: now,
CreatedBy: principalID,
UpdatedBy: principalID,
}
}

View File

@ -0,0 +1,167 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package label
import (
"context"
"fmt"
"time"
"github.com/harness/gitness/types"
)
func (s *Service) DefineValue(
ctx context.Context,
principalID int64,
labelID int64,
in *types.DefineValueInput,
) (*types.LabelValue, error) {
labelValue := newLabelValue(principalID, labelID, in)
err := s.tx.WithTx(ctx, func(ctx context.Context) error {
if err := s.labelValueStore.Define(ctx, labelValue); err != nil {
return err
}
if _, err := s.labelStore.IncrementValueCount(ctx, labelID, 1); err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}
return labelValue, nil
}
func applyValueChanges(
principalID int64,
value *types.LabelValue,
in *types.UpdateValueInput,
) (*types.LabelValue, bool) {
hasChanges := false
if value.UpdatedBy != principalID {
hasChanges = true
value.UpdatedBy = principalID
}
if in.Value != nil && value.Value != *in.Value {
hasChanges = true
value.Value = *in.Value
}
if in.Color != nil && value.Color != *in.Color {
hasChanges = true
value.Color = *in.Color
}
if hasChanges {
value.Updated = time.Now().UnixMilli()
}
return value, hasChanges
}
func (s *Service) UpdateValue(
ctx context.Context,
principalID int64,
labelID int64,
value string,
in *types.UpdateValueInput,
) (*types.LabelValue, error) {
labelValue, err := s.labelValueStore.FindByLabelID(ctx, labelID, value)
if err != nil {
return nil, fmt.Errorf("failed to find label value: %w", err)
}
return s.updateValue(ctx, principalID, labelValue, in)
}
func (s *Service) updateValue(
ctx context.Context,
principalID int64,
labelValue *types.LabelValue,
in *types.UpdateValueInput,
) (*types.LabelValue, error) {
labelValue, hasChanges := applyValueChanges(
principalID, labelValue, in)
if !hasChanges {
return labelValue, nil
}
if err := s.labelValueStore.Update(ctx, labelValue); err != nil {
return nil, fmt.Errorf("failed to update label value: %w", err)
}
return labelValue, nil
}
func (s *Service) ListValues(
ctx context.Context,
spaceID, repoID *int64,
labelKey string,
filter *types.ListQueryFilter,
) ([]*types.LabelValue, error) {
label, err := s.labelStore.Find(ctx, spaceID, repoID, labelKey)
if err != nil {
return nil, err
}
return s.labelValueStore.List(ctx, label.ID, filter)
}
func (s *Service) DeleteValue(
ctx context.Context,
spaceID, repoID *int64,
labelKey string,
value string,
) error {
label, err := s.labelStore.Find(ctx, spaceID, repoID, labelKey)
if err != nil {
return err
}
err = s.tx.WithTx(ctx, func(ctx context.Context) error {
if err := s.labelValueStore.Delete(ctx, label.ID, value); err != nil {
return err
}
if _, err := s.labelStore.IncrementValueCount(ctx, label.ID, -1); err != nil {
return err
}
return nil
})
if err != nil {
return err
}
return nil
}
func newLabelValue(
principalID int64,
labelID int64,
in *types.DefineValueInput,
) *types.LabelValue {
now := time.Now().UnixMilli()
return &types.LabelValue{
LabelID: labelID,
Value: in.Value,
Color: in.Color,
Created: now,
Updated: now,
CreatedBy: principalID,
UpdatedBy: principalID,
}
}

View File

@ -0,0 +1,44 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package label
import (
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/store/database/dbtx"
)
type Service struct {
tx dbtx.Transactor
spaceStore store.SpaceStore
labelStore store.LabelStore
labelValueStore store.LabelValueStore
pullReqLabelAssignmentStore store.PullReqLabelAssignmentStore
}
func New(
tx dbtx.Transactor,
spaceStore store.SpaceStore,
labelStore store.LabelStore,
labelValueStore store.LabelValueStore,
pullReqLabelAssignmentStore store.PullReqLabelAssignmentStore,
) *Service {
return &Service{
tx: tx,
spaceStore: spaceStore,
labelStore: labelStore,
labelValueStore: labelValueStore,
pullReqLabelAssignmentStore: pullReqLabelAssignmentStore,
}
}

View File

@ -0,0 +1,36 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package label
import (
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/store/database/dbtx"
"github.com/google/wire"
)
var WireSet = wire.NewSet(
ProvideLabel,
)
func ProvideLabel(
tx dbtx.Transactor,
spaceStore store.SpaceStore,
labelStore store.LabelStore,
labelValueStore store.LabelValueStore,
pullReqLabelStore store.PullReqLabelAssignmentStore,
) *Service {
return New(tx, spaceStore, labelStore, labelValueStore, pullReqLabelStore)
}

View File

@ -166,6 +166,14 @@ type (
// GetRootSpace returns a space where space_parent_id is NULL. // GetRootSpace returns a space where space_parent_id is NULL.
GetRootSpace(ctx context.Context, spaceID int64) (*types.Space, error) GetRootSpace(ctx context.Context, spaceID int64) (*types.Space, error)
// GetAncestorIDs returns a list of all space IDs along the recursive path to the root space.
GetAncestorIDs(ctx context.Context, spaceID int64) ([]int64, error)
GetHierarchy(
ctx context.Context,
spaceID int64,
) ([]*types.Space, error)
// Create creates a new space // Create creates a new space
Create(ctx context.Context, space *types.Space) error Create(ctx context.Context, space *types.Space) error
@ -930,4 +938,108 @@ type (
gitspaceConfigID int64, gitspaceConfigID int64,
) (*types.GitspaceEvent, error) ) (*types.GitspaceEvent, error)
} }
LabelStore interface {
// Define defines a label.
Define(ctx context.Context, lbl *types.Label) error
// Update updates a label.
Update(ctx context.Context, lbl *types.Label) error
// Find finds a label defined in a specified space/repo with a specified key.
Find(
ctx context.Context,
spaceID, repoID *int64,
key string,
) (*types.Label, error)
// Delete deletes a label defined in a specified space/repo with a specified key.
Delete(ctx context.Context, spaceID, repoID *int64, key string) error
// List list labels defined in a specified space/repo.
List(
ctx context.Context,
spaceID, repoID *int64,
filter *types.LabelFilter,
) ([]*types.Label, error)
// FindByID finds label with a specified id.
FindByID(ctx context.Context, id int64) (*types.Label, error)
// ListInScopes lists labels defined in repo and/or specified spaces.
ListInScopes(
ctx context.Context,
repoID int64,
scopeIDs []int64,
filter *types.LabelFilter,
) ([]*types.Label, error)
// ListInfosInScopes lists label infos defined in repo and/or specified spaces.
ListInfosInScopes(
ctx context.Context,
repoID int64,
spaceIDs []int64,
filter *types.AssignableLabelFilter,
) ([]*types.LabelInfo, error)
// IncrementValueCount increments count of values defined for a specified label.
IncrementValueCount(ctx context.Context, labelID int64, increment int) (int64, error)
}
LabelValueStore interface {
// Define defines a label value.
Define(ctx context.Context, lbl *types.LabelValue) error
// Update updates a label value.
Update(ctx context.Context, lblVal *types.LabelValue) error
// Delete deletes a label value associated with a specified label.
Delete(ctx context.Context, labelID int64, value string) error
// Delete deletes specified label values associated with a specified label.
DeleteMany(ctx context.Context, labelID int64, values []string) error
// FindByLabelID finds a label value defined for a specified label.
FindByLabelID(
ctx context.Context,
labelID int64,
value string,
) (*types.LabelValue, error)
// List lists label values defined for a specified label.
List(
ctx context.Context,
labelID int64,
opts *types.ListQueryFilter,
) ([]*types.LabelValue, error)
// FindByID finds label value with a specified id.
FindByID(ctx context.Context, id int64) (*types.LabelValue, error)
// ListInfosByLabelIDs list label infos by a specified label id.
ListInfosByLabelIDs(
ctx context.Context,
labelIDs []int64,
) (map[int64][]*types.LabelValueInfo, error)
Upsert(ctx context.Context, lblVal *types.LabelValue) error
}
PullReqLabelAssignmentStore interface {
// Assign assigns a label to a pullreq.
Assign(ctx context.Context, label *types.PullReqLabel) error
// Unassign removes a label from a pullreq with a specified id.
Unassign(ctx context.Context, pullreqID int64, labelID int64) error
// ListAssigned list labels assigned to a specified pullreq.
ListAssigned(ctx context.Context, pullreqID int64) (map[int64]*types.LabelAssignment, error)
// Find finds a label assigned to a pullreq with a specified id.
FindByLabelID(
ctx context.Context,
pullreqID int64,
labelID int64,
) (*types.PullReqLabel, error)
}
) )

398
app/store/database/label.go Normal file
View File

@ -0,0 +1,398 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package database
import (
"context"
"strings"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/store/database"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/Masterminds/squirrel"
"github.com/guregu/null"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
const (
labelColumns = `
label_space_id
,label_repo_id
,label_scope
,label_key
,label_description
,label_type
,label_color
,label_created
,label_updated
,label_created_by
,label_updated_by`
labelSelectBase = `SELECT label_id, ` + labelColumns + ` FROM labels`
)
type label struct {
ID int64 `db:"label_id"`
SpaceID null.Int `db:"label_space_id"`
RepoID null.Int `db:"label_repo_id"`
Scope int64 `db:"label_scope"`
Key string `db:"label_key"`
Description string `db:"label_description"`
Type enum.LabelType `db:"label_type"`
Color enum.LabelColor `db:"label_color"`
ValueCount int64 `db:"label_value_count"`
Created int64 `db:"label_created"`
Updated int64 `db:"label_updated"`
CreatedBy int64 `db:"label_created_by"`
UpdatedBy int64 `db:"label_updated_by"`
}
type labelInfo struct {
LabelID int64 `db:"label_id"`
SpaceID null.Int `db:"label_space_id"`
RepoID null.Int `db:"label_repo_id"`
Scope int64 `db:"label_scope"`
Key string `db:"label_key"`
Type enum.LabelType `db:"label_type"`
LabelColor enum.LabelColor `db:"label_color"`
}
type labelStore struct {
db *sqlx.DB
}
func NewLabelStore(
db *sqlx.DB,
) store.LabelStore {
return &labelStore{
db: db,
}
}
var _ store.LabelStore = (*labelStore)(nil)
func (s *labelStore) Define(ctx context.Context, lbl *types.Label) error {
const sqlQuery = `
INSERT INTO labels (` + labelColumns + `)` + `
values (
:label_space_id
,:label_repo_id
,:label_scope
,:label_key
,:label_description
,:label_type
,:label_color
,:label_created
,:label_updated
,:label_created_by
,:label_updated_by
)
RETURNING label_id`
db := dbtx.GetAccessor(ctx, s.db)
query, args, err := db.BindNamed(sqlQuery, mapInternalLabel(lbl))
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to bind query")
}
if err = db.QueryRowContext(ctx, query, args...).Scan(&lbl.ID); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to create label")
}
return nil
}
func (s *labelStore) Update(ctx context.Context, lbl *types.Label) error {
const sqlQuery = `
UPDATE labels SET
label_key = :label_key
,label_description = :label_description
,label_type = :label_type
,label_color = :label_color
,label_updated = :label_updated
,label_updated_by = :label_updated_by
WHERE label_id = :label_id`
db := dbtx.GetAccessor(ctx, s.db)
query, args, err := db.BindNamed(sqlQuery, mapInternalLabel(lbl))
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to bind query")
}
if _, err := db.ExecContext(ctx, query, args...); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to update label")
}
return nil
}
func (s *labelStore) IncrementValueCount(
ctx context.Context,
labelID int64,
increment int,
) (int64, error) {
const sqlQuery = `
UPDATE labels
SET label_value_count = label_value_count + $1
WHERE label_id = $2
RETURNING label_value_count
`
db := dbtx.GetAccessor(ctx, s.db)
var valueCount int64
if err := db.QueryRowContext(ctx, sqlQuery, increment, labelID).Scan(&valueCount); err != nil {
return 0, database.ProcessSQLErrorf(ctx, err, "Failed to increment label_value_count")
}
return valueCount, nil
}
func (s *labelStore) Find(
ctx context.Context,
spaceID, repoID *int64,
key string,
) (*types.Label, error) {
const sqlQuery = labelSelectBase + `
WHERE (label_space_id = $1 OR label_repo_id = $2) AND LOWER(label_key) = LOWER($3)`
db := dbtx.GetAccessor(ctx, s.db)
var dst label
if err := db.GetContext(ctx, &dst, sqlQuery, spaceID, repoID, key); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find label")
}
return mapLabel(&dst), nil
}
func (s *labelStore) FindByID(ctx context.Context, id int64) (*types.Label, error) {
const sqlQuery = labelSelectBase + `
WHERE label_id = $1`
db := dbtx.GetAccessor(ctx, s.db)
var dst label
if err := db.GetContext(ctx, &dst, sqlQuery, id); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find label")
}
return mapLabel(&dst), nil
}
func (s *labelStore) Delete(ctx context.Context, spaceID, repoID *int64, name string) error {
const sqlQuery = `
DELETE FROM labels
WHERE (label_space_id = $1 OR label_repo_id = $2) AND LOWER(label_key) = LOWER($3)`
db := dbtx.GetAccessor(ctx, s.db)
if _, err := db.ExecContext(ctx, sqlQuery, spaceID, repoID, name); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to delete label")
}
return nil
}
// List returns a list of pull requests for a repo or space.
func (s *labelStore) List(
ctx context.Context,
spaceID, repoID *int64,
filter *types.LabelFilter,
) ([]*types.Label, error) {
stmt := database.Builder.
Select(`label_id, ` + labelColumns + `, label_value_count`).
From("labels").
OrderBy("label_key")
stmt = stmt.Where("(label_space_id = ? OR label_repo_id = ?)", spaceID, repoID)
stmt = stmt.Limit(database.Limit(filter.Size))
stmt = stmt.Offset(database.Offset(filter.Page, filter.Size))
if filter.Query != "" {
stmt = stmt.Where(
"LOWER(label_key) LIKE ?", strings.ToLower(filter.Query))
}
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, s.db)
var dst []*label
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to list labels")
}
return mapSliceLabel(dst), nil
}
func (s *labelStore) ListInScopes(
ctx context.Context,
repoID int64,
scopeIDs []int64,
filter *types.LabelFilter,
) ([]*types.Label, error) {
stmt := database.Builder.
Select(`label_id, ` + labelColumns + `, label_value_count`).
From("labels")
stmt = stmt.Where(squirrel.Or{
squirrel.Eq{"label_space_id": scopeIDs},
squirrel.Eq{"label_repo_id": repoID},
}).
OrderBy("label_key").
OrderBy("label_scope")
stmt = stmt.Limit(database.Limit(filter.Size))
stmt = stmt.Offset(database.Offset(filter.Page, filter.Size))
if filter.Query != "" {
stmt = stmt.Where(
"LOWER(label_key) LIKE ?", strings.ToLower(filter.Query))
}
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, s.db)
var dst []*label
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to list labels in hierarchy")
}
return mapSliceLabel(dst), nil
}
func (s *labelStore) ListInfosInScopes(
ctx context.Context,
repoID int64,
spaceIDs []int64,
filter *types.AssignableLabelFilter,
) ([]*types.LabelInfo, error) {
stmt := database.Builder.
Select(`
label_id
,label_space_id
,label_repo_id
,label_scope
,label_key
,label_type
,label_color`).
From("labels").
Where(squirrel.Or{
squirrel.Eq{"label_space_id": spaceIDs},
squirrel.Eq{"label_repo_id": repoID},
}).
OrderBy("label_key").
OrderBy("label_scope")
stmt = stmt.Limit(database.Limit(filter.Size))
stmt = stmt.Offset(database.Offset(filter.Page, filter.Size))
if filter.Query != "" {
stmt = stmt.Where(
"LOWER(label_key) LIKE ?", strings.ToLower(filter.Query))
}
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, s.db)
var dst []*labelInfo
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to list labels")
}
return mapLabelInfos(dst), nil
}
func mapLabel(lbl *label) *types.Label {
return &types.Label{
ID: lbl.ID,
SpaceID: lbl.SpaceID.Ptr(),
RepoID: lbl.RepoID.Ptr(),
Scope: lbl.Scope,
Key: lbl.Key,
Type: lbl.Type,
Description: lbl.Description,
ValueCount: lbl.ValueCount,
Color: lbl.Color,
Created: lbl.Created,
Updated: lbl.Updated,
CreatedBy: lbl.CreatedBy,
UpdatedBy: lbl.UpdatedBy,
}
}
func mapSliceLabel(dbLabels []*label) []*types.Label {
result := make([]*types.Label, len(dbLabels))
for i, lbl := range dbLabels {
result[i] = mapLabel(lbl)
}
return result
}
func mapInternalLabel(lbl *types.Label) *label {
return &label{
ID: lbl.ID,
SpaceID: null.IntFromPtr(lbl.SpaceID),
RepoID: null.IntFromPtr(lbl.RepoID),
Scope: lbl.Scope,
Key: lbl.Key,
Description: lbl.Description,
Type: lbl.Type,
Color: lbl.Color,
Created: lbl.Created,
Updated: lbl.Updated,
CreatedBy: lbl.CreatedBy,
UpdatedBy: lbl.UpdatedBy,
}
}
func mapLabelInfo(internal *labelInfo) *types.LabelInfo {
return &types.LabelInfo{
ID: internal.LabelID,
RepoID: internal.RepoID.Ptr(),
SpaceID: internal.SpaceID.Ptr(),
Scope: internal.Scope,
Key: internal.Key,
Type: internal.Type,
Color: internal.LabelColor,
}
}
func mapLabelInfos(
dbLabels []*labelInfo,
) []*types.LabelInfo {
result := make([]*types.LabelInfo, len(dbLabels))
for i, lbl := range dbLabels {
result[i] = mapLabelInfo(lbl)
}
return result
}

View File

@ -0,0 +1,196 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package database
import (
"context"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/store/database"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/guregu/null"
"github.com/jmoiron/sqlx"
)
var _ store.PullReqLabelAssignmentStore = (*pullReqLabelStore)(nil)
func NewPullReqLabelStore(db *sqlx.DB) store.PullReqLabelAssignmentStore {
return &pullReqLabelStore{
db: db,
}
}
type pullReqLabelStore struct {
db *sqlx.DB
}
type pullReqLabel struct {
PullReqID int64 `db:"pullreq_label_pullreq_id"`
LabelID int64 `db:"pullreq_label_label_id"`
LabelValueID null.Int `db:"pullreq_label_label_value_id"`
Created int64 `db:"pullreq_label_created"`
Updated int64 `db:"pullreq_label_updated"`
CreatedBy int64 `db:"pullreq_label_created_by"`
UpdatedBy int64 `db:"pullreq_label_updated_by"`
}
const (
pullReqLabelColumns = `
pullreq_label_pullreq_id
,pullreq_label_label_id
,pullreq_label_label_value_id
,pullreq_label_created
,pullreq_label_updated
,pullreq_label_created_by
,pullreq_label_updated_by`
)
func (s *pullReqLabelStore) Assign(ctx context.Context, label *types.PullReqLabel) error {
const sqlQuery = `
INSERT INTO pullreq_labels (` + pullReqLabelColumns + `)
values (
:pullreq_label_pullreq_id
,:pullreq_label_label_id
,:pullreq_label_label_value_id
,:pullreq_label_created
,:pullreq_label_updated
,:pullreq_label_created_by
,:pullreq_label_updated_by
)
ON CONFLICT (pullreq_label_pullreq_id, pullreq_label_label_id)
DO UPDATE SET
pullreq_label_label_value_id = EXCLUDED.pullreq_label_label_value_id,
pullreq_label_updated = EXCLUDED.pullreq_label_updated,
pullreq_label_updated_by = EXCLUDED.pullreq_label_updated_by
RETURNING pullreq_label_created, pullreq_label_created_by
`
db := dbtx.GetAccessor(ctx, s.db)
query, args, err := db.BindNamed(sqlQuery, mapInternalPullReqLabel(label))
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "failed to bind query")
}
if err = db.QueryRowContext(ctx, query, args...).Scan(&label.Created, &label.CreatedBy); err != nil {
return database.ProcessSQLErrorf(ctx, err, "failed to create pull request label")
}
return nil
}
func (s *pullReqLabelStore) Unassign(ctx context.Context, pullreqID int64, labelID int64) error {
const sqlQuery = `
DELETE FROM pullreq_labels
WHERE pullreq_label_pullreq_id = $1 AND pullreq_label_label_id = $2`
db := dbtx.GetAccessor(ctx, s.db)
if _, err := db.ExecContext(ctx, sqlQuery, pullreqID, labelID); err != nil {
return database.ProcessSQLErrorf(ctx, err, "failed to delete pullreq label")
}
return nil
}
func (s *pullReqLabelStore) FindByLabelID(
ctx context.Context,
pullreqID int64,
labelID int64,
) (*types.PullReqLabel, error) {
const sqlQuery = `SELECT ` + pullReqLabelColumns + `
FROM pullreq_labels
WHERE pullreq_label_pullreq_id = $1 AND pullreq_label_label_id = $2`
db := dbtx.GetAccessor(ctx, s.db)
var dst pullReqLabel
if err := db.GetContext(ctx, &dst, sqlQuery, pullreqID, labelID); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "failed to delete pullreq label")
}
return mapPullReqLabel(&dst), nil
}
func (s *pullReqLabelStore) ListAssigned(
ctx context.Context,
pullreqID int64,
) (map[int64]*types.LabelAssignment, error) {
const sqlQueryAssigned = `
SELECT
label_id
,label_repo_id
,label_space_id
,label_key
,label_value_id
,label_value_label_id
,label_value_value
,label_color
,label_value_color
,label_scope
,label_type
FROM pullreq_labels prl
INNER JOIN labels l ON prl.pullreq_label_label_id = l.label_id
LEFT JOIN label_values lv ON prl.pullreq_label_label_value_id = lv.label_value_id
WHERE prl.pullreq_label_pullreq_id = $1`
db := dbtx.GetAccessor(ctx, s.db)
var dst []*struct {
labelInfo
labelValueInfo
}
if err := db.SelectContext(ctx, &dst, sqlQueryAssigned, pullreqID); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "failed to find label")
}
ret := make(map[int64]*types.LabelAssignment, len(dst))
for _, res := range dst {
li := mapLabelInfo(&res.labelInfo)
lvi := mapLabeValuelInfo(&res.labelValueInfo)
ret[li.ID] = &types.LabelAssignment{
LabelInfo: *li,
AssignedValue: lvi,
}
}
return ret, nil
}
func mapInternalPullReqLabel(lbl *types.PullReqLabel) *pullReqLabel {
return &pullReqLabel{
PullReqID: lbl.PullReqID,
LabelID: lbl.LabelID,
LabelValueID: null.IntFromPtr(lbl.ValueID),
Created: lbl.Created,
Updated: lbl.Updated,
CreatedBy: lbl.CreatedBy,
UpdatedBy: lbl.UpdatedBy,
}
}
func mapPullReqLabel(lbl *pullReqLabel) *types.PullReqLabel {
return &types.PullReqLabel{
PullReqID: lbl.PullReqID,
LabelID: lbl.LabelID,
ValueID: lbl.LabelValueID.Ptr(),
Created: lbl.Created,
Updated: lbl.Updated,
CreatedBy: lbl.CreatedBy,
UpdatedBy: lbl.UpdatedBy,
}
}

View File

@ -0,0 +1,358 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package database
import (
"context"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/store/database"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/Masterminds/squirrel"
"github.com/guregu/null"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
const (
labelValueColumns = `
label_value_label_id
,label_value_value
,label_value_color
,label_value_created
,label_value_updated
,label_value_created_by
,label_value_updated_by`
labelValueSelectBase = `SELECT label_value_id, ` + labelValueColumns + ` FROM label_values`
)
type labelValue struct {
ID int64 `db:"label_value_id"`
LabelID int64 `db:"label_value_label_id"`
Value string `db:"label_value_value"`
Color enum.LabelColor `db:"label_value_color"`
Created int64 `db:"label_value_created"`
Updated int64 `db:"label_value_updated"`
CreatedBy int64 `db:"label_value_created_by"`
UpdatedBy int64 `db:"label_value_updated_by"`
}
type labelValueInfo struct {
ValueID null.Int `db:"label_value_id"`
LabelID null.Int `db:"label_value_label_id"`
Value null.String `db:"label_value_value"`
ValueColor null.String `db:"label_value_color"`
}
type labelValueStore struct {
db *sqlx.DB
}
func NewLabelValueStore(
db *sqlx.DB,
) store.LabelValueStore {
return &labelValueStore{
db: db,
}
}
var _ store.LabelValueStore = (*labelValueStore)(nil)
func (s *labelValueStore) Define(ctx context.Context, lblVal *types.LabelValue) error {
const sqlQuery = `
INSERT INTO label_values (` + labelValueColumns + `)` + `
values (
:label_value_label_id
,:label_value_value
,:label_value_color
,:label_value_created
,:label_value_updated
,:label_value_created_by
,:label_value_updated_by
)
RETURNING label_value_id`
db := dbtx.GetAccessor(ctx, s.db)
query, args, err := db.BindNamed(sqlQuery, mapInternalLabelValue(lblVal))
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to bind query")
}
if err = db.QueryRowContext(ctx, query, args...).Scan(&lblVal.ID); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to create label value")
}
return nil
}
func (s *labelValueStore) Update(ctx context.Context, lblVal *types.LabelValue) error {
const sqlQuery = `
UPDATE label_values SET
label_value_value = :label_value_value
,label_value_color = :label_value_color
,label_value_updated = :label_value_updated
,label_value_updated_by = :label_value_updated_by
WHERE label_value_id = :label_value_id`
db := dbtx.GetAccessor(ctx, s.db)
query, args, err := db.BindNamed(sqlQuery, mapInternalLabelValue(lblVal))
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to bind query")
}
if _, err := db.ExecContext(ctx, query, args...); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to update label value")
}
return nil
}
func (s *labelValueStore) Upsert(ctx context.Context, lblVal *types.LabelValue) error {
const sqlQuery = `
INSERT INTO label_values (` + labelValueColumns + `)
VALUES (
:label_value_label_id,
:label_value_value,
:label_value_color,
:label_value_created,
:label_value_updated,
:label_value_created_by,
:label_value_updated_by
)
ON CONFLICT (label_value_id) DO UPDATE SET
label_value_value = EXCLUDED.label_value_value,
label_value_color = EXCLUDED.label_value_color,
label_value_updated = EXCLUDED.label_value_updated,
label_value_updated_by = EXCLUDED.label_value_updated_by
RETURNING label_value_id`
db := dbtx.GetAccessor(ctx, s.db)
query, args, err := db.BindNamed(sqlQuery, mapInternalLabelValue(lblVal))
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to bind query")
}
if err = db.QueryRowContext(ctx, query, args...).Scan(&lblVal.ID); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to upsert label value")
}
return nil
}
func (s *labelValueStore) Delete(
ctx context.Context,
labelID int64,
value string,
) error {
const sqlQuery = `
DELETE FROM label_values
WHERE label_value_label_id = $1 AND LOWER(label_value_value) = LOWER($2)`
db := dbtx.GetAccessor(ctx, s.db)
if _, err := db.ExecContext(ctx, sqlQuery, labelID, value); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to delete label")
}
return nil
}
func (s *labelValueStore) DeleteMany(
ctx context.Context,
labelID int64,
values []string,
) error {
stmt := database.Builder.
Delete("label_values").
Where("label_value_label_id = ?", labelID).
Where(squirrel.Eq{"label_value_value": values})
sql, args, err := stmt.ToSql()
if err != nil {
return errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, s.db)
if _, err := db.ExecContext(ctx, sql, args...); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to delete label")
}
return nil
}
// List returns a list of label values for a specified label.
func (s *labelValueStore) List(
ctx context.Context,
labelID int64,
opts *types.ListQueryFilter,
) ([]*types.LabelValue, error) {
stmt := database.Builder.
Select(`label_value_id, ` + labelValueColumns).
From("label_values")
stmt = stmt.Where("label_value_label_id = ?", labelID)
stmt = stmt.Limit(database.Limit(opts.Size))
stmt = stmt.Offset(database.Offset(opts.Page, opts.Size))
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, s.db)
var dst []*labelValue
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Fail to list labels")
}
return mapSliceLabelValue(dst), nil
}
func (s *labelValueStore) ListInfosByLabelIDs(
ctx context.Context,
labelIDs []int64,
) (map[int64][]*types.LabelValueInfo, error) {
stmt := database.Builder.
Select(`
label_value_id
,label_value_label_id
,label_value_value
,label_value_color
`).
From("label_values").
Where(squirrel.Eq{"label_value_label_id": labelIDs}).
OrderBy("label_value_value")
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, s.db)
var dst []*labelValueInfo
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Fail to list labels")
}
valueInfos := mapLabelValuInfos(dst)
labelValueMap := make(map[int64][]*types.LabelValueInfo)
for _, info := range valueInfos {
labelValueMap[*info.LabelID] = append(labelValueMap[*info.LabelID], info)
}
return labelValueMap, nil
}
func (s *labelValueStore) FindByLabelID(
ctx context.Context,
labelID int64,
value string,
) (*types.LabelValue, error) {
const sqlQuery = labelValueSelectBase + `
WHERE label_value_label_id = $1 AND LOWER(label_value_value) = LOWER($2)`
db := dbtx.GetAccessor(ctx, s.db)
var dst labelValue
if err := db.GetContext(ctx, &dst, sqlQuery, labelID, value); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find label")
}
return mapLabelValue(&dst), nil
}
func (s *labelValueStore) FindByID(ctx context.Context, id int64) (*types.LabelValue, error) {
const sqlQuery = labelValueSelectBase + `
WHERE label_value_id = $1`
db := dbtx.GetAccessor(ctx, s.db)
var dst labelValue
if err := db.GetContext(ctx, &dst, sqlQuery, id); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find label")
}
return mapLabelValue(&dst), nil
}
func mapLabelValue(lbl *labelValue) *types.LabelValue {
return &types.LabelValue{
ID: lbl.ID,
LabelID: lbl.LabelID,
Value: lbl.Value,
Color: lbl.Color,
Created: lbl.Created,
Updated: lbl.Updated,
CreatedBy: lbl.CreatedBy,
UpdatedBy: lbl.UpdatedBy,
}
}
func mapSliceLabelValue(dbLabelValues []*labelValue) []*types.LabelValue {
result := make([]*types.LabelValue, len(dbLabelValues))
for i, lbl := range dbLabelValues {
result[i] = mapLabelValue(lbl)
}
return result
}
func mapInternalLabelValue(lblVal *types.LabelValue) *labelValue {
return &labelValue{
ID: lblVal.ID,
LabelID: lblVal.LabelID,
Value: lblVal.Value,
Color: lblVal.Color,
Created: lblVal.Created,
Updated: lblVal.Updated,
CreatedBy: lblVal.CreatedBy,
UpdatedBy: lblVal.UpdatedBy,
}
}
func mapLabeValuelInfo(internal *labelValueInfo) *types.LabelValueInfo {
if !internal.ValueID.Valid {
return nil
}
return &types.LabelValueInfo{
ID: internal.ValueID.Ptr(),
LabelID: internal.LabelID.Ptr(),
Value: internal.Value.Ptr(),
Color: internal.ValueColor.Ptr(),
}
}
func mapLabelValuInfos(
dbLabels []*labelValueInfo,
) []*types.LabelValueInfo {
result := make([]*types.LabelValueInfo, len(dbLabels))
for i, lbl := range dbLabels {
result[i] = mapLabeValuelInfo(lbl)
}
return result
}

View File

@ -0,0 +1,3 @@
DROP TABLE pullreq_labels;
DROP TABLE label_values;
DROP TABLE labels;

View File

@ -0,0 +1,78 @@
CREATE TABLE labels (
label_id SERIAL PRIMARY KEY,
label_space_id INTEGER DEFAULT NULL,
label_repo_id INTEGER DEFAULT NULL,
label_scope INTEGER DEFAULT 0,
label_key TEXT NOT NULL,
label_description TEXT NOT NULL DEFAULT '',
label_color TEXT NOT NULL DEFAULT 'black',
label_type TEXT NOT NULL DEFAULT 'static',
label_created BIGINT NOT NULL,
label_updated BIGINT NOT NULL,
label_created_by INTEGER NOT NULL,
label_updated_by INTEGER NOT NULL,
label_value_count INTEGER DEFAULT 0,
CONSTRAINT fk_labels_space_id FOREIGN KEY (label_space_id)
REFERENCES spaces (space_id) ON DELETE CASCADE,
CONSTRAINT fk_labels_repo_id FOREIGN KEY (label_repo_id)
REFERENCES repositories (repo_id) ON DELETE CASCADE,
CONSTRAINT chk_label_space_or_repo
CHECK (label_space_id IS NULL OR label_repo_id IS NULL),
CONSTRAINT fk_labels_created_by FOREIGN KEY (label_created_by)
REFERENCES principals (principal_id),
CONSTRAINT fk_labels_updated_by FOREIGN KEY (label_updated_by)
REFERENCES principals (principal_id)
);
CREATE UNIQUE INDEX labels_space_id_key
ON labels(label_space_id, LOWER(label_key))
WHERE label_space_id IS NOT NULL;
CREATE UNIQUE INDEX labels_repo_id_key
ON labels(label_repo_id, LOWER(label_key))
WHERE label_repo_id IS NOT NULL;
CREATE TABLE label_values (
label_value_id SERIAL PRIMARY KEY,
label_value_label_id INTEGER NOT NULL,
label_value_value TEXT NOT NULL,
label_value_color TEXT NOT NULL,
label_value_created BIGINT NOT NULL,
label_value_updated BIGINT NOT NULL,
label_value_created_by INTEGER NOT NULL,
label_value_updated_by INTEGER NOT NULL,
CONSTRAINT fk_label_values_label_id FOREIGN KEY (label_value_label_id)
REFERENCES labels (label_id) ON DELETE CASCADE,
CONSTRAINT fk_label_values_created_by FOREIGN KEY (label_value_created_by)
REFERENCES principals (principal_id),
CONSTRAINT fk_labels_values_updated_by FOREIGN KEY (label_value_updated_by)
REFERENCES principals (principal_id)
);
CREATE UNIQUE INDEX unique_label_value_label_id_value
ON label_values(label_value_label_id, LOWER(label_value_value));
CREATE TABLE pullreq_labels (
pullreq_label_pullreq_id INTEGER NOT NULL,
pullreq_label_label_id INTEGER NOT NULL,
pullreq_label_label_value_id INTEGER DEFAULT NULL,
pullreq_label_created BIGINT NOT NULL,
pullreq_label_updated BIGINT NOT NULL,
pullreq_label_created_by INTEGER NOT NULL,
pullreq_label_updated_by INTEGER NOT NULL,
CONSTRAINT fk_pullreq_labels_pullreq_id FOREIGN KEY (pullreq_label_pullreq_id)
REFERENCES pullreqs (pullreq_id) ON DELETE CASCADE,
CONSTRAINT fk_pullreq_labels_label_id FOREIGN KEY (pullreq_label_label_id)
REFERENCES labels (label_id) ON DELETE CASCADE,
CONSTRAINT fk_pullreq_labels_label_value_id FOREIGN KEY (pullreq_label_label_value_id)
REFERENCES label_values (label_value_id) ON DELETE SET NULL,
CONSTRAINT fk_pullreq_labels_created_by FOREIGN KEY (pullreq_label_created_by)
REFERENCES principals (principal_id),
CONSTRAINT fk_pullreq_labels_updated_by FOREIGN KEY (pullreq_label_updated_by)
REFERENCES principals (principal_id),
PRIMARY KEY (pullreq_label_pullreq_id, pullreq_label_label_id)
);

View File

@ -0,0 +1,3 @@
DROP TABLE pullreq_labels;
DROP TABLE label_values;
DROP TABLE labels;

View File

@ -0,0 +1,78 @@
CREATE TABLE labels (
label_id INTEGER PRIMARY KEY AUTOINCREMENT,
label_space_id INTEGER DEFAULT NULL,
label_repo_id INTEGER DEFAULT NULL,
label_scope INTEGER DEFAULT 0,
label_key TEXT NOT NULL,
label_description TEXT NOT NULL DEFAULT '',
label_color TEXT NOT NULL DEFAULT 'black',
label_type TEXT NOT NULL DEFAULT 'static',
label_created BIGINT NOT NULL,
label_updated BIGINT NOT NULL,
label_created_by INTEGER NOT NULL,
label_updated_by INTEGER NOT NULL,
label_value_count INTEGER DEFAULT 0,
CONSTRAINT fk_labels_space_id FOREIGN KEY (label_space_id)
REFERENCES spaces (space_id) ON DELETE CASCADE,
CONSTRAINT fk_labels_repo_id FOREIGN KEY (label_repo_id)
REFERENCES repositories (repo_id) ON DELETE CASCADE,
CONSTRAINT chk_label_space_or_repo
CHECK (label_space_id IS NULL OR label_repo_id IS NULL),
CONSTRAINT fk_labels_created_by FOREIGN KEY (label_created_by)
REFERENCES principals (principal_id),
CONSTRAINT fk_labels_updated_by FOREIGN KEY (label_updated_by)
REFERENCES principals (principal_id)
);
CREATE UNIQUE INDEX labels_space_id_key
ON labels(label_space_id, LOWER(label_key))
WHERE label_space_id IS NOT NULL;
CREATE UNIQUE INDEX labels_repo_id_key
ON labels(label_repo_id, LOWER(label_key))
WHERE label_repo_id IS NOT NULL;
CREATE TABLE label_values (
label_value_id INTEGER PRIMARY KEY AUTOINCREMENT,
label_value_label_id INTEGER NOT NULL,
label_value_value TEXT NOT NULL,
label_value_color TEXT NOT NULL,
label_value_created BIGINT NOT NULL,
label_value_updated BIGINT NOT NULL,
label_value_created_by INTEGER NOT NULL,
label_value_updated_by INTEGER NOT NULL,
CONSTRAINT fk_label_values_label_id FOREIGN KEY (label_value_label_id)
REFERENCES labels (label_id) ON DELETE CASCADE,
CONSTRAINT fk_label_values_created_by FOREIGN KEY (label_value_created_by)
REFERENCES principals (principal_id),
CONSTRAINT fk_labels_values_updated_by FOREIGN KEY (label_value_updated_by)
REFERENCES principals (principal_id)
);
CREATE UNIQUE INDEX unique_label_value_label_id_value
ON label_values(label_value_label_id, LOWER(label_value_value));
CREATE TABLE pullreq_labels (
pullreq_label_pullreq_id INTEGER NOT NULL,
pullreq_label_label_id INTEGER NOT NULL,
pullreq_label_label_value_id INTEGER DEFAULT NULL,
pullreq_label_created BIGINT NOT NULL,
pullreq_label_updated BIGINT NOT NULL,
pullreq_label_created_by INTEGER NOT NULL,
pullreq_label_updated_by INTEGER NOT NULL,
CONSTRAINT fk_pullreq_labels_pullreq_id FOREIGN KEY (pullreq_label_pullreq_id)
REFERENCES pullreqs (pullreq_id) ON DELETE CASCADE,
CONSTRAINT fk_pullreq_labels_label_id FOREIGN KEY (pullreq_label_label_id)
REFERENCES labels (label_id) ON DELETE CASCADE,
CONSTRAINT fk_pullreq_labels_label_value_id FOREIGN KEY (pullreq_label_label_value_id)
REFERENCES label_values (label_value_id) ON DELETE SET NULL,
CONSTRAINT fk_pullreq_labels_created_by FOREIGN KEY (pullreq_label_created_by)
REFERENCES principals (principal_id),
CONSTRAINT fk_pullreq_labels_updated_by FOREIGN KEY (pullreq_label_updated_by)
REFERENCES principals (principal_id),
PRIMARY KEY (pullreq_label_pullreq_id, pullreq_label_label_id)
);

View File

@ -192,9 +192,8 @@ func (s *SpaceStore) findByPathAndDeletedAt(
return s.find(ctx, spaceID, &deletedAt) return s.find(ctx, spaceID, &deletedAt)
} }
// GetRootSpace returns a space where space_parent_id is NULL. const spaceRecursiveQuery = `
func (s *SpaceStore) GetRootSpace(ctx context.Context, spaceID int64) (*types.Space, error) { WITH RECURSIVE SpaceHierarchy(space_hierarchy_id, space_hierarchy_parent_id) AS (
query := `WITH RECURSIVE SpaceHierarchy AS (
SELECT space_id, space_parent_id SELECT space_id, space_parent_id
FROM spaces FROM spaces
WHERE space_id = $1 WHERE space_id = $1
@ -203,11 +202,16 @@ func (s *SpaceStore) GetRootSpace(ctx context.Context, spaceID int64) (*types.Sp
SELECT s.space_id, s.space_parent_id SELECT s.space_id, s.space_parent_id
FROM spaces s FROM spaces s
JOIN SpaceHierarchy h ON s.space_id = h.space_parent_id JOIN SpaceHierarchy h ON s.space_id = h.space_hierarchy_parent_id
) )
SELECT space_id `
FROM SpaceHierarchy
WHERE space_parent_id IS NULL;` // GetRootSpace returns a space where space_parent_id is NULL.
func (s *SpaceStore) GetRootSpace(ctx context.Context, spaceID int64) (*types.Space, error) {
query := spaceRecursiveQuery + `
SELECT space_hierarchy_id
FROM SpaceHierarchy
WHERE space_hierarchy_parent_id IS NULL;`
db := dbtx.GetAccessor(ctx, s.db) db := dbtx.GetAccessor(ctx, s.db)
@ -219,6 +223,39 @@ WHERE space_parent_id IS NULL;`
return s.Find(ctx, rootID) return s.Find(ctx, rootID)
} }
// GetAncestorIDs returns a list of all space IDs along the recursive path to the root space.
func (s *SpaceStore) GetAncestorIDs(ctx context.Context, spaceID int64) ([]int64, error) {
query := spaceRecursiveQuery + `
SELECT space_hierarchy_id FROM SpaceHierarchy`
db := dbtx.GetAccessor(ctx, s.db)
var spaceIDs []int64
if err := db.SelectContext(ctx, &spaceIDs, query, spaceID); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "failed to get space hierarchy")
}
return spaceIDs, nil
}
func (s *SpaceStore) GetHierarchy(
ctx context.Context,
spaceID int64,
) ([]*types.Space, error) {
query := spaceRecursiveQuery + `
SELECT ` + spaceColumns + `
FROM spaces INNER JOIN SpaceHierarchy ON space_id = space_hierarchy_id`
db := dbtx.GetAccessor(ctx, s.db)
var dst []*space
if err := db.SelectContext(ctx, &dst, query, spaceID); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed executing custom list query")
}
return s.mapToSpaces(ctx, s.db, dst)
}
// Create a new space. // Create a new space.
func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error { func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error {
if space == nil { if space == nil {

View File

@ -65,6 +65,9 @@ var WireSet = wire.NewSet(
ProvideGitspaceConfigStore, ProvideGitspaceConfigStore,
ProvideGitspaceInstanceStore, ProvideGitspaceInstanceStore,
ProvideGitspaceEventStore, ProvideGitspaceEventStore,
ProvideLabelStore,
ProvideLabelValueStore,
ProvidePullReqLabelStore,
) )
// migrator is helper function to set up the database by performing automated // migrator is helper function to set up the database by performing automated
@ -289,3 +292,18 @@ func ProvidePublicKeyStore(db *sqlx.DB) store.PublicKeyStore {
func ProvideGitspaceEventStore(db *sqlx.DB) store.GitspaceEventStore { func ProvideGitspaceEventStore(db *sqlx.DB) store.GitspaceEventStore {
return NewGitspaceEventStore(db) return NewGitspaceEventStore(db)
} }
// ProvideLabelStore provides a label store.
func ProvideLabelStore(db *sqlx.DB) store.LabelStore {
return NewLabelStore(db)
}
// ProvideLabelValueStore provides a label value store.
func ProvideLabelValueStore(db *sqlx.DB) store.LabelValueStore {
return NewLabelValueStore(db)
}
// ProvideLabelValueStore provides a label value store.
func ProvidePullReqLabelStore(db *sqlx.DB) store.PullReqLabelAssignmentStore {
return NewPullReqLabelStore(db)
}

View File

@ -70,6 +70,7 @@ import (
"github.com/harness/gitness/app/services/importer" "github.com/harness/gitness/app/services/importer"
infraproviderSvc "github.com/harness/gitness/app/services/infraprovider" infraproviderSvc "github.com/harness/gitness/app/services/infraprovider"
"github.com/harness/gitness/app/services/keywordsearch" "github.com/harness/gitness/app/services/keywordsearch"
svclabel "github.com/harness/gitness/app/services/label"
locker "github.com/harness/gitness/app/services/locker" locker "github.com/harness/gitness/app/services/locker"
"github.com/harness/gitness/app/services/metric" "github.com/harness/gitness/app/services/metric"
"github.com/harness/gitness/app/services/notification" "github.com/harness/gitness/app/services/notification"
@ -135,6 +136,7 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e
reposettings.WireSet, reposettings.WireSet,
pullreq.WireSet, pullreq.WireSet,
controllerwebhook.WireSet, controllerwebhook.WireSet,
svclabel.WireSet,
serviceaccount.WireSet, serviceaccount.WireSet,
user.WireSet, user.WireSet,
upload.WireSet, upload.WireSet,

View File

@ -69,6 +69,7 @@ import (
"github.com/harness/gitness/app/services/importer" "github.com/harness/gitness/app/services/importer"
infraprovider2 "github.com/harness/gitness/app/services/infraprovider" infraprovider2 "github.com/harness/gitness/app/services/infraprovider"
"github.com/harness/gitness/app/services/keywordsearch" "github.com/harness/gitness/app/services/keywordsearch"
"github.com/harness/gitness/app/services/label"
"github.com/harness/gitness/app/services/locker" "github.com/harness/gitness/app/services/locker"
"github.com/harness/gitness/app/services/metric" "github.com/harness/gitness/app/services/metric"
"github.com/harness/gitness/app/services/notification" "github.com/harness/gitness/app/services/notification"
@ -216,7 +217,11 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
lockerLocker := locker.ProvideLocker(mutexManager) lockerLocker := locker.ProvideLocker(mutexManager)
repoIdentifier := check.ProvideRepoIdentifierCheck() repoIdentifier := check.ProvideRepoIdentifierCheck()
repoCheck := repo.ProvideRepoCheck() repoCheck := repo.ProvideRepoCheck()
repoController := repo.ProvideController(config, transactor, provider, authorizer, repoStore, spaceStore, pipelineStore, principalStore, ruleStore, settingsService, principalInfoCache, protectionManager, gitInterface, repository, codeownersService, reporter, indexer, resourceLimiter, lockerLocker, auditService, mutexManager, repoIdentifier, repoCheck, publicaccessService) labelStore := database.ProvideLabelStore(db)
labelValueStore := database.ProvideLabelValueStore(db)
pullReqLabelAssignmentStore := database.ProvidePullReqLabelStore(db)
labelService := label.ProvideLabel(transactor, spaceStore, labelStore, labelValueStore, pullReqLabelAssignmentStore)
repoController := repo.ProvideController(config, transactor, provider, authorizer, repoStore, spaceStore, pipelineStore, principalStore, ruleStore, settingsService, principalInfoCache, protectionManager, gitInterface, repository, codeownersService, reporter, indexer, resourceLimiter, lockerLocker, auditService, mutexManager, repoIdentifier, repoCheck, publicaccessService, labelService)
reposettingsController := reposettings.ProvideController(authorizer, repoStore, settingsService, auditService) reposettingsController := reposettings.ProvideController(authorizer, repoStore, settingsService, auditService)
executionStore := database.ProvideExecutionStore(db) executionStore := database.ProvideExecutionStore(db)
checkStore := database.ProvideCheckStore(db, principalInfoCache) checkStore := database.ProvideCheckStore(db, principalInfoCache)
@ -247,7 +252,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
gitspaceConfigStore := database.ProvideGitspaceConfigStore(db) gitspaceConfigStore := database.ProvideGitspaceConfigStore(db)
gitspaceInstanceStore := database.ProvideGitspaceInstanceStore(db) gitspaceInstanceStore := database.ProvideGitspaceInstanceStore(db)
gitspaceService := gitspace.ProvideGitspace(transactor, gitspaceConfigStore, gitspaceInstanceStore, spaceStore) gitspaceService := gitspace.ProvideGitspace(transactor, gitspaceConfigStore, gitspaceInstanceStore, spaceStore)
spaceController := space.ProvideController(config, transactor, provider, streamer, spaceIdentifier, authorizer, spacePathStore, pipelineStore, secretStore, connectorStore, templateStore, spaceStore, repoStore, principalStore, repoController, membershipStore, repository, exporterRepository, resourceLimiter, publicaccessService, auditService, gitspaceService) spaceController := space.ProvideController(config, transactor, provider, streamer, spaceIdentifier, authorizer, spacePathStore, pipelineStore, secretStore, connectorStore, templateStore, spaceStore, repoStore, principalStore, repoController, membershipStore, repository, exporterRepository, resourceLimiter, publicaccessService, auditService, gitspaceService, gitspaceConfigStore, gitspaceInstanceStore, labelService)
pipelineController := pipeline.ProvideController(repoStore, triggerStore, authorizer, pipelineStore) pipelineController := pipeline.ProvideController(repoStore, triggerStore, authorizer, pipelineStore)
secretController := secret.ProvideController(encrypter, secretStore, authorizer, spaceStore) secretController := secret.ProvideController(encrypter, secretStore, authorizer, spaceStore)
triggerController := trigger.ProvideController(authorizer, triggerStore, pipelineStore, repoStore) triggerController := trigger.ProvideController(authorizer, triggerStore, pipelineStore, repoStore)
@ -280,7 +285,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
return nil, err return nil, err
} }
pullReq := importer.ProvidePullReqImporter(provider, gitInterface, principalStore, repoStore, pullReqStore, pullReqActivityStore, transactor) pullReq := importer.ProvidePullReqImporter(provider, gitInterface, principalStore, repoStore, pullReqStore, pullReqActivityStore, transactor)
pullreqController := pullreq2.ProvideController(transactor, provider, authorizer, pullReqStore, pullReqActivityStore, codeCommentView, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, principalInfoCache, pullReqFileViewStore, membershipStore, checkStore, gitInterface, eventsReporter, migrator, pullreqService, protectionManager, streamer, codeownersService, lockerLocker, pullReq) pullreqController := pullreq2.ProvideController(transactor, provider, authorizer, pullReqStore, pullReqActivityStore, codeCommentView, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, principalInfoCache, pullReqFileViewStore, membershipStore, checkStore, gitInterface, eventsReporter, migrator, pullreqService, protectionManager, streamer, codeownersService, lockerLocker, pullReq, labelService)
webhookConfig := server.ProvideWebhookConfig(config) webhookConfig := server.ProvideWebhookConfig(config)
webhookStore := database.ProvideWebhookStore(db) webhookStore := database.ProvideWebhookStore(db)
webhookExecutionStore := database.ProvideWebhookExecutionStore(db) webhookExecutionStore := database.ProvideWebhookExecutionStore(db)

77
types/enum/label.go Normal file
View File

@ -0,0 +1,77 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package enum
type LabelType string
func (LabelType) Enum() []interface{} { return toInterfaceSlice(LabelTypes) }
func (t LabelType) Sanitize() (LabelType, bool) { return Sanitize(t, GetAllLabelTypes) }
func GetAllLabelTypes() ([]LabelType, LabelType) { return LabelTypes, LabelTypeStatic }
const (
LabelTypeStatic LabelType = "static"
LabelTypeDynamic LabelType = "dynamic"
)
var LabelTypes = sortEnum([]LabelType{
LabelTypeStatic,
LabelTypeDynamic,
})
type LabelColor string
func (LabelColor) Enum() []interface{} { return toInterfaceSlice(LabelColors) }
func (t LabelColor) Sanitize() (LabelColor, bool) { return Sanitize(t, GetAllLabelColors) }
func GetAllLabelColors() ([]LabelColor, LabelColor) { return LabelColors, LabelColorBackground }
const (
LabelColorBackground LabelColor = "background"
LabelColorStroke LabelColor = "stroke"
LabelColorText LabelColor = "text"
LabelColorAccent LabelColor = "accent"
LabelColorRed LabelColor = "red"
LabelColorGreen LabelColor = "green"
LabelColorYellow LabelColor = "yellow"
LabelColorBlue LabelColor = "blue"
LabelColorPink LabelColor = "pink"
LabelColorPurple LabelColor = "purple"
LabelColorViolet LabelColor = "violet"
LabelColorIndigo LabelColor = "indigo"
LabelColorCyan LabelColor = "cyan"
LabelColorOrange LabelColor = "orange"
LabelColorBrown LabelColor = "brown"
LabelColorMint LabelColor = "mint"
LabelColorLime LabelColor = "lime"
)
var LabelColors = sortEnum([]LabelColor{
LabelColorBackground,
LabelColorStroke,
LabelColorText,
LabelColorAccent,
LabelColorRed,
LabelColorGreen,
LabelColorYellow,
LabelColorBlue,
LabelColorPink,
LabelColorPurple,
LabelColorViolet,
LabelColorIndigo,
LabelColorCyan,
LabelColorOrange,
LabelColorBrown,
LabelColorMint,
LabelColorLime,
})

292
types/label.go Normal file
View File

@ -0,0 +1,292 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"unicode"
"unicode/utf8"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/types/enum"
)
const (
maxLabelLength = 50
)
type Label struct {
ID int64 `json:"id"`
SpaceID *int64 `json:"space_id,omitempty"`
RepoID *int64 `json:"repo_id,omitempty"`
Scope int64 `json:"scope"`
Key string `json:"key"`
Description string `json:"description"`
Type enum.LabelType `json:"type"`
Color enum.LabelColor `json:"color"`
ValueCount int64 `json:"value_count"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
CreatedBy int64 `json:"created_by"`
UpdatedBy int64 `json:"updated_by"`
}
type LabelValue struct {
ID int64 `json:"id"`
LabelID int64 `json:"label_id"`
Value string `json:"value"`
Color enum.LabelColor `json:"color"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
CreatedBy int64 `json:"created_by"`
UpdatedBy int64 `json:"updated_by"`
}
type LabelWithValues struct {
Label `json:"label"`
Values []*LabelValue `json:"values"`
}
// Used to assign label to pullreq.
type PullReqLabel struct {
PullReqID int64 `json:"pullreq_id"`
LabelID int64 `json:"label_id"`
ValueID *int64 `json:"value_id,omitempty"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
CreatedBy int64 `json:"created_by"`
UpdatedBy int64 `json:"updated_by"`
}
type LabelInfo struct {
SpaceID *int64 `json:"-"`
RepoID *int64 `json:"-"`
Scope int64 `json:"scope"`
ID int64 `json:"id"`
Type enum.LabelType `json:"type"`
Key string `json:"key"`
Color enum.LabelColor `json:"color"`
Assigned *bool `json:"assigned,omitempty"`
}
type LabelValueInfo struct {
LabelID *int64 `json:"-"`
ID *int64 `json:"id,omitempty"`
Value *string `json:"value,omitempty"`
Color *string `json:"color,omitempty"`
}
type LabelAssignment struct {
LabelInfo
AssignedValue *LabelValueInfo `json:"assigned_value,omitempty"`
Values []*LabelValueInfo `json:"values,omitempty"` // query param ?assignable=true
}
type ScopeData struct {
// Scope = 0 is repo, scope >= 1 is a depth level of a space
Scope int64 `json:"scope"`
Space *Space `json:"space,omitempty"`
Repo *Repository `json:"repository,omitempty"`
}
// Used to fetch label and values from a repo and space hierarchy.
type ScopesLabels struct {
ScopeData []*ScopeData `json:"scope_data"`
LabelData []*LabelAssignment `json:"label_data"`
}
// LabelFilter stores label query parameters.
type AssignableLabelFilter struct {
ListQueryFilter
Assignable bool `json:"assignable,omitempty"`
}
type LabelFilter struct {
ListQueryFilter
Inherited bool `json:"inherited,omitempty"`
}
type DefineLabelInput struct {
Key string `json:"key"`
Type enum.LabelType `json:"type"`
Description string `json:"description"`
Color enum.LabelColor `json:"color"`
}
func (in DefineLabelInput) Validate() error {
if err := validateLabelText(in.Key, "key"); err != nil {
return err
}
if err := validateLabelType(in.Type); err != nil {
return err
}
err := validateLabelColor(in.Color)
if err != nil {
return err
}
return nil
}
type UpdateLabelInput struct {
Key *string `json:"key,omitempty"`
Type *enum.LabelType `json:"type,omitempty"`
Description *string `json:"description,omitempty"`
Color *enum.LabelColor `json:"color,omitempty"`
}
func (in UpdateLabelInput) Validate() error {
if in.Key != nil {
if err := validateLabelText(*in.Key, "key"); err != nil {
return err
}
}
if in.Type != nil {
if err := validateLabelType(*in.Type); err != nil {
return err
}
}
if in.Color != nil {
err := validateLabelColor(*in.Color)
if err != nil {
return err
}
}
return nil
}
type DefineValueInput struct {
Value string `json:"value"`
Color enum.LabelColor `json:"color"`
}
func (in DefineValueInput) Validate() error {
if err := validateLabelText(in.Value, "value"); err != nil {
return err
}
if err := validateLabelColor(in.Color); err != nil {
return err
}
return nil
}
type UpdateValueInput struct {
Value *string `json:"value"`
Color *enum.LabelColor `json:"color"`
}
func (in UpdateValueInput) Validate() error {
if in.Value != nil {
if err := validateLabelText(*in.Value, "value"); err != nil {
return err
}
}
if in.Color != nil {
if err := validateLabelColor(*in.Color); err != nil {
return err
}
}
return nil
}
type PullReqCreateInput struct {
LabelID int64 `json:"label_id"`
ValueID *int64 `json:"value_id"`
Value string `json:"value"`
}
type PullReqUpdateInput struct {
LabelValueID *int64 `json:"label_value_id,omitempty"`
}
func (in PullReqCreateInput) Validate() error {
if (in.ValueID != nil && *in.ValueID > 0) && in.Value != "" {
return errors.InvalidArgument("cannot accept both value id and value")
}
return nil
}
type SaveLabelInput struct {
ID int64 `json:"id"`
DefineLabelInput
}
type SaveLabelValueInput struct {
ID int64 `json:"id"`
DefineValueInput
}
type SaveInput struct {
Label SaveLabelInput `json:"label"`
Values []*SaveLabelValueInput `json:"values,omitempty"`
}
func (in *SaveInput) Validate() error {
if err := in.Label.Validate(); err != nil {
return err
}
for _, value := range in.Values {
if err := value.Validate(); err != nil {
return err
}
}
return nil
}
var labelTypes, _ = enum.GetAllLabelTypes()
func validateLabelText(text string, typ string) error {
if len(text) == 0 {
return errors.InvalidArgument("%s must be a non-empty string", typ)
}
if utf8.RuneCountInString(text) > maxLabelLength {
return errors.InvalidArgument("%s can have at most %d characters", typ, maxLabelLength)
}
for _, ch := range text {
if unicode.IsControl(ch) {
return errors.InvalidArgument("%s cannot contain control characters", typ)
}
}
return nil
}
func validateLabelType(typ enum.LabelType) error {
if _, ok := typ.Sanitize(); !ok {
return errors.InvalidArgument("label type must be in %v", labelTypes)
}
return nil
}
var colorTypes, _ = enum.GetAllLabelColors()
func validateLabelColor(color enum.LabelColor) error {
_, ok := color.Sanitize()
if !ok {
return errors.InvalidArgument("color type must be in %v", colorTypes)
}
return nil
}