diff --git a/src/cmd/factory/exchange.go b/src/cmd/factory/exchange.go index ea06317d8..9d1ec81a2 100644 --- a/src/cmd/factory/exchange.go +++ b/src/cmd/factory/exchange.go @@ -1,10 +1,18 @@ package main import ( + "time" + + "github.com/google/uuid" "github.com/spf13/cobra" . "github.com/alcionai/corso/src/cli/print" "github.com/alcionai/corso/src/cli/utils" + "github.com/alcionai/corso/src/internal/common" + "github.com/alcionai/corso/src/internal/connector/mockconnector" + "github.com/alcionai/corso/src/pkg/control" + "github.com/alcionai/corso/src/pkg/path" + "github.com/alcionai/corso/src/pkg/selectors" ) var ( @@ -34,13 +42,72 @@ func addExchangeCommands(parent *cobra.Command) { } func handleExchangeEmailFactory(cmd *cobra.Command, args []string) error { - Err(cmd.Context(), ErrNotYetImplemeted) + var ( + ctx = cmd.Context() + service = path.ExchangeService + category = path.EmailCategory + ) if utils.HasNoFlagsAndShownHelp(cmd) { return nil } - // generate mocked emails + gc, tenantID, err := getGCAndVerifyUser(ctx, user) + if err != nil { + return Only(ctx, err) + } + + items := make([]item, 0, count) + + for i := 0; i < count; i++ { + var ( + now = common.Now() + nowLegacy = common.FormatLegacyTime(time.Now()) + id = uuid.NewString() + subject = "automated " + now[:16] + " - " + id[:8] + body = "automated mail generation for " + user + " at " + now + " - " + id + ) + + items = append(items, item{ + name: id, + // TODO: allow flags that specify a different "from" user, rather than duplicating + data: mockconnector.GetMockMessageWith( + user, user, user, + subject, body, + nowLegacy, nowLegacy, nowLegacy, nowLegacy), + }) + } + + collections := []collection{{ + pathElements: []string{destination}, + category: category, + items: items, + }} + + // TODO: fit the desination to the containers + dest := control.DefaultRestoreDestination(common.SimpleTimeTesting) + dest.ContainerName = destination + + dataColls, err := buildCollections( + service, + tenantID, user, + dest, + collections, + ) + if err != nil { + return Only(ctx, err) + } + + Infof(ctx, "Generating %d emails in %s\n", count, destination) + + sel := selectors.NewExchangeRestore().Selector + + deets, err := gc.RestoreDataCollections(ctx, sel, dest, dataColls) + if err != nil { + return Only(ctx, err) + } + + deets.PrintEntries(ctx) return nil } @@ -58,7 +125,6 @@ func handleExchangeCalendarEventFactory(cmd *cobra.Command, args []string) error } func handleExchangeContactFactory(cmd *cobra.Command, args []string) error { - //nolint Err(cmd.Context(), ErrNotYetImplemeted) if utils.HasNoFlagsAndShownHelp(cmd) { diff --git a/src/cmd/factory/factory.go b/src/cmd/factory/factory.go index 3160c88f2..b24802c8d 100644 --- a/src/cmd/factory/factory.go +++ b/src/cmd/factory/factory.go @@ -8,6 +8,15 @@ import ( "github.com/spf13/cobra" . "github.com/alcionai/corso/src/cli/print" + "github.com/alcionai/corso/src/internal/common" + "github.com/alcionai/corso/src/internal/connector" + "github.com/alcionai/corso/src/internal/connector/mockconnector" + "github.com/alcionai/corso/src/internal/data" + "github.com/alcionai/corso/src/pkg/account" + "github.com/alcionai/corso/src/pkg/control" + "github.com/alcionai/corso/src/pkg/credentials" + "github.com/alcionai/corso/src/pkg/logger" + "github.com/alcionai/corso/src/pkg/path" ) var factoryCmd = &cobra.Command{ @@ -29,10 +38,10 @@ var oneDriveCmd = &cobra.Command{ } var ( - count int - container string - tenant string - user string + count int + destination string + tenant string + user string ) // TODO: ErrGenerating = errors.New("not all items were successfully generated") @@ -44,7 +53,10 @@ var ErrNotYetImplemeted = errors.New("not yet implemented") // ------------------------------------------------------------------------------------------ func main() { - ctx := SetRootCmd(context.Background(), factoryCmd) + ctx, _ := logger.SeedLevel(context.Background(), logger.Development) + ctx = SetRootCmd(ctx, factoryCmd) + + defer logger.Flush(ctx) // persistent flags that are common to all use cases fs := factoryCmd.PersistentFlags() @@ -53,8 +65,8 @@ func main() { cobra.CheckErr(factoryCmd.MarkPersistentFlagRequired("user")) fs.IntVar(&count, "count", 0, "count of items to produce") cobra.CheckErr(factoryCmd.MarkPersistentFlagRequired("count")) - fs.StringVar(&container, "container", "", "container location of the new data (will create as needed)") - cobra.CheckErr(factoryCmd.MarkPersistentFlagRequired("container")) + fs.StringVar(&destination, "destination", "", "destination of the new data (will create as needed)") + cobra.CheckErr(factoryCmd.MarkPersistentFlagRequired("destination")) factoryCmd.AddCommand(exchangeCmd) addExchangeCommands(exchangeCmd) @@ -80,3 +92,102 @@ func handleOneDriveFactory(cmd *cobra.Command, args []string) error { Err(cmd.Context(), ErrNotYetImplemeted) return cmd.Help() } + +// ------------------------------------------------------------------------------------------ +// Common Helpers +// ------------------------------------------------------------------------------------------ + +func getGCAndVerifyUser(ctx context.Context, userID string) (*connector.GraphConnector, string, error) { + tid := common.First(tenant, os.Getenv(account.TenantID)) + + // get account info + m365Cfg := account.M365Config{ + M365: credentials.GetM365(), + TenantID: tid, + } + + acct, err := account.NewAccount(account.ProviderM365, m365Cfg) + if err != nil { + return nil, "", errors.Wrap(err, "finding m365 account details") + } + + // build a graph connector + gc, err := connector.NewGraphConnector(ctx, acct) + if err != nil { + return nil, "", errors.Wrap(err, "connecting to graph api") + } + + if _, ok := gc.Users[user]; !ok { + return nil, "", errors.New("user not found within tenant") + } + + return gc, tid, nil +} + +type item struct { + name string + data []byte +} + +type collection struct { + // Elements (in order) for the path representing this collection. Should + // only contain elements after the prefix that corso uses for the path. For + // example, a collection for the Inbox folder in exchange mail would just be + // "Inbox". + pathElements []string + category path.CategoryType + items []item +} + +func buildCollections( + service path.ServiceType, + tenant, user string, + dest control.RestoreDestination, + colls []collection, +) ([]data.Collection, error) { + collections := make([]data.Collection, 0, len(colls)) + + for _, c := range colls { + pth, err := toDataLayerPath( + service, + tenant, + user, + c.category, + c.pathElements, + false, + ) + if err != nil { + return nil, err + } + + mc := mockconnector.NewMockExchangeCollection(pth, len(c.items)) + + for i := 0; i < len(c.items); i++ { + mc.Names[i] = c.items[i].name + mc.Data[i] = c.items[i].data + } + + collections = append(collections, mc) + } + + return collections, nil +} + +func toDataLayerPath( + service path.ServiceType, + tenant, user string, + category path.CategoryType, + elements []string, + isItem bool, +) (path.Path, error) { + pb := path.Builder{}.Append(elements...) + + switch service { + case path.ExchangeService: + return pb.ToDataLayerExchangePathForCategory(tenant, user, category, isItem) + case path.OneDriveService: + return pb.ToDataLayerOneDrivePath(tenant, user, isItem) + } + + return nil, errors.Errorf("unknown service %s", service.String()) +} diff --git a/src/cmd/purge/purge.go b/src/cmd/purge/purge.go index 045fad44e..e7c817e19 100644 --- a/src/cmd/purge/purge.go +++ b/src/cmd/purge/purge.go @@ -18,6 +18,7 @@ import ( "github.com/alcionai/corso/src/internal/connector/onedrive" "github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/credentials" + "github.com/alcionai/corso/src/pkg/logger" ) var purgeCmd = &cobra.Command{ @@ -64,7 +65,11 @@ var ErrPurging = errors.New("not all items were successfully purged") // ------------------------------------------------------------------------------------------ func main() { - ctx := SetRootCmd(context.Background(), purgeCmd) + ctx, _ := logger.SeedLevel(context.Background(), logger.Development) + ctx = SetRootCmd(ctx, purgeCmd) + + defer logger.Flush(ctx) + fs := purgeCmd.PersistentFlags() fs.StringVar(&before, "before", "", "folders older than this date are deleted. (default: now in UTC)") fs.StringVar(&user, "user", "", "m365 user id whose folders will be deleted") diff --git a/src/internal/connector/exchange/service_restore.go b/src/internal/connector/exchange/service_restore.go index 6f84bd89e..4aa64ee5b 100644 --- a/src/internal/connector/exchange/service_restore.go +++ b/src/internal/connector/exchange/service_restore.go @@ -182,8 +182,7 @@ func RestoreMailMessage( bits []byte, service graph.Service, cp control.CollisionPolicy, - destination, - user string, + destination, user string, ) (*details.ExchangeInfo, error) { // Creates messageable object from original bytes originalMessage, err := support.CreateMessageFromBytes(bits) diff --git a/src/internal/connector/mockconnector/mock_data_message.go b/src/internal/connector/mockconnector/mock_data_message.go index a44184f95..4d302f31a 100644 --- a/src/internal/connector/mockconnector/mock_data_message.go +++ b/src/internal/connector/mockconnector/mock_data_message.go @@ -22,22 +22,73 @@ const ( "\\nThanking you in adv" // Order of fields to fill in: - // 1. message body - // 2. message preview - // 3. sender user ID - // 4. subject - messageTmpl = "{\"id\":\"AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAADCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAEMAADSEBNbUIB9RL6ePDeF3FIYAAB3XwIkAAA=\",\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#users('a4a472f8-ccb0-43ec-bf52-3697a91b926c')/messages/$entity\"," + - "\"@odata.etag\":\"W/\\\"CQAAABYAAADSEBNbUIB9RL6ePDeF3FIYAAB2ZxqU\\\"\",\"categories\":[],\"changeKey\":\"CQAAABYAAADSEBNbUIB9RL6ePDeF3FIYAAB2ZxqU\",\"createdDateTime\":\"2022-09-26T23:15:50Z\",\"lastModifiedDateTime\":\"2022-09-26T23:15:51Z\",\"bccRecipients\":[],\"body\":{\"content\":\"
" + - "\\n