diff --git a/README.md b/README.md index db79095..bfa4f9d 100644 --- a/README.md +++ b/README.md @@ -77,4 +77,14 @@ make cluster_stop Expose a port to database via: ```bash kubectl port-forward svc/gobank-db-rw 5432:5432 +``` + +Generate db docs via: +```bash +tbls doc +``` + +Generate swagger docs via: +```bash +swag init -g main.go -o docs ``` \ No newline at end of file diff --git a/api/accounts.go b/api/accounts.go index 688b98c..97f67de 100644 --- a/api/accounts.go +++ b/api/accounts.go @@ -16,6 +16,20 @@ type createAccountRequest struct { Currency string `json:"currency" binding:"required,currency"` } +// createAccount creates a new account for the authenticated user. +// @Summary Create account +// @Description Create an account in a supported currency for the authenticated user. +// @Tags accounts +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param account body createAccountRequest true "Create account request" +// @Success 200 {object} AccountResponse +// @Failure 400 {object} ErrorResponse +// @Failure 401 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /accounts [post] func (server *Server) createAccount(ctx *gin.Context) { var req createAccountRequest if err := ctx.ShouldBindWith(&req, binding.JSON); err != nil { @@ -50,6 +64,20 @@ type getAccountRequest struct { ID int64 `uri:"id" binding:"required,min=1"` } +// getAccount retrieves an account by ID if the authenticated user owns it. +// @Summary Get account +// @Description Get a single account by ID for the authenticated user. +// @Tags accounts +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param id path int true "Account ID" +// @Success 200 {object} AccountResponse +// @Failure 400 {object} ErrorResponse +// @Failure 401 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /accounts/{id} [get] func (server *Server) getAccount(ctx *gin.Context) { var req getAccountRequest if err := ctx.ShouldBindUri(&req); err != nil { @@ -81,6 +109,20 @@ type deleteAccountRequest struct { ID int64 `uri:"id" binding:"required,min=1"` } +// deleteAccount removes an account by ID if the authenticated user owns it. +// @Summary Delete account +// @Description Delete an account by ID for the authenticated user. +// @Tags accounts +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param id path int true "Account ID" +// @Success 204 {object} nil +// @Failure 400 {object} ErrorResponse +// @Failure 401 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /accounts/{id} [delete] func (server *Server) deleteAccount(ctx *gin.Context) { var req deleteAccountRequest if err := ctx.ShouldBindUri(&req); err != nil { @@ -119,6 +161,20 @@ type listAccountsRequest struct { Size int32 `form:"size" binding:"required,min=5,max=10"` } +// listAccounts returns paginated accounts owned by the authenticated user. +// @Summary List accounts +// @Description List accounts for the authenticated user with pagination. +// @Tags accounts +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param page query int true "Page number" +// @Param size query int true "Page size" +// @Success 200 {array} AccountResponse +// @Failure 400 {object} ErrorResponse +// @Failure 401 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /accounts [get] func (server *Server) listAccounts(ctx *gin.Context) { var req listAccountsRequest if err := ctx.ShouldBindWith(&req, binding.Query); err != nil { diff --git a/api/docs_models.go b/api/docs_models.go new file mode 100644 index 0000000..07c1c82 --- /dev/null +++ b/api/docs_models.go @@ -0,0 +1,37 @@ +package api + +type ErrorResponse struct { + Error string `json:"error"` +} + +type AccountResponse struct { + ID int64 `json:"id"` + Owner string `json:"owner"` + Balance string `json:"balance"` + Currency string `json:"currency"` + CreatedAt string `json:"created_at"` + DeletedAt string `json:"deleted_at,omitempty"` +} + +type EntryResponse struct { + ID int64 `json:"id"` + AccountID int64 `json:"account_id"` + Amount string `json:"amount"` + CreatedAt string `json:"created_at"` +} + +type TransferResponse struct { + ID int64 `json:"id"` + FromAccountID int64 `json:"from_account_id"` + ToAccountID int64 `json:"to_account_id"` + Amount string `json:"amount"` + CreatedAt string `json:"created_at"` +} + +type TransferTxResultResponse struct { + Transfer TransferResponse `json:"transfer"` + FromAccount AccountResponse `json:"from_account"` + ToAccount AccountResponse `json:"to_account"` + FromEntry EntryResponse `json:"from_entry"` + ToEntry EntryResponse `json:"to_entry"` +} diff --git a/api/entries.go b/api/entries.go index f4b0692..863acbb 100644 --- a/api/entries.go +++ b/api/entries.go @@ -15,6 +15,20 @@ type getEntryRequest struct { ID int64 `uri:"id" binding:"required,min=1"` } +// getEntry retrieves a ledger entry by ID for the authenticated user. +// @Summary Get entry +// @Description Get a single entry by ID when it belongs to the authenticated user. +// @Tags entries +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param id path int true "Entry ID" +// @Success 200 {object} EntryResponse +// @Failure 400 {object} ErrorResponse +// @Failure 401 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /entries/{id} [get] func (server *Server) getEntry(ctx *gin.Context) { var req getEntryRequest if err := ctx.ShouldBindUri(&req); err != nil { @@ -58,6 +72,22 @@ type listEntriesRequest struct { Size int32 `form:"size" binding:"required,min=5,max=10"` } +// listEntries returns paginated entries for an account owned by the authenticated user. +// @Summary List entries +// @Description List entries for a specific account with pagination. +// @Tags entries +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param account_id query int true "Account ID" +// @Param page query int true "Page number" +// @Param size query int true "Page size" +// @Success 200 {array} EntryResponse +// @Failure 400 {object} ErrorResponse +// @Failure 401 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /entries [get] func (server *Server) listEntries(ctx *gin.Context) { var req listEntriesRequest diff --git a/api/server.go b/api/server.go index 793294c..0499231 100644 --- a/api/server.go +++ b/api/server.go @@ -11,6 +11,8 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "github.com/go-playground/validator/v10" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" ) type Server struct { @@ -50,6 +52,8 @@ func (server *Server) setupRouter() { router.POST("/users", server.createUser) router.POST("/users/login", server.loginUser) + router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + authRoutes := router.Group("/").Use(authMiddleware(server.tokenMaker)) authRoutes.POST("/accounts", server.createAccount) diff --git a/api/transfers.go b/api/transfers.go index 1bd0e14..fab339e 100644 --- a/api/transfers.go +++ b/api/transfers.go @@ -16,6 +16,20 @@ type getTransferRequest struct { ID int64 `uri:"id" binding:"required,min=1"` } +// getTransfer retrieves a transfer by ID when the authenticated user participates in it. +// @Summary Get transfer +// @Description Get a transfer by ID when the authenticated user is sender or receiver. +// @Tags transfers +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param id path int true "Transfer ID" +// @Success 200 {object} TransferResponse +// @Failure 400 {object} ErrorResponse +// @Failure 401 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /transfers/{id} [get] func (server *Server) getTransfer(ctx *gin.Context) { var req getTransferRequest if err := ctx.ShouldBindUri(&req); err != nil { @@ -71,6 +85,22 @@ type listAccountTransfersRequest struct { Size int32 `form:"size" binding:"required,min=5,max=10"` } +// listTransfers returns paginated transfers for an account owned by the authenticated user. +// @Summary List transfers +// @Description List transfers for a specific account with pagination. +// @Tags transfers +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param account_id query int true "Account ID" +// @Param page query int true "Page number" +// @Param size query int true "Page size" +// @Success 200 {array} TransferResponse +// @Failure 400 {object} ErrorResponse +// @Failure 401 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /transfers [get] func (server *Server) listTransfers(ctx *gin.Context) { var req listAccountTransfersRequest @@ -123,6 +153,20 @@ type transferRequest struct { Currency string `json:"currency" binding:"required,currency"` } +// createTransfer executes a transfer between two accounts owned by the authenticated user. +// @Summary Create transfer +// @Description Create a transfer from one account to another in a supported currency. +// @Tags transfers +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param transfer body transferRequest true "Transfer request" +// @Success 200 {object} TransferTxResultResponse +// @Failure 400 {object} ErrorResponse +// @Failure 401 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /transfers [post] func (server *Server) createTransfer(ctx *gin.Context) { var req transferRequest if err := ctx.ShouldBindWith(&req, binding.JSON); err != nil { diff --git a/api/users.go b/api/users.go index c3ff5a3..feb2a60 100644 --- a/api/users.go +++ b/api/users.go @@ -19,7 +19,7 @@ type createUserRequest struct { Email string `json:"email" binding:"required,email"` } -type userResponse struct { +type UserResponse struct { Username string `json:"username"` FullName string `json:"full_name"` Email string `json:"email"` @@ -27,8 +27,8 @@ type userResponse struct { CreatedAt time.Time `json:"created_at"` } -func newUserResponse(user db.User) userResponse { - return userResponse{ +func newUserResponse(user db.User) UserResponse { + return UserResponse{ Username: user.Username, FullName: user.FullName, Email: user.Email, @@ -37,6 +37,18 @@ func newUserResponse(user db.User) userResponse { } } +// createUser handles user registration. +// @Summary Create user +// @Description Register a new user with username, password, full name, and email. +// @Tags users +// @Accept json +// @Produce json +// @Param user body createUserRequest true "Create user request" +// @Success 200 {object} UserResponse +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /users [post] func (server *Server) createUser(ctx *gin.Context) { var req createUserRequest if err := ctx.ShouldBindWith(&req, binding.JSON); err != nil { @@ -80,11 +92,24 @@ type loginUserRequest struct { Password string `json:"password" binding:"required,min=8"` } -type loginUserResponse struct { - AccessToken string `json:"access_token"` - User userResponse +type LoginUserResponse struct { + AccessToken string `json:"access_token"` + User UserResponse `json:"user"` } +// loginUser authenticates a user and returns a JWT access token. +// @Summary Login user +// @Description Authenticate with username and password to receive an access token. +// @Tags users +// @Accept json +// @Produce json +// @Param credentials body loginUserRequest true "Login credentials" +// @Success 200 {object} LoginUserResponse +// @Failure 400 {object} ErrorResponse +// @Failure 401 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /users/login [post] func (server *Server) loginUser(ctx *gin.Context) { var req loginUserRequest if err := ctx.ShouldBindBodyWith(&req, binding.JSON); err != nil { @@ -114,7 +139,7 @@ func (server *Server) loginUser(ctx *gin.Context) { return } - resp := loginUserResponse{ + resp := LoginUserResponse{ AccessToken: accessToken, User: newUserResponse(user), } diff --git a/api/users_test.go b/api/users_test.go index 2b91cdb..b2eae8c 100644 --- a/api/users_test.go +++ b/api/users_test.go @@ -208,11 +208,11 @@ func requireBodyMatchUser(t *testing.T, body *bytes.Buffer, user db.User) { data, err := io.ReadAll(body) require.NoError(t, err) - var response userResponse + var response UserResponse err = json.Unmarshal(data, &response) require.NoError(t, err) - require.Equal(t, userResponse{ + require.Equal(t, UserResponse{ Username: user.Username, FullName: user.FullName, Email: user.Email, diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..b957297 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,941 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "email": "support@localhost" + }, + "license": { + "name": "MIT", + "url": "https://opensource.org/licenses/MIT" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/accounts": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "List accounts for the authenticated user with pagination.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "accounts" + ], + "summary": "List accounts", + "parameters": [ + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "Page size", + "name": "size", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/api.AccountResponse" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create an account in a supported currency for the authenticated user.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "accounts" + ], + "summary": "Create account", + "parameters": [ + { + "description": "Create account request", + "name": "account", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.createAccountRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.AccountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + }, + "/accounts/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a single account by ID for the authenticated user.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "accounts" + ], + "summary": "Get account", + "parameters": [ + { + "type": "integer", + "description": "Account ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.AccountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete an account by ID for the authenticated user.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "accounts" + ], + "summary": "Delete account", + "parameters": [ + { + "type": "integer", + "description": "Account ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + }, + "/entries": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "List entries for a specific account with pagination.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "entries" + ], + "summary": "List entries", + "parameters": [ + { + "type": "integer", + "description": "Account ID", + "name": "account_id", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "Page size", + "name": "size", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/api.EntryResponse" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + }, + "/entries/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a single entry by ID when it belongs to the authenticated user.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "entries" + ], + "summary": "Get entry", + "parameters": [ + { + "type": "integer", + "description": "Entry ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.EntryResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + }, + "/transfers": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "List transfers for a specific account with pagination.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfers" + ], + "summary": "List transfers", + "parameters": [ + { + "type": "integer", + "description": "Account ID", + "name": "account_id", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "Page size", + "name": "size", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/api.TransferResponse" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a transfer from one account to another in a supported currency.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfers" + ], + "summary": "Create transfer", + "parameters": [ + { + "description": "Transfer request", + "name": "transfer", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.transferRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.TransferTxResultResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + }, + "/transfers/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a transfer by ID when the authenticated user is sender or receiver.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfers" + ], + "summary": "Get transfer", + "parameters": [ + { + "type": "integer", + "description": "Transfer ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.TransferResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + }, + "/users": { + "post": { + "description": "Register a new user with username, password, full name, and email.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "Create user", + "parameters": [ + { + "description": "Create user request", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.createUserRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.UserResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + }, + "/users/login": { + "post": { + "description": "Authenticate with username and password to receive an access token.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "Login user", + "parameters": [ + { + "description": "Login credentials", + "name": "credentials", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.loginUserRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LoginUserResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "api.AccountResponse": { + "type": "object", + "properties": { + "balance": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "deleted_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "owner": { + "type": "string" + } + } + }, + "api.EntryResponse": { + "type": "object", + "properties": { + "account_id": { + "type": "integer" + }, + "amount": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + } + } + }, + "api.ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "api.LoginUserResponse": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/api.UserResponse" + } + } + }, + "api.TransferResponse": { + "type": "object", + "properties": { + "amount": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "from_account_id": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "to_account_id": { + "type": "integer" + } + } + }, + "api.TransferTxResultResponse": { + "type": "object", + "properties": { + "from_account": { + "$ref": "#/definitions/api.AccountResponse" + }, + "from_entry": { + "$ref": "#/definitions/api.EntryResponse" + }, + "to_account": { + "$ref": "#/definitions/api.AccountResponse" + }, + "to_entry": { + "$ref": "#/definitions/api.EntryResponse" + }, + "transfer": { + "$ref": "#/definitions/api.TransferResponse" + } + } + }, + "api.UserResponse": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "password_changed_at": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "api.createAccountRequest": { + "type": "object", + "required": [ + "currency" + ], + "properties": { + "currency": { + "type": "string" + } + } + }, + "api.createUserRequest": { + "type": "object", + "required": [ + "email", + "full_name", + "password", + "username" + ], + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "password": { + "type": "string", + "minLength": 8 + }, + "username": { + "type": "string" + } + } + }, + "api.loginUserRequest": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "password": { + "type": "string", + "minLength": 8 + }, + "username": { + "type": "string" + } + } + }, + "api.transferRequest": { + "type": "object", + "required": [ + "amount", + "currency", + "from_account_id", + "to_account_id" + ], + "properties": { + "amount": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "from_account_id": { + "type": "integer", + "minimum": 1 + }, + "to_account_id": { + "type": "integer", + "minimum": 1 + } + } + } + }, + "securityDefinitions": { + "BearerAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "", + BasePath: "/", + Schemes: []string{"http"}, + Title: "GoBank API", + Description: "GoBank is a simple banking API built with Go, Gin, and SQLC.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/schema/README.md b/docs/schema/README.md new file mode 100644 index 0000000..190a1e2 --- /dev/null +++ b/docs/schema/README.md @@ -0,0 +1,19 @@ +# gobank + +## Tables + +| Name | Columns | Comment | Type | +| ---- | ------- | ------- | ---- | +| [public.schema_migrations](public.schema_migrations.md) | 2 | | BASE TABLE | +| [public.accounts](public.accounts.md) | 6 | | BASE TABLE | +| [public.entries](public.entries.md) | 4 | | BASE TABLE | +| [public.transfers](public.transfers.md) | 5 | | BASE TABLE | +| [public.users](public.users.md) | 6 | | BASE TABLE | + +## Relations + +![er](schema.svg) + +--- + +> Generated by [tbls](https://github.com/k1LoW/tbls) diff --git a/docs/schema/public.accounts.md b/docs/schema/public.accounts.md new file mode 100644 index 0000000..e9fb951 --- /dev/null +++ b/docs/schema/public.accounts.md @@ -0,0 +1,41 @@ +# public.accounts + +## Columns + +| Name | Type | Default | Nullable | Children | Parents | Comment | +| ---- | ---- | ------- | -------- | -------- | ------- | ------- | +| id | bigint | nextval('accounts_id_seq'::regclass) | false | [public.entries](public.entries.md) [public.transfers](public.transfers.md) | | | +| owner | varchar | | false | | [public.users](public.users.md) | | +| balance | numeric | | false | | | | +| currency | varchar | | false | | | | +| created_at | timestamp with time zone | now() | false | | | | +| deleted_at | timestamp with time zone | | true | | | | + +## Constraints + +| Name | Type | Definition | +| ---- | ---- | ---------- | +| accounts_balance_not_null | n | NOT NULL balance | +| accounts_created_at_not_null | n | NOT NULL created_at | +| accounts_currency_not_null | n | NOT NULL currency | +| accounts_id_not_null | n | NOT NULL id | +| accounts_owner_not_null | n | NOT NULL owner | +| accounts_pkey | PRIMARY KEY | PRIMARY KEY (id) | +| accounts_owner_fkey | FOREIGN KEY | FOREIGN KEY (owner) REFERENCES users(username) DEFERRABLE | + +## Indexes + +| Name | Definition | +| ---- | ---------- | +| accounts_pkey | CREATE UNIQUE INDEX accounts_pkey ON public.accounts USING btree (id) | +| accounts_owner_idx | CREATE INDEX accounts_owner_idx ON public.accounts USING btree (owner) | +| accounts_owner_currency_idx | CREATE UNIQUE INDEX accounts_owner_currency_idx ON public.accounts USING btree (owner, currency) WHERE (deleted_at IS NULL) | +| accounts_deleted_at_idx | CREATE INDEX accounts_deleted_at_idx ON public.accounts USING btree (deleted_at) WHERE (deleted_at IS NULL) | + +## Relations + +![er](public.accounts.svg) + +--- + +> Generated by [tbls](https://github.com/k1LoW/tbls) diff --git a/docs/schema/public.accounts.svg b/docs/schema/public.accounts.svg new file mode 100644 index 0000000..0e541cb --- /dev/null +++ b/docs/schema/public.accounts.svg @@ -0,0 +1,141 @@ + + + + + + +public.accounts + + + +public.accounts + + +public.accounts +     +[BASE TABLE] + +id +[bigint] + +owner +[varchar] + +balance +[numeric] + +currency +[varchar] + +created_at +[timestamp with time zone] + +deleted_at +[timestamp with time zone] + + + + +public.users + + +public.users +     +[BASE TABLE] + +username +[varchar] + +hashed_password +[varchar] + +full_name +[varchar] + +email +[varchar] + +password_changed_at +[timestamp with time zone] + +created_at +[timestamp with time zone] + + + +public.accounts:owner->public.users:username + + +FOREIGN KEY (owner) REFERENCES users(username) DEFERRABLE + + + +public.entries + + +public.entries +     +[BASE TABLE] + +id +[bigint] + +account_id +[bigint] + +amount +[numeric] + +created_at +[timestamp with time zone] + + + +public.entries:account_id->public.accounts:id + + +FOREIGN KEY (account_id) REFERENCES accounts(id) DEFERRABLE + + + +public.transfers + + +public.transfers +     +[BASE TABLE] + +id +[bigint] + +from_account_id +[bigint] + +to_account_id +[bigint] + +amount +[numeric] + +created_at +[timestamp with time zone] + + + +public.transfers:from_account_id->public.accounts:id + + +FOREIGN KEY (from_account_id) REFERENCES accounts(id) DEFERRABLE + + + +public.transfers:to_account_id->public.accounts:id + + +FOREIGN KEY (to_account_id) REFERENCES accounts(id) DEFERRABLE + + + diff --git a/docs/schema/public.entries.md b/docs/schema/public.entries.md new file mode 100644 index 0000000..e4194f2 --- /dev/null +++ b/docs/schema/public.entries.md @@ -0,0 +1,36 @@ +# public.entries + +## Columns + +| Name | Type | Default | Nullable | Children | Parents | Comment | +| ---- | ---- | ------- | -------- | -------- | ------- | ------- | +| id | bigint | nextval('entries_id_seq'::regclass) | false | | | | +| account_id | bigint | | false | | [public.accounts](public.accounts.md) | | +| amount | numeric | | false | | | can be negative or positive | +| created_at | timestamp with time zone | now() | false | | | | + +## Constraints + +| Name | Type | Definition | +| ---- | ---- | ---------- | +| entries_account_id_not_null | n | NOT NULL account_id | +| entries_amount_not_null | n | NOT NULL amount | +| entries_created_at_not_null | n | NOT NULL created_at | +| entries_id_not_null | n | NOT NULL id | +| entries_account_id_fkey | FOREIGN KEY | FOREIGN KEY (account_id) REFERENCES accounts(id) DEFERRABLE | +| entries_pkey | PRIMARY KEY | PRIMARY KEY (id) | + +## Indexes + +| Name | Definition | +| ---- | ---------- | +| entries_pkey | CREATE UNIQUE INDEX entries_pkey ON public.entries USING btree (id) | +| entries_account_id_idx | CREATE INDEX entries_account_id_idx ON public.entries USING btree (account_id) | + +## Relations + +![er](public.entries.svg) + +--- + +> Generated by [tbls](https://github.com/k1LoW/tbls) diff --git a/docs/schema/public.entries.svg b/docs/schema/public.entries.svg new file mode 100644 index 0000000..4729fe0 --- /dev/null +++ b/docs/schema/public.entries.svg @@ -0,0 +1,69 @@ + + + + + + +public.entries + + + +public.entries + + +public.entries +     +[BASE TABLE] + +id +[bigint] + +account_id +[bigint] + +amount +[numeric] + +created_at +[timestamp with time zone] + + + + +public.accounts + + +public.accounts +     +[BASE TABLE] + +id +[bigint] + +owner +[varchar] + +balance +[numeric] + +currency +[varchar] + +created_at +[timestamp with time zone] + +deleted_at +[timestamp with time zone] + + + +public.entries:account_id->public.accounts:id + + +FOREIGN KEY (account_id) REFERENCES accounts(id) DEFERRABLE + + + diff --git a/docs/schema/public.schema_migrations.md b/docs/schema/public.schema_migrations.md new file mode 100644 index 0000000..6530312 --- /dev/null +++ b/docs/schema/public.schema_migrations.md @@ -0,0 +1,30 @@ +# public.schema_migrations + +## Columns + +| Name | Type | Default | Nullable | Children | Parents | Comment | +| ---- | ---- | ------- | -------- | -------- | ------- | ------- | +| version | bigint | | false | | | | +| dirty | boolean | | false | | | | + +## Constraints + +| Name | Type | Definition | +| ---- | ---- | ---------- | +| schema_migrations_dirty_not_null | n | NOT NULL dirty | +| schema_migrations_version_not_null | n | NOT NULL version | +| schema_migrations_pkey | PRIMARY KEY | PRIMARY KEY (version) | + +## Indexes + +| Name | Definition | +| ---- | ---------- | +| schema_migrations_pkey | CREATE UNIQUE INDEX schema_migrations_pkey ON public.schema_migrations USING btree (version) | + +## Relations + +![er](public.schema_migrations.svg) + +--- + +> Generated by [tbls](https://github.com/k1LoW/tbls) diff --git a/docs/schema/public.schema_migrations.svg b/docs/schema/public.schema_migrations.svg new file mode 100644 index 0000000..450dc18 --- /dev/null +++ b/docs/schema/public.schema_migrations.svg @@ -0,0 +1,29 @@ + + + + + + +public.schema_migrations + + + +public.schema_migrations + + +public.schema_migrations +     +[BASE TABLE] + +version +[bigint] + +dirty +[boolean] + + + + diff --git a/docs/schema/public.transfers.md b/docs/schema/public.transfers.md new file mode 100644 index 0000000..1d3fcda --- /dev/null +++ b/docs/schema/public.transfers.md @@ -0,0 +1,41 @@ +# public.transfers + +## Columns + +| Name | Type | Default | Nullable | Children | Parents | Comment | +| ---- | ---- | ------- | -------- | -------- | ------- | ------- | +| id | bigint | nextval('transfers_id_seq'::regclass) | false | | | | +| from_account_id | bigint | | false | | [public.accounts](public.accounts.md) | | +| to_account_id | bigint | | false | | [public.accounts](public.accounts.md) | | +| amount | numeric | | false | | | must be positive | +| created_at | timestamp with time zone | now() | false | | | | + +## Constraints + +| Name | Type | Definition | +| ---- | ---- | ---------- | +| transfers_amount_not_null | n | NOT NULL amount | +| transfers_created_at_not_null | n | NOT NULL created_at | +| transfers_from_account_id_not_null | n | NOT NULL from_account_id | +| transfers_id_not_null | n | NOT NULL id | +| transfers_to_account_id_not_null | n | NOT NULL to_account_id | +| transfers_from_account_id_fkey | FOREIGN KEY | FOREIGN KEY (from_account_id) REFERENCES accounts(id) DEFERRABLE | +| transfers_to_account_id_fkey | FOREIGN KEY | FOREIGN KEY (to_account_id) REFERENCES accounts(id) DEFERRABLE | +| transfers_pkey | PRIMARY KEY | PRIMARY KEY (id) | + +## Indexes + +| Name | Definition | +| ---- | ---------- | +| transfers_pkey | CREATE UNIQUE INDEX transfers_pkey ON public.transfers USING btree (id) | +| transfers_from_account_id_idx | CREATE INDEX transfers_from_account_id_idx ON public.transfers USING btree (from_account_id) | +| transfers_to_account_id_idx | CREATE INDEX transfers_to_account_id_idx ON public.transfers USING btree (to_account_id) | +| transfers_from_account_id_to_account_id_idx | CREATE INDEX transfers_from_account_id_to_account_id_idx ON public.transfers USING btree (from_account_id, to_account_id) | + +## Relations + +![er](public.transfers.svg) + +--- + +> Generated by [tbls](https://github.com/k1LoW/tbls) diff --git a/docs/schema/public.transfers.svg b/docs/schema/public.transfers.svg new file mode 100644 index 0000000..b5da730 --- /dev/null +++ b/docs/schema/public.transfers.svg @@ -0,0 +1,79 @@ + + + + + + +public.transfers + + + +public.transfers + + +public.transfers +     +[BASE TABLE] + +id +[bigint] + +from_account_id +[bigint] + +to_account_id +[bigint] + +amount +[numeric] + +created_at +[timestamp with time zone] + + + + +public.accounts + + +public.accounts +     +[BASE TABLE] + +id +[bigint] + +owner +[varchar] + +balance +[numeric] + +currency +[varchar] + +created_at +[timestamp with time zone] + +deleted_at +[timestamp with time zone] + + + +public.transfers:from_account_id->public.accounts:id + + +FOREIGN KEY (from_account_id) REFERENCES accounts(id) DEFERRABLE + + + +public.transfers:to_account_id->public.accounts:id + + +FOREIGN KEY (to_account_id) REFERENCES accounts(id) DEFERRABLE + + + diff --git a/docs/schema/public.users.md b/docs/schema/public.users.md new file mode 100644 index 0000000..8b0ccc9 --- /dev/null +++ b/docs/schema/public.users.md @@ -0,0 +1,40 @@ +# public.users + +## Columns + +| Name | Type | Default | Nullable | Children | Parents | Comment | +| ---- | ---- | ------- | -------- | -------- | ------- | ------- | +| username | varchar | | false | [public.accounts](public.accounts.md) | | | +| hashed_password | varchar | | false | | | | +| full_name | varchar | | false | | | | +| email | varchar | | false | | | | +| password_changed_at | timestamp with time zone | '0001-01-01 00:00:00+00'::timestamp with time zone | false | | | | +| created_at | timestamp with time zone | now() | false | | | | + +## Constraints + +| Name | Type | Definition | +| ---- | ---- | ---------- | +| users_created_at_not_null | n | NOT NULL created_at | +| users_email_not_null | n | NOT NULL email | +| users_full_name_not_null | n | NOT NULL full_name | +| users_hashed_password_not_null | n | NOT NULL hashed_password | +| users_password_changed_at_not_null | n | NOT NULL password_changed_at | +| users_username_not_null | n | NOT NULL username | +| users_pkey | PRIMARY KEY | PRIMARY KEY (username) | +| users_email_key | UNIQUE | UNIQUE (email) | + +## Indexes + +| Name | Definition | +| ---- | ---------- | +| users_pkey | CREATE UNIQUE INDEX users_pkey ON public.users USING btree (username) | +| users_email_key | CREATE UNIQUE INDEX users_email_key ON public.users USING btree (email) | + +## Relations + +![er](public.users.svg) + +--- + +> Generated by [tbls](https://github.com/k1LoW/tbls) diff --git a/docs/schema/public.users.svg b/docs/schema/public.users.svg new file mode 100644 index 0000000..3f2362e --- /dev/null +++ b/docs/schema/public.users.svg @@ -0,0 +1,75 @@ + + + + + + +public.users + + + +public.users + + +public.users +     +[BASE TABLE] + +username +[varchar] + +hashed_password +[varchar] + +full_name +[varchar] + +email +[varchar] + +password_changed_at +[timestamp with time zone] + +created_at +[timestamp with time zone] + + + + +public.accounts + + +public.accounts +     +[BASE TABLE] + +id +[bigint] + +owner +[varchar] + +balance +[numeric] + +currency +[varchar] + +created_at +[timestamp with time zone] + +deleted_at +[timestamp with time zone] + + + +public.accounts:owner->public.users:username + + +FOREIGN KEY (owner) REFERENCES users(username) DEFERRABLE + + + diff --git a/docs/schema/schema.json b/docs/schema/schema.json new file mode 100644 index 0000000..8245afe --- /dev/null +++ b/docs/schema/schema.json @@ -0,0 +1,686 @@ +{ + "name": "gobank", + "tables": [ + { + "name": "public.schema_migrations", + "type": "BASE TABLE", + "columns": [ + { + "name": "version", + "type": "bigint", + "nullable": false + }, + { + "name": "dirty", + "type": "boolean", + "nullable": false + } + ], + "indexes": [ + { + "name": "schema_migrations_pkey", + "def": "CREATE UNIQUE INDEX schema_migrations_pkey ON public.schema_migrations USING btree (version)", + "table": "public.schema_migrations", + "columns": [ + "version" + ] + } + ], + "constraints": [ + { + "name": "schema_migrations_dirty_not_null", + "type": "n", + "def": "NOT NULL dirty", + "table": "public.schema_migrations", + "referenced_table": "", + "columns": [ + "dirty" + ] + }, + { + "name": "schema_migrations_version_not_null", + "type": "n", + "def": "NOT NULL version", + "table": "public.schema_migrations", + "referenced_table": "", + "columns": [ + "version" + ] + }, + { + "name": "schema_migrations_pkey", + "type": "PRIMARY KEY", + "def": "PRIMARY KEY (version)", + "table": "public.schema_migrations", + "referenced_table": "", + "columns": [ + "version" + ] + } + ] + }, + { + "name": "public.accounts", + "type": "BASE TABLE", + "columns": [ + { + "name": "id", + "type": "bigint", + "nullable": false, + "default": "nextval('accounts_id_seq'::regclass)" + }, + { + "name": "owner", + "type": "varchar", + "nullable": false + }, + { + "name": "balance", + "type": "numeric", + "nullable": false + }, + { + "name": "currency", + "type": "varchar", + "nullable": false + }, + { + "name": "created_at", + "type": "timestamp with time zone", + "nullable": false, + "default": "now()" + }, + { + "name": "deleted_at", + "type": "timestamp with time zone", + "nullable": true + } + ], + "indexes": [ + { + "name": "accounts_pkey", + "def": "CREATE UNIQUE INDEX accounts_pkey ON public.accounts USING btree (id)", + "table": "public.accounts", + "columns": [ + "id" + ] + }, + { + "name": "accounts_owner_idx", + "def": "CREATE INDEX accounts_owner_idx ON public.accounts USING btree (owner)", + "table": "public.accounts", + "columns": [ + "owner" + ] + }, + { + "name": "accounts_owner_currency_idx", + "def": "CREATE UNIQUE INDEX accounts_owner_currency_idx ON public.accounts USING btree (owner, currency) WHERE (deleted_at IS NULL)", + "table": "public.accounts", + "columns": [ + "owner", + "currency" + ] + }, + { + "name": "accounts_deleted_at_idx", + "def": "CREATE INDEX accounts_deleted_at_idx ON public.accounts USING btree (deleted_at) WHERE (deleted_at IS NULL)", + "table": "public.accounts", + "columns": [ + "deleted_at" + ] + } + ], + "constraints": [ + { + "name": "accounts_balance_not_null", + "type": "n", + "def": "NOT NULL balance", + "table": "public.accounts", + "referenced_table": "", + "columns": [ + "balance" + ] + }, + { + "name": "accounts_created_at_not_null", + "type": "n", + "def": "NOT NULL created_at", + "table": "public.accounts", + "referenced_table": "", + "columns": [ + "created_at" + ] + }, + { + "name": "accounts_currency_not_null", + "type": "n", + "def": "NOT NULL currency", + "table": "public.accounts", + "referenced_table": "", + "columns": [ + "currency" + ] + }, + { + "name": "accounts_id_not_null", + "type": "n", + "def": "NOT NULL id", + "table": "public.accounts", + "referenced_table": "", + "columns": [ + "id" + ] + }, + { + "name": "accounts_owner_not_null", + "type": "n", + "def": "NOT NULL owner", + "table": "public.accounts", + "referenced_table": "", + "columns": [ + "owner" + ] + }, + { + "name": "accounts_pkey", + "type": "PRIMARY KEY", + "def": "PRIMARY KEY (id)", + "table": "public.accounts", + "referenced_table": "", + "columns": [ + "id" + ] + }, + { + "name": "accounts_owner_fkey", + "type": "FOREIGN KEY", + "def": "FOREIGN KEY (owner) REFERENCES users(username) DEFERRABLE", + "table": "public.accounts", + "referenced_table": "public.users", + "columns": [ + "owner" + ], + "referenced_columns": [ + "username" + ] + } + ] + }, + { + "name": "public.entries", + "type": "BASE TABLE", + "columns": [ + { + "name": "id", + "type": "bigint", + "nullable": false, + "default": "nextval('entries_id_seq'::regclass)" + }, + { + "name": "account_id", + "type": "bigint", + "nullable": false + }, + { + "name": "amount", + "type": "numeric", + "nullable": false, + "comment": "can be negative or positive" + }, + { + "name": "created_at", + "type": "timestamp with time zone", + "nullable": false, + "default": "now()" + } + ], + "indexes": [ + { + "name": "entries_pkey", + "def": "CREATE UNIQUE INDEX entries_pkey ON public.entries USING btree (id)", + "table": "public.entries", + "columns": [ + "id" + ] + }, + { + "name": "entries_account_id_idx", + "def": "CREATE INDEX entries_account_id_idx ON public.entries USING btree (account_id)", + "table": "public.entries", + "columns": [ + "account_id" + ] + } + ], + "constraints": [ + { + "name": "entries_account_id_not_null", + "type": "n", + "def": "NOT NULL account_id", + "table": "public.entries", + "referenced_table": "", + "columns": [ + "account_id" + ] + }, + { + "name": "entries_amount_not_null", + "type": "n", + "def": "NOT NULL amount", + "table": "public.entries", + "referenced_table": "", + "columns": [ + "amount" + ] + }, + { + "name": "entries_created_at_not_null", + "type": "n", + "def": "NOT NULL created_at", + "table": "public.entries", + "referenced_table": "", + "columns": [ + "created_at" + ] + }, + { + "name": "entries_id_not_null", + "type": "n", + "def": "NOT NULL id", + "table": "public.entries", + "referenced_table": "", + "columns": [ + "id" + ] + }, + { + "name": "entries_account_id_fkey", + "type": "FOREIGN KEY", + "def": "FOREIGN KEY (account_id) REFERENCES accounts(id) DEFERRABLE", + "table": "public.entries", + "referenced_table": "public.accounts", + "columns": [ + "account_id" + ], + "referenced_columns": [ + "id" + ] + }, + { + "name": "entries_pkey", + "type": "PRIMARY KEY", + "def": "PRIMARY KEY (id)", + "table": "public.entries", + "referenced_table": "", + "columns": [ + "id" + ] + } + ] + }, + { + "name": "public.transfers", + "type": "BASE TABLE", + "columns": [ + { + "name": "id", + "type": "bigint", + "nullable": false, + "default": "nextval('transfers_id_seq'::regclass)" + }, + { + "name": "from_account_id", + "type": "bigint", + "nullable": false + }, + { + "name": "to_account_id", + "type": "bigint", + "nullable": false + }, + { + "name": "amount", + "type": "numeric", + "nullable": false, + "comment": "must be positive" + }, + { + "name": "created_at", + "type": "timestamp with time zone", + "nullable": false, + "default": "now()" + } + ], + "indexes": [ + { + "name": "transfers_pkey", + "def": "CREATE UNIQUE INDEX transfers_pkey ON public.transfers USING btree (id)", + "table": "public.transfers", + "columns": [ + "id" + ] + }, + { + "name": "transfers_from_account_id_idx", + "def": "CREATE INDEX transfers_from_account_id_idx ON public.transfers USING btree (from_account_id)", + "table": "public.transfers", + "columns": [ + "from_account_id" + ] + }, + { + "name": "transfers_to_account_id_idx", + "def": "CREATE INDEX transfers_to_account_id_idx ON public.transfers USING btree (to_account_id)", + "table": "public.transfers", + "columns": [ + "to_account_id" + ] + }, + { + "name": "transfers_from_account_id_to_account_id_idx", + "def": "CREATE INDEX transfers_from_account_id_to_account_id_idx ON public.transfers USING btree (from_account_id, to_account_id)", + "table": "public.transfers", + "columns": [ + "from_account_id", + "to_account_id" + ] + } + ], + "constraints": [ + { + "name": "transfers_amount_not_null", + "type": "n", + "def": "NOT NULL amount", + "table": "public.transfers", + "referenced_table": "", + "columns": [ + "amount" + ] + }, + { + "name": "transfers_created_at_not_null", + "type": "n", + "def": "NOT NULL created_at", + "table": "public.transfers", + "referenced_table": "", + "columns": [ + "created_at" + ] + }, + { + "name": "transfers_from_account_id_not_null", + "type": "n", + "def": "NOT NULL from_account_id", + "table": "public.transfers", + "referenced_table": "", + "columns": [ + "from_account_id" + ] + }, + { + "name": "transfers_id_not_null", + "type": "n", + "def": "NOT NULL id", + "table": "public.transfers", + "referenced_table": "", + "columns": [ + "id" + ] + }, + { + "name": "transfers_to_account_id_not_null", + "type": "n", + "def": "NOT NULL to_account_id", + "table": "public.transfers", + "referenced_table": "", + "columns": [ + "to_account_id" + ] + }, + { + "name": "transfers_from_account_id_fkey", + "type": "FOREIGN KEY", + "def": "FOREIGN KEY (from_account_id) REFERENCES accounts(id) DEFERRABLE", + "table": "public.transfers", + "referenced_table": "public.accounts", + "columns": [ + "from_account_id" + ], + "referenced_columns": [ + "id" + ] + }, + { + "name": "transfers_to_account_id_fkey", + "type": "FOREIGN KEY", + "def": "FOREIGN KEY (to_account_id) REFERENCES accounts(id) DEFERRABLE", + "table": "public.transfers", + "referenced_table": "public.accounts", + "columns": [ + "to_account_id" + ], + "referenced_columns": [ + "id" + ] + }, + { + "name": "transfers_pkey", + "type": "PRIMARY KEY", + "def": "PRIMARY KEY (id)", + "table": "public.transfers", + "referenced_table": "", + "columns": [ + "id" + ] + } + ] + }, + { + "name": "public.users", + "type": "BASE TABLE", + "columns": [ + { + "name": "username", + "type": "varchar", + "nullable": false + }, + { + "name": "hashed_password", + "type": "varchar", + "nullable": false + }, + { + "name": "full_name", + "type": "varchar", + "nullable": false + }, + { + "name": "email", + "type": "varchar", + "nullable": false + }, + { + "name": "password_changed_at", + "type": "timestamp with time zone", + "nullable": false, + "default": "'0001-01-01 00:00:00+00'::timestamp with time zone" + }, + { + "name": "created_at", + "type": "timestamp with time zone", + "nullable": false, + "default": "now()" + } + ], + "indexes": [ + { + "name": "users_pkey", + "def": "CREATE UNIQUE INDEX users_pkey ON public.users USING btree (username)", + "table": "public.users", + "columns": [ + "username" + ] + }, + { + "name": "users_email_key", + "def": "CREATE UNIQUE INDEX users_email_key ON public.users USING btree (email)", + "table": "public.users", + "columns": [ + "email" + ] + } + ], + "constraints": [ + { + "name": "users_created_at_not_null", + "type": "n", + "def": "NOT NULL created_at", + "table": "public.users", + "referenced_table": "", + "columns": [ + "created_at" + ] + }, + { + "name": "users_email_not_null", + "type": "n", + "def": "NOT NULL email", + "table": "public.users", + "referenced_table": "", + "columns": [ + "email" + ] + }, + { + "name": "users_full_name_not_null", + "type": "n", + "def": "NOT NULL full_name", + "table": "public.users", + "referenced_table": "", + "columns": [ + "full_name" + ] + }, + { + "name": "users_hashed_password_not_null", + "type": "n", + "def": "NOT NULL hashed_password", + "table": "public.users", + "referenced_table": "", + "columns": [ + "hashed_password" + ] + }, + { + "name": "users_password_changed_at_not_null", + "type": "n", + "def": "NOT NULL password_changed_at", + "table": "public.users", + "referenced_table": "", + "columns": [ + "password_changed_at" + ] + }, + { + "name": "users_username_not_null", + "type": "n", + "def": "NOT NULL username", + "table": "public.users", + "referenced_table": "", + "columns": [ + "username" + ] + }, + { + "name": "users_pkey", + "type": "PRIMARY KEY", + "def": "PRIMARY KEY (username)", + "table": "public.users", + "referenced_table": "", + "columns": [ + "username" + ] + }, + { + "name": "users_email_key", + "type": "UNIQUE", + "def": "UNIQUE (email)", + "table": "public.users", + "referenced_table": "", + "columns": [ + "email" + ] + } + ] + } + ], + "relations": [ + { + "table": "public.accounts", + "columns": [ + "owner" + ], + "cardinality": "zero_or_more", + "parent_table": "public.users", + "parent_columns": [ + "username" + ], + "parent_cardinality": "exactly_one", + "def": "FOREIGN KEY (owner) REFERENCES users(username) DEFERRABLE" + }, + { + "table": "public.entries", + "columns": [ + "account_id" + ], + "cardinality": "zero_or_more", + "parent_table": "public.accounts", + "parent_columns": [ + "id" + ], + "parent_cardinality": "exactly_one", + "def": "FOREIGN KEY (account_id) REFERENCES accounts(id) DEFERRABLE" + }, + { + "table": "public.transfers", + "columns": [ + "from_account_id" + ], + "cardinality": "zero_or_more", + "parent_table": "public.accounts", + "parent_columns": [ + "id" + ], + "parent_cardinality": "exactly_one", + "def": "FOREIGN KEY (from_account_id) REFERENCES accounts(id) DEFERRABLE" + }, + { + "table": "public.transfers", + "columns": [ + "to_account_id" + ], + "cardinality": "zero_or_more", + "parent_table": "public.accounts", + "parent_columns": [ + "id" + ], + "parent_cardinality": "exactly_one", + "def": "FOREIGN KEY (to_account_id) REFERENCES accounts(id) DEFERRABLE" + } + ], + "driver": { + "name": "postgres", + "database_version": "PostgreSQL 18.3 on x86_64-pc-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit", + "meta": { + "current_schema": "public", + "search_paths": [ + "root", + "public" + ], + "dict": { + "Functions": "Stored procedures and functions" + } + } + } +} diff --git a/docs/schema/schema.svg b/docs/schema/schema.svg new file mode 100644 index 0000000..2153f7a --- /dev/null +++ b/docs/schema/schema.svg @@ -0,0 +1,155 @@ + + + + + + +gobank + + + +public.schema_migrations + + +public.schema_migrations +     +[BASE TABLE] + +version +[bigint] + +dirty +[boolean] + + + +public.accounts + + +public.accounts +     +[BASE TABLE] + +id +[bigint] + +owner +[varchar] + +balance +[numeric] + +currency +[varchar] + +created_at +[timestamp with time zone] + +deleted_at +[timestamp with time zone] + + + +public.users + + +public.users +     +[BASE TABLE] + +username +[varchar] + +hashed_password +[varchar] + +full_name +[varchar] + +email +[varchar] + +password_changed_at +[timestamp with time zone] + +created_at +[timestamp with time zone] + + + +public.accounts:owner->public.users:username + + +FOREIGN KEY (owner) REFERENCES users(username) DEFERRABLE + + + +public.entries + + +public.entries +     +[BASE TABLE] + +id +[bigint] + +account_id +[bigint] + +amount +[numeric] + +created_at +[timestamp with time zone] + + + +public.entries:account_id->public.accounts:id + + +FOREIGN KEY (account_id) REFERENCES accounts(id) DEFERRABLE + + + +public.transfers + + +public.transfers +     +[BASE TABLE] + +id +[bigint] + +from_account_id +[bigint] + +to_account_id +[bigint] + +amount +[numeric] + +created_at +[timestamp with time zone] + + + +public.transfers:from_account_id->public.accounts:id + + +FOREIGN KEY (from_account_id) REFERENCES accounts(id) DEFERRABLE + + + +public.transfers:to_account_id->public.accounts:id + + +FOREIGN KEY (to_account_id) REFERENCES accounts(id) DEFERRABLE + + + diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..478b631 --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,919 @@ +{ + "schemes": [ + "http" + ], + "swagger": "2.0", + "info": { + "description": "GoBank is a simple banking API built with Go, Gin, and SQLC.", + "title": "GoBank API", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "email": "support@localhost" + }, + "license": { + "name": "MIT", + "url": "https://opensource.org/licenses/MIT" + }, + "version": "1.0" + }, + "basePath": "/", + "paths": { + "/accounts": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "List accounts for the authenticated user with pagination.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "accounts" + ], + "summary": "List accounts", + "parameters": [ + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "Page size", + "name": "size", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/api.AccountResponse" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create an account in a supported currency for the authenticated user.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "accounts" + ], + "summary": "Create account", + "parameters": [ + { + "description": "Create account request", + "name": "account", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.createAccountRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.AccountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + }, + "/accounts/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a single account by ID for the authenticated user.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "accounts" + ], + "summary": "Get account", + "parameters": [ + { + "type": "integer", + "description": "Account ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.AccountResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete an account by ID for the authenticated user.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "accounts" + ], + "summary": "Delete account", + "parameters": [ + { + "type": "integer", + "description": "Account ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + }, + "/entries": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "List entries for a specific account with pagination.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "entries" + ], + "summary": "List entries", + "parameters": [ + { + "type": "integer", + "description": "Account ID", + "name": "account_id", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "Page size", + "name": "size", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/api.EntryResponse" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + }, + "/entries/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a single entry by ID when it belongs to the authenticated user.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "entries" + ], + "summary": "Get entry", + "parameters": [ + { + "type": "integer", + "description": "Entry ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.EntryResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + }, + "/transfers": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "List transfers for a specific account with pagination.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfers" + ], + "summary": "List transfers", + "parameters": [ + { + "type": "integer", + "description": "Account ID", + "name": "account_id", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "Page size", + "name": "size", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/api.TransferResponse" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a transfer from one account to another in a supported currency.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfers" + ], + "summary": "Create transfer", + "parameters": [ + { + "description": "Transfer request", + "name": "transfer", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.transferRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.TransferTxResultResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + }, + "/transfers/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a transfer by ID when the authenticated user is sender or receiver.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "transfers" + ], + "summary": "Get transfer", + "parameters": [ + { + "type": "integer", + "description": "Transfer ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.TransferResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + }, + "/users": { + "post": { + "description": "Register a new user with username, password, full name, and email.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "Create user", + "parameters": [ + { + "description": "Create user request", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.createUserRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.UserResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + }, + "/users/login": { + "post": { + "description": "Authenticate with username and password to receive an access token.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "Login user", + "parameters": [ + { + "description": "Login credentials", + "name": "credentials", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.loginUserRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LoginUserResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "api.AccountResponse": { + "type": "object", + "properties": { + "balance": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "deleted_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "owner": { + "type": "string" + } + } + }, + "api.EntryResponse": { + "type": "object", + "properties": { + "account_id": { + "type": "integer" + }, + "amount": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + } + } + }, + "api.ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "api.LoginUserResponse": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/api.UserResponse" + } + } + }, + "api.TransferResponse": { + "type": "object", + "properties": { + "amount": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "from_account_id": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "to_account_id": { + "type": "integer" + } + } + }, + "api.TransferTxResultResponse": { + "type": "object", + "properties": { + "from_account": { + "$ref": "#/definitions/api.AccountResponse" + }, + "from_entry": { + "$ref": "#/definitions/api.EntryResponse" + }, + "to_account": { + "$ref": "#/definitions/api.AccountResponse" + }, + "to_entry": { + "$ref": "#/definitions/api.EntryResponse" + }, + "transfer": { + "$ref": "#/definitions/api.TransferResponse" + } + } + }, + "api.UserResponse": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "password_changed_at": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "api.createAccountRequest": { + "type": "object", + "required": [ + "currency" + ], + "properties": { + "currency": { + "type": "string" + } + } + }, + "api.createUserRequest": { + "type": "object", + "required": [ + "email", + "full_name", + "password", + "username" + ], + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "password": { + "type": "string", + "minLength": 8 + }, + "username": { + "type": "string" + } + } + }, + "api.loginUserRequest": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "password": { + "type": "string", + "minLength": 8 + }, + "username": { + "type": "string" + } + } + }, + "api.transferRequest": { + "type": "object", + "required": [ + "amount", + "currency", + "from_account_id", + "to_account_id" + ], + "properties": { + "amount": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "from_account_id": { + "type": "integer", + "minimum": 1 + }, + "to_account_id": { + "type": "integer", + "minimum": 1 + } + } + } + }, + "securityDefinitions": { + "BearerAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..cdba82f --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,600 @@ +basePath: / +definitions: + api.AccountResponse: + properties: + balance: + type: string + created_at: + type: string + currency: + type: string + deleted_at: + type: string + id: + type: integer + owner: + type: string + type: object + api.EntryResponse: + properties: + account_id: + type: integer + amount: + type: string + created_at: + type: string + id: + type: integer + type: object + api.ErrorResponse: + properties: + error: + type: string + type: object + api.LoginUserResponse: + properties: + access_token: + type: string + user: + $ref: '#/definitions/api.UserResponse' + type: object + api.TransferResponse: + properties: + amount: + type: string + created_at: + type: string + from_account_id: + type: integer + id: + type: integer + to_account_id: + type: integer + type: object + api.TransferTxResultResponse: + properties: + from_account: + $ref: '#/definitions/api.AccountResponse' + from_entry: + $ref: '#/definitions/api.EntryResponse' + to_account: + $ref: '#/definitions/api.AccountResponse' + to_entry: + $ref: '#/definitions/api.EntryResponse' + transfer: + $ref: '#/definitions/api.TransferResponse' + type: object + api.UserResponse: + properties: + created_at: + type: string + email: + type: string + full_name: + type: string + password_changed_at: + type: string + username: + type: string + type: object + api.createAccountRequest: + properties: + currency: + type: string + required: + - currency + type: object + api.createUserRequest: + properties: + email: + type: string + full_name: + type: string + password: + minLength: 8 + type: string + username: + type: string + required: + - email + - full_name + - password + - username + type: object + api.loginUserRequest: + properties: + password: + minLength: 8 + type: string + username: + type: string + required: + - password + - username + type: object + api.transferRequest: + properties: + amount: + type: string + currency: + type: string + from_account_id: + minimum: 1 + type: integer + to_account_id: + minimum: 1 + type: integer + required: + - amount + - currency + - from_account_id + - to_account_id + type: object +info: + contact: + email: support@localhost + name: API Support + description: GoBank is a simple banking API built with Go, Gin, and SQLC. + license: + name: MIT + url: https://opensource.org/licenses/MIT + termsOfService: http://swagger.io/terms/ + title: GoBank API + version: "1.0" +paths: + /accounts: + get: + consumes: + - application/json + description: List accounts for the authenticated user with pagination. + parameters: + - description: Page number + in: query + name: page + required: true + type: integer + - description: Page size + in: query + name: size + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/api.AccountResponse' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/api.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.ErrorResponse' + security: + - BearerAuth: [] + summary: List accounts + tags: + - accounts + post: + consumes: + - application/json + description: Create an account in a supported currency for the authenticated + user. + parameters: + - description: Create account request + in: body + name: account + required: true + schema: + $ref: '#/definitions/api.createAccountRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.AccountResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/api.ErrorResponse' + "403": + description: Forbidden + schema: + $ref: '#/definitions/api.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.ErrorResponse' + security: + - BearerAuth: [] + summary: Create account + tags: + - accounts + /accounts/{id}: + delete: + consumes: + - application/json + description: Delete an account by ID for the authenticated user. + parameters: + - description: Account ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "204": + description: No Content + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/api.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/api.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.ErrorResponse' + security: + - BearerAuth: [] + summary: Delete account + tags: + - accounts + get: + consumes: + - application/json + description: Get a single account by ID for the authenticated user. + parameters: + - description: Account ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.AccountResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/api.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/api.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.ErrorResponse' + security: + - BearerAuth: [] + summary: Get account + tags: + - accounts + /entries: + get: + consumes: + - application/json + description: List entries for a specific account with pagination. + parameters: + - description: Account ID + in: query + name: account_id + required: true + type: integer + - description: Page number + in: query + name: page + required: true + type: integer + - description: Page size + in: query + name: size + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/api.EntryResponse' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/api.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/api.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.ErrorResponse' + security: + - BearerAuth: [] + summary: List entries + tags: + - entries + /entries/{id}: + get: + consumes: + - application/json + description: Get a single entry by ID when it belongs to the authenticated user. + parameters: + - description: Entry ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.EntryResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/api.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/api.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.ErrorResponse' + security: + - BearerAuth: [] + summary: Get entry + tags: + - entries + /transfers: + get: + consumes: + - application/json + description: List transfers for a specific account with pagination. + parameters: + - description: Account ID + in: query + name: account_id + required: true + type: integer + - description: Page number + in: query + name: page + required: true + type: integer + - description: Page size + in: query + name: size + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/api.TransferResponse' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/api.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/api.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.ErrorResponse' + security: + - BearerAuth: [] + summary: List transfers + tags: + - transfers + post: + consumes: + - application/json + description: Create a transfer from one account to another in a supported currency. + parameters: + - description: Transfer request + in: body + name: transfer + required: true + schema: + $ref: '#/definitions/api.transferRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.TransferTxResultResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/api.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/api.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.ErrorResponse' + security: + - BearerAuth: [] + summary: Create transfer + tags: + - transfers + /transfers/{id}: + get: + consumes: + - application/json + description: Get a transfer by ID when the authenticated user is sender or receiver. + parameters: + - description: Transfer ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.TransferResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/api.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/api.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.ErrorResponse' + security: + - BearerAuth: [] + summary: Get transfer + tags: + - transfers + /users: + post: + consumes: + - application/json + description: Register a new user with username, password, full name, and email. + parameters: + - description: Create user request + in: body + name: user + required: true + schema: + $ref: '#/definitions/api.createUserRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.UserResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorResponse' + "403": + description: Forbidden + schema: + $ref: '#/definitions/api.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.ErrorResponse' + summary: Create user + tags: + - users + /users/login: + post: + consumes: + - application/json + description: Authenticate with username and password to receive an access token. + parameters: + - description: Login credentials + in: body + name: credentials + required: true + schema: + $ref: '#/definitions/api.loginUserRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.LoginUserResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/api.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/api.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.ErrorResponse' + summary: Login user + tags: + - users +schemes: +- http +securityDefinitions: + BearerAuth: + in: header + name: Authorization + type: apiKey +swagger: "2.0" diff --git a/go.mod b/go.mod index 5a581cc..efdb5df 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/HyperNaser/gobank go 1.26.1 -require github.com/lib/pq v1.12.0 +require github.com/lib/pq v1.12.3 require ( github.com/gin-gonic/gin v1.12.0 @@ -16,54 +16,70 @@ require github.com/google/uuid v1.6.0 require ( github.com/golang-jwt/jwt/v5 v5.3.1 github.com/o1egl/paseto v1.0.0 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.1 + github.com/swaggo/swag v1.16.6 ) require ( + github.com/KyleBanks/depth v1.2.1 // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 // indirect github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect + github.com/go-openapi/jsonpointer v0.23.1 // indirect + github.com/go-openapi/jsonreference v0.21.5 // indirect + github.com/go-openapi/spec v0.22.4 // indirect + github.com/go-openapi/swag/conv v0.26.0 // indirect + github.com/go-openapi/swag/jsonname v0.26.0 // indirect + github.com/go-openapi/swag/jsonutils v0.26.0 // indirect + github.com/go-openapi/swag/loading v0.26.0 // indirect + github.com/go-openapi/swag/stringutils v0.26.0 // indirect + github.com/go-openapi/swag/typeutils v0.26.0 // indirect + github.com/go-openapi/swag/yamlutils v0.26.0 // indirect github.com/pkg/errors v0.9.1 // indirect + golang.org/x/mod v0.36.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/tools v0.45.0 // indirect ) require ( - github.com/bytedance/gopkg v0.1.3 // indirect - github.com/bytedance/sonic v1.15.0 // indirect - github.com/bytedance/sonic/loader v0.5.0 // indirect - github.com/cloudwego/base64x v0.1.6 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect - github.com/gin-contrib/sse v1.1.0 // indirect + github.com/bytedance/gopkg v0.1.4 // indirect + github.com/bytedance/sonic v1.15.1 // indirect + github.com/bytedance/sonic/loader v0.5.1 // indirect + github.com/cloudwego/base64x v0.1.7 // indirect + github.com/fsnotify/fsnotify v1.10.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect + github.com/gin-contrib/sse v1.1.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.30.1 - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect - github.com/goccy/go-json v0.10.5 // indirect + github.com/go-playground/validator/v10 v10.30.2 + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect + github.com/goccy/go-json v0.10.6 // indirect github.com/goccy/go-yaml v1.19.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.22 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pelletier/go-toml/v2 v2.3.1 // indirect github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.59.0 // indirect - github.com/sagikazarmark/locafero v0.11.0 // indirect - github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/quic-go/quic-go v0.59.1 // indirect + github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.1 // indirect - go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect + go.mongodb.org/mongo-driver/v2 v2.6.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/arch v0.22.0 // indirect - golang.org/x/crypto v0.50.0 - golang.org/x/net v0.52.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/text v0.36.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + golang.org/x/arch v0.27.0 // indirect + golang.org/x/crypto v0.51.0 + golang.org/x/net v0.54.0 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/text v0.37.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect ) require ( diff --git a/go.sum b/go.sum index 517f344..6e44cb1 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/chacha20poly1305 v0.0.0-20170617001512-233f39982aeb/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU= @@ -5,39 +7,68 @@ github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 h1:1DcvRPZOd github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= -github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= -github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= -github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= -github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= -github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= -github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= -github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= -github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM= +github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4= +github.com/bytedance/sonic v1.15.1 h1:nJD5PmM0vY7J8CT6MxoqbVAAMhkSmV2HgRAUrrpLoOw= +github.com/bytedance/sonic v1.15.1/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA= +github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI= +github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI= +github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= -github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= -github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= +github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko= +github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s= github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= +github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= +github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= +github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= +github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ= +github.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= +github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= +github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= +github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= +github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= +github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= +github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= +github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= +github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= +github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= +github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= +github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= +github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= +github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= +github.com/go-openapi/testify/enable/yaml/v2 v2.4.2 h1:5zRca5jw7lzVREKCZVNBpysDNBjj74rBh0N2BGQbSR0= +github.com/go-openapi/testify/enable/yaml/v2 v2.4.2/go.mod h1:XVevPw5hUXuV+5AkI1u1PeAm27EQVrhXTTCPAF85LmE= +github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4= +github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= -github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ= +github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -57,10 +88,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo= -github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= +github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -68,8 +99,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/o1egl/paseto v1.0.0 h1:bwpvPu2au176w4IBlhbyUv/S5VPptERIA99Oap5qUd0= github.com/o1egl/paseto v1.0.0/go.mod h1:5HxsZPmw/3RI2pAwGo1HhOOwSdvBpcuVzO7uDkm+CLU= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc= +github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -77,16 +108,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= -github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic= +github.com/quic-go/quic-go v0.59.1/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= -github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= +github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= @@ -109,31 +138,69 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY= +github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw= +github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= +github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= -go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= -go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver/v2 v2.6.0 h1:b9sJOYrkmt4l8bY43ZenFBcPlhYIjaOfYHLtbB/5qi8= +go.mongodb.org/mongo-driver/v2 v2.6.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= -golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/arch v0.27.0 h1:0WNVcR8u9yFz8j5FvdHpgwNp3FS5U4guYdzHwEiGjoU= +golang.org/x/arch v0.27.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/main.go b/main.go index f8d0411..a0bad62 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,16 @@ +// @title GoBank API +// @version 1.0 +// @description GoBank is a simple banking API built with Go, Gin, and SQLC. +// @termsOfService http://swagger.io/terms/ +// @contact.name API Support +// @contact.email support@localhost +// @license.name MIT +// @license.url https://opensource.org/licenses/MIT +// @BasePath / +// @schemes http +// @securityDefinitions.apikey BearerAuth +// @in header +// @name Authorization package main import ( @@ -6,6 +19,7 @@ import ( "github.com/HyperNaser/gobank/api" db "github.com/HyperNaser/gobank/db/sqlc" + "github.com/HyperNaser/gobank/docs" "github.com/HyperNaser/gobank/util" _ "github.com/lib/pq" ) @@ -21,6 +35,12 @@ func main() { log.Fatal("Failed to connect to database:", err) } + if config.ServerAddress == "0.0.0.0:8080" { + docs.SwaggerInfo.Host = "localhost:8080" + } else { + docs.SwaggerInfo.Host = config.ServerAddress + } + store := db.NewStore(conn) server, err := api.NewServer(config, store) if err != nil { diff --git a/tbls.yaml b/tbls.yaml new file mode 100644 index 0000000..cbd5cae --- /dev/null +++ b/tbls.yaml @@ -0,0 +1,2 @@ +dsn: postgresql://root:root@localhost:5432/gobank?sslmode=disable +docPath: docs/schema \ No newline at end of file