From c62dd7f42124da47361b2298ded2ae81d77360d0 Mon Sep 17 00:00:00 2001 From: d3m0k1d Date: Mon, 9 Feb 2026 23:33:31 +0300 Subject: [PATCH 1/8] feat: Add new handlers and repository for oauth2 --- backend/internal/handlers/auth_handlers.go | 27 +++++++++++++++++++ backend/internal/handlers/post_handlers.go | 2 +- .../internal/handlers/registry_handlers.go | 4 ++- .../internal/repositories/auth_repository.go | 22 +++++++++++++++ backend/internal/repositories/interface.go | 4 +++ 5 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 backend/internal/handlers/auth_handlers.go create mode 100644 backend/internal/repositories/auth_repository.go diff --git a/backend/internal/handlers/auth_handlers.go b/backend/internal/handlers/auth_handlers.go new file mode 100644 index 0000000..a5c5747 --- /dev/null +++ b/backend/internal/handlers/auth_handlers.go @@ -0,0 +1,27 @@ +package handlers + +import ( + "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/repositories" + + "github.com/gin-gonic/gin" +) + +type AuthHandlers struct { + repo repositories.AuthRepository +} + +func NewAuthHandlers(repo repositories.AuthRepository) *AuthHandlers { + return &AuthHandlers{repo: repo} +} + +// Callback godoc +// @Summary Callback for oauth2 providers +// @Description Callback for oauth2 providers +// @Tags auth +// @Accept json +// @Produce json +// @Success 200 {object} map[string]string +// @Router /callback [get] +func (h *AuthHandlers) Callback(c *gin.Context) { + +} diff --git a/backend/internal/handlers/post_handlers.go b/backend/internal/handlers/post_handlers.go index 6bcfa63..ef68be9 100644 --- a/backend/internal/handlers/post_handlers.go +++ b/backend/internal/handlers/post_handlers.go @@ -13,7 +13,7 @@ type PostHandlers struct { repo repositories.PostRepository } -func NewPostHandler(repo repositories.PostRepository) *PostHandlers { +func NewPostHandlers(repo repositories.PostRepository) *PostHandlers { return &PostHandlers{repo: repo} } diff --git a/backend/internal/handlers/registry_handlers.go b/backend/internal/handlers/registry_handlers.go index 9394aac..e83b2cc 100644 --- a/backend/internal/handlers/registry_handlers.go +++ b/backend/internal/handlers/registry_handlers.go @@ -8,8 +8,10 @@ import ( ) func Register(router *gin.Engine, db *sql.DB) { - handler_posts := NewPostHandler(repositories.NewPostRepository(db)) + handler_posts := NewPostHandlers(repositories.NewPostRepository(db)) + handler_auth := NewAuthHandlers(repositories.NewAuthRepository(db)) v1 := router.Group("api/v1") + v1.GET("/callback", handler_auth.Callback) posts := v1.Group("posts") { posts.GET("/", handler_posts.GetPosts) diff --git a/backend/internal/repositories/auth_repository.go b/backend/internal/repositories/auth_repository.go new file mode 100644 index 0000000..d8dbb99 --- /dev/null +++ b/backend/internal/repositories/auth_repository.go @@ -0,0 +1,22 @@ +package repositories + +import ( + "context" + "database/sql" + + "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/logger" +) + +type authRepository struct { + db *sql.DB + logger *logger.Logger +} + +func NewAuthRepository(db *sql.DB) AuthRepository { + return &authRepository{ + db: db, + logger: logger.New(false), + } +} +func (a *authRepository) Test(ctx context.Context) { +} diff --git a/backend/internal/repositories/interface.go b/backend/internal/repositories/interface.go index c22dc8a..ccd80e9 100644 --- a/backend/internal/repositories/interface.go +++ b/backend/internal/repositories/interface.go @@ -13,3 +13,7 @@ type PostRepository interface { Update(ctx context.Context, id int, post storage.Post) error Delete(ctx context.Context, id int) error } + +type AuthRepository interface { + Test(ctx context.Context) +} From 53d8760912dbebfe99f74e3a4f56e84213cba9e0 Mon Sep 17 00:00:00 2001 From: d3m0k1d Date: Tue, 10 Feb 2026 00:07:29 +0300 Subject: [PATCH 2/8] feat: add simple Oauth2 logic for github --- backend/docs/docs.go | 26 ++++++++++++++++ backend/docs/swagger.json | 26 ++++++++++++++++ backend/docs/swagger.yaml | 17 ++++++++++ backend/go.mod | 1 + backend/go.sum | 2 ++ backend/internal/handlers/auth_handlers.go | 31 ++++++++++++++++--- backend/internal/handlers/post_handlers.go | 22 ++++++------- .../internal/handlers/registry_handlers.go | 2 +- 8 files changed, 109 insertions(+), 18 deletions(-) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 0ffb957..211e957 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -15,6 +15,32 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { + "/callback": { + "get": { + "description": "Callback for oauth2 providers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Callback for oauth2 providers", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, "/posts": { "get": { "description": "Get all posts", diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 518167b..2a9b7bc 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -4,6 +4,32 @@ "contact": {} }, "paths": { + "/callback": { + "get": { + "description": "Callback for oauth2 providers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Callback for oauth2 providers", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, "/posts": { "get": { "description": "Get all posts", diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index ff5bb35..53470b0 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -32,6 +32,23 @@ definitions: info: contact: {} paths: + /callback: + get: + consumes: + - application/json + description: Callback for oauth2 providers + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + summary: Callback for oauth2 providers + tags: + - auth /posts: get: consumes: diff --git a/backend/go.mod b/backend/go.mod index 4e7eb31..362c206 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -57,6 +57,7 @@ require ( golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect golang.org/x/mod v0.32.0 // indirect golang.org/x/net v0.49.0 // indirect + golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index da97b44..660e839 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -138,6 +138,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= 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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= diff --git a/backend/internal/handlers/auth_handlers.go b/backend/internal/handlers/auth_handlers.go index a5c5747..c7c94ff 100644 --- a/backend/internal/handlers/auth_handlers.go +++ b/backend/internal/handlers/auth_handlers.go @@ -1,17 +1,30 @@ package handlers import ( - "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/repositories" + "os" + "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/logger" + "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/repositories" "github.com/gin-gonic/gin" + "golang.org/x/oauth2" + "golang.org/x/oauth2/endpoints" ) type AuthHandlers struct { - repo repositories.AuthRepository + repo repositories.AuthRepository + logger *logger.Logger +} + +var configGithub = &oauth2.Config{ + ClientID: os.Getenv("GITHUB_CLIENT_ID"), + ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"), + RedirectURL: "https://d3m0k1d.ru/", + Scopes: []string{"user"}, + Endpoint: endpoints.GitHub, } func NewAuthHandlers(repo repositories.AuthRepository) *AuthHandlers { - return &AuthHandlers{repo: repo} + return &AuthHandlers{repo: repo, logger: logger.New(false)} } // Callback godoc @@ -21,7 +34,15 @@ func NewAuthHandlers(repo repositories.AuthRepository) *AuthHandlers { // @Accept json // @Produce json // @Success 200 {object} map[string]string -// @Router /callback [get] -func (h *AuthHandlers) Callback(c *gin.Context) { +// @Router /callback/github [get] +func (h *AuthHandlers) CallbackGithub(c *gin.Context) { + h.logger.Info("CallbackGithub called") + token, err := configGithub.Exchange(c.Request.Context(), c.Query("code")) + if err != nil { + h.logger.Error("error request: " + err.Error()) + c.Status(500) + } + h.logger.Info("200 OK GET /callback/github") + c.JSON(200, gin.H{"token": token}) } diff --git a/backend/internal/handlers/post_handlers.go b/backend/internal/handlers/post_handlers.go index ef68be9..0e074d4 100644 --- a/backend/internal/handlers/post_handlers.go +++ b/backend/internal/handlers/post_handlers.go @@ -10,15 +10,14 @@ import ( ) type PostHandlers struct { - repo repositories.PostRepository + repo repositories.PostRepository + logger *logger.Logger } func NewPostHandlers(repo repositories.PostRepository) *PostHandlers { - return &PostHandlers{repo: repo} + return &PostHandlers{repo: repo, logger: logger.New(false)} } -var log = logger.New(false) - // GetPosts godoc // @Summary Get all posts // @Description Get all posts @@ -31,10 +30,10 @@ func (h *PostHandlers) GetPosts(c *gin.Context) { var result []storage.PostReq result, err := h.repo.GetAll(c.Request.Context()) if err != nil { - log.Error("error request: " + err.Error()) + h.logger.Error("error request: " + err.Error()) c.Status(500) } - log.Info("200 OK GET /posts") + h.logger.Info("200 OK GET /posts") c.JSON(200, result) } @@ -54,15 +53,15 @@ func (h *PostHandlers) GetPost(c *gin.Context) { id_p := c.Param("id") id, err := strconv.Atoi(id_p) if err != nil { - log.Error("error request: " + err.Error()) + h.logger.Error("error request: " + err.Error()) c.Status(500) } result, err = h.repo.GetByID(c.Request.Context(), id) if err != nil { - log.Error("error request: " + err.Error()) + h.logger.Error("error request: " + err.Error()) c.Status(500) } - log.Info("200 OK GET /posts/" + id_p) + h.logger.Info("200 OK GET /posts/" + id_p) c.JSON(200, result) // TODO: added validaton for 400 response } @@ -112,7 +111,7 @@ func (h *PostHandlers) UpdatePost(c *gin.Context) { id_p := c.Param("id") id, err := strconv.Atoi(id_p) if err != nil { - log.Error("error request: " + err.Error()) + h.logger.Error("error request: " + err.Error()) c.Status(500) } var req storage.Post @@ -127,7 +126,7 @@ func (h *PostHandlers) UpdatePost(c *gin.Context) { } c.JSON(200, req) - log.Info("200 OK PUT /posts/" + id_p) + h.logger.Info("200 OK PUT /posts/" + id_p) } // DeletePost godoc @@ -139,5 +138,4 @@ func (h *PostHandlers) UpdatePost(c *gin.Context) { // @Success 200 {object} storage.Post // @Router /posts/{id} [delete] func DeletePost(c *gin.Context) { - log.Info("DeletePost") } diff --git a/backend/internal/handlers/registry_handlers.go b/backend/internal/handlers/registry_handlers.go index e83b2cc..f121cfe 100644 --- a/backend/internal/handlers/registry_handlers.go +++ b/backend/internal/handlers/registry_handlers.go @@ -11,7 +11,7 @@ func Register(router *gin.Engine, db *sql.DB) { handler_posts := NewPostHandlers(repositories.NewPostRepository(db)) handler_auth := NewAuthHandlers(repositories.NewAuthRepository(db)) v1 := router.Group("api/v1") - v1.GET("/callback", handler_auth.Callback) + v1.GET("/callback", handler_auth.CallbackGithub) posts := v1.Group("posts") { posts.GET("/", handler_posts.GetPosts) From 7822da7e5c7dbf3bf1e67b6f6fee4b3d66d89afc Mon Sep 17 00:00:00 2001 From: d3m0k1d Date: Tue, 10 Feb 2026 00:09:30 +0300 Subject: [PATCH 3/8] feat: update openapi docs --- backend/docs/docs.go | 2 +- backend/docs/swagger.json | 2 +- backend/docs/swagger.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 211e957..fb0a6ec 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -15,7 +15,7 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { - "/callback": { + "/callback/github": { "get": { "description": "Callback for oauth2 providers", "consumes": [ diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 2a9b7bc..c52c036 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -4,7 +4,7 @@ "contact": {} }, "paths": { - "/callback": { + "/callback/github": { "get": { "description": "Callback for oauth2 providers", "consumes": [ diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 53470b0..3a1cd46 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -32,7 +32,7 @@ definitions: info: contact: {} paths: - /callback: + /callback/github: get: consumes: - application/json From a58e0f44515365513288787b015c997c63da470b Mon Sep 17 00:00:00 2001 From: d3m0k1d Date: Tue, 10 Feb 2026 21:26:48 +0300 Subject: [PATCH 4/8] feat: add simple jwt middleware and update oauth handlers --- backend/.gitignore | 1 + backend/go.mod | 1 + backend/go.sum | 2 + backend/internal/auth/jwt.go | 60 ++++++++++++++ backend/internal/handlers/auth_handlers.go | 79 ++++++++++++++----- .../internal/handlers/registry_handlers.go | 3 +- backend/internal/storage/migrations.go | 10 ++- backend/internal/storage/models.go | 8 ++ 8 files changed, 142 insertions(+), 22 deletions(-) create mode 100644 backend/internal/auth/jwt.go diff --git a/backend/.gitignore b/backend/.gitignore index 634b0a2..adef40a 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,2 +1,3 @@ /bin /data +.env diff --git a/backend/go.mod b/backend/go.mod index 362c206..8341c4a 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -31,6 +31,7 @@ require ( github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/backend/go.sum b/backend/go.sum index 660e839..3a6e0ba 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -61,6 +61,8 @@ 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/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= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/backend/internal/auth/jwt.go b/backend/internal/auth/jwt.go new file mode 100644 index 0000000..1da5967 --- /dev/null +++ b/backend/internal/auth/jwt.go @@ -0,0 +1,60 @@ +package auth + +import ( + "fmt" + "os" + "strings" + + "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/storage" + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" +) + +var jwtSecret = []byte(os.Getenv("JWT_SECRET")) + +func GenerateJWT(user storage.User) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.MapClaims{ + "id": user.ID, + "email": user.Email, + "login": user.GithubLogin, + }) + tokenString, err := token.SignedString(jwtSecret) + if err != nil { + return "", err + } + return tokenString, nil +} + +func JWTMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + auth := c.GetHeader("Authorization") + if !strings.HasPrefix(auth, "Bearer ") { + c.AbortWithStatusJSON(401, gin.H{"error": "Bearer required"}) + return + } + + tokenString := strings.TrimPrefix(auth, "Bearer ") + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return jwtSecret, nil + }) + + if err != nil || !token.Valid { + c.AbortWithStatusJSON(401, gin.H{"error": "invalid token"}) + return + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + c.AbortWithStatusJSON(401, gin.H{"error": "invalid claims"}) + return + } + + c.Set("user_id", int(claims["id"].(float64))) + c.Set("login", claims["login"].(string)) + c.Next() + } +} diff --git a/backend/internal/handlers/auth_handlers.go b/backend/internal/handlers/auth_handlers.go index c7c94ff..b3ef451 100644 --- a/backend/internal/handlers/auth_handlers.go +++ b/backend/internal/handlers/auth_handlers.go @@ -13,36 +13,75 @@ import ( type AuthHandlers struct { repo repositories.AuthRepository logger *logger.Logger -} - -var configGithub = &oauth2.Config{ - ClientID: os.Getenv("GITHUB_CLIENT_ID"), - ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"), - RedirectURL: "https://d3m0k1d.ru/", - Scopes: []string{"user"}, - Endpoint: endpoints.GitHub, + config *oauth2.Config } func NewAuthHandlers(repo repositories.AuthRepository) *AuthHandlers { - return &AuthHandlers{repo: repo, logger: logger.New(false)} + clientID := os.Getenv("GITHUB_CLIENT_ID") + clientSecret := os.Getenv("GITHUB_CLIENT_SECRET") + redirectURL := os.Getenv("REDIRECT_URL") + + if clientID == "" || clientSecret == "" { + panic("GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET must be set") + } + if redirectURL == "" { + redirectURL = "http://localhost:8080/api/v1/callback/github" + } + + return &AuthHandlers{ + repo: repo, + logger: logger.New(false), + config: &oauth2.Config{ + ClientID: clientID, + ClientSecret: clientSecret, + RedirectURL: redirectURL, + Scopes: []string{"user:email"}, + Endpoint: endpoints.GitHub, + }, + } } -// Callback godoc -// @Summary Callback for oauth2 providers -// @Description Callback for oauth2 providers +// LoginGithub godoc +// @Summary Start GitHub OAuth login +// @Description Redirects to GitHub authorization // @Tags auth -// @Accept json -// @Produce json -// @Success 200 {object} map[string]string +// @Success 302 +// @Router /api/v1/auth/github [get] +func (h *AuthHandlers) LoginGithub(c *gin.Context) { + url := h.config.AuthCodeURL("state", oauth2.AccessTypeOnline) + h.logger.Info("Redirect to GitHub: " + url) + c.Redirect(302, url) +} + +// CallbackGithub godoc +// @Summary GitHub OAuth callback +// @Description Exchanges authorization code for access token +// @Tags auth +// @Param code query string true "Authorization code" +// @Produce json +// @Success 200 {object} map[string]interface{} "Access token" +// @Failure 400 {object} map[string]string "Missing code" +// @Failure 500 {object} map[string]string "Exchange failed" // @Router /callback/github [get] func (h *AuthHandlers) CallbackGithub(c *gin.Context) { h.logger.Info("CallbackGithub called") - token, err := configGithub.Exchange(c.Request.Context(), c.Query("code")) - if err != nil { - h.logger.Error("error request: " + err.Error()) - c.Status(500) + code := c.Query("code") + if code == "" { + h.logger.Error("missing code") + c.JSON(400, gin.H{"error": "missing code"}) + return } - h.logger.Info("200 OK GET /callback/github") + + h.logger.Info("Processing code: " + code[:10] + "...") + + token, err := h.config.Exchange(c.Request.Context(), code) + if err != nil { + h.logger.Error("Exchange failed: " + err.Error()) + c.JSON(500, gin.H{"error": "exchange failed", "details": err.Error()}) + return + } + + h.logger.Info("200 OK - token received") c.JSON(200, gin.H{"token": token}) } diff --git a/backend/internal/handlers/registry_handlers.go b/backend/internal/handlers/registry_handlers.go index f121cfe..d8e9a16 100644 --- a/backend/internal/handlers/registry_handlers.go +++ b/backend/internal/handlers/registry_handlers.go @@ -11,7 +11,8 @@ func Register(router *gin.Engine, db *sql.DB) { handler_posts := NewPostHandlers(repositories.NewPostRepository(db)) handler_auth := NewAuthHandlers(repositories.NewAuthRepository(db)) v1 := router.Group("api/v1") - v1.GET("/callback", handler_auth.CallbackGithub) + v1.GET("/callback/github", handler_auth.CallbackGithub) + v1.GET("/auth/github", handler_auth.LoginGithub) posts := v1.Group("posts") { posts.GET("/", handler_posts.GetPosts) diff --git a/backend/internal/storage/migrations.go b/backend/internal/storage/migrations.go index 5cdf8da..58f1a9d 100644 --- a/backend/internal/storage/migrations.go +++ b/backend/internal/storage/migrations.go @@ -6,5 +6,13 @@ CREATE TABLE IF NOT EXISTS posts( title TEXT NOT NULL, content TEXT NOT NULL, CREATED_AT DATETIME DEFAULT CURRENT_TIMESTAMP - ); +); + +CREATE TABLE IF NOT EXISTS users( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT, + github_id TEXT, + github_login TEXT, + avatar_url TEXT +); ` diff --git a/backend/internal/storage/models.go b/backend/internal/storage/models.go index 69c8637..99c8de7 100644 --- a/backend/internal/storage/models.go +++ b/backend/internal/storage/models.go @@ -17,3 +17,11 @@ type PostCreate struct { Title string `json:"title"` Content string `json:"content"` } + +type User struct { + ID int `db:"id"` + Email string `db:"email"` + GithubID string `db:"github_id"` + GithubLogin string `db:"github_login"` + AvatarURL string `db:"avatar_url"` +} From 4a5c42ca06f3aa2f88a0536e033e3d8fe798408a Mon Sep 17 00:00:00 2001 From: d3m0k1d Date: Tue, 10 Feb 2026 22:45:27 +0300 Subject: [PATCH 5/8] feat: update callback handler and repository for user --- backend/internal/auth/jwt.go | 6 ++-- backend/internal/handlers/auth_handlers.go | 31 ++++++++++++++++++- .../internal/repositories/auth_repository.go | 21 ++++++++++++- backend/internal/repositories/interface.go | 3 +- backend/internal/storage/migrations.go | 2 +- backend/internal/storage/models.go | 7 +++++ 6 files changed, 63 insertions(+), 7 deletions(-) diff --git a/backend/internal/auth/jwt.go b/backend/internal/auth/jwt.go index 1da5967..e836759 100644 --- a/backend/internal/auth/jwt.go +++ b/backend/internal/auth/jwt.go @@ -14,9 +14,9 @@ var jwtSecret = []byte(os.Getenv("JWT_SECRET")) func GenerateJWT(user storage.User) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.MapClaims{ - "id": user.ID, - "email": user.Email, - "login": user.GithubLogin, + "id": user.ID, + "email": user.Email, + "github_id": user.GithubID, }) tokenString, err := token.SignedString(jwtSecret) if err != nil { diff --git a/backend/internal/handlers/auth_handlers.go b/backend/internal/handlers/auth_handlers.go index b3ef451..31dcf0e 100644 --- a/backend/internal/handlers/auth_handlers.go +++ b/backend/internal/handlers/auth_handlers.go @@ -1,6 +1,7 @@ package handlers import ( + "encoding/json" "os" "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/logger" @@ -81,7 +82,35 @@ func (h *AuthHandlers) CallbackGithub(c *gin.Context) { c.JSON(500, gin.H{"error": "exchange failed", "details": err.Error()}) return } + client := h.config.Client(c.Request.Context(), token) + resp, err := client.Get("https://api.github.com/user") + if err != nil { + h.logger.Error("Get failed: " + err.Error()) + c.JSON(500, gin.H{"error": "get request failed to github", "details": err.Error()}) + return + } + var ghUser struct { + ID int `json:"id"` + Login string `json:"login"` + Email string `json:"email"` + AvatarURL string `json:"avatar_url"` + } + err = json.NewDecoder(resp.Body).Decode(&ghUser) + if err != nil { + h.logger.Error("Decode failed: " + err.Error()) + c.JSON(500, gin.H{"error": "decode failed", "details": err.Error()}) + return + } + isreg, err := h.repo.IsRegistered(c.Request.Context(), ghUser.ID) + if err != nil { + c.JSON(500, gin.H{"error": "database error", "details": err.Error()}) + return + } + if isreg { + c.JSON(200, gin.H{"user": ghUser}) + return + } h.logger.Info("200 OK - token received") - c.JSON(200, gin.H{"token": token}) + c.JSON(200, gin.H{"user": ghUser}) } diff --git a/backend/internal/repositories/auth_repository.go b/backend/internal/repositories/auth_repository.go index d8dbb99..7ef2caf 100644 --- a/backend/internal/repositories/auth_repository.go +++ b/backend/internal/repositories/auth_repository.go @@ -5,6 +5,7 @@ import ( "database/sql" "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/logger" + "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/storage" ) type authRepository struct { @@ -18,5 +19,23 @@ func NewAuthRepository(db *sql.DB) AuthRepository { logger: logger.New(false), } } -func (a *authRepository) Test(ctx context.Context) { + +func (a *authRepository) Register(ctx context.Context, user storage.UserReg) error { + _, err := a.db.Exec( + "INSERT INTO users(email, github_id, github_login, avatar_url) VALUES(?, ?, ?, ?)", + ) + if err != nil { + a.logger.Error("error scan: " + err.Error()) + return err + } + a.logger.Info("User registered:", "email", user.Email) + return nil +} + +func (a *authRepository) IsRegistered(ctx context.Context, github_id int) (bool, error) { + row := a.db.QueryRow("SELECT id FROM users WHERE github_id = ?", github_id) + if row != nil { + return true, nil + } + return false, nil } diff --git a/backend/internal/repositories/interface.go b/backend/internal/repositories/interface.go index ccd80e9..1c1cbcc 100644 --- a/backend/internal/repositories/interface.go +++ b/backend/internal/repositories/interface.go @@ -15,5 +15,6 @@ type PostRepository interface { } type AuthRepository interface { - Test(ctx context.Context) + Register(ctx context.Context, user storage.UserReg) error + IsRegistered(ctx context.Context, github_id int) (bool, error) } diff --git a/backend/internal/storage/migrations.go b/backend/internal/storage/migrations.go index 58f1a9d..50cd1c3 100644 --- a/backend/internal/storage/migrations.go +++ b/backend/internal/storage/migrations.go @@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS posts( CREATE TABLE IF NOT EXISTS users( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT, - github_id TEXT, + github_id INTEGER, github_login TEXT, avatar_url TEXT ); diff --git a/backend/internal/storage/models.go b/backend/internal/storage/models.go index 99c8de7..6a60251 100644 --- a/backend/internal/storage/models.go +++ b/backend/internal/storage/models.go @@ -25,3 +25,10 @@ type User struct { GithubLogin string `db:"github_login"` AvatarURL string `db:"avatar_url"` } + +type UserReg struct { + Email string `db:"email"` + GithubID string `db:"github_id"` + GithubLogin string `db:"github_login"` + AvatarURL string `db:"avatar_url"` +} From 0804e4e80a44d509c12cc66b6d30596a294e91db Mon Sep 17 00:00:00 2001 From: d3m0k1d Date: Wed, 11 Feb 2026 15:12:52 +0300 Subject: [PATCH 6/8] feat: update models added json links --- backend/internal/handlers/auth_handlers.go | 23 ++++++++------ backend/internal/storage/models.go | 36 +++++++++++----------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/backend/internal/handlers/auth_handlers.go b/backend/internal/handlers/auth_handlers.go index 31dcf0e..37d2dd2 100644 --- a/backend/internal/handlers/auth_handlers.go +++ b/backend/internal/handlers/auth_handlers.go @@ -6,6 +6,7 @@ import ( "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/logger" "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/repositories" + "gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/storage" "github.com/gin-gonic/gin" "golang.org/x/oauth2" "golang.org/x/oauth2/endpoints" @@ -89,28 +90,30 @@ func (h *AuthHandlers) CallbackGithub(c *gin.Context) { c.JSON(500, gin.H{"error": "get request failed to github", "details": err.Error()}) return } - var ghUser struct { - ID int `json:"id"` - Login string `json:"login"` - Email string `json:"email"` - AvatarURL string `json:"avatar_url"` - } + var ghUser storage.UserReg err = json.NewDecoder(resp.Body).Decode(&ghUser) if err != nil { h.logger.Error("Decode failed: " + err.Error()) c.JSON(500, gin.H{"error": "decode failed", "details": err.Error()}) return } - isreg, err := h.repo.IsRegistered(c.Request.Context(), ghUser.ID) + isreg, err := h.repo.IsRegistered(c.Request.Context(), ghUser.GithubID) if err != nil { c.JSON(500, gin.H{"error": "database error", "details": err.Error()}) return } if isreg { c.JSON(200, gin.H{"user": ghUser}) - return + + reg := h.repo.Register(c.Request.Context(), ghUser) + if reg != nil { + c.JSON(500, gin.H{"error": "database error", "details": reg.Error()}) + err := h.repo.Register(c.Request.Context(), ghUser) + if err != nil { + c.JSON(500, gin.H{"error": "database error", "details": err.Error()}) + h.logger.Error("Database eer in gh callback handler") + } + } } - h.logger.Info("200 OK - token received") - c.JSON(200, gin.H{"user": ghUser}) } diff --git a/backend/internal/storage/models.go b/backend/internal/storage/models.go index 6a60251..c9503b4 100644 --- a/backend/internal/storage/models.go +++ b/backend/internal/storage/models.go @@ -1,34 +1,34 @@ package storage type Post struct { - ID int `db:"id"` - Title string `db:"title"` - Content string `db:"content"` - CreatedAt string `db:"created_at"` + ID int `db:"id" json:"id"` + Title string `db:"title" json:"title"` + Content string `db:"content" json:"content"` + CreatedAt string `db:"created_at" json:"created_at"` } type PostReq struct { - ID int `json:"id"` - Title string `json:"title"` - Content string `json:"content"` + ID int `db:"id" json:"id"` + Title string `db:"title" json:"title"` + Content string `db:"content" json:"content"` } type PostCreate struct { - Title string `json:"title"` - Content string `json:"content"` + Title string `db:"title" json:"title"` + Content string `db:"content" json:"content"` } type User struct { - ID int `db:"id"` - Email string `db:"email"` - GithubID string `db:"github_id"` - GithubLogin string `db:"github_login"` - AvatarURL string `db:"avatar_url"` + ID int `db:"id" json:"id"` + Email string `db:"email" json:"email"` + GithubID int `db:"github_id" json:"github_id"` + GithubLogin string `db:"github_login" json:"github_login"` + AvatarURL string `db:"avatar_url" json:"avatar_url"` } type UserReg struct { - Email string `db:"email"` - GithubID string `db:"github_id"` - GithubLogin string `db:"github_login"` - AvatarURL string `db:"avatar_url"` + Email string `db:"email" json:"email"` + GithubID int `db:"github_id" json:"github_id"` + GithubLogin string `db:"github_login" json:"github_login"` + AvatarURL string `db:"avatar_url" json:"avatar_url"` } From 54436e9cbaa67c243cae671fcc36324caf5fc801 Mon Sep 17 00:00:00 2001 From: d3m0k1d Date: Wed, 11 Feb 2026 15:31:52 +0300 Subject: [PATCH 7/8] feat: add health hander and healtchek to docker image and update cd pipline for backend --- .gitea/workflows/cd-back.yml | 13 +++++++++++-- backend/Dockerfile | 2 +- backend/internal/handlers/registry_handlers.go | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/cd-back.yml b/.gitea/workflows/cd-back.yml index cd8fa68..9b607d3 100644 --- a/.gitea/workflows/cd-back.yml +++ b/.gitea/workflows/cd-back.yml @@ -23,7 +23,10 @@ jobs: secrets/site/prod/data/server SSH_KEY | SSH_KEY ; secrets/site/prod/data/server USER | SERVER_USER ; secrets/site/prod/data/server HOST | SERVER_HOST ; - secrets/site/prod/data/server PORT | SERVER_PORT + secrets/site/prod/data/server PORT | SERVER_PORT ; + secrets/site/prod/data/auth GITHUB_CLIENT_ID | GITHUB_CLIENT_ID ; + secrets/site/prod/data/auth GITHUB_CLIENT_SECRET | GITHUB_CLIENT_SECRET ; + secrets/site/prod/data/auth JWT_SECRET | JWT_SECRET - name: Login to registry run: echo "${{ steps.import-secrets.outputs.GITEA_TOKEN }}" | docker login gitea.d3m0k1d.ru -u d3m0k1d --password-stdin @@ -44,4 +47,10 @@ jobs: docker login -u d3m0k1d -p ${{ steps.import-secrets.outputs.GITEA_TOKEN }} gitea.d3m0k1d.ru docker pull gitea.d3m0k1d.ru/d3m0k1d/backend:latest docker rm -f d3m0k1d-backend || true - docker run --name d3m0k1d-backend -d -p 8080:8080 --restart unless-stopped gitea.d3m0k1d.ru/d3m0k1d/backend:latest + docker run --name d3m0k1d-backend -d -p 8080:8080 \ + -e JWT_SECRET="${{ steps.import-secrets.outputs.JWT_SECRET }}" \ + -e GITHUB_CLIENT_ID="${{ steps.import-secrets.outputs.GITHUB_CLIENT_ID }}" \ + -e GITHUB_CLIENT_SECRET="${{ steps.import-secrets.outputs.GITHUB_CLIENT_SECRET }}" \ + -e REDIRECT_URL="https://d3m0k1d.ru/api/v1/callback/github" \ + --restart unless-stopped \ + gitea.d3m0k1d.ru/d3m0k1d/backend:latest diff --git a/backend/Dockerfile b/backend/Dockerfile index f9c0a02..8f70254 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -15,5 +15,5 @@ FROM alpine:3.23.0 COPY --from=builder /app/backend . EXPOSE 8080 - +HEALTHCHECK CMD curl --fail http://localhost:8080/health || exit 1 CMD ["./backend"] diff --git a/backend/internal/handlers/registry_handlers.go b/backend/internal/handlers/registry_handlers.go index d8e9a16..2109afb 100644 --- a/backend/internal/handlers/registry_handlers.go +++ b/backend/internal/handlers/registry_handlers.go @@ -10,6 +10,7 @@ import ( func Register(router *gin.Engine, db *sql.DB) { handler_posts := NewPostHandlers(repositories.NewPostRepository(db)) handler_auth := NewAuthHandlers(repositories.NewAuthRepository(db)) + router.GET("/health", func(c *gin.Context) { c.Status(200) }) v1 := router.Group("api/v1") v1.GET("/callback/github", handler_auth.CallbackGithub) v1.GET("/auth/github", handler_auth.LoginGithub) From 045d329a1e2d1a03415ca13d143db4ac60b748b9 Mon Sep 17 00:00:00 2001 From: d3m0k1d Date: Wed, 11 Feb 2026 16:10:00 +0300 Subject: [PATCH 8/8] feat: Add hashiCorp vault to skills page --- frontend/src/components/Skills.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Skills.tsx b/frontend/src/components/Skills.tsx index 7e0fccb..cfc45d6 100644 --- a/frontend/src/components/Skills.tsx +++ b/frontend/src/components/Skills.tsx @@ -75,7 +75,7 @@ export default function About() {

Docker, k8s, LXC, Proxmox, Git, CI/CD (GitHub Actions, GitLab), - Ansible, nginx + Ansible, nginx, HashiCorp Vault