Skip to content

Commit

Permalink
feat: support gift code operation (#90)
Browse files Browse the repository at this point in the history
  • Loading branch information
zmh-program committed Mar 11, 2024
1 parent 5adc0fd commit de5d863
Show file tree
Hide file tree
Showing 15 changed files with 132 additions and 16 deletions.
21 changes: 21 additions & 0 deletions admin/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,27 @@ func RedeemListAPI(c *gin.Context) {
c.JSON(http.StatusOK, GetRedeemData(db))
}

func RedeemSegmentAPI(c *gin.Context) {
quota := utils.ParseFloat32(c.Query("quota"))
onlyUnused := utils.ParseBool(c.Query("unused"))

db := utils.GetDBFromContext(c)

data, err := GetRedeemSegment(db, quota, onlyUnused)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"status": false,
"error": err.Error(),
})
return

}
c.JSON(http.StatusOK, gin.H{
"status": true,
"data": data,
})
}

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

Expand Down
17 changes: 12 additions & 5 deletions admin/invitation.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,14 @@ func GetInvitationPagination(db *sql.DB, page int64) PaginationForm {
}
}

// get used_user from auth table by `user_id`
rows, err := globals.QueryDb(db, `
SELECT code, quota, type, used, updated_at FROM invitation
ORDER BY id DESC LIMIT ? OFFSET ?
SELECT invitation.code, invitation.quota, invitation.type, invitation.used,
invitation.created_at, invitation.updated_at,
COALESCE(auth.username, '-') as username
FROM invitation
LEFT JOIN auth ON auth.id = invitation.used_id
ORDER BY invitation.id DESC LIMIT ? OFFSET ?
`, pagination, page*pagination)
if err != nil {
return PaginationForm{
Expand All @@ -35,14 +40,16 @@ func GetInvitationPagination(db *sql.DB, page int64) PaginationForm {

for rows.Next() {
var invitation InvitationData
var date []uint8
if err := rows.Scan(&invitation.Code, &invitation.Quota, &invitation.Type, &invitation.Used, &date); err != nil {
var createdAt []uint8
var updatedAt []uint8
if err := rows.Scan(&invitation.Code, &invitation.Quota, &invitation.Type, &invitation.Used, &createdAt, &updatedAt, &invitation.Username); err != nil {
return PaginationForm{
Status: false,
Message: err.Error(),
}
}
invitation.UpdatedAt = utils.ConvertTime(date).Format("2006-01-02 15:04:05")
invitation.CreatedAt = utils.ConvertTime(createdAt).Format("2006-01-02 15:04:05")
invitation.UpdatedAt = utils.ConvertTime(updatedAt).Format("2006-01-02 15:04:05")
invitations = append(invitations, invitation)
}

Expand Down
30 changes: 30 additions & 0 deletions admin/redeem.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,36 @@ func GetRedeemData(db *sql.DB) []RedeemData {
return data
}

func GetRedeemSegment(db *sql.DB, quota float32, onlyUnused bool) ([]string, error) {
var codes []string
var rows *sql.Rows
var err error

if onlyUnused {
rows, err = globals.QueryDb(db, `
SELECT code FROM redeem WHERE quota = ? AND used = 0
`, quota)
} else {
rows, err = globals.QueryDb(db, `
SELECT code FROM redeem WHERE quota = ?
`, quota)
}

if err != nil {
return codes, err
}

for rows.Next() {
var code string
if err := rows.Scan(&code); err != nil {
return codes, err
}
codes = append(codes, code)
}

return codes, nil
}

func GenerateRedeemCodes(db *sql.DB, num int, quota float32) RedeemGenerateResponse {
arr := make([]string, 0)
idx := 0
Expand Down
1 change: 1 addition & 0 deletions admin/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func Register(app *gin.RouterGroup) {
app.POST("/admin/invitation/delete", DeleteInvitationAPI)

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

app.GET("/admin/user/list", UserPaginationAPI)
Expand Down
2 changes: 2 additions & 0 deletions admin/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ type InvitationData struct {
Quota float32 `json:"quota"`
Type string `json:"type"`
Used bool `json:"used"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
Username string `json:"username"`
}

type RedeemData struct {
Expand Down
12 changes: 12 additions & 0 deletions app/src/admin/api/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,18 @@ export async function getRedeemList(): Promise<RedeemResponse> {
}
}

export async function getRedeemSegment(quota: number, only_unused: boolean) {
try {
const response = await axios.get(
`/admin/redeem/segment?quota=${quota}&unused=${only_unused}`,
);
return response.data as RedeemResponse;
} catch (e) {
console.warn(e);
return [];
}
}

export async function generateRedeem(
quota: number,
number: number,
Expand Down
5 changes: 5 additions & 0 deletions app/src/admin/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export type InvitationData = {
quota: number;
type: string;
used: boolean;
username: string;
created_at: string;
updated_at: string;
};

Expand All @@ -69,6 +71,9 @@ export type Redeem = {
};

export type RedeemResponse = Redeem[];
export type RedeemSegmentResponse = CommonResponse & {
data: string[];
};

export type InvitationGenerateResponse = {
status: boolean;
Expand Down
1 change: 0 additions & 1 deletion app/src/assets/pages/chat.less
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@
border-radius: var(--radius);
border: 1px solid hsl(var(--border-hover));
transition: 0.25s linear;
cursor: pointer;

&:hover {
border-color: hsl(var(--border-active));
Expand Down
10 changes: 8 additions & 2 deletions app/src/components/admin/InvitationTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,19 +175,25 @@ function InvitationTable() {
<TableHead>{t("admin.quota")}</TableHead>
<TableHead>{t("admin.type")}</TableHead>
<TableHead>{t("admin.used")}</TableHead>
<TableHead>{t("admin.updated-at")}</TableHead>
<TableHead>{t("admin.used-username")}</TableHead>
<TableHead>{t("admin.created-at")}</TableHead>
<TableHead>{t("admin.used-at")}</TableHead>
<TableHead>{t("admin.action")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{(data.data || []).map((invitation, idx) => (
<TableRow key={idx} className={`whitespace-nowrap`}>
<TableCell>{invitation.code}</TableCell>
<TableCell>{invitation.quota}</TableCell>
<TableCell>
<Badge variant={`outline`}>{invitation.quota}</Badge>
</TableCell>
<TableCell>
<Badge>{invitation.type}</Badge>
</TableCell>
<TableCell>{t(`admin.used-${invitation.used}`)}</TableCell>
<TableCell>{invitation.username || "-"}</TableCell>
<TableCell>{invitation.created_at}</TableCell>
<TableCell>{invitation.updated_at}</TableCell>
<TableCell className={`flex gap-2`}>
<TemporaryButton
Expand Down
7 changes: 6 additions & 1 deletion app/src/components/admin/RedeemTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { useToast } from "@/components/ui/use-toast.ts";
import { Textarea } from "@/components/ui/textarea.tsx";
import { saveAsFile } from "@/utils/dom.ts";
import { useEffectAsync } from "@/utils/hook.ts";
import { Badge } from "@/components/ui/badge.tsx";

function GenerateDialog({ sync }: { sync: () => void }) {
const { t } = useTranslation();
Expand Down Expand Up @@ -157,7 +158,11 @@ function RedeemTable() {
<TableBody>
{data.map((redeem, idx) => (
<TableRow key={idx} className={`whitespace-nowrap`}>
<TableCell>{redeem.quota}</TableCell>
<TableCell>
<Badge variant={`outline`}>
{redeem.quota}
</Badge>
</TableCell>
<TableCell>{redeem.total}</TableCell>
<TableCell>{redeem.used}</TableCell>
</TableRow>
Expand Down
3 changes: 3 additions & 0 deletions app/src/resources/i18n/cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,10 @@
"operate-success-prompt": "您的操作已成功执行。",
"operate-failed": "操作失败",
"operate-failed-prompt": "操作失败,原因:{{reason}}",
"created-at": "创建时间",
"updated-at": "更新时间",
"used-at": "领取时间",
"used-username": "领取用户",
"used-true": "已使用",
"used-false": "未使用",
"generate": "批量生成",
Expand Down
13 changes: 8 additions & 5 deletions app/src/resources/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@
"fast": "Fast",
"english-model": "English Model",
"badges": {
"non-billing": "FREE PASS",
"times-billing": "{{price}}/time",
"token-billing": "Input {{input}}/1k tokens Output {{output}}/1k tokens"
"non-billing": "free",
"times-billing": "{{price}} / time",
"token-billing": "{{input}} / 1k input tokens {{output}} / 1k output tokens"
}
},
"market": {
Expand Down Expand Up @@ -672,9 +672,12 @@
"unban-action-desc": "Are you sure you want to unblock this user?",
"billing": "Income",
"chatnio-format-only": "This format is unique to Chat Nio",
"exit": "Log out of the background",
"exit": "Exit",
"view": "View",
"broadcast-tip": "Notifications will only show the most recent one and will only be notified once. Site announcements can be set in the system settings. The pop-up window will be displayed on the homepage for the first time and subsequent viewing will be supported."
"broadcast-tip": "Notifications will only show the most recent one and will only be notified once. Site announcements can be set in the system settings. The pop-up window will be displayed on the homepage for the first time and subsequent viewing will be supported.",
"created-at": "Creation Time",
"used-at": "Collection time",
"used-username": "Claim User"
},
"mask": {
"title": "Mask Settings",
Expand Down
5 changes: 4 additions & 1 deletion app/src/resources/i18n/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,10 @@
"chatnio-format-only": "このフォーマットはChat Nioに固有です",
"exit": "バックグラウンドからログアウト",
"view": "確認",
"broadcast-tip": "通知には最新の通知のみが表示され、一度だけ通知されます。サイトのお知らせは、システム設定で設定できます。ポップアップウィンドウがホームページに初めて表示され、その後の表示がサポートされます。"
"broadcast-tip": "通知には最新の通知のみが表示され、一度だけ通知されます。サイトのお知らせは、システム設定で設定できます。ポップアップウィンドウがホームページに初めて表示され、その後の表示がサポートされます。",
"created-at": "作成日時",
"used-at": "乗車時間",
"used-username": "ユーザーを請求する"
},
"mask": {
"title": "プリセット設定",
Expand Down
5 changes: 4 additions & 1 deletion app/src/resources/i18n/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,10 @@
"chatnio-format-only": "Этот формат уникален для Chat Nio",
"exit": "Выйти из фонового режима",
"view": "проверить",
"broadcast-tip": "Уведомления будут отображаться только самые последние и будут уведомлены только один раз. Объявления сайта можно задать в системных настройках. Всплывающее окно будет отображаться на главной странице в первый раз и будет поддерживаться последующий просмотр."
"broadcast-tip": "Уведомления будут отображаться только самые последние и будут уведомлены только один раз. Объявления сайта можно задать в системных настройках. Всплывающее окно будет отображаться на главной странице в первый раз и будет поддерживаться последующий просмотр.",
"created-at": "Время создания",
"used-at": "Время награждения",
"used-username": "Получить пользователя"
},
"mask": {
"title": "Настройки маски",
Expand Down
16 changes: 16 additions & 0 deletions utils/char.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,22 @@ func ParseInt64(value string) int64 {
}
}

func ParseFloat32(value string) float32 {
if res, err := strconv.ParseFloat(value, 32); err == nil {
return float32(res)
} else {
return 0
}
}

func ParseBool(value string) bool {
if res, err := strconv.ParseBool(value); err == nil {
return res
} else {
return false
}
}

func ConvertSqlTime(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
}
Expand Down

0 comments on commit de5d863

Please sign in to comment.