Sfoglia il codice sorgente

feat: add redemption remarks

master
fengsilin 1 settimana fa
parent
commit
1c0898257c
7 ha cambiato i file con 220 aggiunte e 20 eliminazioni
  1. +2
    -0
      controller/redemption.go
  2. +159
    -0
      controller/redemption_test.go
  3. +1
    -0
      docs/DATABASE_SCHEMA.md
  4. +1
    -1
      model/redemption.go
  5. +33
    -1
      model/redemption_test.go
  6. +13
    -18
      web/src/components/table/redemptions/RedemptionsColumnDefs.jsx
  7. +11
    -0
      web/src/components/table/redemptions/modals/EditRedemptionModal.jsx

+ 2
- 0
controller/redemption.go Vedi File

@@ -87,6 +87,7 @@ func AddRedemption(c *gin.Context) {
cleanRedemption := model.Redemption{
UserId: c.GetInt("id"),
Name: redemption.Name,
Remark: redemption.Remark,
Key: key,
CreatedTime: common.GetTimestamp(),
Quota: redemption.Quota,
@@ -146,6 +147,7 @@ func UpdateRedemption(c *gin.Context) {
}
// If you add more fields, please also update redemption.Update()
cleanRedemption.Name = redemption.Name
cleanRedemption.Remark = redemption.Remark
cleanRedemption.Quota = redemption.Quota
cleanRedemption.ExpiredTime = redemption.ExpiredTime
}


+ 159
- 0
controller/redemption_test.go Vedi File

@@ -0,0 +1,159 @@
package controller

import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/model"
"github.com/glebarez/sqlite"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
)

func setupRedemptionControllerDB(t *testing.T) *gorm.DB {
t.Helper()

db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
require.NoError(t, err)

sqlDB, _ := db.DB()
sqlDB.SetMaxOpenConns(1)

origDB := model.DB
origLogDB := model.LOG_DB
model.DB = db
model.LOG_DB = db
common.UsingSQLite = true
common.RedisEnabled = false

require.NoError(t, db.AutoMigrate(&model.User{}, &model.Redemption{}, &model.Log{}))

t.Cleanup(func() {
model.DB = origDB
model.LOG_DB = origLogDB
_ = sqlDB.Close()
})

return db
}

func setupRedemptionRouter() *gin.Engine {
gin.SetMode(gin.TestMode)
r := gin.New()
r.Use(func(c *gin.Context) {
c.Set("id", 1)
c.Next()
})

g := r.Group("/api/redemption")
g.POST("/", AddRedemption)
g.PUT("/", UpdateRedemption)

return r
}

func TestAddRedemptionStoresRemark(t *testing.T) {
db := setupRedemptionControllerDB(t)
router := setupRedemptionRouter()

body, err := json.Marshal(map[string]interface{}{
"name": "campaign-a",
"remark": "admin only note",
"quota": 500000,
"count": 1,
"expired_time": 0,
})
require.NoError(t, err)

req := httptest.NewRequest(http.MethodPost, "/api/redemption/", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)

assert.Equal(t, http.StatusOK, w.Code)

var rows []model.Redemption
require.NoError(t, db.Find(&rows).Error)
require.Len(t, rows, 1)
assert.Equal(t, "campaign-a", rows[0].Name)
assert.Equal(t, "admin only note", rows[0].Remark)
}

func TestUpdateRedemptionStoresRemark(t *testing.T) {
db := setupRedemptionControllerDB(t)
router := setupRedemptionRouter()

row := model.Redemption{
Id: 1,
UserId: 1,
Key: "update-remark-key",
Name: "campaign-b",
Remark: "before update",
Status: common.RedemptionCodeStatusEnabled,
Quota: 500000,
CreatedTime: common.GetTimestamp(),
}
require.NoError(t, db.Create(&row).Error)

body, err := json.Marshal(map[string]interface{}{
"id": 1,
"name": "campaign-b",
"remark": "after update",
"quota": 500000,
"expired_time": 0,
})
require.NoError(t, err)

req := httptest.NewRequest(http.MethodPut, "/api/redemption/", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)

assert.Equal(t, http.StatusOK, w.Code)

var stored model.Redemption
require.NoError(t, db.First(&stored, 1).Error)
assert.Equal(t, "after update", stored.Remark)
}

func TestUpdateRedemptionStatusOnlyKeepsRemark(t *testing.T) {
db := setupRedemptionControllerDB(t)
router := setupRedemptionRouter()

row := model.Redemption{
Id: 1,
UserId: 1,
Key: "status-only-remark-key",
Name: "campaign-c",
Remark: "keep this remark",
Status: common.RedemptionCodeStatusEnabled,
Quota: 500000,
CreatedTime: common.GetTimestamp(),
}
require.NoError(t, db.Create(&row).Error)

body, err := json.Marshal(map[string]interface{}{
"id": 1,
"status": common.RedemptionCodeStatusDisabled,
"remark": "should not overwrite",
})
require.NoError(t, err)

req := httptest.NewRequest(http.MethodPut, "/api/redemption/?status_only=true", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)

assert.Equal(t, http.StatusOK, w.Code)

var stored model.Redemption
require.NoError(t, db.First(&stored, 1).Error)
assert.Equal(t, common.RedemptionCodeStatusDisabled, stored.Status)
assert.Equal(t, "keep this remark", stored.Remark)
}

+ 1
- 0
docs/DATABASE_SCHEMA.md Vedi File

@@ -189,6 +189,7 @@
| key | string | 兑换码(32字符,唯一) |
| status | int | 状态:1=启用,2=已使用,3=已禁用 |
| name | string | 兑换码名称 |
| remark | string | 备注 |
| quota | int | 额度值 |
| created_time | int64 | 创建时间 |
| redeemed_time | int64 | 兑换时间 |


+ 1
- 1
model/redemption.go Vedi File

@@ -20,7 +20,7 @@ type Redemption struct {
Key string `json:"key" gorm:"type:char(32);uniqueIndex"`
Status int `json:"status" gorm:"default:1"`
Name string `json:"name" gorm:"index"`
Remark string `json:"remark"`
Remark string `json:"remark" gorm:"index"`
Quota int `json:"quota" gorm:"default:100"`
CreatedTime int64 `json:"created_time" gorm:"bigint"`
RedeemedTime int64 `json:"redeemed_time" gorm:"bigint"`


+ 33
- 1
model/redemption_test.go Vedi File

@@ -276,7 +276,7 @@ func TestRedemptionUpdateRemarkPersistsRemark(t *testing.T) {
assert.Equal(t, "vip-updated", updated.Remark)
}

func TestSearchRedemptionsByRemarkRemark(t *testing.T) {
func TestSearchRedemptionsByRemark(t *testing.T) {
db := setupRedemptionDB(t)

require.NoError(t, db.Create(&Redemption{
@@ -307,3 +307,35 @@ func TestSearchRedemptionsByRemarkRemark(t *testing.T) {
assert.Equal(t, 1, results[0].Id)
assert.Equal(t, "vip benefit", results[0].Remark)
}

func TestSearchRedemptionsByRemarkWithNumericKeyword(t *testing.T) {
db := setupRedemptionDB(t)

require.NoError(t, db.Create(&Redemption{
Id: 1,
UserId: 1,
Key: "remark-search-numeric",
Status: common.RedemptionCodeStatusEnabled,
Name: "starter-pack",
Remark: "123-vip benefit",
Quota: 100,
CreatedTime: common.GetTimestamp(),
}).Error)
require.NoError(t, db.Create(&Redemption{
Id: 2,
UserId: 1,
Key: "remark-search-other-numeric",
Status: common.RedemptionCodeStatusEnabled,
Name: "basic-pack",
Remark: "standard benefit",
Quota: 100,
CreatedTime: common.GetTimestamp(),
}).Error)

results, total, err := SearchRedemptions("123", 0, 10)
require.NoError(t, err)
require.Equal(t, int64(1), total)
require.Len(t, results, 1)
assert.Equal(t, 1, results[0].Id)
assert.Equal(t, "123-vip benefit", results[0].Remark)
}

+ 13
- 18
web/src/components/table/redemptions/RedemptionsColumnDefs.jsx Vedi File

@@ -27,9 +27,6 @@ import {
REDEMPTION_ACTIONS,
} from '../../../constants/redemption.constants';

/**
* Check if redemption code is expired
*/
export const isExpired = (record) => {
return (
record.status === REDEMPTION_STATUS.UNUSED &&
@@ -38,16 +35,10 @@ export const isExpired = (record) => {
);
};

/**
* Render timestamp
*/
const renderTimestamp = (timestamp) => {
return <>{timestamp2string(timestamp)}</>;
};

/**
* Render redemption code status
*/
const renderStatus = (status, record, t) => {
if (isExpired(record)) {
return (
@@ -73,18 +64,12 @@ const renderStatus = (status, record, t) => {
);
};

/**
* Get redemption code table column definitions
*/
export const getRedemptionsColumns = ({
t,
manageRedemption,
copyText,
setEditingRedemption,
setShowEdit,
refresh,
redemptions,
activePage,
showDeleteRedemptionModal,
}) => {
return [
@@ -96,6 +81,17 @@ export const getRedemptionsColumns = ({
title: t('名称'),
dataIndex: 'name',
},
{
title: t('备注'),
dataIndex: 'remark',
render: (text) => {
return (
<div style={{ whiteSpace: 'pre-wrap', minWidth: 180 }}>
{text || ''}
</div>
);
},
},
{
title: t('状态'),
dataIndex: 'status',
@@ -111,7 +107,7 @@ export const getRedemptionsColumns = ({
return (
<div>
<Tag color='grey' shape='circle'>
{renderQuota(parseInt(text))}
{renderQuota(parseInt(text, 10))}
</Tag>
</div>
);
@@ -132,7 +128,7 @@ export const getRedemptionsColumns = ({
},
},
{
title: t('兑换人ID'),
title: t('使用用户ID'),
dataIndex: 'used_user_id',
render: (text) => {
return <div>{text === 0 ? t('无') : text}</div>;
@@ -144,7 +140,6 @@ export const getRedemptionsColumns = ({
fixed: 'right',
width: 205,
render: (text, record) => {
// Create dropdown menu items for more operations
const moreMenuItems = [
{
node: 'item',


+ 11
- 0
web/src/components/table/redemptions/modals/EditRedemptionModal.jsx Vedi File

@@ -60,6 +60,7 @@ const EditRedemptionModal = (props) => {

const getInitValues = () => ({
name: '',
remark: '',
quota: 100000,
count: 1,
expired_time: null,
@@ -261,6 +262,16 @@ const EditRedemptionModal = (props) => {
showClear
/>
</Col>
<Col span={24}>
<Form.TextArea
field='remark'
label={t('备注')}
placeholder={t('请输入备注(仅管理员可见)')}
autosize={{ minRows: 3, maxRows: 6 }}
style={{ width: '100%' }}
showClear
/>
</Col>
</Row>
</Card>



Caricamento…
Annulla
Salva