first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:43:40 +03:00
commit f34e54c5a5
100 changed files with 27342 additions and 0 deletions

View File

@@ -0,0 +1,297 @@
package handlers
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"gobeyhan/app/account/services"
settingsServices "gobeyhan/app/settings/services"
"gobeyhan/config"
"gobeyhan/database/models"
"github.com/gin-gonic/gin"
"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
"golang.org/x/oauth2/google"
)
type OAuthHandler struct {
userService *services.UserService
socialAccountService *services.SocialAccountService
jwtService *settingsServices.JWTService
googleOAuthConfig *oauth2.Config
githubOAuthConfig *oauth2.Config
}
func NewOAuthHandler(
userService *services.UserService,
socialAccountService *services.SocialAccountService,
jwtService *settingsServices.JWTService,
) *OAuthHandler {
// Google OAuth config
googleConfig := &oauth2.Config{
ClientID: config.AppConfig.GoogleClientID,
ClientSecret: config.AppConfig.GoogleClientSecret,
RedirectURL: config.AppConfig.GoogleRedirectURL,
Scopes: []string{
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
},
Endpoint: google.Endpoint,
}
// GitHub OAuth config
githubConfig := &oauth2.Config{
ClientID: config.AppConfig.GithubClientID,
ClientSecret: config.AppConfig.GithubClientSecret,
RedirectURL: config.AppConfig.GithubRedirectURL,
Scopes: []string{"user:email"},
Endpoint: github.Endpoint,
}
return &OAuthHandler{
userService: userService,
socialAccountService: socialAccountService,
jwtService: jwtService,
googleOAuthConfig: googleConfig,
githubOAuthConfig: githubConfig,
}
}
// GoogleLogin godoc
// @Summary Google OAuth login
// @Description Redirect to Google OAuth
// @Tags auth,oauth
// @Produce json
// @Router /api/v1/auth/google [get]
func (h *OAuthHandler) GoogleLogin(c *gin.Context) {
url := h.googleOAuthConfig.AuthCodeURL("state", oauth2.AccessTypeOffline)
c.Redirect(http.StatusTemporaryRedirect, url)
}
// GoogleCallback godoc
// @Summary Google OAuth callback
// @Description Handle Google OAuth callback
// @Tags auth,oauth
// @Produce json
// @Param code query string true "Authorization code"
// @Success 200 {object} object{token=string,user=models.User}
// @Failure 400 {object} object{error=string}
// @Router /api/v1/auth/google/callback [get]
func (h *OAuthHandler) GoogleCallback(c *gin.Context) {
code := c.Query("code")
if code == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Code not found"})
return
}
// Exchange code for token
token, err := h.googleOAuthConfig.Exchange(context.Background(), code)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to exchange token"})
return
}
// Get user info from Google
client := h.googleOAuthConfig.Client(context.Background(), token)
resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get user info"})
return
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
var googleUser struct {
ID string `json:"id"`
Email string `json:"email"`
VerifiedEmail bool `json:"verified_email"`
Name string `json:"name"`
Picture string `json:"picture"`
}
json.Unmarshal(data, &googleUser)
// Find or create user
user, accessToken, refreshToken, err := h.findOrCreateOAuthUser(
googleUser.Email,
googleUser.Name,
"google",
googleUser.ID,
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"token": accessToken,
"refresh_token": refreshToken,
"user": user,
})
}
// GithubLogin godoc
// @Summary GitHub OAuth login
// @Description Redirect to GitHub OAuth
// @Tags auth,oauth
// @Produce json
// @Router /api/v1/auth/github [get]
func (h *OAuthHandler) GithubLogin(c *gin.Context) {
url := h.githubOAuthConfig.AuthCodeURL("state", oauth2.AccessTypeOffline)
c.Redirect(http.StatusTemporaryRedirect, url)
}
// GithubCallback godoc
// @Summary GitHub OAuth callback
// @Description Handle GitHub OAuth callback
// @Tags auth,oauth
// @Produce json
// @Param code query string true "Authorization code"
// @Success 200 {object} object{token=string,user=models.User}
// @Failure 400 {object} object{error=string}
// @Router /api/v1/auth/github/callback [get]
func (h *OAuthHandler) GithubCallback(c *gin.Context) {
code := c.Query("code")
if code == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Code not found"})
return
}
// Exchange code for token
token, err := h.githubOAuthConfig.Exchange(context.Background(), code)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to exchange token"})
return
}
// Get user info from GitHub
client := h.githubOAuthConfig.Client(context.Background(), token)
resp, err := client.Get("https://api.github.com/user")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get user info"})
return
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
var githubUser struct {
ID int `json:"id"`
Login string `json:"login"`
Email string `json:"email"`
Name string `json:"name"`
}
json.Unmarshal(data, &githubUser)
// If email is not public, fetch it separately
if githubUser.Email == "" {
emailResp, _ := client.Get("https://api.github.com/user/emails")
if emailResp != nil {
defer emailResp.Body.Close()
emailData, _ := io.ReadAll(emailResp.Body)
var emails []struct {
Email string `json:"email"`
Primary bool `json:"primary"`
Verified bool `json:"verified"`
}
json.Unmarshal(emailData, &emails)
for _, e := range emails {
if e.Primary && e.Verified {
githubUser.Email = e.Email
break
}
}
}
}
username := githubUser.Name
if username == "" {
username = githubUser.Login
}
// Find or create user
user, accessToken, refreshToken, err := h.findOrCreateOAuthUser(
githubUser.Email,
username,
"github",
strconv.Itoa(githubUser.ID),
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"token": accessToken,
"refresh_token": refreshToken,
"user": user,
})
}
// findOrCreateOAuthUser finds existing user or creates new one for OAuth
func (h *OAuthHandler) findOrCreateOAuthUser(
email, username, provider, providerUserID string,
) (*models.User, string, string, error) {
// Try to find existing user by email
user, err := h.userService.GetUserByEmail(email)
if err != nil {
return nil, "", "", err
}
// If user doesn't exist, create new one
if user == nil {
user = &models.User{
Email: email,
UserName: username,
}
// Create user with empty password
if err := h.userService.CreateUser(user, ""); err != nil {
return nil, "", "", fmt.Errorf("failed to create user: %w", err)
}
// Assign default role
if err := h.userService.AssignDefaultRole(user.ID); err != nil {
// Log error but continue
// fmt.Printf("Failed to assign default role: %v\n", err)
}
}
// Check if social account exists
accounts, err := h.socialAccountService.GetSocialAccountsByUser(user.ID)
if err != nil {
return nil, "", "", err
}
// Create social account if it doesn't exist
found := false
for _, acc := range accounts {
if acc.Provider == provider && acc.ProviderID == providerUserID {
found = true
break
}
}
if !found {
socialAccount := &models.SocialAccount{
UserID: user.ID,
Provider: provider,
ProviderID: providerUserID,
}
if err := h.socialAccountService.CreateSocialAccount(socialAccount); err != nil {
return nil, "", "", fmt.Errorf("failed to create social account: %w", err)
}
}
// Generate JWT tokens
accessToken, refreshToken, err := h.jwtService.GenerateTokenPair(*user)
if err != nil {
return nil, "", "", fmt.Errorf("failed to generate tokens: %w", err)
}
// Clear password before returning
user.Password = ""
return user, accessToken, refreshToken, nil
}