mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-14 06:57:35 +08:00
the export file name uses browser time zone, the transaction time in exported file uses time zone in transaction
This commit is contained in:
@@ -157,6 +157,7 @@ func startWebServer(c *cli.Context) error {
|
||||
|
||||
if config.EnableDataExport {
|
||||
dataRoute := apiRoute.Group("/data")
|
||||
dataRoute.Use(bindMiddleware(middlewares.HeaderInQueryString))
|
||||
dataRoute.Use(bindMiddleware(middlewares.JWTAuthorizationByQueryString))
|
||||
{
|
||||
dataRoute.GET("/export.csv", bindCsv(api.DataManagements.ExportDataHandler))
|
||||
|
||||
@@ -47,6 +47,15 @@ func (a *DataManagementsApi) ExportDataHandler(c *core.Context) ([]byte, string,
|
||||
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()
|
||||
|
||||
accounts, err := a.accounts.GetAllAccountsByUid(uid)
|
||||
@@ -88,14 +97,14 @@ func (a *DataManagementsApi) ExportDataHandler(c *core.Context) ([]byte, string,
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
fileName := a.getFileName()
|
||||
fileName := a.getFileName(timezone)
|
||||
|
||||
return result, fileName, nil
|
||||
}
|
||||
@@ -150,8 +159,8 @@ func (a *DataManagementsApi) ClearDataHandler(c *core.Context) (interface{}, *er
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *DataManagementsApi) getFileName() string {
|
||||
currentTime := utils.FormatToLongDateTimeWithoutSecond(time.Now())
|
||||
func (a *DataManagementsApi) getFileName(timezone *time.Location) string {
|
||||
currentTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(time.Now().Unix(), timezone)
|
||||
currentTime = strings.Replace(currentTime, "-", "_", -1)
|
||||
currentTime = strings.Replace(currentTime, " ", "_", -1)
|
||||
currentTime = strings.Replace(currentTime, ":", "_", -1)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/mayswind/lab/pkg/errs"
|
||||
@@ -176,7 +178,7 @@ func (a *UserDataCli) ExportTransaction(c *cli.Context, uid int64) ([]byte, erro
|
||||
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 {
|
||||
log.BootErrorf("[user_data.ExportTransaction] failed to get csv format exported data for \"uid:%d\", because %s", uid, err.Error())
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ const requestIdFieldKey = "REQUEST_ID"
|
||||
const tokenClaimsFieldKey = "TOKEN_CLAIMS"
|
||||
const responseErrorFieldKey = "RESPONSE_ERROR"
|
||||
|
||||
const clientTimezoneOffsetHeaderName = "X-Timezone-Offset"
|
||||
const ClientTimezoneOffsetHeaderName = "X-Timezone-Offset"
|
||||
|
||||
// Context represents the request and response context
|
||||
type Context struct {
|
||||
@@ -71,7 +71,7 @@ func (c *Context) GetCurrentUid() int64 {
|
||||
|
||||
// GetClientTimezoneOffset returns the client timezone offset
|
||||
func (c *Context) GetClientTimezoneOffset() (int16, error) {
|
||||
value := c.GetHeader(clientTimezoneOffsetHeaderName)
|
||||
value := c.GetHeader(ClientTimezoneOffsetHeaderName)
|
||||
offset, err := strconv.Atoi(value)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -3,6 +3,7 @@ package exporters
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/lab/pkg/models"
|
||||
"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"
|
||||
|
||||
// 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
|
||||
|
||||
ret.Grow(len(transactions) * 100)
|
||||
@@ -30,7 +31,8 @@ func (e *CSVFileExporter) GetOutputContent(uid int64, transactions []*models.Tra
|
||||
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)
|
||||
category := e.getTransactionCategoryName(transaction.CategoryId, categoryMap)
|
||||
subCategory := e.getTransactionSubCategoryName(transaction.CategoryId, categoryMap)
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package exporters
|
||||
|
||||
import "github.com/mayswind/lab/pkg/models"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/lab/pkg/models"
|
||||
)
|
||||
|
||||
// DataExporter defines the structure of data exporter
|
||||
type DataExporter interface {
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,14 @@ func FormatUnixTimeToLongDateTimeInServerTimezone(unixTime int64) string {
|
||||
return ParseFromUnixTime(unixTime).Format(longDateTimeFormat)
|
||||
}
|
||||
|
||||
// FormatToLongDateTimeWithoutSecond returns a textual representation of the time value formatted by long date time format (no second)
|
||||
func FormatToLongDateTimeWithoutSecond(t time.Time) string {
|
||||
// FormatUnixTimeToLongDateTimeWithoutSecond returns a textual representation of the unix time formatted by long date time format (no second)
|
||||
func FormatUnixTimeToLongDateTimeWithoutSecond(unixTime int64, timezone *time.Location) string {
|
||||
t := ParseFromUnixTime(unixTime)
|
||||
|
||||
if timezone != nil {
|
||||
t = t.In(timezone)
|
||||
}
|
||||
|
||||
return t.Format(longDateTimeWithoutSecondFormat)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<f7-card>
|
||||
<f7-card-content class="no-safe-areas" :padding="false">
|
||||
<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>
|
||||
</f7-card-content>
|
||||
@@ -31,6 +31,11 @@ export default {
|
||||
showInputPasswordSheetForClearData: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentTimezoneOffsetMinutes() {
|
||||
return this.$utilities.getTimezoneOffsetMinutes();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clearData(password) {
|
||||
const self = this;
|
||||
|
||||
Reference in New Issue
Block a user