package utils import ( "context" "github.com/alcionai/clues" "github.com/spf13/cobra" "github.com/alcionai/corso/src/cli/flags" "github.com/alcionai/corso/src/pkg/selectors" ) type GroupsOpts struct { Groups []string Channels []string Messages []string Conversations []string Posts []string MessageCreatedAfter string MessageCreatedBefore string MessageLastReplyAfter string MessageLastReplyBefore string SiteID []string WebURL []string Library string FileName []string // for libraries, to duplicate onedrive interface FolderPath []string // for libraries, to duplicate onedrive interface FileCreatedAfter string FileCreatedBefore string FileModifiedAfter string FileModifiedBefore string Lists []string PageFolder []string Page []string RestoreCfg RestoreCfgOpts ExportCfg ExportCfgOpts Populated flags.PopulatedFlags } func (g GroupsOpts) GetFileTimeField(flag string) string { switch flag { case flags.FileCreatedAfterFN: return g.FileCreatedAfter case flags.FileCreatedBeforeFN: return g.FileCreatedBefore case flags.FileModifiedAfterFN: return g.FileModifiedAfter case flags.FileModifiedBeforeFN: return g.FileModifiedBefore default: return "" } } func GroupsAllowedCategories() map[string]struct{} { return map[string]struct{}{ flags.DataLibraries: {}, flags.DataMessages: {}, flags.DataConversations: {}, } } func AddGroupsCategories(sel *selectors.GroupsBackup, cats []string) *selectors.GroupsBackup { if len(cats) == 0 { sel.Include(sel.AllData()) } for _, d := range cats { switch d { case flags.DataLibraries: sel.Include(sel.LibraryFolders(selectors.Any())) case flags.DataMessages: sel.Include(sel.ChannelMessages(selectors.Any(), selectors.Any())) case flags.DataConversations: sel.Include(sel.ConversationPosts(selectors.Any(), selectors.Any())) } } return sel } func MakeGroupsOpts(cmd *cobra.Command) GroupsOpts { return GroupsOpts{ Groups: flags.GroupFV, Channels: flags.ChannelFV, Messages: flags.MessageFV, Conversations: flags.ConversationFV, Posts: flags.PostFV, WebURL: flags.WebURLFV, SiteID: flags.SiteIDFV, Library: flags.LibraryFV, FileName: flags.FileNameFV, FolderPath: flags.FolderPathFV, FileCreatedAfter: flags.FileCreatedAfterFV, FileCreatedBefore: flags.FileCreatedBeforeFV, FileModifiedAfter: flags.FileModifiedAfterFV, FileModifiedBefore: flags.FileModifiedBeforeFV, MessageCreatedAfter: flags.MessageCreatedAfterFV, MessageCreatedBefore: flags.MessageCreatedBeforeFV, MessageLastReplyAfter: flags.MessageLastReplyAfterFV, MessageLastReplyBefore: flags.MessageLastReplyBeforeFV, Lists: flags.ListFV, Page: flags.PageFV, PageFolder: flags.PageFolderFV, RestoreCfg: makeRestoreCfgOpts(cmd), ExportCfg: makeExportCfgOpts(cmd), // populated contains the list of flags that appear in the // command, according to pflags. Use this to differentiate // between an "empty" and a "missing" value. Populated: flags.GetPopulatedFlags(cmd), } } // ValidateGroupsRestoreFlags checks common flags for correctness and interdependencies func ValidateGroupsRestoreFlags(backupID string, opts GroupsOpts, isRestore bool) error { if len(backupID) == 0 { return clues.New("a backup ID is required") } // The user has to explicitly specify which resource to restore. In // this case, since we can only restore sites, the user is supposed // to specify which site to restore. if isRestore { if len(opts.WebURL)+len(opts.SiteID) == 0 { return clues.New("web URL of the site to restore is required. Use --" + flags.SiteFN + " to provide one.") } else if len(opts.WebURL)+len(opts.SiteID) > 1 { return clues.New("only a single site can be selected for restore") } } if _, ok := opts.Populated[flags.MessageCreatedAfterFN]; ok && !IsValidTimeFormat(opts.MessageCreatedAfter) { return clues.New("invalid time format for " + flags.MessageCreatedAfterFN) } if _, ok := opts.Populated[flags.MessageCreatedBeforeFN]; ok && !IsValidTimeFormat(opts.MessageCreatedBefore) { return clues.New("invalid time format for " + flags.MessageCreatedBeforeFN) } if _, ok := opts.Populated[flags.MessageLastReplyAfterFN]; ok && !IsValidTimeFormat(opts.MessageLastReplyAfter) { return clues.New("invalid time format for " + flags.MessageLastReplyAfterFN) } if _, ok := opts.Populated[flags.MessageLastReplyBeforeFN]; ok && !IsValidTimeFormat(opts.MessageLastReplyBefore) { return clues.New("invalid time format for " + flags.MessageLastReplyBeforeFN) } return validateCommonTimeFlags(opts) } // AddGroupsFilter adds the scope of the provided values to the selector's // filter set func AddGroupsFilter( sel *selectors.GroupsRestore, v string, f func(string) []selectors.GroupsScope, ) { if len(v) == 0 { return } sel.Filter(f(v)) } // IncludeGroupsRestoreDataSelectors builds the common data-selector // inclusions for Group commands. func IncludeGroupsRestoreDataSelectors(ctx context.Context, opts GroupsOpts) *selectors.GroupsRestore { var ( groups = opts.Groups folderPaths, fileNames = len(opts.FolderPath), len(opts.FileName) lists = len(opts.Lists) pageFolders, pageItems = len(opts.PageFolder), len(opts.Page) chans, chanMsgs = len(opts.Channels), len(opts.Messages) convs, convPosts = len(opts.Conversations), len(opts.Posts) ) if len(opts.Groups) == 0 { groups = selectors.Any() } sel := selectors.NewGroupsRestore(groups) if folderPaths+fileNames+ lists+ pageFolders+pageItems+ chans+chanMsgs+ convs+convPosts == 0 { sel.Include(sel.AllData()) return sel } // sharepoint site selectors if folderPaths+fileNames > 0 { if fileNames == 0 { opts.FileName = selectors.Any() } opts.FolderPath = trimFolderSlash(opts.FolderPath) containsFolders, prefixFolders := splitFoldersIntoContainsAndPrefix(opts.FolderPath) if len(containsFolders) > 0 { sel.Include(sel.LibraryItems(containsFolders, opts.FileName)) } if len(prefixFolders) > 0 { sel.Include(sel.LibraryItems(prefixFolders, opts.FileName, selectors.PrefixMatch())) } } if lists > 0 { opts.Lists = trimFolderSlash(opts.Lists) sel.Include(sel.ListItems(opts.Lists, opts.Lists, selectors.StrictEqualMatch())) sel.Configure(selectors.Config{OnlyMatchItemNames: true}) } if pageFolders+pageItems > 0 { if pageItems == 0 { opts.Page = selectors.Any() } opts.PageFolder = trimFolderSlash(opts.PageFolder) containsFolders, prefixFolders := splitFoldersIntoContainsAndPrefix(opts.PageFolder) if len(containsFolders) > 0 { sel.Include(sel.PageItems(containsFolders, opts.Page)) } if len(prefixFolders) > 0 { sel.Include(sel.PageItems(prefixFolders, opts.Page, selectors.PrefixMatch())) } } // channel and message selectors if chans+chanMsgs > 0 { // if no channel is specified, include all channels if chans == 0 { opts.Channels = selectors.Any() } // if no message is specified, only select channels // otherwise, look for channel/message pairs if chanMsgs == 0 { sel.Include(sel.Channels(opts.Channels)) } else { sel.Include(sel.ChannelMessages(opts.Channels, opts.Messages)) } } // conversation and post selectors if convs+convPosts > 0 { // if no conversation is specified, include all conversations if convs == 0 { opts.Conversations = selectors.Any() } // if no post is specified, only select conversations; // otherwise, look for channel/message pairs if chanMsgs == 0 { sel.Include(sel.Conversation(opts.Conversations)) } else { sel.Include(sel.ConversationPosts(opts.Conversations, opts.Posts)) } } return sel } // FilterGroupsRestoreInfoSelectors builds the common info-selector filters. func FilterGroupsRestoreInfoSelectors( sel *selectors.GroupsRestore, opts GroupsOpts, ) { var site string // It is possible that neither exists as this filter is only // mandatory for restore and not for export. if len(opts.SiteID) > 0 { site = opts.SiteID[0] } else if len(opts.WebURL) > 0 { site = opts.WebURL[0] } // sel.Site can accept both ID and URL and so irrespective of // which flag the user uses, we can process both weburl and siteid // Also since the url will start with `https://` in the data that // we store and the id is a uuid, we can grantee that there will be // no collisions. AddGroupsFilter(sel, site, sel.Site) AddGroupsFilter(sel, opts.Library, sel.Library) AddGroupsFilter(sel, opts.FileCreatedAfter, sel.CreatedAfter) AddGroupsFilter(sel, opts.FileCreatedBefore, sel.CreatedBefore) AddGroupsFilter(sel, opts.FileModifiedAfter, sel.ModifiedAfter) AddGroupsFilter(sel, opts.FileModifiedBefore, sel.ModifiedBefore) AddGroupsFilter(sel, opts.MessageCreatedAfter, sel.MessageCreatedAfter) AddGroupsFilter(sel, opts.MessageCreatedBefore, sel.MessageCreatedBefore) AddGroupsFilter(sel, opts.MessageLastReplyAfter, sel.MessageLastReplyAfter) AddGroupsFilter(sel, opts.MessageLastReplyBefore, sel.MessageLastReplyBefore) }