Feat: send test eamil

pull/119/head
zijiren233 11 months ago
parent 33ab4dbee7
commit 4a38f9d921

@ -23,7 +23,7 @@ func WithRole(role model.Role) CreateUserConfig {
func WithAppendProvider(p provider.OAuth2Provider, puid string) CreateUserConfig {
return func(u *model.User) {
u.UserProviders = append(u.UserProviders, model.UserProvider{
u.UserProviders = append(u.UserProviders, &model.UserProvider{
Provider: p,
ProviderUserID: puid,
})
@ -32,20 +32,20 @@ func WithAppendProvider(p provider.OAuth2Provider, puid string) CreateUserConfig
func WithSetProvider(p provider.OAuth2Provider, puid string) CreateUserConfig {
return func(u *model.User) {
u.UserProviders = []model.UserProvider{{
u.UserProviders = []*model.UserProvider{{
Provider: p,
ProviderUserID: puid,
}}
}
}
func WithAppendProviders(providers []model.UserProvider) CreateUserConfig {
func WithAppendProviders(providers []*model.UserProvider) CreateUserConfig {
return func(u *model.User) {
u.UserProviders = append(u.UserProviders, providers...)
}
}
func WithSetProviders(providers []model.UserProvider) CreateUserConfig {
func WithSetProviders(providers []*model.UserProvider) CreateUserConfig {
return func(u *model.User) {
u.UserProviders = providers
}

@ -44,12 +44,12 @@ type User struct {
UpdatedAt time.Time
RegisteredByProvider bool `gorm:"not null;default:false"`
RegisteredByEmail bool `gorm:"not null;default:false"`
UserProviders []UserProvider `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
UserProviders []*UserProvider `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Username string `gorm:"not null;uniqueIndex;type:varchar(32)"`
HashedPassword []byte `gorm:"not null"`
Email string `gorm:"type:varchar(128);uniqueIndex:,where:email <> ''"`
Role Role `gorm:"not null;default:2"`
RoomUserRelations []RoomUserRelation `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
RoomUserRelations []*RoomUserRelation `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Rooms []Room `gorm:"foreignKey:CreatorID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Movies []Movie `gorm:"foreignKey:CreatorID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
BilibiliVendor *BilibiliVendor `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`

@ -316,9 +316,11 @@ func (u *User) UnbindEmail() error {
return nil
}
var ErrEmailUnbound = errors.New("email unbound")
func (u *User) SendTestEmail() error {
if u.Email == "" {
return errors.New("unbound email")
return ErrEmailUnbound
}
return email.SendTestEmail(u.Username, u.Email)
@ -326,7 +328,7 @@ func (u *User) SendTestEmail() error {
func (u *User) SendRetrievePasswordCaptchaEmail(host string) error {
if u.Email == "" {
return errors.New("unbound email")
return ErrEmailUnbound
}
return email.SendRetrievePasswordCaptchaEmail(u.ID, u.Email, host)

@ -2,6 +2,7 @@ package handlers
import (
"context"
"errors"
"fmt"
"net/http"
"slices"
@ -12,6 +13,7 @@ import (
"github.com/maruel/natural"
"github.com/sirupsen/logrus"
"github.com/synctv-org/synctv/internal/db"
"github.com/synctv-org/synctv/internal/email"
dbModel "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op"
"github.com/synctv-org/synctv/internal/settings"
@ -29,7 +31,6 @@ func EditAdminSettings(ctx *gin.Context) {
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
}
@ -261,7 +262,6 @@ func ApprovePendingUser(ctx *gin.Context) {
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
}
@ -295,7 +295,6 @@ func BanUser(ctx *gin.Context) {
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
}
@ -335,7 +334,6 @@ func UnBanUser(ctx *gin.Context) {
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
}
@ -501,7 +499,6 @@ func ApprovePendingRoom(ctx *gin.Context) {
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
}
@ -535,7 +532,6 @@ func BanRoom(ctx *gin.Context) {
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
}
@ -582,7 +578,6 @@ func UnBanRoom(ctx *gin.Context) {
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
}
@ -616,7 +611,6 @@ func AddUser(ctx *gin.Context) {
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
}
@ -643,7 +637,6 @@ func DeleteUser(ctx *gin.Context) {
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
}
@ -682,7 +675,6 @@ func AdminUserPassword(ctx *gin.Context) {
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
}
@ -721,7 +713,6 @@ func AdminUsername(ctx *gin.Context) {
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
}
@ -760,7 +751,6 @@ func AdminRoomPassword(ctx *gin.Context) {
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
}
@ -850,7 +840,6 @@ func AdminAddVendorBackend(ctx *gin.Context) {
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
}
@ -870,7 +859,6 @@ func AdminDeleteVendorBackends(ctx *gin.Context) {
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
}
@ -890,7 +878,6 @@ func AdminUpdateVendorBackends(ctx *gin.Context) {
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
}
@ -910,7 +897,6 @@ func AdminReconnectVendorBackends(ctx *gin.Context) {
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
}
@ -943,7 +929,6 @@ func AdminEnableVendorBackends(ctx *gin.Context) {
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
}
@ -963,7 +948,6 @@ func AdminDisableVendorBackends(ctx *gin.Context) {
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
}
@ -976,3 +960,34 @@ func AdminDisableVendorBackends(ctx *gin.Context) {
ctx.Status(http.StatusNoContent)
}
func SendTestEmail(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
var req model.SendTestEmailReq
if err := model.Decode(ctx, &req); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if req.Email == "" {
if err := user.SendTestEmail(); err != nil {
log.Errorf("failed to send test email: %v", err)
if errors.Is(err, op.ErrEmailUnbound) {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
} else {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
}
return
}
} else {
if err := email.SendTestEmail(user.Username, req.Email); err != nil {
log.Errorf("failed to send test email: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
}
ctx.Status(http.StatusNoContent)
}

@ -79,6 +79,8 @@ func initAdmin(admin *gin.RouterGroup, root *gin.RouterGroup) {
admin.POST("/settings", EditAdminSettings)
admin.POST("/email/test", SendTestEmail)
admin.GET("/vendors", AdminGetVendorBackends)
admin.POST("/vendors/add", AdminAddVendorBackend)
@ -241,8 +243,6 @@ func initUser(user *gin.RouterGroup, needAuthUser *gin.RouterGroup) {
needAuthUser.POST("/bind/email", UserBindEmail)
needAuthUser.POST("/unbind/email", UserUnbindEmail)
needAuthUser.POST("/bind/email/test", UserSendTestEmail)
}
func initVendor(vendor *gin.RouterGroup) {

@ -333,19 +333,6 @@ func UserUnbindEmail(ctx *gin.Context) {
ctx.Status(http.StatusNoContent)
}
func UserSendTestEmail(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
if err := user.SendTestEmail(); err != nil {
log.Errorf("failed to send test email: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.Status(http.StatusNoContent)
}
func GetUserSignupEmailStep1Captcha(ctx *gin.Context) {
log := ctx.MustGet("log").(*logrus.Entry)

@ -175,3 +175,18 @@ func (dvbr *VendorBackendEndpointsReq) Validate() error {
func (dvbr *VendorBackendEndpointsReq) Decode(ctx *gin.Context) error {
return json.NewDecoder(ctx.Request.Body).Decode(dvbr)
}
type SendTestEmailReq struct {
Email string `json:"email"`
}
func (ster *SendTestEmailReq) Validate() error {
if ster.Email != "" && !emailReg.MatchString(ster.Email) {
return errors.New("invalid email")
}
return nil
}
func (ster *SendTestEmailReq) Decode(ctx *gin.Context) error {
return json.NewDecoder(ctx.Request.Body).Decode(ster)
}

Loading…
Cancel
Save