public ActionResult <Group> CreateOrUpdateGroup([FromBody] Group.Input.CreateGroupBody body, [FromQuery] bool merge = false) { // Validate if the parent exists if a parent id is set. var parentID = body.ParentID ?? 0; if (parentID != 0) { var parent = RepoFactory.AnimeGroup.GetByID(parentID); if (parent == null) { return(BadRequest("Invalid parent group id supplied.")); } } // Try to find the group to merge with if we provided an id. bool isNew = false; SVR_AnimeGroup group = null; if (body.ID.HasValue && body.ID.Value != 0) { // Make sure merging is requested. // A merge request without the 'merge' query parameter set is an // error. if (!merge) { return(BadRequest("A group id have been supplied. Set the merge query parameter to continue.")); } group = RepoFactory.AnimeGroup.GetByID(body.ID.Value); // Make sure the group actually exists. if (group == null) { return(BadRequest("No Group entry for the given id.")); } } // Try to find an existing group exists for the given name or create a new one. else { // Look for an existing group if force is not set. group = RepoFactory.AnimeGroup.GetByParentID(parentID) .FirstOrDefault(grp => string.Equals(grp.GroupName, body.Name, StringComparison.InvariantCultureIgnoreCase)); // If no group was found (either because we forced it or because a group by the same name was not found) then if (group == null) { // It's safe to use the group name. isNew = true; group = new() { Description = string.Empty, IsManuallyNamed = 0, DateTimeCreated = DateTime.Now, DateTimeUpdated = DateTime.Now, MissingEpisodeCount = 0, MissingEpisodeCountGroups = 0, OverrideDescription = 0, }; } // Else we need to provide the query parameter to either merge // or forcefully create a new group. else if (!merge) { return(BadRequest("A group with the given name already exists. Set the merge or force query parameter to continue.")); } } // Get the series and validate the series ids. var seriesList = body.SeriesIDs?.Select(id => RepoFactory.AnimeSeries.GetByID(id)).Where(s => s != null).ToList() ?? new(); if (seriesList.Count != (body.SeriesIDs?.Length ?? 0)) { return(BadRequest("One or more series ids are invalid.")); } // Trying to merge 0 series with an existing group is an error. // Trying to create an empty group is also an error. if (seriesList.Count == 0) { return(BadRequest("No series ids have been spesified.")); } // Nor spesifying a group name is an error. body.Name = body.Name?.Trim() ?? ""; if (string.IsNullOrEmpty(body.Name)) { return(BadRequest("A name must be present and contain at least one character.")); } group.AnimeGroupParentID = parentID != 0 ? parentID : null; group.GroupName = body.Name; group.SortName = string.IsNullOrEmpty(body.SortName?.Trim()) ? body.Name : body.SortName; group.Description = body.Description ?? null; group.IsManuallyNamed = body.HasCustomName ? 1 : 0; group.OverrideDescription = 0; // Create a new or update an existing group entry. RepoFactory.AnimeGroup.Save(group, true, false, false); // Iterate over the series list to calculate the groups to update // and to assign the series to the group. var oldGroups = new Dictionary <int, int>(); foreach (var series in seriesList) { // Skip the series if it's already in the group. if (series.AnimeGroupID == group.AnimeGroupID) { continue; } // Count the number of series in each group. var oldGroupID = series.AnimeGroupID; if (oldGroups.TryGetValue(oldGroupID, out var count)) { oldGroups[oldGroupID] = count + 1; } else { oldGroups[oldGroupID] = 1; } // Assign the series to the new group and update the series // entry. series.AnimeGroupID = group.AnimeGroupID; series.UpdateStats(true, true, false); } // We don't need to update the group twice, and we don't need to // check if the group we're creating/updating needs to be // removed. if (oldGroups.Keys.Contains(group.AnimeGroupID)) { oldGroups.Remove(group.AnimeGroupID); } // Remove or update groups stats for the old groups. foreach (var(oldGroupID, seriesCount) in oldGroups) { var oldGroup = RepoFactory.AnimeGroup.GetByID(oldGroupID)?.TopLevelAnimeGroup; // The old group may have already been removed, so silently skip it. if (oldGroup == null) { continue; } // Delete the sub-groups if the old group doesn't contain any other series. if (oldGroup.GetAllSeries().Count <= seriesCount) { RepoFactory.AnimeGroup.Delete(oldGroup.GetAllChildGroups()); RepoFactory.AnimeGroup.Delete(oldGroup); } else { oldGroup.UpdateStatsFromTopLevel(false, true, true); } } // Update the group stats for the new group. group.UpdateStatsFromTopLevel(false, true, true); // Return the info for the newly created or updated group. var dto = new Group(HttpContext, group); return(isNew ? Created($"./{group.AnimeGroupID}", dto) : Ok(dto)); }