diff --git a/Makefile b/Makefile index 9a203ed1..0c4b0f39 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ db: ## Connect to the database cover: sh scripts/coverage_test.sh -lint: ## Run linters +#lint: ## Run linters golangci-lint run test: ## Run tests diff --git a/build/schema/initdb.sql b/build/schema/initdb.sql index 14aa52e2..2062eb04 100644 --- a/build/schema/initdb.sql +++ b/build/schema/initdb.sql @@ -1,7 +1,6 @@ CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -CREATE TABLE Users -( +CREATE TABLE IF NOT EXISTS Users ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, username VARCHAR(20) NOT NULL, login VARCHAR(20) UNIQUE NOT NULL, @@ -10,11 +9,18 @@ CREATE TABLE Users avatar_url UUID ); -CREATE TABLE Accounts ( +CREATE TABLE IF NOT EXISTS Accounts ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, - user_id UUID REFERENCES Users(id), balance numeric(10, 2), - mean_payment TEXT + accumulation BOOLEAN, + balance_enabled BOOLEAN, + mean_payment VARCHAR(30) +); + +CREATE TABLE IF NOT EXISTS UserAccount ( + user_id UUID REFERENCES Users(id), + account_id UUID REFERENCES Accounts(id), + PRIMARY KEY (user_id, account_id) ); CREATE TABLE IF NOT EXISTS category ( @@ -28,7 +34,7 @@ CREATE TABLE IF NOT EXISTS category ( ); -CREATE TABLE Transaction ( +CREATE TABLE IF NOT EXISTS Transaction ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, user_id UUID REFERENCES Users(id), account_income UUID REFERENCES Accounts(id), @@ -40,19 +46,22 @@ CREATE TABLE Transaction ( description VARCHAR(100) ); -CREATE TABLE TransactionCategory ( +CREATE TABLE IF NOT EXISTS TransactionCategory ( transaction_id UUID REFERENCES Transaction(id), category_id UUID REFERENCES Category(id), PRIMARY KEY (transaction_id, category_id) ); +--======================================================================== + CREATE OR REPLACE FUNCTION add_default_categories_accounts_transactions() RETURNS TRIGGER AS $$ DECLARE categoryID UUID; transaction_idI UUID; transaction_idO UUID; - accountID UUID; + accountCashID UUID; + accountCardID UUID; BEGIN INSERT INTO category (user_id, parent_tag, "name", show_income, show_outcome, regular) VALUES (NEW.id, NULL, 'Дети', false, true, false), @@ -71,19 +80,25 @@ BEGIN SELECT id INTO categoryID FROM category WHERE name = 'Продукты' AND user_id = NEW.id; - INSERT INTO accounts(user_id, balance, mean_payment) - VALUES (NEW.id, 0, 'Карта'); + INSERT INTO accounts(balance, mean_payment, accumulation, balance_enabled) + VALUES (0, 'Карта', false, true) RETURNING id INTO accountCardID; - INSERT INTO accounts(user_id, balance, mean_payment) - VALUES (NEW.id, 0, 'Наличка') RETURNING id INTO accountID; + INSERT INTO accounts(balance, mean_payment, accumulation, balance_enabled) + VALUES (0, 'Наличка', false, true) RETURNING id INTO accountCashID; + + INSERT INTO userAccount(user_id, account_id) + VALUES (NEW.id, accountCardID); + + INSERT INTO userAccount(user_id, account_id) + VALUES (NEW.id, accountCashID); INSERT INTO transaction(user_id, account_income, account_outcome, income, outcome, payer, description) - VALUES (NEW.id, accountID, - accountID, 100, 0, 'Пятерочка', 'Пошел в магазин за вкусняшками') RETURNING id INTO transaction_idI; + VALUES (NEW.id, accountCardID, + accountCardID, 100, 0, 'Пятерочка', 'Пошел в магазин за вкусняшками') RETURNING id INTO transaction_idI; INSERT INTO transaction(user_id, account_income, account_outcome, income, outcome, payer, description) - VALUES (NEW.id, accountID, - accountID, 0, 100, 'Пятерочка', 'Вернули деньги оплата не прошла') RETURNING id INTO transaction_idO; + VALUES (NEW.id, accountCardID, + accountCardID, 0, 100, 'Пятерочка', 'Вернули деньги оплата не прошла') RETURNING id INTO transaction_idO; INSERT INTO TransactionCategory(transaction_id, category_id) VALUES (transaction_idI, categoryID), @@ -106,13 +121,3 @@ ALTER COLUMN planned_budget SET DEFAULT 0.0; INSERT INTO "users"(login, username, password_hash, planned_budget) VALUES ('kossmatof','komarov', '$argon2id$v=19$m=65536,t=1,p=4$m8qhM3XLae+RCTGirBFEww$Znu5RBnxlam2xRoVtwBzbdSrN4/sRCm1IMOVX4N2uxw', 10000); - -INSERT INTO "users"(login, username, password_hash, planned_budget) -VALUES ('test','test1', '$argon2id$v=19$m=65536,t=1,p=4$m8qhM3XLae+RCTGirBFEww$Znu5RBnxlam2xRoVtwBzbdSrN4/sRCm1IMOVX4N2uxw', 10000); - -INSERT INTO "accounts"(user_id, balance, mean_payment) -VALUES ((SELECT id FROM Users limit 1), 0, 'Кошелек'); - -INSERT INTO "accounts"(user_id, balance, mean_payment) -VALUES ((SELECT id FROM Users limit 1), 0, 'Наличка'); - diff --git a/cmd/api/init/app/init.go b/cmd/api/init/app/init.go index 69a2a32f..975ee264 100644 --- a/cmd/api/init/app/init.go +++ b/cmd/api/init/app/init.go @@ -26,6 +26,10 @@ import ( userRep "github.com/go-park-mail-ru/2023_2_Hamster/internal/microservices/user/repository/postgresql" userUsecase "github.com/go-park-mail-ru/2023_2_Hamster/internal/microservices/user/usecase" + accountDelivery "github.com/go-park-mail-ru/2023_2_Hamster/internal/microservices/account/delivery/http" + accountRep "github.com/go-park-mail-ru/2023_2_Hamster/internal/microservices/account/repository/postgresql" + accountUsecase "github.com/go-park-mail-ru/2023_2_Hamster/internal/microservices/account/usecase" + sessionRep "github.com/go-park-mail-ru/2023_2_Hamster/internal/monolithic/sessions/repository/redis" sessionUsecase "github.com/go-park-mail-ru/2023_2_Hamster/internal/monolithic/sessions/usecase" @@ -40,6 +44,7 @@ func Init(db *pgxpool.Pool, redis *redis.Client, log *logger.Logger) *mux.Router userRep := userRep.NewRepository(db, *log) transactionRep := transactionRep.NewRepository(db, *log) categoryRep := categoryRep.NewRepository(db, *log) + accountRep := accountRep.NewRepository(db, *log) authUsecase := authUsecase.NewUsecase(authRep, *log) sessionUsecase := sessionUsecase.NewSessionUsecase(sessionRep) @@ -47,6 +52,7 @@ func Init(db *pgxpool.Pool, redis *redis.Client, log *logger.Logger) *mux.Router transactionUsecase := transactionUsecase.NewUsecase(transactionRep, *log) categoryUsecase := categoryUsecase.NewUsecase(categoryRep, *log) csrfUsecase := csrfUsecase.NewUsecase(*log) + accountUsecase := accountUsecase.NewUsecase(accountRep, *log) authMiddlewear := middleware.NewAuthMiddleware(sessionUsecase, userRep, *log) logMiddlewear := middleware.NewLoggingMiddleware(*log) @@ -58,6 +64,7 @@ func Init(db *pgxpool.Pool, redis *redis.Client, log *logger.Logger) *mux.Router transactionHandler := transactionDelivery.NewHandler(transactionUsecase, *log) categoryHandler := categoryDelivary.NewHandler(categoryUsecase, *log) csrfHandler := csrfDelivery.NewHandler(csrfUsecase, *log) + accountHandler := accountDelivery.NewHandler(accountUsecase, *log) return router.InitRouter( authHandler, @@ -65,6 +72,7 @@ func Init(db *pgxpool.Pool, redis *redis.Client, log *logger.Logger) *mux.Router transactionHandler, categoryHandler, csrfHandler, + accountHandler, logMiddlewear, recoveryMiddlewear, authMiddlewear, diff --git a/cmd/api/init/router/router.go b/cmd/api/init/router/router.go index 9d108f07..697ea166 100644 --- a/cmd/api/init/router/router.go +++ b/cmd/api/init/router/router.go @@ -9,12 +9,12 @@ import ( _ "github.com/go-park-mail-ru/2023_2_Hamster/docs" "github.com/prometheus/client_golang/prometheus/promhttp" + account "github.com/go-park-mail-ru/2023_2_Hamster/internal/microservices/account/delivery/http" auth "github.com/go-park-mail-ru/2023_2_Hamster/internal/microservices/auth/delivery/http" category "github.com/go-park-mail-ru/2023_2_Hamster/internal/microservices/category/delivery/http" csrf "github.com/go-park-mail-ru/2023_2_Hamster/internal/microservices/csrf/delivery/http" transaction "github.com/go-park-mail-ru/2023_2_Hamster/internal/microservices/transaction/delivery/http" user "github.com/go-park-mail-ru/2023_2_Hamster/internal/microservices/user/delivery/http" - "github.com/go-park-mail-ru/2023_2_Hamster/internal/middleware" "github.com/gorilla/mux" @@ -27,6 +27,7 @@ func InitRouter(auth *auth.Handler, transaction *transaction.Handler, category *category.Handler, csrf *csrf.Handler, + account *account.Handler, logMid *middleware.LoggingMiddleware, recoveryMid *middleware.RecoveryMiddleware, authMid *middleware.AuthMiddleware, @@ -36,7 +37,7 @@ func InitRouter(auth *auth.Handler, r.Use(middleware.RequestID) r.Use(logMid.LoggingMiddleware) r.Use(recoveryMid.Recoverer) - r.Use(middleware.Timeout(5 * time.Second)) + r.Use(middleware.Timeout(1000000 * time.Second)) r.Use(middleware.Heartbeat("ping")) r.Use(middleware.Metrics()) @@ -65,6 +66,15 @@ func InitRouter(auth *auth.Handler, authRouter.Methods("POST").Path("/logout").HandlerFunc(auth.LogOut) } + accountRouter := apiRouter.PathPrefix("/account").Subrouter() + accountRouter.Use(authMid.Authentication) + accountRouter.Use(csrfMid.CheckCSRF) + { + accountRouter.Methods("POST").Path("/create").HandlerFunc(account.Create) + accountRouter.Methods("PUT").Path("/update").HandlerFunc(account.Update) + accountRouter.Methods("DELETE").Path("/{account_id}/delete").HandlerFunc(account.Delete) + } + userRouter := apiRouter.PathPrefix("/user").Subrouter() userRouter.Use(authMid.Authentication) userRouter.Use(csrfMid.CheckCSRF) @@ -95,7 +105,7 @@ func InitRouter(auth *auth.Handler, categoryRouter := apiRouter.PathPrefix("/tag").Subrouter() categoryRouter.Use(authMid.Authentication) - // categoryRouter.Use(csrfMid.CheckCSRF) + categoryRouter.Use(csrfMid.CheckCSRF) { categoryRouter.Methods("POST").Path("/create").HandlerFunc(category.CreateTag) categoryRouter.Methods("GET").Path("/all").HandlerFunc(category.GetTags) diff --git a/cmd/api/main.go b/cmd/api/main.go index 0391da6c..573af0b0 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -31,7 +31,7 @@ import ( // @name session_id func main() { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 10000000*time.Second) defer cancel() log := logger.NewLogger(ctx) diff --git a/docs/docs.go b/docs/docs.go index 57462aae..75c87a18 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -19,6 +19,160 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { + "/api/account/create": { + "post": { + "description": "Create account", + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "summary": "Create account", + "parameters": [ + { + "description": "Input account create", + "name": "account", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/http.CreateAccount" + } + } + ], + "responses": { + "200": { + "description": "Create account", + "schema": { + "$ref": "#/definitions/http.Response-http_AccountCreateResponse" + } + }, + "400": { + "description": "Client error", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "401": { + "description": "Unauthorized user", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "403": { + "description": "Forbidden user", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "500": { + "description": "Server error", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + } + } + } + }, + "/api/account/update": { + "put": { + "description": "Put \taccount", + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "summary": "PUT \tUpdate", + "parameters": [ + { + "description": "Input transactin update", + "name": "account", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/http.UpdateAccount" + } + } + ], + "responses": { + "200": { + "description": "Update account", + "schema": { + "$ref": "#/definitions/http.Response-http_NilBody" + } + }, + "400": { + "description": "Client error", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "401": { + "description": "Unauthorized user", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "403": { + "description": "Forbidden user", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "500": { + "description": "Server error", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + } + } + } + }, + "/api/account/{account_id}/delete": { + "delete": { + "description": "Delete account with chosen ID", + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "summary": "Delete Account", + "responses": { + "200": { + "description": "Account deleted", + "schema": { + "$ref": "#/definitions/http.Response-http_NilBody" + } + }, + "400": { + "description": "Account error", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "401": { + "description": "User unathorized", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "403": { + "description": "User hasn't rights", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "500": { + "description": "Server error", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + } + } + } + }, "/api/auth/checkAuth": { "post": { "description": "Validate auth", @@ -1151,6 +1305,31 @@ const docTemplate = `{ } } }, + "http.AccountCreateResponse": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + } + } + }, + "http.CreateAccount": { + "type": "object", + "properties": { + "accumulation": { + "type": "boolean" + }, + "balance": { + "type": "number" + }, + "balance_enabled": { + "type": "boolean" + }, + "mean_payment": { + "type": "string" + } + } + }, "http.CreateTransaction": { "type": "object", "properties": { @@ -1244,6 +1423,17 @@ const docTemplate = `{ } } }, + "http.Response-http_AccountCreateResponse": { + "type": "object", + "properties": { + "body": { + "$ref": "#/definitions/http.AccountCreateResponse" + }, + "status": { + "type": "integer" + } + } + }, "http.Response-http_MasTransaction": { "type": "object", "properties": { @@ -1460,6 +1650,26 @@ const docTemplate = `{ } } }, + "http.UpdateAccount": { + "type": "object", + "properties": { + "accumulation": { + "type": "boolean" + }, + "balance": { + "type": "number" + }, + "balance_enabled": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "mean_payment": { + "type": "string" + } + } + }, "http.getCSRFResponce": { "type": "object", "properties": { @@ -1471,17 +1681,20 @@ const docTemplate = `{ "models.Accounts": { "type": "object", "properties": { + "accumulation": { + "type": "boolean" + }, "balance": { "type": "number" }, + "balance_enabled": { + "type": "boolean" + }, "id": { "type": "string" }, "mean_payment": { "type": "string" - }, - "user_id": { - "type": "string" } } }, diff --git a/docs/swagger.json b/docs/swagger.json index cb2df7c8..70a9c80c 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -13,6 +13,160 @@ "host": "localhost:8080", "basePath": "/user/{userID}/account/feed", "paths": { + "/api/account/create": { + "post": { + "description": "Create account", + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "summary": "Create account", + "parameters": [ + { + "description": "Input account create", + "name": "account", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/http.CreateAccount" + } + } + ], + "responses": { + "200": { + "description": "Create account", + "schema": { + "$ref": "#/definitions/http.Response-http_AccountCreateResponse" + } + }, + "400": { + "description": "Client error", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "401": { + "description": "Unauthorized user", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "403": { + "description": "Forbidden user", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "500": { + "description": "Server error", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + } + } + } + }, + "/api/account/update": { + "put": { + "description": "Put \taccount", + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "summary": "PUT \tUpdate", + "parameters": [ + { + "description": "Input transactin update", + "name": "account", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/http.UpdateAccount" + } + } + ], + "responses": { + "200": { + "description": "Update account", + "schema": { + "$ref": "#/definitions/http.Response-http_NilBody" + } + }, + "400": { + "description": "Client error", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "401": { + "description": "Unauthorized user", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "403": { + "description": "Forbidden user", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "500": { + "description": "Server error", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + } + } + } + }, + "/api/account/{account_id}/delete": { + "delete": { + "description": "Delete account with chosen ID", + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "summary": "Delete Account", + "responses": { + "200": { + "description": "Account deleted", + "schema": { + "$ref": "#/definitions/http.Response-http_NilBody" + } + }, + "400": { + "description": "Account error", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "401": { + "description": "User unathorized", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "403": { + "description": "User hasn't rights", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + }, + "500": { + "description": "Server error", + "schema": { + "$ref": "#/definitions/http.ResponseError" + } + } + } + } + }, "/api/auth/checkAuth": { "post": { "description": "Validate auth", @@ -1145,6 +1299,31 @@ } } }, + "http.AccountCreateResponse": { + "type": "object", + "properties": { + "account_id": { + "type": "string" + } + } + }, + "http.CreateAccount": { + "type": "object", + "properties": { + "accumulation": { + "type": "boolean" + }, + "balance": { + "type": "number" + }, + "balance_enabled": { + "type": "boolean" + }, + "mean_payment": { + "type": "string" + } + } + }, "http.CreateTransaction": { "type": "object", "properties": { @@ -1238,6 +1417,17 @@ } } }, + "http.Response-http_AccountCreateResponse": { + "type": "object", + "properties": { + "body": { + "$ref": "#/definitions/http.AccountCreateResponse" + }, + "status": { + "type": "integer" + } + } + }, "http.Response-http_MasTransaction": { "type": "object", "properties": { @@ -1454,6 +1644,26 @@ } } }, + "http.UpdateAccount": { + "type": "object", + "properties": { + "accumulation": { + "type": "boolean" + }, + "balance": { + "type": "number" + }, + "balance_enabled": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "mean_payment": { + "type": "string" + } + } + }, "http.getCSRFResponce": { "type": "object", "properties": { @@ -1465,17 +1675,20 @@ "models.Accounts": { "type": "object", "properties": { + "accumulation": { + "type": "boolean" + }, "balance": { "type": "number" }, + "balance_enabled": { + "type": "boolean" + }, "id": { "type": "string" }, "mean_payment": { "type": "string" - }, - "user_id": { - "type": "string" } } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index fe23a5ed..c1706ff9 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -34,6 +34,22 @@ definitions: user_id: type: string type: object + http.AccountCreateResponse: + properties: + account_id: + type: string + type: object + http.CreateAccount: + properties: + accumulation: + type: boolean + balance: + type: number + balance_enabled: + type: boolean + mean_payment: + type: string + type: object http.CreateTransaction: properties: account_income: @@ -94,6 +110,13 @@ definitions: status: type: integer type: object + http.Response-http_AccountCreateResponse: + properties: + body: + $ref: '#/definitions/http.AccountCreateResponse' + status: + type: integer + type: object http.Response-http_MasTransaction: properties: body: @@ -232,6 +255,19 @@ definitions: transaction_id: type: string type: object + http.UpdateAccount: + properties: + accumulation: + type: boolean + balance: + type: number + balance_enabled: + type: boolean + id: + type: string + mean_payment: + type: string + type: object http.getCSRFResponce: properties: csrf: @@ -239,14 +275,16 @@ definitions: type: object models.Accounts: properties: + accumulation: + type: boolean balance: type: number + balance_enabled: + type: boolean id: type: string mean_payment: type: string - user_id: - type: string type: object models.Category: properties: @@ -373,6 +411,107 @@ info: title: Hamster API version: 1.0.1 paths: + /api/account/{account_id}/delete: + delete: + description: Delete account with chosen ID + produces: + - application/json + responses: + "200": + description: Account deleted + schema: + $ref: '#/definitions/http.Response-http_NilBody' + "400": + description: Account error + schema: + $ref: '#/definitions/http.ResponseError' + "401": + description: User unathorized + schema: + $ref: '#/definitions/http.ResponseError' + "403": + description: User hasn't rights + schema: + $ref: '#/definitions/http.ResponseError' + "500": + description: Server error + schema: + $ref: '#/definitions/http.ResponseError' + summary: Delete Account + tags: + - Account + /api/account/create: + post: + description: Create account + parameters: + - description: Input account create + in: body + name: account + required: true + schema: + $ref: '#/definitions/http.CreateAccount' + produces: + - application/json + responses: + "200": + description: Create account + schema: + $ref: '#/definitions/http.Response-http_AccountCreateResponse' + "400": + description: Client error + schema: + $ref: '#/definitions/http.ResponseError' + "401": + description: Unauthorized user + schema: + $ref: '#/definitions/http.ResponseError' + "403": + description: Forbidden user + schema: + $ref: '#/definitions/http.ResponseError' + "500": + description: Server error + schema: + $ref: '#/definitions/http.ResponseError' + summary: Create account + tags: + - Account + /api/account/update: + put: + description: "Put \taccount" + parameters: + - description: Input transactin update + in: body + name: account + required: true + schema: + $ref: '#/definitions/http.UpdateAccount' + produces: + - application/json + responses: + "200": + description: Update account + schema: + $ref: '#/definitions/http.Response-http_NilBody' + "400": + description: Client error + schema: + $ref: '#/definitions/http.ResponseError' + "401": + description: Unauthorized user + schema: + $ref: '#/definitions/http.ResponseError' + "403": + description: Forbidden user + schema: + $ref: '#/definitions/http.ResponseError' + "500": + description: Server error + schema: + $ref: '#/definitions/http.ResponseError' + summary: "PUT \tUpdate" + tags: + - Account /api/auth/checkAuth: post: consumes: diff --git a/go.mod b/go.mod index 96246b9b..21f6a707 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/jackc/pgx/v4 v4.18.1 github.com/joho/godotenv v1.5.1 github.com/pashagolub/pgxmock v1.8.0 + github.com/prometheus/client_golang v1.17.0 github.com/redis/go-redis/v9 v9.2.1 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 @@ -39,7 +40,6 @@ require ( github.com/jackc/pgproto3/v2 v2.3.2 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgtype v1.14.0 // indirect - github.com/jackc/pgx v3.6.2+incompatible // indirect github.com/jackc/puddle v1.3.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/lib/pq v1.10.9 // indirect @@ -47,7 +47,6 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.17.0 // indirect github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect diff --git a/go.sum b/go.sum index be011a1d..92cd7fe4 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -27,7 +25,6 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -101,8 +98,6 @@ github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrU github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= -github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= @@ -118,9 +113,6 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -150,9 +142,6 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= diff --git a/internal/microservices/account/account.go b/internal/microservices/account/account.go index 0bc76357..3b584977 100644 --- a/internal/microservices/account/account.go +++ b/internal/microservices/account/account.go @@ -1 +1,21 @@ package account + +import ( + "context" + + "github.com/go-park-mail-ru/2023_2_Hamster/internal/models" + "github.com/google/uuid" +) + +type Usecase interface { + CreateAccount(ctx context.Context, userID uuid.UUID, account *models.Accounts) (uuid.UUID, error) + UpdateAccount(ctx context.Context, userID uuid.UUID, account *models.Accounts) error + DeleteAccount(ctx context.Context, userID uuid.UUID, accountID uuid.UUID) error +} + +type Repository interface { + CreateAccount(ctx context.Context, userID uuid.UUID, account *models.Accounts) (uuid.UUID, error) + UpdateAccount(ctx context.Context, userID uuid.UUID, account *models.Accounts) error + DeleteAccount(ctx context.Context, userID uuid.UUID, accountID uuid.UUID) error + CheckForbidden(ctx context.Context, accountID uuid.UUID, userID uuid.UUID) error +} diff --git a/internal/microservices/account/delivery/http/handler.go b/internal/microservices/account/delivery/http/handler.go new file mode 100644 index 00000000..2db1ddaa --- /dev/null +++ b/internal/microservices/account/delivery/http/handler.go @@ -0,0 +1,170 @@ +package http + +import ( + "encoding/json" + "errors" + "net/http" + + commonHttp "github.com/go-park-mail-ru/2023_2_Hamster/internal/common/http" + "github.com/go-park-mail-ru/2023_2_Hamster/internal/models" + + "github.com/go-park-mail-ru/2023_2_Hamster/internal/common/logger" + "github.com/go-park-mail-ru/2023_2_Hamster/internal/microservices/account" +) + +type Handler struct { + accountService account.Usecase + logger logger.Logger +} + +const ( + accountID = "account_id" +) + +func NewHandler(au account.Usecase, l logger.Logger) *Handler { + return &Handler{ + accountService: au, + logger: l, + } +} + +// @Summary Create account +// @Tags Account +// @Description Create account +// @Produce json +// @Param account body CreateAccount true "Input account create" +// @Success 200 {object} Response[AccountCreateResponse] "Create account" +// @Failure 400 {object} ResponseError "Client error" +// @Failure 401 {object} ResponseError "Unauthorized user" +// @Failure 403 {object} ResponseError "Forbidden user" +// @Failure 500 {object} ResponseError "Server error" +// @Router /api/account/create [post] +func (h *Handler) Create(w http.ResponseWriter, r *http.Request) { + user, err := commonHttp.GetUserFromRequest(r) + if err != nil { + commonHttp.ErrorResponse(w, http.StatusUnauthorized, err, commonHttp.ErrUnauthorized.Error(), h.logger) + return + } + + var accountInput CreateAccount + + if err := json.NewDecoder(r.Body).Decode(&accountInput); err != nil { + commonHttp.ErrorResponse(w, http.StatusBadRequest, err, commonHttp.InvalidBodyRequest, h.logger) + return + } + + if err := accountInput.CheckValid(); err != nil { + commonHttp.ErrorResponse(w, http.StatusBadRequest, err, commonHttp.InvalidBodyRequest, h.logger) + return + } + + accountID, err := h.accountService.CreateAccount(r.Context(), user.ID, accountInput.ToAccount()) + if err != nil { + commonHttp.ErrorResponse(w, http.StatusBadRequest, err, AccountNotCreate, h.logger) + return + } + + accountResponse := AccountCreateResponse{AccountID: accountID} + commonHttp.SuccessResponse(w, http.StatusOK, accountResponse) + +} + +// @Summary PUT Update +// @Tags Account +// @Description Put account +// @Produce json +// @Param account body UpdateAccount true "Input transactin update" +// @Success 200 {object} Response[NilBody] "Update account" +// @Failure 400 {object} ResponseError "Client error" +// @Failure 401 {object} ResponseError "Unauthorized user" +// @Failure 403 {object} ResponseError "Forbidden user" +// @Failure 500 {object} ResponseError "Server error" +// @Router /api/account/update [put] +func (h *Handler) Update(w http.ResponseWriter, r *http.Request) { + user, err := commonHttp.GetUserFromRequest(r) + if err != nil && errors.Is(err, commonHttp.ErrUnauthorized) { + commonHttp.ErrorResponse(w, http.StatusUnauthorized, err, commonHttp.ErrUnauthorized.Error(), h.logger) + return + } + + var updateAccountInput UpdateAccount + + if err := json.NewDecoder(r.Body).Decode(&updateAccountInput); err != nil { + commonHttp.ErrorResponse(w, http.StatusBadRequest, err, commonHttp.InvalidBodyRequest, h.logger) + return + } + + if err := updateAccountInput.CheckValid(); err != nil { + commonHttp.ErrorResponse(w, http.StatusBadRequest, err, commonHttp.InvalidBodyRequest, h.logger) + return + } + + if err := h.accountService.UpdateAccount(r.Context(), user.ID, updateAccountInput.ToAccount()); err != nil { + var errNoSuchaccount *models.NoSuchAccounts + if errors.As(err, &errNoSuchaccount) { + commonHttp.ErrorResponse(w, http.StatusBadRequest, err, AccountNotSuch, h.logger) + return + } + + var errForbiddenUser *models.ForbiddenUserError + if errors.As(err, &errForbiddenUser) { + commonHttp.ErrorResponse(w, http.StatusForbidden, err, commonHttp.ForbiddenUser, h.logger) + return + } + + if err != nil { + commonHttp.ErrorResponse(w, http.StatusInternalServerError, err, AccountCreateServerError, h.logger) + return + } + } + + commonHttp.SuccessResponse(w, http.StatusOK, commonHttp.NilBody{}) + +} + +// @Summary Delete Account +// @Tags Account +// @Description Delete account with chosen ID +// @Produce json +// @Success 200 {object} Response[NilBody] "Account deleted" +// @Failure 400 {object} ResponseError "Account error" +// @Failure 401 {object} ResponseError "User unathorized" +// @Failure 403 {object} ResponseError "User hasn't rights" +// @Failure 500 {object} ResponseError "Server error" +// @Router /api/account/{account_id}/delete [delete] +func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) { + accountID, err := commonHttp.GetIDFromRequest(accountID, r) + + if err != nil { + commonHttp.ErrorResponse(w, http.StatusBadRequest, err, commonHttp.InvalidURLParameter, h.logger) + return + } + user, err := commonHttp.GetUserFromRequest(r) + + if err != nil { + commonHttp.ErrorResponse(w, http.StatusUnauthorized, err, commonHttp.ErrUnauthorized.Error(), h.logger) + return + } + + err = h.accountService.DeleteAccount(r.Context(), user.ID, accountID) + + if err != nil { + var errNoSuchaccount *models.NoSuchAccounts + if errors.As(err, &errNoSuchaccount) { + commonHttp.ErrorResponse(w, http.StatusBadRequest, err, AccountNotSuch, h.logger) + return + } + + var errForbiddenUser *models.ForbiddenUserError + if errors.As(err, &errForbiddenUser) { + commonHttp.ErrorResponse(w, http.StatusForbidden, err, commonHttp.ForbiddenUser, h.logger) + return + } + if err != nil { + commonHttp.ErrorResponse(w, http.StatusInternalServerError, err, AccountCreateServerError, h.logger) + return + } + } + commonHttp.SuccessResponse(w, http.StatusOK, commonHttp.NilBody{}) + +} diff --git a/internal/microservices/account/delivery/http/handlers_models.go b/internal/microservices/account/delivery/http/handlers_models.go new file mode 100644 index 00000000..fe240c13 --- /dev/null +++ b/internal/microservices/account/delivery/http/handlers_models.go @@ -0,0 +1,70 @@ +package http + +import ( + "html" + + valid "github.com/asaskevich/govalidator" + "github.com/go-park-mail-ru/2023_2_Hamster/internal/models" + "github.com/google/uuid" +) + +const ( + AccountNotCreate = "can't create account" + AccountNotSuch = "can't such account" + + AccountCreateServerError = "can't get account" + TransactionDeleteServerError = "cat't delete transaction" +) + +type AccountCreateResponse struct { + AccountID uuid.UUID `json:"account_id"` +} + +type CreateAccount struct { + Balance float64 `json:"balance" valid:"-"` + Accumulation bool `json:"accumulation" valid:"-"` + BalanceEnabled bool `json:"balance_enabled" valid:"-"` + MeanPayment string `json:"mean_payment" valid:"required,length(1|30)"` +} + +type UpdateAccount struct { + ID uuid.UUID `json:"id" valid:"required"` + Balance float64 `json:"balance" valid:""` + Accumulation bool `json:"accumulation" valid:""` + BalanceEnabled bool `json:"balance_enabled" valid:""` + MeanPayment string `json:"mean_payment" valid:""` +} + +func (cr *CreateAccount) ToAccount() *models.Accounts { + return &models.Accounts{ + Balance: cr.Balance, + Accumulation: cr.Accumulation, + BalanceEnabled: cr.BalanceEnabled, + MeanPayment: cr.MeanPayment, + } +} + +func (au *UpdateAccount) ToAccount() *models.Accounts { + return &models.Accounts{ + ID: au.ID, + Balance: au.Balance, + Accumulation: au.Accumulation, + BalanceEnabled: au.BalanceEnabled, + MeanPayment: au.MeanPayment, + } +} + +func (ca *CreateAccount) CheckValid() error { + ca.MeanPayment = html.EscapeString(ca.MeanPayment) + + _, err := valid.ValidateStruct(*ca) + + return err +} + +func (ca *UpdateAccount) CheckValid() error { + + _, err := valid.ValidateStruct(*ca) + + return err +} diff --git a/internal/microservices/account/delivery/http/handlers.go b/internal/microservices/account/delivery/http/handlers_test.go similarity index 100% rename from internal/microservices/account/delivery/http/handlers.go rename to internal/microservices/account/delivery/http/handlers_test.go diff --git a/internal/microservices/account/repository/postgres.go b/internal/microservices/account/repository/postgres.go deleted file mode 100644 index 50a4378d..00000000 --- a/internal/microservices/account/repository/postgres.go +++ /dev/null @@ -1 +0,0 @@ -package repository diff --git a/internal/microservices/account/repository/postgresql/postgres.go b/internal/microservices/account/repository/postgresql/postgres.go new file mode 100644 index 00000000..6a4deb0a --- /dev/null +++ b/internal/microservices/account/repository/postgresql/postgres.go @@ -0,0 +1,133 @@ +package postgresql + +import ( + "context" + "fmt" + + "github.com/go-park-mail-ru/2023_2_Hamster/cmd/api/init/db/postgresql" + "github.com/go-park-mail-ru/2023_2_Hamster/internal/common/logger" + "github.com/go-park-mail-ru/2023_2_Hamster/internal/models" + "github.com/google/uuid" +) + +const ( + AccountGetUserByID = `SELECT EXISTS( + SELECT 1 FROM UserAccount + WHERE account_id = $1 AND user_id = $2);` + + AccountUpdate = "UPDATE accounts SET balance = $1, accumulation = $2, balance_enabled = $3, mean_payment = $4 WHERE id = $5;" + AccountDelete = "DELETE FROM accounts WHERE id = $1;" + UserAccountDelete = "DELETE FROM userAccount WHERE account_id = $1;" + AccountCreate = "INSERT INTO accounts (balance, accumulation, balance_enabled, mean_payment) VALUES ($1, $2, $3, $4) RETURNING id;" + AccountUserCreate = "INSERT INTO userAccount (user_id, account_id) VALUES ($1, $2);" + TransactionCategoryDelete = "DELETE FROM TransactionCategory WHERE transaction_id IN (SELECT id FROM Transaction WHERE account_income = $1 OR account_outcome = $1)" + AccountTransactionDelete = "DELETE FROM Transaction WHERE account_income = $1 OR account_outcome = $1" +) + +type AccountRep struct { + db postgresql.DbConn + logger logger.Logger +} + +func NewRepository(db postgresql.DbConn, log logger.Logger) *AccountRep { + return &AccountRep{ + db: db, + logger: log, + } +} + +func (r *AccountRep) CheckForbidden(ctx context.Context, accountID uuid.UUID, userID uuid.UUID) error { // need test + var result bool + row := r.db.QueryRow(ctx, AccountGetUserByID, accountID, userID) + + err := row.Scan(&result) + + return err +} + +// (balance, accumulation, balance_enabled, mean_paymment) +func (r *AccountRep) CreateAccount(ctx context.Context, userID uuid.UUID, account *models.Accounts) (uuid.UUID, error) { + tx, err := r.db.Begin(ctx) + if err != nil { + return uuid.Nil, fmt.Errorf("[repo] failed to start transaction: %w", err) + } + + defer func() { + if err != nil { + if err = tx.Rollback(ctx); err != nil { + r.logger.Fatal("Rollback account Error: %w", err) + } + + } + }() + + row := tx.QueryRow(ctx, AccountCreate, account.Balance, account.Accumulation, account.BalanceEnabled, account.MeanPayment) + var id uuid.UUID + + err = row.Scan(&id) + if err != nil { + return id, fmt.Errorf("[repo] error request %s, %w", AccountCreate, err) + } + + _, err = tx.Exec(ctx, AccountUserCreate, userID, id) + if err != nil { + return id, fmt.Errorf("[repo] can't create accountUser %s, %w", AccountUserCreate, err) + } + + if err = tx.Commit(ctx); err != nil { + return id, fmt.Errorf("[repo] failed to commit account: %w", err) + } + + return id, nil +} + +func (r *AccountRep) UpdateAccount(ctx context.Context, userID uuid.UUID, account *models.Accounts) error { + _, err := r.db.Exec(ctx, AccountUpdate, account.Balance, account.Accumulation, account.BalanceEnabled, account.MeanPayment, account.ID) + if err != nil { + return fmt.Errorf("[repo] failed update account %w", err) + } + + return nil +} + +func (r *AccountRep) DeleteAccount(ctx context.Context, userID uuid.UUID, accountID uuid.UUID) error { + tx, err := r.db.Begin(ctx) + if err != nil { + return fmt.Errorf("[repo] failed to start transaction: %w", err) + } + + defer func() { + if err != nil { + if err = tx.Rollback(ctx); err != nil { + r.logger.Fatal("Rollback account Error: %w", err) + } + + } + }() + + _, err = tx.Exec(ctx, TransactionCategoryDelete, accountID) + if err != nil { + return fmt.Errorf("[repo] failed to delete from TransactionCategory table: %w", err) + } + + _, err = tx.Exec(ctx, AccountTransactionDelete, accountID) + if err != nil { + return fmt.Errorf("[repo] failed to delete from Transaction table: %w", err) + } + + _, err = tx.Exec(ctx, UserAccountDelete, accountID) + if err != nil { + return fmt.Errorf("[repo] failed to delete from UserAccount table: %w", err) + } + + _, err = tx.Exec(ctx, AccountDelete, 2) + if err != nil { + return fmt.Errorf("[repo] failed to delete account %s, %w", AccountDelete, err) + } + + if err = tx.Commit(ctx); err != nil { + return fmt.Errorf("[repo] failed to commit account: %w", err) + } + + return nil +} diff --git a/internal/microservices/account/repository/postgresql/postgres_test.go b/internal/microservices/account/repository/postgresql/postgres_test.go new file mode 100644 index 00000000..4e9a54a3 --- /dev/null +++ b/internal/microservices/account/repository/postgresql/postgres_test.go @@ -0,0 +1 @@ +package postgresql diff --git a/internal/microservices/account/usecase/account_usecase.go b/internal/microservices/account/usecase/account_usecase.go index aed24547..18a31db7 100644 --- a/internal/microservices/account/usecase/account_usecase.go +++ b/internal/microservices/account/usecase/account_usecase.go @@ -1 +1,60 @@ package usecase + +import ( + "context" + "fmt" + + logging "github.com/go-park-mail-ru/2023_2_Hamster/internal/common/logger" + "github.com/go-park-mail-ru/2023_2_Hamster/internal/microservices/account" + "github.com/go-park-mail-ru/2023_2_Hamster/internal/models" + "github.com/google/uuid" +) + +type Usecase struct { + accountRepo account.Repository + logger logging.Logger +} + +func NewUsecase( + ar account.Repository, + log logging.Logger) *Usecase { + return &Usecase{ + accountRepo: ar, + logger: log, + } +} + +func (a *Usecase) CreateAccount(ctx context.Context, userID uuid.UUID, account *models.Accounts) (uuid.UUID, error) { + accountID, err := a.accountRepo.CreateAccount(ctx, userID, account) + + if err != nil { + return accountID, fmt.Errorf("[usecase] can't create account into repository: %w", err) + } + return accountID, nil +} + +func (a *Usecase) UpdateAccount(ctx context.Context, userID uuid.UUID, account *models.Accounts) error { + err := a.accountRepo.CheckForbidden(ctx, account.ID, userID) + if err != nil { + return fmt.Errorf("[usecase] can't be update by user: %w", err) + } + + err = a.accountRepo.UpdateAccount(ctx, userID, account) + if err != nil { + return fmt.Errorf("[usecase] can't update account into repository: %w", err) + } + return nil +} + +func (a *Usecase) DeleteAccount(ctx context.Context, userID uuid.UUID, accountID uuid.UUID) error { + err := a.accountRepo.CheckForbidden(ctx, accountID, userID) + if err != nil { + return fmt.Errorf("[usecase] can't be delete by user: %w", err) + } + + err = a.accountRepo.DeleteAccount(ctx, userID, accountID) + if err != nil { + return fmt.Errorf("[usecase] can't delete account into repository: %w", err) + } + return nil +} diff --git a/internal/microservices/account/usecase/account_usecase_test.go b/internal/microservices/account/usecase/account_usecase_test.go new file mode 100644 index 00000000..aed24547 --- /dev/null +++ b/internal/microservices/account/usecase/account_usecase_test.go @@ -0,0 +1 @@ +package usecase diff --git a/internal/microservices/transaction/repository/postgresql/postgres.go b/internal/microservices/transaction/repository/postgresql/postgres.go index e0f90848..e4c67af3 100644 --- a/internal/microservices/transaction/repository/postgresql/postgres.go +++ b/internal/microservices/transaction/repository/postgresql/postgres.go @@ -378,6 +378,7 @@ func (r *transactionRep) DeleteTransaction(ctx context.Context, transactionID uu if err = tx.Commit(ctx); err != nil { return fmt.Errorf("[repo] failed to commit transaction: %w", err) } + return nil } diff --git a/internal/microservices/user/repository/postgresql/postgres.go b/internal/microservices/user/repository/postgresql/postgres.go index 4009ccd7..1f3d8c11 100644 --- a/internal/microservices/user/repository/postgresql/postgres.go +++ b/internal/microservices/user/repository/postgresql/postgres.go @@ -20,8 +20,16 @@ const ( UserCheck = `SELECT EXISTS(SELECT 1 FROM users WHERE id = $1);` UserUpdate = `UPDATE users SET username = $2, planned_budget = $3, avatar_url = $4 WHERE id = $1;` UserUpdatePhoto = `UPDATE users SET avatar_url = $2 WHERE id = $1;` - AccountBalance = "SELECT SUM(balance) FROM accounts WHERE user_id = $1" // TODO: move accounts - AccountGet = `SELECT * FROM accounts WHERE user_id = $1` // TODO: move accounts + AccountBalance = `SELECT SUM(a.balance) + FROM Accounts a + JOIN UserAccount ua ON a.id = ua.account_id + WHERE ua.user_id = $1 + AND a.balance_enabled = true;` // TODO: move accounts + + AccountGet = `SELECT a.* + FROM Accounts a + JOIN UserAccount ua ON a.id = ua.account_id + WHERE ua.user_id = $1` // TODO: move accounts ) type UserRep struct { @@ -132,7 +140,6 @@ func (r *UserRep) GetCurrentBudget(ctx context.Context, userID uuid.UUID) (float } func (r *UserRep) GetAccounts(ctx context.Context, user_id uuid.UUID) ([]models.Accounts, error) { // need test - var accounts []models.Accounts rows, err := r.db.Query(ctx, AccountGet, user_id) @@ -145,8 +152,9 @@ func (r *UserRep) GetAccounts(ctx context.Context, user_id uuid.UUID) ([]models. var account models.Accounts if err := rows.Scan( &account.ID, - &account.UserID, &account.Balance, + &account.Accumulation, + &account.BalanceEnabled, &account.MeanPayment, ); err != nil { return nil, fmt.Errorf("[repo] %w", err) diff --git a/internal/microservices/user/repository/postgresql/postgres_test.go b/internal/microservices/user/repository/postgresql/postgres_test.go index 445801b3..b864b4ed 100644 --- a/internal/microservices/user/repository/postgresql/postgres_test.go +++ b/internal/microservices/user/repository/postgresql/postgres_test.go @@ -455,50 +455,52 @@ func TestGetAccounts(t *testing.T) { }{ { name: "ValidAccounts", - rows: pgxmock.NewRows([]string{"id", "user_id", "balance", "mean_payment"}). - AddRow(userID, userID, 100.0, "Кошелек"). - AddRow(userID, userID, 200.0, "Наличка"), + rows: pgxmock.NewRows([]string{"id", "balance", "accumulation", "balance_enabled", "mean_payment"}). + AddRow(userID, 100.0, true, false, "Кошелек"). + AddRow(userID, 200.0, true, false, "Наличка"), err: nil, expected: []models.Accounts{ { - ID: userID, - UserID: userID, - Balance: 100.0, - MeanPayment: "Кошелек", + ID: userID, + Balance: 100.0, + Accumulation: true, + BalanceEnabled: false, + MeanPayment: "Кошелек", }, { - ID: userID, - UserID: userID, - Balance: 200.0, - MeanPayment: "Наличка", + ID: userID, + Balance: 200.0, + Accumulation: true, + BalanceEnabled: false, + MeanPayment: "Наличка", }, }, }, { name: "ValidAccounts", - rows: pgxmock.NewRows([]string{"id", "user_id", "balance", "mean_payment"}). - AddRow("fff", userID, 100.0, "Кошелек"). - AddRow(userID, userID, 200.0, "Наличка"), + rows: pgxmock.NewRows([]string{"id", "balance", "accumulation", "balance_enabled", "mean_payment"}). + AddRow("fff", 100.0, true, false, "Кошелек"). + AddRow(userID, 200.0, true, false, "Наличка"), err: fmt.Errorf("[repo] Scanning value error for column 'id': Scan: invalid UUID length: 3"), expected: nil, }, { name: "NoAccountsFound", - rows: pgxmock.NewRows([]string{"id", "user_id", "balance", "mean_payment"}), + rows: pgxmock.NewRows([]string{"id", "balance", "accumulation", "balance_enabled", "mean_payment"}), rowsErr: nil, err: fmt.Errorf("[repo] No Such Accounts from user: %s doesn't exist: ", userID.String()), expected: nil, }, { name: "Rows error", - rows: pgxmock.NewRows([]string{"id", "user_id", "balance", "mean_payment"}).RowError(0, errors.New("err")), + rows: pgxmock.NewRows([]string{"id", "balance", "accumulation", "balance_enabled", "mean_payment"}).RowError(0, errors.New("err")), rowsErr: nil, err: fmt.Errorf("[repo] %w", errors.New("err")), expected: nil, }, { name: "DatabaseError", - rows: pgxmock.NewRows([]string{"id", "user_id", "balance", "mean_payment"}), + rows: pgxmock.NewRows([]string{"id", "balance", "accumulation", "balance_enabled", "mean_payment"}), rowsErr: errors.New("database error"), err: fmt.Errorf("[repo] %w", errors.New("database error")), expected: nil, diff --git a/internal/microservices/user/usecase/user_usecase_test.go b/internal/microservices/user/usecase/user_usecase_test.go index dd5b7361..b7595d8b 100644 --- a/internal/microservices/user/usecase/user_usecase_test.go +++ b/internal/microservices/user/usecase/user_usecase_test.go @@ -119,14 +119,14 @@ func TestUsecase_GetAccounts(t *testing.T) { { name: "Successful accounts retrieval", expectedAccounts: []models.Accounts{ - {ID: uuidTest, UserID: uuidTest, Balance: 100.0, MeanPayment: "Account1"}, - {ID: uuidTest, UserID: uuidTest, Balance: 200.0, MeanPayment: "Account2"}, + {ID: uuidTest, Balance: 100.0, MeanPayment: "Account1"}, + {ID: uuidTest, Balance: 200.0, MeanPayment: "Account2"}, }, expectedErr: nil, mockRepoFn: func(mockRepository *mock.MockRepository) { mockRepository.EXPECT().GetAccounts(gomock.Any(), gomock.Any()).Return([]models.Accounts{ - {ID: uuidTest, UserID: uuidTest, Balance: 100.0, MeanPayment: "Account1"}, - {ID: uuidTest, UserID: uuidTest, Balance: 200.0, MeanPayment: "Account2"}}, nil) + {ID: uuidTest, Balance: 100.0, MeanPayment: "Account1"}, + {ID: uuidTest, Balance: 200.0, MeanPayment: "Account2"}}, nil) }, }, { diff --git a/internal/models/account.go b/internal/models/account.go index c56a5466..dd2cd53b 100644 --- a/internal/models/account.go +++ b/internal/models/account.go @@ -3,14 +3,17 @@ package models import "github.com/google/uuid" type Accounts struct { - ID uuid.UUID `json:"id"` - UserID uuid.UUID `json:"user_id"` - Balance float64 `json:"balance"` - MeanPayment string `json:"mean_payment"` + ID uuid.UUID `json:"id"` + Balance float64 `json:"balance"` + Accumulation bool `json:"accumulation"` + BalanceEnabled bool `json:"balance_enabled"` + MeanPayment string `json:"mean_payment"` } type AccounstTransfer struct { - ID uuid.UUID `json:"id"` - Username string `json:"username"` - PlannedBudget float64 `json:"planned_budget"` + ID uuid.UUID `json:"id"` + Balance float64 `json:"balance"` + Accumulation bool `json:"accumulation"` + BalanceEnabled bool `json:"balance_enabled"` + MeanPayment string `json:"mean_payment"` } diff --git a/local-docker-compose.yaml b/local-docker-compose.yaml index 533ec6e0..aa543b9e 100644 --- a/local-docker-compose.yaml +++ b/local-docker-compose.yaml @@ -1,4 +1,3 @@ - services: db: container_name: hammy-db