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
+
+
+
+---
+
+> 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
+
+
+
+---
+
+> 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 @@
+
+
+
+
+
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
+
+
+
+---
+
+> 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 @@
+
+
+
+
+
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
+
+
+
+---
+
+> 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 @@
+
+
+
+
+
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
+
+
+
+---
+
+> 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 @@
+
+
+
+
+
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
+
+
+
+---
+
+> 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 @@
+
+
+
+
+
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 @@
+
+
+
+
+
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