feat(transaction): #66 fix inputs
Some checks failed
Build Docker Image / Build-Docker-Image (push) Failing after 4m58s
Some checks failed
Build Docker Image / Build-Docker-Image (push) Failing after 4m58s
This commit is contained in:
@@ -127,12 +127,13 @@ func (h TransactionImpl) handleUpdateTransaction() http.HandlerFunc {
|
|||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
input := types.TransactionInput{
|
input := types.TransactionInput{
|
||||||
Id: r.PathValue("id"),
|
Id: r.PathValue("id"),
|
||||||
AccountId: r.FormValue("account_id"),
|
AccountId: r.FormValue("account-id"),
|
||||||
TreasureChestId: r.FormValue("treasure_chest_id"),
|
TreasureChestId: r.FormValue("treasure-chest-id"),
|
||||||
Value: r.FormValue("value"),
|
Value: r.FormValue("value"),
|
||||||
Timestamp: r.FormValue("timestamp"),
|
Timestamp: r.FormValue("timestamp"),
|
||||||
Note: r.FormValue("note"),
|
TimezoneOffsetMinutes: r.FormValue("timezone-offset"),
|
||||||
|
Note: r.FormValue("note"),
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.Id == "new" {
|
if input.Id == "new" {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func (s AccountImpl) Add(user *types.User, name string) (*types.Account, error)
|
|||||||
return nil, types.ErrInternal
|
return nil, types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateString(name)
|
err = validateString(name, "name")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ func (s AccountImpl) Update(user *types.User, id string, name string) (*types.Ac
|
|||||||
if user == nil {
|
if user == nil {
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
err := validateString(name)
|
err := validateString(name, "name")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package service
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
func validateString(value string) error {
|
func validateString(value string, fieldName string) error {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return fmt.Errorf("field \"name\" needs to be set: %w", ErrBadRequest)
|
return fmt.Errorf("field \"%s\" needs to be set: %w", fieldName, ErrBadRequest)
|
||||||
} else if !safeInputRegex.MatchString(value) {
|
} else if !safeInputRegex.MatchString(value) {
|
||||||
return fmt.Errorf("use only letters, dashes and spaces for \"name\": %w", ErrBadRequest)
|
return fmt.Errorf("use only letters, dashes and spaces for \"%s\": %w", fieldName, ErrBadRequest)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,8 +130,8 @@ func (s TransactionImpl) Get(user *types.User, id string) (*types.Transaction, e
|
|||||||
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
var transaction *types.Transaction
|
var transaction types.Transaction
|
||||||
err = s.db.Select(transaction, `SELECT * FROM "transaction" WHERE user_id = ? AND id = ?`, user.Id, uuid)
|
err = s.db.Get(&transaction, `SELECT * FROM "transaction" WHERE user_id = ? AND id = ?`, user.Id, uuid)
|
||||||
err = db.TransformAndLogDbError("transaction Get", nil, err)
|
err = db.TransformAndLogDbError("transaction Get", nil, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == db.ErrNotFound {
|
if err == db.ErrNotFound {
|
||||||
@@ -140,7 +140,7 @@ func (s TransactionImpl) Get(user *types.User, id string) (*types.Transaction, e
|
|||||||
return nil, types.ErrInternal
|
return nil, types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
return transaction, nil
|
return &transaction, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TransactionImpl) GetAll(user *types.User) ([]*types.Transaction, error) {
|
func (s TransactionImpl) GetAll(user *types.User) ([]*types.Transaction, error) {
|
||||||
@@ -244,21 +244,32 @@ func (s TransactionImpl) validateAndEnrichTransaction(transaction *types.Transac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
valueInt, err := strconv.ParseInt(input.Value, 10, 64)
|
valueFloat, err := strconv.ParseFloat(input.Value, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("transaction validate: %v", err)
|
log.Error("transaction validate: %v", err)
|
||||||
return nil, fmt.Errorf("could not parse value: %w", ErrBadRequest)
|
return nil, fmt.Errorf("could not parse value: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
valueInt := int64(valueFloat * 100)
|
||||||
|
|
||||||
timestampTime, err := time.Parse(time.RFC3339, input.Timestamp)
|
timestampTime, err := time.Parse("2006-01-02T15:04", input.Timestamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("transaction validate: %v", err)
|
log.Error("transaction validate: %v", err)
|
||||||
return nil, fmt.Errorf("could not parse timestamp: %w", ErrBadRequest)
|
return nil, fmt.Errorf("could not parse timestamp: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateString(input.Note)
|
if input.TimezoneOffsetMinutes != "" {
|
||||||
if err != nil {
|
timezoneOffsetMinutes, err := strconv.Atoi(input.TimezoneOffsetMinutes)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse timezone offset: %w", ErrBadRequest)
|
||||||
|
}
|
||||||
|
timestampTime = timestampTime.Add(time.Duration(-1*timezoneOffsetMinutes) * time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.Note != "" {
|
||||||
|
err = validateString(input.Note, "note")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &types.Transaction{
|
return &types.Transaction{
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (s TreasureChestImpl) Add(user *types.User, parentId, name string) (*types.
|
|||||||
return nil, types.ErrInternal
|
return nil, types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateString(name)
|
err = validateString(name, "name")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ func (s TreasureChestImpl) Update(user *types.User, idStr, parentId, name string
|
|||||||
if user == nil {
|
if user == nil {
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
err := validateString(name)
|
err := validateString(name, "name")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
27
static/js/time.js
Normal file
27
static/js/time.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
|
||||||
|
htmx.on("htmx:afterSwap", (e) => {
|
||||||
|
updateTime(e.target);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
console.log("DOMContentLoaded");
|
||||||
|
updateTime(document);
|
||||||
|
})
|
||||||
|
|
||||||
|
function updateTime(e) {
|
||||||
|
const timezoneOffset = - new Date().getTimezoneOffset();
|
||||||
|
e.querySelectorAll("#timezone-offset").forEach((el) => {
|
||||||
|
el.value = timezoneOffset;
|
||||||
|
});
|
||||||
|
document.querySelectorAll(".datetime").forEach((el) => {
|
||||||
|
if (el.textContent !== "") {
|
||||||
|
el.textContent = el.textContent.includes("UTC") ? new Date(el.textContent).toLocaleString([], { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }) : el.textContent;
|
||||||
|
} else if (el.attributes['value'] !== "") {
|
||||||
|
const value = el.attributes['value'].value;
|
||||||
|
const newDate = value.includes("UTC") ? new Date(value) : value;
|
||||||
|
newDate.setTime(newDate.getTime() + timezoneOffset * 60 * 1000);
|
||||||
|
el.valueAsDate = newDate;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@@ -26,6 +26,7 @@ templ Layout(slot templ.Component, user templ.Component, loggedIn bool, path str
|
|||||||
/>
|
/>
|
||||||
<script src="/static/js/htmx.min.js"></script>
|
<script src="/static/js/htmx.min.js"></script>
|
||||||
<script src="/static/js/toast.js"></script>
|
<script src="/static/js/toast.js"></script>
|
||||||
|
<script src="/static/js/time.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="h-screen flex flex-col" hx-headers='{"csrf-token": "CSRF_TOKEN"}'>
|
<body class="h-screen flex flex-col" hx-headers='{"csrf-token": "CSRF_TOKEN"}'>
|
||||||
// Header
|
// Header
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ templ Transaction(transactions []*types.Transaction, accounts []*types.Account,
|
|||||||
<p class="">New Transaction</p>
|
<p class="">New Transaction</p>
|
||||||
</button>
|
</button>
|
||||||
<div id="transaction-items" class="my-6 flex flex-col items-center">
|
<div id="transaction-items" class="my-6 flex flex-col items-center">
|
||||||
@EditTransaction(nil, accounts, treasureChests)
|
|
||||||
for _, transaction := range transactions {
|
for _, transaction := range transactions {
|
||||||
@TransactionItem(transaction)
|
@TransactionItem(transaction)
|
||||||
}
|
}
|
||||||
@@ -39,7 +38,7 @@ templ EditTransaction(transaction *types.Transaction, accounts []*types.Account,
|
|||||||
cancelUrl string
|
cancelUrl string
|
||||||
)
|
)
|
||||||
if transaction == nil {
|
if transaction == nil {
|
||||||
timestamp = time.Now()
|
timestamp = time.Now().UTC().Truncate(time.Minute)
|
||||||
note = ""
|
note = ""
|
||||||
// accountId = ""
|
// accountId = ""
|
||||||
// treasureChestId = ""
|
// treasureChestId = ""
|
||||||
@@ -48,7 +47,7 @@ templ EditTransaction(transaction *types.Transaction, accounts []*types.Account,
|
|||||||
id = "new"
|
id = "new"
|
||||||
cancelUrl = "/empty"
|
cancelUrl = "/empty"
|
||||||
} else {
|
} else {
|
||||||
timestamp = transaction.Timestamp
|
timestamp = transaction.Timestamp.UTC().Truncate(time.Minute)
|
||||||
note = transaction.Note
|
note = transaction.Note
|
||||||
// accountId = transaction.AccountId.String()
|
// accountId = transaction.AccountId.String()
|
||||||
// treasureChestId = transaction.TreasureChestId.String()
|
// treasureChestId = transaction.TreasureChestId.String()
|
||||||
@@ -66,14 +65,15 @@ templ EditTransaction(transaction *types.Transaction, accounts []*types.Account,
|
|||||||
class="text-xl flex justify-end gap-4 items-center"
|
class="text-xl flex justify-end gap-4 items-center"
|
||||||
>
|
>
|
||||||
<div class="grid grid-cols-[auto_auto] items-center gap-4 mr-auto">
|
<div class="grid grid-cols-[auto_auto] items-center gap-4 mr-auto">
|
||||||
<label for="name" class="text-sm text-gray-500">Transaction Date</label>
|
<label for="timestamp" class="text-sm text-gray-500">Transaction Date</label>
|
||||||
<input
|
<input
|
||||||
autofocus
|
autofocus
|
||||||
name="name"
|
name="timestamp"
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
value={ timestamp.Format("2006-01-02T15:04") }
|
value={ timestamp.String() }
|
||||||
class="bg-white input"
|
class="bg-white input datetime"
|
||||||
/>
|
/>
|
||||||
|
<input class="hidden" id="timezone-offset" name="timezone-offset" type="text"/>
|
||||||
<label for="note" class="text-sm text-gray-500">Note</label>
|
<label for="note" class="text-sm text-gray-500">Note</label>
|
||||||
<input
|
<input
|
||||||
name="note"
|
name="note"
|
||||||
@@ -140,7 +140,7 @@ templ EditTransaction(transaction *types.Transaction, accounts []*types.Account,
|
|||||||
templ TransactionItem(transaction *types.Transaction) {
|
templ TransactionItem(transaction *types.Transaction) {
|
||||||
<div id="transaction" class="border-1 border-gray-300 w-full my-4 p-4 bg-gray-50 rounded-lg">
|
<div id="transaction" class="border-1 border-gray-300 w-full my-4 p-4 bg-gray-50 rounded-lg">
|
||||||
<div class="text-xl flex justify-end gap-4">
|
<div class="text-xl flex justify-end gap-4">
|
||||||
<p class="mr-auto">{ transaction.Timestamp.String() }</p>
|
<p class="mr-auto datetime">{ transaction.Timestamp.String() }</p>
|
||||||
<p class="mr-20 text-green-700">{ displayBalance(transaction.Value) }</p>
|
<p class="mr-20 text-green-700">{ displayBalance(transaction.Value) }</p>
|
||||||
<button
|
<button
|
||||||
hx-get={ "/transaction/" + transaction.Id.String() + "?edit=true" }
|
hx-get={ "/transaction/" + transaction.Id.String() + "?edit=true" }
|
||||||
|
|||||||
@@ -33,10 +33,11 @@ type Transaction struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TransactionInput struct {
|
type TransactionInput struct {
|
||||||
Id string
|
Id string
|
||||||
AccountId string
|
AccountId string
|
||||||
TreasureChestId string
|
TreasureChestId string
|
||||||
Value string
|
Value string
|
||||||
Timestamp string
|
Timestamp string
|
||||||
Note string
|
TimezoneOffsetMinutes string
|
||||||
|
Note string
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user