mirror of
https://github.com/coaidev/coai.git
synced 2025-05-28 17:30:15 +09:00
feat: support ban user and set admin permission action
This commit is contained in:
parent
4fa85b1f12
commit
e2ff065218
@ -29,6 +29,16 @@ type EmailMigrationForm struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
type SetAdminForm struct {
|
||||
Id int64 `json:"id"`
|
||||
Admin bool `json:"admin"`
|
||||
}
|
||||
|
||||
type BanForm struct {
|
||||
Id int64 `json:"id"`
|
||||
Ban bool `json:"ban"`
|
||||
}
|
||||
|
||||
type QuotaOperationForm struct {
|
||||
Id int64 `json:"id" binding:"required"`
|
||||
Quota float32 `json:"quota" binding:"required"`
|
||||
@ -220,6 +230,58 @@ func UpdateEmailAPI(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func SetAdminAPI(c *gin.Context) {
|
||||
db := utils.GetDBFromContext(c)
|
||||
|
||||
var form SetAdminForm
|
||||
if err := c.ShouldBindJSON(&form); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err := setAdmin(db, form.Id, form.Admin)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": true,
|
||||
})
|
||||
}
|
||||
|
||||
func BanAPI(c *gin.Context) {
|
||||
db := utils.GetDBFromContext(c)
|
||||
|
||||
var form BanForm
|
||||
if err := c.ShouldBindJSON(&form); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err := banUser(db, form.Id, form.Ban)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": true,
|
||||
})
|
||||
}
|
||||
|
||||
func UserQuotaAPI(c *gin.Context) {
|
||||
db := utils.GetDBFromContext(c)
|
||||
|
||||
|
@ -28,6 +28,8 @@ func Register(app *gin.RouterGroup) {
|
||||
app.POST("/admin/user/release", ReleaseUsageAPI)
|
||||
app.POST("/admin/user/password", UpdatePasswordAPI)
|
||||
app.POST("/admin/user/email", UpdateEmailAPI)
|
||||
app.POST("/admin/user/ban", BanAPI)
|
||||
app.POST("/admin/user/admin", SetAdminAPI)
|
||||
app.POST("/admin/user/root", UpdateRootPasswordAPI)
|
||||
|
||||
app.POST("/admin/market/update", UpdateMarketAPI)
|
||||
|
@ -133,6 +133,22 @@ func emailMigration(db *sql.DB, id int64, email string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func setAdmin(db *sql.DB, id int64, isAdmin bool) error {
|
||||
_, err := db.Exec(`
|
||||
UPDATE auth SET is_admin = ? WHERE id = ?
|
||||
`, isAdmin, id)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func banUser(db *sql.DB, id int64, isBanned bool) error {
|
||||
_, err := db.Exec(`
|
||||
UPDATE auth SET is_banned = ? WHERE id = ?
|
||||
`, isBanned, id)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func quotaMigration(db *sql.DB, id int64, quota float32, override bool) error {
|
||||
// if quota is negative, then decrease quota
|
||||
// if quota is positive, then increase quota
|
||||
|
@ -225,6 +225,36 @@ export async function subscriptionOperation(
|
||||
}
|
||||
}
|
||||
|
||||
export async function banUserOperation(
|
||||
id: number,
|
||||
ban: boolean,
|
||||
): Promise<CommonResponse> {
|
||||
try {
|
||||
const response = await axios.post("/admin/user/ban", {
|
||||
id,
|
||||
ban,
|
||||
});
|
||||
return response.data as CommonResponse;
|
||||
} catch (e) {
|
||||
return { status: false, message: getErrorMessage(e) };
|
||||
}
|
||||
}
|
||||
|
||||
export async function setAdminOperation(
|
||||
id: number,
|
||||
admin: boolean,
|
||||
): Promise<CommonResponse> {
|
||||
try {
|
||||
const response = await axios.post("/admin/user/admin", {
|
||||
id,
|
||||
admin,
|
||||
});
|
||||
return response.data as CommonResponse;
|
||||
} catch (e) {
|
||||
return { status: false, message: getErrorMessage(e) };
|
||||
}
|
||||
}
|
||||
|
||||
export async function subscriptionLevelOperation(
|
||||
id: number,
|
||||
level: number,
|
||||
|
@ -23,12 +23,13 @@ export enum popupTypes {
|
||||
export type PopupDialogProps = {
|
||||
title: string;
|
||||
description?: string;
|
||||
name: string;
|
||||
name?: string;
|
||||
placeholder?: string;
|
||||
defaultValue?: string;
|
||||
onValueChange?: (value: string) => string;
|
||||
onSubmit?: (value: string) => Promise<boolean>;
|
||||
destructive?: boolean;
|
||||
disabled?: boolean;
|
||||
|
||||
type?: popupTypes;
|
||||
|
||||
@ -112,6 +113,7 @@ function PopupDialog(props: PopupDialogProps) {
|
||||
cancelLabel,
|
||||
confirmLabel,
|
||||
destructive,
|
||||
disabled,
|
||||
} = props;
|
||||
|
||||
const { t } = useTranslation();
|
||||
@ -137,6 +139,7 @@ function PopupDialog(props: PopupDialogProps) {
|
||||
{cancelLabel || t("cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
variant={destructive ? `destructive` : `default`}
|
||||
loading={true}
|
||||
onClick={async () => {
|
||||
|
@ -8,9 +8,11 @@ import {
|
||||
UserResponse,
|
||||
} from "@/admin/types.ts";
|
||||
import {
|
||||
banUserOperation,
|
||||
getUserList,
|
||||
quotaOperation,
|
||||
releaseUsageOperation,
|
||||
setAdminOperation,
|
||||
subscriptionLevelOperation,
|
||||
subscriptionOperation,
|
||||
updateEmail,
|
||||
@ -43,9 +45,13 @@ import {
|
||||
KeyRound,
|
||||
Loader2,
|
||||
Mail,
|
||||
MinusCircle,
|
||||
MoreHorizontal,
|
||||
PlusCircle,
|
||||
RotateCw,
|
||||
Search,
|
||||
Shield,
|
||||
ShieldMinus,
|
||||
} from "lucide-react";
|
||||
import { Input } from "@/components/ui/input.tsx";
|
||||
import PopupDialog, { popupTypes } from "@/components/PopupDialog.tsx";
|
||||
@ -87,6 +93,8 @@ function OperationMenu({ user, onRefresh }: OperationMenuProps) {
|
||||
const [subscriptionLevelOpen, setSubscriptionLevelOpen] =
|
||||
useState<boolean>(false);
|
||||
const [releaseOpen, setReleaseOpen] = useState<boolean>(false);
|
||||
const [banOpen, setBanOpen] = useState<boolean>(false);
|
||||
const [adminOpen, setAdminOpen] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -217,6 +225,50 @@ function OperationMenu({ user, onRefresh }: OperationMenuProps) {
|
||||
return resp.status;
|
||||
}}
|
||||
/>
|
||||
<PopupDialog
|
||||
disabled={username === user.username}
|
||||
destructive={true}
|
||||
type={popupTypes.Empty}
|
||||
title={user.is_banned ? t("admin.unban-action") : t("admin.ban-action")}
|
||||
description={
|
||||
user.is_banned
|
||||
? t("admin.unban-action-desc")
|
||||
: t("admin.ban-action-desc")
|
||||
}
|
||||
open={banOpen}
|
||||
setOpen={setBanOpen}
|
||||
onSubmit={async () => {
|
||||
const resp = await banUserOperation(user.id, !user.is_banned);
|
||||
doToast(t, toast, resp);
|
||||
|
||||
if (resp.status) onRefresh?.();
|
||||
return resp.status;
|
||||
}}
|
||||
/>
|
||||
<PopupDialog
|
||||
disabled={username === user.username}
|
||||
destructive={true}
|
||||
type={popupTypes.Empty}
|
||||
title={
|
||||
user.is_admin
|
||||
? t("admin.cancel-admin-action")
|
||||
: t("admin.set-admin-action")
|
||||
}
|
||||
description={
|
||||
user.is_admin
|
||||
? t("admin.cancel-admin-action-desc")
|
||||
: t("admin.set-admin-action-desc")
|
||||
}
|
||||
open={adminOpen}
|
||||
setOpen={setAdminOpen}
|
||||
onSubmit={async () => {
|
||||
const resp = await setAdminOperation(user.id, !user.is_admin);
|
||||
doToast(t, toast, resp);
|
||||
|
||||
if (resp.status) onRefresh?.();
|
||||
return resp.status;
|
||||
}}
|
||||
/>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@ -233,6 +285,28 @@ function OperationMenu({ user, onRefresh }: OperationMenuProps) {
|
||||
<Mail className={`h-4 w-4 mr-2`} />
|
||||
{t("admin.email-action")}
|
||||
</DropdownMenuItem>
|
||||
{user.is_banned ? (
|
||||
<DropdownMenuItem onClick={() => setBanOpen(true)}>
|
||||
<PlusCircle className={`h-4 w-4 mr-2`} />
|
||||
{t("admin.unban-action")}
|
||||
</DropdownMenuItem>
|
||||
) : (
|
||||
<DropdownMenuItem onClick={() => setBanOpen(true)}>
|
||||
<MinusCircle className={`h-4 w-4 mr-2`} />
|
||||
{t("admin.ban-action")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{user.is_admin ? (
|
||||
<DropdownMenuItem onClick={() => setAdminOpen(true)}>
|
||||
<ShieldMinus className={`h-4 w-4 mr-2`} />
|
||||
{t("admin.cancel-admin-action")}
|
||||
</DropdownMenuItem>
|
||||
) : (
|
||||
<DropdownMenuItem onClick={() => setAdminOpen(true)}>
|
||||
<Shield className={`h-4 w-4 mr-2`} />
|
||||
{t("admin.set-admin-action")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem onClick={() => setQuotaOpen(true)}>
|
||||
<CloudFog className={`h-4 w-4 mr-2`} />
|
||||
{t("admin.quota-action")}
|
||||
|
@ -433,6 +433,14 @@
|
||||
"search-username": "搜索用户名",
|
||||
"password-action": "修改密码",
|
||||
"password-action-desc": "请输入用户的新密码",
|
||||
"set-admin-action": "设为管理员",
|
||||
"set-admin-action-desc": "确定将该用户设为管理员?",
|
||||
"cancel-admin-action": "取消管理员",
|
||||
"cancel-admin-action-desc": "确定取消该用户的管理员权限?",
|
||||
"ban-action": "封禁用户",
|
||||
"ban-action-desc": "确定将该用户封禁?",
|
||||
"unban-action": "解封用户",
|
||||
"unban-action-desc": "确定将该用户解封?",
|
||||
"email-action": "修改邮箱",
|
||||
"email-action-desc": "请输入用户的新邮箱",
|
||||
"quota-action": "点数变更",
|
||||
|
@ -605,7 +605,15 @@
|
||||
"email-action": "Modify email",
|
||||
"email-action-desc": "Please enter the user's new email",
|
||||
"default-password": "Password change prompt",
|
||||
"default-password-prompt": "Your administrator password is the default password. For the security of your account, please change your password as soon as possible. (Go to Back Office - System Settings - Change Root Password)"
|
||||
"default-password-prompt": "Your administrator password is the default password. For the security of your account, please change your password as soon as possible. (Go to Back Office - System Settings - Change Root Password)",
|
||||
"set-admin-action": "Set as adminisitrator",
|
||||
"set-admin-action-desc": "Are you sure you want to make this user an admin?",
|
||||
"cancel-admin-action": "Cancel the admin",
|
||||
"cancel-admin-action-desc": "Are you sure you want to remove admin access for this user?",
|
||||
"ban-action": "Ban User",
|
||||
"ban-action-desc": "Are you sure you want to ban this user?",
|
||||
"unban-action": " unBlock User",
|
||||
"unban-action-desc": "Are you sure you want to unblock this user?"
|
||||
},
|
||||
"mask": {
|
||||
"title": "Mask Settings",
|
||||
|
@ -605,7 +605,15 @@
|
||||
"email-action": "メールアドレスを変更する",
|
||||
"email-action-desc": "ユーザーの新しいメールアドレスを入力してください",
|
||||
"default-password": "パスワード変更プロンプト",
|
||||
"default-password-prompt": "管理者パスワードはデフォルトのパスワードです。アカウントのセキュリティのため、できるだけ早くパスワードを変更してください。(バックオフィスに移動-システム設定-ルートパスワードの変更)"
|
||||
"default-password-prompt": "管理者パスワードはデフォルトのパスワードです。アカウントのセキュリティのため、できるだけ早くパスワードを変更してください。(バックオフィスに移動-システム設定-ルートパスワードの変更)",
|
||||
"set-admin-action": "管理者にする",
|
||||
"set-admin-action-desc": "このユーザーを管理者にしてもよろしいですか?",
|
||||
"cancel-admin-action": "管理者をキャンセル",
|
||||
"cancel-admin-action-desc": "このユーザーの管理者アクセスを削除してもよろしいですか?",
|
||||
"ban-action": "ゴーストユーザー",
|
||||
"ban-action-desc": "このユーザーを禁止してもよろしいですか?",
|
||||
"unban-action": "ユーザーのブロックを解除する",
|
||||
"unban-action-desc": "このユーザーのブロックを解除してもよろしいですか?"
|
||||
},
|
||||
"mask": {
|
||||
"title": "プリセット設定",
|
||||
|
@ -605,7 +605,15 @@
|
||||
"email-action": "Изменение почтового ящика",
|
||||
"email-action-desc": "Введите новый адрес электронной почты пользователя",
|
||||
"default-password": "Запрос на изменение пароля",
|
||||
"default-password-prompt": "Пароль администратора является паролем по умолчанию. В целях безопасности вашей учетной записи, пожалуйста, измените пароль как можно скорее. (Перейдите в Back Office - System Settings - Change Root Password)"
|
||||
"default-password-prompt": "Пароль администратора является паролем по умолчанию. В целях безопасности вашей учетной записи, пожалуйста, измените пароль как можно скорее. (Перейдите в Back Office - System Settings - Change Root Password)",
|
||||
"set-admin-action": "Сделать администратором",
|
||||
"set-admin-action-desc": "Вы уверены, что хотите сделать этого пользователя администратором?",
|
||||
"cancel-admin-action": "Отменить администрирование",
|
||||
"cancel-admin-action-desc": "Вы уверены, что хотите удалить доступ администратора для этого пользователя?",
|
||||
"ban-action": "Пользователь-призрак",
|
||||
"ban-action-desc": "Вы уверены, что хотите заблокировать этого пользователя?",
|
||||
"unban-action": "Разблокировать пользователя",
|
||||
"unban-action-desc": "Вы уверены, что хотите разблокировать этого пользователя?"
|
||||
},
|
||||
"mask": {
|
||||
"title": "Настройки маски",
|
||||
|
Loading…
Reference in New Issue
Block a user