feat: add file upload workflow

This commit is contained in:
yml
2026-05-22 18:50:32 +08:00
parent 6f915b37d3
commit 236518ade4
22 changed files with 819 additions and 117 deletions
+38 -36
View File
@@ -3,45 +3,47 @@ package listing
import "time"
type ListingDTO struct {
ID uint64 `json:"id"`
AccountID uint64 `json:"account_id"`
OwnerID uint64 `json:"owner_id"`
OwnerPhone string `json:"owner_phone,omitempty"`
OwnerNickname string `json:"owner_nickname,omitempty"`
Title string `json:"title"`
Description string `json:"description"`
GameName string `json:"game_name"`
ServerRegion string `json:"server_region"`
LoginPlatform string `json:"login_platform"`
RankLevel string `json:"rank_level"`
HafCoinAmount int64 `json:"haf_coin_amount"`
PriceHourly float64 `json:"price_hourly"`
PriceDaily float64 `json:"price_daily"`
PriceWeekly float64 `json:"price_weekly"`
DepositAmount float64 `json:"deposit_amount"`
MinRentHours int `json:"min_rent_hours"`
MaxRentHours int `json:"max_rent_hours"`
Status string `json:"status"`
ReviewStatus string `json:"review_status"`
ReviewReason string `json:"review_reason"`
PublishedAt *time.Time `json:"published_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID uint64 `json:"id"`
AccountID uint64 `json:"account_id"`
OwnerID uint64 `json:"owner_id"`
OwnerPhone string `json:"owner_phone,omitempty"`
OwnerNickname string `json:"owner_nickname,omitempty"`
Title string `json:"title"`
Description string `json:"description"`
GameName string `json:"game_name"`
ServerRegion string `json:"server_region"`
LoginPlatform string `json:"login_platform"`
RankLevel string `json:"rank_level"`
HafCoinAmount int64 `json:"haf_coin_amount"`
ScreenshotURLS []string `json:"screenshot_urls"`
PriceHourly float64 `json:"price_hourly"`
PriceDaily float64 `json:"price_daily"`
PriceWeekly float64 `json:"price_weekly"`
DepositAmount float64 `json:"deposit_amount"`
MinRentHours int `json:"min_rent_hours"`
MaxRentHours int `json:"max_rent_hours"`
Status string `json:"status"`
ReviewStatus string `json:"review_status"`
ReviewReason string `json:"review_reason"`
PublishedAt *time.Time `json:"published_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type CreateRequest struct {
Title string `json:"title" binding:"required"`
Description string `json:"description"`
ServerRegion string `json:"server_region" binding:"required"`
LoginPlatform string `json:"login_platform" binding:"required"`
RankLevel string `json:"rank_level"`
HafCoinAmount int64 `json:"haf_coin_amount"`
PriceHourly float64 `json:"price_hourly" binding:"required"`
PriceDaily float64 `json:"price_daily"`
PriceWeekly float64 `json:"price_weekly"`
DepositAmount float64 `json:"deposit_amount" binding:"required"`
MinRentHours int `json:"min_rent_hours" binding:"required"`
MaxRentHours int `json:"max_rent_hours" binding:"required"`
Title string `json:"title" binding:"required"`
Description string `json:"description"`
ServerRegion string `json:"server_region" binding:"required"`
LoginPlatform string `json:"login_platform" binding:"required"`
RankLevel string `json:"rank_level"`
HafCoinAmount int64 `json:"haf_coin_amount"`
ScreenshotURLS []string `json:"screenshot_urls"`
PriceHourly float64 `json:"price_hourly" binding:"required"`
PriceDaily float64 `json:"price_daily"`
PriceWeekly float64 `json:"price_weekly"`
DepositAmount float64 `json:"deposit_amount" binding:"required"`
MinRentHours int `json:"min_rent_hours" binding:"required"`
MaxRentHours int `json:"max_rent_hours" binding:"required"`
}
type UpdateRequest = CreateRequest
+114 -65
View File
@@ -3,6 +3,7 @@ package listing
import (
"encoding/json"
"errors"
"strings"
"time"
"hfb_sys/backend/internal/model"
@@ -24,16 +25,21 @@ func NewRepository(db *gorm.DB) *Repository {
func (r *Repository) Create(ownerID uint64, req CreateRequest) (*ListingDTO, error) {
var dto *ListingDTO
err := r.db.Transaction(func(tx *gorm.DB) error {
screenshots, err := marshalScreenshots(req.ScreenshotURLS)
if err != nil {
return err
}
account := model.GameAccount{
OwnerID: ownerID,
GameName: "delta_force",
ServerRegion: req.ServerRegion,
LoginPlatform: req.LoginPlatform,
Title: req.Title,
Description: req.Description,
RankLevel: req.RankLevel,
HafCoinAmount: req.HafCoinAmount,
Status: "draft",
OwnerID: ownerID,
GameName: "delta_force",
ServerRegion: req.ServerRegion,
LoginPlatform: req.LoginPlatform,
Title: req.Title,
Description: req.Description,
RankLevel: req.RankLevel,
HafCoinAmount: req.HafCoinAmount,
ScreenshotURLS: screenshots,
Status: "draft",
}
if err := tx.Create(&account).Error; err != nil {
return err
@@ -76,6 +82,11 @@ func (r *Repository) Update(ownerID uint64, listingID uint64, req UpdateRequest)
account.LoginPlatform = req.LoginPlatform
account.RankLevel = req.RankLevel
account.HafCoinAmount = req.HafCoinAmount
screenshots, err := marshalScreenshots(req.ScreenshotURLS)
if err != nil {
return err
}
account.ScreenshotURLS = screenshots
if err := tx.Save(account).Error; err != nil {
return err
}
@@ -386,7 +397,7 @@ func (r *Repository) findDTO(where string, args ...any) (*ListingDTO, error) {
func (r *Repository) baseQuery() *gorm.DB {
return r.db.Table("rental_listings AS l").
Select(`l.*, a.title, a.description, a.game_name, a.server_region, a.login_platform, a.rank_level,
a.haf_coin_amount, COALESCE(u.phone, '') AS owner_phone, COALESCE(u.nickname, '') AS owner_nickname`).
a.haf_coin_amount, a.screenshot_urls, COALESCE(u.phone, '') AS owner_phone, COALESCE(u.nickname, '') AS owner_nickname`).
Joins("JOIN game_accounts AS a ON a.id = l.account_id").
Joins("LEFT JOIN users AS u ON u.id = l.owner_id")
}
@@ -405,15 +416,16 @@ func (r *Repository) findForReviewUpdate(tx *gorm.DB, listingID uint64) (*model.
type listingRow struct {
model.RentalListing
Title string
OwnerPhone string
OwnerNickname string
Description string
GameName string
ServerRegion string
LoginPlatform string
RankLevel string
HafCoinAmount int64
Title string
OwnerPhone string
OwnerNickname string
Description string
GameName string
ServerRegion string
LoginPlatform string
RankLevel string
HafCoinAmount int64
ScreenshotURLS datatypes.JSON
}
func rowsToDTO(rows []listingRow) []ListingDTO {
@@ -426,60 +438,97 @@ func rowsToDTO(rows []listingRow) []ListingDTO {
func (row listingRow) toDTO() ListingDTO {
return ListingDTO{
ID: row.ID,
AccountID: row.AccountID,
OwnerID: row.OwnerID,
OwnerPhone: row.OwnerPhone,
OwnerNickname: row.OwnerNickname,
Title: row.Title,
Description: row.Description,
GameName: row.GameName,
ServerRegion: row.ServerRegion,
LoginPlatform: row.LoginPlatform,
RankLevel: row.RankLevel,
HafCoinAmount: row.HafCoinAmount,
PriceHourly: row.PriceHourly,
PriceDaily: row.PriceDaily,
PriceWeekly: row.PriceWeekly,
DepositAmount: row.DepositAmount,
MinRentHours: row.MinRentHours,
MaxRentHours: row.MaxRentHours,
Status: row.Status,
ReviewStatus: row.ReviewStatus,
ReviewReason: row.ReviewReason,
PublishedAt: row.PublishedAt,
CreatedAt: row.CreatedAt,
UpdatedAt: row.UpdatedAt,
ID: row.ID,
AccountID: row.AccountID,
OwnerID: row.OwnerID,
OwnerPhone: row.OwnerPhone,
OwnerNickname: row.OwnerNickname,
Title: row.Title,
Description: row.Description,
GameName: row.GameName,
ServerRegion: row.ServerRegion,
LoginPlatform: row.LoginPlatform,
RankLevel: row.RankLevel,
HafCoinAmount: row.HafCoinAmount,
ScreenshotURLS: decodeScreenshots(row.ScreenshotURLS),
PriceHourly: row.PriceHourly,
PriceDaily: row.PriceDaily,
PriceWeekly: row.PriceWeekly,
DepositAmount: row.DepositAmount,
MinRentHours: row.MinRentHours,
MaxRentHours: row.MaxRentHours,
Status: row.Status,
ReviewStatus: row.ReviewStatus,
ReviewReason: row.ReviewReason,
PublishedAt: row.PublishedAt,
CreatedAt: row.CreatedAt,
UpdatedAt: row.UpdatedAt,
}
}
func toDTO(account model.GameAccount, listing model.RentalListing) *ListingDTO {
return &ListingDTO{
ID: listing.ID,
AccountID: account.ID,
OwnerID: listing.OwnerID,
Title: account.Title,
Description: account.Description,
GameName: account.GameName,
ServerRegion: account.ServerRegion,
LoginPlatform: account.LoginPlatform,
RankLevel: account.RankLevel,
HafCoinAmount: account.HafCoinAmount,
PriceHourly: listing.PriceHourly,
PriceDaily: listing.PriceDaily,
PriceWeekly: listing.PriceWeekly,
DepositAmount: listing.DepositAmount,
MinRentHours: listing.MinRentHours,
MaxRentHours: listing.MaxRentHours,
Status: listing.Status,
ReviewStatus: listing.ReviewStatus,
ReviewReason: listing.ReviewReason,
PublishedAt: listing.PublishedAt,
CreatedAt: listing.CreatedAt,
UpdatedAt: listing.UpdatedAt,
ID: listing.ID,
AccountID: account.ID,
OwnerID: listing.OwnerID,
Title: account.Title,
Description: account.Description,
GameName: account.GameName,
ServerRegion: account.ServerRegion,
LoginPlatform: account.LoginPlatform,
RankLevel: account.RankLevel,
HafCoinAmount: account.HafCoinAmount,
ScreenshotURLS: decodeScreenshots(account.ScreenshotURLS),
PriceHourly: listing.PriceHourly,
PriceDaily: listing.PriceDaily,
PriceWeekly: listing.PriceWeekly,
DepositAmount: listing.DepositAmount,
MinRentHours: listing.MinRentHours,
MaxRentHours: listing.MaxRentHours,
Status: listing.Status,
ReviewStatus: listing.ReviewStatus,
ReviewReason: listing.ReviewReason,
PublishedAt: listing.PublishedAt,
CreatedAt: listing.CreatedAt,
UpdatedAt: listing.UpdatedAt,
}
}
func marshalScreenshots(urls []string) (datatypes.JSON, error) {
cleaned := make([]string, 0, len(urls))
seen := make(map[string]struct{}, len(urls))
for _, url := range urls {
url = strings.TrimSpace(url)
if url == "" {
continue
}
if _, ok := seen[url]; ok {
continue
}
seen[url] = struct{}{}
cleaned = append(cleaned, url)
if len(cleaned) >= 12 {
break
}
}
raw, err := json.Marshal(cleaned)
if err != nil {
return nil, err
}
return datatypes.JSON(raw), nil
}
func decodeScreenshots(raw datatypes.JSON) []string {
if len(raw) == 0 {
return []string{}
}
var urls []string
if err := json.Unmarshal(raw, &urls); err != nil {
return []string{}
}
return urls
}
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error {
raw, err := json.Marshal(detail)
if err != nil {