the export file name uses browser time zone, the transaction time in exported file uses time zone in transaction

This commit is contained in:
MaysWind
2021-03-25 23:36:52 +08:00
parent 62d4ba605d
commit ffcbca01d9
9 changed files with 59 additions and 14 deletions
+1
View File
@@ -157,6 +157,7 @@ func startWebServer(c *cli.Context) error {
if config.EnableDataExport { if config.EnableDataExport {
dataRoute := apiRoute.Group("/data") dataRoute := apiRoute.Group("/data")
dataRoute.Use(bindMiddleware(middlewares.HeaderInQueryString))
dataRoute.Use(bindMiddleware(middlewares.JWTAuthorizationByQueryString)) dataRoute.Use(bindMiddleware(middlewares.JWTAuthorizationByQueryString))
{ {
dataRoute.GET("/export.csv", bindCsv(api.DataManagements.ExportDataHandler)) dataRoute.GET("/export.csv", bindCsv(api.DataManagements.ExportDataHandler))
+13 -4
View File
@@ -47,6 +47,15 @@ func (a *DataManagementsApi) ExportDataHandler(c *core.Context) ([]byte, string,
return nil, "", errs.ErrDataExportNotAllowed return nil, "", errs.ErrDataExportNotAllowed
} }
timezone := time.Local
utcOffset, err := c.GetClientTimezoneOffset()
if err != nil {
log.WarnfWithRequestId(c, "[data_managements.ExportDataHandler] cannot get client timezone offset, because %s", err.Error())
} else {
timezone = time.FixedZone("Client Timezone", int(utcOffset)*60)
}
uid := c.GetCurrentUid() uid := c.GetCurrentUid()
accounts, err := a.accounts.GetAllAccountsByUid(uid) accounts, err := a.accounts.GetAllAccountsByUid(uid)
@@ -88,14 +97,14 @@ func (a *DataManagementsApi) ExportDataHandler(c *core.Context) ([]byte, string,
return nil, "", errs.ErrOperationFailed return nil, "", errs.ErrOperationFailed
} }
result, err := a.exporter.GetOutputContent(uid, allTransactions, accountMap, categoryMap, tagMap, tagIndexs) result, err := a.exporter.GetOutputContent(uid, timezone, allTransactions, accountMap, categoryMap, tagMap, tagIndexs)
if err != nil { if err != nil {
log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get csv format exported data for \"uid:%d\", because %s", uid, err.Error()) log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get csv format exported data for \"uid:%d\", because %s", uid, err.Error())
return nil, "", errs.Or(err, errs.ErrOperationFailed) return nil, "", errs.Or(err, errs.ErrOperationFailed)
} }
fileName := a.getFileName() fileName := a.getFileName(timezone)
return result, fileName, nil return result, fileName, nil
} }
@@ -150,8 +159,8 @@ func (a *DataManagementsApi) ClearDataHandler(c *core.Context) (interface{}, *er
return true, nil return true, nil
} }
func (a *DataManagementsApi) getFileName() string { func (a *DataManagementsApi) getFileName(timezone *time.Location) string {
currentTime := utils.FormatToLongDateTimeWithoutSecond(time.Now()) currentTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(time.Now().Unix(), timezone)
currentTime = strings.Replace(currentTime, "-", "_", -1) currentTime = strings.Replace(currentTime, "-", "_", -1)
currentTime = strings.Replace(currentTime, " ", "_", -1) currentTime = strings.Replace(currentTime, " ", "_", -1)
currentTime = strings.Replace(currentTime, ":", "_", -1) currentTime = strings.Replace(currentTime, ":", "_", -1)
+3 -1
View File
@@ -1,6 +1,8 @@
package cli package cli
import ( import (
"time"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/mayswind/lab/pkg/errs" "github.com/mayswind/lab/pkg/errs"
@@ -176,7 +178,7 @@ func (a *UserDataCli) ExportTransaction(c *cli.Context, uid int64) ([]byte, erro
return nil, err return nil, err
} }
result, err := a.csvExporter.GetOutputContent(uid, allTransactions, accountMap, categoryMap, tagMap, tagIndexs) result, err := a.csvExporter.GetOutputContent(uid, time.Local, allTransactions, accountMap, categoryMap, tagMap, tagIndexs)
if err != nil { if err != nil {
log.BootErrorf("[user_data.ExportTransaction] failed to get csv format exported data for \"uid:%d\", because %s", uid, err.Error()) log.BootErrorf("[user_data.ExportTransaction] failed to get csv format exported data for \"uid:%d\", because %s", uid, err.Error())
+2 -2
View File
@@ -12,7 +12,7 @@ const requestIdFieldKey = "REQUEST_ID"
const tokenClaimsFieldKey = "TOKEN_CLAIMS" const tokenClaimsFieldKey = "TOKEN_CLAIMS"
const responseErrorFieldKey = "RESPONSE_ERROR" const responseErrorFieldKey = "RESPONSE_ERROR"
const clientTimezoneOffsetHeaderName = "X-Timezone-Offset" const ClientTimezoneOffsetHeaderName = "X-Timezone-Offset"
// Context represents the request and response context // Context represents the request and response context
type Context struct { type Context struct {
@@ -71,7 +71,7 @@ func (c *Context) GetCurrentUid() int64 {
// GetClientTimezoneOffset returns the client timezone offset // GetClientTimezoneOffset returns the client timezone offset
func (c *Context) GetClientTimezoneOffset() (int16, error) { func (c *Context) GetClientTimezoneOffset() (int16, error) {
value := c.GetHeader(clientTimezoneOffsetHeaderName) value := c.GetHeader(ClientTimezoneOffsetHeaderName)
offset, err := strconv.Atoi(value) offset, err := strconv.Atoi(value)
if err != nil { if err != nil {
+4 -2
View File
@@ -3,6 +3,7 @@ package exporters
import ( import (
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/mayswind/lab/pkg/models" "github.com/mayswind/lab/pkg/models"
"github.com/mayswind/lab/pkg/utils" "github.com/mayswind/lab/pkg/utils"
@@ -17,7 +18,7 @@ const csvHeaderLine = "Time,Type,Category,Sub Category,Account,Amount,Account2,A
const csvDataLineFormat = "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n" const csvDataLineFormat = "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n"
// GetOutputContent returns the exported csv data // GetOutputContent returns the exported csv data
func (e *CSVFileExporter) GetOutputContent(uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexs map[int64][]int64) ([]byte, error) { func (e *CSVFileExporter) GetOutputContent(uid int64, timezone *time.Location, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexs map[int64][]int64) ([]byte, error) {
var ret strings.Builder var ret strings.Builder
ret.Grow(len(transactions) * 100) ret.Grow(len(transactions) * 100)
@@ -30,7 +31,8 @@ func (e *CSVFileExporter) GetOutputContent(uid int64, transactions []*models.Tra
continue continue
} }
transactionTime := utils.FormatToLongDateTimeWithoutSecond(utils.ParseFromUnixTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime))) transactionTimeZone := time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
transactionTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), transactionTimeZone)
transactionType := e.getTransactionTypeName(transaction.Type) transactionType := e.getTransactionTypeName(transaction.Type)
category := e.getTransactionCategoryName(transaction.CategoryId, categoryMap) category := e.getTransactionCategoryName(transaction.CategoryId, categoryMap)
subCategory := e.getTransactionSubCategoryName(transaction.CategoryId, categoryMap) subCategory := e.getTransactionSubCategoryName(transaction.CategoryId, categoryMap)
+6 -2
View File
@@ -1,9 +1,13 @@
package exporters package exporters
import "github.com/mayswind/lab/pkg/models" import (
"time"
"github.com/mayswind/lab/pkg/models"
)
// DataExporter defines the structure of data exporter // DataExporter defines the structure of data exporter
type DataExporter interface { type DataExporter interface {
// GetOutputContent returns the exported data // GetOutputContent returns the exported data
GetOutputContent(uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexs map[int64][]int64) ([]byte, error) GetOutputContent(uid int64, timezone *time.Location, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexs map[int64][]int64) ([]byte, error)
} }
+16
View File
@@ -0,0 +1,16 @@
package middlewares
import (
"github.com/mayswind/lab/pkg/core"
)
const utcOffsetQueryStringParam = "utc_offset"
// HeaderInQueryString puts some headers from query string
func HeaderInQueryString(c *core.Context) {
utcOffset, exists := c.GetQuery(utcOffsetQueryStringParam)
if exists {
c.Request.Header.Set(core.ClientTimezoneOffsetHeaderName, utcOffset)
}
}
+8 -2
View File
@@ -12,8 +12,14 @@ func FormatUnixTimeToLongDateTimeInServerTimezone(unixTime int64) string {
return ParseFromUnixTime(unixTime).Format(longDateTimeFormat) return ParseFromUnixTime(unixTime).Format(longDateTimeFormat)
} }
// FormatToLongDateTimeWithoutSecond returns a textual representation of the time value formatted by long date time format (no second) // FormatUnixTimeToLongDateTimeWithoutSecond returns a textual representation of the unix time formatted by long date time format (no second)
func FormatToLongDateTimeWithoutSecond(t time.Time) string { func FormatUnixTimeToLongDateTimeWithoutSecond(unixTime int64, timezone *time.Location) string {
t := ParseFromUnixTime(unixTime)
if timezone != nil {
t = t.In(timezone)
}
return t.Format(longDateTimeWithoutSecondFormat) return t.Format(longDateTimeWithoutSecondFormat)
} }
+6 -1
View File
@@ -5,7 +5,7 @@
<f7-card> <f7-card>
<f7-card-content class="no-safe-areas" :padding="false"> <f7-card-content class="no-safe-areas" :padding="false">
<f7-list> <f7-list>
<f7-list-button external no-chevron target="_blank" :link="`${$constants.api.baseUrlPath}/data/export.csv?token=${$user.getToken()}`">{{ $t('Export Data') }}</f7-list-button> <f7-list-button external no-chevron target="_blank" :link="`${$constants.api.baseUrlPath}/data/export.csv?token=${$user.getToken()}&utc_offset=${currentTimezoneOffsetMinutes}`">{{ $t('Export Data') }}</f7-list-button>
<f7-list-button color="red" @click="clearData(null)">{{ $t('Clear User Data') }}</f7-list-button> <f7-list-button color="red" @click="clearData(null)">{{ $t('Clear User Data') }}</f7-list-button>
</f7-list> </f7-list>
</f7-card-content> </f7-card-content>
@@ -31,6 +31,11 @@ export default {
showInputPasswordSheetForClearData: false, showInputPasswordSheetForClearData: false,
}; };
}, },
computed: {
currentTimezoneOffsetMinutes() {
return this.$utilities.getTimezoneOffsetMinutes();
}
},
methods: { methods: {
clearData(password) { clearData(password) {
const self = this; const self = this;