fix(observabillity): include otel logs
Some checks failed
Build Docker Image / Build-Docker-Image (push) Failing after 3m40s
Some checks failed
Build Docker Image / Build-Docker-Image (push) Failing after 3m40s
This commit is contained in:
4
go.mod
4
go.mod
@@ -12,11 +12,15 @@ require (
|
|||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/mattn/go-sqlite3 v1.14.28
|
github.com/mattn/go-sqlite3 v1.14.28
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
|
go.opentelemetry.io/contrib/bridges/otelslog v0.11.0
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0
|
||||||
go.opentelemetry.io/otel v1.36.0
|
go.opentelemetry.io/otel v1.36.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0
|
||||||
|
go.opentelemetry.io/otel/log v0.12.2
|
||||||
go.opentelemetry.io/otel/sdk v1.36.0
|
go.opentelemetry.io/otel/sdk v1.36.0
|
||||||
|
go.opentelemetry.io/otel/sdk/log v0.12.2
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.36.0
|
go.opentelemetry.io/otel/sdk/metric v1.36.0
|
||||||
go.opentelemetry.io/otel/trace v1.36.0
|
go.opentelemetry.io/otel/trace v1.36.0
|
||||||
golang.org/x/crypto v0.39.0
|
golang.org/x/crypto v0.39.0
|
||||||
|
|||||||
10
go.sum
10
go.sum
@@ -53,20 +53,30 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
|||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
|
go.opentelemetry.io/contrib/bridges/otelslog v0.11.0 h1:EMIiYTms4Z4m3bBuKp1VmMNRLZcl6j4YbvOPL1IhlWo=
|
||||||
|
go.opentelemetry.io/contrib/bridges/otelslog v0.11.0/go.mod h1:DIEZmUR7tzuOOVUTDKvkGWtYWSHFV18Qg8+GMb8wPJw=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 h1:zwdo1gS2eH26Rg+CoqVQpEK1h8gvt5qyU5Kk5Bixvow=
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 h1:zwdo1gS2eH26Rg+CoqVQpEK1h8gvt5qyU5Kk5Bixvow=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0/go.mod h1:rUKCPscaRWWcqGT6HnEmYrK+YNe5+Sw64xgQTOJ5b30=
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0/go.mod h1:rUKCPscaRWWcqGT6HnEmYrK+YNe5+Sw64xgQTOJ5b30=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c=
|
||||||
|
go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc=
|
||||||
|
go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E=
|
||||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||||
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
||||||
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
||||||
|
go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0=
|
||||||
|
go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY=
|
||||||
|
go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc h1:uqxdywfHqqCl6LmZzI3pUnXT1RGFYyUgxj0AkWPFxi0=
|
||||||
|
go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc/go.mod h1:TY/N/FT7dmFrP/r5ym3g0yysP1DefqGpAZr4f82P0dE=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
|
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
|
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
|
||||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"spend-sparrow/internal/log"
|
|
||||||
"spend-sparrow/internal/types"
|
|
||||||
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
"spend-sparrow/internal/types"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -52,7 +51,7 @@ func (db AuthSqlite) InsertUser(user *types.User) error {
|
|||||||
return ErrAlreadyExists
|
return ErrAlreadyExists
|
||||||
}
|
}
|
||||||
|
|
||||||
log.L.Error("SQL error InsertUser", "err", err)
|
slog.Error("SQL error InsertUser", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +66,7 @@ func (db AuthSqlite) UpdateUser(user *types.User) error {
|
|||||||
user.EmailVerified, user.EmailVerifiedAt, user.Password, user.Id)
|
user.EmailVerified, user.EmailVerifiedAt, user.Password, user.Id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("SQL error UpdateUser", "err", err)
|
slog.Error("SQL error UpdateUser", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +92,7 @@ func (db AuthSqlite) GetUserByEmail(email string) (*types.User, error) {
|
|||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return nil, ErrNotFound
|
return nil, ErrNotFound
|
||||||
} else {
|
} else {
|
||||||
log.L.Error("SQL error GetUser", "err", err)
|
slog.Error("SQL error GetUser", "err", err)
|
||||||
return nil, types.ErrInternal
|
return nil, types.ErrInternal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,7 +119,7 @@ func (db AuthSqlite) GetUser(userId uuid.UUID) (*types.User, error) {
|
|||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return nil, ErrNotFound
|
return nil, ErrNotFound
|
||||||
} else {
|
} else {
|
||||||
log.L.Error("SQL error GetUser", "err", err)
|
slog.Error("SQL error GetUser", "err", err)
|
||||||
return nil, types.ErrInternal
|
return nil, types.ErrInternal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,55 +130,55 @@ func (db AuthSqlite) GetUser(userId uuid.UUID) (*types.User, error) {
|
|||||||
func (db AuthSqlite) DeleteUser(userId uuid.UUID) error {
|
func (db AuthSqlite) DeleteUser(userId uuid.UUID) error {
|
||||||
tx, err := db.db.Begin()
|
tx, err := db.db.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not start transaction", "err", err)
|
slog.Error("Could not start transaction", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.Exec("DELETE FROM account WHERE user_id = ?", userId)
|
_, err = tx.Exec("DELETE FROM account WHERE user_id = ?", userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
log.L.Error("Could not delete accounts", "err", err)
|
slog.Error("Could not delete accounts", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.Exec("DELETE FROM token WHERE user_id = ?", userId)
|
_, err = tx.Exec("DELETE FROM token WHERE user_id = ?", userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
log.L.Error("Could not delete user tokens", "err", err)
|
slog.Error("Could not delete user tokens", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.Exec("DELETE FROM session WHERE user_id = ?", userId)
|
_, err = tx.Exec("DELETE FROM session WHERE user_id = ?", userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
log.L.Error("Could not delete sessions", "err", err)
|
slog.Error("Could not delete sessions", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.Exec("DELETE FROM user WHERE user_id = ?", userId)
|
_, err = tx.Exec("DELETE FROM user WHERE user_id = ?", userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
log.L.Error("Could not delete user", "err", err)
|
slog.Error("Could not delete user", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.Exec("DELETE FROM treasure_chest WHERE user_id = ?", userId)
|
_, err = tx.Exec("DELETE FROM treasure_chest WHERE user_id = ?", userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
log.L.Error("Could not delete user", "err", err)
|
slog.Error("Could not delete user", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.Exec("DELETE FROM \"transaction\" WHERE user_id = ?", userId)
|
_, err = tx.Exec("DELETE FROM \"transaction\" WHERE user_id = ?", userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
log.L.Error("Could not delete user", "err", err)
|
slog.Error("Could not delete user", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not commit transaction", "err", err)
|
slog.Error("Could not commit transaction", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +191,7 @@ func (db AuthSqlite) InsertToken(token *types.Token) error {
|
|||||||
VALUES (?, ?, ?, ?, ?, ?)`, token.UserId, token.SessionId, token.Type, token.Token, token.CreatedAt, token.ExpiresAt)
|
VALUES (?, ?, ?, ?, ?, ?)`, token.UserId, token.SessionId, token.Type, token.Token, token.CreatedAt, token.ExpiresAt)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not insert token", "err", err)
|
slog.Error("Could not insert token", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,23 +216,23 @@ func (db AuthSqlite) GetToken(token string) (*types.Token, error) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
log.L.Info("Token not found", "token", token)
|
slog.Info("Token not found", "token", token)
|
||||||
return nil, ErrNotFound
|
return nil, ErrNotFound
|
||||||
} else {
|
} else {
|
||||||
log.L.Error("Could not get token", "err", err)
|
slog.Error("Could not get token", "err", err)
|
||||||
return nil, types.ErrInternal
|
return nil, types.ErrInternal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createdAt, err = time.Parse(time.RFC3339, createdAtStr)
|
createdAt, err = time.Parse(time.RFC3339, createdAtStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not parse token.created_at", "err", err)
|
slog.Error("Could not parse token.created_at", "err", err)
|
||||||
return nil, types.ErrInternal
|
return nil, types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
expiresAt, err = time.Parse(time.RFC3339, expiresAtStr)
|
expiresAt, err = time.Parse(time.RFC3339, expiresAtStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not parse token.expires_at", "err", err)
|
slog.Error("Could not parse token.expires_at", "err", err)
|
||||||
return nil, types.ErrInternal
|
return nil, types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +247,7 @@ func (db AuthSqlite) GetTokensByUserIdAndType(userId uuid.UUID, tokenType types.
|
|||||||
AND type = ?`, userId, tokenType)
|
AND type = ?`, userId, tokenType)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not get token", "err", err)
|
slog.Error("Could not get token", "err", err)
|
||||||
return nil, types.ErrInternal
|
return nil, types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +262,7 @@ func (db AuthSqlite) GetTokensBySessionIdAndType(sessionId string, tokenType typ
|
|||||||
AND type = ?`, sessionId, tokenType)
|
AND type = ?`, sessionId, tokenType)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not get token", "err", err)
|
slog.Error("Could not get token", "err", err)
|
||||||
return nil, types.ErrInternal
|
return nil, types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,19 +286,19 @@ func getTokensFromQuery(query *sql.Rows, userId uuid.UUID, sessionId string, tok
|
|||||||
|
|
||||||
err := query.Scan(&token, &createdAtStr, &expiresAtStr)
|
err := query.Scan(&token, &createdAtStr, &expiresAtStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not scan token", "err", err)
|
slog.Error("Could not scan token", "err", err)
|
||||||
return nil, types.ErrInternal
|
return nil, types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
createdAt, err = time.Parse(time.RFC3339, createdAtStr)
|
createdAt, err = time.Parse(time.RFC3339, createdAtStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not parse token.created_at", "err", err)
|
slog.Error("Could not parse token.created_at", "err", err)
|
||||||
return nil, types.ErrInternal
|
return nil, types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
expiresAt, err = time.Parse(time.RFC3339, expiresAtStr)
|
expiresAt, err = time.Parse(time.RFC3339, expiresAtStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not parse token.expires_at", "err", err)
|
slog.Error("Could not parse token.expires_at", "err", err)
|
||||||
return nil, types.ErrInternal
|
return nil, types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +315,7 @@ func getTokensFromQuery(query *sql.Rows, userId uuid.UUID, sessionId string, tok
|
|||||||
func (db AuthSqlite) DeleteToken(token string) error {
|
func (db AuthSqlite) DeleteToken(token string) error {
|
||||||
_, err := db.db.Exec("DELETE FROM token WHERE token = ?", token)
|
_, err := db.db.Exec("DELETE FROM token WHERE token = ?", token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not delete token", "err", err)
|
slog.Error("Could not delete token", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -328,7 +327,7 @@ func (db AuthSqlite) InsertSession(session *types.Session) error {
|
|||||||
VALUES (?, ?, ?, ?)`, session.Id, session.UserId, session.CreatedAt, session.ExpiresAt)
|
VALUES (?, ?, ?, ?)`, session.Id, session.UserId, session.CreatedAt, session.ExpiresAt)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not insert new session", "err", err)
|
slog.Error("Could not insert new session", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,7 +347,7 @@ func (db AuthSqlite) GetSession(sessionId string) (*types.Session, error) {
|
|||||||
WHERE session_id = ?`, sessionId).Scan(&userId, &createdAt, &expiresAt)
|
WHERE session_id = ?`, sessionId).Scan(&userId, &createdAt, &expiresAt)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Warn("Session not found", "session-id", sessionId, "err", err)
|
slog.Warn("Session not found", "session-id", sessionId, "err", err)
|
||||||
return nil, ErrNotFound
|
return nil, ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +361,7 @@ func (db AuthSqlite) GetSessions(userId uuid.UUID) ([]*types.Session, error) {
|
|||||||
FROM session
|
FROM session
|
||||||
WHERE user_id = ?`, userId)
|
WHERE user_id = ?`, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not get sessions", "err", err)
|
slog.Error("Could not get sessions", "err", err)
|
||||||
return nil, types.ErrInternal
|
return nil, types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,7 +374,7 @@ func (db AuthSqlite) DeleteOldSessions(userId uuid.UUID) error {
|
|||||||
WHERE expires_at < datetime('now')
|
WHERE expires_at < datetime('now')
|
||||||
AND user_id = ?`, userId)
|
AND user_id = ?`, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not delete old sessions", "err", err)
|
slog.Error("Could not delete old sessions", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -385,7 +384,7 @@ func (db AuthSqlite) DeleteSession(sessionId string) error {
|
|||||||
if sessionId != "" {
|
if sessionId != "" {
|
||||||
_, err := db.db.Exec("DELETE FROM session WHERE session_id = ?", sessionId)
|
_, err := db.db.Exec("DELETE FROM session WHERE session_id = ?", sessionId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not delete session", "err", err)
|
slog.Error("Could not delete session", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package db
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"spend-sparrow/internal/log"
|
"log/slog"
|
||||||
"spend-sparrow/internal/types"
|
"spend-sparrow/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,19 +17,19 @@ func TransformAndLogDbError(module string, r sql.Result, err error) error {
|
|||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return ErrNotFound
|
return ErrNotFound
|
||||||
}
|
}
|
||||||
log.L.Error("database sql", "module", module, "err", err)
|
slog.Error("database sql", "module", module, "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
if r != nil {
|
if r != nil {
|
||||||
rows, err := r.RowsAffected()
|
rows, err := r.RowsAffected()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("database rows affected", "module", module, "err", err)
|
slog.Error("database rows affected", "module", module, "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
if rows == 0 {
|
if rows == 0 {
|
||||||
log.L.Info("row not found", "module", module)
|
slog.Info("row not found", "module", module)
|
||||||
return ErrNotFound
|
return ErrNotFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"spend-sparrow/internal/log"
|
|
||||||
"spend-sparrow/internal/types"
|
|
||||||
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
"spend-sparrow/internal/types"
|
||||||
|
|
||||||
"github.com/golang-migrate/migrate/v4"
|
"github.com/golang-migrate/migrate/v4"
|
||||||
"github.com/golang-migrate/migrate/v4/database/sqlite3"
|
"github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||||
@@ -15,7 +14,7 @@ import (
|
|||||||
type migrationLogger struct{}
|
type migrationLogger struct{}
|
||||||
|
|
||||||
func (l migrationLogger) Printf(format string, v ...any) {
|
func (l migrationLogger) Printf(format string, v ...any) {
|
||||||
log.L.Info(format, v...)
|
slog.Info(format, v...)
|
||||||
}
|
}
|
||||||
func (l migrationLogger) Verbose() bool {
|
func (l migrationLogger) Verbose() bool {
|
||||||
return false
|
return false
|
||||||
@@ -24,7 +23,7 @@ func (l migrationLogger) Verbose() bool {
|
|||||||
func RunMigrations(db *sqlx.DB, pathPrefix string) error {
|
func RunMigrations(db *sqlx.DB, pathPrefix string) error {
|
||||||
driver, err := sqlite3.WithInstance(db.DB, &sqlite3.Config{})
|
driver, err := sqlite3.WithInstance(db.DB, &sqlite3.Config{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not create Migration instance", "err", err)
|
slog.Error("Could not create Migration instance", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,14 +32,14 @@ func RunMigrations(db *sqlx.DB, pathPrefix string) error {
|
|||||||
"",
|
"",
|
||||||
driver)
|
driver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not create migrations instance", "err", err)
|
slog.Error("Could not create migrations instance", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Log = migrationLogger{}
|
m.Log = migrationLogger{}
|
||||||
|
|
||||||
if err = m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
if err = m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
||||||
log.L.Error("Could not run migrations", "err", err)
|
slog.Error("Could not run migrations", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"spend-sparrow/internal/db"
|
"spend-sparrow/internal/db"
|
||||||
"spend-sparrow/internal/handler"
|
"spend-sparrow/internal/handler"
|
||||||
"spend-sparrow/internal/handler/middleware"
|
"spend-sparrow/internal/handler/middleware"
|
||||||
@@ -26,7 +27,27 @@ func Run(ctx context.Context, database *sqlx.DB, migrationsPrefix string, env fu
|
|||||||
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
log.L.Info("Starting server...")
|
otelEnabled := types.IsOtelEnabled(env)
|
||||||
|
if otelEnabled {
|
||||||
|
// use context.Background(), otherwise the shutdown can't be called, as the context is already cancelled
|
||||||
|
otelShutdown, err := setupOTelSDK(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not setup OpenTelemetry SDK: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
// User context.Background(), as the main context is already cancelled
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
err = otelShutdown(ctx)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error shutting down OpenTelemetry SDK", "err", err)
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
|
slog.SetDefault(log.NewLogPropagator())
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("Starting server...")
|
||||||
|
|
||||||
// init server settings
|
// init server settings
|
||||||
serverSettings, err := types.NewSettingsFromEnv(env)
|
serverSettings, err := types.NewSettingsFromEnv(env)
|
||||||
@@ -40,25 +61,6 @@ func Run(ctx context.Context, database *sqlx.DB, migrationsPrefix string, env fu
|
|||||||
return fmt.Errorf("could not run migrations: %w", err)
|
return fmt.Errorf("could not run migrations: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if serverSettings.OtelEnabled {
|
|
||||||
// use context.Background(), otherwise the shutdown can't be called, as the context is already cancelled
|
|
||||||
otelShutdown, err := setupOTelSDK(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not setup OpenTelemetry SDK: %w", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
// User context.Background(), as the main context is already cancelled
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
err = otelShutdown(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.L.Error("error shutting down OpenTelemetry SDK", "err", err)
|
|
||||||
}
|
|
||||||
cancel()
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.InitOtelLogger()
|
|
||||||
}
|
|
||||||
|
|
||||||
// init server
|
// init server
|
||||||
httpServer := &http.Server{
|
httpServer := &http.Server{
|
||||||
Addr: ":" + serverSettings.Port,
|
Addr: ":" + serverSettings.Port,
|
||||||
@@ -77,9 +79,9 @@ func Run(ctx context.Context, database *sqlx.DB, migrationsPrefix string, env fu
|
|||||||
}
|
}
|
||||||
|
|
||||||
func startServer(s *http.Server) {
|
func startServer(s *http.Server) {
|
||||||
log.L.Info("Starting server", "addr", s.Addr)
|
slog.Info("Starting server", "addr", s.Addr)
|
||||||
if err := s.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err := s.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
log.L.Error("error listening and serving", "err", err)
|
slog.Error("error listening and serving", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,9 +96,9 @@ func shutdownServer(s *http.Server, ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second)
|
shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := s.Shutdown(shutdownCtx); err != nil {
|
if err := s.Shutdown(shutdownCtx); err != nil {
|
||||||
log.L.Error("error shutting down http server", "err", err)
|
slog.Error("error shutting down http server", "err", err)
|
||||||
} else {
|
} else {
|
||||||
log.L.Info("Gracefully stopped http server", "addr", s.Addr)
|
slog.Info("Gracefully stopped http server", "addr", s.Addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"spend-sparrow/internal/handler/middleware"
|
"spend-sparrow/internal/handler/middleware"
|
||||||
"spend-sparrow/internal/log"
|
|
||||||
"spend-sparrow/internal/service"
|
"spend-sparrow/internal/service"
|
||||||
"spend-sparrow/internal/template/auth"
|
"spend-sparrow/internal/template/auth"
|
||||||
"spend-sparrow/internal/types"
|
"spend-sparrow/internal/types"
|
||||||
"spend-sparrow/internal/utils"
|
"spend-sparrow/internal/utils"
|
||||||
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -168,7 +167,7 @@ func (handler AuthImpl) handleVerifyResendComp() http.HandlerFunc {
|
|||||||
|
|
||||||
_, err := w.Write([]byte("<p class=\"mt-8\">Verification email sent</p>"))
|
_, err := w.Write([]byte("<p class=\"mt-8\">Verification email sent</p>"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not write response", "err", err)
|
slog.Error("Could not write response", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,13 +202,13 @@ func (handler AuthImpl) handleSignUp() http.HandlerFunc {
|
|||||||
var password = r.FormValue("password")
|
var password = r.FormValue("password")
|
||||||
|
|
||||||
_, err := utils.WaitMinimumTime(securityWaitDuration, func() (any, error) {
|
_, err := utils.WaitMinimumTime(securityWaitDuration, func() (any, error) {
|
||||||
log.L.Info("signing up", "email", email)
|
slog.Info("signing up", "email", email)
|
||||||
user, err := handler.service.SignUp(email, password)
|
user, err := handler.service.SignUp(email, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.L.Info("Sending verification email", "to", user.Email)
|
slog.Info("Sending verification email", "to", user.Email)
|
||||||
go handler.service.SendVerificationMail(user.Id, user.Email)
|
go handler.service.SendVerificationMail(user.Id, user.Email)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
})
|
})
|
||||||
@@ -389,7 +388,7 @@ func (handler AuthImpl) handleForgotPasswordResponseComp() http.HandlerFunc {
|
|||||||
|
|
||||||
pageUrl, err := url.Parse(r.Header.Get("Hx-Current-Url"))
|
pageUrl, err := url.Parse(r.Header.Get("Hx-Current-Url"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not get current URL", "err", err)
|
slog.Error("Could not get current URL", "err", err)
|
||||||
utils.TriggerToastWithStatus(w, r, "error", "Internal Server Error", http.StatusInternalServerError)
|
utils.TriggerToastWithStatus(w, r, "error", "Internal Server Error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"spend-sparrow/internal/log"
|
|
||||||
"spend-sparrow/internal/service"
|
"spend-sparrow/internal/service"
|
||||||
"spend-sparrow/internal/types"
|
"spend-sparrow/internal/types"
|
||||||
"spend-sparrow/internal/utils"
|
"spend-sparrow/internal/utils"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type csrfResponseWriter struct {
|
type csrfResponseWriter struct {
|
||||||
@@ -46,7 +45,7 @@ func CrossSiteRequestForgery(auth service.Auth) func(http.Handler) http.Handler
|
|||||||
csrfToken := r.Header.Get("Csrf-Token")
|
csrfToken := r.Header.Get("Csrf-Token")
|
||||||
|
|
||||||
if session == nil || csrfToken == "" || !auth.IsCsrfTokenValid(csrfToken, session.Id) {
|
if session == nil || csrfToken == "" || !auth.IsCsrfTokenValid(csrfToken, session.Id) {
|
||||||
log.L.Info("CSRF-Token not correct", "token", csrfToken)
|
slog.Info("CSRF-Token not correct", "token", csrfToken)
|
||||||
if r.Header.Get("Hx-Request") == "true" {
|
if r.Header.Get("Hx-Request") == "true" {
|
||||||
utils.TriggerToastWithStatus(w, r, "error", "CSRF-Token not correct", http.StatusBadRequest)
|
utils.TriggerToastWithStatus(w, r, "error", "CSRF-Token not correct", http.StatusBadRequest)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ import (
|
|||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"spend-sparrow/internal/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type gzipResponseWriter struct {
|
type gzipResponseWriter struct {
|
||||||
@@ -34,7 +33,7 @@ func Gzip(next http.Handler) http.Handler {
|
|||||||
|
|
||||||
err := gz.Close()
|
err := gz.Close()
|
||||||
if err != nil && !errors.Is(err, http.ErrBodyNotAllowed) {
|
if err != nil && !errors.Is(err, http.ErrBodyNotAllowed) {
|
||||||
log.L.Error("Gzip: could not close Writer", "err", err)
|
slog.Error("Gzip: could not close Writer", "err", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"spend-sparrow/internal/log"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,15 +20,13 @@ func Log(next http.Handler) http.Handler {
|
|||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
log.L.Info("request pattern", "pattern", r.Pattern)
|
|
||||||
|
|
||||||
wrapped := &WrappedWriter{
|
wrapped := &WrappedWriter{
|
||||||
ResponseWriter: w,
|
ResponseWriter: w,
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
}
|
}
|
||||||
next.ServeHTTP(wrapped, r)
|
next.ServeHTTP(wrapped, r)
|
||||||
|
|
||||||
log.L.Info("request",
|
slog.Info("request",
|
||||||
"remoteAddr", r.RemoteAddr,
|
"remoteAddr", r.RemoteAddr,
|
||||||
"status", wrapped.StatusCode,
|
"status", wrapped.StatusCode,
|
||||||
"method", r.Method,
|
"method", r.Method,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package middleware
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"spend-sparrow/internal/types"
|
"spend-sparrow/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"spend-sparrow/internal/log"
|
|
||||||
"spend-sparrow/internal/template"
|
"spend-sparrow/internal/template"
|
||||||
"spend-sparrow/internal/template/auth"
|
"spend-sparrow/internal/template/auth"
|
||||||
"spend-sparrow/internal/types"
|
"spend-sparrow/internal/types"
|
||||||
@@ -22,7 +22,7 @@ func (render *Render) RenderWithStatus(r *http.Request, w http.ResponseWriter, c
|
|||||||
w.WriteHeader(status)
|
w.WriteHeader(status)
|
||||||
err := comp.Render(r.Context(), w)
|
err := comp.Render(r.Context(), w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Failed to render layout", "err", err)
|
slog.Error("Failed to render layout", "err", err)
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,8 +105,6 @@ func (h TransactionImpl) handleTransactionItemComp() http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// log.L.Info("request", "pattern", r.Pattern, "path", r.URL.Path, "method", r.Method, "path", r.URL.Path)
|
|
||||||
|
|
||||||
accounts, err := h.account.GetAll(user)
|
accounts, err := h.account.GetAll(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError(w, r, err)
|
handleError(w, r, err)
|
||||||
|
|||||||
@@ -1,14 +1,50 @@
|
|||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
// "go.opentelemetry.io/contrib/bridges/otelslog".
|
"os"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/contrib/bridges/otelslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func NewLogPropagator() *slog.Logger {
|
||||||
L = slog.New(slog.Default().Handler())
|
|
||||||
)
|
|
||||||
|
|
||||||
func InitOtelLogger() {
|
console := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{})
|
||||||
// L = otelslog.NewLogger("spend-sparrow")
|
otel := otelslog.NewHandler("spend-sparrow")
|
||||||
|
return slog.New(&logHandler{console, otel})
|
||||||
|
}
|
||||||
|
|
||||||
|
type logHandler struct {
|
||||||
|
console slog.Handler
|
||||||
|
otel slog.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabled implements slog.Handler.
|
||||||
|
func (l *logHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||||
|
return l.console.Enabled(ctx, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle implements slog.Handler.
|
||||||
|
func (l *logHandler) Handle(ctx context.Context, rec slog.Record) error {
|
||||||
|
if err := l.console.Handle(ctx, rec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return l.otel.Handle(ctx, rec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAttrs implements slog.Handler.
|
||||||
|
func (l *logHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
|
return &logHandler{
|
||||||
|
console: l.console.WithAttrs(attrs),
|
||||||
|
otel: l.otel.WithAttrs(attrs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithGroup implements slog.Handler.
|
||||||
|
func (l *logHandler) WithGroup(name string) slog.Handler {
|
||||||
|
return &logHandler{
|
||||||
|
console: l.console.WithGroup(name),
|
||||||
|
otel: l.otel.WithGroup(name),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
|
||||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
|
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
|
||||||
// "go.opentelemetry.io/otel/exporters/stdout/stdoutlog".
|
|
||||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||||
// "go.opentelemetry.io/otel/log/global".
|
"go.opentelemetry.io/otel/log/global"
|
||||||
"go.opentelemetry.io/otel/propagation"
|
"go.opentelemetry.io/otel/propagation"
|
||||||
// "go.opentelemetry.io/otel/sdk/log".
|
"go.opentelemetry.io/otel/sdk/log"
|
||||||
"go.opentelemetry.io/otel/sdk/metric"
|
"go.opentelemetry.io/otel/sdk/metric"
|
||||||
"go.opentelemetry.io/otel/sdk/trace"
|
"go.opentelemetry.io/otel/sdk/trace"
|
||||||
)
|
)
|
||||||
@@ -66,13 +66,13 @@ func setupOTelSDK(ctx context.Context) (func(context.Context) error, error) {
|
|||||||
otel.SetMeterProvider(meterProvider)
|
otel.SetMeterProvider(meterProvider)
|
||||||
|
|
||||||
// Set up logger provider.
|
// Set up logger provider.
|
||||||
// loggerProvider, err := newLoggerProvider()
|
loggerProvider, err := newLoggerProvider(ctx)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// handleErr(err)
|
handleErr(ctx, err)
|
||||||
// return
|
return nil, err
|
||||||
// }
|
}
|
||||||
// shutdownFuncs = append(shutdownFuncs, loggerProvider.Shutdown)
|
shutdownFuncs = append(shutdownFuncs, loggerProvider.Shutdown)
|
||||||
// global.SetLoggerProvider(loggerProvider)
|
global.SetLoggerProvider(loggerProvider)
|
||||||
|
|
||||||
return shutdown, nil
|
return shutdown, nil
|
||||||
}
|
}
|
||||||
@@ -110,14 +110,17 @@ func newMeterProvider(ctx context.Context) (*metric.MeterProvider, error) {
|
|||||||
exp, metric.WithInterval(15*time.Second)))), nil
|
exp, metric.WithInterval(15*time.Second)))), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// func newLoggerProvider() (*log.LoggerProvider, error) {
|
func newLoggerProvider(ctx context.Context) (*log.LoggerProvider, error) {
|
||||||
// logExporter, err := stdoutlog.New()
|
logExporter, err := otlploggrpc.New(
|
||||||
// if err != nil {
|
ctx,
|
||||||
// return nil, err
|
otlploggrpc.WithInsecure(),
|
||||||
// }
|
otlploggrpc.WithEndpoint(otelEndpoint))
|
||||||
//
|
if err != nil {
|
||||||
// loggerProvider := log.NewLoggerProvider(
|
return nil, err
|
||||||
// log.WithProcessor(log.NewBatchProcessor(logExporter)),
|
}
|
||||||
// )
|
|
||||||
// return loggerProvider, nil
|
loggerProvider := log.NewLoggerProvider(
|
||||||
// }
|
log.WithProcessor(log.NewBatchProcessor(logExporter)),
|
||||||
|
)
|
||||||
|
return loggerProvider, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package service
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"spend-sparrow/internal/db"
|
"spend-sparrow/internal/db"
|
||||||
"spend-sparrow/internal/log"
|
|
||||||
"spend-sparrow/internal/types"
|
"spend-sparrow/internal/types"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -85,7 +85,7 @@ func (s AccountImpl) UpdateName(user *types.User, id string, name string) (*type
|
|||||||
}
|
}
|
||||||
uuid, err := uuid.Parse(id)
|
uuid, err := uuid.Parse(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("account update", "err", err)
|
slog.Error("account update", "err", err)
|
||||||
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ func (s AccountImpl) Get(user *types.User, id string) (*types.Account, error) {
|
|||||||
}
|
}
|
||||||
uuid, err := uuid.Parse(id)
|
uuid, err := uuid.Parse(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("account get", "err", err)
|
slog.Error("account get", "err", err)
|
||||||
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ func (s AccountImpl) Get(user *types.User, id string) (*types.Account, error) {
|
|||||||
SELECT * FROM account WHERE user_id = ? AND id = ?`, user.Id, uuid)
|
SELECT * FROM account WHERE user_id = ? AND id = ?`, user.Id, uuid)
|
||||||
err = db.TransformAndLogDbError("account Get", nil, err)
|
err = db.TransformAndLogDbError("account Get", nil, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("account get", "err", err)
|
slog.Error("account get", "err", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ func (s AccountImpl) Delete(user *types.User, id string) error {
|
|||||||
}
|
}
|
||||||
uuid, err := uuid.Parse(id)
|
uuid, err := uuid.Parse(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("account delete", "err", err)
|
slog.Error("account delete", "err", err)
|
||||||
return fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
return fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"errors"
|
"errors"
|
||||||
|
"log/slog"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"spend-sparrow/internal/db"
|
"spend-sparrow/internal/db"
|
||||||
"spend-sparrow/internal/log"
|
|
||||||
mailTemplate "spend-sparrow/internal/template/mail"
|
mailTemplate "spend-sparrow/internal/template/mail"
|
||||||
"spend-sparrow/internal/types"
|
"spend-sparrow/internal/types"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -125,7 +125,7 @@ func (service AuthImpl) SignInAnonymous() (*types.Session, error) {
|
|||||||
return nil, types.ErrInternal
|
return nil, types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
log.L.Info("anonymous session created", "session-id", session.Id)
|
slog.Info("anonymous session created", "session-id", session.Id)
|
||||||
|
|
||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
@@ -201,7 +201,7 @@ func (service AuthImpl) SendVerificationMail(userId uuid.UUID, email string) {
|
|||||||
var w strings.Builder
|
var w strings.Builder
|
||||||
err = mailTemplate.Register(service.serverSettings.BaseUrl, token.Token).Render(context.Background(), &w)
|
err = mailTemplate.Register(service.serverSettings.BaseUrl, token.Token).Render(context.Background(), &w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not render welcome email", "err", err)
|
slog.Error("Could not render welcome email", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +340,7 @@ func (service AuthImpl) SendForgotPasswordMail(email string) error {
|
|||||||
var mail strings.Builder
|
var mail strings.Builder
|
||||||
err = mailTemplate.ResetPassword(service.serverSettings.BaseUrl, token.Token).Render(context.Background(), &mail)
|
err = mailTemplate.ResetPassword(service.serverSettings.BaseUrl, token.Token).Render(context.Background(), &mail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not render reset password email", "err", err)
|
slog.Error("Could not render reset password email", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
service.mail.SendMail(email, "Reset Password", mail.String())
|
service.mail.SendMail(email, "Reset Password", mail.String())
|
||||||
@@ -370,7 +370,7 @@ func (service AuthImpl) ForgotPassword(tokenStr string, newPass string) error {
|
|||||||
|
|
||||||
user, err := service.db.GetUser(token.UserId)
|
user, err := service.db.GetUser(token.UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not get user from token", "err", err)
|
slog.Error("Could not get user from token", "err", err)
|
||||||
return types.ErrInternal
|
return types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,7 +440,7 @@ func (service AuthImpl) GetCsrfToken(session *types.Session) (string, error) {
|
|||||||
return "", types.ErrInternal
|
return "", types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
log.L.Info("CSRF-Token created", "token", tokenStr)
|
slog.Info("CSRF-Token created", "token", tokenStr)
|
||||||
|
|
||||||
return tokenStr, nil
|
return tokenStr, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
"spend-sparrow/internal/log"
|
|
||||||
"spend-sparrow/internal/types"
|
"spend-sparrow/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,9 +47,9 @@ func (m MailImpl) internalSendMail(to string, subject string, message string) {
|
|||||||
subject,
|
subject,
|
||||||
message)
|
message)
|
||||||
|
|
||||||
log.L.Info("sending mail", "to", to)
|
slog.Info("sending mail", "to", to)
|
||||||
err := smtp.SendMail(s.Host+":"+s.Port, auth, s.FromMail, []string{to}, []byte(msg))
|
err := smtp.SendMail(s.Host+":"+s.Port, auth, s.FromMail, []string{to}, []byte(msg))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Error sending mail", "err", err)
|
slog.Error("Error sending mail", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"spend-sparrow/internal/log"
|
"log/slog"
|
||||||
"spend-sparrow/internal/types"
|
"spend-sparrow/internal/types"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -26,7 +26,7 @@ func (r *RandomImpl) Bytes(size int) ([]byte, error) {
|
|||||||
b := make([]byte, 32)
|
b := make([]byte, 32)
|
||||||
_, err := rand.Read(b)
|
_, err := rand.Read(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Error generating random bytes", "err", err)
|
slog.Error("Error generating random bytes", "err", err)
|
||||||
return []byte{}, types.ErrInternal
|
return []byte{}, types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ func (r *RandomImpl) Bytes(size int) ([]byte, error) {
|
|||||||
func (r *RandomImpl) String(size int) (string, error) {
|
func (r *RandomImpl) String(size int) (string, error) {
|
||||||
bytes, err := r.Bytes(size)
|
bytes, err := r.Bytes(size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Error generating random string", "err", err)
|
slog.Error("Error generating random string", "err", err)
|
||||||
return "", types.ErrInternal
|
return "", types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ func (r *RandomImpl) String(size int) (string, error) {
|
|||||||
func (r *RandomImpl) UUID() (uuid.UUID, error) {
|
func (r *RandomImpl) UUID() (uuid.UUID, error) {
|
||||||
id, err := uuid.NewRandom()
|
id, err := uuid.NewRandom()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Error generating random UUID", "err", err)
|
slog.Error("Error generating random UUID", "err", err)
|
||||||
return uuid.Nil, types.ErrInternal
|
return uuid.Nil, types.ErrInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package service
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"spend-sparrow/internal/db"
|
"spend-sparrow/internal/db"
|
||||||
"spend-sparrow/internal/log"
|
|
||||||
"spend-sparrow/internal/types"
|
"spend-sparrow/internal/types"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -208,7 +208,7 @@ func (s TransactionImpl) Get(user *types.User, id string) (*types.Transaction, e
|
|||||||
}
|
}
|
||||||
uuid, err := uuid.Parse(id)
|
uuid, err := uuid.Parse(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("transaction get", "err", err)
|
slog.Error("transaction get", "err", err)
|
||||||
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@ func (s TransactionImpl) Delete(user *types.User, id string) error {
|
|||||||
}
|
}
|
||||||
uuid, err := uuid.Parse(id)
|
uuid, err := uuid.Parse(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("transaction delete", "err", err)
|
slog.Error("transaction delete", "err", err)
|
||||||
return fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
return fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +362,7 @@ func (s TransactionImpl) RecalculateBalances(user *types.User) error {
|
|||||||
defer func() {
|
defer func() {
|
||||||
err := rows.Close()
|
err := rows.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("transaction RecalculateBalances", "err", err)
|
slog.Error("transaction RecalculateBalances", "err", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -455,7 +455,7 @@ func (s TransactionImpl) validateAndEnrichTransaction(tx *sqlx.Tx, oldTransactio
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if rowCount == 0 {
|
if rowCount == 0 {
|
||||||
log.L.Error("transaction validate", "err", err)
|
slog.Error("transaction validate", "err", err)
|
||||||
return nil, fmt.Errorf("account not found: %w", ErrBadRequest)
|
return nil, fmt.Errorf("account not found: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package service
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"math"
|
"math"
|
||||||
"spend-sparrow/internal/db"
|
"spend-sparrow/internal/db"
|
||||||
"spend-sparrow/internal/log"
|
|
||||||
"spend-sparrow/internal/types"
|
"spend-sparrow/internal/types"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@@ -92,7 +92,7 @@ func (s TransactionRecurringImpl) Update(
|
|||||||
}
|
}
|
||||||
uuid, err := uuid.Parse(input.Id)
|
uuid, err := uuid.Parse(input.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("transactionRecurring update", "err", err)
|
slog.Error("transactionRecurring update", "err", err)
|
||||||
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ func (s TransactionRecurringImpl) GetAllByAccount(user *types.User, accountId st
|
|||||||
|
|
||||||
accountUuid, err := uuid.Parse(accountId)
|
accountUuid, err := uuid.Parse(accountId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("transactionRecurring GetAllByAccount", "err", err)
|
slog.Error("transactionRecurring GetAllByAccount", "err", err)
|
||||||
return nil, fmt.Errorf("could not parse accountId: %w", ErrBadRequest)
|
return nil, fmt.Errorf("could not parse accountId: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +230,7 @@ func (s TransactionRecurringImpl) GetAllByTreasureChest(
|
|||||||
|
|
||||||
treasureChestUuid, err := uuid.Parse(treasureChestId)
|
treasureChestUuid, err := uuid.Parse(treasureChestId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("transactionRecurring GetAllByTreasureChest", "err", err)
|
slog.Error("transactionRecurring GetAllByTreasureChest", "err", err)
|
||||||
return nil, fmt.Errorf("could not parse treasureChestId: %w", ErrBadRequest)
|
return nil, fmt.Errorf("could not parse treasureChestId: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +281,7 @@ func (s TransactionRecurringImpl) Delete(user *types.User, id string) error {
|
|||||||
}
|
}
|
||||||
uuid, err := uuid.Parse(id)
|
uuid, err := uuid.Parse(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("transactionRecurring delete", "err", err)
|
slog.Error("transactionRecurring delete", "err", err)
|
||||||
return fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
return fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,7 +413,7 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring(
|
|||||||
if input.AccountId != "" {
|
if input.AccountId != "" {
|
||||||
temp, err := uuid.Parse(input.AccountId)
|
temp, err := uuid.Parse(input.AccountId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("transactionRecurring validate", "err", err)
|
slog.Error("transactionRecurring validate", "err", err)
|
||||||
return nil, fmt.Errorf("could not parse accountId: %w", ErrBadRequest)
|
return nil, fmt.Errorf("could not parse accountId: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
accountUuid = &temp
|
accountUuid = &temp
|
||||||
@@ -423,7 +423,7 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if rowCount == 0 {
|
if rowCount == 0 {
|
||||||
log.L.Error("transactionRecurring validate", "err", err)
|
slog.Error("transactionRecurring validate", "err", err)
|
||||||
return nil, fmt.Errorf("account not found: %w", ErrBadRequest)
|
return nil, fmt.Errorf("account not found: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,7 +433,7 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring(
|
|||||||
if input.TreasureChestId != "" {
|
if input.TreasureChestId != "" {
|
||||||
temp, err := uuid.Parse(input.TreasureChestId)
|
temp, err := uuid.Parse(input.TreasureChestId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("transactionRecurring validate", "err", err)
|
slog.Error("transactionRecurring validate", "err", err)
|
||||||
return nil, fmt.Errorf("could not parse treasureChestId: %w", ErrBadRequest)
|
return nil, fmt.Errorf("could not parse treasureChestId: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
treasureChestUuid = &temp
|
treasureChestUuid = &temp
|
||||||
@@ -453,17 +453,17 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !hasAccount && !hasTreasureChest {
|
if !hasAccount && !hasTreasureChest {
|
||||||
log.L.Error("transactionRecurring validate", "err", err)
|
slog.Error("transactionRecurring validate", "err", err)
|
||||||
return nil, fmt.Errorf("either account or treasure chest is required: %w", ErrBadRequest)
|
return nil, fmt.Errorf("either account or treasure chest is required: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
if hasAccount && hasTreasureChest {
|
if hasAccount && hasTreasureChest {
|
||||||
log.L.Error("transactionRecurring validate", "err", err)
|
slog.Error("transactionRecurring validate", "err", err)
|
||||||
return nil, fmt.Errorf("either account or treasure chest is required, not both: %w", ErrBadRequest)
|
return nil, fmt.Errorf("either account or treasure chest is required, not both: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
valueFloat, err := strconv.ParseFloat(input.Value, 64)
|
valueFloat, err := strconv.ParseFloat(input.Value, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("transactionRecurring validate", "err", err)
|
slog.Error("transactionRecurring validate", "err", err)
|
||||||
return nil, fmt.Errorf("could not parse value: %w", ErrBadRequest)
|
return nil, fmt.Errorf("could not parse value: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
value := int64(math.Round(valueFloat * DECIMALS_MULTIPLIER))
|
value := int64(math.Round(valueFloat * DECIMALS_MULTIPLIER))
|
||||||
@@ -482,18 +482,18 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring(
|
|||||||
}
|
}
|
||||||
intervalMonths, err = strconv.ParseInt(input.IntervalMonths, 10, 0)
|
intervalMonths, err = strconv.ParseInt(input.IntervalMonths, 10, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("transactionRecurring validate", "err", err)
|
slog.Error("transactionRecurring validate", "err", err)
|
||||||
return nil, fmt.Errorf("could not parse intervalMonths: %w", ErrBadRequest)
|
return nil, fmt.Errorf("could not parse intervalMonths: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
if intervalMonths < 1 {
|
if intervalMonths < 1 {
|
||||||
log.L.Error("transactionRecurring validate", "err", err)
|
slog.Error("transactionRecurring validate", "err", err)
|
||||||
return nil, fmt.Errorf("intervalMonths needs to be greater than 0: %w", ErrBadRequest)
|
return nil, fmt.Errorf("intervalMonths needs to be greater than 0: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
var nextExecution *time.Time = nil
|
var nextExecution *time.Time = nil
|
||||||
if input.NextExecution != "" {
|
if input.NextExecution != "" {
|
||||||
t, err := time.Parse("2006-01-02", input.NextExecution)
|
t, err := time.Parse("2006-01-02", input.NextExecution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("transaction validate", "err", err)
|
slog.Error("transaction validate", "err", err)
|
||||||
return nil, fmt.Errorf("could not parse timestamp: %w", ErrBadRequest)
|
return nil, fmt.Errorf("could not parse timestamp: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package service
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"slices"
|
"slices"
|
||||||
"spend-sparrow/internal/db"
|
"spend-sparrow/internal/db"
|
||||||
"spend-sparrow/internal/log"
|
|
||||||
"spend-sparrow/internal/types"
|
"spend-sparrow/internal/types"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -97,7 +97,7 @@ func (s TreasureChestImpl) Update(user *types.User, idStr, parentId, name string
|
|||||||
}
|
}
|
||||||
id, err := uuid.Parse(idStr)
|
id, err := uuid.Parse(idStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("treasureChest update", "err", err)
|
slog.Error("treasureChest update", "err", err)
|
||||||
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ func (s TreasureChestImpl) Get(user *types.User, id string) (*types.TreasureChes
|
|||||||
}
|
}
|
||||||
uuid, err := uuid.Parse(id)
|
uuid, err := uuid.Parse(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("treasureChest get", "err", err)
|
slog.Error("treasureChest get", "err", err)
|
||||||
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +213,7 @@ func (s TreasureChestImpl) Delete(user *types.User, idStr string) error {
|
|||||||
}
|
}
|
||||||
id, err := uuid.Parse(idStr)
|
id, err := uuid.Parse(idStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("treasureChest delete", "err", err)
|
slog.Error("treasureChest delete", "err", err)
|
||||||
return fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
return fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"spend-sparrow/internal/log"
|
"log/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -10,8 +10,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
Port string
|
Port string
|
||||||
OtelEnabled bool
|
|
||||||
|
|
||||||
BaseUrl string
|
BaseUrl string
|
||||||
Environment string
|
Environment string
|
||||||
@@ -41,27 +40,27 @@ func NewSettingsFromEnv(env func(string) string) (*Settings, error) {
|
|||||||
|
|
||||||
settings := &Settings{
|
settings := &Settings{
|
||||||
Port: env("PORT"),
|
Port: env("PORT"),
|
||||||
OtelEnabled: env("OTEL_ENABLED") == "true",
|
|
||||||
BaseUrl: env("BASE_URL"),
|
BaseUrl: env("BASE_URL"),
|
||||||
Environment: env("ENVIRONMENT"),
|
Environment: env("ENVIRONMENT"),
|
||||||
Smtp: smtp,
|
Smtp: smtp,
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.BaseUrl == "" {
|
if settings.BaseUrl == "" {
|
||||||
log.L.Error("BASE_URL must be set")
|
slog.Error("BASE_URL must be set")
|
||||||
return nil, ErrMissingConfig
|
return nil, ErrMissingConfig
|
||||||
}
|
}
|
||||||
if settings.Port == "" {
|
if settings.Port == "" {
|
||||||
log.L.Error("PORT must be set")
|
slog.Error("PORT must be set")
|
||||||
return nil, ErrMissingConfig
|
return nil, ErrMissingConfig
|
||||||
}
|
}
|
||||||
if settings.Environment == "" {
|
if settings.Environment == "" {
|
||||||
log.L.Error("ENVIRONMENT must be set")
|
slog.Error("ENVIRONMENT must be set")
|
||||||
return nil, ErrMissingConfig
|
return nil, ErrMissingConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
log.L.Info("settings read", "BASE_URL", settings.BaseUrl)
|
slog.Info("settings read", "BASE_URL", settings.BaseUrl)
|
||||||
log.L.Info("settings read", "ENVIRONMENT", settings.Environment)
|
slog.Info("settings read", "ENVIRONMENT", settings.Environment)
|
||||||
|
slog.Info("settings read", "ENVIRONMENT", settings.Environment)
|
||||||
|
|
||||||
return settings, nil
|
return settings, nil
|
||||||
}
|
}
|
||||||
@@ -77,29 +76,33 @@ func getSmtpSettings(env func(string) string) (*SmtpSettings, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if smtp.Host == "" {
|
if smtp.Host == "" {
|
||||||
log.L.Error("SMTP_HOST must be set")
|
slog.Error("SMTP_HOST must be set")
|
||||||
return nil, ErrMissingConfig
|
return nil, ErrMissingConfig
|
||||||
}
|
}
|
||||||
if smtp.Port == "" {
|
if smtp.Port == "" {
|
||||||
log.L.Error("SMTP_PORT must be set")
|
slog.Error("SMTP_PORT must be set")
|
||||||
return nil, ErrMissingConfig
|
return nil, ErrMissingConfig
|
||||||
}
|
}
|
||||||
if smtp.User == "" {
|
if smtp.User == "" {
|
||||||
log.L.Error("SMTP_USER must be set")
|
slog.Error("SMTP_USER must be set")
|
||||||
return nil, ErrMissingConfig
|
return nil, ErrMissingConfig
|
||||||
}
|
}
|
||||||
if smtp.Pass == "" {
|
if smtp.Pass == "" {
|
||||||
log.L.Error("SMTP_PASS must be set")
|
slog.Error("SMTP_PASS must be set")
|
||||||
return nil, ErrMissingConfig
|
return nil, ErrMissingConfig
|
||||||
}
|
}
|
||||||
if smtp.FromMail == "" {
|
if smtp.FromMail == "" {
|
||||||
log.L.Error("SMTP_FROM_MAIL must be set")
|
slog.Error("SMTP_FROM_MAIL must be set")
|
||||||
return nil, ErrMissingConfig
|
return nil, ErrMissingConfig
|
||||||
}
|
}
|
||||||
if smtp.FromName == "" {
|
if smtp.FromName == "" {
|
||||||
log.L.Error("SMTP_FROM_NAME must be set")
|
slog.Error("SMTP_FROM_NAME must be set")
|
||||||
return nil, ErrMissingConfig
|
return nil, ErrMissingConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
return &smtp, nil
|
return &smtp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsOtelEnabled(env func(string) string) bool {
|
||||||
|
return env("OTEL_ENABLED") == "true"
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,18 +2,17 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"spend-sparrow/internal/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TriggerToast(w http.ResponseWriter, r *http.Request, class string, message string) {
|
func TriggerToast(w http.ResponseWriter, r *http.Request, class string, message string) {
|
||||||
if IsHtmx(r) {
|
if IsHtmx(r) {
|
||||||
w.Header().Set("Hx-Trigger", fmt.Sprintf(`{"toast": "%v|%v"}`, class, strings.ReplaceAll(message, `"`, `\"`)))
|
w.Header().Set("Hx-Trigger", fmt.Sprintf(`{"toast": "%v|%v"}`, class, strings.ReplaceAll(message, `"`, `\"`)))
|
||||||
} else {
|
} else {
|
||||||
log.L.Error("Trying to trigger toast in non-HTMX request")
|
slog.Error("Trying to trigger toast in non-HTMX request")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
main.go
10
main.go
@@ -2,9 +2,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"spend-sparrow/internal"
|
"spend-sparrow/internal"
|
||||||
"spend-sparrow/internal/log"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
@@ -14,23 +14,23 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
err := godotenv.Load()
|
err := godotenv.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Error loading .env file")
|
slog.Error("Error loading .env file")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err := sqlx.Open("sqlite3", "./data/spend-sparrow.db")
|
db, err := sqlx.Open("sqlite3", "./data/spend-sparrow.db")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.Error("Could not open Database data.db", "err", err)
|
slog.Error("Could not open Database data.db", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err = db.Close(); err != nil {
|
if err = db.Close(); err != nil {
|
||||||
log.L.Error("Database close failed", "err", err)
|
slog.Error("Database close failed", "err", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err = internal.Run(context.Background(), db, "", os.Getenv); err != nil {
|
if err = internal.Run(context.Background(), db, "", os.Getenv); err != nil {
|
||||||
log.L.Error("Error running server", "err", err)
|
slog.Error("Error running server", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user