diff --git a/graph2/resolvers/log.go b/graph2/resolvers/log.go index 2e73ce7..7c996a2 100644 --- a/graph2/resolvers/log.go +++ b/graph2/resolvers/log.go @@ -62,6 +62,12 @@ func (r *mutationResolver) SplitLog(ctx context.Context, input graphcore.LogSpli return r.s.Logs.SplitLog(ctx, input.LogID, input.StartPostID) } +func (r *mutationResolver) MergeLog(ctx context.Context, input graphcore.LogMergeInput) (*models.Log, error) { + removeAfter := input.RemoveAfter != nil && *input.RemoveAfter + + return r.s.Logs.MergeLogs(ctx, input.TargetLogID, input.SourceLogID, removeAfter) +} + func (r *mutationResolver) EditLog(ctx context.Context, input graphcore.LogEditInput) (*models.Log, error) { update := models.LogUpdate{ Open: input.Open, diff --git a/graph2/schema/root.gql b/graph2/schema/root.gql index ef79997..97146d6 100644 --- a/graph2/schema/root.gql +++ b/graph2/schema/root.gql @@ -120,6 +120,9 @@ type Mutation { # Split a log splitLog(input: LogSplitInput!): Log! + # Merge a log + mergeLog(input: LogMergeInput!): Log! + # Edit a log editLog(input: LogEditInput!): Log! diff --git a/graph2/schema/types/Log.gql b/graph2/schema/types/Log.gql index c476d2a..563ad9e 100644 --- a/graph2/schema/types/Log.gql +++ b/graph2/schema/types/Log.gql @@ -129,10 +129,24 @@ input LogSplitInput { "Log ID" logId: String! - "After Post ID" + "Split out all posts from and including the post with this ID" startPostId: String! } +""" +Input for mergeLog mutation. +""" +input LogMergeInput { + "Target log ID" + targetLogId: String! + + "Source log ID" + sourceLogId: String! + + "Remove the source log after merging" + removeAfter: Boolean +} + enum LogImporter { """ mIRC-like: This importer parses logs that copied from mIRC posts without spaces. diff --git a/services/logs.go b/services/logs.go index 2c3fbf9..485517f 100644 --- a/services/logs.go +++ b/services/logs.go @@ -280,12 +280,78 @@ func (s *LogService) SplitLog(ctx context.Context, logId string, startPostId str s.changeService.Submit(ctx, models.ChangeModelPost, "add", true, changekeys.Many(newLog, newPosts), newPosts) // Refresh character lists. - _ = s.refreshLogCharacters(ctx, *l, nil) - _ = s.refreshLogCharacters(ctx, *newLog, nil) + _, _ = s.refreshLogCharacters(ctx, *l, nil) + newLog2, err := s.refreshLogCharacters(ctx, *newLog, nil) + if err == nil { + newLog = newLog2 + } return newLog, nil } +func (s *LogService) MergeLogs(ctx context.Context, targetID string, sourceID string, removeAfter bool) (*models.Log, error) { + // Check permissions + if err := auth.CheckPermission(ctx, "edit", &models.Log{}); err != nil { + return nil, err + } + if removeAfter { + if err := auth.CheckPermission(ctx, "remove", &models.Log{}); err != nil { + return nil, err + } + } + + // Merge log posts into log. + source, err := s.logs.Find(ctx, sourceID) + if err != nil { + return nil, errors.New("could not find source log: " + err.Error()) + } + target, err := s.logs.Find(ctx, targetID) + if err != nil { + return nil, errors.New("could not find target log: " + err.Error()) + } + + // Get the source posts. + posts, err := s.posts.List(ctx, models.PostFilter{LogID: &source.ShortID}) + if err != nil { + return nil, errors.New("could not fetch source posts: " + err.Error()) + } + + // Associate the posts with the target logs + for _, post := range posts { + post.ID = "" + post.LogID = target.ShortID + } + + // Insert them + posts, err = s.posts.InsertMany(ctx, posts...) + if err != nil { + return nil, errors.New("could not insert posts into target: " + err.Error()) + } + + // Remove other log + if removeAfter { + err = s.logs.Delete(ctx, *source) + if err != nil { + return nil, errors.New("posts have been inserted, but could not remove source: " + err.Error()) + } + + s.changeService.Submit(ctx, models.ChangeModelLog, "remove", true, changekeys.Listed(source), source) + } + + // Refresh characters + target2, err := s.refreshLogCharacters(ctx, *target, nil) + if err != nil { + log.Printf("Failed to update characters in log %s: %s", target.ID, err) + } else { + target = target2 + } + + // Submit changes after the target + s.changeService.Submit(ctx, models.ChangeModelPost, "add", true, changekeys.Many(target, posts), target, posts) + + return target, nil +} + func (s *LogService) AddPost(ctx context.Context, logId string, time time.Time, kind, nick, text string) (*models.Post, error) { if kind == "" || nick == "" || time.IsZero() { return nil, errors.New("kind, nick and time must be non-empty") @@ -313,9 +379,11 @@ func (s *LogService) AddPost(ctx context.Context, logId string, time time.Time, return nil, err } - err = s.refreshLogCharacters(ctx, *l, nil) + l2, err := s.refreshLogCharacters(ctx, *l, nil) if err != nil { log.Printf("Failed to update characters in log %s: %s", l.ID, err) + } else { + l = l2 } s.changeService.Submit(ctx, models.ChangeModelPost, "add", true, changekeys.Many(l, post), post) @@ -348,7 +416,7 @@ func (s *LogService) EditPost(ctx context.Context, id string, update models.Post return } - err = s.refreshLogCharacters(ctx, *l, nil) + _, err = s.refreshLogCharacters(ctx, *l, nil) if err != nil { log.Printf("Failed to update characters in log %s: %s", l.ID, err) } @@ -415,7 +483,7 @@ func (s *LogService) DeletePost(ctx context.Context, id string) (*models.Post, e return } - err = s.refreshLogCharacters(ctx, *l, nil) + _, err = s.refreshLogCharacters(ctx, *l, nil) if err != nil { log.Printf("Failed to update characters in log %s: %s", l.ID, err) } @@ -575,7 +643,8 @@ func (s *LogService) RefreshAllLogCharacters(ctx context.Context) error { l := logs[i] eg.Go(func() error { - return s.refreshLogCharacters(ctx, *l, characterMap) + _, err := s.refreshLogCharacters(ctx, *l, characterMap) + return err }) } err = eg.Wait() @@ -588,14 +657,14 @@ func (s *LogService) RefreshAllLogCharacters(ctx context.Context) error { return nil } -func (s *LogService) RefreshLogCharacters(ctx context.Context, log models.Log) error { +func (s *LogService) RefreshLogCharacters(ctx context.Context, log models.Log) (*models.Log, error) { return s.refreshLogCharacters(ctx, log, nil) } -func (s *LogService) refreshLogCharacters(ctx context.Context, log models.Log, characterMap map[string]*models.Character) error { +func (s *LogService) refreshLogCharacters(ctx context.Context, log models.Log, characterMap map[string]*models.Character) (*models.Log, error) { posts, err := s.ListPosts(ctx, &models.PostFilter{LogID: &log.ShortID}) if err != nil { - return nil + return &log, nil } counts := make(map[string]int) @@ -639,7 +708,7 @@ func (s *LogService) refreshLogCharacters(ctx context.Context, log models.Log, c if characterMap == nil { characters, err := s.characterService.List(ctx, models.CharacterFilter{Nicks: nicks}) if err != nil { - return err + return nil, err } characterMap = s.makeCharacterMap(characters) @@ -659,8 +728,7 @@ func (s *LogService) refreshLogCharacters(ctx context.Context, log models.Log, c log.CharacterIDs = append(log.CharacterIDs, character.ID) } - _, err = s.logs.Update(ctx, log, models.LogUpdate{CharacterIDs: log.CharacterIDs}) - return err + return s.logs.Update(ctx, log, models.LogUpdate{CharacterIDs: log.CharacterIDs}) } func (s *LogService) makeCharacterMap(characters []*models.Character) map[string]*models.Character {