v0.0.13
✨ New Features
Go backend
- Refactored the plan
featuresfield toarray<string>(Issue #11):- Added a new
StringSlicetype inmodel/plan.gothat implementsMarshalJSON,UnmarshalJSON,driver.Valuer, andsql.Scanner— it bridges[]stringbetween the JSON wire format and the DBtextcolumn. - JSON wire format unified to
["A","B","C"]; the DB still stores a JSON string in thetextcolumn, soAutoMigrateis a no-op (zero schema migration). UnmarshalJSON/Scanaccept all three historical shapes (JSON array string, newline-delimited plain text, JSON object) and normalize them to[]string.- Added
Plan.GetFeatures()returning a copy-safe[]stringfor controller serialization so external code never mutates the underlying slice. - Added
model/plan_test.go: 16 cases covering Marshal/Unmarshal/Value/Scan round-trips plus nil, empty, whitespace, CRLF, and invalid JSON edge cases.
- Added a new
GetCurrentPlanreturns features as an array (controller/plan.go): replaceddata["features"] = up.Plan.Features(raw string) withdata["features"] = up.Plan.GetFeatures()so it matches/api/plan/detail. The user-sidePlans.vue::parseFeaturesalready handledArray.isArray, no coordinated front-end change required.Usermodel auto-fills timestamps (model/user.go):- Added
User.BeforeCreate: whenCreatedAt/UpdatedAtare 0 they are auto-filled viahelper.GetTimestamp(). - Added
User.BeforeUpdate: everyUpdates(...)path now refreshesUpdatedAt. - Covers every write path:
Register,CreateUser,UpdateUser,ManageUser,EmailBind,GenerateAccessToken, etc. - Fixes the historical bug where
/api/user/?p=0returnedcreated_at=0and never-refreshedupdated_at.
- Added
Frontend (web/default-pro/)
- Admin plan modal's "Feature description" is now a dynamic row editor (
views/setting/PlanSetting.vue):- Replaced the
<a-textarea>with a.features-editorcontainer: one<a-input allow-clear>per row + a delete button, with a trailing dashed "Add feature" button. - Introduced a
formFeaturesref decoupled fromform.features; addedaddFeature/addFeatureAfter/removeFeature/updateFeaturefor row manipulation. - Pressing Enter on any row auto-appends an empty row; when the last row is empty and the only one, delete is disabled to prevent accidental emptying.
openModalbackfills viautils/plan.featuresFromRecord(backward-compatible with legacy shapes);handleSavePOSTs/PUTs/api/plan/with asanitizeFeaturesList-cleaned array — zero format ambiguity.- Added
.features-editor/.features-editor-row/.features-editor-add/.features-editor-removestyles.
- Replaced the
- Added a "Registration time" column to the admin user list (
views/user/User.vue):- New header column rendering
u.created_at; cell showsYYYY-MM-DD, hover tooltip reveals the full local datetime. - Added
formatDate/formatDateTimehelpers; falls back to-on missing/zero timestamps. - Adjusted
.list-head/.list-rowgrid template to add a 120px column.
- New header column rendering
- New
src/utils/plan.jspure-function helpers + 14node:testunit tests:normalizeFeaturesRaw(raw): normalize any of the historical shapes (array / JSON string / newline text / CRLF / JSON object / null / invalid JSON / numbers) to[]string, dropping blanks.sanitizeFeaturesList(list): trim, filter blanks, return a fresh array without mutating the input.featuresFromRecord(raw): convert the backend field into modal form rows (empty record falls back to a single empty row).buildEmptyFeaturesForm(): returns a single empty-string initial form.
🐛 Bug Fixes
- Fixed the historical
Userbug wherecreated_atwas always 0 andupdated_atnever refreshed: addressed by the newUser.BeforeCreate/BeforeUpdatehooks above, covering every write path. - Fixed the admin plan modal "Feature description" being easy to mistype and rendering empty on the user side: the textarea required admin to hand-type a JSON
["A","B","C"]string, and any typo produced an empty list. The new dynamic-row editor withsanitizeFeaturesListremoves the format ambiguity.
📚 Documentation
docs/API.mdupdated to reflect the newfeaturestype (Issue #11):- Field-table row
features:string→array<string>; wording updated to "list of features, one per row in the user-facing plan card". GET /api/plan/response sample:"features": "功能特性描述"→["API 调用 1000 次/月", "支持 GPT-4o"].POST /api/plan/request sample synced to the array form.
- Field-table row
⚠️ Upgrade Notes
- Zero database migration:
Plan.Featurescolumn type is unchanged (stilltext);StringSliceis purely a Go-side bridge between JSON and the DB column. Existing data — whether plain text like"A\nB\nC"or legacy JSON-object shapes — is normalized to[]stringautomatically on first read. - API compatibility:
GET /api/plan/andGET /api/plan/currentnow returnfeaturesas an array. The user-sidePlans.vue::parseFeaturesalready handledArray.isArray, so existing user-facing code is unaffected. - Admin UX: when an admin opens any plan in Settings → Plan Management, the feature list now renders as one
<a-input>per row with add/delete buttons; legacy data is split into rows automatically. - Backend build: added
model/plan_test.goandmodel/user_test.go, both pure unit tests againstglebarez/sqlitein-memory +gorm.AutoMigrate, no external DB required. - Frontend build: no new dependencies. Recommend
pnpm install && npm run buildbefore publishing.