SimpleBank
For local development
- Run app using docker compose:
docker compose up
- Create new migration
migrate create -ext sql -dir db/migration -seq [name]
Feat/dbdocs
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package api | ||
|
||
import ( | ||
"database/sql" | ||
"errors" | ||
"github.com/gin-gonic/gin" | ||
"net/http" | ||
"time" | ||
) | ||
|
||
type renewAccessTokenRequest struct { | ||
RefreshToken string `json:"refresh_token" binding:"required"` | ||
} | ||
|
||
type renewAccessTokenResponse struct { | ||
AccessToken string `json:"access_token"` | ||
AccessTokenExpiresAt time.Time `json:"access_token_expires_at"` | ||
} | ||
|
||
func (server *Server) renewAccessTokenUser(ctx *gin.Context) { | ||
var req renewAccessTokenRequest | ||
|
||
if err := ctx.ShouldBindJSON(&req); err != nil { | ||
ctx.JSON(http.StatusBadRequest, errorResponse(err)) | ||
return | ||
} | ||
|
||
refreshPayload, err := server.tokenMaker.VerifyToken(req.RefreshToken) | ||
|
||
session, err := server.store.GetSession(ctx, refreshPayload.Id) | ||
if err != nil { | ||
if errors.Is(err, sql.ErrNoRows) { | ||
ctx.JSON(http.StatusNotFound, errorResponse(err)) | ||
return | ||
} | ||
|
||
ctx.JSON(http.StatusInternalServerError, errorResponse(err)) | ||
return | ||
} | ||
|
||
if session.RefreshToken != req.RefreshToken { | ||
err = errors.New("mismatched session token") | ||
ctx.JSON(http.StatusUnauthorized, errorResponse(err)) | ||
return | ||
} | ||
|
||
if session.IsBlocked { | ||
err = errors.New("target session is blocked") | ||
ctx.JSON(http.StatusUnauthorized, errorResponse(err)) | ||
return | ||
} | ||
|
||
if session.Username != refreshPayload.Username { | ||
err = errors.New("incorrect session user") | ||
ctx.JSON(http.StatusUnauthorized, errorResponse(err)) | ||
return | ||
} | ||
|
||
if time.Now().After(session.ExpiresAt) { | ||
err = errors.New("expired session") | ||
ctx.JSON(http.StatusUnauthorized, errorResponse(err)) | ||
return | ||
} | ||
|
||
token, accessTokenPayload, err := server.tokenMaker.CreateToken( | ||
refreshPayload.Username, | ||
server.config.TokenDuration, | ||
) | ||
if err != nil { | ||
ctx.JSON(http.StatusInternalServerError, errorResponse(err)) | ||
return | ||
} | ||
|
||
res := renewAccessTokenResponse{ | ||
AccessToken: token, | ||
AccessTokenExpiresAt: accessTokenPayload.ExpiredAt, | ||
} | ||
|
||
ctx.JSON(http.StatusOK, res) | ||
return | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package api | ||
|
||
import ( | ||
mockdb "github.com/cukhoaimon/SimpleBank/db/mock" | ||
"github.com/gin-gonic/gin" | ||
"net/http/httptest" | ||
"testing" | ||
) | ||
|
||
func TestServer_renewAccessTokenUser(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
body gin.H | ||
buildStubs func(store *mockdb.MockStore) | ||
checkResponse func(t *testing.T, recorder *httptest.ResponseRecorder) | ||
}{ | ||
{ /* TODO: add test cases*/ }, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
|
||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
DROP TABLE IF EXISTS entries; | ||
DROP TABLE IF EXISTS transfers; | ||
DROP TABLE IF EXISTS accounts; | ||
DROP TABLE IF EXISTS users; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
DROP TABLE IF EXISTS "sessions"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
CREATE TABLE "sessions" ( | ||
"id" uuid PRIMARY KEY, | ||
"username" varchar NOT NULL, | ||
"refresh_token" varchar NOT NULL, | ||
"user_agent" varchar NOT NULL, | ||
"client_ip" varchar NOT NULL, | ||
"is_blocked" boolean NOT NULL DEFAULT false, | ||
"expires_at" timestamptz NOT NULL, | ||
"created_at" timestamptz NOT NULL DEFAULT (now()) | ||
); | ||
|
||
ALTER TABLE "sessions" ADD FOREIGN KEY ("username") REFERENCES "users" ("username"); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
-- name: CreateSession :one | ||
INSERT INTO sessions ( | ||
"id", | ||
"username", | ||
"refresh_token", | ||
"user_agent", | ||
"client_ip", | ||
"is_blocked", | ||
"expires_at" | ||
) VALUES ( | ||
$1, $2, $3, $4, $5, $6, $7 | ||
) RETURNING *; | ||
|
||
-- name: GetSession :one | ||
SELECT * FROM sessions | ||
WHERE id = $1 LIMIT 1; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.