mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
318 lines
8.3 KiB
Go
318 lines
8.3 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package admin
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/hanzoai/git/models/db"
|
|
user_model "github.com/hanzoai/git/models/user"
|
|
"github.com/hanzoai/git/modules/log"
|
|
"github.com/hanzoai/git/modules/setting"
|
|
"github.com/hanzoai/git/modules/templates"
|
|
"github.com/hanzoai/git/modules/util"
|
|
"github.com/hanzoai/git/modules/web"
|
|
"github.com/hanzoai/git/services/context"
|
|
"github.com/hanzoai/git/services/forms"
|
|
)
|
|
|
|
const (
|
|
tplBadges templates.TplName = "admin/badge/list"
|
|
tplBadgeNew templates.TplName = "admin/badge/new"
|
|
tplBadgeView templates.TplName = "admin/badge/view"
|
|
tplBadgeEdit templates.TplName = "admin/badge/edit"
|
|
tplBadgeUsers templates.TplName = "admin/badge/users"
|
|
)
|
|
|
|
// BadgeSearchDefaultAdminSort is the default sort type for admin view
|
|
const BadgeSearchDefaultAdminSort = "oldest"
|
|
|
|
// Badges show all the badges
|
|
func Badges(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("admin.badges")
|
|
ctx.Data["PageIsAdminBadges"] = true
|
|
|
|
RenderBadgeSearch(ctx, &user_model.SearchBadgeOptions{
|
|
ListOptions: db.ListOptions{
|
|
Page: max(ctx.FormInt("page"), 1),
|
|
PageSize: setting.UI.Admin.UserPagingNum,
|
|
},
|
|
}, tplBadges)
|
|
}
|
|
|
|
// NewBadge render adding a new badge
|
|
func NewBadge(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("admin.badges.new_badge")
|
|
ctx.Data["PageIsAdminBadges"] = true
|
|
|
|
ctx.HTML(http.StatusOK, tplBadgeNew)
|
|
}
|
|
|
|
// NewBadgePost response for adding a new badge
|
|
func NewBadgePost(ctx *context.Context) {
|
|
form := web.GetForm(ctx).(*forms.AdminCreateBadgeForm)
|
|
|
|
if ctx.HasError() {
|
|
ctx.JSONError(ctx.GetErrMsg())
|
|
return
|
|
}
|
|
|
|
b := &user_model.Badge{
|
|
Slug: form.Slug,
|
|
Description: form.Description,
|
|
ImageURL: form.ImageURL,
|
|
}
|
|
|
|
if err := user_model.CreateBadge(ctx, b); err != nil {
|
|
if errors.Is(err, util.ErrAlreadyExist) {
|
|
ctx.JSONError(ctx.Tr("admin.badges.slug_been_taken"))
|
|
} else {
|
|
ctx.ServerError("CreateBadge", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
log.Trace("Badge created by admin (%s): %s", ctx.Doer.Name, b.Slug)
|
|
|
|
ctx.Flash.Success(ctx.Tr("admin.badges.new_success", b.Slug))
|
|
ctx.JSONRedirect(setting.AppSubURL + "/-/admin/badges/slug/" + url.PathEscape(b.Slug))
|
|
}
|
|
|
|
func prepareBadgeInfo(ctx *context.Context) *user_model.Badge {
|
|
b, err := user_model.GetBadge(ctx, ctx.PathParam("badge_slug"))
|
|
if err != nil {
|
|
if errors.Is(err, util.ErrNotExist) {
|
|
ctx.Redirect(setting.AppSubURL + "/-/admin/badges")
|
|
} else {
|
|
ctx.ServerError("GetBadge", err)
|
|
}
|
|
return nil
|
|
}
|
|
ctx.Data["Badge"] = b
|
|
return b
|
|
}
|
|
|
|
func ViewBadge(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("admin.badges.details")
|
|
ctx.Data["PageIsAdminBadges"] = true
|
|
|
|
prepareBadgeInfo(ctx)
|
|
if ctx.Written() {
|
|
return
|
|
}
|
|
|
|
badge := ctx.Data["Badge"].(*user_model.Badge)
|
|
opts := &user_model.GetBadgeUsersOptions{
|
|
ListOptions: db.ListOptions{
|
|
Page: 1,
|
|
PageSize: setting.UI.Admin.UserPagingNum,
|
|
},
|
|
BadgeSlug: badge.Slug,
|
|
}
|
|
users, count, err := user_model.GetBadgeUsers(ctx, opts)
|
|
if err != nil {
|
|
ctx.ServerError("GetBadgeUsers", err)
|
|
return
|
|
}
|
|
ctx.Data["Users"] = users
|
|
ctx.Data["UsersTotal"] = int(count)
|
|
|
|
ctx.HTML(http.StatusOK, tplBadgeView)
|
|
}
|
|
|
|
// EditBadge show editing badge page
|
|
func EditBadge(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("admin.badges.edit_badge")
|
|
ctx.Data["PageIsAdminBadges"] = true
|
|
prepareBadgeInfo(ctx)
|
|
if ctx.Written() {
|
|
return
|
|
}
|
|
|
|
ctx.HTML(http.StatusOK, tplBadgeEdit)
|
|
}
|
|
|
|
// EditBadgePost response for editing badge
|
|
func EditBadgePost(ctx *context.Context) {
|
|
b := prepareBadgeInfo(ctx)
|
|
if ctx.Written() {
|
|
return
|
|
}
|
|
|
|
form := web.GetForm(ctx).(*forms.AdminEditBadgeForm)
|
|
if ctx.HasError() {
|
|
ctx.JSONError(ctx.GetErrMsg())
|
|
return
|
|
}
|
|
|
|
b.ImageURL = form.ImageURL
|
|
b.Description = form.Description
|
|
|
|
if err := user_model.UpdateBadge(ctx, b); err != nil {
|
|
ctx.ServerError("UpdateBadge", err)
|
|
return
|
|
}
|
|
|
|
log.Trace("Badge updated by admin (%s): %s", ctx.Doer.Name, b.Slug)
|
|
|
|
ctx.Flash.Success(ctx.Tr("admin.badges.update_success"))
|
|
ctx.JSONRedirect(setting.AppSubURL + "/-/admin/badges/slug/" + url.PathEscape(ctx.PathParam("badge_slug")))
|
|
}
|
|
|
|
// DeleteBadge response for deleting a badge
|
|
func DeleteBadge(ctx *context.Context) {
|
|
b, err := user_model.GetBadge(ctx, ctx.PathParam("badge_slug"))
|
|
if err != nil {
|
|
ctx.ServerError("GetBadge", err)
|
|
return
|
|
}
|
|
|
|
if err = user_model.DeleteBadge(ctx, b); err != nil {
|
|
ctx.ServerError("DeleteBadge", err)
|
|
return
|
|
}
|
|
|
|
log.Trace("Badge deleted by admin (%s): %s", ctx.Doer.Name, b.Slug)
|
|
|
|
ctx.Flash.Success(ctx.Tr("admin.badges.deletion_success"))
|
|
ctx.Redirect(setting.AppSubURL + "/-/admin/badges")
|
|
}
|
|
|
|
func BadgeUsers(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("admin.badges.users_with_badge", ctx.PathParam("badge_slug"))
|
|
ctx.Data["PageIsAdminBadges"] = true
|
|
|
|
page := max(ctx.FormInt("page"), 1)
|
|
|
|
badge := &user_model.Badge{Slug: ctx.PathParam("badge_slug")}
|
|
opts := &user_model.GetBadgeUsersOptions{
|
|
ListOptions: db.ListOptions{
|
|
Page: page,
|
|
PageSize: setting.UI.Admin.UserPagingNum,
|
|
},
|
|
BadgeSlug: badge.Slug,
|
|
}
|
|
users, count, err := user_model.GetBadgeUsers(ctx, opts)
|
|
if err != nil {
|
|
ctx.ServerError("GetBadgeUsers", err)
|
|
return
|
|
}
|
|
|
|
ctx.Data["Users"] = users
|
|
ctx.Data["Total"] = count
|
|
ctx.Data["Page"] = context.NewPagination(count, setting.UI.Admin.UserPagingNum, page, 5)
|
|
|
|
ctx.HTML(http.StatusOK, tplBadgeUsers)
|
|
}
|
|
|
|
// BadgeUsersPost response for actions for user badges
|
|
func BadgeUsersPost(ctx *context.Context) {
|
|
name := strings.ToLower(ctx.FormString("user"))
|
|
|
|
u, err := user_model.GetUserByName(ctx, name)
|
|
if err != nil {
|
|
if user_model.IsErrUserNotExist(err) {
|
|
ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
|
|
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
|
} else {
|
|
ctx.ServerError("GetUserByName", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
if err = user_model.AddUserBadge(ctx, u, &user_model.Badge{Slug: ctx.PathParam("badge_slug")}); err != nil {
|
|
if errors.Is(err, util.ErrNotExist) {
|
|
ctx.Flash.Error(ctx.Tr("admin.badges.not_found"))
|
|
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
|
} else if errors.Is(err, util.ErrAlreadyExist) {
|
|
ctx.Flash.Error(ctx.Tr("admin.badges.user_already_has"))
|
|
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
|
} else {
|
|
ctx.ServerError("AddUserBadge", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("admin.badges.user_add_success"))
|
|
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
|
}
|
|
|
|
// DeleteBadgeUser delete a badge from a user
|
|
func DeleteBadgeUser(ctx *context.Context) {
|
|
badgeUsersURL := setting.AppSubURL + "/-/admin/badges/slug/" + url.PathEscape(ctx.PathParam("badge_slug")) + "/users"
|
|
|
|
user, err := user_model.GetUserByID(ctx, ctx.FormInt64("id"))
|
|
if err != nil {
|
|
if user_model.IsErrUserNotExist(err) {
|
|
ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
|
|
ctx.JSONRedirect(badgeUsersURL)
|
|
return
|
|
} else {
|
|
ctx.ServerError("GetUserByID", err)
|
|
return
|
|
}
|
|
}
|
|
if err := user_model.RemoveUserBadge(ctx, user, &user_model.Badge{Slug: ctx.PathParam("badge_slug")}); err == nil {
|
|
ctx.Flash.Success(ctx.Tr("admin.badges.user_remove_success"))
|
|
} else {
|
|
ctx.ServerError("RemoveUserBadge", err)
|
|
return
|
|
}
|
|
|
|
ctx.JSONRedirect(badgeUsersURL)
|
|
}
|
|
|
|
func RenderBadgeSearch(ctx *context.Context, opts *user_model.SearchBadgeOptions, tplName templates.TplName) {
|
|
var (
|
|
badges []*user_model.Badge
|
|
count int64
|
|
err error
|
|
orderBy db.SearchOrderBy
|
|
)
|
|
|
|
sortOrder := ctx.FormString("sort")
|
|
if sortOrder == "" {
|
|
sortOrder = BadgeSearchDefaultAdminSort
|
|
}
|
|
ctx.Data["SortType"] = sortOrder
|
|
|
|
switch sortOrder {
|
|
case "newest":
|
|
orderBy = "`badge`.id DESC"
|
|
case "oldest":
|
|
orderBy = "`badge`.id ASC"
|
|
case "reversealphabetically":
|
|
orderBy = "`badge`.slug DESC"
|
|
case "alphabetically":
|
|
orderBy = "`badge`.slug ASC"
|
|
default:
|
|
// In case the sort type is invalid, keep admin default sorting.
|
|
ctx.Data["SortType"] = "oldest"
|
|
orderBy = "`badge`.id ASC"
|
|
}
|
|
|
|
opts.Keyword = ctx.FormTrim("q")
|
|
opts.OrderBy = orderBy
|
|
if len(opts.Keyword) == 0 || isKeywordValid(opts.Keyword) {
|
|
badges, count, err = user_model.SearchBadges(ctx, opts)
|
|
if err != nil {
|
|
ctx.ServerError("SearchBadges", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
ctx.Data["Keyword"] = opts.Keyword
|
|
ctx.Data["Total"] = count
|
|
ctx.Data["Badges"] = badges
|
|
|
|
pager := context.NewPagination(count, opts.PageSize, opts.Page, 5)
|
|
pager.AddParamFromRequest(ctx.Req)
|
|
ctx.Data["Page"] = pager
|
|
|
|
ctx.HTML(http.StatusOK, tplName)
|
|
}
|