From 2b79788fbe62813ac8b03dc85a54f0b9e1306d28 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Thu, 18 Jan 2024 13:45:42 -0600 Subject: [PATCH] feat: user defined currencies (#700) * basic currency service for loading at runtime * api endpoint for currencies * sort slice before return * remove currency validation * validate using currency service * implement selecting dynamic currency options * bump go version * fix type definition * specify explicit type * change go versions * proper types for assetId * log/return currency error * make case insensative * use ToUpper instead * feat: adding new currencies (#715) * fix: task swag (#710) Co-authored-by: Quoing * [feat] Adding new currencies --------- Co-authored-by: quoing Co-authored-by: Quoing Co-authored-by: Bradley <41597815+userbradley@users.noreply.github.com> * remove ts file and consoldate new values into json * move flag to options namespace * add env config for currencies * basic documentaion * remove in sync test --------- Co-authored-by: quoing Co-authored-by: Quoing Co-authored-by: Bradley <41597815+userbradley@users.noreply.github.com> Former-commit-id: c4b923847a1b695dcddd1b346adcccfd3f3ce706 --- .devcontainer/devcontainer.json | 2 +- .github/workflows/partial-backend.yaml | 2 +- .github/workflows/partial-frontend.yaml | 2 +- backend/app/api/handlers/v1/controller.go | 16 + backend/app/api/handlers/v1/v1_ctrl_group.go | 9 + backend/app/api/main.go | 30 + backend/app/api/routes.go | 2 + backend/app/api/static/docs/docs.go | 81 ++- backend/app/api/static/docs/swagger.json | 81 ++- backend/app/api/static/docs/swagger.yaml | 53 +- backend/go.sum | 8 + .../internal/core/currencies/currencies.go | 99 +++ .../internal/core/currencies/currencies.json | 626 ++++++++++++++++++ backend/internal/core/services/all.go | 18 + backend/internal/core/services/main_test.go | 8 +- backend/internal/data/ent/group.go | 6 +- backend/internal/data/ent/group/group.go | 58 +- backend/internal/data/ent/group/where.go | 58 +- backend/internal/data/ent/group_create.go | 17 +- backend/internal/data/ent/group_update.go | 34 +- backend/internal/data/ent/migrate/schema.go | 2 +- backend/internal/data/ent/mutation.go | 12 +- backend/internal/data/ent/runtime.go | 4 + backend/internal/data/ent/schema/group.go | 37 +- backend/internal/data/ent/schema/user.go | 1 - backend/internal/data/repo/repo_group.go | 6 +- backend/internal/data/repo/repo_items.go | 2 +- backend/internal/sys/config/conf.go | 17 +- backend/internal/sys/validate/errors.go | 6 +- docs/docs/api/openapi-2.0.json | 81 ++- docs/docs/quick-start.md | 2 + docs/docs/tips-tricks.md | 24 +- frontend/lib/api/__test__/user/group.test.ts | 15 - frontend/lib/api/__test__/user/items.test.ts | 2 +- frontend/lib/api/classes/group.ts | 14 +- frontend/lib/api/public.ts | 4 +- frontend/lib/api/types/data-contracts.ts | 19 +- frontend/lib/data/currency.ts | 73 -- frontend/pages/profile.vue | 23 +- 39 files changed, 1226 insertions(+), 328 deletions(-) create mode 100644 backend/internal/core/currencies/currencies.go create mode 100644 backend/internal/core/currencies/currencies.json delete mode 100644 frontend/lib/data/currency.ts diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2a2754a..87730fb 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -35,6 +35,6 @@ // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "node", "features": { - "golang": "1.20" + "golang": "1.21" } } diff --git a/.github/workflows/partial-backend.yaml b/.github/workflows/partial-backend.yaml index c027ff4..0af2eaa 100644 --- a/.github/workflows/partial-backend.yaml +++ b/.github/workflows/partial-backend.yaml @@ -12,7 +12,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.20" + go-version: "1.21" - name: Install Task uses: arduino/setup-task@v1 diff --git a/.github/workflows/partial-frontend.yaml b/.github/workflows/partial-frontend.yaml index c26c5f4..cdf51b7 100644 --- a/.github/workflows/partial-frontend.yaml +++ b/.github/workflows/partial-frontend.yaml @@ -46,7 +46,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.20" + go-version: "1.21" - uses: actions/setup-node@v4 with: diff --git a/backend/app/api/handlers/v1/controller.go b/backend/app/api/handlers/v1/controller.go index 34a7542..25f6aab 100644 --- a/backend/app/api/handlers/v1/controller.go +++ b/backend/app/api/handlers/v1/controller.go @@ -127,6 +127,22 @@ func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) errchain.Hand } } +// HandleCurrency godoc +// +// @Summary Currency +// @Tags Base +// @Produce json +// @Success 200 {object} currencies.Currency +// @Router /v1/currency [GET] +func (ctrl *V1Controller) HandleCurrency() errchain.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) error { + // Set Cache for 10 Minutes + w.Header().Set("Cache-Control", "max-age=600") + + return server.JSON(w, http.StatusOK, ctrl.svc.Currencies.Slice()) + } +} + func (ctrl *V1Controller) HandleCacheWS() errchain.HandlerFunc { m := melody.New() diff --git a/backend/app/api/handlers/v1/v1_ctrl_group.go b/backend/app/api/handlers/v1/v1_ctrl_group.go index 45a8557..0d26869 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_group.go +++ b/backend/app/api/handlers/v1/v1_ctrl_group.go @@ -6,6 +6,7 @@ import ( "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/data/repo" + "github.com/hay-kot/homebox/backend/internal/sys/validate" "github.com/hay-kot/homebox/backend/internal/web/adapters" "github.com/hay-kot/httpkit/errchain" ) @@ -52,6 +53,14 @@ func (ctrl *V1Controller) HandleGroupGet() errchain.HandlerFunc { func (ctrl *V1Controller) HandleGroupUpdate() errchain.HandlerFunc { fn := func(r *http.Request, body repo.GroupUpdate) (repo.Group, error) { auth := services.NewContext(r.Context()) + + ok := ctrl.svc.Currencies.IsSupported(body.Currency) + if !ok { + return repo.Group{}, validate.NewFieldErrors( + validate.NewFieldError("currency", "currency '" + body.Currency + "' is not supported"), + ) + } + return ctrl.svc.Group.UpdateGroup(auth, body) } diff --git a/backend/app/api/main.go b/backend/app/api/main.go index 2c27017..1389285 100644 --- a/backend/app/api/main.go +++ b/backend/app/api/main.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "context" "fmt" "net/http" @@ -13,6 +14,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" + "github.com/hay-kot/homebox/backend/internal/core/currencies" "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus" "github.com/hay-kot/homebox/backend/internal/data/ent" @@ -126,12 +128,40 @@ func run(cfg *config.Config) error { return err } + collectFuncs := []currencies.CollectorFunc{ + currencies.CollectDefaults(), + } + + if cfg.Options.CurrencyConfig != "" { + log.Info(). + Str("path", cfg.Options.CurrencyConfig). + Msg("loading currency config file") + + content, err := os.ReadFile(cfg.Options.CurrencyConfig) + if err != nil { + log.Fatal(). + Err(err). + Str("path", cfg.Options.CurrencyConfig). + Msg("failed to read currency config file") + } + + collectFuncs = append(collectFuncs, currencies.CollectJSON(bytes.NewReader(content))) + } + + currencies, err := currencies.CollectionCurrencies(collectFuncs...) + if err != nil { + log.Fatal(). + Err(err). + Msg("failed to collect currencies") + } + app.bus = eventbus.New() app.db = c app.repos = repo.New(c, app.bus, cfg.Storage.Data) app.services = services.New( app.repos, services.WithAutoIncrementAssetID(cfg.Options.AutoIncrementAssetID), + services.WithCurrencies(currencies), ) // ========================================================================= diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go index 62a260b..7ffc313 100644 --- a/backend/app/api/routes.go +++ b/backend/app/api/routes.go @@ -64,6 +64,8 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR BuildTime: buildTime, }))) + r.Get(v1Base("/currencies"), chain.ToHandlerFunc(v1Ctrl.HandleCurrency())) + providers := []v1.AuthProvider{ providers.NewLocalProvider(a.services.User), } diff --git a/backend/app/api/static/docs/docs.go b/backend/app/api/static/docs/docs.go index bbbe376..d5a8b71 100644 --- a/backend/app/api/static/docs/docs.go +++ b/backend/app/api/static/docs/docs.go @@ -150,6 +150,25 @@ const docTemplate = `{ } } }, + "/v1/currency": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Base" + ], + "summary": "Currency", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/currencies.Currency" + } + } + } + } + }, "/v1/groups": { "get": { "security": [ @@ -410,6 +429,16 @@ const docTemplate = `{ "description": "location Ids", "name": "locations", "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "parent Ids", + "name": "parentIds", + "in": "query" } ], "responses": { @@ -1574,7 +1603,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/v1.ApiSummary" + "$ref": "#/definitions/v1.APISummary" } } } @@ -1645,6 +1674,12 @@ const docTemplate = `{ "schema": { "$ref": "#/definitions/v1.LoginForm" } + }, + { + "type": "string", + "description": "auth provider", + "name": "provider", + "in": "query" } ], "responses": { @@ -1823,6 +1858,23 @@ const docTemplate = `{ } }, "definitions": { + "currencies.Currency": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "local": { + "type": "string" + }, + "name": { + "type": "string" + }, + "symbol": { + "type": "string" + } + } + }, "repo.DocumentOut": { "type": "object", "properties": { @@ -1998,12 +2050,6 @@ const docTemplate = `{ "$ref": "#/definitions/repo.ItemAttachment" } }, - "children": { - "type": "array", - "items": { - "$ref": "#/definitions/repo.ItemSummary" - } - }, "createdAt": { "type": "string" }, @@ -2181,8 +2227,7 @@ const docTemplate = `{ "type": "boolean" }, "assetId": { - "type": "string", - "example": "0" + "type": "string" }, "description": { "type": "string" @@ -2736,15 +2781,7 @@ const docTemplate = `{ } } }, - "v1.ActionAmountResult": { - "type": "object", - "properties": { - "completed": { - "type": "integer" - } - } - }, - "v1.ApiSummary": { + "v1.APISummary": { "type": "object", "properties": { "allowRegistration": { @@ -2773,6 +2810,14 @@ const docTemplate = `{ } } }, + "v1.ActionAmountResult": { + "type": "object", + "properties": { + "completed": { + "type": "integer" + } + } + }, "v1.Build": { "type": "object", "properties": { diff --git a/backend/app/api/static/docs/swagger.json b/backend/app/api/static/docs/swagger.json index e2d98fe..2ff1295 100644 --- a/backend/app/api/static/docs/swagger.json +++ b/backend/app/api/static/docs/swagger.json @@ -143,6 +143,25 @@ } } }, + "/v1/currency": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Base" + ], + "summary": "Currency", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/currencies.Currency" + } + } + } + } + }, "/v1/groups": { "get": { "security": [ @@ -403,6 +422,16 @@ "description": "location Ids", "name": "locations", "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "parent Ids", + "name": "parentIds", + "in": "query" } ], "responses": { @@ -1567,7 +1596,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/v1.ApiSummary" + "$ref": "#/definitions/v1.APISummary" } } } @@ -1638,6 +1667,12 @@ "schema": { "$ref": "#/definitions/v1.LoginForm" } + }, + { + "type": "string", + "description": "auth provider", + "name": "provider", + "in": "query" } ], "responses": { @@ -1816,6 +1851,23 @@ } }, "definitions": { + "currencies.Currency": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "local": { + "type": "string" + }, + "name": { + "type": "string" + }, + "symbol": { + "type": "string" + } + } + }, "repo.DocumentOut": { "type": "object", "properties": { @@ -1991,12 +2043,6 @@ "$ref": "#/definitions/repo.ItemAttachment" } }, - "children": { - "type": "array", - "items": { - "$ref": "#/definitions/repo.ItemSummary" - } - }, "createdAt": { "type": "string" }, @@ -2174,8 +2220,7 @@ "type": "boolean" }, "assetId": { - "type": "string", - "example": "0" + "type": "string" }, "description": { "type": "string" @@ -2729,15 +2774,7 @@ } } }, - "v1.ActionAmountResult": { - "type": "object", - "properties": { - "completed": { - "type": "integer" - } - } - }, - "v1.ApiSummary": { + "v1.APISummary": { "type": "object", "properties": { "allowRegistration": { @@ -2766,6 +2803,14 @@ } } }, + "v1.ActionAmountResult": { + "type": "object", + "properties": { + "completed": { + "type": "integer" + } + } + }, "v1.Build": { "type": "object", "properties": { diff --git a/backend/app/api/static/docs/swagger.yaml b/backend/app/api/static/docs/swagger.yaml index 2f16660..509d2cc 100644 --- a/backend/app/api/static/docs/swagger.yaml +++ b/backend/app/api/static/docs/swagger.yaml @@ -1,5 +1,16 @@ basePath: /api definitions: + currencies.Currency: + properties: + code: + type: string + local: + type: string + name: + type: string + symbol: + type: string + type: object repo.DocumentOut: properties: id: @@ -116,10 +127,6 @@ definitions: items: $ref: '#/definitions/repo.ItemAttachment' type: array - children: - items: - $ref: '#/definitions/repo.ItemSummary' - type: array createdAt: type: string description: @@ -238,7 +245,6 @@ definitions: archived: type: boolean assetId: - example: "0" type: string description: type: string @@ -608,12 +614,7 @@ definitions: token: type: string type: object - v1.ActionAmountResult: - properties: - completed: - type: integer - type: object - v1.ApiSummary: + v1.APISummary: properties: allowRegistration: type: boolean @@ -632,6 +633,11 @@ definitions: type: string type: array type: object + v1.ActionAmountResult: + properties: + completed: + type: integer + type: object v1.Build: properties: buildTime: @@ -789,6 +795,18 @@ paths: summary: Get Item by Asset ID tags: - Items + /v1/currency: + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/currencies.Currency' + summary: Currency + tags: + - Base /v1/groups: get: produces: @@ -942,6 +960,13 @@ paths: type: string name: locations type: array + - collectionFormat: multi + description: parent Ids + in: query + items: + type: string + name: parentIds + type: array produces: - application/json responses: @@ -1656,7 +1681,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/v1.ApiSummary' + $ref: '#/definitions/v1.APISummary' summary: Application Info tags: - Base @@ -1699,6 +1724,10 @@ paths: required: true schema: $ref: '#/definitions/v1.LoginForm' + - description: auth provider + in: query + name: provider + type: string produces: - application/json responses: diff --git a/backend/go.sum b/backend/go.sum index fc7cdd2..4cb487e 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -115,6 +115,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI= github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -122,6 +124,8 @@ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTS github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/olahol/melody v1.1.4 h1:RQHfKZkQmDxI0+SLZRNBCn4LiXdqxLKRGSkT8Dyoe/E= github.com/olahol/melody v1.1.4/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= @@ -139,6 +143,10 @@ github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/backend/internal/core/currencies/currencies.go b/backend/internal/core/currencies/currencies.go new file mode 100644 index 0000000..b6a12c0 --- /dev/null +++ b/backend/internal/core/currencies/currencies.go @@ -0,0 +1,99 @@ +// Package currencies provides a shared definition of currencies. This uses a global +// variable to hold the currencies. +package currencies + +import ( + "bytes" + _ "embed" + "encoding/json" + "io" + "slices" + "strings" + "sync" +) + +//go:embed currencies.json +var defaults []byte + +type CollectorFunc func() ([]Currency, error) + +func CollectJSON(reader io.Reader) CollectorFunc { + return func() ([]Currency, error) { + var currencies []Currency + err := json.NewDecoder(reader).Decode(¤cies) + if err != nil { + return nil, err + } + + return currencies, nil + } +} + +func CollectDefaults() CollectorFunc { + return CollectJSON(bytes.NewReader(defaults)) +} + +func CollectionCurrencies(collectors ...CollectorFunc) ([]Currency, error) { + out := make([]Currency, 0, len(collectors)) + for i := range collectors { + c, err := collectors[i]() + if err != nil { + return nil, err + } + + out = append(out, c...) + } + + return out, nil +} + +type Currency struct { + Name string `json:"name"` + Code string `json:"code"` + Local string `json:"local"` + Symbol string `json:"symbol"` +} + +type CurrencyRegistry struct { + mu sync.RWMutex + registry map[string]Currency +} + +func NewCurrencyService(currencies []Currency) *CurrencyRegistry { + registry := make(map[string]Currency, len(currencies)) + for i := range currencies { + registry[currencies[i].Code] = currencies[i] + } + + return &CurrencyRegistry{ + registry: registry, + } +} + +func (cs *CurrencyRegistry) Slice() []Currency { + cs.mu.RLock() + defer cs.mu.RUnlock() + + keys := make([]string, 0, len(cs.registry)) + for key := range cs.registry { + keys = append(keys, key) + } + + slices.Sort(keys) + + out := make([]Currency, 0, len(cs.registry)) + for i := range keys { + out = append(out, cs.registry[keys[i]]) + } + + return out +} + +func (cs *CurrencyRegistry) IsSupported(code string) bool { + upper := strings.ToUpper(code) + + cs.mu.RLock() + defer cs.mu.RUnlock() + _, ok := cs.registry[upper] + return ok +} diff --git a/backend/internal/core/currencies/currencies.json b/backend/internal/core/currencies/currencies.json new file mode 100644 index 0000000..ffd39b4 --- /dev/null +++ b/backend/internal/core/currencies/currencies.json @@ -0,0 +1,626 @@ +[ + { + "code": "AED", + "local": "United Arab Emirates", + "symbol": "د.إ", + "name": "United Arab Emirates Dirham" + }, + { + "code": "AFN", + "local": "Afghanistan", + "symbol": "؋", + "name": "Afghan Afghani" + }, + { + "code": "ALL", + "local": "Albania", + "symbol": "L", + "name": "Albanian Lek" + }, + { + "code": "AMD", + "local": "Armenia", + "symbol": "֏", + "name": "Armenian Dram" + }, + { + "code": "ANG", + "local": "Netherlands Antilles", + "symbol": "ƒ", + "name": "Netherlands Antillean Guilder" + }, + { + "code": "AOA", + "local": "Angola", + "symbol": "Kz", + "name": "Angolan Kwanza" + }, + { + "code": "ARS", + "local": "Argentina", + "symbol": "$", + "name": "Argentine Peso" + }, + { + "code": "AUD", + "local": "Australia", + "symbol": "A$", + "name": "Australian Dollar" + }, + { + "code": "AWG", + "local": "Aruba", + "symbol": "ƒ", + "name": "Aruban Florin" + }, + { + "code": "AZN", + "local": "Azerbaijan", + "symbol": "₼", + "name": "Azerbaijani Manat" + }, + { + "code": "BAM", + "local": "Bosnia and Herzegovina", + "symbol": "KM", + "name": "Bosnia and Herzegovina Convertible Mark" + }, + { + "code": "BBD", + "local": "Barbados", + "symbol": "Bds$", + "name": "Barbadian Dollar" + }, + { + "code": "BDT", + "local": "Bangladesh", + "symbol": "৳", + "name": "Bangladeshi Taka" + }, + { + "code": "BGN", + "local": "Bulgaria", + "symbol": "лв", + "name": "Bulgarian lev" + }, + { + "code": "BHD", + "local": "Bahrain", + "symbol": "ب.د", + "name": "Bahraini Dinar" + }, + { + "code": "BIF", + "local": "Burundi", + "symbol": "FBu", + "name": "Burundian Franc" + }, + { + "code": "BMD", + "local": "Bermuda", + "symbol": "BD$", + "name": "Bermudian Dollar" + }, + { + "code": "BND", + "local": "Brunei", + "symbol": "B$", + "name": "Brunei Dollar" + }, + { + "code": "BOB", + "local": "Bolivia", + "symbol": "Bs.", + "name": "Bolivian Boliviano" + }, + { + "code": "BRL", + "local": "Brazil", + "symbol": "R$", + "name": "Brazilian Real" + }, + { + "code": "BSD", + "local": "Bahamas", + "symbol": "B$", + "name": "Bahamian Dollar" + }, + { + "code": "BTN", + "local": "Bhutan", + "symbol": "Nu.", + "name": "Bhutanese Ngultrum" + }, + { + "code": "BWP", + "local": "Botswana", + "symbol": "P", + "name": "Botswana Pula" + }, + { + "code": "BYN", + "local": "Belarus", + "symbol": "Br", + "name": "Belarusian Ruble" + }, + { + "code": "BZD", + "local": "Belize", + "symbol": "BZ$", + "name": "Belize Dollar" + }, + { + "code": "CAD", + "local": "Canada", + "symbol": "C$", + "name": "Canadian Dollar" + }, + { + "code": "CDF", + "local": "Democratic Republic of the Congo", + "symbol": "FC", + "name": "Congolese Franc" + }, + { + "code": "CHF", + "local": "Switzerland", + "symbol": "CHF", + "name": "Swiss Franc" + }, + { + "code": "CLP", + "local": "Chile", + "symbol": "CL$", + "name": "Chilean Peso" + }, + { + "code": "CNY", + "local": "China", + "symbol": "¥", + "name": "Chinese Yuan" + }, + { + "code": "COP", + "local": "Colombia", + "symbol": "COL$", + "name": "Colombian Peso" + }, + { + "code": "CRC", + "local": "Costa Rica", + "symbol": "₡", + "name": "Costa Rican Colón" + }, + { + "code": "CUP", + "local": "Cuba", + "symbol": "₱", + "name": "Cuban Peso" + }, + { + "code": "CVE", + "local": "Cape Verde", + "symbol": "$", + "name": "Cape Verdean Escudo" + }, + { + "code": "CZK", + "local": "Czech Republic", + "symbol": "Kč", + "name": "Czech Koruna" + }, + { + "code": "DJF", + "local": "Djibouti", + "symbol": "Fdj", + "name": "Djiboutian Franc" + }, + { + "code": "DKK", + "local": "Denmark", + "symbol": "kr", + "name": "Danish Krone" + }, + { + "code": "DOP", + "local": "Dominican Republic", + "symbol": "RD$", + "name": "Dominican Peso" + }, + { + "code": "DZD", + "local": "Algeria", + "symbol": "د.ج", + "name": "Algerian Dinar" + }, + { + "code": "EGP", + "local": "Egypt", + "symbol": "£", + "name": "Egyptian Pound" + }, + { + "code": "ERN", + "local": "Eritrea", + "symbol": "Nfk", + "name": "Eritrean Nakfa" + }, + { + "code": "ETB", + "local": "Ethiopia", + "symbol": "Br", + "name": "Ethiopian Birr" + }, + { + "code": "EUR", + "local": "Eurozone", + "symbol": "€", + "name": "Euro" + }, + { + "code": "FJD", + "local": "Fiji", + "symbol": "FJ$", + "name": "Fijian Dollar" + }, + { + "code": "FKP", + "local": "Falkland Islands", + "symbol": "£", + "name": "Falkland Islands Pound" + }, + { + "code": "FOK", + "local": "Faroe Islands", + "symbol": "kr", + "name": "Faroese Króna" + }, + { + "code": "GBP", + "local": "United Kingdom", + "symbol": "£", + "name": "British Pound Sterling" + }, + { + "code": "GEL", + "local": "Georgia", + "symbol": "₾", + "name": "Georgian Lari" + }, + { + "code": "GGP", + "local": "Guernsey", + "symbol": "£", + "name": "Guernsey Pound" + }, + { + "code": "GHS", + "local": "Ghana", + "symbol": "GH₵", + "name": "Ghanaian Cedi" + }, + { + "code": "GIP", + "local": "Gibraltar", + "symbol": "£", + "name": "Gibraltar Pound" + }, + { + "code": "GMD", + "local": "Gambia", + "symbol": "D", + "name": "Gambian Dalasi" + }, + { + "code": "GNF", + "local": "Guinea", + "symbol": "FG", + "name": "Guinean Franc" + }, + { + "code": "GTQ", + "local": "Guatemala", + "symbol": "Q", + "name": "Guatemalan Quetzal" + }, + { + "code": "GYD", + "local": "Guyana", + "symbol": "GY$", + "name": "Guyanese Dollar" + }, + { + "code": "HKD", + "local": "Hong Kong", + "symbol": "HK$", + "name": "Hong Kong Dollar" + }, + { + "code": "HNL", + "local": "Honduras", + "symbol": "L", + "name": "Honduran Lempira" + }, + { + "code": "HRK", + "local": "Croatia", + "symbol": "kn", + "name": "Croatian Kuna" + }, + { + "code": "HTG", + "local": "Haiti", + "symbol": "G", + "name": "Haitian Gourde" + }, + { + "code": "HUF", + "local": "Hungary", + "symbol": "Ft", + "name": "Hungarian Forint" + }, + { + "code": "IDR", + "local": "Indonesia", + "symbol": "Rp", + "name": "Indonesian Rupiah" + }, + { + "code": "ILS", + "local": "Israel", + "symbol": "₪", + "name": "Israeli New Shekel" + }, + { + "code": "IMP", + "local": "Isle of Man", + "symbol": "£", + "name": "Manx Pound" + }, + { + "code": "INR", + "local": "India", + "symbol": "₹", + "name": "Indian Rupee" + }, + { + "code": "IQD", + "local": "Iraq", + "symbol": "ع.د", + "name": "Iraqi Dinar" + }, + { + "code": "IRR", + "local": "Iran", + "symbol": "﷼", + "name": "Iranian Rial" + }, + { + "code": "ISK", + "local": "Iceland", + "symbol": "kr", + "name": "Icelandic Króna" + }, + { + "code": "JEP", + "local": "Jersey", + "symbol": "£", + "name": "Jersey Pound" + }, + { + "code": "JMD", + "local": "Jamaica", + "symbol": "J$", + "name": "Jamaican Dollar" + }, + { + "code": "JOD", + "local": "Jordan", + "symbol": "د.ا", + "name": "Jordanian Dinar" + }, + { + "code": "JPY", + "local": "Japan", + "symbol": "¥", + "name": "Japanese Yen" + }, + { + "code": "KES", + "local": "Kenya", + "symbol": "KSh", + "name": "Kenyan Shilling" + }, + { + "code": "KGS", + "local": "Kyrgyzstan", + "symbol": "с", + "name": "Kyrgyzstani Som" + }, + { + "code": "KHR", + "local": "Cambodia", + "symbol": "៛", + "name": "Cambodian Riel" + }, + { + "code": "KID", + "local": "Kiribati", + "symbol": "$", + "name": "Kiribati Dollar" + }, + { + "code": "KMF", + "local": "Comoros", + "symbol": "CF", + "name": "Comorian Franc" + }, + { + "code": "KRW", + "local": "South Korea", + "symbol": "₩", + "name": "South Korean Won" + }, + { + "code": "KWD", + "local": "Kuwait", + "symbol": "د.ك", + "name": "Kuwaiti Dinar" + }, + { + "code": "KYD", + "local": "Cayman Islands", + "symbol": "CI$", + "name": "Cayman Islands Dollar" + }, + { + "code": "KZT", + "local": "Kazakhstan", + "symbol": "₸", + "name": "Kazakhstani Tenge" + }, + { + "code": "LAK", + "local": "Laos", + "symbol": "₭", + "name": "Lao Kip" + }, + { + "code": "LBP", + "local": "Lebanon", + "symbol": "ل.ل", + "name": "Lebanese Pound" + }, + { + "code": "LKR", + "local": "Sri Lanka", + "symbol": "₨", + "name": "Sri Lankan Rupee" + }, + { + "code": "LRD", + "local": "Liberia", + "symbol": "L$", + "name": "Liberian Dollar" + }, + { + "code": "LSL", + "local": "Lesotho", + "symbol": "M", + "name": "Lesotho Loti" + }, + { + "code": "LYD", + "local": "Libya", + "symbol": "ل.د", + "name": "Libyan Dinar" + }, + { + "code": "MAD", + "local": "Morocco", + "symbol": "د.م.", + "name": "Moroccan Dirham" + }, + { + "code": "MDL", + "local": "Moldova", + "symbol": "lei", + "name": "Moldovan Leu" + }, + { + "code": "MGA", + "local": "Madagascar", + "symbol": "Ar", + "name": "Malagasy Ariary" + }, + { + "code": "MKD", + "local": "North Macedonia", + "symbol": "ден", + "name": "Macedonian Denar" + }, + { + "code": "MMK", + "local": "Myanmar", + "symbol": "K", + "name": "Myanmar Kyat" + }, + { + "code": "MNT", + "local": "Mongolia", + "symbol": "₮", + "name": "Mongolian Tugrik" + }, + { + "code": "MOP", + "local": "Macau", + "symbol": "MOP$", + "name": "Macanese Pataca" + }, + { + "code": "MRU", + "local": "Mauritania", + "symbol": "UM", + "name": "Mauritanian Ouguiya" + }, + { + "code": "MUR", + "local": "Mauritius", + "symbol": "₨", + "name": "Mauritian Rupee" + }, + { + "code": "MVR", + "local": "Maldives", + "symbol": "Rf", + "name": "Maldivian Rufiyaa" + }, + { + "code": "MWK", + "local": "Malawi", + "symbol": "MK", + "name": "Malawian Kwacha" + }, + { + "code": "MXN", + "local": "Mexico", + "symbol": "Mex$", + "name": "Mexican Peso" + }, + { + "code": "MYR", + "local": "Malaysia", + "symbol": "RM", + "name": "Malaysian Ringgit" + }, + { + "code": "MZN", + "local": "Mozambique", + "symbol": "MT", + "name": "Mozambican Metical" + }, + { + "code": "NAD", + "local": "Namibia", + "symbol": "N$", + "name": "Namibian Dollar" + }, + { + "code": "NGN", + "local": "Nigeria", + "symbol": "₦", + "name": "Nigerian Naira" + }, + { + "code": "NIO", + "local": "Nicaragua", + "symbol": "C$", + "name": "Nicaraguan Córdoba" + }, + { + "code": "UAH", + "local": "Ukraine", + "symbol": "₴", + "name": "Ukrainian Hryvnia" + } +] diff --git a/backend/internal/core/services/all.go b/backend/internal/core/services/all.go index 9b5e127..3c03a4e 100644 --- a/backend/internal/core/services/all.go +++ b/backend/internal/core/services/all.go @@ -2,6 +2,7 @@ package services import ( + "github.com/hay-kot/homebox/backend/internal/core/currencies" "github.com/hay-kot/homebox/backend/internal/data/repo" ) @@ -10,12 +11,14 @@ type AllServices struct { Group *GroupService Items *ItemService BackgroundService *BackgroundService + Currencies *currencies.CurrencyRegistry } type OptionsFunc func(*options) type options struct { autoIncrementAssetID bool + currencies []currencies.Currency } func WithAutoIncrementAssetID(v bool) func(*options) { @@ -24,13 +27,27 @@ func WithAutoIncrementAssetID(v bool) func(*options) { } } +func WithCurrencies(v []currencies.Currency) func(*options) { + return func(o *options) { + o.currencies = v + } +} + func New(repos *repo.AllRepos, opts ...OptionsFunc) *AllServices { if repos == nil { panic("repos cannot be nil") } + defaultCurrencies, err := currencies.CollectionCurrencies( + currencies.CollectDefaults(), + ) + if err != nil { + panic("failed to collect default currencies") + } + options := &options{ autoIncrementAssetID: true, + currencies: defaultCurrencies, } for _, opt := range opts { @@ -45,5 +62,6 @@ func New(repos *repo.AllRepos, opts ...OptionsFunc) *AllServices { autoIncrementAssetID: options.autoIncrementAssetID, }, BackgroundService: &BackgroundService{repos}, + Currencies: currencies.NewCurrencyService(options.currencies), } } diff --git a/backend/internal/core/services/main_test.go b/backend/internal/core/services/main_test.go index 57dce95..ecb07b0 100644 --- a/backend/internal/core/services/main_test.go +++ b/backend/internal/core/services/main_test.go @@ -6,6 +6,7 @@ import ( "os" "testing" + "github.com/hay-kot/homebox/backend/internal/core/currencies" "github.com/hay-kot/homebox/backend/internal/core/services/reporting/eventbus" "github.com/hay-kot/homebox/backend/internal/data/ent" "github.com/hay-kot/homebox/backend/internal/data/repo" @@ -61,7 +62,12 @@ func TestMain(m *testing.M) { tClient = client tRepos = repo.New(tClient, tbus, os.TempDir()+"/homebox") - tSvc = New(tRepos) + + defaults, _ := currencies.CollectionCurrencies( + currencies.CollectDefaults(), + ) + + tSvc = New(tRepos, WithCurrencies(defaults)) defer func() { _ = client.Close() }() bootstrap() diff --git a/backend/internal/data/ent/group.go b/backend/internal/data/ent/group.go index 9357087..69c67de 100644 --- a/backend/internal/data/ent/group.go +++ b/backend/internal/data/ent/group.go @@ -25,7 +25,7 @@ type Group struct { // Name holds the value of the "name" field. Name string `json:"name,omitempty"` // Currency holds the value of the "currency" field. - Currency group.Currency `json:"currency,omitempty"` + Currency string `json:"currency,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the GroupQuery when eager-loading is set. Edges GroupEdges `json:"edges"` @@ -170,7 +170,7 @@ func (gr *Group) assignValues(columns []string, values []any) error { if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field currency", values[i]) } else if value.Valid { - gr.Currency = group.Currency(value.String) + gr.Currency = value.String } default: gr.selectValues.Set(columns[i], values[i]) @@ -253,7 +253,7 @@ func (gr *Group) String() string { builder.WriteString(gr.Name) builder.WriteString(", ") builder.WriteString("currency=") - builder.WriteString(fmt.Sprintf("%v", gr.Currency)) + builder.WriteString(gr.Currency) builder.WriteByte(')') return builder.String() } diff --git a/backend/internal/data/ent/group/group.go b/backend/internal/data/ent/group/group.go index 47bceac..32cb101 100644 --- a/backend/internal/data/ent/group/group.go +++ b/backend/internal/data/ent/group/group.go @@ -3,7 +3,6 @@ package group import ( - "fmt" "time" "entgo.io/ent/dialect/sql" @@ -119,65 +118,12 @@ var ( UpdateDefaultUpdatedAt func() time.Time // NameValidator is a validator for the "name" field. It is called by the builders before save. NameValidator func(string) error + // DefaultCurrency holds the default value on creation for the "currency" field. + DefaultCurrency string // DefaultID holds the default value on creation for the "id" field. DefaultID func() uuid.UUID ) -// Currency defines the type for the "currency" enum field. -type Currency string - -// CurrencyUsd is the default value of the Currency enum. -const DefaultCurrency = CurrencyUsd - -// Currency values. -const ( - CurrencyAed Currency = "aed" - CurrencyAud Currency = "aud" - CurrencyBgn Currency = "bgn" - CurrencyBrl Currency = "brl" - CurrencyCad Currency = "cad" - CurrencyChf Currency = "chf" - CurrencyCzk Currency = "czk" - CurrencyDkk Currency = "dkk" - CurrencyEur Currency = "eur" - CurrencyGbp Currency = "gbp" - CurrencyHkd Currency = "hkd" - CurrencyIdr Currency = "idr" - CurrencyInr Currency = "inr" - CurrencyJpy Currency = "jpy" - CurrencyKrw Currency = "krw" - CurrencyMxn Currency = "mxn" - CurrencyNok Currency = "nok" - CurrencyNzd Currency = "nzd" - CurrencyPln Currency = "pln" - CurrencyRmb Currency = "rmb" - CurrencyRon Currency = "ron" - CurrencyRub Currency = "rub" - CurrencySar Currency = "sar" - CurrencySek Currency = "sek" - CurrencySgd Currency = "sgd" - CurrencyThb Currency = "thb" - CurrencyTry Currency = "try" - CurrencyUsd Currency = "usd" - CurrencyXag Currency = "xag" - CurrencyXau Currency = "xau" - CurrencyZar Currency = "zar" -) - -func (c Currency) String() string { - return string(c) -} - -// CurrencyValidator is a validator for the "currency" field enum values. It is called by the builders before save. -func CurrencyValidator(c Currency) error { - switch c { - case CurrencyAed, CurrencyAud, CurrencyBgn, CurrencyBrl, CurrencyCad, CurrencyChf, CurrencyCzk, CurrencyDkk, CurrencyEur, CurrencyGbp, CurrencyHkd, CurrencyIdr, CurrencyInr, CurrencyJpy, CurrencyKrw, CurrencyMxn, CurrencyNok, CurrencyNzd, CurrencyPln, CurrencyRmb, CurrencyRon, CurrencyRub, CurrencySar, CurrencySek, CurrencySgd, CurrencyThb, CurrencyTry, CurrencyUsd, CurrencyXag, CurrencyXau, CurrencyZar: - return nil - default: - return fmt.Errorf("group: invalid enum value for currency field: %q", c) - } -} - // OrderOption defines the ordering options for the Group queries. type OrderOption func(*sql.Selector) diff --git a/backend/internal/data/ent/group/where.go b/backend/internal/data/ent/group/where.go index 5f1bc06..d18faa7 100644 --- a/backend/internal/data/ent/group/where.go +++ b/backend/internal/data/ent/group/where.go @@ -71,6 +71,11 @@ func Name(v string) predicate.Group { return predicate.Group(sql.FieldEQ(FieldName, v)) } +// Currency applies equality check predicate on the "currency" field. It's identical to CurrencyEQ. +func Currency(v string) predicate.Group { + return predicate.Group(sql.FieldEQ(FieldCurrency, v)) +} + // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v time.Time) predicate.Group { return predicate.Group(sql.FieldEQ(FieldCreatedAt, v)) @@ -217,25 +222,70 @@ func NameContainsFold(v string) predicate.Group { } // CurrencyEQ applies the EQ predicate on the "currency" field. -func CurrencyEQ(v Currency) predicate.Group { +func CurrencyEQ(v string) predicate.Group { return predicate.Group(sql.FieldEQ(FieldCurrency, v)) } // CurrencyNEQ applies the NEQ predicate on the "currency" field. -func CurrencyNEQ(v Currency) predicate.Group { +func CurrencyNEQ(v string) predicate.Group { return predicate.Group(sql.FieldNEQ(FieldCurrency, v)) } // CurrencyIn applies the In predicate on the "currency" field. -func CurrencyIn(vs ...Currency) predicate.Group { +func CurrencyIn(vs ...string) predicate.Group { return predicate.Group(sql.FieldIn(FieldCurrency, vs...)) } // CurrencyNotIn applies the NotIn predicate on the "currency" field. -func CurrencyNotIn(vs ...Currency) predicate.Group { +func CurrencyNotIn(vs ...string) predicate.Group { return predicate.Group(sql.FieldNotIn(FieldCurrency, vs...)) } +// CurrencyGT applies the GT predicate on the "currency" field. +func CurrencyGT(v string) predicate.Group { + return predicate.Group(sql.FieldGT(FieldCurrency, v)) +} + +// CurrencyGTE applies the GTE predicate on the "currency" field. +func CurrencyGTE(v string) predicate.Group { + return predicate.Group(sql.FieldGTE(FieldCurrency, v)) +} + +// CurrencyLT applies the LT predicate on the "currency" field. +func CurrencyLT(v string) predicate.Group { + return predicate.Group(sql.FieldLT(FieldCurrency, v)) +} + +// CurrencyLTE applies the LTE predicate on the "currency" field. +func CurrencyLTE(v string) predicate.Group { + return predicate.Group(sql.FieldLTE(FieldCurrency, v)) +} + +// CurrencyContains applies the Contains predicate on the "currency" field. +func CurrencyContains(v string) predicate.Group { + return predicate.Group(sql.FieldContains(FieldCurrency, v)) +} + +// CurrencyHasPrefix applies the HasPrefix predicate on the "currency" field. +func CurrencyHasPrefix(v string) predicate.Group { + return predicate.Group(sql.FieldHasPrefix(FieldCurrency, v)) +} + +// CurrencyHasSuffix applies the HasSuffix predicate on the "currency" field. +func CurrencyHasSuffix(v string) predicate.Group { + return predicate.Group(sql.FieldHasSuffix(FieldCurrency, v)) +} + +// CurrencyEqualFold applies the EqualFold predicate on the "currency" field. +func CurrencyEqualFold(v string) predicate.Group { + return predicate.Group(sql.FieldEqualFold(FieldCurrency, v)) +} + +// CurrencyContainsFold applies the ContainsFold predicate on the "currency" field. +func CurrencyContainsFold(v string) predicate.Group { + return predicate.Group(sql.FieldContainsFold(FieldCurrency, v)) +} + // HasUsers applies the HasEdge predicate on the "users" edge. func HasUsers() predicate.Group { return predicate.Group(func(s *sql.Selector) { diff --git a/backend/internal/data/ent/group_create.go b/backend/internal/data/ent/group_create.go index 8ccd05b..be56ba0 100644 --- a/backend/internal/data/ent/group_create.go +++ b/backend/internal/data/ent/group_create.go @@ -63,15 +63,15 @@ func (gc *GroupCreate) SetName(s string) *GroupCreate { } // SetCurrency sets the "currency" field. -func (gc *GroupCreate) SetCurrency(gr group.Currency) *GroupCreate { - gc.mutation.SetCurrency(gr) +func (gc *GroupCreate) SetCurrency(s string) *GroupCreate { + gc.mutation.SetCurrency(s) return gc } // SetNillableCurrency sets the "currency" field if the given value is not nil. -func (gc *GroupCreate) SetNillableCurrency(gr *group.Currency) *GroupCreate { - if gr != nil { - gc.SetCurrency(*gr) +func (gc *GroupCreate) SetNillableCurrency(s *string) *GroupCreate { + if s != nil { + gc.SetCurrency(*s) } return gc } @@ -267,11 +267,6 @@ func (gc *GroupCreate) check() error { if _, ok := gc.mutation.Currency(); !ok { return &ValidationError{Name: "currency", err: errors.New(`ent: missing required field "Group.currency"`)} } - if v, ok := gc.mutation.Currency(); ok { - if err := group.CurrencyValidator(v); err != nil { - return &ValidationError{Name: "currency", err: fmt.Errorf(`ent: validator failed for field "Group.currency": %w`, err)} - } - } return nil } @@ -320,7 +315,7 @@ func (gc *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) { _node.Name = value } if value, ok := gc.mutation.Currency(); ok { - _spec.SetField(group.FieldCurrency, field.TypeEnum, value) + _spec.SetField(group.FieldCurrency, field.TypeString, value) _node.Currency = value } if nodes := gc.mutation.UsersIDs(); len(nodes) > 0 { diff --git a/backend/internal/data/ent/group_update.go b/backend/internal/data/ent/group_update.go index 872ee08..fdb11a3 100644 --- a/backend/internal/data/ent/group_update.go +++ b/backend/internal/data/ent/group_update.go @@ -57,15 +57,15 @@ func (gu *GroupUpdate) SetNillableName(s *string) *GroupUpdate { } // SetCurrency sets the "currency" field. -func (gu *GroupUpdate) SetCurrency(gr group.Currency) *GroupUpdate { - gu.mutation.SetCurrency(gr) +func (gu *GroupUpdate) SetCurrency(s string) *GroupUpdate { + gu.mutation.SetCurrency(s) return gu } // SetNillableCurrency sets the "currency" field if the given value is not nil. -func (gu *GroupUpdate) SetNillableCurrency(gr *group.Currency) *GroupUpdate { - if gr != nil { - gu.SetCurrency(*gr) +func (gu *GroupUpdate) SetNillableCurrency(s *string) *GroupUpdate { + if s != nil { + gu.SetCurrency(*s) } return gu } @@ -370,11 +370,6 @@ func (gu *GroupUpdate) check() error { return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Group.name": %w`, err)} } } - if v, ok := gu.mutation.Currency(); ok { - if err := group.CurrencyValidator(v); err != nil { - return &ValidationError{Name: "currency", err: fmt.Errorf(`ent: validator failed for field "Group.currency": %w`, err)} - } - } return nil } @@ -397,7 +392,7 @@ func (gu *GroupUpdate) sqlSave(ctx context.Context) (n int, err error) { _spec.SetField(group.FieldName, field.TypeString, value) } if value, ok := gu.mutation.Currency(); ok { - _spec.SetField(group.FieldCurrency, field.TypeEnum, value) + _spec.SetField(group.FieldCurrency, field.TypeString, value) } if gu.mutation.UsersCleared() { edge := &sqlgraph.EdgeSpec{ @@ -755,15 +750,15 @@ func (guo *GroupUpdateOne) SetNillableName(s *string) *GroupUpdateOne { } // SetCurrency sets the "currency" field. -func (guo *GroupUpdateOne) SetCurrency(gr group.Currency) *GroupUpdateOne { - guo.mutation.SetCurrency(gr) +func (guo *GroupUpdateOne) SetCurrency(s string) *GroupUpdateOne { + guo.mutation.SetCurrency(s) return guo } // SetNillableCurrency sets the "currency" field if the given value is not nil. -func (guo *GroupUpdateOne) SetNillableCurrency(gr *group.Currency) *GroupUpdateOne { - if gr != nil { - guo.SetCurrency(*gr) +func (guo *GroupUpdateOne) SetNillableCurrency(s *string) *GroupUpdateOne { + if s != nil { + guo.SetCurrency(*s) } return guo } @@ -1081,11 +1076,6 @@ func (guo *GroupUpdateOne) check() error { return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Group.name": %w`, err)} } } - if v, ok := guo.mutation.Currency(); ok { - if err := group.CurrencyValidator(v); err != nil { - return &ValidationError{Name: "currency", err: fmt.Errorf(`ent: validator failed for field "Group.currency": %w`, err)} - } - } return nil } @@ -1125,7 +1115,7 @@ func (guo *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error _spec.SetField(group.FieldName, field.TypeString, value) } if value, ok := guo.mutation.Currency(); ok { - _spec.SetField(group.FieldCurrency, field.TypeEnum, value) + _spec.SetField(group.FieldCurrency, field.TypeString, value) } if guo.mutation.UsersCleared() { edge := &sqlgraph.EdgeSpec{ diff --git a/backend/internal/data/ent/migrate/schema.go b/backend/internal/data/ent/migrate/schema.go index 265e2ea..2b58838 100644 --- a/backend/internal/data/ent/migrate/schema.go +++ b/backend/internal/data/ent/migrate/schema.go @@ -117,7 +117,7 @@ var ( {Name: "created_at", Type: field.TypeTime}, {Name: "updated_at", Type: field.TypeTime}, {Name: "name", Type: field.TypeString, Size: 255}, - {Name: "currency", Type: field.TypeEnum, Enums: []string{"aed", "aud", "bgn", "brl", "cad", "chf", "czk", "dkk", "eur", "gbp", "hkd", "idr", "inr", "jpy", "krw", "mxn", "nok", "nzd", "pln", "rmb", "ron", "rub", "sar", "sek", "sgd", "thb", "try", "usd", "xag", "xau", "zar"}, Default: "usd"}, + {Name: "currency", Type: field.TypeString, Default: "usd"}, } // GroupsTable holds the schema information for the "groups" table. GroupsTable = &schema.Table{ diff --git a/backend/internal/data/ent/mutation.go b/backend/internal/data/ent/mutation.go index 5873a3c..6fa15d3 100644 --- a/backend/internal/data/ent/mutation.go +++ b/backend/internal/data/ent/mutation.go @@ -2340,7 +2340,7 @@ type GroupMutation struct { created_at *time.Time updated_at *time.Time name *string - currency *group.Currency + currency *string clearedFields map[string]struct{} users map[uuid.UUID]struct{} removedusers map[uuid.UUID]struct{} @@ -2581,12 +2581,12 @@ func (m *GroupMutation) ResetName() { } // SetCurrency sets the "currency" field. -func (m *GroupMutation) SetCurrency(gr group.Currency) { - m.currency = &gr +func (m *GroupMutation) SetCurrency(s string) { + m.currency = &s } // Currency returns the value of the "currency" field in the mutation. -func (m *GroupMutation) Currency() (r group.Currency, exists bool) { +func (m *GroupMutation) Currency() (r string, exists bool) { v := m.currency if v == nil { return @@ -2597,7 +2597,7 @@ func (m *GroupMutation) Currency() (r group.Currency, exists bool) { // OldCurrency returns the old "currency" field's value of the Group entity. // If the Group object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *GroupMutation) OldCurrency(ctx context.Context) (v group.Currency, err error) { +func (m *GroupMutation) OldCurrency(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCurrency is only allowed on UpdateOne operations") } @@ -3105,7 +3105,7 @@ func (m *GroupMutation) SetField(name string, value ent.Value) error { m.SetName(v) return nil case group.FieldCurrency: - v, ok := value.(group.Currency) + v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } diff --git a/backend/internal/data/ent/runtime.go b/backend/internal/data/ent/runtime.go index d32ae7d..c3aff00 100644 --- a/backend/internal/data/ent/runtime.go +++ b/backend/internal/data/ent/runtime.go @@ -161,6 +161,10 @@ func init() { return nil } }() + // groupDescCurrency is the schema descriptor for currency field. + groupDescCurrency := groupFields[1].Descriptor() + // group.DefaultCurrency holds the default value on creation for the currency field. + group.DefaultCurrency = groupDescCurrency.Default.(string) // groupDescID is the schema descriptor for id field. groupDescID := groupMixinFields0[0].Descriptor() // group.DefaultID holds the default value on creation for the id field. diff --git a/backend/internal/data/ent/schema/group.go b/backend/internal/data/ent/schema/group.go index 64e9d95..352ac0b 100644 --- a/backend/internal/data/ent/schema/group.go +++ b/backend/internal/data/ent/schema/group.go @@ -27,41 +27,8 @@ func (Group) Fields() []ent.Field { field.String("name"). MaxLen(255). NotEmpty(), - field.Enum("currency"). - Default("usd"). - Values( - "aed", - "aud", - "bgn", - "brl", - "cad", - "chf", - "czk", - "dkk", - "eur", - "gbp", - "hkd", - "idr", - "inr", - "jpy", - "krw", - "mxn", - "nok", - "nzd", - "pln", - "rmb", - "ron", - "rub", - "sar", - "sek", - "sgd", - "thb", - "try", - "usd", - "xag", - "xau", - "zar", - ), + field.String("currency"). + Default("usd"), } } diff --git a/backend/internal/data/ent/schema/user.go b/backend/internal/data/ent/schema/user.go index 39eb38c..10b0a8a 100644 --- a/backend/internal/data/ent/schema/user.go +++ b/backend/internal/data/ent/schema/user.go @@ -78,7 +78,6 @@ func (g UserMixin) Fields() []ent.Field { } return nil - } func (g UserMixin) Edges() []ent.Edge { diff --git a/backend/internal/data/repo/repo_group.go b/backend/internal/data/repo/repo_group.go index 3e5e584..8f93c78 100644 --- a/backend/internal/data/repo/repo_group.go +++ b/backend/internal/data/repo/repo_group.go @@ -28,7 +28,7 @@ func NewGroupRepository(db *ent.Client) *GroupRepository { Name: g.Name, CreatedAt: g.CreatedAt, UpdatedAt: g.UpdatedAt, - Currency: strings.ToUpper(g.Currency.String()), + Currency: strings.ToUpper(g.Currency), } } @@ -265,11 +265,9 @@ func (r *GroupRepository) GroupCreate(ctx context.Context, name string) (Group, } func (r *GroupRepository) GroupUpdate(ctx context.Context, ID uuid.UUID, data GroupUpdate) (Group, error) { - currency := group.Currency(strings.ToLower(data.Currency)) - entity, err := r.db.Group.UpdateOneID(ID). SetName(data.Name). - SetCurrency(currency). + SetCurrency(strings.ToLower(data.Currency)). Save(ctx) return r.groupMapper.MapErr(entity, err) diff --git a/backend/internal/data/repo/repo_items.go b/backend/internal/data/repo/repo_items.go index bd487c0..a454c1b 100644 --- a/backend/internal/data/repo/repo_items.go +++ b/backend/internal/data/repo/repo_items.go @@ -68,7 +68,7 @@ type ( ItemUpdate struct { ParentID uuid.UUID `json:"parentId" extensions:"x-nullable,x-omitempty"` ID uuid.UUID `json:"id"` - AssetID AssetID `json:"assetId"` + AssetID AssetID `json:"assetId" swaggertype:"string"` Name string `json:"name"` Description string `json:"description"` Quantity int `json:"quantity"` diff --git a/backend/internal/sys/config/conf.go b/backend/internal/sys/config/conf.go index e2d04d4..efc1871 100644 --- a/backend/internal/sys/config/conf.go +++ b/backend/internal/sys/config/conf.go @@ -17,19 +17,20 @@ const ( type Config struct { conf.Version - Mode string `yaml:"mode" conf:"default:development"` // development or production - Web WebConfig `yaml:"web"` - Storage Storage `yaml:"storage"` - Log LoggerConf `yaml:"logger"` - Mailer MailerConf `yaml:"mailer"` - Demo bool `yaml:"demo"` - Debug DebugConf `yaml:"debug"` - Options Options `yaml:"options"` + Mode string `yaml:"mode" conf:"default:development"` // development or production + Web WebConfig `yaml:"web"` + Storage Storage `yaml:"storage"` + Log LoggerConf `yaml:"logger"` + Mailer MailerConf `yaml:"mailer"` + Demo bool `yaml:"demo"` + Debug DebugConf `yaml:"debug"` + Options Options `yaml:"options"` } type Options struct { AllowRegistration bool `yaml:"disable_registration" conf:"default:true"` AutoIncrementAssetID bool `yaml:"auto_increment_asset_id" conf:"default:true"` + CurrencyConfig string `yaml:"currencies"` } type DebugConf struct { diff --git a/backend/internal/sys/validate/errors.go b/backend/internal/sys/validate/errors.go index 2338785..09fdf2c 100644 --- a/backend/internal/sys/validate/errors.go +++ b/backend/internal/sys/validate/errors.go @@ -88,7 +88,7 @@ func (fe FieldErrors) Nil() bool { return len(fe) == 0 } -// Error implments the error interface. +// Error implements the error interface. func (fe FieldErrors) Error() string { d, err := json.Marshal(fe) if err != nil { @@ -101,6 +101,10 @@ func NewFieldErrors(errs ...FieldError) FieldErrors { return errs } +func NewFieldError(field, reason string) FieldError { + return FieldError{Field: field, Error: reason} +} + func IsFieldError(err error) bool { v := FieldErrors{} return errors.As(err, &v) diff --git a/docs/docs/api/openapi-2.0.json b/docs/docs/api/openapi-2.0.json index e2d98fe..2ff1295 100644 --- a/docs/docs/api/openapi-2.0.json +++ b/docs/docs/api/openapi-2.0.json @@ -143,6 +143,25 @@ } } }, + "/v1/currency": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Base" + ], + "summary": "Currency", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/currencies.Currency" + } + } + } + } + }, "/v1/groups": { "get": { "security": [ @@ -403,6 +422,16 @@ "description": "location Ids", "name": "locations", "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "parent Ids", + "name": "parentIds", + "in": "query" } ], "responses": { @@ -1567,7 +1596,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/v1.ApiSummary" + "$ref": "#/definitions/v1.APISummary" } } } @@ -1638,6 +1667,12 @@ "schema": { "$ref": "#/definitions/v1.LoginForm" } + }, + { + "type": "string", + "description": "auth provider", + "name": "provider", + "in": "query" } ], "responses": { @@ -1816,6 +1851,23 @@ } }, "definitions": { + "currencies.Currency": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "local": { + "type": "string" + }, + "name": { + "type": "string" + }, + "symbol": { + "type": "string" + } + } + }, "repo.DocumentOut": { "type": "object", "properties": { @@ -1991,12 +2043,6 @@ "$ref": "#/definitions/repo.ItemAttachment" } }, - "children": { - "type": "array", - "items": { - "$ref": "#/definitions/repo.ItemSummary" - } - }, "createdAt": { "type": "string" }, @@ -2174,8 +2220,7 @@ "type": "boolean" }, "assetId": { - "type": "string", - "example": "0" + "type": "string" }, "description": { "type": "string" @@ -2729,15 +2774,7 @@ } } }, - "v1.ActionAmountResult": { - "type": "object", - "properties": { - "completed": { - "type": "integer" - } - } - }, - "v1.ApiSummary": { + "v1.APISummary": { "type": "object", "properties": { "allowRegistration": { @@ -2766,6 +2803,14 @@ } } }, + "v1.ActionAmountResult": { + "type": "object", + "properties": { + "completed": { + "type": "integer" + } + } + }, "v1.Build": { "type": "object", "properties": { diff --git a/docs/docs/quick-start.md b/docs/docs/quick-start.md index 8fa4e4d..2443966 100644 --- a/docs/docs/quick-start.md +++ b/docs/docs/quick-start.md @@ -61,6 +61,7 @@ volumes: | HBOX_WEB_HOST | | host to run the web server on, if you're using docker do not change this | | HBOX_OPTIONS_ALLOW_REGISTRATION | true | allow users to register themselves | | HBOX_OPTIONS_AUTO_INCREMENT_ASSET_ID | true | auto increments the asset_id field for new items | +| HBOX_OPTIONS_CURRENCY_CONFIG | | json configuration file containing additional currencie | | HBOX_WEB_MAX_UPLOAD_SIZE | 10 | maximum file upload size supported in MB | | HBOX_WEB_READ_TIMEOUT | 10 | Read timeout of HTTP sever | | HBOX_WEB_WRITE_TIMEOUT | 10 | Write timeout of HTTP server | @@ -104,6 +105,7 @@ volumes: --debug-port/$HBOX_DEBUG_PORT (default: 4000) --options-allow-registration/$HBOX_OPTIONS_ALLOW_REGISTRATION (default: true) --options-auto-increment-asset-id/$HBOX_OPTIONS_AUTO_INCREMENT_ASSET_ID (default: true) + --options-currency-config/$HBOX_OPTIONS_CURRENCY_CONFIG --help/-h display this help message ``` diff --git a/docs/docs/tips-tricks.md b/docs/docs/tips-tricks.md index 2e0638f..a5ed05a 100644 --- a/docs/docs/tips-tricks.md +++ b/docs/docs/tips-tricks.md @@ -55,4 +55,26 @@ Homebox uses [shoutrrr](https://containrrr.dev/shoutrrr/0.7/) to send notificati **Notifications are sent on the day the maintenance is scheduled at or around 8am.** -As of `v0.9.0` we have limited support for complex scheduling of maintenance events. If you have requests for extended functionality, please open an issue on GitHub or reach out on Discord. We're still gauging the demand for this feature. \ No newline at end of file +As of `v0.9.0` we have limited support for complex scheduling of maintenance events. If you have requests for extended functionality, please open an issue on GitHub or reach out on Discord. We're still gauging the demand for this feature. + + +## Custom Currencies + +:octicons-tag-24: v0.11.0 + +Homebox allows you to add additional currencies to your instance by specify a JSON file containing the currencies you want to add. + +**Environment Variable:** `HBOX_OPTIONS_CURRENCY_CONFIG` + +### Example + +```json +[ + { + "code": "AED", + "local": "United Arab Emirates", + "symbol": "د.إ", + "name": "United Arab Emirates Dirham" + }, +] +``` diff --git a/frontend/lib/api/__test__/user/group.test.ts b/frontend/lib/api/__test__/user/group.test.ts index 40eafce..4ad82b2 100644 --- a/frontend/lib/api/__test__/user/group.test.ts +++ b/frontend/lib/api/__test__/user/group.test.ts @@ -2,7 +2,6 @@ import { faker } from "@faker-js/faker"; import { describe, test, expect } from "vitest"; import { factories } from "../factories"; import { sharedUserClient } from "../test-utils"; -import { currencies } from "~~/lib/data/currency"; describe("first time user workflow (register, login, join group)", () => { test("user should be able to update group", async () => { @@ -29,20 +28,6 @@ describe("first time user workflow (register, login, join group)", () => { expect(group.currency).toBe("USD"); }); - test("currencies should be in sync with backend", async () => { - const { client } = await factories.client.singleUse(); - - for (const currency of currencies) { - const { response, data: group } = await client.group.update({ - name: faker.person.firstName(), - currency: currency.code, - }); - - expect(response.status).toBe(200); - expect(group.currency).toBe(currency.code); - } - }); - test("user should be able to join create join token and have user signup", async () => { const api = factories.client.public(); diff --git a/frontend/lib/api/__test__/user/items.test.ts b/frontend/lib/api/__test__/user/items.test.ts index 698bd3f..72b54b1 100644 --- a/frontend/lib/api/__test__/user/items.test.ts +++ b/frontend/lib/api/__test__/user/items.test.ts @@ -9,7 +9,7 @@ import { sharedUserClient } from "../test-utils"; describe("user should be able to create an item and add an attachment", () => { let increment = 0; /** - * useLocatio sets up a location resource for testing, and returns a function + * useLocation sets up a location resource for testing, and returns a function * that can be used to delete the location from the backend server. */ async function useLocation(api: UserClient): Promise<[LocationOut, () => Promise]> { diff --git a/frontend/lib/api/classes/group.ts b/frontend/lib/api/classes/group.ts index 7468f09..963157f 100644 --- a/frontend/lib/api/classes/group.ts +++ b/frontend/lib/api/classes/group.ts @@ -1,5 +1,11 @@ import { BaseAPI, route } from "../base"; -import { Group, GroupInvitation, GroupInvitationCreate, GroupUpdate } from "../types/data-contracts"; +import { + CurrenciesCurrency, + Group, + GroupInvitation, + GroupInvitationCreate, + GroupUpdate, +} from "../types/data-contracts"; export class GroupApi extends BaseAPI { createInvitation(data: GroupInvitationCreate) { @@ -21,4 +27,10 @@ export class GroupApi extends BaseAPI { url: route("/groups"), }); } + + currencies() { + return this.http.get({ + url: route("/currencies"), + }); + } } diff --git a/frontend/lib/api/public.ts b/frontend/lib/api/public.ts index ae8735a..187728d 100644 --- a/frontend/lib/api/public.ts +++ b/frontend/lib/api/public.ts @@ -1,5 +1,5 @@ import { BaseAPI, route } from "./base"; -import { ApiSummary, LoginForm, TokenResponse, UserRegistration } from "./types/data-contracts"; +import { APISummary, LoginForm, TokenResponse, UserRegistration } from "./types/data-contracts"; export type StatusResult = { health: boolean; @@ -10,7 +10,7 @@ export type StatusResult = { export class PublicApi extends BaseAPI { public status() { - return this.http.get({ url: route("/status") }); + return this.http.get({ url: route("/status") }); } public login(username: string, password: string, stayLoggedIn = false) { diff --git a/frontend/lib/api/types/data-contracts.ts b/frontend/lib/api/types/data-contracts.ts index e5457bb..67c4f9c 100644 --- a/frontend/lib/api/types/data-contracts.ts +++ b/frontend/lib/api/types/data-contracts.ts @@ -10,6 +10,13 @@ * --------------------------------------------------------------- */ +export interface CurrenciesCurrency { + code: string; + local: string; + name: string; + symbol: string; +} + export interface DocumentOut { id: string; path: string; @@ -81,7 +88,6 @@ export interface ItemOut { /** @example "0" */ assetId: string; attachments: ItemAttachment[]; - children: ItemSummary[]; createdAt: Date | string; description: string; fields: ItemField[]; @@ -141,7 +147,6 @@ export interface ItemSummary { export interface ItemUpdate { archived: boolean; - /** @example "0" */ assetId: string; description: string; fields: ItemField[]; @@ -364,11 +369,7 @@ export interface UserRegistration { token: string; } -export interface ActionAmountResult { - completed: number; -} - -export interface ApiSummary { +export interface APISummary { allowRegistration: boolean; build: Build; demo: boolean; @@ -378,6 +379,10 @@ export interface ApiSummary { versions: string[]; } +export interface ActionAmountResult { + completed: number; +} + export interface Build { buildTime: string; commit: string; diff --git a/frontend/lib/data/currency.ts b/frontend/lib/data/currency.ts deleted file mode 100644 index baa9b10..0000000 --- a/frontend/lib/data/currency.ts +++ /dev/null @@ -1,73 +0,0 @@ -export type Codes = - | "AED" - | "AUD" - | "BGN" - | "BRL" - | "CAD" - | "CHF" - | "CZK" - | "DKK" - | "EUR" - | "GBP" - | "HKD" - | "IDR" - | "INR" - | "JPY" - | "KRW" - | "MXN" - | "NOK" - | "NZD" - | "PLN" - | "RMB" - | "RUB" - | "RON" - | "SAR" - | "SEK" - | "SGD" - | "THB" - | "TRY" - | "USD" - | "XAG" - | "XAU" - | "ZAR"; - -export type Currency = { - code: Codes; - local: string; - symbol: string; - name: string; -}; - -export const currencies: Currency[] = [ - { code: "AED", local: "United Arab Emirates", symbol: "د.إ", name: "United Arab Emirates Dirham" }, - { code: "AUD", local: "Australia", symbol: "A$", name: "Australian Dollar" }, - { code: "BGN", local: "bg-BG", symbol: "lv", name: "Bulgarian lev" }, - { code: "BRL", local: "Brazil", symbol: "R$", name: "Brazilian Real" }, - { code: "CAD", local: "Canada", symbol: "C$", name: "Canadian Dollar" }, - { code: "CHF", local: "Switzerland", symbol: "CHF", name: "Swiss Franc" }, - { code: "CZK", local: "cs-CZ", symbol: "Kč", name: "Czech Koruna" }, - { code: "DKK", local: "da-DK", symbol: "kr", name: "Danish Krone" }, - { code: "EUR", local: "Eurozone", symbol: "€", name: "Euro" }, - { code: "GBP", local: "United Kingdom", symbol: "£", name: "British Pound Sterling" }, - { code: "HKD", local: "Hong Kong", symbol: "HK$", name: "Hong Kong Dollar" }, - { code: "IDR", local: "Indonesia", symbol: "Rp", name: "Indonesian Rupiah" }, - { code: "INR", local: "India", symbol: "₹", name: "Indian Rupee" }, - { code: "JPY", local: "Japan", symbol: "¥", name: "Japanese Yen" }, - { code: "KRW", local: "South Korea", symbol: "₩", name: "South Korean Won" }, - { code: "MXN", local: "Mexico", symbol: "Mex$", name: "Mexican Peso" }, - { code: "NOK", local: "Norway", symbol: "kr", name: "Norwegian Krone" }, - { code: "NZD", local: "New Zealand", symbol: "NZ$", name: "New Zealand Dollar" }, - { code: "PLN", local: "Poland", symbol: "zł", name: "Polish Zloty" }, - { code: "RMB", local: "zh-CN", symbol: "¥", name: "Chinese Yuan" }, - { code: "RON", local: "ro-RO", symbol: "lei", name: "Romanian Leu" }, - { code: "RUB", local: "Russia", symbol: "₽", name: "Russian Ruble" }, - { code: "SAR", local: "Saudi Arabia", symbol: "﷼", name: "Saudi Riyal" }, - { code: "SEK", local: "Sweden", symbol: "kr", name: "Swedish Krona" }, - { code: "SGD", local: "Singapore", symbol: "S$", name: "Singapore Dollar" }, - { code: "THB", local: "Thailand", symbol: "฿", name: "Thai Baht" }, - { code: "TRY", local: "Turkey", symbol: "₺", name: "Turkish Lira" }, - { code: "USD", local: "United States", symbol: "$", name: "United States Dollar" }, - { code: "XAG", local: "Global", symbol: "XAG", name: "Silver Troy Ounce" }, - { code: "XAU", local: "Global", symbol: "XAU", name: "Gold Troy Ounce" }, - { code: "ZAR", local: "South Africa", symbol: "R", name: "South African Rand" }, -]; diff --git a/frontend/pages/profile.vue b/frontend/pages/profile.vue index bc3273a..fffdf12 100644 --- a/frontend/pages/profile.vue +++ b/frontend/pages/profile.vue @@ -1,8 +1,7 @@