diff --git a/cmd/user_data.go b/cmd/user_data.go index a38ca3c1..d6f7defe 100644 --- a/cmd/user_data.go +++ b/cmd/user_data.go @@ -1,10 +1,13 @@ package cmd import ( + "os" + "github.com/urfave/cli/v2" clis "github.com/mayswind/lab/pkg/cli" "github.com/mayswind/lab/pkg/log" + "github.com/mayswind/lab/pkg/utils" ) // UserData represents the data command @@ -24,6 +27,23 @@ var UserData = &cli.Command{ }, }, }, + { + Name: "export", + Usage: "Export user all transactions to csv file", + Action: exportUserTransaction, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "username", + Aliases: []string{"n"}, + Usage: "Specific user name", + }, + &cli.StringFlag{ + Name: "file", + Aliases: []string{"f"}, + Usage: "Specific exported file path (e.g. transaction.csv)", + }, + }, + }, }, } @@ -55,3 +75,53 @@ func checkUserTransactionAndAccount(c *cli.Context) error { return nil } + +func exportUserTransaction(c *cli.Context) error { + _, err := initializeSystem(c) + + if err != nil { + return err + } + + userName := c.String("username") + uid, err := clis.UserData.GetUserIdByUsername(c, userName) + + if err != nil { + log.BootErrorf("[user_data.exportUserTransaction] error occurs when getting user id by user name") + return err + } + + filePath := c.String("file") + + if filePath == "" { + log.BootErrorf("[user_data.exportUserTransaction] export file path is not specified") + return os.ErrNotExist + } + + fileExists, err := utils.IsExists(filePath) + + if fileExists { + log.BootErrorf("[user_data.exportUserTransaction] specified file path already exists") + return os.ErrExist + } + + log.BootInfof("[user_data.exportUserTransaction] starting exporting user \"%s\" data", userName) + + content, err := clis.UserData.ExportTransaction(c, uid) + + if err != nil { + log.BootErrorf("[user_data.exportUserTransaction] error occurs when exporting user data") + return err + } + + err = utils.WriteFile(filePath, content) + + if err != nil { + log.BootErrorf("[user_data.exportUserTransaction] failed to write to %s", filePath) + return err + } + + log.BootInfof("[user_data.exportUserTransaction] user transactions have been exported to %s", filePath) + + return nil +} diff --git a/pkg/cli/user_data.go b/pkg/cli/user_data.go index a717331f..bdc5506b 100644 --- a/pkg/cli/user_data.go +++ b/pkg/cli/user_data.go @@ -4,15 +4,18 @@ import ( "github.com/urfave/cli/v2" "github.com/mayswind/lab/pkg/errs" + "github.com/mayswind/lab/pkg/exporters" "github.com/mayswind/lab/pkg/log" "github.com/mayswind/lab/pkg/models" "github.com/mayswind/lab/pkg/services" ) const pageCountForGettingTransactions = 1000 +const pageCountForDataExport = 1000 // UserDataCli represents user data cli type UserDataCli struct { + csvExporter *exporters.CSVFileExporter accounts *services.AccountService transactions *services.TransactionService categories *services.TransactionCategoryService @@ -23,6 +26,7 @@ type UserDataCli struct { // Initialize an user data cli singleton instance var ( UserData = &UserDataCli{ + csvExporter: &exporters.CSVFileExporter{}, accounts: services.Accounts, transactions: services.Transactions, users: services.Users, @@ -139,6 +143,32 @@ func (a *UserDataCli) CheckTransactionAndAccount(c *cli.Context, uid int64) (boo return true, nil } +// ExportTransaction returns csv file content according user all transactions +func (a *UserDataCli) ExportTransaction(c *cli.Context, uid int64) ([]byte, error) { + accountMap, categoryMap, tagMap, tagIndexs, err := a.getUserEssentialData(uid) + + if err != nil { + log.BootErrorf("[user_data.ExportTransaction] failed to get essential data for user \"uid:%d\", because %s", uid, err.Error()) + return nil, err + } + + allTransactions, err := a.transactions.GetAllTransactions(uid, pageCountForDataExport, true) + + if err != nil { + log.BootErrorf("[user_data.ExportTransaction] failed to all transactions for user \"uid:%d\", because %s", uid, err.Error()) + return nil, err + } + + result, err := a.csvExporter.GetOutputContent(uid, allTransactions, accountMap, categoryMap, tagMap, tagIndexs) + + if err != nil { + log.BootErrorf("[user_data.ExportTransaction] failed to get csv format exported data for \"uid:%d\", because %s", uid, err.Error()) + return nil, err + } + + return result, nil +} + // GetUserIdByUsername returns user id by user name func (a *UserDataCli) GetUserIdByUsername(c *cli.Context, username string) (int64, error) { if username == "" { diff --git a/pkg/utils/io.go b/pkg/utils/io.go index 623d7f27..2de86a8a 100644 --- a/pkg/utils/io.go +++ b/pkg/utils/io.go @@ -1,6 +1,43 @@ package utils -import "io" +import ( + "io" + "os" +) + +// IsExists returns whether specified file or directory path exits +func IsExists(path string) (bool, error) { + _, err := os.Stat(path) + + if err == nil { + return true, nil + } + + if os.IsNotExist(err) { + return false, nil + } + + return false, err +} + +// WriteFile would write file according to specified content +func WriteFile(path string, data []byte) error { + file, err := os.Create(path) + + if err != nil { + return err + } + + defer file.Close() + + n, err := file.Write(data) + + if err == nil && n < len(data) { + return io.ErrShortWrite + } + + return err +} // IdentReader returns the original io reader func IdentReader(encoding string, input io.Reader) (io.Reader, error) {