mirror of
https://github.com/coaidev/coai.git
synced 2025-05-19 21:10:18 +09:00
update user operation and fix api connection
This commit is contained in:
parent
b40c60edd8
commit
38dad633ee
@ -14,6 +14,16 @@ type GenerateInvitationForm struct {
|
|||||||
Number int `json:"number"`
|
Number int `json:"number"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QuotaOperationForm struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Quota float32 `json:"quota"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubscriptionOperationForm struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Month int64 `json:"month"`
|
||||||
|
}
|
||||||
|
|
||||||
func InfoAPI(c *gin.Context) {
|
func InfoAPI(c *gin.Context) {
|
||||||
db := utils.GetDBFromContext(c)
|
db := utils.GetDBFromContext(c)
|
||||||
cache := utils.GetCacheFromContext(c)
|
cache := utils.GetCacheFromContext(c)
|
||||||
@ -74,3 +84,55 @@ func UserPaginationAPI(c *gin.Context) {
|
|||||||
search := strings.TrimSpace(c.Query("search"))
|
search := strings.TrimSpace(c.Query("search"))
|
||||||
c.JSON(http.StatusOK, GetUserPagination(db, int64(page), search))
|
c.JSON(http.StatusOK, GetUserPagination(db, int64(page), search))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UserQuotaAPI(c *gin.Context) {
|
||||||
|
db := utils.GetDBFromContext(c)
|
||||||
|
|
||||||
|
var form QuotaOperationForm
|
||||||
|
if err := c.ShouldBindJSON(&form); err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := QuotaOperation(db, form.Id, form.Quota)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserSubscriptionAPI(c *gin.Context) {
|
||||||
|
db := utils.GetDBFromContext(c)
|
||||||
|
|
||||||
|
var form SubscriptionOperationForm
|
||||||
|
if err := c.ShouldBindJSON(&form); err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SubscriptionOperation(db, form.Id, form.Month)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -13,4 +13,6 @@ func Register(app *gin.Engine) {
|
|||||||
app.POST("/admin/invitation/generate", GenerateInvitationAPI)
|
app.POST("/admin/invitation/generate", GenerateInvitationAPI)
|
||||||
|
|
||||||
app.GET("/admin/user/list", UserPaginationAPI)
|
app.GET("/admin/user/list", UserPaginationAPI)
|
||||||
|
app.POST("/admin/user/quota", UserQuotaAPI)
|
||||||
|
app.POST("/admin/user/subscription", UserSubscriptionAPI)
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ func IncrRequest(cache *redis.Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IncrModelRequest(cache *redis.Client, model string, tokens int64) {
|
func IncrModelRequest(cache *redis.Client, model string, tokens int64) {
|
||||||
IncrRequest(cache)
|
|
||||||
utils.IncrWithExpire(cache, getModelFormat(getDay(), model), tokens, time.Hour*24*7*2)
|
utils.IncrWithExpire(cache, getModelFormat(getDay(), model), tokens, time.Hour*24*7*2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ func GetUserPagination(db *sql.DB, page int64, search string) PaginationForm {
|
|||||||
LEFT JOIN quota ON quota.user_id = auth.id
|
LEFT JOIN quota ON quota.user_id = auth.id
|
||||||
LEFT JOIN subscription ON subscription.user_id = auth.id
|
LEFT JOIN subscription ON subscription.user_id = auth.id
|
||||||
WHERE auth.username LIKE ?
|
WHERE auth.username LIKE ?
|
||||||
ORDER BY auth.id DESC LIMIT ? OFFSET ?
|
ORDER BY auth.id LIMIT ? OFFSET ?
|
||||||
`, "%"+search+"%", pagination, page*pagination)
|
`, "%"+search+"%", pagination, page*pagination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return PaginationForm{
|
return PaginationForm{
|
||||||
@ -79,3 +79,29 @@ func GetUserPagination(db *sql.DB, page int64, search string) PaginationForm {
|
|||||||
Data: users,
|
Data: users,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func QuotaOperation(db *sql.DB, id int64, quota float32) error {
|
||||||
|
// if quota is negative, then decrease quota
|
||||||
|
// if quota is positive, then increase quota
|
||||||
|
|
||||||
|
_, err := db.Exec(`
|
||||||
|
INSERT INTO quota (user_id, quota, used) VALUES (?, ?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE quota = quota + ?
|
||||||
|
`, id, quota, 0., quota)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func SubscriptionOperation(db *sql.DB, id int64, month int64) error {
|
||||||
|
// if month is negative, then decrease month
|
||||||
|
// if month is positive, then increase month
|
||||||
|
|
||||||
|
expireAt := time.Now().AddDate(0, int(month), 0)
|
||||||
|
|
||||||
|
_, err := db.Exec(`
|
||||||
|
INSERT INTO subscription (user_id, total_month, expired_at) VALUES (?, ?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE total_month = total_month + ?, expired_at = DATE_ADD(expired_at, INTERVAL ? MONTH)
|
||||||
|
`, id, month, expireAt, month, month)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
BillingChartResponse,
|
BillingChartResponse,
|
||||||
|
CommonResponse,
|
||||||
ErrorChartResponse,
|
ErrorChartResponse,
|
||||||
InfoResponse,
|
InfoResponse,
|
||||||
InvitationGenerateResponse,
|
InvitationGenerateResponse,
|
||||||
@ -111,3 +112,27 @@ export async function getUserList(
|
|||||||
|
|
||||||
return response.data as UserResponse;
|
return response.data as UserResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function quotaOperation(
|
||||||
|
id: number,
|
||||||
|
quota: number,
|
||||||
|
): Promise<CommonResponse> {
|
||||||
|
const response = await axios.post("/admin/user/quota", { id, quota });
|
||||||
|
if (response.status !== 200) {
|
||||||
|
return { status: false, message: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data as CommonResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function subscriptionOperation(
|
||||||
|
id: number,
|
||||||
|
month: number,
|
||||||
|
): Promise<CommonResponse> {
|
||||||
|
const response = await axios.post("/admin/user/subscription", { id, month });
|
||||||
|
if (response.status !== 200) {
|
||||||
|
return { status: false, message: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data as CommonResponse;
|
||||||
|
}
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
export type CommonResponse = {
|
||||||
|
status: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type InfoResponse = {
|
export type InfoResponse = {
|
||||||
billing_today: number;
|
billing_today: number;
|
||||||
billing_month: number;
|
billing_month: number;
|
||||||
@ -54,7 +59,7 @@ export type InvitationGenerateResponse = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type UserData = {
|
export type UserData = {
|
||||||
id: string;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
is_admin: boolean;
|
is_admin: boolean;
|
||||||
quota: number;
|
quota: number;
|
||||||
|
79
app/src/components/PopupDialog.tsx
Normal file
79
app/src/components/PopupDialog.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog.tsx";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
|
import { Input } from "@/components/ui/input.tsx";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export type PopupDialogProps = {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
name: string;
|
||||||
|
defaultValue?: string;
|
||||||
|
onValueChange?: (value: string) => string;
|
||||||
|
onSubmit?: (value: string) => Promise<boolean>;
|
||||||
|
|
||||||
|
open: boolean;
|
||||||
|
setOpen: (open: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function PopupDialog({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
name,
|
||||||
|
defaultValue,
|
||||||
|
onValueChange,
|
||||||
|
onSubmit,
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
}: PopupDialogProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [value, setValue] = useState<string>(defaultValue || "");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
<DialogDescription className={`pt-1.5`}>{description}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className={`pt-1 flex flex-row items-center justify-center`}>
|
||||||
|
<span className={`mr-4 whitespace-nowrap`}>{name}</span>
|
||||||
|
<Input
|
||||||
|
onChange={(e) => {
|
||||||
|
setValue(
|
||||||
|
onValueChange ? onValueChange(e.target.value) : e.target.value,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant={`outline`} onClick={() => setOpen(false)}>
|
||||||
|
{t("cancel")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onSubmit && onSubmit(value).then((success) => {
|
||||||
|
if (success) {
|
||||||
|
setOpen(false);
|
||||||
|
setValue(defaultValue || "");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("confirm")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PopupDialog;
|
@ -155,8 +155,8 @@ function InvitationTable() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{data.data.map((invitation, idx) => (
|
{(data.data || []).map((invitation, idx) => (
|
||||||
<TableRow key={idx}>
|
<TableRow key={idx} className={`whitespace-nowrap`}>
|
||||||
<TableCell>{invitation.code}</TableCell>
|
<TableCell>{invitation.code}</TableCell>
|
||||||
<TableCell>{invitation.quota}</TableCell>
|
<TableCell>{invitation.quota}</TableCell>
|
||||||
<TableCell>{invitation.type}</TableCell>
|
<TableCell>{invitation.type}</TableCell>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useToast } from "@/components/ui/use-toast.ts";
|
import { useToast } from "@/components/ui/use-toast.ts";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { UserForm, UserResponse } from "@/admin/types.ts";
|
import {CommonResponse, UserForm, UserResponse} from "@/admin/types.ts";
|
||||||
import { getUserList } from "@/admin/api.ts";
|
import {getUserList, quotaOperation, subscriptionOperation} from "@/admin/api.ts";
|
||||||
import { useEffectAsync } from "@/utils/hook.ts";
|
import { useEffectAsync } from "@/utils/hook.ts";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@ -12,15 +12,93 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table.tsx";
|
} from "@/components/ui/table.tsx";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu.tsx";
|
||||||
import { Button } from "@/components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import {
|
import {
|
||||||
|
CalendarClock,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
CloudCog,
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
RotateCw,
|
RotateCw,
|
||||||
Search,
|
Search,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Input } from "@/components/ui/input.tsx";
|
import { Input } from "@/components/ui/input.tsx";
|
||||||
|
import PopupDialog from "@/components/PopupDialog.tsx";
|
||||||
|
import {getNumber, parseNumber} from "@/utils/base.ts";
|
||||||
|
|
||||||
|
type OperationMenuProps = {
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function doToast(t: any, toast: any, resp: CommonResponse) {
|
||||||
|
if (!resp.status) toast({
|
||||||
|
title: t("admin.operate-failed"),
|
||||||
|
description: t("admin.operate-failed-prompt", { reason: resp.message }),
|
||||||
|
});
|
||||||
|
else toast({
|
||||||
|
title: t("admin.operate-success"),
|
||||||
|
description: t("admin.operate-success-prompt"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function OperationMenu({ id }: OperationMenuProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const [quotaOpen, setQuotaOpen] = useState<boolean>(false);
|
||||||
|
const [subscriptionOpen, setSubscriptionOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PopupDialog
|
||||||
|
title={t("admin.quota-action")} name={t("admin.quota")}
|
||||||
|
description={t("admin.quota-action-desc")}
|
||||||
|
defaultValue={"0"} onValueChange={getNumber}
|
||||||
|
open={quotaOpen} setOpen={setQuotaOpen}
|
||||||
|
onSubmit={async (value) => {
|
||||||
|
const quota = parseNumber(value);
|
||||||
|
const resp = await quotaOperation(id, quota);
|
||||||
|
doToast(t, toast, resp);
|
||||||
|
return resp.status;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PopupDialog
|
||||||
|
title={t("admin.subscription-action")} name={t("admin.month")}
|
||||||
|
description={t("admin.subscription-action-desc")}
|
||||||
|
defaultValue={"0"} onValueChange={getNumber}
|
||||||
|
open={subscriptionOpen} setOpen={setSubscriptionOpen}
|
||||||
|
onSubmit={async (value) => {
|
||||||
|
const month = parseNumber(value);
|
||||||
|
const resp = await subscriptionOperation(id, month);
|
||||||
|
doToast(t, toast, resp);
|
||||||
|
return resp.status;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger>
|
||||||
|
<Button variant={`outline`} size={`icon`}>
|
||||||
|
<MoreHorizontal className={`h-4 w-4`} />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem onClick={() => setQuotaOpen(true)}>
|
||||||
|
<CloudCog className={`h-4 w-4 mr-2`} />
|
||||||
|
{t("admin.quota-action")}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => setSubscriptionOpen(true)}>
|
||||||
|
<CalendarClock className={`h-4 w-4 mr-2`} />
|
||||||
|
{t("admin.subscription-action")}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function UserTable() {
|
function UserTable() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -73,7 +151,7 @@ function UserTable() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{data.data.map((user, idx) => (
|
{(data.data || []).map((user, idx) => (
|
||||||
<TableRow key={idx}>
|
<TableRow key={idx}>
|
||||||
<TableCell>{user.id}</TableCell>
|
<TableCell>{user.id}</TableCell>
|
||||||
<TableCell>{user.username}</TableCell>
|
<TableCell>{user.username}</TableCell>
|
||||||
@ -84,9 +162,7 @@ function UserTable() {
|
|||||||
<TableCell>{t(user.enterprise.toString())}</TableCell>
|
<TableCell>{t(user.enterprise.toString())}</TableCell>
|
||||||
<TableCell>{t(user.is_admin.toString())}</TableCell>
|
<TableCell>{t(user.is_admin.toString())}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Button variant={`outline`} size={`icon`}>
|
<OperationMenu id={user.id} />
|
||||||
<MoreHorizontal className={`h-4 w-4`} />
|
|
||||||
</Button>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
FolderKanban,
|
FolderKanban,
|
||||||
Link,
|
Link,
|
||||||
Newspaper,
|
Newspaper,
|
||||||
|
Shield,
|
||||||
Users2,
|
Users2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import router from "@/router.tsx";
|
import router from "@/router.tsx";
|
||||||
@ -20,8 +21,10 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog.tsx";
|
} from "@/components/ui/dialog.tsx";
|
||||||
import { getLanguage } from "@/i18n.ts";
|
import { getLanguage } from "@/i18n.ts";
|
||||||
|
import { selectAdmin } from "@/store/auth.ts";
|
||||||
|
|
||||||
function ChatSpace() {
|
function ChatSpace() {
|
||||||
|
const admin = useSelector(selectAdmin);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const subscription = useSelector(isSubscribedSelector);
|
const subscription = useSelector(isSubscribedSelector);
|
||||||
@ -82,6 +85,12 @@ function ChatSpace() {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<div className={`space-footer`}>
|
<div className={`space-footer`}>
|
||||||
|
{admin && (
|
||||||
|
<p>
|
||||||
|
<Shield className={`h-3 w-3 mr-1`} />
|
||||||
|
<a onClick={() => router.navigate("/admin")}>{t("admin.users")}</a>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<p>
|
<p>
|
||||||
<Link className={`h-3 w-3 mr-1`} />
|
<Link className={`h-3 w-3 mr-1`} />
|
||||||
<a
|
<a
|
||||||
|
@ -29,7 +29,6 @@ export class Connection {
|
|||||||
public constructor(id: number, callback?: StreamCallback) {
|
public constructor(id: number, callback?: StreamCallback) {
|
||||||
this.state = false;
|
this.state = false;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.init();
|
|
||||||
this.callback && this.setCallback(callback);
|
this.callback && this.setCallback(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +61,7 @@ export class Connection {
|
|||||||
|
|
||||||
public send(data: Record<string, string | boolean | number>): boolean {
|
public send(data: Record<string, string | boolean | number>): boolean {
|
||||||
if (!this.state || !this.connection) {
|
if (!this.state || !this.connection) {
|
||||||
|
if (this.connection === undefined) this.init();
|
||||||
console.debug("[connection] connection not ready, retrying in 500ms...");
|
console.debug("[connection] connection not ready, retrying in 500ms...");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -267,6 +267,7 @@ const resources = {
|
|||||||
used: "状态",
|
used: "状态",
|
||||||
number: "数量",
|
number: "数量",
|
||||||
username: "用户名",
|
username: "用户名",
|
||||||
|
month: "月数",
|
||||||
"is-admin": "管理员",
|
"is-admin": "管理员",
|
||||||
"used-quota": "已用点数",
|
"used-quota": "已用点数",
|
||||||
"is-subscribed": "是否订阅",
|
"is-subscribed": "是否订阅",
|
||||||
@ -276,7 +277,7 @@ const resources = {
|
|||||||
"search-username": "搜索用户名",
|
"search-username": "搜索用户名",
|
||||||
"quota-action": "点数变更",
|
"quota-action": "点数变更",
|
||||||
"quota-action-desc": "请输入点数变更值(正数为增加,负数为减少)",
|
"quota-action-desc": "请输入点数变更值(正数为增加,负数为减少)",
|
||||||
"subscription-action": "赠送订阅",
|
"subscription-action": "订阅管理",
|
||||||
"subscription-action-desc": "请输入赠送的订阅月数",
|
"subscription-action-desc": "请输入赠送的订阅月数",
|
||||||
"operate-success": "操作成功",
|
"operate-success": "操作成功",
|
||||||
"operate-success-prompt": "您的操作已成功执行。",
|
"operate-success-prompt": "您的操作已成功执行。",
|
||||||
@ -567,6 +568,7 @@ const resources = {
|
|||||||
used: "Status",
|
used: "Status",
|
||||||
number: "Number",
|
number: "Number",
|
||||||
username: "Username",
|
username: "Username",
|
||||||
|
month: "Month",
|
||||||
"is-admin": "Admin",
|
"is-admin": "Admin",
|
||||||
"used-quota": "Used Quota",
|
"used-quota": "Used Quota",
|
||||||
"is-subscribed": "Subscribed",
|
"is-subscribed": "Subscribed",
|
||||||
@ -577,7 +579,7 @@ const resources = {
|
|||||||
"quota-action": "Quota Change",
|
"quota-action": "Quota Change",
|
||||||
"quota-action-desc":
|
"quota-action-desc":
|
||||||
"Please enter the quota change value (positive for increase, negative for decrease)",
|
"Please enter the quota change value (positive for increase, negative for decrease)",
|
||||||
"subscription-action": "Subscription Gift",
|
"subscription-action": "Subscription Management",
|
||||||
"subscription-action-desc": "Please enter the gift subscription months",
|
"subscription-action-desc": "Please enter the gift subscription months",
|
||||||
"operate-success": "Operate Success",
|
"operate-success": "Operate Success",
|
||||||
"operate-success-prompt":
|
"operate-success-prompt":
|
||||||
@ -870,6 +872,7 @@ const resources = {
|
|||||||
used: "Статус",
|
used: "Статус",
|
||||||
number: "Количество",
|
number: "Количество",
|
||||||
username: "Имя пользователя",
|
username: "Имя пользователя",
|
||||||
|
month: "Месяц",
|
||||||
"is-admin": "Админ",
|
"is-admin": "Админ",
|
||||||
"used-quota": "Использовано",
|
"used-quota": "Использовано",
|
||||||
"is-subscribed": "Подписан",
|
"is-subscribed": "Подписан",
|
||||||
@ -880,7 +883,7 @@ const resources = {
|
|||||||
"quota-action": "Изменение квоты",
|
"quota-action": "Изменение квоты",
|
||||||
"quota-action-desc":
|
"quota-action-desc":
|
||||||
"Пожалуйста, введите значение изменения квоты (положительное для увеличения, отрицательное для уменьшения)",
|
"Пожалуйста, введите значение изменения квоты (положительное для увеличения, отрицательное для уменьшения)",
|
||||||
"subscription-action": "Подарок подписки",
|
"subscription-action": "Управление подпиской",
|
||||||
"subscription-action-desc":
|
"subscription-action-desc":
|
||||||
"Пожалуйста, введите количество месяцев подарочной подписки",
|
"Пожалуйста, введите количество месяцев подарочной подписки",
|
||||||
"operate-success": "Успешно",
|
"operate-success": "Успешно",
|
||||||
|
@ -26,3 +26,14 @@ export function asyncCaller<T>(fn: (...args: any[]) => Promise<T>) {
|
|||||||
return promise;
|
return promise;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNumber(value: string, supportNegative = true): string {
|
||||||
|
return value.replace(
|
||||||
|
supportNegative ? /[^-0-9.]/g : /[^0-9.]/g,
|
||||||
|
""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseNumber(value: string): number {
|
||||||
|
return parseFloat(getNumber(value));
|
||||||
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebsocketAuthForm struct {
|
type WebsocketAuthForm struct {
|
||||||
@ -15,6 +16,23 @@ type WebsocketAuthForm struct {
|
|||||||
Ref string `json:"ref"`
|
Ref string `json:"ref"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseAuth(c *gin.Context, token string) *auth.User {
|
||||||
|
token = strings.TrimSpace(token)
|
||||||
|
if token == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(token, "Bearer ") {
|
||||||
|
token = token[7:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(token, "sk-") {
|
||||||
|
return auth.ParseApiKey(c, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth.ParseToken(c, token)
|
||||||
|
}
|
||||||
|
|
||||||
func ChatAPI(c *gin.Context) {
|
func ChatAPI(c *gin.Context) {
|
||||||
var conn *utils.WebSocket
|
var conn *utils.WebSocket
|
||||||
if conn = utils.NewWebsocket(c, false); conn == nil {
|
if conn = utils.NewWebsocket(c, false); conn == nil {
|
||||||
@ -28,7 +46,7 @@ func ChatAPI(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user := auth.ParseToken(c, form.Token)
|
user := ParseAuth(c, form.Token)
|
||||||
authenticated := user != nil
|
authenticated := user != nil
|
||||||
|
|
||||||
id := auth.GetId(db, user)
|
id := auth.GetId(db, user)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"chat/admin"
|
||||||
"chat/utils"
|
"chat/utils"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -58,6 +59,7 @@ func ThrottleMiddleware() gin.HandlerFunc {
|
|||||||
ip := c.ClientIP()
|
ip := c.ClientIP()
|
||||||
path := c.Request.URL.Path
|
path := c.Request.URL.Path
|
||||||
cache := utils.GetCacheFromContext(c)
|
cache := utils.GetCacheFromContext(c)
|
||||||
|
admin.IncrRequest(cache)
|
||||||
limiter := GetPrefixMap[Limiter](path, limits)
|
limiter := GetPrefixMap[Limiter](path, limits)
|
||||||
if limiter != nil && limiter.RateLimit(c, cache, ip, path) {
|
if limiter != nil && limiter.RateLimit(c, cache, ip, path) {
|
||||||
c.JSON(200, gin.H{"status": false, "reason": "You have sent too many requests. Please try again later."})
|
c.JSON(200, gin.H{"status": false, "reason": "You have sent too many requests. Please try again later."})
|
||||||
|
Loading…
Reference in New Issue
Block a user