Phase 2 completed: Stream Server Auth, Database and Business API
This commit is contained in:
96
internal/api/auth.go
Normal file
96
internal/api/auth.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"hightube/internal/db"
|
||||
"hightube/internal/model"
|
||||
"hightube/internal/utils"
|
||||
)
|
||||
|
||||
type RegisterRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
func Register(c *gin.Context) {
|
||||
var req RegisterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user exists
|
||||
var existingUser model.User
|
||||
if err := db.DB.Where("username = ?", req.Username).First(&existingUser).Error; err == nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "Username already exists"})
|
||||
return
|
||||
}
|
||||
|
||||
// Hash password
|
||||
hashedPassword, err := utils.HashPassword(req.Password)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
|
||||
return
|
||||
}
|
||||
|
||||
// Create user
|
||||
user := model.User{
|
||||
Username: req.Username,
|
||||
Password: hashedPassword,
|
||||
}
|
||||
if err := db.DB.Create(&user).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user"})
|
||||
return
|
||||
}
|
||||
|
||||
// Create a default live room for the new user
|
||||
room := model.Room{
|
||||
UserID: user.ID,
|
||||
Title: user.Username + "'s Live Room",
|
||||
StreamKey: utils.GenerateStreamKey(),
|
||||
IsActive: false,
|
||||
}
|
||||
if err := db.DB.Create(&room).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create room for user"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"message": "User registered successfully", "user_id": user.ID})
|
||||
}
|
||||
|
||||
func Login(c *gin.Context) {
|
||||
var req LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var user model.User
|
||||
if err := db.DB.Where("username = ?", req.Username).First(&user).Error; err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid username or password"})
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.CheckPasswordHash(req.Password, user.Password) {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid username or password"})
|
||||
return
|
||||
}
|
||||
|
||||
token, err := utils.GenerateToken(user.ID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"token": token,
|
||||
})
|
||||
}
|
||||
42
internal/api/middleware.go
Normal file
42
internal/api/middleware.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"hightube/internal/utils"
|
||||
)
|
||||
|
||||
// AuthMiddleware intercepts requests, validates JWT, and injects user_id into context
|
||||
func AuthMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header is required"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.Split(authHeader, " ")
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header format must be Bearer {token}"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
tokenStr := parts[1]
|
||||
userIDStr, err := utils.ParseToken(tokenStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired token"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
userID, _ := strconv.ParseUint(userIDStr, 10, 32)
|
||||
c.Set("user_id", uint(userID))
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
50
internal/api/room.go
Normal file
50
internal/api/room.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"hightube/internal/db"
|
||||
"hightube/internal/model"
|
||||
)
|
||||
|
||||
// GetMyRoom returns the room details for the currently authenticated user
|
||||
func GetMyRoom(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
|
||||
var room model.Room
|
||||
if err := db.DB.Where("user_id = ?", userID).First(&room).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Room not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"room_id": room.ID,
|
||||
"title": room.Title,
|
||||
"stream_key": room.StreamKey,
|
||||
"is_active": room.IsActive,
|
||||
})
|
||||
}
|
||||
|
||||
// GetActiveRooms returns a list of all currently active live rooms
|
||||
func GetActiveRooms(c *gin.Context) {
|
||||
var rooms []model.Room
|
||||
// Fetch rooms where is_active is true
|
||||
if err := db.DB.Where("is_active = ?", true).Find(&rooms).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch active rooms"})
|
||||
return
|
||||
}
|
||||
|
||||
// Return safe information (do not leak stream keys)
|
||||
var result []map[string]interface{}
|
||||
for _, r := range rooms {
|
||||
result = append(result, map[string]interface{}{
|
||||
"room_id": r.ID,
|
||||
"title": r.Title,
|
||||
"user_id": r.UserID,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"active_rooms": result})
|
||||
}
|
||||
30
internal/api/router.go
Normal file
30
internal/api/router.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// SetupRouter configures the Gin router and defines API endpoints
|
||||
func SetupRouter() *gin.Engine {
|
||||
// 设置为发布模式,消除 "[WARNING] Running in debug mode" 警告
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
|
||||
r := gin.Default()
|
||||
|
||||
// 清除代理信任警告 "[WARNING] You trusted all proxies"
|
||||
r.SetTrustedProxies(nil)
|
||||
|
||||
// Public routes
|
||||
r.POST("/api/register", Register)
|
||||
r.POST("/api/login", Login)
|
||||
r.GET("/api/rooms/active", GetActiveRooms)
|
||||
|
||||
// Protected routes (require JWT)
|
||||
authGroup := r.Group("/api")
|
||||
authGroup.Use(AuthMiddleware())
|
||||
{
|
||||
authGroup.GET("/room/my", GetMyRoom)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
Reference in New Issue
Block a user