提交 4420a75c 编写于 作者: H HFO4

Feat: user setting

上级 33a917cc
......@@ -148,7 +148,7 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti
{Name: "hot_share_num", Value: `10`, Type: "share"},
{Name: "allow_buy_group", Value: `1`, Type: "group_sell"},
{Name: "group_sell_data", Value: `[]`, Type: "group_sell"},
{Name: "gravatar_server", Value: `https://v2ex.assets.uxengine.net/gravatar/`, Type: "avatar"},
{Name: "gravatar_server", Value: `https://gravatar.loli.net/`, Type: "avatar"},
{Name: "defaultTheme", Value: `#3f51b5`, Type: "basic"},
{Name: "themes", Value: `{"#3f51b5":{"palette":{"primary":{"light":"#7986cb","main":"#3f51b5","dark":"#303f9f","contrastText":"#fff"},"secondary":{"light":"#ff4081","main":"#f50057","dark":"#c51162","contrastText":"#fff"},"error":{"light":"#e57373","main":"#f44336","dark":"#d32f2f","contrastText":"#fff"},"explorer":{"filename":"#474849","icon":"#8f8f8f","bgSelected":"#D5DAF0","emptyIcon":"#e8e8e8"}}}}`, Type: "basic"},
{Name: "aria2_token", Value: `your token`, Type: "aria2"},
......@@ -160,6 +160,11 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti
{Name: "max_parallel_transfer", Value: `4`, Type: "task"},
{Name: "secret_key", Value: util.RandStringRunes(256), Type: "auth"},
{Name: "temp_path", Value: "temp", Type: "path"},
{Name: "avatar_path", Value: "avatar", Type: "path"},
{Name: "avatar_size", Value: "2097152", Type: "avatar"},
{Name: "avatar_size_l", Value: "200", Type: "avatar"},
{Name: "avatar_size_m", Value: "130", Type: "avatar"},
{Name: "avatar_size_s", Value: "50", Type: "avatar"},
{Name: "score_enabled", Value: "1", Type: "score"},
{Name: "share_score_rate", Value: "80", Type: "score"},
{Name: "score_price", Value: "1", Type: "score"},
......
......@@ -45,7 +45,7 @@ type User struct {
NotifyDate *time.Time // 通知超出配额时的日期
// 关联模型
Group Group `gorm:"association_autoupdate:false"`
Group Group `gorm:"save_associations:false:false"`
Policy Policy `gorm:"PRELOAD:false,association_autoupdate:false"`
// 数据库忽略字段
......@@ -73,12 +73,12 @@ func (user *User) DeductionStorage(size uint64) bool {
}
if size <= user.Storage {
user.Storage -= size
DB.Model(user).UpdateColumn("storage", gorm.Expr("storage - ?", size))
DB.Model(user).Update("storage", gorm.Expr("storage - ?", size))
return true
}
// 如果要减少的容量超出已用容量,则设为零
user.Storage = 0
DB.Model(user).UpdateColumn("storage", 0)
DB.Model(user).Update("storage", 0)
return false
}
......@@ -90,7 +90,7 @@ func (user *User) IncreaseStorage(size uint64) bool {
}
if size <= user.GetRemainingCapacity() {
user.Storage += size
DB.Model(user).UpdateColumn("storage", gorm.Expr("storage + ?", size))
DB.Model(user).Update("storage", gorm.Expr("storage + ?", size))
return true
}
return false
......@@ -103,7 +103,7 @@ func (user *User) PayScore(score int) bool {
}
if score <= user.Score {
user.Score -= score
DB.Model(user).UpdateColumn("score", gorm.Expr("score - ?", score))
DB.Model(user).Update("score", gorm.Expr("score - ?", score))
return true
}
return false
......@@ -112,7 +112,7 @@ func (user *User) PayScore(score int) bool {
// AddScore 增加积分
func (user *User) AddScore(score int) {
user.Score += score
DB.Model(user).UpdateColumn("score", gorm.Expr("score + ?", score))
DB.Model(user).Update("score", gorm.Expr("score + ?", score))
}
// IncreaseStorageWithoutCheck 忽略可用容量,增加用户已用容量
......@@ -121,7 +121,7 @@ func (user *User) IncreaseStorageWithoutCheck(size uint64) {
return
}
user.Storage += size
DB.Model(user).UpdateColumn("storage", gorm.Expr("storage + ?", size))
DB.Model(user).Update("storage", gorm.Expr("storage + ?", size))
}
......@@ -175,7 +175,7 @@ func GetActiveUserByID(ID interface{}) (User, error) {
// GetUserByEmail 用Email获取用户
func GetUserByEmail(email string) (User, error) {
var user User
result := DB.Set("gorm:auto_preload", true).Where("email = ?", email).First(&user)
result := DB.Set("gorm:auto_preload", true).Where("status = ? and email = ?", Active, email).First(&user)
return user, result.Error
}
......@@ -296,6 +296,11 @@ func (user *User) SetStatus(status int) {
DB.Model(&user).Update("status", status)
}
// Update 更新用户
func (user *User) Update(val map[string]interface{}) error {
return DB.Model(user).Updates(val).Error
}
// GetGroupExpiredUsers 获取用户组过期的用户
func GetGroupExpiredUsers() []User {
var users []User
......
......@@ -49,5 +49,5 @@ func (user *User) RegisterAuthn(credential *webauthn.Credential) {
if err != nil {
fmt.Println(err)
}
DB.Model(user).UpdateColumn("authn", string(res))
DB.Model(user).Update("authn", string(res))
}
......@@ -151,7 +151,6 @@ func TestNewUser(t *testing.T) {
newUser := NewUser()
asserts.IsType(User{}, newUser)
asserts.NotEmpty(newUser.Avatar)
asserts.NotEmpty(newUser.OptionsSerialized)
}
func TestUser_AfterFind(t *testing.T) {
......@@ -304,7 +303,7 @@ func TestUser_DeductionStorage(t *testing.T) {
Storage: 10,
}
mock.ExpectBegin()
mock.ExpectExec("UPDATE(.+)").WithArgs(5, 1).WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectExec("UPDATE(.+)").WithArgs(5, sqlmock.AnyArg(), 1).WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
asserts.True(user.DeductionStorage(5))
......@@ -319,7 +318,7 @@ func TestUser_DeductionStorage(t *testing.T) {
Storage: 10,
}
mock.ExpectBegin()
mock.ExpectExec("UPDATE(.+)").WithArgs(0, 1).WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectExec("UPDATE(.+)").WithArgs(0, sqlmock.AnyArg(), 1).WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
asserts.False(user.DeductionStorage(20))
......@@ -355,11 +354,11 @@ func TestUser_IncreaseStorageWithoutCheck(t *testing.T) {
func TestGetUserByEmail(t *testing.T) {
asserts := assert.New(t)
mock.ExpectQuery("SELECT(.+)").WithArgs("abslant@foxmail.com").WillReturnRows(sqlmock.NewRows([]string{"id", "email"}))
mock.ExpectQuery("SELECT(.+)").WithArgs(Active, "abslant@foxmail.com").WillReturnRows(sqlmock.NewRows([]string{"id", "email"}))
_, err := GetUserByEmail("abslant@foxmail.com")
asserts.NoError(mock.ExpectationsWereMet())
asserts.Error(err)
asserts.NoError(mock.ExpectationsWereMet())
}
func TestUser_AfterCreate(t *testing.T) {
......
package qq
import (
"crypto/md5"
"encoding/json"
"errors"
"fmt"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/request"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/gofrs/uuid"
"net/url"
"strings"
)
// LoginPage 登陆页面描述
type LoginPage struct {
URL string
SecretKey string
}
// UserCredentials 登陆成功后的凭证
type UserCredentials struct {
OpenID string
AccessToken string
}
var (
// ErrNotEnabled 未开启登录功能
ErrNotEnabled = serializer.NewError(serializer.CodeNoPermissionErr, "QQ登录功能未开启", nil)
// ErrObtainAccessToken 无法获取AccessToken
ErrObtainAccessToken = serializer.NewError(serializer.CodeParamErr, "无法获取AccessToken", nil)
// ErrObtainOpenID 无法获取OpenID
ErrObtainOpenID = serializer.NewError(serializer.CodeParamErr, "无法获取OpenID", nil)
//ErrDecodeResponse 无法解析服务端响应
ErrDecodeResponse = serializer.NewError(serializer.CodeInternalSetting, "无法解析服务端响应", nil)
)
// NewLoginRequest 新建登录会话
func NewLoginRequest() (*LoginPage, error) {
// 获取相关设定
options := model.GetSettingByNames("qq_login", "qq_login_id")
if options["qq_login"] == "0" {
return nil, ErrNotEnabled
}
// 生成唯一ID
u2, err := uuid.NewV4()
if err != nil {
return nil, err
}
secret := fmt.Sprintf("%x", md5.Sum(u2.Bytes()))
// 生成登录地址
loginURL, _ := url.Parse("https://graph.qq.com/oauth2.0/authorize?response_type=code")
queries := loginURL.Query()
queries.Add("client_id", options["qq_login_id"])
queries.Add("redirect_uri", getCallbackURL())
queries.Add("state", secret)
loginURL.RawQuery = queries.Encode()
return &LoginPage{
URL: loginURL.String(),
SecretKey: secret,
}, nil
}
func getCallbackURL() string {
return "https://drive.aoaoao.me/Callback/QQ"
// 生成回调地址
gateway, _ := url.Parse("/#/login/qq")
callback := model.GetSiteURL().ResolveReference(gateway).String()
return callback
}
func getAccessTokenURL(code string) string {
// 获取相关设定
options := model.GetSettingByNames("qq_login_id", "qq_login_key")
api, _ := url.Parse("https://graph.qq.com/oauth2.0/token?grant_type=authorization_code")
queries := api.Query()
queries.Add("client_id", options["qq_login_id"])
queries.Add("redirect_uri", getCallbackURL())
queries.Add("client_secret", options["qq_login_key"])
queries.Add("code", code)
api.RawQuery = queries.Encode()
return api.String()
}
func getResponse(body string) (map[string]interface{}, error) {
var res map[string]interface{}
if !strings.Contains(body, "callback") {
return res, nil
}
body = strings.TrimPrefix(body, "callback(")
body = strings.TrimSuffix(body, ");\n")
err := json.Unmarshal([]byte(body), &res)
return res, err
}
// Callback 处理回调,返回openid和access key
func Callback(code string) (*UserCredentials, error) {
// 获取相关设定
options := model.GetSettingByNames("qq_login")
if options["qq_login"] == "0" {
return nil, ErrNotEnabled
}
api := getAccessTokenURL(code)
// 获取AccessToken
client := request.HTTPClient{}
res := client.Request("GET", api, nil)
resp, err := res.GetResponse()
if err != nil {
return nil, ErrObtainAccessToken.WithError(err)
}
// 如果服务端返回错误
errResp, err := getResponse(resp)
if msg, ok := errResp["error_description"]; err == nil && ok {
return nil, ErrObtainAccessToken.WithError(errors.New(msg.(string)))
}
// 获取AccessToken
vals, err := url.ParseQuery(resp)
if err != nil {
return nil, ErrDecodeResponse.WithError(err)
}
accessToken := vals.Get("access_token")
// 用 AccessToken 换取OpenID
res = client.Request("GET", "https://graph.qq.com/oauth2.0/me?access_token="+accessToken, nil)
resp, err = res.GetResponse()
if err != nil {
return nil, ErrObtainOpenID.WithError(err)
}
// 解析服务端响应
errResp, err = getResponse(resp)
if msg, ok := errResp["error_description"]; err == nil && ok {
return nil, ErrObtainOpenID.WithError(errors.New(msg.(string)))
}
if openid, ok := errResp["openid"]; ok {
return &UserCredentials{
OpenID: openid.(string),
AccessToken: accessToken,
}, nil
}
return nil, ErrDecodeResponse
}
......@@ -46,6 +46,7 @@ type group struct {
ShareFreeEnabled bool `json:"shareFree"`
ShareDownload bool `json:"shareDownload"`
CompressEnabled bool `json:"compress"`
WebDAVEnabled bool `json:"webdav"`
}
type tag struct {
......@@ -91,6 +92,7 @@ func BuildUser(user model.User) User {
ShareFreeEnabled: user.Group.OptionsSerialized.ShareFree,
ShareDownload: user.Group.OptionsSerialized.ShareDownload,
CompressEnabled: user.Group.OptionsSerialized.ArchiveTask,
WebDAVEnabled: user.Group.WebDAVEnabled,
},
Tags: buildTagRes(tags),
}
......
......@@ -2,12 +2,14 @@ package thumb
import (
"errors"
"fmt"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/util"
"image"
"image/gif"
"image/jpeg"
"image/png"
"io"
"os"
"path/filepath"
"strings"
......@@ -66,14 +68,36 @@ func (image *Thumb) GetSize() (int, int) {
// Save 保存图像到给定路径
func (image *Thumb) Save(path string) (err error) {
out, err := os.Create(path)
defer out.Close()
out, err := util.CreatNestedFile(path)
if err != nil {
return err
}
defer out.Close()
err = png.Encode(out, image.src)
return err
}
// CreateAvatar 创建头像
func (image *Thumb) CreateAvatar(uid uint) error {
// 读取头像相关设定
savePath := model.GetSettingByName("avatar_path")
s := model.GetIntSetting("avatar_size_s", 50)
m := model.GetIntSetting("avatar_size_m", 130)
l := model.GetIntSetting("avatar_size_l", 200)
// 生成头像缩略图
src := image.src
for k, size := range []int{s, m, l} {
image.src = resize.Resize(uint(size), uint(size), src, resize.Lanczos3)
err := image.Save(filepath.Join(savePath, fmt.Sprintf("avatar_%d_%d.png", uid, k)))
if err != nil {
return err
}
}
return nil
}
......@@ -9,7 +9,6 @@ import (
"github.com/HFO4/cloudreve/pkg/filesystem/driver/local"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/HFO4/cloudreve/service/explorer"
"github.com/gin-gonic/gin"
"net/http"
......@@ -294,7 +293,7 @@ func FileUploadStream(c *gin.Context) {
File: c.Request.Body,
Size: fileSize,
Name: fileName,
VirtualPath: util.DotPathToStandardPath(filePath),
VirtualPath: filePath,
}
// 创建文件系统
......
......@@ -17,6 +17,7 @@ func ParamErrorMsg(filed string, tag string) string {
"Path": "路径",
"SourceID": "原始资源",
"URL": "链接",
"Nick": "昵称",
}
// 未通过的规则与中文对应
tagMap := map[string]string{
......
......@@ -5,6 +5,7 @@ import (
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/authn"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/thumb"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/HFO4/cloudreve/service/user"
"github.com/duo-labs/webauthn/webauthn"
......@@ -157,3 +158,117 @@ func UserTasks(c *gin.Context) {
c.JSON(200, ErrorResponse(err))
}
}
// UserSetting 获取用户设定
func UserSetting(c *gin.Context) {
var service user.SettingService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Settings(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// UseGravatar 设定头像使用全球通用
func UseGravatar(c *gin.Context) {
u := CurrentUser(c)
if err := u.Update(map[string]interface{}{"avatar": "gravatar"}); err != nil {
c.JSON(200, serializer.Err(serializer.CodeDBError, "无法更新头像", err))
return
}
c.JSON(200, serializer.Response{})
}
// UploadAvatar 从文件上传头像
func UploadAvatar(c *gin.Context) {
// 取得头像上传大小限制
maxSize := model.GetIntSetting("avatar_size", 2097152)
if c.Request.ContentLength == -1 || c.Request.ContentLength > int64(maxSize) {
c.JSON(200, serializer.Err(serializer.CodeUploadFailed, "头像尺寸太大", nil))
return
}
// 取得上传的文件
file, err := c.FormFile("avatar")
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeIOFailed, "无法读取头像数据", err))
return
}
// 初始化头像
r, err := file.Open()
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeIOFailed, "无法读取头像数据", err))
return
}
avatar, err := thumb.NewThumbFromFile(r, file.Filename)
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeIOFailed, "无法解析图像数据", err))
return
}
// 创建头像
u := CurrentUser(c)
err = avatar.CreateAvatar(u.ID)
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeIOFailed, "无法创建头像", err))
return
}
// 保存头像标记
if err := u.Update(map[string]interface{}{
"avatar": "file",
}); err != nil {
c.JSON(200, serializer.Err(serializer.CodeDBError, "无法更新头像", err))
return
}
c.JSON(200, serializer.Response{})
}
// GetUserAvatar 获取用户头像
func GetUserAvatar(c *gin.Context) {
var service user.AvatarService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Get(c)
if res.Code == -301 {
// 重定向到gravatar
c.Redirect(301, res.Data.(string))
}
} else {
c.JSON(200, ErrorResponse(err))
}
}
// UpdateOption 更改用户设定
func UpdateOption(c *gin.Context) {
var service user.SettingUpdateService
if err := c.ShouldBindUri(&service); err == nil {
var (
subService user.OptionsChangeHandler
subErr error
)
switch service.Option {
case "nick":
subService = &user.ChangerNick{}
case "vip":
subService = &user.VIPUnsubscribe{}
case "qq":
subService = &user.QQBind{}
}
subErr = c.ShouldBindJSON(subService)
if subErr != nil {
c.JSON(200, ErrorResponse(subErr))
return
}
res := subService.Update(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
......@@ -131,3 +131,14 @@ func PayJSCallback(c *gin.Context) {
payNotify.SendResponseMsg()
}
// QQCallback QQ互联回调
func QQCallback(c *gin.Context) {
var service vas.QQCallbackService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Callback(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
......@@ -112,6 +112,11 @@ func InitMasterRouter() *gin.Engine {
middleware.HashID(hashid.UserID),
controllers.GetUserShare,
)
// 获取用户头像
user.GET("avatar/:id/:size",
middleware.HashID(hashid.UserID),
controllers.GetUserAvatar,
)
}
// 需要携带签名验证的
......@@ -132,6 +137,11 @@ func InitMasterRouter() *gin.Engine {
// 回调接口
callback := v3.Group("callback")
{
// QQ互联回调
callback.POST(
"qq",
controllers.QQCallback,
)
// PAYJS回调
callback.POST(
"payjs",
......@@ -266,6 +276,14 @@ func InitMasterRouter() *gin.Engine {
setting.GET("policies", controllers.UserAvailablePolicies)
// 任务队列
setting.GET("tasks", controllers.UserTasks)
// 获取当前用户设定
setting.GET("", controllers.UserSetting)
// 从文件上传头像
setting.POST("avatar", controllers.UploadAvatar)
// 设定为Gravatar头像
setting.PUT("avatar", controllers.UseGravatar)
// 更改用户设定
setting.PATCH(":option", controllers.UpdateOption)
}
}
......
package user
import (
"crypto/md5"
"fmt"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/qq"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/gin-gonic/gin"
"net/http"
"net/url"
"os"
"path/filepath"
"time"
)
// SettingService 通用设置服务
......@@ -15,6 +24,143 @@ type SettingListService struct {
Page int `form:"page" binding:"required,min=1"`
}
// AvatarService 头像服务
type AvatarService struct {
Size string `uri:"size" binding:"required,eq=l|eq=m|eq=s"`
}
// SettingUpdateService 设定更改服务
type SettingUpdateService struct {
Option string `uri:"option" binding:"required,eq=nick|eq=theme|eq=homepage|eq=vip|eq=qq"`
}
// OptionsChangeHandler 属性更改接口
type OptionsChangeHandler interface {
Update(*gin.Context, *model.User) serializer.Response
}
// ChangerNick 昵称更改服务
type ChangerNick struct {
Nick string `json:"nick" binding:"required,min=1,max=255"`
}
// VIPUnsubscribe 用户组解约服务
type VIPUnsubscribe struct {
}
// QQBind QQ互联服务
type QQBind struct {
}
// Update 绑定或解绑QQ
func (service *QQBind) Update(c *gin.Context, user *model.User) serializer.Response {
// 解除绑定
if user.OpenID != "" {
if err := user.Update(map[string]interface{}{"open_id": ""}); err != nil {
return serializer.DBErr("接触绑定失败", err)
}
return serializer.Response{}
}
// 新建绑定
res, err := qq.NewLoginRequest()
if err != nil {
return serializer.Err(serializer.CodeNotSet, "无法使用QQ登录", err)
}
// 设定QQ登录会话Secret
util.SetSession(c, map[string]interface{}{"qq_login_secret": res.SecretKey})
return serializer.Response{
Data: res.URL,
}
}
// Update 用户组解约
func (service *VIPUnsubscribe) Update(c *gin.Context, user *model.User) serializer.Response {
if user.GroupExpires != nil {
timeNow := time.Now()
if time.Now().Before(*user.GroupExpires) {
if err := user.Update(map[string]interface{}{"group_expires": &timeNow}); err != nil {
return serializer.DBErr("解约失败", err)
}
}
}
return serializer.Response{}
}
// Update 更改昵称
func (service *ChangerNick) Update(c *gin.Context, user *model.User) serializer.Response {
if err := user.Update(map[string]interface{}{"nick": service.Nick}); err != nil {
return serializer.DBErr("无法更新昵称", err)
}
return serializer.Response{}
}
// Get 获取用户头像
func (service *AvatarService) Get(c *gin.Context) serializer.Response {
// 查找目标用户
uid, _ := c.Get("object_id")
user, err := model.GetActiveUserByID(uid.(uint))
if err != nil {
return serializer.Err(serializer.CodeNotFound, "用户不存在", err)
}
// 未设定头像时,返回404错误
if user.Avatar == "" {
c.Status(404)
return serializer.Response{}
}
// 获取头像设置
sizes := map[string]string{
"s": model.GetSettingByName("avatar_size_s"),
"m": model.GetSettingByName("avatar_size_m"),
"l": model.GetSettingByName("avatar_size_l"),
}
// Gravatar 头像重定向
if user.Avatar == "gravatar" {
server := model.GetSettingByName("gravatar_server")
gravatarRoot, err := url.Parse(server)
if err != nil {
return serializer.Err(serializer.CodeInternalSetting, "无法解析 Gravatar 服务器地址", err)
}
has := md5.Sum([]byte(user.Email))
avatar, _ := url.Parse(fmt.Sprintf("/avatar/%x?d=mm&s=%s", has, sizes[service.Size]))
return serializer.Response{
Code: -301,
Data: gravatarRoot.ResolveReference(avatar).String(),
}
}
// 本地文件头像
if user.Avatar == "file" {
avatarRoot := model.GetSettingByName("avatar_path")
sizeToInt := map[string]string{
"s": "0",
"m": "1",
"l": "2",
}
avatar, err := os.Open(filepath.Join(avatarRoot, fmt.Sprintf("avatar_%d_%s.png", user.ID, sizeToInt[service.Size])))
if err != nil {
c.Status(404)
return serializer.Response{}
}
defer avatar.Close()
http.ServeContent(c.Writer, c.Request, "avatar.png", user.UpdatedAt, avatar)
return serializer.Response{}
}
c.Status(404)
return serializer.Response{}
}
// ListTasks 列出任务
func (service *SettingListService) ListTasks(c *gin.Context, user *model.User) serializer.Response {
tasks, total := model.ListTasks(user.ID, service.Page, 10, "updated_at desc")
......@@ -36,3 +182,30 @@ func (service *SettingService) Policy(c *gin.Context, user *model.User) serializ
return serializer.BuildPolicySettingRes(available, &current)
}
// Settings 获取用户设定
func (service *SettingService) Settings(c *gin.Context, user *model.User) serializer.Response {
// 取得存储策略设定
policy := service.Policy(c, user)
// 用户组有效期
var groupExpires int64
if user.GroupExpires != nil {
if expires := user.GroupExpires.Unix() - time.Now().Unix(); expires > 0 {
groupExpires = user.GroupExpires.Unix()
}
}
return serializer.Response{
Data: map[string]interface{}{
"policy": policy.Data.(map[string]interface{}),
"uid": user.ID,
"qq": user.OpenID != "",
"homepage": !user.OptionsSerialized.ProfileOff,
"two_factor": user.TwoFactor != "",
"prefer_theme": user.OptionsSerialized.PreferredTheme,
"themes": model.GetSettingByNames("themes"),
"group_expires": groupExpires,
},
}
}
package vas
import (
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/qq"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/gin-gonic/gin"
)
// QQCallbackService QQ互联回调处理服务
type QQCallbackService struct {
Code string `json:"code" binding:"required"`
State string `json:"state" binding:"required"`
}
// Callback 处理QQ互联回调
func (service *QQCallbackService) Callback(c *gin.Context, user *model.User) serializer.Response {
state := util.GetSession(c, "qq_login_secret")
if stateStr, ok := state.(string); !ok || stateStr != service.State {
return serializer.Err(serializer.CodeSignExpired, "请求过期,请重试", nil)
}
credential, err := qq.Callback(service.Code)
if err != nil {
return serializer.Err(serializer.CodeNotSet, "无法获取登录状态", err)
}
// 如果已登录,则绑定已有用户
if user != nil {
if user.OpenID != "" {
return serializer.Err(serializer.CodeCallbackError, "您已绑定了QQ账号,请先解除绑定", nil)
}
if err := user.Update(map[string]interface{}{"open_id": credential.OpenID}); err != nil {
return serializer.DBErr("绑定失败", err)
}
return serializer.Response{
Data: "/setting",
}
}
return serializer.Response{}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册