diff --git a/graph2/resolvers/log.go b/graph2/resolvers/log.go index 5abbd12..2e73ce7 100644 --- a/graph2/resolvers/log.go +++ b/graph2/resolvers/log.go @@ -58,6 +58,10 @@ func (r *mutationResolver) ImportLog(ctx context.Context, input graphcore.LogImp return r.s.Logs.Import(ctx, input.Importer, date, tz, input.ChannelName, input.Data) } +func (r *mutationResolver) SplitLog(ctx context.Context, input graphcore.LogSplitInput) (*models.Log, error) { + return r.s.Logs.SplitLog(ctx, input.LogID, input.StartPostID) +} + 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 5e9a03b..ef79997 100644 --- a/graph2/schema/root.gql +++ b/graph2/schema/root.gql @@ -117,6 +117,9 @@ type Mutation { # Import a log importLog(input: LogImportInput!): [Log!]! + # Split a log + splitLog(input: LogSplitInput!): Log! + # Edit a log editLog(input: LogEditInput!): Log! diff --git a/graph2/schema/types/Log.gql b/graph2/schema/types/Log.gql index 0d5a89d..c476d2a 100644 --- a/graph2/schema/types/Log.gql +++ b/graph2/schema/types/Log.gql @@ -122,6 +122,17 @@ input LogImportInput { data: String! } +""" +Input for splitLog mutation. +""" +input LogSplitInput { + "Log ID" + logId: String! + + "After Post ID" + startPostId: String! +} + 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 492959f..2c3fbf9 100644 --- a/services/logs.go +++ b/services/logs.go @@ -203,6 +203,89 @@ func (s *LogService) Update(ctx context.Context, id string, update models.LogUpd return log, nil } +func (s *LogService) SplitLog(ctx context.Context, logId string, startPostId string) (*models.Log, error) { + // Find log + l, err := s.logs.Find(ctx, logId) + if err != nil { + return nil, err + } + if err := auth.CheckPermission(ctx, "add", l); err != nil { + return nil, err + } + + // Find posts + posts, err := s.ListPosts(ctx, &models.PostFilter{LogID: &l.ShortID}) + if err != nil { + return nil, err + } + if len(posts) == 0 { + return nil, errors.New("cannot split empty log") + } + + // Cut the posts slice. + firstPost := posts[0] + cutPosts := posts[len(posts):] + for i, post := range posts { + if post.ID == startPostId { + cutPosts = posts[i:] + firstPost = post + break + } + } + if len(cutPosts) == 0 { + return nil, errors.New("post not found") + } + if len(cutPosts) == len(posts) || firstPost.Time.Equal(l.Date) { + return nil, errors.New("cannot move posts") + } + + // Create a new log + newLog := &models.Log{ + Date: firstPost.Time, + ChannelName: l.ChannelName, + EventName: l.EventName, + } + newLog, err = s.logs.Insert(ctx, *newLog) + if err != nil { + return nil, err + } + + // Put the cut posts in the new log + newPosts := make([]*models.Post, 0, len(cutPosts)) + for _, post := range cutPosts { + postCopy := *post + postCopy.ID = "" + postCopy.LogID = newLog.ShortID + + newPost, err := s.posts.Insert(ctx, postCopy) + if err != nil { + _ = s.logs.Delete(ctx, *newLog) + return nil, err + } + + newPosts = append(newPosts, newPost) + } + + // Remove the posts from the old log (this can't error because that'll make a mess) + for _, post := range cutPosts { + err := s.posts.Delete(ctx, *post) + if err != nil { + log.Printf("Failed to delete post %s: %s", post.ID, err) + } + } + + // Submit the changes + s.changeService.Submit(ctx, models.ChangeModelPost, "remove", true, changekeys.Many(l, cutPosts), cutPosts) + s.changeService.Submit(ctx, models.ChangeModelLog, "add", true, changekeys.Listed(newLog), newLog) + 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) + + return newLog, 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")