diff --git a/backend/docs/docs.go b/backend/docs/docs.go index aa022e8e..0408b2ce 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -471,6 +471,282 @@ const docTemplate = `{ } } }, + "/log_transactions": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get all transactions logs within a specific time range", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Log Transactions" + ], + "summary": "Get all transactions logs", + "parameters": [ + { + "type": "string", + "description": "Time range (e.g., last 24 hours, last 7 days, last 30 days)", + "name": "time_range", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/entity.LogTransaction" + } + } + } + } + }, + "/log_transactions/asset/{asset_id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get all transactions logs for a specific asset", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Log Transactions" + ], + "summary": "Get transactions logs by Asset ID", + "parameters": [ + { + "type": "integer", + "description": "Asset ID", + "name": "asset_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/entity.LogTransaction" + } + } + } + } + }, + "/log_transactions/assets/sum/{time_range}/{time_frame}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get sum of amounts for all assets, grouped by a specified time frame (e.g., '1h' for 1 hour)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Log Transactions" + ], + "summary": "Get sum of amounts for all assets within a specific time frame", + "parameters": [ + { + "type": "string", + "description": "Time range for the query (e.g., '24h')", + "name": "time_range", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Time frame for grouping (e.g., '1h'). Default is '1h'", + "name": "time_frame", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Array of sum log transactions", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/entity.SumLogTransaction" + } + } + }, + "400": { + "description": "Invalid time_frame format", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "type": "string" + } + } + } + } + }, + "/log_transactions/assets/{asset_id}/type/{transaction_type_id}/sum/{time_range}/{time_frame}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get sum of amounts for a specific asset, grouped by a specified time frame (e.g., '1h' for 1 hour) and a specific transaction type", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Log Transactions" + ], + "summary": "Get sum of amounts by Asset ID within a specific time frame", + "parameters": [ + { + "type": "integer", + "description": "Asset ID", + "name": "asset_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Transaction type ID (e.g., '0' for all transactions, '1' for type create asset '2' for mint asset)", + "name": "transaction_type_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Time range for the query (e.g., '24h' or '1d' '7d' '30d')", + "name": "time_range", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Time frame for the query (e.g., '1h' '2h' '24h' '36h')", + "name": "time_frame", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Sum log transaction for the specified asset", + "schema": { + "$ref": "#/definitions/entity.SumLogTransaction" + } + }, + "400": { + "description": "invalid transaction type", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "type": "string" + } + } + } + } + }, + "/log_transactions/transaction_type/{transaction_type_id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get all transactions logs for a specific transaction type", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Log Transactions" + ], + "summary": "Get transactions logs by Transaction Type ID", + "parameters": [ + { + "type": "integer", + "description": "Transaction Type ID", + "name": "transaction_type_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/entity.LogTransaction" + } + } + } + } + }, + "/log_transactions/user/{user_id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get all transactions logs for a specific user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Log Transactions" + ], + "summary": "Get transactions logs by User ID", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "user_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/entity.LogTransaction" + } + } + } + } + }, "/role": { "get": { "description": "List role", @@ -1141,6 +1417,38 @@ const docTemplate = `{ } } }, + "entity.LogTransaction": { + "type": "object", + "properties": { + "amount": { + "type": "number", + "example": 100000 + }, + "asset": { + "$ref": "#/definitions/entity.Asset" + }, + "date": { + "type": "string", + "example": "2023-08-10T14:30:00Z" + }, + "description": { + "type": "string", + "example": "Mint Asset" + }, + "log_id": { + "type": "integer", + "example": 1 + }, + "transaction_type_id": { + "type": "integer", + "example": 1 + }, + "user_id": { + "type": "integer", + "example": 42 + } + } + }, "entity.Role": { "type": "object", "properties": { @@ -1167,6 +1475,32 @@ const docTemplate = `{ } } }, + "entity.SumLogTransaction": { + "type": "object", + "properties": { + "amount": { + "type": "array", + "items": { + "type": "number" + }, + "example": [ + 100000 + ] + }, + "asset": { + "$ref": "#/definitions/entity.Asset" + }, + "date": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "2023-08-10T14:30:00Z" + ] + } + } + }, "entity.User": { "type": "object", "properties": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 5fb620f3..c907af2e 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -459,6 +459,282 @@ } } }, + "/log_transactions": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get all transactions logs within a specific time range", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Log Transactions" + ], + "summary": "Get all transactions logs", + "parameters": [ + { + "type": "string", + "description": "Time range (e.g., last 24 hours, last 7 days, last 30 days)", + "name": "time_range", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/entity.LogTransaction" + } + } + } + } + }, + "/log_transactions/asset/{asset_id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get all transactions logs for a specific asset", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Log Transactions" + ], + "summary": "Get transactions logs by Asset ID", + "parameters": [ + { + "type": "integer", + "description": "Asset ID", + "name": "asset_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/entity.LogTransaction" + } + } + } + } + }, + "/log_transactions/assets/sum/{time_range}/{time_frame}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get sum of amounts for all assets, grouped by a specified time frame (e.g., '1h' for 1 hour)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Log Transactions" + ], + "summary": "Get sum of amounts for all assets within a specific time frame", + "parameters": [ + { + "type": "string", + "description": "Time range for the query (e.g., '24h')", + "name": "time_range", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Time frame for grouping (e.g., '1h'). Default is '1h'", + "name": "time_frame", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Array of sum log transactions", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/entity.SumLogTransaction" + } + } + }, + "400": { + "description": "Invalid time_frame format", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "type": "string" + } + } + } + } + }, + "/log_transactions/assets/{asset_id}/type/{transaction_type_id}/sum/{time_range}/{time_frame}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get sum of amounts for a specific asset, grouped by a specified time frame (e.g., '1h' for 1 hour) and a specific transaction type", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Log Transactions" + ], + "summary": "Get sum of amounts by Asset ID within a specific time frame", + "parameters": [ + { + "type": "integer", + "description": "Asset ID", + "name": "asset_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Transaction type ID (e.g., '0' for all transactions, '1' for type create asset '2' for mint asset)", + "name": "transaction_type_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Time range for the query (e.g., '24h' or '1d' '7d' '30d')", + "name": "time_range", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Time frame for the query (e.g., '1h' '2h' '24h' '36h')", + "name": "time_frame", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Sum log transaction for the specified asset", + "schema": { + "$ref": "#/definitions/entity.SumLogTransaction" + } + }, + "400": { + "description": "invalid transaction type", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "type": "string" + } + } + } + } + }, + "/log_transactions/transaction_type/{transaction_type_id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get all transactions logs for a specific transaction type", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Log Transactions" + ], + "summary": "Get transactions logs by Transaction Type ID", + "parameters": [ + { + "type": "integer", + "description": "Transaction Type ID", + "name": "transaction_type_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/entity.LogTransaction" + } + } + } + } + }, + "/log_transactions/user/{user_id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get all transactions logs for a specific user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Log Transactions" + ], + "summary": "Get transactions logs by User ID", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "user_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/entity.LogTransaction" + } + } + } + } + }, "/role": { "get": { "description": "List role", @@ -1129,6 +1405,38 @@ } } }, + "entity.LogTransaction": { + "type": "object", + "properties": { + "amount": { + "type": "number", + "example": 100000 + }, + "asset": { + "$ref": "#/definitions/entity.Asset" + }, + "date": { + "type": "string", + "example": "2023-08-10T14:30:00Z" + }, + "description": { + "type": "string", + "example": "Mint Asset" + }, + "log_id": { + "type": "integer", + "example": 1 + }, + "transaction_type_id": { + "type": "integer", + "example": 1 + }, + "user_id": { + "type": "integer", + "example": 42 + } + } + }, "entity.Role": { "type": "object", "properties": { @@ -1155,6 +1463,32 @@ } } }, + "entity.SumLogTransaction": { + "type": "object", + "properties": { + "amount": { + "type": "array", + "items": { + "type": "number" + }, + "example": [ + 100000 + ] + }, + "asset": { + "$ref": "#/definitions/entity.Asset" + }, + "date": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "2023-08-10T14:30:00Z" + ] + } + } + }, "entity.User": { "type": "object", "properties": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 0a1dcff6..5cf810a8 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -60,6 +60,29 @@ definitions: example: 3 type: integer type: object + entity.LogTransaction: + properties: + amount: + example: 100000 + type: number + asset: + $ref: '#/definitions/entity.Asset' + date: + example: "2023-08-10T14:30:00Z" + type: string + description: + example: Mint Asset + type: string + log_id: + example: 1 + type: integer + transaction_type_id: + example: 1 + type: integer + user_id: + example: 42 + type: integer + type: object entity.Role: properties: id: @@ -78,6 +101,23 @@ definitions: example: Edit type: string type: object + entity.SumLogTransaction: + properties: + amount: + example: + - 100000 + items: + type: number + type: array + asset: + $ref: '#/definitions/entity.Asset' + date: + example: + - "2023-08-10T14:30:00Z" + items: + type: string + type: array + type: object entity.User: properties: created_at: @@ -707,6 +747,184 @@ paths: summary: Create a new contract tags: - Contract + /log_transactions: + get: + consumes: + - application/json + description: Get all transactions logs within a specific time range + parameters: + - description: Time range (e.g., last 24 hours, last 7 days, last 30 days) + in: query + name: time_range + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/entity.LogTransaction' + security: + - ApiKeyAuth: [] + summary: Get all transactions logs + tags: + - Log Transactions + /log_transactions/asset/{asset_id}: + get: + consumes: + - application/json + description: Get all transactions logs for a specific asset + parameters: + - description: Asset ID + in: path + name: asset_id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/entity.LogTransaction' + security: + - ApiKeyAuth: [] + summary: Get transactions logs by Asset ID + tags: + - Log Transactions + /log_transactions/assets/{asset_id}/type/{transaction_type_id}/sum/{time_range}/{time_frame}: + get: + consumes: + - application/json + description: Get sum of amounts for a specific asset, grouped by a specified + time frame (e.g., '1h' for 1 hour) and a specific transaction type + parameters: + - description: Asset ID + in: path + name: asset_id + required: true + type: integer + - description: Transaction type ID (e.g., '0' for all transactions, '1' for + type create asset '2' for mint asset) + in: path + name: transaction_type_id + required: true + type: string + - description: Time range for the query (e.g., '24h' or '1d' '7d' '30d') + in: path + name: time_range + required: true + type: string + - description: Time frame for the query (e.g., '1h' '2h' '24h' '36h') + in: path + name: time_frame + required: true + type: string + produces: + - application/json + responses: + "200": + description: Sum log transaction for the specified asset + schema: + $ref: '#/definitions/entity.SumLogTransaction' + "400": + description: invalid transaction type + schema: + type: string + "500": + description: Internal server error + schema: + type: string + security: + - ApiKeyAuth: [] + summary: Get sum of amounts by Asset ID within a specific time frame + tags: + - Log Transactions + /log_transactions/assets/sum/{time_range}/{time_frame}: + get: + consumes: + - application/json + description: Get sum of amounts for all assets, grouped by a specified time + frame (e.g., '1h' for 1 hour) + parameters: + - description: Time range for the query (e.g., '24h') + in: path + name: time_range + required: true + type: string + - description: Time frame for grouping (e.g., '1h'). Default is '1h' + in: query + name: time_frame + type: string + produces: + - application/json + responses: + "200": + description: Array of sum log transactions + schema: + items: + $ref: '#/definitions/entity.SumLogTransaction' + type: array + "400": + description: Invalid time_frame format + schema: + type: string + "500": + description: Internal server error + schema: + type: string + security: + - ApiKeyAuth: [] + summary: Get sum of amounts for all assets within a specific time frame + tags: + - Log Transactions + /log_transactions/transaction_type/{transaction_type_id}: + get: + consumes: + - application/json + description: Get all transactions logs for a specific transaction type + parameters: + - description: Transaction Type ID + in: path + name: transaction_type_id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/entity.LogTransaction' + security: + - ApiKeyAuth: [] + summary: Get transactions logs by Transaction Type ID + tags: + - Log Transactions + /log_transactions/user/{user_id}: + get: + consumes: + - application/json + description: Get all transactions logs for a specific user + parameters: + - description: User ID + in: path + name: user_id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/entity.LogTransaction' + security: + - ApiKeyAuth: [] + summary: Get transactions logs by User ID + tags: + - Log Transactions /role: get: consumes: diff --git a/backend/internal/app/app.go b/backend/internal/app/app.go index 64467a77..9dd9d26b 100644 --- a/backend/internal/app/app.go +++ b/backend/internal/app/app.go @@ -21,8 +21,6 @@ import ( // Run creates objects via constructors. func Run(cfg *config.Config, pg *postgres.Postgres, pKp, pHor, pEnv entity.ProducerInterface) { - // l := logger.New(cfg.Log.Level) - // Use cases authUc := usecase.NewAuthUseCase( repo.New(pg), cfg.JWT.SecretKey, @@ -54,10 +52,13 @@ func Run(cfg *config.Config, pg *postgres.Postgres, pKp, pHor, pEnv entity.Produ contractUc := usecase.NewContractUseCase( repo.NewContractRepo(pg), ) + logUc := usecase.NewLogTransactionUseCase( + repo.NewLogTransactionRepo(pg), + ) // HTTP Server handler := gin.Default() - v1.NewRouter(handler, pKp, pHor, pEnv, *authUc, *userUc, *walletUc, *assetUc, *roleUc, *rolePermissionUc, *vaultCategoryUc, *vaultUc, *contractUc, cfg.HTTP) + v1.NewRouter(handler, pKp, pHor, pEnv, *authUc, *userUc, *walletUc, *assetUc, *roleUc, *rolePermissionUc, *vaultCategoryUc, *vaultUc, *contractUc, *logUc, cfg.HTTP) httpServer := httpserver.New(handler, httpserver.Port(cfg.HTTP.Port), httpserver.ReadTimeout(60*time.Second), diff --git a/backend/internal/controller/http/v1/assets.go b/backend/internal/controller/http/v1/assets.go index 36f6ed10..5a019ae9 100644 --- a/backend/internal/controller/http/v1/assets.go +++ b/backend/internal/controller/http/v1/assets.go @@ -3,6 +3,7 @@ package v1 import ( "fmt" "net/http" + "strconv" "github.com/CheesecakeLabs/token-factory-v2/backend/internal/entity" "github.com/CheesecakeLabs/token-factory-v2/backend/internal/usecase" @@ -19,10 +20,11 @@ type assetsRoutes struct { as usecase.AssetUseCase m HTTPControllerMessenger a usecase.AuthUseCase + l usecase.LogTransactionUseCase } -func newAssetsRoutes(handler *gin.RouterGroup, w usecase.WalletUseCase, as usecase.AssetUseCase, m HTTPControllerMessenger, a usecase.AuthUseCase) { - r := &assetsRoutes{w, as, m, a} +func newAssetsRoutes(handler *gin.RouterGroup, w usecase.WalletUseCase, as usecase.AssetUseCase, m HTTPControllerMessenger, a usecase.AuthUseCase, l usecase.LogTransactionUseCase) { + r := &assetsRoutes{w, as, m, a, l} h := handler.Group("/assets").Use(Auth(r.a.ValidateToken())) { h.GET("", r.getAllAssets) @@ -217,6 +219,34 @@ func (r *assetsRoutes) createAsset(c *gin.Context) { return } + token := c.Request.Header.Get("Authorization") + user, err := r.a.GetUserByToken(token) + if err != nil { + errorResponse(c, http.StatusNotFound, "user not found", err) + return + } + + userID, err := strconv.Atoi(user.ID) + if err != nil { + errorResponse(c, http.StatusNotFound, "error to parse user id", err) + } + + amount, err := strconv.ParseFloat(request.Amount, 64) + if err != nil { + amount = 0 + } + err = r.l.CreateLogTransaction(entity.LogTransaction{ + Asset: asset, + Amount: amount, + TransactionTypeID: entity.CreateAsset, + UserID: userID, + Description: createLogDescription(entity.CreateAsset, asset.Code, nil, nil), + }) + if err != nil { + errorResponse(c, http.StatusNotFound, "error to create log transaction", err) + return + } + c.JSON(http.StatusOK, asset) } @@ -275,6 +305,34 @@ func (r *assetsRoutes) mintAsset(c *gin.Context) { return } + token := c.Request.Header.Get("Authorization") + user, err := r.a.GetUserByToken(token) + if err != nil { + errorResponse(c, http.StatusNotFound, "user not found", err) + return + } + + userID, err := strconv.Atoi(user.ID) + if err != nil { + errorResponse(c, http.StatusNotFound, "error to parse user id", err) + } + + amount, err := strconv.ParseFloat(request.Amount, 64) + if err != nil { + amount = 0 + } + err = r.l.CreateLogTransaction(entity.LogTransaction{ + Asset: asset, + Amount: amount, + TransactionTypeID: entity.MintAsset, + UserID: userID, + Description: createLogDescription(entity.MintAsset, asset.Code, nil, nil), + }) + if err != nil { + errorResponse(c, http.StatusNotFound, "error to create log transaction", err) + return + } + c.JSON(http.StatusOK, gin.H{"message": "asset minted"}) } @@ -335,6 +393,34 @@ func (r *assetsRoutes) burnAsset(c *gin.Context) { } + token := c.Request.Header.Get("Authorization") + user, err := r.a.GetUserByToken(token) + if err != nil { + errorResponse(c, http.StatusNotFound, "user not found", err) + return + } + + userID, err := strconv.Atoi(user.ID) + if err != nil { + errorResponse(c, http.StatusNotFound, "error to parse user id", err) + } + + amount, err := strconv.ParseFloat(request.Amount, 64) + if err != nil { + amount = 0 + } + err = r.l.CreateLogTransaction(entity.LogTransaction{ + Asset: asset, + TransactionTypeID: entity.BurnAsset, + Amount: amount, + UserID: userID, + Description: createLogDescription(entity.BurnAsset, asset.Code, nil, nil), + }) + if err != nil { + errorResponse(c, http.StatusNotFound, "error to create log transaction", err) + return + } + c.JSON(http.StatusOK, gin.H{ "message": "Asset burned successfully", }) @@ -401,6 +487,34 @@ func (r *assetsRoutes) transferAsset(c *gin.Context) { errorResponse(c, http.StatusInternalServerError, "starlabs messaging problems", err) } + token := c.Request.Header.Get("Authorization") + user, err := r.a.GetUserByToken(token) + if err != nil { + errorResponse(c, http.StatusNotFound, "user not found", err) + return + } + + userID, err := strconv.Atoi(user.ID) + if err != nil { + errorResponse(c, http.StatusNotFound, "error to parse user id", err) + } + + amount, err := strconv.ParseFloat(request.Amount, 64) + if err != nil { + amount = 0 + } + err = r.l.CreateLogTransaction(entity.LogTransaction{ + Asset: asset, + Amount: amount, + TransactionTypeID: entity.TransferAsset, + UserID: userID, + Description: createLogDescription(entity.TransferAsset, asset.Code, nil, nil), + }) + if err != nil { + errorResponse(c, http.StatusNotFound, "error to create log transaction", err) + return + } + c.JSON(http.StatusOK, gin.H{"message": "asset transferred"}) } @@ -443,7 +557,7 @@ func (r *assetsRoutes) clawbackAsset(c *gin.Context) { Type: entity.ClawbackOp, Target: asset.Issuer.Key.PublicKey, Origin: request.From, - Amount: request.Amount, + Amount: fmt.Sprintf("%v", request.Amount), Sponsor: sponsor.Key.PublicKey, Asset: entity.OpAsset{ Code: request.Code, @@ -461,6 +575,34 @@ func (r *assetsRoutes) clawbackAsset(c *gin.Context) { errorResponse(c, http.StatusInternalServerError, "starlabs messaging problems", err) } + token := c.Request.Header.Get("Authorization") + user, err := r.a.GetUserByToken(token) + if err != nil { + errorResponse(c, http.StatusNotFound, "user not found", err) + return + } + + userID, err := strconv.Atoi(user.ID) + if err != nil { + errorResponse(c, http.StatusNotFound, "error to parse user id", err) + } + + amount, err := strconv.ParseFloat(request.Amount, 64) + if err != nil { + amount = 0 + } + err = r.l.CreateLogTransaction(entity.LogTransaction{ + Asset: asset, + TransactionTypeID: entity.ClawbackAsset, + Amount: amount, + UserID: userID, + Description: createLogDescription(entity.ClawbackAsset, asset.Code, nil, nil), + }) + if err != nil { + errorResponse(c, http.StatusNotFound, "error to create log transaction", err) + return + } + c.JSON(http.StatusOK, gin.H{"message": "asset clawed back"}) } @@ -528,6 +670,29 @@ func (r *assetsRoutes) updateAuthFlags(c *gin.Context) { } + token := c.Request.Header.Get("Authorization") + user, err := r.a.GetUserByToken(token) + if err != nil { + errorResponse(c, http.StatusNotFound, "user not found", err) + return + } + + userID, err := strconv.Atoi(user.ID) + if err != nil { + errorResponse(c, http.StatusNotFound, "error to parse user id", err) + } + + err = r.l.CreateLogTransaction(entity.LogTransaction{ + Asset: asset, + TransactionTypeID: entity.UpdateAuthFlags, + UserID: userID, + Description: createLogDescription(entity.UpdateAuthFlags, asset.Code, request.SetFlags, request.ClearFlags), + }) + if err != nil { + errorResponse(c, http.StatusNotFound, "error to create log transaction", err) + return + } + c.JSON(http.StatusOK, gin.H{"message": "authorization flags updated"}) } diff --git a/backend/internal/controller/http/v1/log_transaction.go b/backend/internal/controller/http/v1/log_transaction.go new file mode 100644 index 00000000..4be3a7d1 --- /dev/null +++ b/backend/internal/controller/http/v1/log_transaction.go @@ -0,0 +1,218 @@ +package v1 + +import ( + "fmt" + "net/http" + "strconv" + "time" + + "github.com/CheesecakeLabs/token-factory-v2/backend/internal/usecase" + "github.com/gin-gonic/gin" +) + +type logTransactionsRoutes struct { + w usecase.WalletUseCase + as usecase.AssetUseCase + m HTTPControllerMessenger + l usecase.LogTransactionUseCase + a usecase.AuthUseCase +} + +func newLogTransactionsRoutes(handler *gin.RouterGroup, w usecase.WalletUseCase, as usecase.AssetUseCase, m HTTPControllerMessenger, l usecase.LogTransactionUseCase, a usecase.AuthUseCase) { + r := &logTransactionsRoutes{w, as, m, l, a} + h := handler.Group("/log_transactions").Use(Auth(r.a.ValidateToken())) + { + h.GET("/:time_range", r.getLogTransactions) + h.GET("/assets/:asset_id/:time_range", r.getLogTransactionsByAssetID) + h.GET("/user/:user_id/:time_range", r.getLogTransactionsByUserID) + h.GET("/transaction_type/:transaction_type_id/:time_range", r.getLogTransactionsByTransactionTypeID) + h.GET("/assets/:asset_id/type/:transaction_type_id/sum/:time_range/:time_frame", r.sumAmountsByAssetID) + h.GET("/assets/sum/:time_range/:time_frame", r.sumAmountsForAllAssets) + } +} + +// @Summary Get all transactions logs +// @Description Get all transactions logs within a specific time range +// @Tags Log Transactions +// @Accept json +// @Produce json +// @Param time_range query string true "Time range (e.g., last 24 hours, last 7 days, last 30 days)" +// @Security ApiKeyAuth +// @Success 200 {object} entity.LogTransaction +// @Router /log_transactions [get] +func (r *logTransactionsRoutes) getLogTransactions(c *gin.Context) { + timeRange := c.Param("time_range") + + logTransactions, err := r.l.GetLogTransactions(timeRange) + if err != nil { + errorResponse(c, http.StatusInternalServerError, "error getting log transactions: %s", err) + return + } + + c.JSON(http.StatusOK, logTransactions) +} + +// @Summary Get transactions logs by Asset ID +// @Description Get all transactions logs for a specific asset +// @Tags Log Transactions +// @Accept json +// @Produce json +// @Param asset_id path int true "Asset ID" +// @Security ApiKeyAuth +// @Success 200 {object} entity.LogTransaction +// @Router /log_transactions/asset/{asset_id} [get] +func (r *logTransactionsRoutes) getLogTransactionsByAssetID(c *gin.Context) { + assetIDStr := c.Param("asset_id") + timeRange := c.Param("time_range") + + assetID, err := strconv.Atoi(assetIDStr) + if err != nil { + errorResponse(c, http.StatusBadRequest, fmt.Sprintf("invalid asset ID: %s", err.Error()), err) + return + } + + logTransactions, err := r.l.GetLogTransactionsByAssetID(assetID, timeRange) + if err != nil { + errorResponse(c, http.StatusInternalServerError, fmt.Sprintf("error getting log transactions: %s", err.Error()), err) + return + } + + c.JSON(http.StatusOK, logTransactions) +} + +// @Summary Get transactions logs by User ID +// @Description Get all transactions logs for a specific user +// @Tags Log Transactions +// @Accept json +// @Produce json +// @Param user_id path int true "User ID" +// @Security ApiKeyAuth +// @Success 200 {object} entity.LogTransaction +// @Router /log_transactions/user/{user_id} [get] +func (r *logTransactionsRoutes) getLogTransactionsByUserID(c *gin.Context) { + userIDStr := c.Param("user_id") + timeRange := c.Param("time_range") + + userID, err := strconv.Atoi(userIDStr) + if err != nil { + errorResponse(c, http.StatusBadRequest, fmt.Sprintf("invalid user ID: %s", err.Error()), err) + return + } + + logTransactions, err := r.l.GetLogTransactionsByUserID(userID, timeRange) + if err != nil { + errorResponse(c, http.StatusInternalServerError, fmt.Sprintf("error getting log transactions: %s", err.Error()), err) + return + } + + c.JSON(http.StatusOK, logTransactions) +} + +// @Summary Get transactions logs by Transaction Type ID +// @Description Get all transactions logs for a specific transaction type +// @Tags Log Transactions +// @Accept json +// @Produce json +// @Param transaction_type_id path int true "Transaction Type ID" +// @Security ApiKeyAuth +// @Success 200 {object} entity.LogTransaction +// @Router /log_transactions/transaction_type/{transaction_type_id} [get] +func (r *logTransactionsRoutes) getLogTransactionsByTransactionTypeID(c *gin.Context) { + transactionTypeIDStr := c.Param("transaction_type_id") + timeRange := c.Param("time_range") + + transactionTypeID, err := strconv.Atoi(transactionTypeIDStr) + if err != nil { + errorResponse(c, http.StatusBadRequest, fmt.Sprintf("invalid asset ID: %s", err.Error()), err) + return + } + + logTransactions, err := r.l.GetLogTransactionsByTransactionTypeID(transactionTypeID, timeRange) + if err != nil { + errorResponse(c, http.StatusInternalServerError, fmt.Sprintf("error getting log transactions: %s", err.Error()), err) + return + } + + c.JSON(http.StatusOK, logTransactions) +} + +// @Summary Get sum of amounts by Asset ID within a specific time frame +// @Description Get sum of amounts for a specific asset, grouped by a specified time frame (e.g., '1h' for 1 hour) and a specific transaction type +// @Tags Log Transactions +// @Accept json +// @Produce json +// @Param asset_id path int true "Asset ID" +// @Param transaction_type_id path string true "Transaction type ID (e.g., '0' for all transactions, '1' for type create asset '2' for mint asset)" +// @Param time_range path string true "Time range for the query (e.g., '24h' or '1d' '7d' '30d')" +// @Param time_frame path string true "Time frame for the query (e.g., '1h' '2h' '24h' '36h')" +// @Security ApiKeyAuth +// @Success 200 {object} entity.SumLogTransaction "Sum log transaction for the specified asset" +// @Failure 400 {string} string "invalid transaction type" +// @Failure 500 {string} string "Internal server error" +// @Router /log_transactions/assets/{asset_id}/type/{transaction_type_id}/sum/{time_range}/{time_frame} [get] +func (r *logTransactionsRoutes) sumAmountsByAssetID(c *gin.Context) { + assetIDStr := c.Param("asset_id") + transactionTypeStr := c.Param("transaction_type_id") + timeRange := c.Param("time_range") + timeFrame := c.Param("time_frame") + + if transactionTypeStr == "" { + transactionTypeStr = "0" + } + + // Convert transactionTypeStr to integer + transactionType, err := strconv.Atoi(transactionTypeStr) + if err != nil { + errorResponse(c, http.StatusBadRequest, fmt.Sprintf("invalid transaction type: %s", err.Error()), err) + return + } + duration, err := time.ParseDuration(timeFrame) + if err != nil { + errorResponse(c, http.StatusBadRequest, "Invalid time_frame format", err) + return + } + + assetID, err := strconv.Atoi(assetIDStr) + if err != nil { + errorResponse(c, http.StatusBadRequest, fmt.Sprintf("invalid asset ID: %s", err.Error()), err) + return + } + + sum, err := r.l.SumLogTransactionsByAssetID(assetID, timeRange, duration, transactionType) + if err != nil { + errorResponse(c, http.StatusInternalServerError, fmt.Sprintf("error getting log transactions: %s", err.Error()), err) + return + } + + c.JSON(http.StatusOK, sum) +} + +// @Summary Get sum of amounts for all assets within a specific time frame +// @Description Get sum of amounts for all assets, grouped by a specified time frame (e.g., '1h' for 1 hour) +// @Tags Log Transactions +// @Accept json +// @Produce json +// @Param time_range path string true "Time range for the query (e.g., '24h')" +// @Param time_frame query string false "Time frame for grouping (e.g., '1h'). Default is '1h'" +// @Security ApiKeyAuth +// @Success 200 {array} entity.SumLogTransaction "Array of sum log transactions" +// @Failure 400 {string} string "Invalid time_frame format" +// @Failure 500 {string} string "Internal server error" +// @Router /log_transactions/assets/sum/{time_range}/{time_frame} [get] +func (r *logTransactionsRoutes) sumAmountsForAllAssets(c *gin.Context) { + timeRange := c.Param("time_range") + timeFrame := c.Param("time_frame") + + duration, err := time.ParseDuration(timeFrame) + if err != nil { + errorResponse(c, http.StatusBadRequest, "Invalid time_frame format", err) + return + } + sum, err := r.l.SumLogTransactions(timeRange, duration) + if err != nil { + errorResponse(c, http.StatusInternalServerError, fmt.Sprintf("error getting log transactions: %s", err.Error()), err) + return + } + + c.JSON(http.StatusOK, sum) +} diff --git a/backend/internal/controller/http/v1/router.go b/backend/internal/controller/http/v1/router.go index 57a5edb9..d70458a5 100644 --- a/backend/internal/controller/http/v1/router.go +++ b/backend/internal/controller/http/v1/router.go @@ -45,6 +45,7 @@ func NewRouter( vaultCategoryUc usecase.VaultCategoryUseCase, vaultUc usecase.VaultUseCase, contractUc usecase.ContractUseCase, + logUc usecase.LogTransactionUseCase, cfg config.HTTP, ) { // Messenger @@ -63,11 +64,12 @@ func NewRouter( { newUserRoutes(groupV1, userUseCase, authUseCase, rolePermissionUc) newWalletsRoutes(groupV1, walletUseCase, messengerController, authUseCase) - newAssetsRoutes(groupV1, walletUseCase, assetUseCase, messengerController, authUseCase) + newAssetsRoutes(groupV1, walletUseCase, assetUseCase, messengerController, authUseCase, logUc) newRoleRoutes(groupV1, roleUseCase, messengerController) newRolePermissionsRoutes(groupV1, rolePermissionUc, messengerController) newVaultCategoryRoutes(groupV1, messengerController, authUseCase, vaultCategoryUc) newVaultRoutes(groupV1, messengerController, authUseCase, vaultUc, vaultCategoryUc, walletUseCase, assetUseCase) newContractRoutes(groupV1, messengerController, authUseCase, contractUc, vaultUc, assetUseCase) + newLogTransactionsRoutes(groupV1, walletUseCase, assetUseCase, messengerController, logUc, authUseCase) } } diff --git a/backend/internal/controller/http/v1/utils.go b/backend/internal/controller/http/v1/utils.go index 2939525c..80ae2b17 100644 --- a/backend/internal/controller/http/v1/utils.go +++ b/backend/internal/controller/http/v1/utils.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "encoding/json" "fmt" + "strings" "github.com/CheesecakeLabs/token-factory-v2/backend/internal/entity" "github.com/bitly/go-notify" @@ -36,7 +37,6 @@ func (m *HTTPControllerMessenger) SendMessage(chanName string, value interface{} res := <-channel notify.Stop(msgKey, channel) - if notifyData, ok := res.(*entity.NotifyData); ok { switch msg := notifyData.Message.(type) { case entity.EnvelopeResponse: @@ -81,3 +81,28 @@ func (m *HTTPControllerMessenger) produce(chanName string, msgKey string, value } return } + +func createLogDescription(transaction int, assetCode string, setFlags, clearFlags []string) string { + switch transaction { + case entity.CreateAsset: + return fmt.Sprintf("Operation: Create Asset | Asset Code: %s", assetCode) + case entity.MintAsset: + return fmt.Sprintf("Operation: Mint | Asset Code: %s", assetCode) + case entity.BurnAsset: + return fmt.Sprintf("Operation: Burn | Asset Code: %s", assetCode) + case entity.ClawbackAsset: + return fmt.Sprintf("Operation: Clawback | Asset Code: %s", assetCode) + case entity.TransferAsset: + return fmt.Sprintf("Operation: Transfer | Asset Code: %s", assetCode) + case entity.UpdateAuthFlags: + if len(setFlags) == 0 { + setFlags = []string{"none"} + } + if len(clearFlags) == 0 { + clearFlags = []string{"none"} + } + return fmt.Sprintf("Operation: Update Auth Flags | Asset Code: %s | Set Flags: %s | Clear Flags: %s", assetCode, strings.Join(setFlags, ","), strings.Join(clearFlags, ",")) + default: + return "Unrecognized transaction type" + } +} diff --git a/backend/internal/controller/http/v1/utils_test.go b/backend/internal/controller/http/v1/utils_test.go index ea872015..b67afe0d 100644 --- a/backend/internal/controller/http/v1/utils_test.go +++ b/backend/internal/controller/http/v1/utils_test.go @@ -34,3 +34,28 @@ func TestSendMessage(t *testing.T) { require.EqualValues(t, mockResponse, actualData) } + +func TestCreateLogDescription(t *testing.T) { + tests := []struct { + name string + transaction int + assetCode string + setFlags []string + clearFlags []string + expected string + }{ + {"Test Mint Asset", entity.MintAsset, "USDC", nil, nil, "Operation: Mint | Asset Code: USDC"}, + {"Test Burn Asset", entity.BurnAsset, "USDC", nil, nil, "Operation: Burn | Asset Code: USDC"}, + {"Test Update Auth Flags", entity.UpdateAuthFlags, "USDC", []string{"flag1"}, []string{"flag2"}, "Operation: Update Auth Flags | Asset Code: USDC | Set Flags: flag1 | Clear Flags: flag2"}, + {"Test Unrecognized Transaction", 999, "USDC", nil, nil, "Unrecognized transaction type"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := createLogDescription(tt.transaction, tt.assetCode, tt.setFlags, tt.clearFlags) + if actual != tt.expected { + t.Errorf("expected: %v, actual: %v", tt.expected, actual) + } + }) + } +} diff --git a/backend/internal/entity/transaction_log.go b/backend/internal/entity/transaction_log.go new file mode 100644 index 00000000..39292c9c --- /dev/null +++ b/backend/internal/entity/transaction_log.go @@ -0,0 +1,27 @@ +package entity + +type LogTransaction struct { + LogID int `json:"log_id" example:"1"` + UserID int `json:"user_id" example:"42"` + TransactionTypeID int `json:"transaction_type_id" example:"1"` + Asset Asset `json:"asset"` + Date string `json:"date" example:"2023-08-10T14:30:00Z"` + Amount float64 `json:"amount" example:"100000"` + Description string `json:"description" example:"Mint Asset"` +} + +type SumLogTransaction struct { + Asset Asset `json:"asset"` + Amount []float64 `json:"amount" example:"100000"` + Date []string `json:"date" example:"2023-08-10T14:30:00Z"` + Quantity []int `json:"quantity" example:"1"` +} + +const ( + CreateAsset int = iota + 1 // CreateAsset = 1 + MintAsset // MintAsset = 2 + UpdateAuthFlags // UpdateAuthFlags = 3 + ClawbackAsset // ClawbackAsset = 4 + BurnAsset // BurnAsset = 5 + TransferAsset // TransferAsset = 6 +) diff --git a/backend/internal/usecase/auth.go b/backend/internal/usecase/auth.go index 3a1684bd..c968360a 100644 --- a/backend/internal/usecase/auth.go +++ b/backend/internal/usecase/auth.go @@ -1,5 +1,7 @@ package usecase +import "github.com/CheesecakeLabs/token-factory-v2/backend/internal/entity" + // Auth Use Case type AuthUseCase struct { repo UserRepo @@ -29,3 +31,11 @@ func (uc *AuthUseCase) ValidateToken() string { } return uc.jwtSecretKey } + +func (uc *AuthUseCase) GetUserByToken(token string) (entity.User, error) { + user, err := uc.repo.GetUserByToken(token) + if err != nil { + return entity.User{}, err + } + return user, nil +} diff --git a/backend/internal/usecase/interfaces.go b/backend/internal/usecase/interfaces.go index d7b94e47..8f193ab3 100644 --- a/backend/internal/usecase/interfaces.go +++ b/backend/internal/usecase/interfaces.go @@ -1,6 +1,10 @@ package usecase -import "github.com/CheesecakeLabs/token-factory-v2/backend/internal/entity" +import ( + "time" + + "github.com/CheesecakeLabs/token-factory-v2/backend/internal/entity" +) // mockgen -source=internal/usecase/interfaces.go -destination=internal/usecase/mocks/mocks.go -package=mocks @@ -74,4 +78,14 @@ type ( CreateContract(entity.Contract) (entity.Contract, error) GetContractById(id string) (entity.Contract, error) } + + LogTransactionRepoInterface interface { + StoreLogTransaction(entity.LogTransaction) error + GetLogTransactions(timeRange string) ([]entity.LogTransaction, error) + GetLogTransactionsByAssetID(assetID int, timeRange string) ([]entity.LogTransaction, error) + GetLogTransactionsByUserID(userID int, timeRange string) ([]entity.LogTransaction, error) + GetLogTransactionsByTransactionTypeID(transactionTypeID int, timeRange string) ([]entity.LogTransaction, error) + SumLogTransactionsByAssetID(assetID int, timeRange string, timeFrame time.Duration, transactionType int) (entity.SumLogTransaction, error) + SumLogTransactions(timeRange string, timeFrame time.Duration) ([]entity.SumLogTransaction, error) + } ) diff --git a/backend/internal/usecase/log_transaction.go b/backend/internal/usecase/log_transaction.go new file mode 100644 index 00000000..60777760 --- /dev/null +++ b/backend/internal/usecase/log_transaction.go @@ -0,0 +1,71 @@ +package usecase + +import ( + "time" + + "github.com/CheesecakeLabs/token-factory-v2/backend/internal/entity" +) + +type LogTransactionUseCase struct { + lRepo LogTransactionRepoInterface +} + +func NewLogTransactionUseCase(lRepo LogTransactionRepoInterface) *LogTransactionUseCase { + return &LogTransactionUseCase{lRepo} +} + +func (l *LogTransactionUseCase) CreateLogTransaction(logTransaction entity.LogTransaction) error { + err := l.lRepo.StoreLogTransaction(logTransaction) + if err != nil { + return err + } + return nil +} + +func (l *LogTransactionUseCase) GetLogTransactionsByAssetID(assetId int, timeRange string) ([]entity.LogTransaction, error) { + logTransactions, err := l.lRepo.GetLogTransactionsByAssetID(assetId, timeRange) + if err != nil { + return nil, err + } + return logTransactions, nil +} + +func (l *LogTransactionUseCase) GetLogTransactionsByUserID(userId int, timeRange string) ([]entity.LogTransaction, error) { + logTransactions, err := l.lRepo.GetLogTransactionsByUserID(userId, timeRange) + if err != nil { + return nil, err + } + return logTransactions, nil +} + +func (l *LogTransactionUseCase) GetLogTransactions(timeRange string) ([]entity.LogTransaction, error) { + logTransactions, err := l.lRepo.GetLogTransactions(timeRange) + if err != nil { + return nil, err + } + return logTransactions, nil +} + +func (l *LogTransactionUseCase) GetLogTransactionsByTransactionTypeID(transactionTypeID int, timeRange string) ([]entity.LogTransaction, error) { + logTransactions, err := l.lRepo.GetLogTransactionsByTransactionTypeID(transactionTypeID, timeRange) + if err != nil { + return nil, err + } + return logTransactions, nil +} + +func (l *LogTransactionUseCase) SumLogTransactions(timeRange string, timeFrame time.Duration) ([]entity.SumLogTransaction, error) { + sum, err := l.lRepo.SumLogTransactions(timeRange, timeFrame) + if err != nil { + return []entity.SumLogTransaction{}, err + } + return sum, nil +} + +func (l *LogTransactionUseCase) SumLogTransactionsByAssetID(assetID int, timeRange string, duration time.Duration, transactionType int) (entity.SumLogTransaction, error) { + sum, err := l.lRepo.SumLogTransactionsByAssetID(assetID, timeRange, duration, transactionType) + if err != nil { + return entity.SumLogTransaction{}, err + } + return sum, nil +} diff --git a/backend/internal/usecase/mocks/mocks.go b/backend/internal/usecase/mocks/mocks.go index d477e7b8..ecd8298f 100644 --- a/backend/internal/usecase/mocks/mocks.go +++ b/backend/internal/usecase/mocks/mocks.go @@ -6,6 +6,7 @@ package mocks import ( reflect "reflect" + time "time" entity "github.com/CheesecakeLabs/token-factory-v2/backend/internal/entity" usecase "github.com/CheesecakeLabs/token-factory-v2/backend/internal/usecase" @@ -641,6 +642,21 @@ func (mr *MockVaultRepoInterfaceMockRecorder) CreateVault(arg0 interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateVault", reflect.TypeOf((*MockVaultRepoInterface)(nil).CreateVault), arg0) } +// DeleteVault mocks base method. +func (m *MockVaultRepoInterface) DeleteVault(arg0 entity.Vault) (entity.Vault, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteVault", arg0) + ret0, _ := ret[0].(entity.Vault) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteVault indicates an expected call of DeleteVault. +func (mr *MockVaultRepoInterfaceMockRecorder) DeleteVault(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteVault", reflect.TypeOf((*MockVaultRepoInterface)(nil).DeleteVault), arg0) +} + // GetVaultById mocks base method. func (m *MockVaultRepoInterface) GetVaultById(id int) (entity.Vault, error) { m.ctrl.T.Helper() @@ -683,20 +699,200 @@ func (m *MockVaultRepoInterface) UpdateVault(arg0 entity.Vault) (entity.Vault, e // UpdateVault indicates an expected call of UpdateVault. func (mr *MockVaultRepoInterfaceMockRecorder) UpdateVault(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateVault", reflect.TypeOf((*MockVaultRepoInterface)(nil).DeleteVault), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateVault", reflect.TypeOf((*MockVaultRepoInterface)(nil).UpdateVault), arg0) } -// DeleteVault mocks base method. -func (m *MockVaultRepoInterface) DeleteVault(arg0 entity.Vault) (entity.Vault, error) { +// MockContractRepoInterface is a mock of ContractRepoInterface interface. +type MockContractRepoInterface struct { + ctrl *gomock.Controller + recorder *MockContractRepoInterfaceMockRecorder +} + +// MockContractRepoInterfaceMockRecorder is the mock recorder for MockContractRepoInterface. +type MockContractRepoInterfaceMockRecorder struct { + mock *MockContractRepoInterface +} + +// NewMockContractRepoInterface creates a new mock instance. +func NewMockContractRepoInterface(ctrl *gomock.Controller) *MockContractRepoInterface { + mock := &MockContractRepoInterface{ctrl: ctrl} + mock.recorder = &MockContractRepoInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockContractRepoInterface) EXPECT() *MockContractRepoInterfaceMockRecorder { + return m.recorder +} + +// CreateContract mocks base method. +func (m *MockContractRepoInterface) CreateContract(arg0 entity.Contract) (entity.Contract, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteVault", arg0) - ret0, _ := ret[0].(entity.Vault) + ret := m.ctrl.Call(m, "CreateContract", arg0) + ret0, _ := ret[0].(entity.Contract) ret1, _ := ret[1].(error) return ret0, ret1 } -// DeleteVault indicates an expected call of DeleteVault. -func (mr *MockVaultRepoInterfaceMockRecorder) DeleteVault(arg0 interface{}) *gomock.Call { +// CreateContract indicates an expected call of CreateContract. +func (mr *MockContractRepoInterfaceMockRecorder) CreateContract(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteVault", reflect.TypeOf((*MockVaultRepoInterface)(nil).DeleteVault), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateContract", reflect.TypeOf((*MockContractRepoInterface)(nil).CreateContract), arg0) +} + +// GetContractById mocks base method. +func (m *MockContractRepoInterface) GetContractById(id string) (entity.Contract, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetContractById", id) + ret0, _ := ret[0].(entity.Contract) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetContractById indicates an expected call of GetContractById. +func (mr *MockContractRepoInterfaceMockRecorder) GetContractById(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContractById", reflect.TypeOf((*MockContractRepoInterface)(nil).GetContractById), id) +} + +// GetContracts mocks base method. +func (m *MockContractRepoInterface) GetContracts() ([]entity.Contract, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetContracts") + ret0, _ := ret[0].([]entity.Contract) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetContracts indicates an expected call of GetContracts. +func (mr *MockContractRepoInterfaceMockRecorder) GetContracts() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContracts", reflect.TypeOf((*MockContractRepoInterface)(nil).GetContracts)) +} + +// MockLogTransactionRepoInterface is a mock of LogTransactionRepoInterface interface. +type MockLogTransactionRepoInterface struct { + ctrl *gomock.Controller + recorder *MockLogTransactionRepoInterfaceMockRecorder +} + +// MockLogTransactionRepoInterfaceMockRecorder is the mock recorder for MockLogTransactionRepoInterface. +type MockLogTransactionRepoInterfaceMockRecorder struct { + mock *MockLogTransactionRepoInterface +} + +// NewMockLogTransactionRepoInterface creates a new mock instance. +func NewMockLogTransactionRepoInterface(ctrl *gomock.Controller) *MockLogTransactionRepoInterface { + mock := &MockLogTransactionRepoInterface{ctrl: ctrl} + mock.recorder = &MockLogTransactionRepoInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLogTransactionRepoInterface) EXPECT() *MockLogTransactionRepoInterfaceMockRecorder { + return m.recorder +} + +// GetLogTransactions mocks base method. +func (m *MockLogTransactionRepoInterface) GetLogTransactions(timeRange string) ([]entity.LogTransaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLogTransactions", timeRange) + ret0, _ := ret[0].([]entity.LogTransaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLogTransactions indicates an expected call of GetLogTransactions. +func (mr *MockLogTransactionRepoInterfaceMockRecorder) GetLogTransactions(timeRange interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogTransactions", reflect.TypeOf((*MockLogTransactionRepoInterface)(nil).GetLogTransactions), timeRange) +} + +// GetLogTransactionsByAssetID mocks base method. +func (m *MockLogTransactionRepoInterface) GetLogTransactionsByAssetID(assetID int, timeRange string) ([]entity.LogTransaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLogTransactionsByAssetID", assetID, timeRange) + ret0, _ := ret[0].([]entity.LogTransaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLogTransactionsByAssetID indicates an expected call of GetLogTransactionsByAssetID. +func (mr *MockLogTransactionRepoInterfaceMockRecorder) GetLogTransactionsByAssetID(assetID, timeRange interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogTransactionsByAssetID", reflect.TypeOf((*MockLogTransactionRepoInterface)(nil).GetLogTransactionsByAssetID), assetID, timeRange) +} + +// GetLogTransactionsByTransactionTypeID mocks base method. +func (m *MockLogTransactionRepoInterface) GetLogTransactionsByTransactionTypeID(transactionTypeID int, timeRange string) ([]entity.LogTransaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLogTransactionsByTransactionTypeID", transactionTypeID, timeRange) + ret0, _ := ret[0].([]entity.LogTransaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLogTransactionsByTransactionTypeID indicates an expected call of GetLogTransactionsByTransactionTypeID. +func (mr *MockLogTransactionRepoInterfaceMockRecorder) GetLogTransactionsByTransactionTypeID(transactionTypeID, timeRange interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogTransactionsByTransactionTypeID", reflect.TypeOf((*MockLogTransactionRepoInterface)(nil).GetLogTransactionsByTransactionTypeID), transactionTypeID, timeRange) +} + +// GetLogTransactionsByUserID mocks base method. +func (m *MockLogTransactionRepoInterface) GetLogTransactionsByUserID(userID int, timeRange string) ([]entity.LogTransaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLogTransactionsByUserID", userID, timeRange) + ret0, _ := ret[0].([]entity.LogTransaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLogTransactionsByUserID indicates an expected call of GetLogTransactionsByUserID. +func (mr *MockLogTransactionRepoInterfaceMockRecorder) GetLogTransactionsByUserID(userID, timeRange interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogTransactionsByUserID", reflect.TypeOf((*MockLogTransactionRepoInterface)(nil).GetLogTransactionsByUserID), userID, timeRange) +} + +// StoreLogTransaction mocks base method. +func (m *MockLogTransactionRepoInterface) StoreLogTransaction(arg0 entity.LogTransaction) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StoreLogTransaction", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// StoreLogTransaction indicates an expected call of StoreLogTransaction. +func (mr *MockLogTransactionRepoInterfaceMockRecorder) StoreLogTransaction(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoreLogTransaction", reflect.TypeOf((*MockLogTransactionRepoInterface)(nil).StoreLogTransaction), arg0) +} + +// SumLogTransactions mocks base method. +func (m *MockLogTransactionRepoInterface) SumLogTransactions(timeRange string, timeFrame time.Duration) ([]entity.SumLogTransaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SumLogTransactions", timeRange, timeFrame) + ret0, _ := ret[0].([]entity.SumLogTransaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SumLogTransactions indicates an expected call of SumLogTransactions. +func (mr *MockLogTransactionRepoInterfaceMockRecorder) SumLogTransactions(timeRange, timeFrame interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SumLogTransactions", reflect.TypeOf((*MockLogTransactionRepoInterface)(nil).SumLogTransactions), timeRange, timeFrame) +} + +// SumLogTransactionsByAssetID mocks base method. +func (m *MockLogTransactionRepoInterface) SumLogTransactionsByAssetID(assetID int, timeRange string, timeFrame time.Duration) (entity.SumLogTransaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SumLogTransactionsByAssetID", assetID, timeRange, timeFrame) + ret0, _ := ret[0].(entity.SumLogTransaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SumLogTransactionsByAssetID indicates an expected call of SumLogTransactionsByAssetID. +func (mr *MockLogTransactionRepoInterfaceMockRecorder) SumLogTransactionsByAssetID(assetID, timeRange, timeFrame interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SumLogTransactionsByAssetID", reflect.TypeOf((*MockLogTransactionRepoInterface)(nil).SumLogTransactionsByAssetID), assetID, timeRange, timeFrame) } diff --git a/backend/internal/usecase/repo/asset_postgres.go b/backend/internal/usecase/repo/asset_postgres.go index 88694eef..2de12aef 100644 --- a/backend/internal/usecase/repo/asset_postgres.go +++ b/backend/internal/usecase/repo/asset_postgres.go @@ -44,7 +44,8 @@ func (r AssetRepo) GetAssets() ([]entity.Asset, error) { JOIN wallet d ON a.distributor_id = d.id JOIN key dk ON d.id = dk.wallet_id JOIN wallet i ON a.issuer_id = i.id - JOIN key ik ON i.id = ik.wallet_id; + JOIN key ik ON i.id = ik.wallet_id + ORDER BY a.name; ` rows, err := r.Db.Query(query) diff --git a/backend/internal/usecase/repo/log_transaction_postgres.go b/backend/internal/usecase/repo/log_transaction_postgres.go new file mode 100644 index 00000000..10130475 --- /dev/null +++ b/backend/internal/usecase/repo/log_transaction_postgres.go @@ -0,0 +1,237 @@ +package repo + +import ( + "fmt" + "strconv" + "time" + + "github.com/CheesecakeLabs/token-factory-v2/backend/internal/entity" + "github.com/CheesecakeLabs/token-factory-v2/backend/pkg/postgres" +) + +type LogTransactionRepo struct { + *postgres.Postgres +} + +func NewLogTransactionRepo(postgres *postgres.Postgres) *LogTransactionRepo { + return &LogTransactionRepo{postgres} +} + +func (repo *LogTransactionRepo) StoreLogTransaction(log entity.LogTransaction) error { + stmp := `INSERT INTO logtransactions (user_id, transaction_type_id, asset_id, amount, description) VALUES ($1, $2, $3, $4, $5)` + _, err := repo.Db.Exec(stmp, log.UserID, log.TransactionTypeID, log.Asset.Id, log.Amount, log.Description) + if err != nil { + fmt.Println(err) + return err + } + + return nil +} + +func (repo *LogTransactionRepo) GetLogTransactions(timeRange string) ([]entity.LogTransaction, error) { + return getLogTransactions(repo, timeRange, "") +} + +func (repo *LogTransactionRepo) GetLogTransactionsByUserID(userID int, timeRange string) ([]entity.LogTransaction, error) { + return getLogTransactions(repo, timeRange, "WHERE user_id = $2") +} + +func (repo *LogTransactionRepo) GetLogTransactionsByAssetID(assetID int, timeRange string) ([]entity.LogTransaction, error) { + return getLogTransactions(repo, timeRange, "WHERE asset_id = $2") +} + +func (repo *LogTransactionRepo) GetLogTransactionsByTransactionTypeID(transactionTypeID int, timeRange string) ([]entity.LogTransaction, error) { + return getLogTransactions(repo, timeRange, "WHERE transaction_type_id = $2") +} + +func (repo *LogTransactionRepo) SumLogTransactionsByAssetID(assetID int, timeRange string, timeFrame time.Duration, transactionType int) (entity.SumLogTransaction, error) { + dateFilter, err := getDateFilter(timeRange) + if err != nil { + return entity.SumLogTransaction{}, err + } + + timeFrameUnit := getTimeFrame(timeFrame) + timeFrameSeconds := timeFrame.Seconds() + + baseQuery := ` + SELECT + a.id, a.name, a.code, a.asset_type, SUM(lt.amount), + DATE_TRUNC($3, TIMESTAMP 'epoch' + INTERVAL '1 second' * floor(EXTRACT(EPOCH FROM lt.date)/$4) * $4) as dateFrame, + COUNT(lt.amount) as quantity + FROM logtransactions AS lt + JOIN asset AS a ON lt.asset_id = a.id + ` + + var whereClause string + var queryArgs []interface{} + + if transactionType == 0 { + whereClause = "WHERE lt.date >= $1 AND lt.asset_id = $2" + queryArgs = append(queryArgs, dateFilter, assetID, timeFrameUnit, timeFrameSeconds) + } else { + whereClause = "WHERE lt.date >= $1 AND lt.asset_id = $2 AND lt.transaction_type_id = $5" + queryArgs = append(queryArgs, dateFilter, assetID, timeFrameUnit, timeFrameSeconds, transactionType) + } + + groupAndOrderClause := ` + GROUP BY a.id, dateFrame + ORDER BY a.id, dateFrame; + ` + + query := baseQuery + whereClause + groupAndOrderClause + + rows, err := repo.Db.Query(query, queryArgs...) + if err != nil { + return entity.SumLogTransaction{}, err + } + defer rows.Close() + + var sumLogTransaction entity.SumLogTransaction + for rows.Next() { + var asset entity.Asset + var amount float64 + var quantity int + var date string + err := rows.Scan(&asset.Id, &asset.Name, &asset.Code, &asset.AssetType, &amount, &date, &quantity) + if err != nil { + return entity.SumLogTransaction{}, err + } + + sumLogTransaction.Asset = asset + sumLogTransaction.Amount = append(sumLogTransaction.Amount, amount) + sumLogTransaction.Quantity = append(sumLogTransaction.Quantity, quantity) + sumLogTransaction.Date = append(sumLogTransaction.Date, date) + sumLogTransaction.Asset.Amount += int(amount) + } + + return sumLogTransaction, nil +} + +func (repo *LogTransactionRepo) SumLogTransactions(timeRange string, timeFrame time.Duration) ([]entity.SumLogTransaction, error) { + dateFilter, err := getDateFilter(timeRange) + if err != nil { + return []entity.SumLogTransaction{}, err + } + + timeFrameUnit := getTimeFrame(timeFrame) + timeFrameSeconds := timeFrame.Seconds() + + query := ` + SELECT + a.id, a.name, a.code, a.asset_type, SUM(lt.amount), + DATE_TRUNC($2, TIMESTAMP 'epoch' + INTERVAL '1 second' * floor(EXTRACT(EPOCH FROM lt.date)/$3) * $3) as dateFrame + FROM logtransactions AS lt + JOIN asset AS a ON lt.asset_id = a.id + WHERE lt.date >= $1 + GROUP BY a.id, dateFrame + ORDER BY a.id, dateFrame; + ` + + rows, err := repo.Db.Query(query, dateFilter, timeFrameUnit, timeFrameSeconds) + if err != nil { + return nil, err + } + defer rows.Close() + + var sums []entity.SumLogTransaction + var currentAsset entity.Asset + var sumLogTransaction entity.SumLogTransaction + for rows.Next() { + var asset entity.Asset + var amount float64 + var date string + err := rows.Scan(&asset.Id, &asset.Name, &asset.Code, &asset.AssetType, &amount, &date) + if err != nil { + return nil, err + } + + if asset.Id != currentAsset.Id && currentAsset.Id != 0 { + sums = append(sums, sumLogTransaction) + sumLogTransaction = entity.SumLogTransaction{} + } + + sumLogTransaction.Asset = asset + sumLogTransaction.Amount = append(sumLogTransaction.Amount, amount) + sumLogTransaction.Date = append(sumLogTransaction.Date, date) + sumLogTransaction.Asset.Amount += int(amount) + currentAsset = asset + } + + if currentAsset.Id != 0 { + sums = append(sums, sumLogTransaction) + } + + return sums, nil +} + +func getLogTransactions(repo *LogTransactionRepo, timeRange string, whereClause string) ([]entity.LogTransaction, error) { + dateFilter, err := getDateFilter(timeRange) + if err != nil { + return nil, err + } + query := ` + SELECT lt.log_id, lt.user_id, lt.transaction_type_id, a.id, a.name, a.code, a.distributor_id, a.issuer_id, a.asset_type, lt.amount, lt.date, lt.description + FROM logtransactions AS lt + JOIN Asset AS a ON lt.asset_id = a.id + WHERE lt.date >= $1 ` + whereClause + ` + ORDER BY lt.date DESC + ` + + rows, err := repo.Db.Query(query, dateFilter) + if err != nil { + return nil, err + } + defer rows.Close() + + var logs []entity.LogTransaction + for rows.Next() { + var log entity.LogTransaction + var distributorID, issuerID int + err := rows.Scan(&log.LogID, &log.UserID, &log.TransactionTypeID, &log.Asset.Id, &log.Asset.Name, &log.Asset.Code, &distributorID, &issuerID, &log.Asset.AssetType, &log.Amount, &log.Date, &log.Description) + if err != nil { + return nil, err + } + log.Asset.Distributor.Id = distributorID + log.Asset.Issuer.Id = issuerID + logs = append(logs, log) + } + + return logs, nil +} + +func getDateFilter(timeRange string) (time.Time, error) { + valueStr := timeRange[:len(timeRange)-1] + unit := timeRange[len(timeRange)-1] + value, err := strconv.Atoi(valueStr) + if err != nil { + return time.Time{}, fmt.Errorf("invalid numeric value in time range: %s", err.Error()) + } + + currentTime := time.Now() + switch unit { + case 'h': + return currentTime.Add(time.Duration(-value) * time.Hour), nil + case 'd': + // If value is 1, return the start of the current day. + if value == 1 { + return time.Date(currentTime.Year(), currentTime.Month(), currentTime.Day(), 0, 0, 0, 0, currentTime.Location()), nil + } + + targetDate := currentTime.AddDate(0, 0, -value+1) + return time.Date(targetDate.Year(), targetDate.Month(), targetDate.Day(), 0, 0, 0, 0, targetDate.Location()), nil + default: + return time.Time{}, fmt.Errorf("invalid time unit in time range") + } +} + +func getTimeFrame(timeFrame time.Duration) string { + var timeFrameUnit string + if timeFrame.Hours() >= 24 { + timeFrameUnit = "day" + } else if timeFrame.Hours() >= 1 { + timeFrameUnit = "hour" + } else { + timeFrameUnit = "minute" + } + return timeFrameUnit +} diff --git a/backend/internal/usecase/repo/user_postgres.go b/backend/internal/usecase/repo/user_postgres.go index b3379786..45a6887a 100644 --- a/backend/internal/usecase/repo/user_postgres.go +++ b/backend/internal/usecase/repo/user_postgres.go @@ -99,7 +99,6 @@ func (r UserRepo) GetAllUsers() ([]entity.UserResponse, error) { ORDER BY u.name ASC` rows, err := r.Db.Query(stmt) - if err != nil { return nil, fmt.Errorf("UserRepo - GetAllUsers - db.Query: %w", err) } diff --git a/backend/migrations/000017_transaction_log_types.down.sql b/backend/migrations/000017_transaction_log_types.down.sql new file mode 100644 index 00000000..1478dfa2 --- /dev/null +++ b/backend/migrations/000017_transaction_log_types.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS LogTransactionTypes; \ No newline at end of file diff --git a/backend/migrations/000017_transaction_log_types.up.sql b/backend/migrations/000017_transaction_log_types.up.sql new file mode 100644 index 00000000..d8e80450 --- /dev/null +++ b/backend/migrations/000017_transaction_log_types.up.sql @@ -0,0 +1,13 @@ +CREATE TABLE LogTransactionTypes( + id SERIAL PRIMARY KEY, + name VARCHAR(50) NOT NULL, + description VARCHAR(255) +); + +INSERT INTO LogTransactionTypes (name, description) VALUES + ('Create Asset', 'Creation of a new asset'), + ('Mint Asset', 'Increasing the quantity of an existing asset'), + ('Update Auth Flags', 'Changing permissions or access controls for an asset'), + ('Clawback Asset', 'Revoking or taking back an asset'), + ('Burn Asset', 'Reducing the quantity of an existing asset'), + ('Transfer Asset', 'Moving an asset from one account or user to another'); diff --git a/backend/migrations/000018_transactions_log.down.sql b/backend/migrations/000018_transactions_log.down.sql new file mode 100644 index 00000000..82d5d9a2 --- /dev/null +++ b/backend/migrations/000018_transactions_log.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS LogTransactions; \ No newline at end of file diff --git a/backend/migrations/000018_transactions_log.up.sql b/backend/migrations/000018_transactions_log.up.sql new file mode 100644 index 00000000..15614200 --- /dev/null +++ b/backend/migrations/000018_transactions_log.up.sql @@ -0,0 +1,9 @@ +CREATE TABLE LogTransactions ( + log_id SERIAL PRIMARY KEY, + user_id INT REFERENCES UserAccount(id), + transaction_type_id INTEGER REFERENCES LogTransactionTypes(id), + asset_id INTEGER REFERENCES Asset(id), + amount FLOAT, + date TIMESTAMPTZ DEFAULT now(), + description VARCHAR(255) +); diff --git a/dev-env.sh b/dev-env.sh new file mode 100644 index 00000000..55c02809 --- /dev/null +++ b/dev-env.sh @@ -0,0 +1,4 @@ +echo -e "\033[31;7m Start the Backend and start the integration test... \e[0m"; +docker-compose -f dev.docker-compose.yml --profile starlabs build +docker-compose -f dev.docker-compose.yml --profile starlabs up -d +docker exec -it postgres psql -U postgres -c "SELECT 1 FROM pg_database WHERE datname='backend'" | grep -q 1 || docker exec -it postgres psql -U postgres -c "CREATE DATABASE backend;" diff --git a/starlabs b/starlabs index b57f3067..d283a223 160000 --- a/starlabs +++ b/starlabs @@ -1 +1 @@ -Subproject commit b57f3067bf12fdf58c44e906cd92cc1994d7238a +Subproject commit d283a2237762a4ef3acc0d4855f81d5d7fe2c866 diff --git a/stellar-kms b/stellar-kms index 4021b62b..630eba7d 160000 --- a/stellar-kms +++ b/stellar-kms @@ -1 +1 @@ -Subproject commit 4021b62b74a7ccec24b9a760f85986707142c702 +Subproject commit 630eba7d3e7ea4083a0c0824153ed64e20f84828