Feat: add web control log

pull/83/head
zijiren233 3 years ago
parent 6648895f92
commit 31b0294005

@ -10,6 +10,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/maruel/natural"
"github.com/sirupsen/logrus"
"github.com/synctv-org/synctv/internal/bootstrap"
"github.com/synctv-org/synctv/internal/db"
dbModel "github.com/synctv-org/synctv/internal/model"
@ -25,9 +26,11 @@ import (
func EditAdminSettings(ctx *gin.Context) {
// user := ctx.MustGet("user").(*op.UserEntry)
log := ctx.MustGet("log").(*logrus.Entry)
req := model.AdminSettingsReq{}
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -35,6 +38,7 @@ func EditAdminSettings(ctx *gin.Context) {
for k, v := range req {
err := settings.SetValue(k, v)
if err != nil {
log.WithError(err).Error("set value error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -45,6 +49,8 @@ func EditAdminSettings(ctx *gin.Context) {
func AdminSettings(ctx *gin.Context) {
// user := ctx.MustGet("user").(*op.UserEntry)
log := ctx.MustGet("log").(*logrus.Entry)
group := ctx.Param("group")
switch group {
case "oauth2":
@ -58,6 +64,7 @@ func AdminSettings(ctx *gin.Context) {
}
s, ok := f.Interface().(settings.Setting)
if !ok {
log.Error("type error")
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("type error"))
return
}
@ -81,6 +88,7 @@ func AdminSettings(ctx *gin.Context) {
default:
s, ok := settings.GroupSettings[dbModel.SettingGroup(group)]
if !ok {
log.Error("group not found")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("group not found"))
return
}
@ -98,8 +106,11 @@ func AdminSettings(ctx *gin.Context) {
func Users(ctx *gin.Context) {
// user := ctx.MustGet("user").(*op.UserEntry)
log := ctx.MustGet("log").(*logrus.Entry)
page, pageSize, err := utils.GetPageAndMax(ctx)
if err != nil {
log.WithError(err).Error("get page and max error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -135,6 +146,7 @@ func Users(ctx *gin.Context) {
scopes = append(scopes, db.OrderByAsc("username"))
}
default:
log.Error("not support sort")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support sort"))
return
}
@ -171,14 +183,18 @@ func genUserListResp(us []*dbModel.User) []*model.UserInfoResp {
}
func GetRoomUsers(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
id := ctx.Query("id")
if len(id) != 32 {
log.Error("room id error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("room id error"))
return
}
page, pageSize, err := utils.GetPageAndMax(ctx)
if err != nil {
log.WithError(err).Error("get page and max error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -203,6 +219,7 @@ func GetRoomUsers(ctx *gin.Context) {
scopes = append(scopes, db.OrderByAsc("username"))
}
default:
log.Error("not support sort")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support sort"))
return
}
@ -242,25 +259,31 @@ func genRoomUserListResp(us []*dbModel.User) []*model.RoomUsersResp {
}
func ApprovePendingUser(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
req := model.UserIDReq{}
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
user, err := db.GetUserByID(req.ID)
if err != nil {
log.WithError(err).Error("get user by id error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if !user.IsPending() {
log.Error("user is not pending")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("user is not pending"))
return
}
err = db.SetRoleByID(req.ID, dbModel.RoleUser)
if err != nil {
log.WithError(err).Error("set role by id error")
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -270,30 +293,37 @@ func ApprovePendingUser(ctx *gin.Context) {
func BanUser(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
req := model.UserIDReq{}
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
u, err := op.LoadOrInitUserByID(req.ID)
if err != nil {
log.WithError(err).Error("load or init user by id error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if u.Value().IsRoot() {
log.Error("cannot ban root")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cannot ban root"))
return
}
if u.Value().IsAdmin() && !user.IsRoot() {
log.Error("cannot ban admin")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cannot ban admin"))
return
}
err = u.Value().SetRole(dbModel.RoleBanned)
if err != nil {
log.WithError(err).Error("set role error")
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -303,25 +333,31 @@ func BanUser(ctx *gin.Context) {
func UnBanUser(ctx *gin.Context) {
// user := ctx.MustGet("user").(*op.UserEntry)
log := ctx.MustGet("log").(*logrus.Entry)
req := model.UserIDReq{}
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
u, err := op.LoadOrInitUserByID(req.ID)
if err != nil {
log.WithError(err).Error("load or init user by id error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if !u.Value().IsBanned() {
log.Error("user is not banned")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("user is not banned"))
return
}
err = u.Value().SetRole(dbModel.RoleUser)
if err != nil {
log.WithError(err).Error("set role error")
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -331,9 +367,11 @@ func UnBanUser(ctx *gin.Context) {
func Rooms(ctx *gin.Context) {
// user := ctx.MustGet("user").(*op.UserEntry)
log := ctx.MustGet("log").(*logrus.Entry)
page, pageSize, err := utils.GetPageAndMax(ctx)
if err != nil {
log.WithError(err).Error("get page and max error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -365,6 +403,7 @@ func Rooms(ctx *gin.Context) {
scopes = append(scopes, db.OrderByAsc("name"))
}
default:
log.Error("not support sort")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support sort"))
return
}
@ -392,13 +431,17 @@ func Rooms(ctx *gin.Context) {
}
func GetUserRooms(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
id := ctx.Query("id")
if len(id) != 32 {
log.Error("user id error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("user id error"))
return
}
page, pageSize, err := utils.GetPageAndMax(ctx)
if err != nil {
log.WithError(err).Error("get page and max error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -432,6 +475,7 @@ func GetUserRooms(ctx *gin.Context) {
scopes = append(scopes, db.OrderByAsc("name"))
}
default:
log.Error("not support sort")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support sort"))
return
}
@ -455,25 +499,31 @@ func GetUserRooms(ctx *gin.Context) {
}
func ApprovePendingRoom(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
req := model.RoomIDReq{}
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
room, err := db.GetRoomByID(req.Id)
if err != nil {
log.WithError(err).Error("get room by id error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if !room.IsPending() {
log.Error("room is not pending")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("room is not pending"))
return
}
err = db.SetRoomStatus(req.Id, dbModel.RoomStatusActive)
if err != nil {
log.WithError(err).Error("set room status error")
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -483,36 +533,44 @@ func ApprovePendingRoom(ctx *gin.Context) {
func BanRoom(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
req := model.RoomIDReq{}
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
r, err := db.GetRoomByID(req.Id)
if err != nil {
log.WithError(err).Error("get room by id error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
creator, err := db.GetUserByID(r.CreatorID)
if err != nil {
log.WithError(err).Error("get user by id error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if creator.IsRoot() {
log.Error("cannot ban root")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cannot ban root"))
return
}
if creator.IsAdmin() && !user.IsRoot() {
log.Error("cannot ban admin")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("cannot ban admin"))
return
}
err = op.SetRoomStatusByID(req.Id, dbModel.RoomStatusBanned)
if err != nil {
log.WithError(err).Error("set room status error")
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -522,25 +580,31 @@ func BanRoom(ctx *gin.Context) {
func UnBanRoom(ctx *gin.Context) {
// user := ctx.MustGet("user").(*op.UserEntry)
log := ctx.MustGet("log").(*logrus.Entry)
req := model.RoomIDReq{}
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
r, err := db.GetRoomByID(req.Id)
if err != nil {
log.WithError(err).Error("get room by id error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if !r.IsBanned() {
log.Error("room is not banned")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("room is not banned"))
return
}
err = op.SetRoomStatusByID(req.Id, dbModel.RoomStatusActive)
if err != nil {
log.WithError(err).Error("set room status error")
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -550,20 +614,24 @@ func UnBanRoom(ctx *gin.Context) {
func AddUser(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
req := model.AddUserReq{}
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if req.Role == dbModel.RoleRoot && !user.IsRoot() {
log.Error("cannot add root user")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("you cannot add root user"))
return
}
_, err := op.CreateUser(req.Username, req.Password, db.WithRole(req.Role))
if err != nil {
log.WithError(err).Error("create user error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -573,30 +641,36 @@ func AddUser(ctx *gin.Context) {
func DeleteUser(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
req := model.UserIDReq{}
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
u, err := op.LoadOrInitUserByID(req.ID)
if err != nil {
log.WithError(err).Error("load or init user by id error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if u.Value().IsRoot() {
log.Error("cannot delete root")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cannot delete root"))
return
}
if u.Value().IsAdmin() && !user.IsRoot() {
log.Error("cannot delete admin")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("cannot delete admin"))
return
}
if err := op.DeleteUserByID(req.ID); err != nil {
log.WithError(err).Error("delete user by id error")
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -606,30 +680,36 @@ func DeleteUser(ctx *gin.Context) {
func AdminUserPassword(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
req := model.AdminUserPasswordReq{}
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp(err.Error()))
return
}
u, err := op.LoadOrInitUserByID(req.ID)
if err != nil {
log.WithError(err).Error("load or init user by id error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("user not found"))
return
}
if u.Value().IsRoot() {
log.Error("cannot change root password")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cannot change root password"))
return
}
if u.Value().IsAdmin() && !user.IsRoot() {
log.Error("cannot change admin password")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("cannot change admin password"))
return
}
if err := u.Value().SetPassword(req.Password); err != nil {
log.WithError(err).Error("set password error")
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp(err.Error()))
return
}
@ -639,30 +719,36 @@ func AdminUserPassword(ctx *gin.Context) {
func AdminUsername(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
req := model.AdminUsernameReq{}
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp(err.Error()))
return
}
u, err := op.LoadOrInitUserByID(req.ID)
if err != nil {
log.WithError(err).Error("load or init user by id error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("user not found"))
return
}
if u.Value().IsRoot() {
log.Error("cannot change root username")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cannot change root username"))
return
}
if u.Value().IsAdmin() && !user.IsRoot() {
log.Error("cannot change admin username")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("cannot change admin username"))
return
}
if err := u.Value().SetUsername(req.Username); err != nil {
log.WithError(err).Error("set username error")
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp(err.Error()))
return
}
@ -672,36 +758,43 @@ func AdminUsername(ctx *gin.Context) {
func AdminRoomPassword(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
req := model.AdminRoomPasswordReq{}
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp(err.Error()))
return
}
r, err := op.LoadOrInitRoomByID(req.ID)
if err != nil {
log.WithError(err).Error("load or init room by id error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("room not found"))
return
}
creator, err := op.LoadOrInitUserByID(r.Value().CreatorID)
if err != nil {
log.WithError(err).Error("load or init user by id error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("room creator not found"))
return
}
if creator.Value().IsRoot() {
log.Error("cannot change root room password")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cannot change root room password"))
return
}
if creator.Value().IsAdmin() && !user.IsRoot() {
log.Error("cannot change admin room password")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("cannot change admin room password"))
return
}
if err := r.Value().SetPassword(req.Password); err != nil {
log.WithError(err).Error("set password error")
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp(err.Error()))
return
}
@ -711,10 +804,12 @@ func AdminRoomPassword(ctx *gin.Context) {
func AdminGetVendorBackends(ctx *gin.Context) {
// user := ctx.MustGet("user").(*op.UserEntry)
log := ctx.MustGet("log").(*logrus.Entry)
conns := vendor.LoadConns()
page, size, err := utils.GetPageAndMax(ctx)
if err != nil {
log.WithError(err).Error("get page and max error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -753,14 +848,17 @@ func AdminGetVendorBackends(ctx *gin.Context) {
func AdminAddVendorBackend(ctx *gin.Context) {
// user := ctx.MustGet("user").(*op.UserEntry)
log := ctx.MustGet("log").(*logrus.Entry)
var req model.AddVendorBackendReq
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if err := vendor.AddVendorBackend(ctx, (*dbModel.VendorBackend)(&req)); err != nil {
log.WithError(err).Error("add vendor backend error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -770,14 +868,17 @@ func AdminAddVendorBackend(ctx *gin.Context) {
func AdminDeleteVendorBackends(ctx *gin.Context) {
// user := ctx.MustGet("user").(*op.UserEntry)
log := ctx.MustGet("log").(*logrus.Entry)
var req model.VendorBackendEndpointsReq
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if err := vendor.DeleteVendorBackends(ctx, req.Endpoints); err != nil {
log.WithError(err).Error("delete vendor backends error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -787,14 +888,17 @@ func AdminDeleteVendorBackends(ctx *gin.Context) {
func AdminUpdateVendorBackends(ctx *gin.Context) {
// user := ctx.MustGet("user").(*op.UserEntry)
log := ctx.MustGet("log").(*logrus.Entry)
var req model.AddVendorBackendReq
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if err := vendor.UpdateVendorBackend(ctx, (*dbModel.VendorBackend)(&req)); err != nil {
log.WithError(err).Error("update vendor backend error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -804,9 +908,11 @@ func AdminUpdateVendorBackends(ctx *gin.Context) {
func AdminReconnectVendorBackends(ctx *gin.Context) {
// user := ctx.MustGet("user").(*op.UserEntry)
log := ctx.MustGet("log").(*logrus.Entry)
var req model.VendorBackendEndpointsReq
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -824,6 +930,7 @@ func AdminReconnectVendorBackends(ctx *gin.Context) {
}
}
} else {
log.WithField("endpoint", v).Error("endpoint not found")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp(fmt.Sprintf("endpoint %s not found", v)))
return
}
@ -834,14 +941,17 @@ func AdminReconnectVendorBackends(ctx *gin.Context) {
func AdminEnableVendorBackends(ctx *gin.Context) {
// user := ctx.MustGet("user").(*op.UserEntry)
log := ctx.MustGet("log").(*logrus.Entry)
var req model.VendorBackendEndpointsReq
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if err := vendor.EnableVendorBackends(ctx, req.Endpoints); err != nil {
log.WithError(err).Error("enable vendor backends error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -851,14 +961,17 @@ func AdminEnableVendorBackends(ctx *gin.Context) {
func AdminDisableVendorBackends(ctx *gin.Context) {
// user := ctx.MustGet("user").(*op.UserEntry)
log := ctx.MustGet("log").(*logrus.Entry)
var req model.VendorBackendEndpointsReq
if err := model.Decode(ctx, &req); err != nil {
log.WithError(err).Error("decode error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if err := vendor.DisableVendorBackends(ctx, req.Endpoints); err != nil {
log.WithError(err).Error("disable vendor backends error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}

@ -17,6 +17,7 @@ import (
"strings"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"github.com/synctv-org/synctv/internal/conf"
dbModel "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op"
@ -42,23 +43,26 @@ func GetPageItems[T any](ctx *gin.Context, items []T) ([]T, error) {
func MovieList(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry).Value()
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
page, max, err := utils.GetPageAndMax(ctx)
if err != nil {
log.Errorf("get page and max error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
currentResp, err := genCurrentResp(ctx, user, room)
if err != nil {
log.Errorf("gen current resp error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
m := room.GetMoviesWithPage(page, max)
mresp := make([]model.MoviesResp, len(m))
mresp := make([]model.MovieResp, len(m))
for i, v := range m {
mresp[i] = model.MoviesResp{
mresp[i] = model.MovieResp{
Id: v.Movie.ID,
Base: v.Movie.Base,
Creator: op.GetUserName(v.Movie.CreatorID),
@ -81,13 +85,9 @@ func genCurrentResp(ctx context.Context, user *op.User, room *op.Room) (*model.C
return genCurrentRespWithCurrent(ctx, user, room, room.Current())
}
func genCurrentRespWithCurrent(ctx context.Context, user *op.User, room *op.Room, current *op.Current) (*model.CurrentMovieResp, error) {
if current.Movie.ID == "" {
return &model.CurrentMovieResp{}, nil
}
opMovie, err := room.GetMovieByID(current.Movie.ID)
if err != nil {
return nil, fmt.Errorf("get current movie error: %w", err)
func genCurrentMovieInfo(ctx context.Context, user *op.User, room *op.Room, opMovie *op.Movie) (*model.MovieResp, error) {
if opMovie == nil || opMovie.ID == "" {
return &model.MovieResp{}, nil
}
var movie = opMovie.Movie
if movie.Base.VendorInfo.Vendor != "" {
@ -113,15 +113,33 @@ func genCurrentRespWithCurrent(ctx context.Context, user *op.User, room *op.Room
if movie.Base.Type == "" && movie.Base.Url != "" {
movie.Base.Type = utils.GetUrlExtension(movie.Base.Url)
}
resp := &model.MovieResp{
Id: movie.ID,
CreatedAt: movie.CreatedAt.UnixMilli(),
Base: movie.Base,
Creator: op.GetUserName(movie.CreatorID),
CreatorId: movie.CreatorID,
}
return resp, nil
}
func genCurrentRespWithCurrent(ctx context.Context, user *op.User, room *op.Room, current *op.Current) (*model.CurrentMovieResp, error) {
if current.Movie.ID == "" {
return &model.CurrentMovieResp{
Movie: &model.MovieResp{},
}, nil
}
opMovie, err := room.GetMovieByID(current.Movie.ID)
if err != nil {
return nil, fmt.Errorf("get current movie error: %w", err)
}
mr, err := genCurrentMovieInfo(ctx, user, room, opMovie)
if err != nil {
return nil, fmt.Errorf("gen current movie info error: %w", err)
}
resp := &model.CurrentMovieResp{
Status: current.UpdateStatus(),
Movie: model.MoviesResp{
Id: movie.ID,
CreatedAt: movie.CreatedAt.UnixMilli(),
Base: movie.Base,
Creator: op.GetUserName(movie.CreatorID),
CreatorId: movie.CreatorID,
},
Status: current.UpdateStatus(),
Movie: mr,
ExpireId: opMovie.ExpireId(),
}
return resp, nil
@ -130,9 +148,11 @@ func genCurrentRespWithCurrent(ctx context.Context, user *op.User, room *op.Room
func CurrentMovie(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry).Value()
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
currentResp, err := genCurrentResp(ctx, user, room)
if err != nil {
log.Errorf("gen current resp error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -143,18 +163,20 @@ func CurrentMovie(ctx *gin.Context) {
func Movies(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry).Value()
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
page, max, err := utils.GetPageAndMax(ctx)
if err != nil {
log.Errorf("get page and max error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
m := room.GetMoviesWithPage(int(page), int(max))
mresp := make([]*model.MoviesResp, len(m))
mresp := make([]*model.MovieResp, len(m))
for i, v := range m {
mresp[i] = &model.MoviesResp{
mresp[i] = &model.MovieResp{
Id: v.Movie.ID,
Base: v.Movie.Base,
Creator: op.GetUserName(v.Movie.CreatorID),
@ -175,15 +197,18 @@ func Movies(ctx *gin.Context) {
func PushMovie(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry).Value()
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
req := model.PushMovieReq{}
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("push movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
err := user.AddMovieToRoom(room, (*dbModel.BaseMovie)(&req))
if err != nil {
log.Errorf("push movie error: %v", err)
if errors.Is(err, dbModel.ErrNoPermission) {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err))
return
@ -199,6 +224,7 @@ func PushMovie(ctx *gin.Context) {
Userid: user.ID,
},
}); err != nil {
log.Errorf("push movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -209,6 +235,7 @@ func PushMovie(ctx *gin.Context) {
func PushMovies(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry).Value()
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
req := model.PushMoviesReq{}
if err := model.Decode(ctx, &req); err != nil {
@ -225,6 +252,7 @@ func PushMovies(ctx *gin.Context) {
err := user.AddMoviesToRoom(room, ms)
if err != nil {
log.Errorf("push movies error: %v", err)
if errors.Is(err, dbModel.ErrNoPermission) {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err))
return
@ -240,6 +268,7 @@ func PushMovies(ctx *gin.Context) {
Userid: user.ID,
},
}); err != nil {
log.Errorf("push movies error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -248,7 +277,10 @@ func PushMovies(ctx *gin.Context) {
}
func NewPublishKey(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
if !conf.Conf.Server.Rtmp.Enable {
log.Errorf("rtmp is not enabled")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("rtmp is not enabled"))
return
}
@ -258,27 +290,32 @@ func NewPublishKey(ctx *gin.Context) {
req := model.IdReq{}
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("new publish key error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
movie, err := room.GetMovieByID(req.Id)
if err != nil {
log.Errorf("new publish key error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if movie.Movie.CreatorID != user.ID && !user.HasRoomPermission(room, dbModel.PermissionEditUser) {
log.Errorf("new publish key error: %v", dbModel.ErrNoPermission)
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(dbModel.ErrNoPermission))
return
}
if !movie.Movie.Base.RtmpSource {
log.Errorf("new publish key error: %v", "only rtmp source movie can get publish key")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("only live movie can get publish key"))
return
}
token, err := rtmp.NewRtmpAuthorization(movie.Movie.ID)
if err != nil {
log.Errorf("new publish key error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -298,14 +335,17 @@ func NewPublishKey(ctx *gin.Context) {
func EditMovie(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry).Value()
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
req := model.EditMovieReq{}
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("edit movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if err := user.UpdateMovie(room, req.Id, (*dbModel.BaseMovie)(&req.PushMovieReq)); err != nil {
log.Errorf("edit movie error: %v", err)
if errors.Is(err, dbModel.ErrNoPermission) {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err))
return
@ -321,6 +361,7 @@ func EditMovie(ctx *gin.Context) {
Userid: user.ID,
},
}); err != nil {
log.Errorf("edit movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -331,15 +372,18 @@ func EditMovie(ctx *gin.Context) {
func DelMovie(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry).Value()
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
req := model.IdsReq{}
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("del movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
err := user.DeleteMoviesByID(room, req.Ids)
if err != nil {
log.Errorf("del movie error: %v", err)
if errors.Is(err, dbModel.ErrNoPermission) {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err))
return
@ -421,6 +465,7 @@ func SwapMovie(ctx *gin.Context) {
func ChangeCurrentMovie(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry).Value()
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
req := model.IdCanEmptyReq{}
err := model.Decode(ctx, &req)
@ -432,14 +477,29 @@ func ChangeCurrentMovie(ctx *gin.Context) {
if req.Id == "" {
err = user.SetCurrentMovie(room, nil, false)
} else {
err = user.SetCurrentMovieByID(room, req.Id, true)
var movie *op.Movie
movie, err = room.GetMovieByID(req.Id)
if err != nil {
log.Errorf("change current movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
_, err = genCurrentMovieInfo(ctx, user, room, movie)
if err != nil {
log.Errorf("change current movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
err = user.SetCurrentMovie(room, &movie.Movie, true)
}
if err != nil {
log.Errorf("change current movie error: %v", err)
if errors.Is(err, dbModel.ErrNoPermission) {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err))
return
}
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if err := room.Broadcast(&pb.ElementMessage{
@ -449,6 +509,7 @@ func ChangeCurrentMovie(ctx *gin.Context) {
Userid: user.ID,
},
}); err != nil {
log.Errorf("change current movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -457,24 +518,30 @@ func ChangeCurrentMovie(ctx *gin.Context) {
}
func ProxyMovie(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
if !settings.MovieProxy.Get() {
log.Errorf("movie proxy is not enabled")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("movie proxy is not enabled"))
return
}
roomId := ctx.Param("roomId")
if roomId == "" {
log.Errorf("room id is empty")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("roomId is empty"))
return
}
room, err := op.LoadOrInitRoomByID(roomId)
if err != nil {
log.Errorf("load or init room by id error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
m, err := room.Value().GetMovieByID(ctx.Param("movieId"))
if err != nil {
log.Errorf("get movie by id error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -496,7 +563,7 @@ func ProxyMovie(ctx *gin.Context) {
default:
err = proxyURL(ctx, m.Movie.Base.Url, m.Movie.Base.Headers)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
log.Errorf("proxy movie error: %v", err)
return
}
}
@ -588,23 +655,29 @@ func (e FormatErrNotSupportFileType) Error() string {
}
func JoinLive(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
ctx.Header("Cache-Control", "no-store")
room := ctx.MustGet("room").(*op.RoomEntry).Value()
movieId := strings.Trim(ctx.Param("movieId"), "/")
m, err := room.GetMovieByID(movieId)
if err != nil {
log.Errorf("join live error: %v", err)
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
if m.Movie.Base.RtmpSource && !conf.Conf.Server.Rtmp.Enable {
log.Errorf("join live error: %v", "rtmp is not enabled")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("rtmp is not enabled"))
return
} else if m.Movie.Base.Live && !settings.LiveProxy.Get() {
log.Errorf("join live error: %v", "live proxy is not enabled")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("live proxy is not enabled"))
return
}
channel, err := m.Channel()
if err != nil {
log.Errorf("join live error: %v", err)
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
@ -619,6 +692,7 @@ func JoinLive(ctx *gin.Context) {
defer w.Close()
err = channel.AddPlayer(w)
if err != nil {
log.Errorf("join live error: %v", err)
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
@ -632,34 +706,42 @@ func JoinLive(ctx *gin.Context) {
return fmt.Sprintf("/api/movie/live/hls/data/%s/%s/%s.%s", room.ID, movieId, tsName, ext)
})
if err != nil {
log.Errorf("join live error: %v", err)
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
ctx.Data(http.StatusOK, hls.M3U8ContentType, b)
default:
log.Errorf("join live error: %v", FormatErrNotSupportFileType(joinType))
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp(fmt.Sprintf("not support join type: %s", joinType)))
return
}
}
func JoinFlvLive(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
ctx.Header("Cache-Control", "no-store")
room := ctx.MustGet("room").(*op.RoomEntry).Value()
movieId := strings.TrimSuffix(strings.Trim(ctx.Param("movieId"), "/"), ".flv")
m, err := room.GetMovieByID(movieId)
if err != nil {
log.Errorf("join flv live error: %v", err)
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
if m.Movie.Base.RtmpSource && !conf.Conf.Server.Rtmp.Enable {
log.Errorf("join flv live error: %v", "rtmp is not enabled")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("rtmp is not enabled"))
return
} else if m.Movie.Base.Live && !settings.LiveProxy.Get() {
log.Errorf("join flv live error: %v", "live proxy is not enabled")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("live proxy is not enabled"))
return
}
channel, err := m.Channel()
if err != nil {
log.Errorf("join flv live error: %v", err)
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
@ -668,6 +750,7 @@ func JoinFlvLive(ctx *gin.Context) {
defer w.Close()
err = channel.AddPlayer(w)
if err != nil {
log.Errorf("join flv live error: %v", err)
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
@ -675,23 +758,29 @@ func JoinFlvLive(ctx *gin.Context) {
}
func JoinHlsLive(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
ctx.Header("Cache-Control", "no-store")
room := ctx.MustGet("room").(*op.RoomEntry).Value()
movieId := strings.TrimSuffix(strings.Trim(ctx.Param("movieId"), "/"), ".m3u8")
m, err := room.GetMovieByID(movieId)
if err != nil {
log.Errorf("join hls live error: %v", err)
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
if m.Movie.Base.RtmpSource && !conf.Conf.Server.Rtmp.Enable {
log.Errorf("join hls live error: %v", "rtmp is not enabled")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("rtmp is not enabled"))
return
} else if m.Movie.Base.Live && !settings.LiveProxy.Get() {
log.Errorf("join hls live error: %v", "live proxy is not enabled")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("live proxy is not enabled"))
return
}
channel, err := m.Channel()
if err != nil {
log.Errorf("join hls live error: %v", err)
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
@ -704,6 +793,7 @@ func JoinHlsLive(ctx *gin.Context) {
return fmt.Sprintf("/api/movie/live/hls/data/%s/%s/%s.%s", room.ID, movieId, tsName, ext)
})
if err != nil {
log.Errorf("join hls live error: %v", err)
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
@ -711,10 +801,13 @@ func JoinHlsLive(ctx *gin.Context) {
}
func ServeHlsLive(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
ctx.Header("Cache-Control", "no-store")
roomId := ctx.Param("roomId")
roomE, err := op.LoadOrInitRoomByID(roomId)
if err != nil {
log.Errorf("serve hls live error: %v", err)
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
@ -722,18 +815,22 @@ func ServeHlsLive(ctx *gin.Context) {
movieId := ctx.Param("movieId")
m, err := room.GetMovieByID(movieId)
if err != nil {
log.Errorf("serve hls live error: %v", err)
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
if m.Movie.Base.RtmpSource && !conf.Conf.Server.Rtmp.Enable {
log.Errorf("serve hls live error: %v", "rtmp is not enabled")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("rtmp is not enabled"))
return
} else if m.Movie.Base.Live && !settings.LiveProxy.Get() {
log.Errorf("serve hls live error: %v", "live proxy is not enabled")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("live proxy is not enabled"))
return
}
channel, err := m.Channel()
if err != nil {
log.Errorf("serve hls live error: %v", err)
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
@ -742,11 +839,13 @@ func ServeHlsLive(ctx *gin.Context) {
switch fileExt := filepath.Ext(dataId); fileExt {
case ".ts":
if settings.TsDisguisedAsPng.Get() {
log.Errorf("serve hls live error: %v", FormatErrNotSupportFileType(fileExt))
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(FormatErrNotSupportFileType(fileExt)))
return
}
b, err := channel.GetTsFile(strings.TrimSuffix(dataId, fileExt))
if err != nil {
log.Errorf("serve hls live error: %v", err)
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
@ -754,11 +853,13 @@ func ServeHlsLive(ctx *gin.Context) {
ctx.Data(http.StatusOK, hls.TSContentType, b)
case ".png":
if !settings.TsDisguisedAsPng.Get() {
log.Errorf("serve hls live error: %v", FormatErrNotSupportFileType(fileExt))
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(FormatErrNotSupportFileType(fileExt)))
return
}
b, err := channel.GetTsFile(strings.TrimSuffix(dataId, fileExt))
if err != nil {
log.Errorf("serve hls live error: %v", err)
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
@ -768,33 +869,40 @@ func ServeHlsLive(ctx *gin.Context) {
cache := bytes.NewBuffer(make([]byte, 0, 71))
err = png.Encode(cache, img)
if err != nil {
log.Errorf("serve hls live error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.Data(http.StatusOK, "image/png", append(cache.Bytes(), b...))
default:
ctx.Header("Cache-Control", "no-store")
log.Errorf("serve hls live error: %v", FormatErrNotSupportFileType(fileExt))
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(FormatErrNotSupportFileType(fileExt)))
}
}
func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) {
log := ctx.MustGet("log").(*logrus.Entry)
switch movie.Movie.Base.VendorInfo.Vendor {
case dbModel.VendorBilibili:
t := ctx.Query("t")
switch t {
case "", "hevc":
if !movie.Movie.Base.Proxy {
log.Errorf("proxy vendor movie error: %v", "not support movie proxy")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support movie proxy"))
return
}
u, err := op.LoadOrInitUserByID(movie.Movie.CreatorID)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
mpdC, err := movie.BilibiliCache().SharedMpd.Get(ctx, u.Value().BilibiliCache())
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -808,10 +916,12 @@ func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) {
} else {
streamId, err := strconv.Atoi(id)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if streamId >= len(mpdC.Urls) {
log.Errorf("proxy vendor movie error: %v", "stream id out of range")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("stream id out of range"))
return
}
@ -825,34 +935,42 @@ func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) {
headers["Referer"] = "https://www.bilibili.com"
headers["User-Agent"] = utils.UA
}
proxyURL(ctx, mpdC.Urls[streamId], headers)
err = proxyURL(ctx, mpdC.Urls[streamId], headers)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
}
return
}
case "subtitle":
id := ctx.Query("n")
if id == "" {
log.Errorf("proxy vendor movie error: %v", "n is empty")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("n is empty"))
return
}
u, err := op.LoadOrInitUserByID(movie.Movie.CreatorID)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
srtI, err := movie.BilibiliCache().Subtitle.Get(ctx, u.Value().BilibiliCache())
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
if s, ok := srtI[id]; ok {
srtData, err := s.Srt.Get(ctx)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.Data(http.StatusOK, "text/plain; charset=utf-8", srtData)
return
} else {
log.Errorf("proxy vendor movie error: %v", "subtitle not found")
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorStringResp("subtitle not found"))
return
}
@ -861,11 +979,13 @@ func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) {
case dbModel.VendorAlist:
u, err := op.LoadOrInitUserByID(movie.Movie.CreatorID)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
alistC, err := movie.AlistCache().Get(ctx, u.Value().AlistCache())
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -878,30 +998,38 @@ func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) {
case "subtitle":
idS := ctx.Query("id")
if idS == "" {
log.Errorf("proxy vendor movie error: %v", "id is empty")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("id is empty"))
return
}
id, err := strconv.Atoi(idS)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if id >= len(alistC.Ali.Subtitles) {
log.Errorf("proxy vendor movie error: %v", "id out of range")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("id out of range"))
return
}
data, err := alistC.Ali.Subtitles[id].Cache.Get(ctx)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.Data(http.StatusOK, "text/plain; charset=utf-8", data)
}
} else if !movie.Movie.Base.Proxy {
log.Errorf("proxy vendor movie error: %v", "not support movie proxy")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support movie proxy"))
return
} else {
proxyURL(ctx, alistC.URL, nil)
err = proxyURL(ctx, alistC.URL, nil)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
}
}
return
@ -911,71 +1039,88 @@ func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) {
switch t {
case "":
if !movie.Movie.Base.Proxy {
log.Errorf("proxy vendor movie error: %v", "not support movie proxy")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support movie proxy"))
return
}
u, err := op.LoadOrInitUserByID(movie.Movie.CreatorID)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
embyC, err := movie.EmbyCache().Get(ctx, u.Value().EmbyCache())
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
source, err := strconv.Atoi(ctx.Query("source"))
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if source >= len(embyC.Sources) {
log.Errorf("proxy vendor movie error: %v", "source out of range")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("source out of range"))
return
}
id, err := strconv.Atoi(ctx.Query("id"))
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if id >= len(embyC.Sources[source].URLs) {
log.Errorf("proxy vendor movie error: %v", "id out of range")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("id out of range"))
return
}
proxyURL(ctx, embyC.Sources[source].URLs[id].URL, nil)
err = proxyURL(ctx, embyC.Sources[source].URLs[id].URL, nil)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
}
return
case "subtitle":
u, err := op.LoadOrInitUserByID(movie.Movie.CreatorID)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
embyC, err := movie.EmbyCache().Get(ctx, u.Value().EmbyCache())
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
source, err := strconv.Atoi(ctx.Query("source"))
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if source >= len(embyC.Sources) {
log.Errorf("proxy vendor movie error: %v", "source out of range")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("source out of range"))
return
}
id, err := strconv.Atoi(ctx.Query("id"))
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if id >= len(embyC.Sources[source].Subtitles) {
log.Errorf("proxy vendor movie error: %v", "id out of range")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("id out of range"))
return
}
data, err := embyC.Sources[source].Subtitles[id].Cache.Get(ctx)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -984,6 +1129,7 @@ func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) {
}
default:
log.Errorf("proxy vendor movie error: %v", "vendor not support proxy")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("vendor not support proxy"))
return
}

@ -10,6 +10,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/maruel/natural"
"github.com/sirupsen/logrus"
"github.com/synctv-org/synctv/internal/db"
dbModel "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op"
@ -36,26 +37,31 @@ func (e FormatErrNotSupportPosition) Error() string {
func CreateRoom(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
if settings.DisableCreateRoom.Get() && !user.IsAdmin() {
log.Warn("create room is disabled")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("create room is disabled"))
return
}
req := model.CreateRoomReq{}
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("create room failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
room, err := user.CreateRoom(req.RoomName, req.Password, db.WithSetting(req.Setting))
if err != nil {
log.Errorf("create room failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
token, err := middlewares.NewAuthRoomToken(user, room.Value())
if err != nil {
log.Errorf("create room failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -104,14 +110,18 @@ var roomHotCache = refreshcache.NewRefreshCache[[]*model.RoomListResp](func(cont
}, time.Second*3)
func RoomHotList(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
page, pageSize, err := utils.GetPageAndMax(ctx)
if err != nil {
log.Errorf("get room hot list failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
r, err := roomHotCache.Get(ctx)
if err != nil {
log.Errorf("get room hot list failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -123,8 +133,11 @@ func RoomHotList(ctx *gin.Context) {
}
func RoomList(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
page, pageSize, err := utils.GetPageAndMax(ctx)
if err != nil {
log.Errorf("get room list failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -150,6 +163,7 @@ func RoomList(ctx *gin.Context) {
scopes = append(scopes, db.OrderByAsc("name"))
}
default:
log.Errorf("get room list failed: not support sort")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support sort"))
return
}
@ -193,8 +207,11 @@ func genRoomListResp(scopes ...func(db *gorm.DB) *gorm.DB) []*model.RoomListResp
}
func CheckRoom(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
r, err := db.GetRoomByID(ctx.Query("roomId"))
if err != nil {
log.Errorf("check room failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err))
return
}
@ -208,15 +225,18 @@ func CheckRoom(ctx *gin.Context) {
func LoginRoom(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
req := model.LoginRoomReq{}
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("login room failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
room, err := op.LoadOrInitRoomByID(req.RoomId)
if err != nil {
log.Errorf("login room failed: %v", err)
if err == op.ErrRoomBanned || err == op.ErrRoomPending {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err))
return
@ -226,12 +246,14 @@ func LoginRoom(ctx *gin.Context) {
}
if room.Value().CreatorID != user.ID && !room.Value().CheckPassword(req.Password) {
log.Warn("login room failed: password error")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("password error"))
return
}
token, err := middlewares.NewAuthRoomToken(user, room.Value())
if err != nil {
log.Errorf("login room failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -245,8 +267,10 @@ func LoginRoom(ctx *gin.Context) {
func DeleteRoom(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry)
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
if err := user.DeleteRoom(room); err != nil {
log.Errorf("delete room failed: %v", err)
if errors.Is(err, dbModel.ErrNoPermission) {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err))
return
@ -261,14 +285,17 @@ func DeleteRoom(ctx *gin.Context) {
func SetRoomPassword(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry).Value()
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
req := model.SetRoomPasswordReq{}
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("set room password failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if err := user.SetRoomPassword(room, req.Password); err != nil {
log.Errorf("set room password failed: %v", err)
if errors.Is(err, dbModel.ErrNoPermission) {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err))
return
@ -279,6 +306,7 @@ func SetRoomPassword(ctx *gin.Context) {
token, err := middlewares.NewAuthRoomToken(user, room)
if err != nil {
log.Errorf("set room password failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -299,14 +327,17 @@ func RoomSetting(ctx *gin.Context) {
func SetRoomSetting(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry).Value()
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
req := model.SetRoomSettingReq{}
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("set room setting failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if err := user.SetRoomSetting(room, dbModel.RoomSettings(req)); err != nil {
log.Errorf("set room setting failed: %v", err)
if errors.Is(err, dbModel.ErrNoPermission) {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err))
return
@ -320,8 +351,11 @@ func SetRoomSetting(ctx *gin.Context) {
func RoomUsers(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
page, pageSize, err := utils.GetPageAndMax(ctx)
if err != nil {
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -354,6 +388,7 @@ func RoomUsers(ctx *gin.Context) {
scopes = append(scopes, db.OrderByAsc("username"))
}
default:
log.Errorf("get room users failed: not support sort")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support sort"))
return
}

@ -4,6 +4,7 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
dbModel "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op"
"github.com/synctv-org/synctv/server/model"
@ -11,29 +12,35 @@ import (
func AddAdmin(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
req := model.IdReq{}
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("failed to decode request: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if req.Id == user.ID {
log.Errorf("cannot add yourself")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cannot add yourself"))
return
}
u, err := op.LoadOrInitUserByID(req.Id)
if err != nil {
log.Errorf("failed to load user: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("user not found"))
return
}
if u.Value().IsAdmin() {
log.Errorf("user is already admin")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("user is already admin"))
return
}
if err := u.Value().SetRole(dbModel.RoleAdmin); err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
log.Errorf("failed to set role: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -42,29 +49,35 @@ func AddAdmin(ctx *gin.Context) {
func DeleteAdmin(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry)
log := ctx.MustGet("log").(*logrus.Entry)
req := model.IdReq{}
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("failed to decode request: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if req.Id == user.Value().ID {
log.Errorf("cannot remove yourself")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cannot remove yourself"))
return
}
u, err := op.LoadOrInitUserByID(req.Id)
if err != nil {
log.Errorf("failed to load user: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("user not found"))
return
}
if u.Value().IsRoot() {
log.Errorf("cannot remove root")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cannot remove root"))
return
}
if err := u.Value().SetRole(dbModel.RoleUser); err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
log.Errorf("failed to set role: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}

@ -4,6 +4,7 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"github.com/synctv-org/synctv/internal/db"
dbModel "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op"
@ -27,14 +28,18 @@ func Me(ctx *gin.Context) {
}
func LoginUser(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
req := model.LoginUserReq{}
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("failed to decode request: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
user, err := op.LoadUserByUsername(req.Username)
if err != nil {
log.Errorf("failed to load user: %v", err)
if err == op.ErrUserBanned || err == op.ErrUserPending {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err))
return
@ -44,12 +49,14 @@ func LoginUser(ctx *gin.Context) {
}
if ok := user.Value().CheckPassword(req.Password); !ok {
log.Errorf("password incorrect")
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("password incorrect"))
return
}
token, err := middlewares.NewAuthUserToken(user.Value())
if err != nil {
log.Errorf("failed to generate token: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -61,9 +68,11 @@ func LoginUser(ctx *gin.Context) {
func LogoutUser(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry)
log := ctx.MustGet("log").(*logrus.Entry)
err := op.CompareAndDeleteUser(user)
if err != nil {
log.Errorf("failed to logout: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -73,9 +82,11 @@ func LogoutUser(ctx *gin.Context) {
func UserRooms(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
page, pageSize, err := utils.GetPageAndMax(ctx)
if err != nil {
log.Errorf("failed to get page and max: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -109,6 +120,7 @@ func UserRooms(ctx *gin.Context) {
scopes = append(scopes, db.OrderByAsc("name"))
}
default:
log.Errorf("not support sort")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support sort"))
return
}
@ -133,15 +145,18 @@ func UserRooms(ctx *gin.Context) {
func SetUsername(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
var req model.SetUsernameReq
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("failed to decode request: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
err := user.SetUsername(req.Username)
if err != nil {
log.Errorf("failed to set username: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -151,21 +166,25 @@ func SetUsername(ctx *gin.Context) {
func SetUserPassword(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
var req model.SetUserPasswordReq
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("failed to decode request: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
err := user.SetPassword(req.Password)
if err != nil {
log.Errorf("failed to set password: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
token, err := middlewares.NewAuthUserToken(user)
if err != nil {
log.Errorf("failed to generate token: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
@ -177,9 +196,11 @@ func SetUserPassword(ctx *gin.Context) {
func UserBindProviders(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
up, err := db.GetBindProviders(user.ID)
if err != nil {
log.Errorf("failed to get bind providers: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}

@ -7,6 +7,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus"
"github.com/synctv-org/synctv/internal/op"
pb "github.com/synctv-org/synctv/proto/message"
@ -21,20 +22,27 @@ const maxInterval = 10
func NewWebSocketHandler(wss *utils.WebSocket) gin.HandlerFunc {
return func(ctx *gin.Context) {
token := ctx.GetHeader("Sec-WebSocket-Protocol")
user, room, err := middlewares.AuthRoom(token)
userE, roomE, err := middlewares.AuthRoom(token)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err))
return
}
user := userE.Value()
room := roomE.Value()
entry := log.WithFields(log.Fields{
"rid": room.ID,
"rnm": room.Name,
"uid": user.ID,
"unm": user.Username,
"uro": user.Role.String(),
})
wss.Server(ctx.Writer, ctx.Request, []string{token}, NewWSMessageHandler(user, room))
_ = wss.Server(ctx.Writer, ctx.Request, []string{token}, NewWSMessageHandler(user, room, entry))
}
}
func NewWSMessageHandler(uE *op.UserEntry, rE *op.RoomEntry) func(c *websocket.Conn) error {
func NewWSMessageHandler(u *op.User, r *op.Room, l *logrus.Entry) func(c *websocket.Conn) error {
return func(c *websocket.Conn) error {
r := rE.Value()
u := uE.Value()
client, err := r.NewClient(u, c)
if err != nil {
log.Errorf("ws: register client error: %v", err)
@ -49,92 +57,92 @@ func NewWSMessageHandler(uE *op.UserEntry, rE *op.RoomEntry) func(c *websocket.C
}
return em.Encode(wc)
}
log.Infof("ws: room %s user %s connected", r.Name, u.Username)
l.Info("ws: connected")
defer func() {
r.UnregisterClient(client)
_ = r.UnregisterClient(client)
client.Close()
log.Infof("ws: room %s user %s disconnected", r.Name, u.Username)
l.Info("ws: disconnected")
}()
go handleReaderMessage(client)
return handleWriterMessage(client)
go handleReaderMessage(client, l)
return handleWriterMessage(client, l)
}
}
func handleWriterMessage(c *op.Client) error {
func handleWriterMessage(c *op.Client, l *logrus.Entry) error {
for v := range c.GetReadChan() {
wc, err := c.NextWriter(v.MessageType())
if err != nil {
log.Debugf("ws: room %s user %s get next writer error: %v", c.Room().Name, c.User().Username, err)
l.Errorf("ws: get next writer error: %v", err)
return err
}
if err = v.Encode(wc); err != nil {
log.Debugf("ws: room %s user %s encode message error: %v", c.Room().Name, c.User().Username, err)
l.Errorf("ws: encode message error: %v", err)
return err
}
if err = wc.Close(); err != nil {
l.Errorf("ws: close writer error: %v", err)
return err
}
}
return nil
}
func handleReaderMessage(c *op.Client) error {
defer c.Close()
func handleReaderMessage(c *op.Client, l *logrus.Entry) error {
defer func() {
c.Close()
if r := recover(); r != nil {
l.Errorf("ws: panic: %v", r)
}
}()
for {
t, rd, err := c.NextReader()
if err != nil {
log.Debugf("ws: room %s user %s get next reader error: %v", c.Room().Name, c.User().Username, err)
l.Errorf("ws: get next reader error: %v", err)
return err
}
log.Debugf("ws: room %s user %s receive message type: %d", c.Room().Name, c.User().Username, t)
switch t {
case websocket.CloseMessage:
log.Debugf("ws: room %s user %s receive close message", c.Room().Name, c.User().Username)
return nil
case websocket.BinaryMessage:
var data []byte
if data, err = io.ReadAll(rd); err != nil {
log.Errorf("ws: room %s user %s read message error: %v", c.Room().Name, c.User().Username, err)
if err := c.Send(&pb.ElementMessage{
Type: pb.ElementMessageType_ERROR,
Error: err.Error(),
}); err != nil {
log.Errorf("ws: room %s user %s send error message error: %v", c.Room().Name, c.User().Username, err)
return err
}
continue
}
var msg pb.ElementMessage
if err := proto.Unmarshal(data, &msg); err != nil {
log.Errorf("ws: room %s user %s decode message error: %v", c.Room().Name, c.User().Username, err)
if err := c.Send(&pb.ElementMessage{
Type: pb.ElementMessageType_ERROR,
Error: err.Error(),
}); err != nil {
log.Errorf("ws: room %s user %s send error message error: %v", c.Room().Name, c.User().Username, err)
return err
}
continue
l.Debugf("ws: receive message type: %d", t)
if t != websocket.BinaryMessage {
l.Errorf("ws: receive unknown message type: %d", t)
continue
}
var data []byte
if data, err = io.ReadAll(rd); err != nil {
l.Errorf("ws: read message error: %v", err)
if err := c.Send(&pb.ElementMessage{
Type: pb.ElementMessageType_ERROR,
Error: err.Error(),
}); err != nil {
l.Errorf("ws: send error message error: %v", err)
return err
}
log.Debugf("ws: receive room %s user %s message: %+v", c.Room().Name, c.User().Username, msg.String())
if err = handleElementMsg(c, &msg); err != nil {
log.Errorf("ws: room %s user %s handle message error: %v", c.Room().Name, c.User().Username, err)
continue
}
var msg pb.ElementMessage
if err := proto.Unmarshal(data, &msg); err != nil {
l.Errorf("ws: unmarshal message error: %v", err)
if err := c.Send(&pb.ElementMessage{
Type: pb.ElementMessageType_ERROR,
Error: err.Error(),
}); err != nil {
l.Errorf("ws: send error message error: %v", err)
return err
}
default:
log.Errorf("ws: room %s user %s receive unknown message type: %d", c.Room().Name, c.User().Username, t)
continue
}
l.Debugf("ws: receive message: %v", msg.String())
if err = handleElementMsg(c, &msg, l); err != nil {
l.Errorf("ws: handle message error: %v", err)
return err
}
}
}
const MaxChatMessageLength = 4096
func handleElementMsg(cli *op.Client, msg *pb.ElementMessage) error {
func handleElementMsg(cli *op.Client, msg *pb.ElementMessage, l *logrus.Entry) error {
var timeDiff float64
if msg.Time != 0 {
timeDiff = time.Since(time.UnixMilli(msg.Time)).Seconds()
@ -150,13 +158,12 @@ func handleElementMsg(cli *op.Client, msg *pb.ElementMessage) error {
case pb.ElementMessageType_CHAT_MESSAGE:
message := msg.GetChatReq()
if len(message) > MaxChatMessageLength {
cli.Send(&pb.ElementMessage{
return cli.Send(&pb.ElementMessage{
Type: pb.ElementMessageType_ERROR,
Error: "message too long",
})
return nil
}
cli.Broadcast(&pb.ElementMessage{
return cli.Broadcast(&pb.ElementMessage{
Type: pb.ElementMessageType_CHAT_MESSAGE,
ChatResp: &pb.ChatResp{
Sender: &pb.Sender{
@ -170,7 +177,7 @@ func handleElementMsg(cli *op.Client, msg *pb.ElementMessage) error {
pb.ElementMessageType_PAUSE,
pb.ElementMessageType_CHANGE_RATE:
status := cli.Room().SetStatus(msg.ChangeMovieStatusReq.Playing, msg.ChangeMovieStatusReq.Seek, msg.ChangeMovieStatusReq.Rate, timeDiff)
cli.Broadcast(&pb.ElementMessage{
return cli.Broadcast(&pb.ElementMessage{
Type: msg.Type,
MovieStatusChanged: &pb.MovieStatusChanged{
Sender: &pb.Sender{
@ -186,7 +193,7 @@ func handleElementMsg(cli *op.Client, msg *pb.ElementMessage) error {
}, op.WithIgnoreClient(cli))
case pb.ElementMessageType_CHANGE_SEEK:
status := cli.Room().SetSeekRate(msg.ChangeMovieStatusReq.Seek, msg.ChangeMovieStatusReq.Rate, timeDiff)
cli.Broadcast(&pb.ElementMessage{
return cli.Broadcast(&pb.ElementMessage{
Type: msg.Type,
MovieStatusChanged: &pb.MovieStatusChanged{
Sender: &pb.Sender{
@ -202,7 +209,7 @@ func handleElementMsg(cli *op.Client, msg *pb.ElementMessage) error {
}, op.WithIgnoreClient(cli))
case pb.ElementMessageType_SYNC_MOVIE_STATUS:
status := cli.Room().Current().Status
cli.Send(&pb.ElementMessage{
return cli.Send(&pb.ElementMessage{
Type: pb.ElementMessageType_SYNC_MOVIE_STATUS,
MovieStatusChanged: &pb.MovieStatusChanged{
Sender: &pb.Sender{
@ -237,7 +244,7 @@ func handleElementMsg(cli *op.Client, msg *pb.ElementMessage) error {
status := current.Status
cliStatus := msg.CheckReq.Status
if status.Seek+maxInterval < cliStatus.Seek+timeDiff {
cli.Send(&pb.ElementMessage{
return cli.Send(&pb.ElementMessage{
Type: pb.ElementMessageType_TOO_FAST,
MovieStatusChanged: &pb.MovieStatusChanged{
Status: &pb.MovieStatus{
@ -248,7 +255,7 @@ func handleElementMsg(cli *op.Client, msg *pb.ElementMessage) error {
},
})
} else if status.Seek-maxInterval > cliStatus.Seek+timeDiff {
cli.Send(&pb.ElementMessage{
return cli.Send(&pb.ElementMessage{
Type: pb.ElementMessageType_TOO_SLOW,
MovieStatusChanged: &pb.MovieStatusChanged{
Status: &pb.MovieStatus{

@ -8,6 +8,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"github.com/sirupsen/logrus"
"github.com/synctv-org/synctv/internal/conf"
"github.com/synctv-org/synctv/internal/op"
"github.com/synctv-org/synctv/server/model"
@ -213,6 +214,15 @@ func AuthRoomMiddleware(ctx *gin.Context) {
ctx.Set("user", userE)
ctx.Set("room", roomE)
log := ctx.MustGet("log").(*logrus.Entry)
if log.Data == nil {
log.Data = make(logrus.Fields, 5)
}
log.Data["rid"] = room.ID
log.Data["rnm"] = room.Name
log.Data["uid"] = user.ID
log.Data["unm"] = user.Username
log.Data["uro"] = user.Role.String()
ctx.Next()
}
@ -222,21 +232,29 @@ func AuthUserMiddleware(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err))
return
}
user, err := AuthUser(token)
userE, err := AuthUser(token)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err))
return
}
if user.Value().IsBanned() {
user := userE.Value()
if user.IsBanned() {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("user banned"))
return
}
if user.Value().IsPending() {
if user.IsPending() {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("user is pending, need admin to approve"))
return
}
ctx.Set("user", user)
ctx.Set("user", userE)
log := ctx.MustGet("log").(*logrus.Entry)
if log.Data == nil {
log.Data = make(logrus.Fields, 3)
}
log.Data["uid"] = user.ID
log.Data["unm"] = user.Username
log.Data["uro"] = user.Role.String()
ctx.Next()
}
@ -246,17 +264,25 @@ func AuthAdminMiddleware(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err))
return
}
user, err := AuthUser(token)
userE, err := AuthUser(token)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err))
return
}
if !user.Value().IsAdmin() {
user := userE.Value()
if !user.IsAdmin() {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("user is not admin"))
return
}
ctx.Set("user", user)
ctx.Set("user", userE)
log := ctx.MustGet("log").(*logrus.Entry)
if log.Data == nil {
log.Data = make(logrus.Fields, 3)
}
log.Data["uid"] = user.ID
log.Data["unm"] = user.Username
log.Data["uro"] = user.Role.String()
ctx.Next()
}
@ -266,17 +292,25 @@ func AuthRootMiddleware(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err))
return
}
user, err := AuthUser(token)
userE, err := AuthUser(token)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err))
return
}
if !user.Value().IsRoot() {
user := userE.Value()
if !user.IsRoot() {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("user is not root"))
return
}
ctx.Set("user", user)
ctx.Set("user", userE)
log := ctx.MustGet("log").(*logrus.Entry)
if log.Data == nil {
log.Data = make(logrus.Fields, 3)
}
log.Data["uid"] = user.ID
log.Data["unm"] = user.Username
log.Data["uro"] = user.Role.String()
ctx.Next()
}

@ -13,7 +13,8 @@ func Init(e *gin.Engine) {
w := log.StandardLogger().Writer()
e.
Use(gin.LoggerWithWriter(w), gin.RecoveryWithWriter(w)).
Use(NewCors())
Use(NewCors()).
Use(NewLog(log.StandardLogger()))
if conf.Conf.RateLimit.Enable {
d, err := time.ParseDuration(conf.Conf.RateLimit.Period)
if err != nil {

@ -0,0 +1,14 @@
package middlewares
import (
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
func NewLog(l *logrus.Logger) gin.HandlerFunc {
return func(ctx *gin.Context) {
ctx.Set("log", &logrus.Entry{
Logger: l,
})
}
}

@ -145,7 +145,7 @@ func (s *SwapMovieReq) Validate() error {
return nil
}
type MoviesResp struct {
type MovieResp struct {
Id string `json:"id"`
CreatedAt int64 `json:"createAt"`
Base model.BaseMovie `json:"base"`
@ -155,6 +155,6 @@ type MoviesResp struct {
type CurrentMovieResp struct {
Status op.Status `json:"status"`
Movie MoviesResp `json:"movie"`
Movie *MovieResp `json:"movie"`
ExpireId uint64 `json:"expireId"`
}

@ -6,6 +6,7 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"github.com/synctv-org/synctv/internal/bootstrap"
"github.com/synctv-org/synctv/internal/db"
dbModel "github.com/synctv-org/synctv/internal/model"
@ -21,8 +22,11 @@ import (
// GET
// /oauth2/login/:type
func OAuth2(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
pi, err := providers.GetProvider(provider.OAuth2Provider(ctx.Param("type")))
if err != nil {
log.Errorf("failed to get provider: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -30,18 +34,25 @@ func OAuth2(ctx *gin.Context) {
state := utils.RandString(16)
states.Store(state, newAuthFunc(ctx.Query("redirect")), time.Minute*5)
RenderRedirect(ctx, pi.NewAuthURL(state))
err = RenderRedirect(ctx, pi.NewAuthURL(state))
if err != nil {
log.Errorf("failed to render redirect: %v", err)
}
}
// POST
func OAuth2Api(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
pi, err := providers.GetProvider(provider.OAuth2Provider(ctx.Param("type")))
if err != nil {
log.Errorf("failed to get provider: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
}
meta := model.OAuth2Req{}
if err := model.Decode(ctx, &meta); err != nil {
log.Errorf("failed to decode request: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -57,20 +68,25 @@ func OAuth2Api(ctx *gin.Context) {
// GET
// /oauth2/callback/:type
func OAuth2Callback(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
code := ctx.Query("code")
if code == "" {
log.Errorf("invalid oauth2 code")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("invalid oauth2 code"))
return
}
pi, err := providers.GetProvider(provider.OAuth2Provider(ctx.Param("type")))
if err != nil {
log.Errorf("failed to get provider: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
meta, loaded := states.LoadAndDelete(ctx.Query("state"))
if !loaded {
log.Errorf("invalid oauth2 state")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("invalid oauth2 state"))
return
}
@ -78,6 +94,7 @@ func OAuth2Callback(ctx *gin.Context) {
if meta.Value() != nil {
meta.Value()(ctx, pi, code)
} else {
log.Errorf("invalid oauth2 handler")
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("invalid oauth2 handler"))
}
}
@ -85,19 +102,24 @@ func OAuth2Callback(ctx *gin.Context) {
// POST
// /oauth2/callback/:type
func OAuth2CallbackApi(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
req := model.OAuth2CallbackReq{}
if err := req.Decode(ctx); err != nil {
log.Errorf("failed to decode request: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
pi, err := providers.GetProvider(provider.OAuth2Provider(ctx.Param("type")))
if err != nil {
log.Errorf("failed to get provider: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
}
meta, loaded := states.LoadAndDelete(req.State)
if !loaded {
log.Errorf("invalid oauth2 state")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("invalid oauth2 state"))
return
}
@ -105,26 +127,32 @@ func OAuth2CallbackApi(ctx *gin.Context) {
if meta.Value() != nil {
meta.Value()(ctx, pi, req.Code)
} else {
log.Errorf("invalid oauth2 handler")
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("invalid oauth2 handler"))
}
}
func newAuthFunc(redirect string) stateHandler {
return func(ctx *gin.Context, pi provider.ProviderInterface, code string) {
log := ctx.MustGet("log").(*logrus.Entry)
t, err := pi.GetToken(ctx, code)
if err != nil {
log.Errorf("failed to get token: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
ui, err := pi.GetUserInfo(ctx, t)
if err != nil {
log.Errorf("failed to get user info: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
pgs, loaded := bootstrap.ProviderGroupSettings[dbModel.SettingGroup(fmt.Sprintf("%s_%s", dbModel.SettingGroupOauth2, pi.Provider()))]
if !loaded {
log.Errorf("invalid oauth2 provider")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("invalid oauth2 provider"))
return
}
@ -140,18 +168,23 @@ func newAuthFunc(redirect string) stateHandler {
}
}
if err != nil {
log.Errorf("failed to create or load user: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
token, err := middlewares.NewAuthUserToken(user.Value())
if err != nil {
log.Errorf("failed to generate token: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if ctx.Request.Method == http.MethodGet {
RenderToken(ctx, redirect, token)
err = RenderToken(ctx, redirect, token)
if err != nil {
log.Errorf("failed to render token: %v", err)
}
} else if ctx.Request.Method == http.MethodPost {
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"token": token,

@ -5,6 +5,7 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"github.com/synctv-org/synctv/internal/db"
"github.com/synctv-org/synctv/internal/op"
"github.com/synctv-org/synctv/internal/provider"
@ -16,14 +17,17 @@ import (
func BindApi(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
pi, err := providers.GetProvider(provider.OAuth2Provider(ctx.Param("type")))
if err != nil {
log.Errorf("failed to get provider: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
}
meta := model.OAuth2Req{}
if err := model.Decode(ctx, &meta); err != nil {
log.Errorf("failed to decode request: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -38,15 +42,18 @@ func BindApi(ctx *gin.Context) {
func UnBindApi(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
pi, err := providers.GetProvider(provider.OAuth2Provider(ctx.Param("type")))
if err != nil {
log.Errorf("failed to get provider: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
err = db.UnBindProvider(user.ID, pi.Provider())
if err != nil {
log.Errorf("failed to unbind provider: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
@ -56,32 +63,39 @@ func UnBindApi(ctx *gin.Context) {
func newBindFunc(userID, redirect string) stateHandler {
return func(ctx *gin.Context, pi provider.ProviderInterface, code string) {
log := ctx.MustGet("log").(*logrus.Entry)
t, err := pi.GetToken(ctx, code)
if err != nil {
log.Errorf("failed to get token: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
ui, err := pi.GetUserInfo(ctx, t)
if err != nil {
log.Errorf("failed to get user info: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
user, err := op.LoadOrInitUserByID(userID)
if err != nil {
log.Errorf("failed to load user: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
err = user.Value().BindProvider(pi.Provider(), ui.ProviderUserID)
if err != nil {
log.Errorf("failed to bind provider: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
token, err := middlewares.NewAuthUserToken(user.Value())
if err != nil {
log.Errorf("failed to generate token: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}

@ -4,13 +4,17 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"github.com/synctv-org/synctv/internal/bootstrap"
"github.com/synctv-org/synctv/server/model"
)
func OAuth2EnabledApi(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)
data, err := bootstrap.Oauth2EnabledCache.Get(ctx)
if err != nil {
log.Errorf("failed to get oauth2 enabled: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}

Loading…
Cancel
Save