package handlers import ( "time" "git.linuxforward.com/byom/byom-core/common" "git.linuxforward.com/byom/byom-core/hook" "git.linuxforward.com/byom/byom-core/jwtutils" "git.linuxforward.com/byom/byom-core/logger" "git.linuxforward.com/byom/byom-core/smtp" "git.linuxforward.com/byom/byom-core/store" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" "github.com/sirupsen/logrus" "golang.org/x/crypto/bcrypt" ) type UserHandler struct { store *store.DataStore emailService smtp.EmailService jwtService *jwtutils.Service logger *logrus.Entry hook *hook.HookClient } func NewUserHandler(db *store.DataStore, emailSvc smtp.EmailService, jwtSvc *jwtutils.Service, hook *hook.HookClient) *UserHandler { return &UserHandler{ store: db, emailService: emailSvc, jwtService: jwtSvc, logger: logger.NewLogger("user-handler"), hook: hook, } } // Auth func (h *UserHandler) Login(c *gin.Context) { var req common.AuthLoginRequest var resp common.AuthLoginResponse if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": "Invalid request"}) return } user, err := h.store.GetUserByEmail(c, req.Email) if err != nil { c.JSON(400, gin.H{"error": "Invalid email or password"}) return } err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)) if err != nil { c.JSON(400, gin.H{"error": "Invalid email or password"}) return } token, err := h.jwtService.GenerateToken( user.Email, user.Role, ) if err != nil { c.JSON(500, gin.H{"error": "Failed to generate token"}) return } resp.Token = token c.JSON(200, resp) } // Workspace Owner Management func (h *UserHandler) InitOwner(c *gin.Context) { var req common.WorkspaceOwnerInitRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": "Invalid request"}) return } user := &common.User{ ID: uuid.New(), Email: req.Email, Name: req.Name, Role: "owner", Status: "pending", CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := h.store.CreateUser(c, user); err != nil { c.JSON(500, gin.H{"error": "Failed to create user"}) return } c.JSON(201, user) } func (h *UserHandler) CreateOwner(c *gin.Context) { var req common.WorkspaceOwnerCreateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": "Invalid request"}) return } //check if the received email is already the one from the owner tmpOwner, err := h.store.GetUserByEmail(c, req.Email) if err != nil { c.JSON(500, gin.H{"error": "Failed to get user"}) return } if tmpOwner.Email != req.Email { c.JSON(400, gin.H{"error": "Email does not match the owner"}) return } hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) user := &common.User{ ID: tmpOwner.ID, Email: tmpOwner.Email, Name: tmpOwner.Name, PhoneNumber: tmpOwner.PhoneNumber, Password: string(hashedPassword), PasswordCreated: true, Role: tmpOwner.Role, Status: "active", CreatedAt: tmpOwner.CreatedAt, UpdatedAt: time.Now(), } if err := h.store.UpdateUser(c, user); err != nil { c.JSON(500, gin.H{"error": "Failed to create user"}) return } user.Password = "" c.JSON(201, user) } // Invitation Management func (h *UserHandler) AcceptInvitation(c *gin.Context) { var req common.InvitationAcceptRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": "Invalid request"}) return } invite, err := h.store.GetInvite(c, req.Token) if err != nil { c.JSON(400, gin.H{"error": "Invalid invitation"}) return } tx := h.store.BeginTx() hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) user := &common.User{ ID: uuid.New(), Email: req.Email, Name: req.Name, PhoneNumber: req.PhoneNumber, Password: string(hashedPassword), PasswordCreated: true, Role: invite.Role, Status: "active", CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := h.store.CreateUserTx(c, tx, user); err != nil { tx.Rollback() c.JSON(500, gin.H{"error": "Failed to create user"}) return } //add user to workspace if err := h.store.AddUserToWorkspaceTx(c, tx, invite.Role, user.ID, invite.WorkspaceID); err != nil { tx.Rollback() c.JSON(500, gin.H{"error": "Failed to add user to workspace"}) return } //update invite status invite.Status = "accepted" if err := h.store.UpdateInviteStatusTx(c, tx, invite); err != nil { tx.Rollback() c.JSON(500, gin.H{"error": "Failed to update invitation"}) return } if err := tx.Commit().Error; err != nil { c.JSON(500, gin.H{"error": "Failed to complete registration"}) return } user.Password = "" resp := common.InvitationAcceptResponse{ User: *user, WorkspaceID: invite.WorkspaceID, } c.JSON(201, resp) } func (h *UserHandler) ValidateInvitation(c *gin.Context) { //request token from query token := c.Query("token") h.logger.WithField("token", token).Info("Validating invitation") inv, err := h.store.GetInvite(c, token) if err != nil { c.JSON(400, gin.H{"error": "Invalid invitation"}) return } //validate token _, err = h.jwtService.ValidateToken(token) if err != nil { if err == jwt.ErrTokenExpired { //update invite status inv.Status = "expired" if err := h.store.UpdateInvite(c, inv); err != nil { c.JSON(500, gin.H{"error": "Failed to update invitation"}) return } c.JSON(400, gin.H{"error": "Invalid token"}) return } } response := common.InvitationValidateResponse{ Valid: true, WorkspaceID: inv.WorkspaceID, Email: inv.Email, } c.JSON(200, response) } func (h *UserHandler) CreateInvitation(c *gin.Context) { var req common.InvitationCreateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": "Invalid request"}) return } //check if user exists _, err := h.store.GetUserByEmail(c, req.Email) if err == nil { c.JSON(400, gin.H{"error": "User already exists"}) return } //generate jwt token token, err := h.jwtService.GenerateToken( req.Email, req.Role, ) if err != nil { c.JSON(500, gin.H{"error": "Failed to generate token"}) return } //store token in db invite := &common.Invite{ ID: uuid.New(), Email: req.Email, Role: req.Role, WorkspaceID: req.WorkspaceID, Token: token, Status: "pending", ExpiresAt: time.Now().Add(24 * time.Hour), CreatedAt: time.Now(), UpdatedAt: time.Now(), } //return invite if err := h.store.CreateInvite(c, invite); err != nil { c.JSON(500, gin.H{"error": "Failed to create invitation"}) return } //send email with token if err := h.emailService.SendInviteEmail(req.Email, token, req.WorkspaceID.String()); err != nil { c.JSON(500, gin.H{"error": "Failed to send email"}) return } c.JSON(201, invite) } // User Management func (h *UserHandler) GetCurrent(c *gin.Context) { // Get claims from context that were set by middleware claimsValue, exists := c.Get("claims") if !exists { c.JSON(500, gin.H{"error": "Failed to get user claims"}) return } claims := claimsValue.(jwt.MapClaims) if claims == nil { c.JSON(500, gin.H{"error": "Failed to get user claims"}) return } email := h.jwtService.ExtractEmailFromClaims(claims) if email == "" { c.JSON(500, gin.H{"error": "Failed to get user email"}) return } user, err := h.store.GetUserByEmail(c, email) if err != nil { c.JSON(500, gin.H{"error": "Failed to get user"}) return } workspaces, err := h.store.GetWorkspacesByUserID(c, user.ID) if err != nil { c.JSON(500, gin.H{"error": "Failed to get user workspace role"}) return } resp := common.UserResponse{ User: *user, Workspaces: workspaces, } c.JSON(200, resp) } func (h *UserHandler) UpdateCurrent(c *gin.Context) { var req common.UserUpdateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": "Invalid request"}) return } // Implementation } // Workspace Member Management func (h *UserHandler) AddMember(c *gin.Context) { var req common.WorkspaceMemberAddRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": "Invalid request"}) return } //retrieve user from token claimsValue, exists := c.Get("claims") if !exists { c.JSON(500, gin.H{"error": "Failed to get user claims"}) return } claims := claimsValue.(jwt.MapClaims) if claims == nil { c.JSON(500, gin.H{"error": "Failed to get user claims"}) return } email := h.jwtService.ExtractEmailFromClaims(claims) if email == "" { c.JSON(500, gin.H{"error": "Failed to get user email"}) return } user, err := h.store.GetUserByEmail(c, email) if err != nil { c.JSON(500, gin.H{"error": "Failed to get user"}) return } //add user to workspace if err := h.store.AddUserToWorkspace(c, user.ID, req.WorkspaceID, req.Role); err != nil { c.JSON(500, gin.H{"error": "Failed to add user to workspace"}) return } c.JSON(200, gin.H{"message": "User added to workspace"}) } func (h *UserHandler) CancelInvitation(c *gin.Context) { // Implementation }