diff --git a/common/const_var.go b/common/const_var.go index cdd6cae..30378ce 100644 --- a/common/const_var.go +++ b/common/const_var.go @@ -184,6 +184,7 @@ const ( const ( Member = "member" Tag = "tag" + TagValue = "value" StartRange = "startRange" EndRange = "endRange" ) diff --git a/taggingapi/router.go b/taggingapi/router.go index ae5b66b..605e013 100644 --- a/taggingapi/router.go +++ b/taggingapi/router.go @@ -35,6 +35,7 @@ func routeTaggingServiceApis(r *mux.Router, s *xhttp.WebconfigServer) { taggingPath.HandleFunc("/{tag}/members", tag.GetTagMembersHandler).Methods("GET").Name("Get-tag-members") taggingPath.HandleFunc("/members/{member}", tag.GetTagsByMemberHandler).Methods("GET").Name("Get-tags-by-member") + taggingPath.HandleFunc("/members/{member}/values", tag.GetTagsWithValuesByMemberHandler).Methods("GET").Name("Get-tags-with-values-by-member") paths = append(paths, taggingPath) diff --git a/taggingapi/tag/tag_handler.go b/taggingapi/tag/tag_handler.go index 372c107..0511a77 100644 --- a/taggingapi/tag/tag_handler.go +++ b/taggingapi/tag/tag_handler.go @@ -27,7 +27,34 @@ func GetTagsByMemberHandler(w http.ResponseWriter, r *http.Request) { xhttp.WriteXconfResponse(w, http.StatusBadRequest, []byte(fmt.Sprintf(NotSpecifiedErrorMsg, common.Member))) return } + tags, err := GetTagsByMember(member) + if err != nil { + xhttp.WriteXconfErrorResponse(w, err) + return + } + + respBytes, err := json.Marshal(tags) + if err != nil { + xhttp.WriteXconfErrorResponse(w, err) + return + } + xhttp.WriteXconfResponse(w, http.StatusOK, respBytes) +} + +func GetTagsWithValuesByMemberHandler(w http.ResponseWriter, r *http.Request) { + member, found := mux.Vars(r)[common.Member] + if !found { + xhttp.WriteXconfResponse(w, http.StatusBadRequest, []byte(fmt.Sprintf(NotSpecifiedErrorMsg, common.Member))) + return + } + + tags, err := GetTagsWithValuesByMember(member) + if err != nil { + xhttp.WriteXconfErrorResponse(w, err) + return + } + respBytes, err := json.Marshal(tags) if err != nil { xhttp.WriteXconfErrorResponse(w, err) diff --git a/taggingapi/tag/tag_handler_test.go b/taggingapi/tag/tag_handler_test.go index 5da295a..9ad8e47 100644 --- a/taggingapi/tag/tag_handler_test.go +++ b/taggingapi/tag/tag_handler_test.go @@ -115,6 +115,25 @@ func TestAddMembersToTagHandler_ExceedsBatchSize(t *testing.T) { assert.Contains(t, recorder.Body.String(), "exceeds maximum") } +func TestGetTagValueFromRequest_QueryParameterPresent(t *testing.T) { + req := httptest.NewRequest("PUT", "/tags/test-tag/members?value=business", nil) + + assert.Equal(t, "business", getTagValueFromRequest(req)) +} + +func TestGetTagValueFromRequest_EmptyQueryParameter(t *testing.T) { + req := httptest.NewRequest("PUT", "/tags/test-tag/members?value=", nil) + + assert.Equal(t, "", getTagValueFromRequest(req)) +} + +func TestGetTagValueFromRequest_DefaultsToEmpty(t *testing.T) { + req := httptest.NewRequest("PUT", "/tags/test-tag/members", nil) + req = mux.SetURLVars(req, map[string]string{common.Tag: "test-tag"}) + + assert.Equal(t, "", getTagValueFromRequest(req)) +} + func TestRemoveMembersFromTagHandler_MissingTag(t *testing.T) { setupTestEnvironment() req := httptest.NewRequest("DELETE", "/tags//members", nil) diff --git a/taggingapi/tag/tag_member_handler.go b/taggingapi/tag/tag_member_handler.go index 55b3cd2..366ec02 100644 --- a/taggingapi/tag/tag_member_handler.go +++ b/taggingapi/tag/tag_member_handler.go @@ -111,6 +111,8 @@ func AddMembersToTagHandler(w http.ResponseWriter, r *http.Request) { return } + tagValue := getTagValueFromRequest(r) + xw, ok := w.(*xwhttp.XResponseWriter) if !ok { xhttp.WriteXconfResponse(w, http.StatusInternalServerError, []byte(ResponseWriterCastErrorMsg)) @@ -134,9 +136,7 @@ func AddMembersToTagHandler(w http.ResponseWriter, r *http.Request) { return } - log.Debugf("AddMembers request: tag=%s, memberCount=%d", tagId, len(members)) - - stored, err := AddMembersWithXdas(tagId, members) + stored, err := AddMembersWithXdas(tagId, members, tagValue) if err != nil { xhttp.WriteXconfErrorResponse(w, err) return @@ -155,6 +155,16 @@ func AddMembersToTagHandler(w http.ResponseWriter, r *http.Request) { xhttp.WriteXconfResponse(w, http.StatusAccepted, respBytes) } +func getTagValueFromRequest(r *http.Request) string { + if values, found := r.URL.Query()[common.TagValue]; found { + if len(values) > 0 { + return values[0] + } + } + + return "" +} + // RemoveMembersFromTagHandler - Updated with bucketed implementation func RemoveMembersFromTagHandler(w http.ResponseWriter, r *http.Request) { id, found := mux.Vars(r)[common.Tag] @@ -186,8 +196,6 @@ func RemoveMembersFromTagHandler(w http.ResponseWriter, r *http.Request) { return } - log.Debugf("RemoveMembers request: tag=%s, memberCount=%d", id, len(members)) - removed, err := RemoveMembersWithXdas(id, members) if err != nil { xhttp.WriteXconfErrorResponse(w, err) @@ -292,6 +300,12 @@ func GetTagByIdHandler(w http.ResponseWriter, r *http.Request) { // DeleteTagHandler deletes a tag and all its members from V2 storage asynchronously func DeleteTagHandler(w http.ResponseWriter, r *http.Request) { + xw, ok := w.(*xwhttp.XResponseWriter) + if !ok { + xhttp.WriteXconfResponse(w, http.StatusInternalServerError, []byte(ResponseWriterCastErrorMsg)) + return + } + id, found := mux.Vars(r)[common.Tag] if !found { xhttp.WriteXconfResponse(w, http.StatusBadRequest, []byte(fmt.Sprintf(NotSpecifiedErrorMsg, common.Tag))) @@ -309,9 +323,20 @@ func DeleteTagHandler(w http.ResponseWriter, r *http.Request) { return } + auditId := xw.AuditId() go func(tagId string) { if err := DeleteTag(tagId); err != nil { - log.Errorf("Background deletion failed for tag '%s': %v", tagId, err) + log.WithFields(log.Fields{ + "audit_id": auditId, + "endpoint": "DeleteTag", + "tag": tagId, + }).Errorf("background deletion failed: %v", err) + } else { + log.WithFields(log.Fields{ + "audit_id": auditId, + "endpoint": "DeleteTag", + "tag": tagId, + }).Info("tagging background deletion completed") } }(id) diff --git a/taggingapi/tag/tag_member_service.go b/taggingapi/tag/tag_member_service.go index bd6884a..d2c3160 100644 --- a/taggingapi/tag/tag_member_service.go +++ b/taggingapi/tag/tag_member_service.go @@ -553,7 +553,7 @@ func fetchMembersFromBucketsConcurrent(tagId string, bucketIds []int, totalLimit // AddMembersWithXdas adds members to both XDAS and Cassandra (XDAS-first approach) // Returns the count of members actually stored to Cassandra. -func AddMembersWithXdas(tagId string, members []string) (int, error) { +func AddMembersWithXdas(tagId string, members []string, tagValue string) (int, error) { startTime := time.Now() if len(members) == 0 { @@ -564,7 +564,7 @@ func AddMembersWithXdas(tagId string, members []string) (int, error) { return 0, fmt.Errorf("batch size %d exceeds maximum %d", len(members), MaxBatchSizeV2) } - savedToXdasMembers, err := addMembersToXdas(tagId, members) + savedToXdasMembers, err := addMembersToXdas(tagId, members, tagValue) if err != nil { return 0, fmt.Errorf("XDAS operation failed: %w", err) } @@ -630,7 +630,7 @@ func RemoveMemberWithXdas(tagId string, member string) error { } // addMembersToXdas adds members to Xdas using concurrent workers (similar to V1 pattern) -func addMembersToXdas(tagId string, members []string) ([]string, error) { +func addMembersToXdas(tagId string, members []string, tagValue string) ([]string, error) { tagId = SetTagPrefix(tagId) membersChannel := make(chan string, len(members)) @@ -653,7 +653,7 @@ func addMembersToXdas(tagId string, members []string) ([]string, error) { } for i := 0; i < numOfWorkers; i++ { wg.Add(1) - go storeTagMembersInXdas(tagId, membersChannel, savedMembersChannel, wg) + go storeTagMembersInXdas(tagId, membersChannel, savedMembersChannel, wg, tagValue) } go func() { diff --git a/taggingapi/tag/tag_service.go b/taggingapi/tag/tag_service.go index b636fba..273596f 100644 --- a/taggingapi/tag/tag_service.go +++ b/taggingapi/tag/tag_service.go @@ -39,6 +39,17 @@ func GetTagsByMember(member string) ([]string, error) { return filterTagEntriesByPrefix(tagsMap.Keys()), err } +func GetTagsWithValuesByMember(member string) (map[string]string, error) { + member = ToNormalizedEcm(member) + tagsAsHashes, err := GetGroupServiceConnector().GetGroupsMemberBelongsTo(member) + if err != nil { + log.Errorf("xdas error getting members by %s group: %s", member, err.Error()) + return map[string]string{}, err + } + tagsMap := util.StringMap(tagsAsHashes.GetFields()) + return filterTagEntriesWithValuesByPrefix(tagsMap), err +} + func filterTagEntriesByPrefix(ftEntries []string) []string { tags := []string{} for _, ftEntry := range ftEntries { @@ -49,10 +60,20 @@ func filterTagEntriesByPrefix(ftEntries []string) []string { return tags } -func storeTagMembersInXdas(id string, members <-chan string, savedMembers chan<- string, wg *sync.WaitGroup) { +func filterTagEntriesWithValuesByPrefix(entries util.StringMap) map[string]string { + result := map[string]string{} + for key, value := range entries { + if strings.HasPrefix(key, Prefix) { + result[RemovePrefixFromTag(key)] = value + } + } + return result +} + +func storeTagMembersInXdas(id string, members <-chan string, savedMembers chan<- string, wg *sync.WaitGroup, tagValue string) { defer wg.Done() xdasMembers := proto.XdasHashes{ - Fields: map[string]string{id: ""}, + Fields: map[string]string{id: tagValue}, } successCount := 0