diff --git a/internal/db/room.go b/internal/db/room.go index 9fdb068..7561d3c 100644 --- a/internal/db/room.go +++ b/internal/db/room.go @@ -11,7 +11,7 @@ import ( type CreateRoomConfig func(r *model.Room) -func WithSetting(setting model.Setting) CreateRoomConfig { +func WithSetting(setting model.Settings) CreateRoomConfig { return func(r *model.Room) { r.Settings = setting } @@ -77,7 +77,7 @@ func GetRoomAndCreatorByID(id uint) (*model.Room, error) { return r, err } -func ChangeRoomSetting(roomID uint, setting model.Setting) error { +func ChangeRoomSetting(roomID uint, setting model.Settings) error { err := db.Model(&model.Room{}).Where("id = ?", roomID).Update("setting", setting).Error if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("room not found") @@ -165,13 +165,13 @@ func GetAllRooms(scopes ...func(*gorm.DB) *gorm.DB) []*model.Room { func GetAllRoomsWithoutHidden(scopes ...func(*gorm.DB) *gorm.DB) []*model.Room { rooms := []*model.Room{} - db.Preload("Setting", "hidden = ?", false).Scopes(scopes...).Find(&rooms) + db.Where("settings_hidden = ?", false).Scopes(scopes...).Find(&rooms) return rooms } func GetAllRoomsWithoutHiddenCount(scopes ...func(*gorm.DB) *gorm.DB) int64 { var count int64 - db.Model(&model.Room{}).Preload("Setting", "hidden = ?", false).Scopes(scopes...).Count(&count) + db.Model(&model.Room{}).Where("settings_hidden = ?", false).Scopes(scopes...).Count(&count) return count } diff --git a/internal/model/movie.go b/internal/model/movie.go index 89242ab..973cb64 100644 --- a/internal/model/movie.go +++ b/internal/model/movie.go @@ -1,20 +1,20 @@ package model -import ( - "gorm.io/gorm" -) +import "time" type Movie struct { - gorm.Model - Position uint `gorm:"not null"` - RoomID uint `gorm:"not null;index"` - CreatorID uint `gorm:"not null;index" json:"creatorId"` + ID uint `gorm:"primarykey" json:"id"` + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` + Position uint `gorm:"not null" json:"-"` + RoomID uint `gorm:"not null;index" json:"-"` + CreatorID uint `gorm:"not null;index" json:"creatorId"` MovieInfo } type MovieInfo struct { - BaseMovieInfo - PullKey string `json:"pullKey"` + Base BaseMovieInfo `gorm:"embedded;embeddedPrefix:base_" json:"base"` + PullKey string `json:"pullKey"` } type BaseMovieInfo struct { diff --git a/internal/model/oauth2.go b/internal/model/oauth2.go index ddbac32..8977dbe 100644 --- a/internal/model/oauth2.go +++ b/internal/model/oauth2.go @@ -1,12 +1,15 @@ package model import ( + "time" + "github.com/synctv-org/synctv/internal/provider" - "gorm.io/gorm" ) type UserProvider struct { - gorm.Model + ID uint `gorm:"primarykey"` + CreatedAt time.Time + UpdatedAt time.Time UserID uint `gorm:"not null"` Provider provider.OAuth2Provider `gorm:"not null;uniqueIndex:provider_user_id"` ProviderUserID uint `gorm:"not null;uniqueIndex:provider_user_id"` diff --git a/internal/model/relation.go b/internal/model/relation.go index a2dca43..975e99a 100644 --- a/internal/model/relation.go +++ b/internal/model/relation.go @@ -1,6 +1,6 @@ package model -import "gorm.io/gorm" +import "time" type RoomRole uint32 @@ -38,7 +38,9 @@ func (p Permission) Has(permission Permission) bool { } type RoomUserRelation struct { - gorm.Model + ID uint `gorm:"primarykey"` + CreatedAt time.Time + UpdatedAt time.Time UserID uint `gorm:"not null;uniqueIndex:idx_user_room"` RoomID uint `gorm:"not null;uniqueIndex:idx_user_room"` Role RoomRole `gorm:"not null"` diff --git a/internal/model/room.go b/internal/model/room.go index 58b7d79..fa12c02 100644 --- a/internal/model/room.go +++ b/internal/model/room.go @@ -1,21 +1,28 @@ package model import ( + "time" + "github.com/zijiren233/stream" "golang.org/x/crypto/bcrypt" - "gorm.io/gorm" ) type Room struct { - gorm.Model - Name string `gorm:"not null;uniqueIndex"` - Settings Setting `gorm:"foreignKey:RoomID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` - CreatorID uint `gorm:"index"` + ID uint `gorm:"primarykey"` + CreatedAt time.Time + UpdatedAt time.Time + Name string `gorm:"not null;uniqueIndex"` + Settings Settings `gorm:"embedded;embeddedPrefix:settings_"` + CreatorID uint `gorm:"index"` HashedPassword []byte GroupUserRelations []RoomUserRelation `gorm:"foreignKey:RoomID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` Movies []Movie `gorm:"foreignKey:RoomID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` } +type Settings struct { + Hidden bool +} + func (r *Room) NeedPassword() bool { return len(r.HashedPassword) != 0 } diff --git a/internal/model/setting.go b/internal/model/setting.go deleted file mode 100644 index fe2ee1b..0000000 --- a/internal/model/setting.go +++ /dev/null @@ -1,9 +0,0 @@ -package model - -import "gorm.io/gorm" - -type Setting struct { - gorm.Model - RoomID uint `gorm:"uniqueIndex"` - Hidden bool -} diff --git a/internal/model/user.go b/internal/model/user.go index da87b0b..8558513 100644 --- a/internal/model/user.go +++ b/internal/model/user.go @@ -3,6 +3,7 @@ package model import ( "fmt" "math/rand" + "time" "gorm.io/gorm" ) @@ -17,7 +18,9 @@ const ( ) type User struct { - gorm.Model + ID uint `gorm:"primarykey"` + CreatedAt time.Time + UpdatedAt time.Time Providers []UserProvider `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` Username string `gorm:"not null;uniqueIndex"` Role Role `gorm:"not null"` diff --git a/internal/op/current.go b/internal/op/current.go index 7c15e24..bf161f9 100644 --- a/internal/op/current.go +++ b/internal/op/current.go @@ -90,13 +90,13 @@ func (c *Current) Proto() *pb.Current { Movie: &pb.MovieInfo{ Id: uint64(c.Movie.ID), Base: &pb.BaseMovieInfo{ - Url: c.Movie.BaseMovieInfo.Url, - Name: c.Movie.BaseMovieInfo.Name, - Live: c.Movie.BaseMovieInfo.Live, - Proxy: c.Movie.BaseMovieInfo.Proxy, - RtmpSource: c.Movie.BaseMovieInfo.RtmpSource, - Type: c.Movie.BaseMovieInfo.Type, - Headers: c.Movie.BaseMovieInfo.Headers, + Url: c.Movie.Base.Url, + Name: c.Movie.Base.Name, + Live: c.Movie.Base.Live, + Proxy: c.Movie.Base.Proxy, + RtmpSource: c.Movie.Base.RtmpSource, + Type: c.Movie.Base.Type, + Headers: c.Movie.Base.Headers, }, PullKey: c.Movie.PullKey, CreatedAt: c.Movie.CreatedAt.UnixMilli(), @@ -111,7 +111,7 @@ func (c *Current) Proto() *pb.Current { } func (c *Current) updateSeek() { - if c.Movie.BaseMovieInfo.Live { + if c.Movie.Base.Live { c.Status.lastUpdate = time.Now() return } @@ -130,7 +130,7 @@ func (c *Current) setLiveStatus() Status { } func (c *Current) SetStatus(playing bool, seek, rate, timeDiff float64) Status { - if c.Movie.BaseMovieInfo.Live { + if c.Movie.Base.Live { return c.setLiveStatus() } c.Status.Playing = playing @@ -145,7 +145,7 @@ func (c *Current) SetStatus(playing bool, seek, rate, timeDiff float64) Status { } func (c *Current) SetSeekRate(seek, rate, timeDiff float64) Status { - if c.Movie.BaseMovieInfo.Live { + if c.Movie.Base.Live { return c.setLiveStatus() } if c.Status.Playing { @@ -159,7 +159,7 @@ func (c *Current) SetSeekRate(seek, rate, timeDiff float64) Status { } func (c *Current) SetSeek(seek, timeDiff float64) Status { - if c.Movie.BaseMovieInfo.Live { + if c.Movie.Base.Live { return c.setLiveStatus() } if c.Status.Playing { diff --git a/internal/op/movie.go b/internal/op/movie.go index 5dad55b..0ced439 100644 --- a/internal/op/movie.go +++ b/internal/op/movie.go @@ -32,9 +32,9 @@ func (m *movie) Channel() (*rtmps.Channel, error) { func (m *movie) init() (err error) { switch { - case m.RtmpSource && m.Proxy: + case m.Base.RtmpSource && m.Base.Proxy: return errors.New("rtmp source and proxy can't be true at the same time") - case m.Live && m.RtmpSource: + case m.Base.Live && m.Base.RtmpSource: if !conf.Conf.Rtmp.Enable { return errors.New("rtmp is not enabled") } @@ -45,11 +45,11 @@ func (m *movie) init() (err error) { m.channel = rtmps.NewChannel() m.channel.InitHlsPlayer() } - case m.Live && m.Proxy: + case m.Base.Live && m.Base.Proxy: if !conf.Conf.Proxy.LiveProxy { return errors.New("live proxy is not enabled") } - u, err := url.Parse(m.Url) + u, err := url.Parse(m.Base.Url) if err != nil { return err } @@ -58,7 +58,7 @@ func (m *movie) init() (err error) { } switch u.Scheme { case "rtmp": - m.PullKey = uuid.NewMD5(uuid.NameSpaceURL, []byte(m.Url)).String() + m.PullKey = uuid.NewMD5(uuid.NameSpaceURL, []byte(m.Base.Url)).String() if m.channel == nil { m.channel = rtmps.NewChannel() m.channel.InitHlsPlayer() @@ -68,7 +68,7 @@ func (m *movie) init() (err error) { return } cli := core.NewConnClient() - if err = cli.Start(m.Url, av.PLAY); err != nil { + if err = cli.Start(m.Base.Url, av.PLAY); err != nil { cli.Close() time.Sleep(time.Second) continue @@ -81,10 +81,10 @@ func (m *movie) init() (err error) { }() } case "http", "https": - if m.Type != "flv" { + if m.Base.Type != "flv" { return errors.New("only flv is supported") } - m.PullKey = uuid.NewMD5(uuid.NameSpaceURL, []byte(m.Url)).String() + m.PullKey = uuid.NewMD5(uuid.NameSpaceURL, []byte(m.Base.Url)).String() if m.channel == nil { m.channel = rtmps.NewChannel() m.channel.InitHlsPlayer() @@ -94,11 +94,11 @@ func (m *movie) init() (err error) { return } r := resty.New().R() - for k, v := range m.Headers { + for k, v := range m.Base.Headers { r.SetHeader(k, v) } // r.SetHeader("User-Agent", UserAgent) - resp, err := r.Get(m.Url) + resp, err := r.Get(m.Base.Url) if err != nil { time.Sleep(time.Second) continue @@ -113,13 +113,13 @@ func (m *movie) init() (err error) { default: return errors.New("unsupported scheme") } - case !m.Live && m.RtmpSource: + case !m.Base.Live && m.Base.RtmpSource: return errors.New("rtmp source can't be true when movie is not live") - case !m.Live && m.Proxy: + case !m.Base.Live && m.Base.Proxy: if !conf.Conf.Proxy.MovieProxy { return errors.New("movie proxy is not enabled") } - u, err := url.Parse(m.Url) + u, err := url.Parse(m.Base.Url) if err != nil { return err } @@ -129,9 +129,9 @@ func (m *movie) init() (err error) { if u.Scheme != "http" && u.Scheme != "https" { return errors.New("unsupported scheme") } - m.PullKey = uuid.NewMD5(uuid.NameSpaceURL, []byte(m.Url)).String() - case !m.Live && !m.Proxy, m.Live && !m.Proxy && !m.RtmpSource: - u, err := url.Parse(m.Url) + m.PullKey = uuid.NewMD5(uuid.NameSpaceURL, []byte(m.Base.Url)).String() + case !m.Base.Live && !m.Base.Proxy, m.Base.Live && !m.Base.Proxy && !m.Base.RtmpSource: + u, err := url.Parse(m.Base.Url) if err != nil { return err } @@ -162,6 +162,6 @@ func (m *movie) Update(movie model.BaseMovieInfo) error { m.lock.Lock() defer m.lock.Unlock() m.terminate() - m.Movie.BaseMovieInfo = movie + m.Movie.Base = movie return m.init() } diff --git a/server/handlers/movie.go b/server/handlers/movie.go index 83c8b40..1f6c36b 100644 --- a/server/handlers/movie.go +++ b/server/handlers/movie.go @@ -61,16 +61,26 @@ func MovieList(ctx *gin.Context) { for i, v := range m { mresp[i] = model.MoviesResp{ Id: v.ID, - Base: m[i].BaseMovieInfo, + Base: m[i].Base, PullKey: v.PullKey, Creater: op.GetUserName(v.CreatorID), } } + current := room.Current() + ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ - "current": room.Current(), - "total": room.GetMoviesCount(), - "movies": mresp, + "current": &model.CurrentMovieResp{ + Status: current.Status, + Movie: model.MoviesResp{ + Id: current.Movie.ID, + Base: current.Movie.Base, + PullKey: current.Movie.PullKey, + Creater: op.GetUserName(current.Movie.CreatorID), + }, + }, + "total": room.GetMoviesCount(), + "movies": mresp, })) } @@ -99,7 +109,7 @@ func Movies(ctx *gin.Context) { for i, v := range m { mresp[i] = model.MoviesResp{ Id: v.ID, - Base: m[i].BaseMovieInfo, + Base: m[i].Base, PullKey: v.PullKey, Creater: op.GetUserName(v.CreatorID), } @@ -122,7 +132,7 @@ func PushMovie(ctx *gin.Context) { } mi := user.NewMovie(dbModel.MovieInfo{ - BaseMovieInfo: dbModel.BaseMovieInfo(req), + Base: dbModel.BaseMovieInfo(req), }) err := room.AddMovie(mi) @@ -164,7 +174,7 @@ func NewPublishKey(ctx *gin.Context) { return } - if !movie.RtmpSource { + if !movie.Base.RtmpSource { ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("only live movie can get publish key")) return } @@ -361,22 +371,22 @@ func ProxyMovie(ctx *gin.Context) { return } - if !m.Proxy || m.Live || m.RtmpSource { + if !m.Base.Proxy || m.Base.Live || m.Base.RtmpSource { ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support proxy")) return } - if l, err := utils.ParseURLIsLocalIP(m.Url); err != nil || l { + if l, err := utils.ParseURLIsLocalIP(m.Base.Url); err != nil || l { ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("parse url error or url is local ip")) return } r := resty.New().R() - for k, v := range m.Headers { + for k, v := range m.Base.Headers { r.SetHeader(k, v) } - resp, err := r.Head(m.Url) + resp, err := r.Head(m.Base.Url) if err != nil { ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return @@ -399,9 +409,9 @@ func ProxyMovie(ctx *gin.Context) { return } - hrs := proxy.NewBufferedHttpReadSeeker(128*1024, m.Url, + hrs := proxy.NewBufferedHttpReadSeeker(128*1024, m.Base.Url, proxy.WithContext(ctx), - proxy.WithHeaders(m.Headers), + proxy.WithHeaders(m.Base.Headers), proxy.WithContext(ctx), proxy.WithContentLength(length), ) diff --git a/server/model/movie.go b/server/model/movie.go index e38d985..1fd62d8 100644 --- a/server/model/movie.go +++ b/server/model/movie.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" json "github.com/json-iterator/go" "github.com/synctv-org/synctv/internal/model" + "github.com/synctv-org/synctv/internal/op" ) var ( @@ -114,3 +115,8 @@ type MoviesResp struct { PullKey string `json:"pullKey"` Creater string `json:"creater"` } + +type CurrentMovieResp struct { + Status op.Status `json:"status"` + Movie MoviesResp `json:"movie"` +} diff --git a/server/model/room.go b/server/model/room.go index d9c60a6..2cd28b7 100644 --- a/server/model/room.go +++ b/server/model/room.go @@ -39,9 +39,9 @@ func (f FormatEmptyPasswordError) Error() string { } type CreateRoomReq struct { - RoomName string `json:"roomName"` - Password string `json:"password"` - Setting model.Setting `json:"setting"` + RoomName string `json:"roomName"` + Password string `json:"password"` + Setting model.Settings `json:"setting"` } func (c *CreateRoomReq) Decode(ctx *gin.Context) error {