feat: implement chat history, theme customization, and password management
- Added chat history persistence for active rooms with auto-cleanup on stream end. - Overhauled Settings page with user profile, theme color picker, and password change. - Added backend API for user password updates. - Integrated flutter_launcher_icons and updated app icon to 'H' logo. - Fixed 'Duplicate keys' bug in danmaku by using UniqueKey and filtering historical messages. - Updated version to 1.0.0-beta3.5 and author info.
This commit is contained in:
@@ -20,6 +20,11 @@ type LoginRequest struct {
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type ChangePasswordRequest struct {
|
||||
OldPassword string `json:"old_password" binding:"required"`
|
||||
NewPassword string `json:"new_password" binding:"required"`
|
||||
}
|
||||
|
||||
func Register(c *gin.Context) {
|
||||
var req RegisterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
@@ -95,3 +100,40 @@ func Login(c *gin.Context) {
|
||||
"username": user.Username,
|
||||
})
|
||||
}
|
||||
|
||||
func ChangePassword(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
|
||||
var req ChangePasswordRequest
|
||||
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.First(&user, userID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// Verify old password
|
||||
if !utils.CheckPasswordHash(req.OldPassword, user.Password) {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid old password"})
|
||||
return
|
||||
}
|
||||
|
||||
// Hash new password
|
||||
hashedPassword, err := utils.HashPassword(req.NewPassword)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
|
||||
return
|
||||
}
|
||||
|
||||
// Update user
|
||||
if err := db.DB.Model(&user).Update("password", hashedPassword).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update password"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Password updated successfully"})
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ func SetupRouter() *gin.Engine {
|
||||
authGroup.Use(AuthMiddleware())
|
||||
{
|
||||
authGroup.GET("/room/my", GetMyRoom)
|
||||
authGroup.POST("/user/change-password", ChangePassword)
|
||||
}
|
||||
|
||||
return r
|
||||
|
||||
@@ -16,10 +16,11 @@ const (
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Type string `json:"type"` // "chat", "system", "danmaku"
|
||||
Username string `json:"username"`
|
||||
Content string `json:"content"`
|
||||
RoomID string `json:"room_id"`
|
||||
Type string `json:"type"` // "chat", "system", "danmaku"
|
||||
Username string `json:"username"`
|
||||
Content string `json:"content"`
|
||||
RoomID string `json:"room_id"`
|
||||
IsHistory bool `json:"is_history"`
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
@@ -31,19 +32,21 @@ type Client struct {
|
||||
}
|
||||
|
||||
type Hub struct {
|
||||
rooms map[string]map[*Client]bool
|
||||
broadcast chan Message
|
||||
register chan *Client
|
||||
unregister chan *Client
|
||||
mutex sync.RWMutex
|
||||
rooms map[string]map[*Client]bool
|
||||
roomsHistory map[string][]Message
|
||||
broadcast chan Message
|
||||
register chan *Client
|
||||
unregister chan *Client
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewHub() *Hub {
|
||||
return &Hub{
|
||||
broadcast: make(chan Message),
|
||||
register: make(chan *Client),
|
||||
unregister: make(chan *Client),
|
||||
rooms: make(map[string]map[*Client]bool),
|
||||
broadcast: make(chan Message),
|
||||
register: make(chan *Client),
|
||||
unregister: make(chan *Client),
|
||||
rooms: make(map[string]map[*Client]bool),
|
||||
roomsHistory: make(map[string][]Message),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +59,20 @@ func (h *Hub) Run() {
|
||||
h.rooms[client.RoomID] = make(map[*Client]bool)
|
||||
}
|
||||
h.rooms[client.RoomID][client] = true
|
||||
|
||||
// Send existing history to the newly joined client
|
||||
if history, ok := h.roomsHistory[client.RoomID]; ok {
|
||||
for _, msg := range history {
|
||||
msg.IsHistory = true
|
||||
msgBytes, _ := json.Marshal(msg)
|
||||
// Use select to avoid blocking if client's send channel is full
|
||||
select {
|
||||
case client.Send <- msgBytes:
|
||||
default:
|
||||
// If send fails, we could potentially log or ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
h.mutex.Unlock()
|
||||
|
||||
case client := <-h.unregister:
|
||||
@@ -64,6 +81,10 @@ func (h *Hub) Run() {
|
||||
if _, ok := rooms[client]; ok {
|
||||
delete(rooms, client)
|
||||
close(client.Send)
|
||||
// We no longer delete the room from h.rooms here if we want history to persist
|
||||
// even if everyone leaves (as long as it's active in DB).
|
||||
// But we should clean up if the room is empty and we want to save memory.
|
||||
// However, the history is what matters.
|
||||
if len(rooms) == 0 {
|
||||
delete(h.rooms, client.RoomID)
|
||||
}
|
||||
@@ -72,7 +93,16 @@ func (h *Hub) Run() {
|
||||
h.mutex.Unlock()
|
||||
|
||||
case message := <-h.broadcast:
|
||||
h.mutex.RLock()
|
||||
h.mutex.Lock()
|
||||
// Only store "chat" and "danmaku" messages in history
|
||||
if message.Type == "chat" || message.Type == "danmaku" {
|
||||
h.roomsHistory[message.RoomID] = append(h.roomsHistory[message.RoomID], message)
|
||||
// Limit history size to avoid memory leak (e.g., last 100 messages)
|
||||
if len(h.roomsHistory[message.RoomID]) > 100 {
|
||||
h.roomsHistory[message.RoomID] = h.roomsHistory[message.RoomID][1:]
|
||||
}
|
||||
}
|
||||
|
||||
clients := h.rooms[message.RoomID]
|
||||
if clients != nil {
|
||||
msgBytes, _ := json.Marshal(message)
|
||||
@@ -85,11 +115,18 @@ func (h *Hub) Run() {
|
||||
}
|
||||
}
|
||||
}
|
||||
h.mutex.RUnlock()
|
||||
h.mutex.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ClearRoomHistory removes history for a room, should be called when stream ends
|
||||
func (h *Hub) ClearRoomHistory(roomID string) {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
delete(h.roomsHistory, roomID)
|
||||
}
|
||||
|
||||
func (h *Hub) RegisterClient(c *Client) {
|
||||
h.register <- c
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/nareix/joy4/format"
|
||||
"github.com/nareix/joy4/format/rtmp"
|
||||
|
||||
"hightube/internal/chat"
|
||||
"hightube/internal/db"
|
||||
"hightube/internal/model"
|
||||
)
|
||||
@@ -83,6 +84,10 @@ func NewRTMPServer() *RTMPServer {
|
||||
q.Close()
|
||||
// Explicitly set is_active to false using map
|
||||
db.DB.Model(&room).Updates(map[string]interface{}{"is_active": false})
|
||||
|
||||
// Clear chat history for this room
|
||||
chat.MainHub.ClearRoomHistory(fmt.Sprintf("%d", room.ID))
|
||||
|
||||
fmt.Printf("[INFO] Publishing ended for Room ID: %d\n", room.ID)
|
||||
}()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user