package admin import ( "net/http" "time" "github.com/gin-gonic/gin" "gorm.io/gorm" "github.com/llamalink/llamalink/internal/auth" "github.com/llamalink/llamalink/internal/db" ) type LoginHandler struct { db *gorm.DB jwtService *auth.AdminJWTService } func NewLoginHandler(db *gorm.DB, jwtService *auth.AdminJWTService) *LoginHandler { return &LoginHandler{db: db, jwtService: jwtService} } type LoginRequest struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required,min=8"` } func (h *LoginHandler) Login(c *gin.Context) { var req LoginRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{"code": "validation_error", "message": "username and password are required (password min 8 chars)"}}) return } var user db.AdminUser if err := h.db.Where("username = ?", req.Username).First(&user).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusUnauthorized, gin.H{"error": gin.H{"code": "invalid_credentials", "message": "Invalid username or password"}}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{"code": "internal_error", "message": "Internal server error"}}) return } if err := auth.ValidateAdminPassword(&user, req.Password); err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": gin.H{"code": "invalid_credentials", "message": "Invalid username or password"}}) return } token, expiresAt, err := h.jwtService.IssueToken(&user) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{"code": "internal_error", "message": "Failed to issue session token"}}) return } h.db.Model(&user).Update("last_login_at", time.Now()) c.JSON(http.StatusOK, gin.H{ "token": token, "expires_at": expiresAt.Unix(), "user": gin.H{ "id": user.ID, "username": user.Username, "is_active": user.IsActive, }, }) }