Skip to content

Commit

Permalink
add redeem feature
Browse files Browse the repository at this point in the history
  • Loading branch information
zmh-program committed Dec 26, 2023
1 parent 13c1295 commit d278f24
Show file tree
Hide file tree
Showing 29 changed files with 687 additions and 32 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@

chatnio 版本更新:
```shell
docker-compose pull chatnio # pull latest image
docker-compose down
docker-compose pull # pull latest image
docker-compose up -d # start service in background
```

> - MySQL 数据库挂载目录项目 ~/**db**
Expand Down
25 changes: 25 additions & 0 deletions admin/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ type GenerateInvitationForm struct {
Number int `json:"number"`
}

type GenerateRedeemForm struct {
Quota float32 `json:"quota"`
Number int `json:"number"`
}

type QuotaOperationForm struct {
Id int64 `json:"id"`
Quota float32 `json:"quota"`
Expand Down Expand Up @@ -55,6 +60,11 @@ func ErrorAnalysisAPI(c *gin.Context) {
c.JSON(http.StatusOK, GetErrorData(cache))
}

func RedeemListAPI(c *gin.Context) {
db := utils.GetDBFromContext(c)
c.JSON(http.StatusOK, GetRedeemData(db))
}

func InvitationPaginationAPI(c *gin.Context) {
db := utils.GetDBFromContext(c)

Expand All @@ -77,6 +87,21 @@ func GenerateInvitationAPI(c *gin.Context) {
c.JSON(http.StatusOK, GenerateInvitations(db, form.Number, form.Quota, form.Type))
}

func GenerateRedeemAPI(c *gin.Context) {
db := utils.GetDBFromContext(c)

var form GenerateRedeemForm
if err := c.ShouldBindJSON(&form); err != nil {
c.JSON(http.StatusOK, gin.H{
"status": false,
"message": err.Error(),
})
return
}

c.JSON(http.StatusOK, GenerateRedeemCodes(db, form.Number, form.Quota))
}

func UserPaginationAPI(c *gin.Context) {
db := utils.GetDBFromContext(c)

Expand Down
67 changes: 67 additions & 0 deletions admin/redeem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package admin

import (
"chat/utils"
"database/sql"
"fmt"
"strings"
)

func GetRedeemData(db *sql.DB) []RedeemData {
var data []RedeemData

rows, err := db.Query(`
SELECT quota, COUNT(*) AS total, SUM(IF(used = 0, 0, 1)) AS used
FROM redeem
GROUP BY quota
`)
if err != nil {
return data
}

for rows.Next() {
var d RedeemData
if err := rows.Scan(&d.Quota, &d.Total, &d.Used); err != nil {
return data
}
data = append(data, d)
}

return data
}

func GenerateRedeemCodes(db *sql.DB, num int, quota float32) RedeemGenerateResponse {
arr := make([]string, 0)
idx := 0
for idx < num {
code, err := CreateRedeemCode(db, quota)

if err != nil {
return RedeemGenerateResponse{
Status: false,
Message: err.Error(),
}
}
arr = append(arr, code)
idx++
}

return RedeemGenerateResponse{
Status: true,
Data: arr,
}
}

func CreateRedeemCode(db *sql.DB, quota float32) (string, error) {
code := fmt.Sprintf("nio-%s", utils.GenerateChar(32))
_, err := db.Exec(`
INSERT INTO redeem (code, quota) VALUES (?, ?)
`, code, quota)

if err != nil && strings.Contains(err.Error(), "Duplicate entry") {
// code name is duplicate
return CreateRedeemCode(db, quota)
}

return code, err
}
3 changes: 3 additions & 0 deletions admin/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ func Register(app *gin.RouterGroup) {
app.GET("/admin/invitation/list", InvitationPaginationAPI)
app.POST("/admin/invitation/generate", GenerateInvitationAPI)

app.GET("/admin/redeem/list", RedeemListAPI)
app.POST("/admin/redeem/generate", GenerateRedeemAPI)

app.GET("/admin/user/list", UserPaginationAPI)
app.POST("/admin/user/quota", UserQuotaAPI)
app.POST("/admin/user/subscription", UserSubscriptionAPI)
Expand Down
12 changes: 12 additions & 0 deletions admin/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,24 @@ type InvitationData struct {
UpdatedAt string `json:"updated_at"`
}

type RedeemData struct {
Quota float32 `json:"quota"`
Used float32 `json:"used"`
Total float32 `json:"total"`
}

type InvitationGenerateResponse struct {
Status bool `json:"status"`
Message string `json:"message"`
Data []string `json:"data"`
}

type RedeemGenerateResponse struct {
Status bool `json:"status"`
Message string `json:"message"`
Data []string `json:"data"`
}

type UserData struct {
Id int64 `json:"id"`
Username string `json:"username"`
Expand Down
25 changes: 25 additions & 0 deletions app/src/admin/api/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
InvitationGenerateResponse,
InvitationResponse,
ModelChartResponse,
RedeemResponse,
RequestChartResponse,
UserResponse,
} from "@/admin/types.ts";
Expand Down Expand Up @@ -94,6 +95,30 @@ export async function generateInvitation(
return response.data as InvitationGenerateResponse;
}

export async function getRedeemList(): Promise<RedeemResponse> {
const response = await axios.get("/admin/redeem/list");
if (response.status !== 200) {
return [];
}

return response.data as RedeemResponse;
}

export async function generateRedeem(
quota: number,
number: number,
): Promise<InvitationGenerateResponse> {
const response = await axios.post("/admin/redeem/generate", {
quota,
number,
});
if (response.status !== 200) {
return { status: false, data: [], message: "" };
}

return response.data as InvitationGenerateResponse;
}

export async function getUserList(
page: number,
search: string,
Expand Down
2 changes: 1 addition & 1 deletion app/src/admin/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const ChannelTypes: Record<string, string> = {
bing: "New Bing",
palm: "Google PaLM2",
midjourney: "Midjourney",
oneapi: "One API",
oneapi: "Nio API",
};

export const ChannelInfos: Record<string, ChannelInfo> = {
Expand Down
14 changes: 14 additions & 0 deletions app/src/admin/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,26 @@ export type InvitationResponse = {
total: number;
};

export type Redeem = {
quota: number;
used: boolean;
total: number;
};

export type RedeemResponse = Redeem[];

export type InvitationGenerateResponse = {
status: boolean;
data: string[];
message: string;
};

export type RedeemGenerateResponse = {
status: boolean;
data: string[];
message: string;
};

export type UserData = {
id: number;
username: string;
Expand Down
22 changes: 22 additions & 0 deletions app/src/api/redeem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import axios from "axios";
import { getErrorMessage } from "@/utils/base.ts";

export type RedeemResponse = {
status: boolean;
error: string;
quota: number;
};

export async function useRedeem(code: string): Promise<RedeemResponse> {
try {
const resp = await axios.get(`/redeem?code=${code}`);
return resp.data as RedeemResponse;
} catch (e) {
console.debug(e);
return {
status: false,
error: `network error: ${getErrorMessage(e)}`,
quota: 0,
};
}
}
2 changes: 2 additions & 0 deletions app/src/assets/admin/management.less
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
}

.user-row,
.redeem-row,
.invitation-row {
display: flex;
flex-direction: row;
Expand All @@ -58,6 +59,7 @@
}

.user-action,
.redeem-action,
.invitation-action {
display: flex;
margin-top: 1rem;
Expand Down
5 changes: 3 additions & 2 deletions app/src/components/Tips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import { HelpCircle } from "lucide-react";

type TipsProps = {
content: string;
className?: string;
};

function Tips({ content }: TipsProps) {
function Tips({ content, className }: TipsProps) {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className={`tips-icon`} />
<HelpCircle className={`tips-icon ${className}`} />
</TooltipTrigger>
<TooltipContent>
<p>{content}</p>
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/admin/InvitationTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ function InvitationTable() {
<Table>
<TableHeader>
<TableRow className={`select-none whitespace-nowrap`}>
<TableHead>{t("admin.code")}</TableHead>
<TableHead>{t("admin.invitation-code")}</TableHead>
<TableHead>{t("admin.quota")}</TableHead>
<TableHead>{t("admin.type")}</TableHead>
<TableHead>{t("admin.used")}</TableHead>
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/admin/MenuBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function MenuBar() {
icon={<LayoutDashboard />}
path={"/"}
/>
<MenuItem title={t("admin.users")} icon={<Users />} path={"/users"} />
<MenuItem title={t("admin.user")} icon={<Users />} path={"/users"} />
<MenuItem
title={t("admin.broadcast")}
icon={<Radio />}
Expand Down
Loading

0 comments on commit d278f24

Please sign in to comment.