| @@ -0,0 +1,181 @@ | |||||
| package models | |||||
| import ( | |||||
| "code.gitea.io/gitea/modules/setting" | |||||
| "code.gitea.io/gitea/modules/timeutil" | |||||
| "path/filepath" | |||||
| "strings" | |||||
| "xorm.io/builder" | |||||
| ) | |||||
| type Badge struct { | |||||
| ID int64 `xorm:"pk autoincr"` | |||||
| Name string | |||||
| LightedIcon string `xorm:"varchar(2048)"` | |||||
| GreyedIcon string `xorm:"varchar(2048)"` | |||||
| Url string `xorm:"varchar(2048)"` | |||||
| CategoryId int64 | |||||
| CreatedUnix timeutil.TimeStamp `xorm:"created"` | |||||
| UpdatedUnix timeutil.TimeStamp `xorm:"updated"` | |||||
| DeletedAt timeutil.TimeStamp `xorm:"deleted"` | |||||
| } | |||||
| func (m *Badge) ToUserShow() *Badge4UserShow { | |||||
| return &Badge4UserShow{ | |||||
| Name: m.Name, | |||||
| LightedIcon: GetIconOuterLink(m.LightedIcon), | |||||
| GreyedIcon: GetIconOuterLink(m.GreyedIcon), | |||||
| Url: m.Url, | |||||
| } | |||||
| } | |||||
| type GetBadgeOpts struct { | |||||
| BadgeType BadgeType | |||||
| CategoryId int64 | |||||
| ListOpts ListOptions | |||||
| } | |||||
| type BadgeAndCategory struct { | |||||
| Badge Badge `xorm:"extends"` | |||||
| Category BadgeCategory `xorm:"extends"` | |||||
| } | |||||
| func (*BadgeAndCategory) TableName() string { | |||||
| return "badge" | |||||
| } | |||||
| func (m *BadgeAndCategory) ToShow() *Badge4AdminShow { | |||||
| return &Badge4AdminShow{ | |||||
| ID: m.Badge.ID, | |||||
| Name: m.Badge.Name, | |||||
| LightedIcon: GetIconOuterLink(m.Badge.LightedIcon), | |||||
| GreyedIcon: GetIconOuterLink(m.Badge.GreyedIcon), | |||||
| Url: m.Badge.Url, | |||||
| CategoryName: m.Category.Name, | |||||
| CategoryId: m.Category.ID, | |||||
| CreatedUnix: m.Badge.CreatedUnix, | |||||
| UpdatedUnix: m.Badge.UpdatedUnix, | |||||
| } | |||||
| } | |||||
| type Badge4AdminShow struct { | |||||
| ID int64 | |||||
| Name string | |||||
| LightedIcon string | |||||
| GreyedIcon string | |||||
| Url string | |||||
| CategoryName string | |||||
| CategoryId int64 | |||||
| CreatedUnix timeutil.TimeStamp | |||||
| UpdatedUnix timeutil.TimeStamp | |||||
| } | |||||
| func (m Badge4AdminShow) ToDTO() Badge { | |||||
| return Badge{ | |||||
| Name: m.Name, | |||||
| LightedIcon: m.LightedIcon, | |||||
| GreyedIcon: m.GreyedIcon, | |||||
| Url: m.Url, | |||||
| CategoryId: m.CategoryId, | |||||
| } | |||||
| } | |||||
| type BadgeOperateReq struct { | |||||
| ID int64 | |||||
| Name string | |||||
| LightedIcon string | |||||
| GreyedIcon string | |||||
| Url string | |||||
| CategoryId int64 | |||||
| } | |||||
| func (m BadgeOperateReq) ToDTO() Badge { | |||||
| return Badge{ | |||||
| Name: m.Name, | |||||
| LightedIcon: m.LightedIcon, | |||||
| GreyedIcon: m.GreyedIcon, | |||||
| Url: m.Url, | |||||
| CategoryId: m.CategoryId, | |||||
| } | |||||
| } | |||||
| type Badge4UserShow struct { | |||||
| Name string | |||||
| LightedIcon string | |||||
| GreyedIcon string | |||||
| Url string | |||||
| } | |||||
| type BadgeShowWithStatus struct { | |||||
| Badge *Badge4UserShow | |||||
| IsLighted bool | |||||
| } | |||||
| type UserAllBadgeInCategory struct { | |||||
| CategoryName string | |||||
| CategoryId int64 | |||||
| LightedNum int | |||||
| Badges []*BadgeShowWithStatus | |||||
| } | |||||
| func GetBadgeList(opts GetBadgeOpts) (int64, []*BadgeAndCategory, error) { | |||||
| if opts.ListOpts.Page <= 0 { | |||||
| opts.ListOpts.Page = 1 | |||||
| } | |||||
| var cond = builder.NewCond() | |||||
| if opts.BadgeType > 0 { | |||||
| cond = cond.And(builder.Eq{"badge_category.type": opts.BadgeType}) | |||||
| } | |||||
| if opts.CategoryId > 0 { | |||||
| cond = cond.And(builder.Eq{"badge_category.id": opts.CategoryId}) | |||||
| } | |||||
| n, err := x.Join("INNER", "badge_category", "badge_category.ID = badge.category_id").Where(cond).Count(&BadgeAndCategory{}) | |||||
| if err != nil { | |||||
| return 0, nil, err | |||||
| } | |||||
| r := make([]*BadgeAndCategory, 0) | |||||
| if err = x.Join("INNER", "badge_category", "badge_category.ID = badge.category_id").Where(cond).OrderBy("badge.created_unix desc").Limit(opts.ListOpts.PageSize, (opts.ListOpts.Page-1)*opts.ListOpts.PageSize).Find(&r); err != nil { | |||||
| return 0, nil, err | |||||
| } | |||||
| return n, r, nil | |||||
| } | |||||
| func AddBadge(m Badge) (int64, error) { | |||||
| return x.Insert(&m) | |||||
| } | |||||
| func UpdateBadgeById(id int64, param Badge) (int64, error) { | |||||
| return x.ID(id).Update(¶m) | |||||
| } | |||||
| func DelBadge(id int64) (int64, error) { | |||||
| return x.ID(id).Delete(&Badge{}) | |||||
| } | |||||
| func GetBadgeById(id int64) (*Badge, error) { | |||||
| m := &Badge{} | |||||
| has, err := x.ID(id).Get(m) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } else if !has { | |||||
| return nil, &ErrRecordNotExist{} | |||||
| } | |||||
| return m, nil | |||||
| } | |||||
| func GetBadgeByCategoryId(categoryId int64) ([]*Badge, error) { | |||||
| r := make([]*Badge, 0) | |||||
| err := x.Where("category_id = ?", categoryId).Find(&r) | |||||
| return r, err | |||||
| } | |||||
| func GetCustomIconByHash(hash string) string { | |||||
| if len(hash) == 0 { | |||||
| return "" | |||||
| } | |||||
| return filepath.Join(setting.IconUploadPath, hash) | |||||
| } | |||||
| func GetIconOuterLink(hash string) string { | |||||
| return strings.TrimRight(setting.AppSubURL, "/") + "/show/icon/" + hash | |||||
| } | |||||
| @@ -0,0 +1,94 @@ | |||||
| package models | |||||
| import "code.gitea.io/gitea/modules/timeutil" | |||||
| type BadgeType int | |||||
| const ( | |||||
| CustomizeBadge = iota + 1 | |||||
| SystemBadge | |||||
| ) | |||||
| type BadgeCategory struct { | |||||
| ID int64 `xorm:"pk autoincr"` | |||||
| Name string | |||||
| Position int64 | |||||
| Type BadgeType | |||||
| CreatedUnix timeutil.TimeStamp `xorm:"created"` | |||||
| UpdatedUnix timeutil.TimeStamp `xorm:"updated"` | |||||
| DeletedAt timeutil.TimeStamp `xorm:"deleted"` | |||||
| } | |||||
| func (m *BadgeCategory) ToShow() *BadgeCategory4Show { | |||||
| return &BadgeCategory4Show{ | |||||
| ID: m.ID, | |||||
| Name: m.Name, | |||||
| Position: m.Position, | |||||
| Type: m.Type, | |||||
| CreatedUnix: m.CreatedUnix, | |||||
| } | |||||
| } | |||||
| type BadgeCategory4Show struct { | |||||
| ID int64 `xorm:"pk autoincr"` | |||||
| Name string | |||||
| Position int64 | |||||
| Type BadgeType | |||||
| CreatedUnix timeutil.TimeStamp `xorm:"created"` | |||||
| } | |||||
| func (m BadgeCategory4Show) ToDTO() BadgeCategory { | |||||
| return BadgeCategory{ | |||||
| ID: m.ID, | |||||
| Name: m.Name, | |||||
| Position: m.Position, | |||||
| Type: m.Type, | |||||
| CreatedUnix: m.CreatedUnix, | |||||
| } | |||||
| } | |||||
| func GetBadgeCategoryListPaging(opts ListOptions) (int64, []*BadgeCategory, error) { | |||||
| n, err := x.Count(&BadgeCategory{}) | |||||
| if err != nil { | |||||
| return 0, nil, err | |||||
| } | |||||
| if opts.Page <= 0 { | |||||
| opts.Page = 1 | |||||
| } | |||||
| r := make([]*BadgeCategory, 0) | |||||
| if err := x.OrderBy("position asc,created_unix desc").Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Find(&r); err != nil { | |||||
| return 0, nil, err | |||||
| } | |||||
| return n, r, nil | |||||
| } | |||||
| func GetBadgeCategoryList() ([]*BadgeCategory, error) { | |||||
| r := make([]*BadgeCategory, 0) | |||||
| if err := x.OrderBy("position asc,created_unix desc").Find(&r); err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return r, nil | |||||
| } | |||||
| func AddBadgeCategory(m BadgeCategory) (int64, error) { | |||||
| return x.Insert(&m) | |||||
| } | |||||
| func UpdateBadgeCategoryById(id int64, param BadgeCategory) (int64, error) { | |||||
| return x.ID(id).Update(¶m) | |||||
| } | |||||
| func DelBadgeCategory(id int64) (int64, error) { | |||||
| return x.ID(id).Delete(&BadgeCategory{}) | |||||
| } | |||||
| func GetBadgeCategoryById(id int64) (*BadgeCategory, error) { | |||||
| m := &BadgeCategory{} | |||||
| has, err := x.ID(id).Get(m) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } else if !has { | |||||
| return nil, ErrRecordNotExist{} | |||||
| } | |||||
| return m, nil | |||||
| } | |||||
| @@ -0,0 +1,159 @@ | |||||
| package models | |||||
| import ( | |||||
| "code.gitea.io/gitea/modules/timeutil" | |||||
| "xorm.io/builder" | |||||
| ) | |||||
| const ( | |||||
| ActionAddBadgeUser = 1 | |||||
| ActionDelBadgeUser = 2 | |||||
| ) | |||||
| type BadgeUser struct { | |||||
| ID int64 `xorm:"pk autoincr"` | |||||
| UserId int64 `xorm:"unique(user_badge)"` | |||||
| BadgeId int64 `xorm:"unique(user_badge) index"` | |||||
| CreatedUnix timeutil.TimeStamp `xorm:"created index"` | |||||
| } | |||||
| type BadgeUserLog struct { | |||||
| ID int64 `xorm:"pk autoincr"` | |||||
| UserId int64 `xorm:"index"` | |||||
| BadgeId int64 `xorm:"index"` | |||||
| Action int | |||||
| CreatedUnix timeutil.TimeStamp `xorm:"created index"` | |||||
| } | |||||
| type BadgeUserDetail struct { | |||||
| BadgeUser BadgeUser `xorm:"extends"` | |||||
| User User `xorm:"extends"` | |||||
| } | |||||
| func (*BadgeUserDetail) TableName() string { | |||||
| return "badge_user" | |||||
| } | |||||
| func (m *BadgeUserDetail) ToShow() *BadgeUser4SHow { | |||||
| return &BadgeUser4SHow{ | |||||
| ID: m.BadgeUser.ID, | |||||
| UserId: m.BadgeUser.UserId, | |||||
| Name: m.User.Name, | |||||
| Avatar: m.User.RelAvatarLink(), | |||||
| Email: m.User.Email, | |||||
| CreatedUnix: m.BadgeUser.CreatedUnix, | |||||
| } | |||||
| } | |||||
| type BadgeUser4SHow struct { | |||||
| ID int64 | |||||
| UserId int64 | |||||
| Name string | |||||
| Avatar string | |||||
| Email string | |||||
| CreatedUnix timeutil.TimeStamp | |||||
| } | |||||
| type AddBadgeUsersReq struct { | |||||
| BadgeId int64 | |||||
| Users string | |||||
| } | |||||
| type DelBadgeUserReq struct { | |||||
| ID int64 | |||||
| } | |||||
| type GetUserBadgesOpts struct { | |||||
| CategoryId int64 | |||||
| ListOptions | |||||
| } | |||||
| func AddBadgeUser(m BadgeUser) (int64, error) { | |||||
| sess := x.NewSession() | |||||
| defer sess.Close() | |||||
| sess.Begin() | |||||
| n, err := sess.Insert(&m) | |||||
| if err != nil || n == 0 { | |||||
| return 0, err | |||||
| } | |||||
| _, err = sess.Insert(&BadgeUserLog{ | |||||
| UserId: m.UserId, | |||||
| BadgeId: m.BadgeId, | |||||
| Action: ActionAddBadgeUser, | |||||
| }) | |||||
| if err != nil { | |||||
| sess.Rollback() | |||||
| return 0, err | |||||
| } | |||||
| return n, sess.Commit() | |||||
| } | |||||
| func DelBadgeUser(id int64) (int64, error) { | |||||
| m := BadgeUser{} | |||||
| has, err := x.ID(id).Get(&m) | |||||
| if err != nil { | |||||
| return 0, err | |||||
| } | |||||
| if !has { | |||||
| return 0, ErrRecordNotExist{} | |||||
| } | |||||
| sess := x.NewSession() | |||||
| defer sess.Close() | |||||
| sess.Begin() | |||||
| n, err := x.ID(m.ID).Delete(&BadgeUser{}) | |||||
| if err != nil || n == 0 { | |||||
| return 0, err | |||||
| } | |||||
| _, err = sess.Insert(&BadgeUserLog{ | |||||
| UserId: m.UserId, | |||||
| BadgeId: m.BadgeId, | |||||
| Action: ActionDelBadgeUser, | |||||
| }) | |||||
| if err != nil { | |||||
| sess.Rollback() | |||||
| return 0, err | |||||
| } | |||||
| return n, sess.Commit() | |||||
| } | |||||
| func GetBadgeUsers(badgeId int64, opts ListOptions) (int64, []BadgeUserDetail, error) { | |||||
| n, err := x.Join("LEFT", "public.user", "public.user.ID = badge_user.user_id").Where("badge_user.badge_id = ?", badgeId).Count(&BadgeUserDetail{}) | |||||
| if err != nil { | |||||
| return 0, nil, err | |||||
| } | |||||
| if opts.Page <= 0 { | |||||
| opts.Page = 1 | |||||
| } | |||||
| m := make([]BadgeUserDetail, 0) | |||||
| err = x.Join("LEFT", "public.user", "public.user.ID = badge_user.user_id").Where("badge_user.badge_id = ?", badgeId).OrderBy("badge_user.id desc").Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Find(&m) | |||||
| if err != nil { | |||||
| return 0, nil, err | |||||
| } | |||||
| return n, m, nil | |||||
| } | |||||
| func GetUserBadgesPaging(userId int64, opts GetUserBadgesOpts) ([]*Badge, error) { | |||||
| cond := builder.NewCond() | |||||
| cond = cond.And(builder.Eq{"badge_user.user_id": userId}) | |||||
| if opts.CategoryId > 0 { | |||||
| cond = cond.And(builder.Eq{"badge.category_id": opts.CategoryId}) | |||||
| } | |||||
| r := make([]*Badge, 0) | |||||
| err := x.Join("INNER", "badge_user", "badge_user.badge_id = badge.id").Where(cond).OrderBy("badge_user.id desc").Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Find(&r) | |||||
| return r, err | |||||
| } | |||||
| func CountUserBadges(userId int64) (int64, error) { | |||||
| return x.Where("user_id = ?", userId).Count(&BadgeUser{}) | |||||
| } | |||||
| func GetUserBadges(userId, categoryId int64) ([]*Badge, error) { | |||||
| cond := builder.NewCond() | |||||
| cond = cond.And(builder.Eq{"badge_user.user_id": userId}) | |||||
| if categoryId > 0 { | |||||
| cond = cond.And(builder.Eq{"badge.category_id": categoryId}) | |||||
| } | |||||
| r := make([]*Badge, 0) | |||||
| err := x.Join("INNER", "badge_user", "badge_user.badge_id = badge.id").Where(cond).OrderBy("badge_user.created_unix desc").Find(&r) | |||||
| return r, err | |||||
| } | |||||
| @@ -161,6 +161,10 @@ func init() { | |||||
| new(CloudbrainSpec), | new(CloudbrainSpec), | ||||
| new(CloudbrainTemp), | new(CloudbrainTemp), | ||||
| new(DatasetReference), | new(DatasetReference), | ||||
| new(BadgeCategory), | |||||
| new(Badge), | |||||
| new(BadgeUser), | |||||
| new(BadgeUserLog), | |||||
| ) | ) | ||||
| tablesStatistic = append(tablesStatistic, | tablesStatistic = append(tablesStatistic, | ||||
| @@ -2184,3 +2184,24 @@ func GetBlockChainUnSuccessUsers() ([]*User, error) { | |||||
| Find(&users) | Find(&users) | ||||
| return users, err | return users, err | ||||
| } | } | ||||
| //GetUserIdsByUserNames Get userIDs in batches through username paging, this method will ignore errors | |||||
| func GetUserIdsByUserNames(names []string) []int64 { | |||||
| pageSize := 200 | |||||
| length := len(names) | |||||
| r := make([]int64, 0, length) | |||||
| for i := 0; i < length; i = i + pageSize { | |||||
| if length-i < 200 { | |||||
| pageSize = length - i | |||||
| } | |||||
| userNameTemp := names[i : i+pageSize] | |||||
| t := make([]int64, 0, length) | |||||
| err := x.Table("public.user").Cols("id").In("name", userNameTemp).Find(&t) | |||||
| if err != nil { | |||||
| continue | |||||
| } | |||||
| r = append(r, t...) | |||||
| } | |||||
| return r | |||||
| } | |||||
| @@ -622,6 +622,13 @@ var ( | |||||
| DeductTaskRange time.Duration | DeductTaskRange time.Duration | ||||
| DeductTaskRangeForFirst time.Duration | DeductTaskRangeForFirst time.Duration | ||||
| //badge config | |||||
| BadgeIconMaxFileSize int64 | |||||
| BadgeIconMaxWidth int | |||||
| BadgeIconMaxHeight int | |||||
| BadgeIconDefaultSize uint | |||||
| IconUploadPath string | |||||
| //wechat auto reply config | //wechat auto reply config | ||||
| UserNameOfWechatReply string | UserNameOfWechatReply string | ||||
| RepoNameOfWechatReply string | RepoNameOfWechatReply string | ||||
| @@ -1531,6 +1538,14 @@ func NewContext() { | |||||
| CloudBrainPayInterval = sec.Key("CLOUDBRAIN_PAY_INTERVAL").MustDuration(60 * time.Minute) | CloudBrainPayInterval = sec.Key("CLOUDBRAIN_PAY_INTERVAL").MustDuration(60 * time.Minute) | ||||
| DeductTaskRange = sec.Key("DEDUCT_TASK_RANGE").MustDuration(30 * time.Minute) | DeductTaskRange = sec.Key("DEDUCT_TASK_RANGE").MustDuration(30 * time.Minute) | ||||
| DeductTaskRangeForFirst = sec.Key("DEDUCT_TASK_RANGE_FOR_FIRST").MustDuration(3 * time.Hour) | DeductTaskRangeForFirst = sec.Key("DEDUCT_TASK_RANGE_FOR_FIRST").MustDuration(3 * time.Hour) | ||||
| sec = Cfg.Section("icons") | |||||
| BadgeIconMaxFileSize = sec.Key("BADGE_ICON_MAX_FILE_SIZE").MustInt64(1048576) | |||||
| BadgeIconMaxWidth = sec.Key("BADGE_ICON_MAX_WIDTH").MustInt(4096) | |||||
| BadgeIconMaxHeight = sec.Key("BADGE_ICON_MAX_HEIGHT").MustInt(3072) | |||||
| BadgeIconDefaultSize = sec.Key("BADGE_ICON_DEFAULT_SIZE").MustUint(200) | |||||
| IconUploadPath = sec.Key("ICON_UPLOAD_PATH").MustString(path.Join(AppDataPath, "icons")) | |||||
| SetRadarMapConfig() | SetRadarMapConfig() | ||||
| sec = Cfg.Section("warn_mail") | sec = Cfg.Section("warn_mail") | ||||
| @@ -525,6 +525,7 @@ datasets = Datasets | |||||
| activity = Public Activity | activity = Public Activity | ||||
| followers = Followers | followers = Followers | ||||
| starred = Starred Repositories | starred = Starred Repositories | ||||
| badge = Achievement Badge | |||||
| following = Following | following = Following | ||||
| follow = Follow | follow = Follow | ||||
| unfollow = Unfollow | unfollow = Unfollow | ||||
| @@ -530,6 +530,7 @@ datasets=数据集 | |||||
| activity=公开活动 | activity=公开活动 | ||||
| followers=关注者 | followers=关注者 | ||||
| starred=已点赞 | starred=已点赞 | ||||
| badge=成就徽章 | |||||
| following=关注中 | following=关注中 | ||||
| follow=关注 | follow=关注 | ||||
| unfollow=取消关注 | unfollow=取消关注 | ||||
| @@ -0,0 +1,136 @@ | |||||
| package badge | |||||
| import ( | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/context" | |||||
| "code.gitea.io/gitea/modules/log" | |||||
| "code.gitea.io/gitea/modules/setting" | |||||
| "code.gitea.io/gitea/routers/response" | |||||
| "code.gitea.io/gitea/services/badge" | |||||
| "errors" | |||||
| "github.com/unknwon/com" | |||||
| "net/http" | |||||
| "strings" | |||||
| ) | |||||
| func GetCustomizeBadgeList(ctx *context.Context) { | |||||
| page := ctx.QueryInt("page") | |||||
| category := ctx.QueryInt64("category") | |||||
| pageSize := 50 | |||||
| n, r, err := badge.GetBadgeList(models.GetBadgeOpts{CategoryId: category, BadgeType: models.CustomizeBadge, ListOpts: models.ListOptions{PageSize: pageSize, Page: page}}) | |||||
| if err != nil { | |||||
| log.Error("GetCustomizeBadgeList error.%v", err) | |||||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||||
| return | |||||
| } | |||||
| m := make(map[string]interface{}) | |||||
| m["List"] = r | |||||
| m["Total"] = n | |||||
| m["PageSize"] = pageSize | |||||
| ctx.JSON(http.StatusOK, response.SuccessWithData(m)) | |||||
| } | |||||
| func OperateBadge(ctx *context.Context, req models.BadgeOperateReq) { | |||||
| action := ctx.Params(":action") | |||||
| var err *response.BizError | |||||
| switch action { | |||||
| case "edit": | |||||
| err = badge.EditBadge(req, ctx.User) | |||||
| case "new": | |||||
| err = badge.AddBadge(req, ctx.User) | |||||
| case "del": | |||||
| err = badge.DelBadge(req.ID, ctx.User) | |||||
| default: | |||||
| err = response.NewBizError(errors.New("action type error")) | |||||
| } | |||||
| if err != nil { | |||||
| log.Error("OperateBadge error ,%v", err) | |||||
| ctx.JSON(http.StatusOK, response.ResponseError(err)) | |||||
| return | |||||
| } | |||||
| ctx.JSON(http.StatusOK, response.Success()) | |||||
| } | |||||
| func GetBadgeUsers(ctx *context.Context) { | |||||
| page := ctx.QueryInt("page") | |||||
| badgeId := ctx.QueryInt64("badge") | |||||
| pageSize := 50 | |||||
| n, r, err := badge.GetBadgeUsers(badgeId, models.ListOptions{PageSize: pageSize, Page: page}) | |||||
| if err != nil { | |||||
| log.Error("GetBadgeUsers error.%v", err) | |||||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||||
| return | |||||
| } | |||||
| m := make(map[string]interface{}) | |||||
| m["List"] = r | |||||
| m["Total"] = n | |||||
| m["PageSize"] = pageSize | |||||
| ctx.JSON(http.StatusOK, response.SuccessWithData(m)) | |||||
| } | |||||
| func AddOperateBadgeUsers(ctx *context.Context, req models.AddBadgeUsersReq) { | |||||
| userStr := req.Users | |||||
| if userStr == "" { | |||||
| ctx.JSON(http.StatusOK, response.Success()) | |||||
| return | |||||
| } | |||||
| userStr = strings.ReplaceAll(userStr, " ", "") | |||||
| userStr = strings.ReplaceAll(userStr, "\r", "") | |||||
| userNames := strings.Split(userStr, "\n") | |||||
| n, err := badge.AddBadgeUsers(req.BadgeId, userNames) | |||||
| if err != nil { | |||||
| log.Error("AddOperateBadgeUsers error.%v", err) | |||||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||||
| return | |||||
| } | |||||
| m := make(map[string]interface{}) | |||||
| m["Total"] = len(userNames) | |||||
| m["Success"] = n | |||||
| ctx.JSON(http.StatusOK, response.SuccessWithData(m)) | |||||
| } | |||||
| func DelBadgeUsers(ctx *context.Context, req models.DelBadgeUserReq) { | |||||
| id := req.ID | |||||
| if id <= 0 { | |||||
| ctx.JSON(http.StatusOK, response.Success()) | |||||
| return | |||||
| } | |||||
| err := badge.DelBadgeUser(id) | |||||
| if err != nil { | |||||
| log.Error("DelBadgeUsers error.%v", err) | |||||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||||
| return | |||||
| } | |||||
| ctx.JSON(http.StatusOK, response.Success()) | |||||
| } | |||||
| func UploadIcon(ctx *context.Context, form badge.IconUploadForm) { | |||||
| uploader := badge.NewIconUploader(badge.IconUploadConfig{ | |||||
| FileMaxSize: setting.BadgeIconMaxFileSize, | |||||
| FileMaxWidth: setting.BadgeIconMaxWidth, | |||||
| FileMaxHeight: setting.BadgeIconMaxHeight, | |||||
| NeedSquare: true, | |||||
| }) | |||||
| iconName, err := uploader.Upload(form, ctx.User) | |||||
| if err != nil { | |||||
| log.Error("UploadIcon error.%v", err) | |||||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||||
| return | |||||
| } | |||||
| m := make(map[string]string, 0) | |||||
| m["IconName"] = iconName | |||||
| ctx.JSON(http.StatusOK, response.SuccessWithData(m)) | |||||
| } | |||||
| func GetIcon(ctx *context.Context) { | |||||
| hash := ctx.Params(":hash") | |||||
| if !com.IsFile(models.GetCustomIconByHash(hash)) { | |||||
| ctx.NotFound(ctx.Req.URL.RequestURI(), nil) | |||||
| return | |||||
| } | |||||
| ctx.Redirect(setting.AppSubURL + "/icons/" + hash) | |||||
| } | |||||
| @@ -0,0 +1,50 @@ | |||||
| package badge | |||||
| import ( | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/context" | |||||
| "code.gitea.io/gitea/modules/log" | |||||
| "code.gitea.io/gitea/routers/response" | |||||
| "code.gitea.io/gitea/services/badge" | |||||
| "errors" | |||||
| "net/http" | |||||
| ) | |||||
| func GetBadgeCategoryList(ctx *context.Context) { | |||||
| page := ctx.QueryInt("page") | |||||
| pageSize := 50 | |||||
| n, r, err := badge.GetBadgeCategoryList(models.ListOptions{Page: page, PageSize: pageSize}) | |||||
| if err != nil { | |||||
| log.Error("GetCategoryList error.%v", err) | |||||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||||
| return | |||||
| } | |||||
| m := make(map[string]interface{}) | |||||
| m["List"] = r | |||||
| m["Total"] = n | |||||
| m["PageSize"] = pageSize | |||||
| ctx.JSON(http.StatusOK, response.SuccessWithData(m)) | |||||
| } | |||||
| func OperateBadgeCategory(ctx *context.Context, category models.BadgeCategory4Show) { | |||||
| action := ctx.Params(":action") | |||||
| var err *response.BizError | |||||
| switch action { | |||||
| case "edit": | |||||
| err = badge.EditBadgeCategory(category, ctx.User) | |||||
| case "new": | |||||
| err = badge.AddBadgeCategory(category, ctx.User) | |||||
| case "del": | |||||
| err = badge.DelBadgeCategory(category.ID, ctx.User) | |||||
| default: | |||||
| err = response.NewBizError(errors.New("action type error")) | |||||
| } | |||||
| if err != nil { | |||||
| log.Error("OperateBadgeCategory error ,%v", err) | |||||
| ctx.JSON(http.StatusOK, response.ResponseError(err)) | |||||
| return | |||||
| } | |||||
| ctx.JSON(http.StatusOK, response.Success()) | |||||
| } | |||||
| @@ -3,3 +3,6 @@ package response | |||||
| var RESOURCE_QUEUE_NOT_AVAILABLE = &BizError{Code: 1001, Err: "resource queue not available"} | var RESOURCE_QUEUE_NOT_AVAILABLE = &BizError{Code: 1001, Err: "resource queue not available"} | ||||
| var SPECIFICATION_NOT_EXIST = &BizError{Code: 1002, Err: "specification not exist"} | var SPECIFICATION_NOT_EXIST = &BizError{Code: 1002, Err: "specification not exist"} | ||||
| var SPECIFICATION_NOT_AVAILABLE = &BizError{Code: 1003, Err: "specification not available"} | var SPECIFICATION_NOT_AVAILABLE = &BizError{Code: 1003, Err: "specification not available"} | ||||
| var CATEGORY_STILL_HAS_BADGES = &BizError{Code: 1004, Err: "Please delete badges in the category first"} | |||||
| var BADGES_STILL_HAS_USERS = &BizError{Code: 1005, Err: "Please delete users of badge first"} | |||||
| @@ -6,6 +6,11 @@ package routes | |||||
| import ( | import ( | ||||
| "bytes" | "bytes" | ||||
| "code.gitea.io/gitea/routers/badge" | |||||
| "code.gitea.io/gitea/routers/reward/point" | |||||
| "code.gitea.io/gitea/routers/task" | |||||
| badge_service "code.gitea.io/gitea/services/badge" | |||||
| "code.gitea.io/gitea/services/reward" | |||||
| "encoding/gob" | "encoding/gob" | ||||
| "net/http" | "net/http" | ||||
| "path" | "path" | ||||
| @@ -13,9 +18,6 @@ import ( | |||||
| "time" | "time" | ||||
| "code.gitea.io/gitea/routers/modelapp" | "code.gitea.io/gitea/routers/modelapp" | ||||
| "code.gitea.io/gitea/routers/reward/point" | |||||
| "code.gitea.io/gitea/routers/task" | |||||
| "code.gitea.io/gitea/services/reward" | |||||
| "code.gitea.io/gitea/modules/slideimage" | "code.gitea.io/gitea/modules/slideimage" | ||||
| @@ -195,6 +197,14 @@ func NewMacaron() *macaron.Macaron { | |||||
| ExpiresAfter: setting.StaticCacheTime, | ExpiresAfter: setting.StaticCacheTime, | ||||
| }, | }, | ||||
| )) | )) | ||||
| m.Use(public.StaticHandler( | |||||
| setting.IconUploadPath, | |||||
| &public.Options{ | |||||
| Prefix: "icons", | |||||
| SkipLogging: setting.DisableRouterLog, | |||||
| ExpiresAfter: setting.StaticCacheTime, | |||||
| }, | |||||
| )) | |||||
| m.Use(public.StaticHandler( | m.Use(public.StaticHandler( | ||||
| setting.RepositoryAvatarUploadPath, | setting.RepositoryAvatarUploadPath, | ||||
| &public.Options{ | &public.Options{ | ||||
| @@ -521,6 +531,8 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
| m.Get("/avatar/:hash", user.AvatarByEmailHash) | m.Get("/avatar/:hash", user.AvatarByEmailHash) | ||||
| m.Get("/show/icon/:hash", badge.GetIcon) | |||||
| adminReq := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true}) | adminReq := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true}) | ||||
| // ***** START: Admin ***** | // ***** START: Admin ***** | ||||
| @@ -666,6 +678,23 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
| m.Post("/add/batch", bindIgnErr(models.BatchLimitConfigVO{}), task.BatchAddTaskConfig) | m.Post("/add/batch", bindIgnErr(models.BatchLimitConfigVO{}), task.BatchAddTaskConfig) | ||||
| m.Post("/^:action(new|edit|del)$", bindIgnErr(models.TaskConfigWithLimit{}), task.OperateTaskConfig) | m.Post("/^:action(new|edit|del)$", bindIgnErr(models.TaskConfigWithLimit{}), task.OperateTaskConfig) | ||||
| }) | }) | ||||
| m.Group("/badge", func() { | |||||
| m.Group("/category", func() { | |||||
| m.Get("/list", badge.GetBadgeCategoryList) | |||||
| m.Post("/^:action(new|edit|del)$", bindIgnErr(models.BadgeCategory4Show{}), badge.OperateBadgeCategory) | |||||
| }) | |||||
| m.Group("/customize", func() { | |||||
| m.Get("/list", badge.GetCustomizeBadgeList) | |||||
| }) | |||||
| m.Group("/users", func() { | |||||
| m.Get("", badge.GetBadgeUsers) | |||||
| m.Post("/add", bindIgnErr(models.AddBadgeUsersReq{}), badge.AddOperateBadgeUsers) | |||||
| m.Post("/del", bindIgnErr(models.DelBadgeUserReq{}), badge.DelBadgeUsers) | |||||
| }) | |||||
| m.Post("/^:action(new|edit|del)$", bindIgnErr(models.BadgeOperateReq{}), badge.OperateBadge) | |||||
| }) | |||||
| m.Post("/icon/upload", bindIgnErr(badge_service.IconUploadForm{}), badge.UploadIcon) | |||||
| }, operationReq) | }, operationReq) | ||||
| // ***** END: Operation ***** | // ***** END: Operation ***** | ||||
| @@ -6,6 +6,7 @@ | |||||
| package user | package user | ||||
| import ( | import ( | ||||
| "code.gitea.io/gitea/services/badge" | |||||
| "errors" | "errors" | ||||
| "fmt" | "fmt" | ||||
| "path" | "path" | ||||
| @@ -90,10 +91,25 @@ func Profile(ctx *context.Context) { | |||||
| return | return | ||||
| } | } | ||||
| // Show user badges | |||||
| badges, err := badge.GetUserBadges(ctxUser.ID, models.ListOptions{Page: 1, PageSize: 5}) | |||||
| if err != nil { | |||||
| ctx.ServerError("GetUserBadges", err) | |||||
| return | |||||
| } | |||||
| // Count user badges | |||||
| cnt, err := badge.CountUserBadges(ctxUser.ID) | |||||
| if err != nil { | |||||
| ctx.ServerError("CountUserBadges", err) | |||||
| return | |||||
| } | |||||
| ctx.Data["Title"] = ctxUser.DisplayName() | ctx.Data["Title"] = ctxUser.DisplayName() | ||||
| ctx.Data["PageIsUserProfile"] = true | ctx.Data["PageIsUserProfile"] = true | ||||
| ctx.Data["Owner"] = ctxUser | ctx.Data["Owner"] = ctxUser | ||||
| ctx.Data["OpenIDs"] = openIDs | ctx.Data["OpenIDs"] = openIDs | ||||
| ctx.Data["RecentBadges"] = badges | |||||
| ctx.Data["TotalBadges"] = cnt | |||||
| ctx.Data["EnableHeatmap"] = setting.Service.EnableUserHeatmap | ctx.Data["EnableHeatmap"] = setting.Service.EnableUserHeatmap | ||||
| ctx.Data["HeatmapUser"] = ctxUser.Name | ctx.Data["HeatmapUser"] = ctxUser.Name | ||||
| showPrivate := ctx.IsSigned && (ctx.User.IsAdmin || ctx.User.ID == ctxUser.ID) | showPrivate := ctx.IsSigned && (ctx.User.IsAdmin || ctx.User.ID == ctxUser.ID) | ||||
| @@ -297,6 +313,13 @@ func Profile(ctx *context.Context) { | |||||
| } | } | ||||
| total = int(count) | total = int(count) | ||||
| case "badge": | |||||
| allBadges, err := badge.GetUserAllBadges(ctxUser.ID) | |||||
| if err != nil { | |||||
| ctx.ServerError("GetUserAllBadges", err) | |||||
| return | |||||
| } | |||||
| ctx.Data["AllBadges"] = allBadges | |||||
| default: | default: | ||||
| ctx.ServerError("tab error", errors.New("tab error")) | ctx.ServerError("tab error", errors.New("tab error")) | ||||
| return | return | ||||
| @@ -4,6 +4,13 @@ import ( | |||||
| "code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
| ) | ) | ||||
| type LogBizType string | |||||
| const ( | |||||
| BadgeCategoryOperate LogBizType = "BadgeCategoryOperate" | |||||
| BadgeOperate LogBizType = "BadgeOperate" | |||||
| ) | |||||
| func Log(log models.AdminOperateLog) error { | func Log(log models.AdminOperateLog) error { | ||||
| _, err := models.InsertAdminOperateLog(log) | _, err := models.InsertAdminOperateLog(log) | ||||
| return err | return err | ||||
| @@ -12,3 +19,34 @@ func Log(log models.AdminOperateLog) error { | |||||
| func NewLogValues() *models.LogValues { | func NewLogValues() *models.LogValues { | ||||
| return &models.LogValues{Params: make([]models.LogValue, 0)} | return &models.LogValues{Params: make([]models.LogValue, 0)} | ||||
| } | } | ||||
| func Log4Add(bizType LogBizType, newValue interface{}, doerId int64, comment string) { | |||||
| Log(models.AdminOperateLog{ | |||||
| BizType: string(bizType), | |||||
| OperateType: "add", | |||||
| NewValue: NewLogValues().Add("new", newValue).JsonString(), | |||||
| CreatedBy: doerId, | |||||
| Comment: comment, | |||||
| }) | |||||
| } | |||||
| func Log4Edit(bizType LogBizType, oldValue interface{}, newValue interface{}, doerId int64, comment string) { | |||||
| Log(models.AdminOperateLog{ | |||||
| BizType: string(bizType), | |||||
| OperateType: "edit", | |||||
| NewValue: NewLogValues().Add("new", newValue).JsonString(), | |||||
| OldValue: NewLogValues().Add("old", oldValue).JsonString(), | |||||
| CreatedBy: doerId, | |||||
| Comment: comment, | |||||
| }) | |||||
| } | |||||
| func Log4Del(bizType LogBizType, oldValue interface{}, doerId int64, comment string) { | |||||
| Log(models.AdminOperateLog{ | |||||
| BizType: string(bizType), | |||||
| OperateType: "del", | |||||
| OldValue: NewLogValues().Add("old", oldValue).JsonString(), | |||||
| CreatedBy: doerId, | |||||
| Comment: comment, | |||||
| }) | |||||
| } | |||||
| @@ -0,0 +1,80 @@ | |||||
| package badge | |||||
| import ( | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/log" | |||||
| "code.gitea.io/gitea/routers/response" | |||||
| "code.gitea.io/gitea/services/admin/operate_log" | |||||
| "errors" | |||||
| ) | |||||
| func GetBadgeList(opts models.GetBadgeOpts) (int64, []*models.Badge4AdminShow, error) { | |||||
| total, list, err := models.GetBadgeList(opts) | |||||
| if err != nil { | |||||
| return 0, nil, err | |||||
| } | |||||
| if len(list) == 0 { | |||||
| return 0, nil, nil | |||||
| } | |||||
| r := make([]*models.Badge4AdminShow, len(list)) | |||||
| for i := 0; i < len(list); i++ { | |||||
| r[i] = list[i].ToShow() | |||||
| } | |||||
| return total, r, nil | |||||
| } | |||||
| func AddBadge(m models.BadgeOperateReq, doer *models.User) *response.BizError { | |||||
| _, err := models.GetBadgeCategoryById(m.CategoryId) | |||||
| if err != nil { | |||||
| if models.IsErrRecordNotExist(err) { | |||||
| return response.NewBizError(errors.New("badge category is not available")) | |||||
| } | |||||
| return response.NewBizError(err) | |||||
| } | |||||
| _, err = models.AddBadge(m.ToDTO()) | |||||
| if err != nil { | |||||
| return response.NewBizError(err) | |||||
| } | |||||
| operate_log.Log4Add(operate_log.BadgeOperate, m, doer.ID, "新增了勋章") | |||||
| return nil | |||||
| } | |||||
| func EditBadge(m models.BadgeOperateReq, doer *models.User) *response.BizError { | |||||
| if m.ID == 0 { | |||||
| log.Error(" EditBadge param error") | |||||
| return response.NewBizError(errors.New("param error")) | |||||
| } | |||||
| old, err := models.GetBadgeById(m.ID) | |||||
| if err != nil { | |||||
| return response.NewBizError(err) | |||||
| } | |||||
| _, err = models.UpdateBadgeById(m.ID, m.ToDTO()) | |||||
| if err != nil { | |||||
| return response.NewBizError(err) | |||||
| } | |||||
| operate_log.Log4Edit(operate_log.BadgeOperate, old, m.ToDTO(), doer.ID, "修改了勋章") | |||||
| return nil | |||||
| } | |||||
| func DelBadge(id int64, doer *models.User) *response.BizError { | |||||
| if id == 0 { | |||||
| log.Error(" DelBadge param error") | |||||
| return response.NewBizError(errors.New("param error")) | |||||
| } | |||||
| old, err := models.GetBadgeById(id) | |||||
| if err != nil { | |||||
| return response.NewBizError(err) | |||||
| } | |||||
| n, _, err := models.GetBadgeUsers(id, models.ListOptions{PageSize: 1, Page: 1}) | |||||
| if err != nil { | |||||
| return response.NewBizError(err) | |||||
| } | |||||
| if n > 0 { | |||||
| return response.BADGES_STILL_HAS_USERS | |||||
| } | |||||
| _, err = models.DelBadge(id) | |||||
| operate_log.Log4Del(operate_log.BadgeOperate, old, doer.ID, "删除了勋章") | |||||
| return nil | |||||
| } | |||||
| @@ -0,0 +1,72 @@ | |||||
| package badge | |||||
| import ( | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/log" | |||||
| "code.gitea.io/gitea/routers/response" | |||||
| "code.gitea.io/gitea/services/admin/operate_log" | |||||
| "errors" | |||||
| ) | |||||
| func GetBadgeCategoryList(opts models.ListOptions) (int64, []*models.BadgeCategory4Show, error) { | |||||
| total, list, err := models.GetBadgeCategoryListPaging(opts) | |||||
| if err != nil { | |||||
| return 0, nil, err | |||||
| } | |||||
| if len(list) == 0 { | |||||
| return 0, nil, nil | |||||
| } | |||||
| r := make([]*models.BadgeCategory4Show, len(list)) | |||||
| for i := 0; i < len(list); i++ { | |||||
| r[i] = list[i].ToShow() | |||||
| } | |||||
| return total, r, nil | |||||
| } | |||||
| func AddBadgeCategory(m models.BadgeCategory4Show, doer *models.User) *response.BizError { | |||||
| _, err := models.AddBadgeCategory(m.ToDTO()) | |||||
| if err != nil { | |||||
| return response.NewBizError(err) | |||||
| } | |||||
| operate_log.Log4Add(operate_log.BadgeCategoryOperate, m, doer.ID, "新增了勋章分类") | |||||
| return nil | |||||
| } | |||||
| func EditBadgeCategory(m models.BadgeCategory4Show, doer *models.User) *response.BizError { | |||||
| if m.ID == 0 { | |||||
| log.Error(" EditBadgeCategory param error") | |||||
| return response.NewBizError(errors.New("param error")) | |||||
| } | |||||
| old, err := models.GetBadgeCategoryById(m.ID) | |||||
| if err != nil { | |||||
| return response.NewBizError(err) | |||||
| } | |||||
| _, err = models.UpdateBadgeCategoryById(m.ID, m.ToDTO()) | |||||
| if err != nil { | |||||
| return response.NewBizError(err) | |||||
| } | |||||
| operate_log.Log4Edit(operate_log.BadgeCategoryOperate, old, m.ToDTO(), doer.ID, "修改了勋章分类") | |||||
| return nil | |||||
| } | |||||
| func DelBadgeCategory(id int64, doer *models.User) *response.BizError { | |||||
| if id == 0 { | |||||
| log.Error(" DelBadgeCategory param error") | |||||
| return response.NewBizError(errors.New("param error")) | |||||
| } | |||||
| old, err := models.GetBadgeCategoryById(id) | |||||
| if err != nil { | |||||
| return response.NewBizError(err) | |||||
| } | |||||
| badges, err := models.GetBadgeByCategoryId(id) | |||||
| if err != nil { | |||||
| return response.NewBizError(err) | |||||
| } | |||||
| if len(badges) > 0 { | |||||
| return response.CATEGORY_STILL_HAS_BADGES | |||||
| } | |||||
| _, err = models.DelBadgeCategory(id) | |||||
| operate_log.Log4Del(operate_log.BadgeCategoryOperate, old, doer.ID, "删除了勋章分类") | |||||
| return nil | |||||
| } | |||||
| @@ -0,0 +1,140 @@ | |||||
| package badge | |||||
| import ( | |||||
| "bytes" | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/base" | |||||
| "code.gitea.io/gitea/modules/setting" | |||||
| "crypto/md5" | |||||
| "errors" | |||||
| "fmt" | |||||
| "github.com/nfnt/resize" | |||||
| "github.com/oliamb/cutter" | |||||
| "image" | |||||
| "image/png" | |||||
| "io/ioutil" | |||||
| "mime/multipart" | |||||
| "os" | |||||
| ) | |||||
| type IconUploader struct { | |||||
| Config IconUploadConfig | |||||
| } | |||||
| type IconUploadForm struct { | |||||
| Icon *multipart.FileHeader | |||||
| } | |||||
| type IconUploadConfig struct { | |||||
| FileMaxSize int64 | |||||
| FileMaxWidth int | |||||
| FileMaxHeight int | |||||
| DefaultSize uint | |||||
| NeedResize bool | |||||
| NeedSquare bool | |||||
| } | |||||
| func NewIconUploader(config IconUploadConfig) IconUploader { | |||||
| return IconUploader{Config: config} | |||||
| } | |||||
| func (u IconUploader) Upload(form IconUploadForm, user *models.User) (string, error) { | |||||
| if form.Icon == nil || form.Icon.Filename == "" { | |||||
| return "", errors.New("File or fileName is empty") | |||||
| } | |||||
| fr, err := form.Icon.Open() | |||||
| if err != nil { | |||||
| return "", fmt.Errorf("Icon.Open: %v", err) | |||||
| } | |||||
| defer fr.Close() | |||||
| if form.Icon.Size > u.Config.FileMaxSize { | |||||
| return "", errors.New("File is too large") | |||||
| } | |||||
| data, err := ioutil.ReadAll(fr) | |||||
| if err != nil { | |||||
| return "", fmt.Errorf("ioutil.ReadAll: %v", err) | |||||
| } | |||||
| if !base.IsImageFile(data) { | |||||
| return "", errors.New("File is not a image") | |||||
| } | |||||
| iconName, err := u.uploadIcon(data, user.ID) | |||||
| if err != nil { | |||||
| return "", fmt.Errorf("uploadIcon: %v", err) | |||||
| } | |||||
| return iconName, nil | |||||
| } | |||||
| func (u IconUploader) uploadIcon(data []byte, userId int64) (string, error) { | |||||
| m, err := u.prepare(data) | |||||
| if err != nil { | |||||
| return "", err | |||||
| } | |||||
| iconName := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", userId, md5.Sum(data))))) | |||||
| if err := os.MkdirAll(setting.IconUploadPath, os.ModePerm); err != nil { | |||||
| return "", fmt.Errorf("uploadIcon. Failed to create dir %s: %v", setting.AvatarUploadPath, err) | |||||
| } | |||||
| fw, err := os.Create(models.GetCustomIconByHash(iconName)) | |||||
| if err != nil { | |||||
| return "", fmt.Errorf("Create: %v", err) | |||||
| } | |||||
| defer fw.Close() | |||||
| if err = png.Encode(fw, *m); err != nil { | |||||
| return "", fmt.Errorf("Encode: %v", err) | |||||
| } | |||||
| return iconName, nil | |||||
| } | |||||
| func (u IconUploader) prepare(data []byte) (*image.Image, error) { | |||||
| imgCfg, _, err := image.DecodeConfig(bytes.NewReader(data)) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("DecodeConfig: %v", err) | |||||
| } | |||||
| if imgCfg.Width > u.Config.FileMaxWidth { | |||||
| return nil, fmt.Errorf("Image width is too large: %d > %d", imgCfg.Width, setting.AvatarMaxWidth) | |||||
| } | |||||
| if imgCfg.Height > u.Config.FileMaxHeight { | |||||
| return nil, fmt.Errorf("Image height is too large: %d > %d", imgCfg.Height, setting.AvatarMaxHeight) | |||||
| } | |||||
| img, _, err := image.Decode(bytes.NewReader(data)) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("Decode: %v", err) | |||||
| } | |||||
| if u.Config.NeedSquare { | |||||
| if imgCfg.Width != imgCfg.Height { | |||||
| var newSize, ax, ay int | |||||
| if imgCfg.Width > imgCfg.Height { | |||||
| newSize = imgCfg.Height | |||||
| ax = (imgCfg.Width - imgCfg.Height) / 2 | |||||
| } else { | |||||
| newSize = imgCfg.Width | |||||
| ay = (imgCfg.Height - imgCfg.Width) / 2 | |||||
| } | |||||
| img, err = cutter.Crop(img, cutter.Config{ | |||||
| Width: newSize, | |||||
| Height: newSize, | |||||
| Anchor: image.Point{ax, ay}, | |||||
| }) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| } | |||||
| } | |||||
| if u.Config.NeedResize && u.Config.DefaultSize > 0 { | |||||
| img = resize.Resize(u.Config.DefaultSize, u.Config.DefaultSize, img, resize.NearestNeighbor) | |||||
| } | |||||
| return &img, nil | |||||
| } | |||||
| @@ -0,0 +1,111 @@ | |||||
| package badge | |||||
| import ( | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/log" | |||||
| ) | |||||
| func GetBadgeUsers(badgeId int64, opts models.ListOptions) (int64, []*models.BadgeUser4SHow, error) { | |||||
| total, list, err := models.GetBadgeUsers(badgeId, opts) | |||||
| if err != nil { | |||||
| return 0, nil, err | |||||
| } | |||||
| if len(list) == 0 { | |||||
| return 0, nil, nil | |||||
| } | |||||
| r := make([]*models.BadgeUser4SHow, len(list)) | |||||
| for i := 0; i < len(list); i++ { | |||||
| r[i] = list[i].ToShow() | |||||
| } | |||||
| return total, r, nil | |||||
| } | |||||
| func AddBadgeUsers(badgeId int64, userNames []string) (int, error) { | |||||
| userIds := models.GetUserIdsByUserNames(userNames) | |||||
| if len(userIds) == 0 { | |||||
| return 0, nil | |||||
| } | |||||
| successCount := 0 | |||||
| for _, v := range userIds { | |||||
| m := models.BadgeUser{ | |||||
| UserId: v, | |||||
| BadgeId: badgeId, | |||||
| } | |||||
| _, err := models.AddBadgeUser(m) | |||||
| if err != nil { | |||||
| log.Error("AddBadgeUser err in loop, m=%+v. e=%v", m, err) | |||||
| continue | |||||
| } | |||||
| successCount++ | |||||
| } | |||||
| return successCount, nil | |||||
| } | |||||
| func DelBadgeUser(id int64) error { | |||||
| _, err := models.DelBadgeUser(id) | |||||
| return err | |||||
| } | |||||
| //GetUserBadges Only Returns badges the user has earned | |||||
| func GetUserBadges(userId int64, opts models.ListOptions) ([]*models.Badge4UserShow, error) { | |||||
| badges, err := models.GetUserBadgesPaging(userId, models.GetUserBadgesOpts{ListOptions: opts}) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| r := make([]*models.Badge4UserShow, len(badges)) | |||||
| for i, v := range badges { | |||||
| r[i] = v.ToUserShow() | |||||
| } | |||||
| return r, nil | |||||
| } | |||||
| func CountUserBadges(userId int64) (int64, error) { | |||||
| return models.CountUserBadges(userId) | |||||
| } | |||||
| func GetUserAllBadges(userId int64) ([]models.UserAllBadgeInCategory, error) { | |||||
| categoryList, err := models.GetBadgeCategoryList() | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| r := make([]models.UserAllBadgeInCategory, 0) | |||||
| for _, v := range categoryList { | |||||
| badges, err := models.GetBadgeByCategoryId(v.ID) | |||||
| if badges == nil || len(badges) == 0 { | |||||
| continue | |||||
| } | |||||
| userBadgeMap, err := getUserBadgesMap(userId, v.ID) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| t := models.UserAllBadgeInCategory{ | |||||
| CategoryName: v.Name, | |||||
| CategoryId: v.ID, | |||||
| LightedNum: len(userBadgeMap), | |||||
| } | |||||
| bArray := make([]*models.BadgeShowWithStatus, len(badges)) | |||||
| for j, v := range badges { | |||||
| b := &models.BadgeShowWithStatus{Badge: v.ToUserShow()} | |||||
| if _, has := userBadgeMap[v.ID]; has { | |||||
| b.IsLighted = true | |||||
| } | |||||
| bArray[j] = b | |||||
| } | |||||
| t.Badges = bArray | |||||
| r = append(r, t) | |||||
| } | |||||
| return r, nil | |||||
| } | |||||
| func getUserBadgesMap(userId, categoryId int64) (map[int64]*models.Badge, error) { | |||||
| userBadges, err := models.GetUserBadges(userId, categoryId) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| m := make(map[int64]*models.Badge, 0) | |||||
| for _, v := range userBadges { | |||||
| m[v.ID] = v | |||||
| } | |||||
| return m, nil | |||||
| } | |||||
| @@ -0,0 +1,80 @@ | |||||
| {{template "base/head" .}} | |||||
| <div class="organization members"> | |||||
| {{template "org/header" .}} | |||||
| <div class="ui container"> | |||||
| {{template "base/alert" .}} | |||||
| {{template "org/navber" .}} | |||||
| <div class="ui stackable grid"> | |||||
| <div class="ui sixteen wide computer column list"> | |||||
| {{ range .Members}} | |||||
| <div class="item ui grid"> | |||||
| <div class="three wide mobile two wide tablet one wide computer column"> | |||||
| <img class="ui avatar" src="{{.SizedRelAvatarLink 48}}"> | |||||
| </div> | |||||
| <div class="seven wide mobile three wide tablet three wide computer column"> | |||||
| <div class="meta"><a href="{{.HomeLink}}">{{.Name}}</a></div> | |||||
| <div class="meta">{{.FullName}}</div> | |||||
| </div> | |||||
| <div class="ui three wide tablet four wide computer column center tablet only computer only"> | |||||
| <div class="meta"> | |||||
| {{$.i18n.Tr "org.members.membership_visibility"}} | |||||
| </div> | |||||
| <div class="meta"> | |||||
| {{ $isPublic := index $.MembersIsPublicMember .ID}} | |||||
| {{if $isPublic}} | |||||
| <strong>{{$.i18n.Tr "org.members.public"}}</strong> | |||||
| {{if or (eq $.SignedUser.ID .ID) $.IsOrganizationOwner}}(<a class="link-action" href data-url="{{$.OrgLink}}/members/action/private?uid={{.ID}}">{{$.i18n.Tr "org.members.public_helper"}}</a>){{end}} | |||||
| {{else}} | |||||
| <strong>{{$.i18n.Tr "org.members.private"}}</strong> | |||||
| {{if or (eq $.SignedUser.ID .ID) $.IsOrganizationOwner}}(<a class="link-action" href data-url="{{$.OrgLink}}/members/action/public?uid={{.ID}}">{{$.i18n.Tr "org.members.private_helper"}}</a>){{end}} | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| <div class="five wide mobile three wide tablet three wide computer column"> | |||||
| <div class="meta"> | |||||
| {{$.i18n.Tr "org.members.member_role"}} | |||||
| </div> | |||||
| <div class="meta"> | |||||
| <strong>{{if index $.MembersIsUserOrgOwner .ID}}{{svg "octicon-shield-lock" 16}} {{$.i18n.Tr "org.members.owner"}}{{else}}{{$.i18n.Tr "org.members.member"}}{{end}}</strong> | |||||
| </div> | |||||
| </div> | |||||
| <div class="ui one wide column center tablet only computer only"> | |||||
| <div class="meta"> | |||||
| 2FA | |||||
| </div> | |||||
| <div class="meta"> | |||||
| <strong> | |||||
| {{if index $.MembersTwoFaStatus .ID}} | |||||
| <span class="text green">{{svg "octicon-check" 16}}</span> | |||||
| {{else}} | |||||
| {{svg "octicon-x" 16}} | |||||
| {{end}} | |||||
| </strong> | |||||
| </div> | |||||
| </div> | |||||
| <div class="ui three wide column tablet only computer only"> | |||||
| <div class="text right"> | |||||
| {{if eq $.SignedUser.ID .ID}} | |||||
| <form method="post" action="{{$.OrgLink}}/members/action/leave"> | |||||
| {{$.CsrfTokenHtml}} | |||||
| <button type="submit" class="ui red small button" name="uid" value="{{.ID}}">{{$.i18n.Tr "org.members.leave"}}</button> | |||||
| </form> | |||||
| {{else if $.IsOrganizationOwner}} | |||||
| <form method="post" action="{{$.OrgLink}}/members/action/remove"> | |||||
| {{$.CsrfTokenHtml}} | |||||
| <button type="submit" class="ui red small button" name="uid" value="{{.ID}}">{{$.i18n.Tr "org.members.remove"}}</button> | |||||
| </form> | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| {{end}} | |||||
| </div> | |||||
| {{template "base/paginate" .}} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| {{template "base/footer" .}} | |||||
| @@ -0,0 +1,25 @@ | |||||
| <div class="badge-achive"> | |||||
| {{range .AllBadges }} | |||||
| <div class="bagde-section"> | |||||
| <div class="badge-section-title">{{.CategoryName}} (已点亮{{.LightedNum}}个)</div> | |||||
| <div class="badge-section-children"> | |||||
| <div class="badge-honor-badge"> | |||||
| <div class="badge-honor-badge-basic"> | |||||
| {{range .Badges }} | |||||
| <a class="badge-honor-badge-basic-item {{if not .Badge.Url}} is-not-pointer {{end}}" href="{{.Badge.Url}}"> | |||||
| {{if .IsLighted}} | |||||
| <img src="{{.Badge.LightedIcon}}" alt="" class="badge-honor-badge-basic-img" /> | |||||
| {{else}} | |||||
| <img src="{{.Badge.GreyedIcon}}" alt="" class="badge-honor-badge-basic-img" /> | |||||
| {{ end }} | |||||
| <div class="badge-honor-badge-basic-txt">{{.Badge.Name}}</div> | |||||
| </a> | |||||
| {{ end }} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| {{ end }} | |||||
| {{ template "base/paginate" . }} | |||||
| </div> | |||||
| @@ -17,6 +17,17 @@ | |||||
| {{if .Owner.FullName}}<span class="header text center">{{.Owner.FullName}}</span>{{end}} | {{if .Owner.FullName}}<span class="header text center">{{.Owner.FullName}}</span>{{end}} | ||||
| <span class="username text center">{{.Owner.Name}}</span> | <span class="username text center">{{.Owner.Name}}</span> | ||||
| </div> | </div> | ||||
| <div class="badge-wrap"> | |||||
| {{range $k,$v :=.RecentBadges}} | |||||
| {{if le $k 3}} | |||||
| <div class="badge-img-avatar" title="{{.Name}}"><img style="width: 100%;height: 100%;" src="{{.LightedIcon}}" class="ui poping up" data-content="{{.Name}}" data-position="top center" data-variation="tiny inverted"></div> | |||||
| {{else}} | |||||
| <a class="badge-more-icon" href="{{$.Owner.HomeLink}}?tab=badge"><i class="ri-more-fill"></i></a> | |||||
| {{end}} | |||||
| {{end}} | |||||
| </div> | |||||
| <div class="extra content wrap"> | <div class="extra content wrap"> | ||||
| <ul class="text black"> | <ul class="text black"> | ||||
| {{if .Owner.Location}} | {{if .Owner.Location}} | ||||
| @@ -170,6 +181,12 @@ | |||||
| {{svg "octicon-person" 16}} {{.i18n.Tr "user.followers"}} | {{svg "octicon-person" 16}} {{.i18n.Tr "user.followers"}} | ||||
| <div class="ui label">{{.Owner.NumFollowers}}</div> | <div class="ui label">{{.Owner.NumFollowers}}</div> | ||||
| </a> | </a> | ||||
| <a class='{{if eq .TabName "badge"}}active{{end}} item' href="{{.Owner.HomeLink}}?tab=badge"> | |||||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="16"><path fill="none" d="M0 0h24v24H0z"/><path d="M17 15.245v6.872a.5.5 0 0 1-.757.429L12 20l-4.243 2.546a.5.5 0 0 1-.757-.43v-6.87a8 8 0 1 1 10 0zM12 15a6 6 0 1 0 0-12 6 6 0 0 0 0 12zm0-2a4 4 0 1 1 0-8 4 4 0 0 1 0 8z"/></svg> | |||||
| {{.i18n.Tr "user.badge"}} | |||||
| <div class="ui label">{{.TotalBadges}}</div> | |||||
| </a> | |||||
| </div> | </div> | ||||
| {{if eq .TabName "activity"}} | {{if eq .TabName "activity"}} | ||||
| @@ -201,6 +218,8 @@ | |||||
| {{template "explore/dataset_search" .}} | {{template "explore/dataset_search" .}} | ||||
| {{template "explore/dataset_list" .}} | {{template "explore/dataset_list" .}} | ||||
| {{template "base/paginate" .}} | {{template "base/paginate" .}} | ||||
| {{else if eq .TabName "badge"}} | |||||
| {{template "repo/badge" .}} | |||||
| {{else}} | {{else}} | ||||
| {{template "explore/repo_search" .}} | {{template "explore/repo_search" .}} | ||||
| {{template "explore/repo_list" .}} | {{template "explore/repo_list" .}} | ||||
| @@ -228,5 +247,8 @@ | |||||
| .user.profile .ui.card .extra.content ul { | .user.profile .ui.card .extra.content ul { | ||||
| padding: 5px 0; | padding: 5px 0; | ||||
| } | } | ||||
| .ui.secondary.pointing.menu .item{ | |||||
| padding: 0.78571429em 0.92857143em; | |||||
| } | |||||
| </style> | </style> | ||||
| @@ -9,11 +9,30 @@ | |||||
| .username { | .username { | ||||
| display: block; | display: block; | ||||
| } | } | ||||
| .badge-wrap { | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| .badge-img-avatar { | |||||
| width: 32px; | |||||
| height: 32px; | |||||
| margin-right: 5px; | |||||
| } | |||||
| .badge-more-icon { | |||||
| width: 32px; | |||||
| height: 32px; | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| border-radius: 50%; | |||||
| border: 1px #f8f9fa solid; | |||||
| background: #f8f9fa; | |||||
| } | |||||
| } | |||||
| .header { | .header { | ||||
| font-weight: 700; | font-weight: 700; | ||||
| font-size: 1.3rem; | font-size: 1.3rem; | ||||
| margin-top: -.2rem; | |||||
| margin-top: -0.2rem; | |||||
| line-height: 1.3rem; | line-height: 1.3rem; | ||||
| } | } | ||||
| @@ -158,3 +177,70 @@ | |||||
| max-width: 60px; | max-width: 60px; | ||||
| } | } | ||||
| } | } | ||||
| .badge-achive { | |||||
| .bagde-section { | |||||
| color: #000; | |||||
| margin-top: 28px; | |||||
| border-bottom: 1px solid #dededf; | |||||
| } | |||||
| .bagde-section:last-child { | |||||
| color: #000; | |||||
| margin-top: 28px; | |||||
| border-bottom: none; | |||||
| } | |||||
| .badge-section-title { | |||||
| position: relative; | |||||
| font-size: 16px; | |||||
| line-height: 24px; | |||||
| padding-left: 8px; | |||||
| margin-bottom: 20px; | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| } | |||||
| .badge-section-children { | |||||
| width: 100%; | |||||
| } | |||||
| .badge-honor-badge { | |||||
| margin-bottom: 25px; | |||||
| } | |||||
| .badge-honor-badge-basic { | |||||
| display: flex; | |||||
| align-items: flex-start; | |||||
| flex-wrap: wrap; | |||||
| } | |||||
| .badge-honor-badge-basic-item { | |||||
| text-align: center; | |||||
| font-size: 12px; | |||||
| margin-right: 30px; | |||||
| color: #101010; | |||||
| } | |||||
| .is-not-pointer { | |||||
| cursor: pointer; | |||||
| pointer-events: none; | |||||
| } | |||||
| .badge-honor-badge-basic-img { | |||||
| width: 100px; | |||||
| height: 100px; | |||||
| margin-bottom: 10px; | |||||
| } | |||||
| .badge-honor-badge-basic-txt { | |||||
| line-height: 20px; | |||||
| width: 100px; | |||||
| word-break: break-all; | |||||
| overflow: hidden; | |||||
| text-overflow: ellipsis; | |||||
| display: -webkit-box; | |||||
| -webkit-line-clamp: 2; | |||||
| -webkit-box-orient: vertical; | |||||
| } | |||||
| .badge-section-title:before { | |||||
| content: ""; | |||||
| position: absolute; | |||||
| top: 50%; | |||||
| left: 0; | |||||
| transform: translateY(-50%); | |||||
| width: 3px; | |||||
| height: 1em; | |||||
| background-color: #000; | |||||
| } | |||||
| } | |||||