数据结构优化
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
var DB *sql.DB
|
||||
var DB *gorm.DB
|
||||
|
||||
// JWTSecret is the secret key used for signing JWT tokens
|
||||
var JWTSecret = "your-secret-key-change-this-in-production" // Default value
|
||||
@@ -28,12 +29,20 @@ func InitDB() {
|
||||
JWTSecret = secret
|
||||
}
|
||||
|
||||
DB, err = sql.Open("mysql", dsn)
|
||||
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent), // 禁用 GORM 日志,使用自定义日志
|
||||
// 不使用 NowFunc,因为我们使用模型钩子手动处理 int64 时间戳
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal("Failed to connect to database:", err)
|
||||
}
|
||||
|
||||
if err = DB.Ping(); err != nil {
|
||||
sqlDB, err := DB.DB()
|
||||
if err != nil {
|
||||
log.Fatal("Failed to get underlying sql.DB:", err)
|
||||
}
|
||||
|
||||
if err = sqlDB.Ping(); err != nil {
|
||||
log.Fatal("Failed to ping database:", err)
|
||||
}
|
||||
|
||||
@@ -42,6 +51,9 @@ func InitDB() {
|
||||
|
||||
func CloseDB() {
|
||||
if DB != nil {
|
||||
DB.Close()
|
||||
sqlDB, err := DB.DB()
|
||||
if err == nil {
|
||||
sqlDB.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260109033043-398149f17e54
|
||||
golang.org/x/crypto v0.40.0
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
gorm.io/gorm v1.25.12
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -24,6 +26,8 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
|
||||
@@ -25,6 +25,7 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
@@ -38,6 +39,10 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
@@ -98,3 +103,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
|
||||
@@ -2,7 +2,6 @@ package handlers
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/repositories"
|
||||
"github.com/niangaodev/art-code/utils"
|
||||
)
|
||||
@@ -75,8 +74,7 @@ func GetDashboardStats(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 5. 合作咨询总数
|
||||
var inquiryCount int
|
||||
config.DB.QueryRow("SELECT COUNT(*) FROM inquiries WHERE deleted_at = 0").Scan(&inquiryCount)
|
||||
inquiryCount, _ := repositories.GetInquiryCount()
|
||||
|
||||
// 6. 作品总数
|
||||
workCount, _ := repositories.GetWorkCount()
|
||||
|
||||
BIN
server/ip2region.xdb
Normal file
BIN
server/ip2region.xdb
Normal file
Binary file not shown.
@@ -1,5 +1,11 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Experience struct {
|
||||
Year string `json:"year"`
|
||||
Role string `json:"role"`
|
||||
@@ -8,19 +14,45 @@ type Experience struct {
|
||||
|
||||
// AboutProfile 关于我页面数据模型
|
||||
type AboutProfile struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Avatar string `json:"avatar"`
|
||||
Location string `json:"location"`
|
||||
Bio string `json:"bio"`
|
||||
Email string `json:"email"`
|
||||
Wechat string `json:"wechat"`
|
||||
TechStack string `json:"-"` // Stored as string in DB
|
||||
TechList []string `json:"techStack"` // Exposed as array in JSON
|
||||
ExperiencesStr string `json:"-"` // Stored as string in DB
|
||||
ExperienceList []Experience `json:"experiences"` // Exposed as array in JSON
|
||||
IsPrimary bool `json:"isPrimary"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Name string `json:"name" gorm:"column:name"`
|
||||
Avatar string `json:"avatar" gorm:"column:avatar"`
|
||||
Location string `json:"location" gorm:"column:location"`
|
||||
Bio string `json:"bio" gorm:"column:bio;type:text"`
|
||||
Email string `json:"email" gorm:"column:email"`
|
||||
Wechat string `json:"wechat" gorm:"column:wechat"`
|
||||
TechStack string `json:"-" gorm:"column:tech_stack;type:text"` // Stored as string in DB
|
||||
TechList []string `json:"techStack" gorm:"-"` // Exposed as array in JSON
|
||||
ExperiencesStr string `json:"-" gorm:"column:experiences;type:text"` // Stored as string in DB
|
||||
ExperienceList []Experience `json:"experiences" gorm:"-"` // Exposed as array in JSON
|
||||
IsPrimary bool `json:"isPrimary" gorm:"column:is_primary;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (AboutProfile) TableName() string {
|
||||
return "about_profiles"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (a *AboutProfile) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if a.CreatedAt == 0 {
|
||||
a.CreatedAt = now
|
||||
}
|
||||
if a.UpdatedAt == 0 {
|
||||
a.UpdatedAt = now
|
||||
}
|
||||
if a.DeletedAt == 0 {
|
||||
a.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (a *AboutProfile) BeforeUpdate(tx *gorm.DB) error {
|
||||
a.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,15 +1,37 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AccessLog 访问日志模型
|
||||
type AccessLog struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
IP string `json:"ip"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
Path string `json:"path"`
|
||||
Method string `json:"method"`
|
||||
StatusCode int `json:"status_code"`
|
||||
ResponseTime int64 `json:"response_time"` // 毫秒
|
||||
Region string `json:"region"` // IP归属地
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
DeletedAt int64 `json:"deleted_at"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
IP string `json:"ip" gorm:"column:ip;index"`
|
||||
UserAgent string `json:"user_agent" gorm:"column:user_agent"`
|
||||
Path string `json:"path" gorm:"column:path;index"`
|
||||
Method string `json:"method" gorm:"column:method"`
|
||||
StatusCode int `json:"status_code" gorm:"column:status_code"`
|
||||
ResponseTime int64 `json:"response_time" gorm:"column:response_time"` // 毫秒
|
||||
Region string `json:"region" gorm:"column:region"` // IP归属地
|
||||
CreatedAt int64 `json:"created_at" gorm:"column:created_at"`
|
||||
DeletedAt int64 `json:"deleted_at" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (AccessLog) TableName() string {
|
||||
return "access_logs"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (a *AccessLog) BeforeCreate(tx *gorm.DB) error {
|
||||
if a.CreatedAt == 0 {
|
||||
a.CreatedAt = time.Now().Unix()
|
||||
}
|
||||
if a.DeletedAt == 0 {
|
||||
a.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,13 +1,45 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Category 分类模型
|
||||
type Category struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Description string `json:"description"`
|
||||
SortOrder uint `json:"sortOrder"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Name string `json:"name" gorm:"column:name"`
|
||||
Slug string `json:"slug" gorm:"column:slug;uniqueIndex"`
|
||||
Description string `json:"description" gorm:"column:description;type:text"`
|
||||
SortOrder uint `json:"sortOrder" gorm:"column:sort_order;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Category) TableName() string {
|
||||
return "categories"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (c *Category) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if c.CreatedAt == 0 {
|
||||
c.CreatedAt = now
|
||||
}
|
||||
if c.UpdatedAt == 0 {
|
||||
c.UpdatedAt = now
|
||||
}
|
||||
if c.DeletedAt == 0 {
|
||||
c.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (c *Category) BeforeUpdate(tx *gorm.DB) error {
|
||||
c.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,22 +1,67 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Column 专栏模型
|
||||
type Column struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Cover string `json:"cover"`
|
||||
IsActive int `json:"isActive"`
|
||||
SortOrder uint `json:"sortOrder"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Name string `json:"name" gorm:"column:name"`
|
||||
Description string `json:"description" gorm:"column:description;type:text"`
|
||||
Cover string `json:"cover" gorm:"column:cover"`
|
||||
IsActive int `json:"isActive" gorm:"column:is_active;default:1"`
|
||||
SortOrder uint `json:"sortOrder" gorm:"column:sort_order;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Column) TableName() string {
|
||||
return "columns"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (c *Column) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if c.CreatedAt == 0 {
|
||||
c.CreatedAt = now
|
||||
}
|
||||
if c.UpdatedAt == 0 {
|
||||
c.UpdatedAt = now
|
||||
}
|
||||
if c.DeletedAt == 0 {
|
||||
c.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (c *Column) BeforeUpdate(tx *gorm.DB) error {
|
||||
c.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ColumnPost 专栏文章关联模型
|
||||
type ColumnPost struct {
|
||||
ColumnID uint `json:"columnId"`
|
||||
PostID uint `json:"postId"`
|
||||
SortOrder uint `json:"sortOrder"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
ColumnID uint `json:"columnId" gorm:"primaryKey;column:column_id"`
|
||||
PostID uint `json:"postId" gorm:"primaryKey;column:post_id"`
|
||||
SortOrder uint `json:"sortOrder" gorm:"column:sort_order;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (ColumnPost) TableName() string {
|
||||
return "column_posts"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (cp *ColumnPost) BeforeCreate(tx *gorm.DB) error {
|
||||
if cp.CreatedAt == 0 {
|
||||
cp.CreatedAt = time.Now().Unix()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,27 +1,85 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Inquiry 合作咨询
|
||||
type Inquiry struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Company string `json:"company"`
|
||||
ContactMethod string `json:"contactMethod"` // email, wechat, phone
|
||||
ContactValue string `json:"contactValue"`
|
||||
Budget string `json:"budget"`
|
||||
Description string `json:"description"`
|
||||
Status int `json:"status"` // 0-Unread, 1-Read, 2-Contacted
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Name string `json:"name" gorm:"column:name"`
|
||||
Company string `json:"company" gorm:"column:company"`
|
||||
ContactMethod string `json:"contactMethod" gorm:"column:contact_method"` // email, wechat, phone
|
||||
ContactValue string `json:"contactValue" gorm:"column:contact_value"`
|
||||
Budget string `json:"budget" gorm:"column:budget"`
|
||||
Description string `json:"description" gorm:"column:description;type:text"`
|
||||
Status int `json:"status" gorm:"column:status;default:0"` // 0-Unread, 1-Read, 2-Contacted
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Inquiry) TableName() string {
|
||||
return "inquiries"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (i *Inquiry) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if i.CreatedAt == 0 {
|
||||
i.CreatedAt = now
|
||||
}
|
||||
if i.UpdatedAt == 0 {
|
||||
i.UpdatedAt = now
|
||||
}
|
||||
if i.DeletedAt == 0 {
|
||||
i.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (i *Inquiry) BeforeUpdate(tx *gorm.DB) error {
|
||||
i.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmailSuffix 邮箱后缀配置
|
||||
type EmailSuffix struct {
|
||||
ID uint `json:"id"`
|
||||
Suffix string `json:"suffix"`
|
||||
IsActive bool `json:"isActive"`
|
||||
SortOrder int `json:"sortOrder"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Suffix string `json:"suffix" gorm:"column:suffix;uniqueIndex"`
|
||||
IsActive bool `json:"isActive" gorm:"column:is_active;default:1"`
|
||||
SortOrder int `json:"sortOrder" gorm:"column:sort_order;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (EmailSuffix) TableName() string {
|
||||
return "email_suffixes"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (es *EmailSuffix) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if es.CreatedAt == 0 {
|
||||
es.CreatedAt = now
|
||||
}
|
||||
if es.UpdatedAt == 0 {
|
||||
es.UpdatedAt = now
|
||||
}
|
||||
if es.DeletedAt == 0 {
|
||||
es.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (es *EmailSuffix) BeforeUpdate(tx *gorm.DB) error {
|
||||
es.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,18 +1,40 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// OperationLog 操作日志模型
|
||||
type OperationLog struct {
|
||||
ID uint `json:"id"`
|
||||
UserID uint `json:"userId"`
|
||||
Username string `json:"username"`
|
||||
IP string `json:"ip"`
|
||||
Path string `json:"path"`
|
||||
Method string `json:"method"`
|
||||
Params string `json:"params"`
|
||||
Status int `json:"status"`
|
||||
Duration int `json:"duration"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
UserID uint `json:"userId" gorm:"column:user_id;index"`
|
||||
Username string `json:"username" gorm:"column:username"`
|
||||
IP string `json:"ip" gorm:"column:ip"`
|
||||
Path string `json:"path" gorm:"column:path;index"`
|
||||
Method string `json:"method" gorm:"column:method"`
|
||||
Params string `json:"params" gorm:"column:params;type:text"`
|
||||
Status int `json:"status" gorm:"column:status"`
|
||||
Duration int `json:"duration" gorm:"column:duration"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (OperationLog) TableName() string {
|
||||
return "operation_logs"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (o *OperationLog) BeforeCreate(tx *gorm.DB) error {
|
||||
if o.CreatedAt == 0 {
|
||||
o.CreatedAt = time.Now().Unix()
|
||||
}
|
||||
if o.DeletedAt == 0 {
|
||||
o.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OperationLogResponse 操作日志响应模型
|
||||
|
||||
@@ -1,14 +1,46 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Permission 权限模型
|
||||
type Permission struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Resource string `json:"resource"`
|
||||
Action string `json:"action"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Name string `json:"name" gorm:"column:name"`
|
||||
Resource string `json:"resource" gorm:"column:resource"`
|
||||
Action string `json:"action" gorm:"column:action"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Permission) TableName() string {
|
||||
return "permissions"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (p *Permission) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if p.CreatedAt == 0 {
|
||||
p.CreatedAt = now
|
||||
}
|
||||
if p.UpdatedAt == 0 {
|
||||
p.UpdatedAt = now
|
||||
}
|
||||
if p.DeletedAt == 0 {
|
||||
p.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (p *Permission) BeforeUpdate(tx *gorm.DB) error {
|
||||
p.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
// PermissionResponse 权限响应模型
|
||||
|
||||
@@ -1,20 +1,52 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Post 博客文章模型
|
||||
type Post struct {
|
||||
ID uint `json:"id"`
|
||||
OriginalID string `json:"originalId,omitempty"` // For backward compatibility
|
||||
Title string `json:"title"`
|
||||
CategoryID uint `json:"categoryId"`
|
||||
Category *Category `json:"category,omitempty"` // For join query result
|
||||
Excerpt string `json:"excerpt"`
|
||||
Content string `json:"content"`
|
||||
ReadCount uint `json:"readCount"`
|
||||
IsPublished int `json:"isPublished"` // 0: draft, 1: published
|
||||
Tags []Tag `json:"tags"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
OriginalID string `json:"originalId,omitempty" gorm:"-"` // For backward compatibility
|
||||
Title string `json:"title" gorm:"column:title"`
|
||||
CategoryID uint `json:"categoryId" gorm:"column:category_id"`
|
||||
Category *Category `json:"category,omitempty" gorm:"foreignKey:CategoryID"` // For join query result
|
||||
Excerpt string `json:"excerpt" gorm:"column:excerpt"`
|
||||
Content string `json:"content" gorm:"column:content;type:text"`
|
||||
ReadCount uint `json:"readCount" gorm:"column:read_count;default:0"`
|
||||
IsPublished int `json:"isPublished" gorm:"column:is_published;default:0"` // 0: draft, 1: published
|
||||
Tags []Tag `json:"tags" gorm:"many2many:post_tags;joinForeignKey:post_id;joinReferences:tag_id"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Post) TableName() string {
|
||||
return "posts"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (p *Post) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if p.CreatedAt == 0 {
|
||||
p.CreatedAt = now
|
||||
}
|
||||
if p.UpdatedAt == 0 {
|
||||
p.UpdatedAt = now
|
||||
}
|
||||
if p.DeletedAt == 0 {
|
||||
p.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (p *Post) BeforeUpdate(tx *gorm.DB) error {
|
||||
p.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostResponse 博客文章响应模型
|
||||
@@ -32,17 +64,34 @@ type PostResponse struct {
|
||||
|
||||
// PostHistory 文章历史记录模型
|
||||
type PostHistory struct {
|
||||
ID uint `json:"id"`
|
||||
PostID uint `json:"postId"`
|
||||
Version int `json:"version"`
|
||||
Title string `json:"title"`
|
||||
CategoryID uint `json:"categoryId"`
|
||||
Excerpt string `json:"excerpt"`
|
||||
Content string `json:"content"`
|
||||
IsPublished int `json:"isPublished"`
|
||||
ModifiedBy uint `json:"modifiedBy"`
|
||||
ModifiedAt int64 `json:"modifiedAt"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
PostID uint `json:"postId" gorm:"column:post_id;index"`
|
||||
Version int `json:"version" gorm:"column:version"`
|
||||
Title string `json:"title" gorm:"column:title"`
|
||||
CategoryID uint `json:"categoryId" gorm:"column:category_id"`
|
||||
Excerpt string `json:"excerpt" gorm:"column:excerpt"`
|
||||
Content string `json:"content" gorm:"column:content;type:text"`
|
||||
IsPublished int `json:"isPublished" gorm:"column:is_published"`
|
||||
ModifiedBy uint `json:"modifiedBy" gorm:"column:modified_by"`
|
||||
ModifiedAt int64 `json:"modifiedAt" gorm:"column:modified_at"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (PostHistory) TableName() string {
|
||||
return "post_history"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (ph *PostHistory) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if ph.CreatedAt == 0 {
|
||||
ph.CreatedAt = now
|
||||
}
|
||||
if ph.ModifiedAt == 0 {
|
||||
ph.ModifiedAt = now
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostHistoryResponse 文章历史记录响应模型
|
||||
|
||||
@@ -1,14 +1,46 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Role 角色模型
|
||||
type Role struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Permissions []Permission `json:"permissions,omitempty"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Name string `json:"name" gorm:"column:name;uniqueIndex"`
|
||||
Description string `json:"description" gorm:"column:description"`
|
||||
Permissions []Permission `json:"permissions,omitempty" gorm:"many2many:role_permissions;joinForeignKey:role_id;joinReferences:permission_id"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Role) TableName() string {
|
||||
return "roles"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (r *Role) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if r.CreatedAt == 0 {
|
||||
r.CreatedAt = now
|
||||
}
|
||||
if r.UpdatedAt == 0 {
|
||||
r.UpdatedAt = now
|
||||
}
|
||||
if r.DeletedAt == 0 {
|
||||
r.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (r *Role) BeforeUpdate(tx *gorm.DB) error {
|
||||
r.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
// RoleResponse 角色响应模型
|
||||
|
||||
@@ -1,28 +1,86 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Testimonial 客户评价模型
|
||||
type Testimonial struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
Avatar string `json:"avatar"`
|
||||
Rating uint8 `json:"rating"`
|
||||
SortOrder uint `json:"sortOrder"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Name string `json:"name" gorm:"column:name"`
|
||||
Role string `json:"role" gorm:"column:role"`
|
||||
Content string `json:"content" gorm:"column:content;type:text"`
|
||||
Avatar string `json:"avatar" gorm:"column:avatar"`
|
||||
Rating uint8 `json:"rating" gorm:"column:rating;default:5"`
|
||||
SortOrder uint `json:"sortOrder" gorm:"column:sort_order;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Testimonial) TableName() string {
|
||||
return "testimonials"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (t *Testimonial) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if t.CreatedAt == 0 {
|
||||
t.CreatedAt = now
|
||||
}
|
||||
if t.UpdatedAt == 0 {
|
||||
t.UpdatedAt = now
|
||||
}
|
||||
if t.DeletedAt == 0 {
|
||||
t.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (t *Testimonial) BeforeUpdate(tx *gorm.DB) error {
|
||||
t.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Partner 合作伙伴模型
|
||||
type Partner struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Logo string `json:"logo"`
|
||||
Description string `json:"description"`
|
||||
URL string `json:"url"`
|
||||
SortOrder uint `json:"sortOrder"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Name string `json:"name" gorm:"column:name"`
|
||||
Logo string `json:"logo" gorm:"column:logo"`
|
||||
Description string `json:"description" gorm:"column:description;type:text"`
|
||||
URL string `json:"url" gorm:"column:url"`
|
||||
SortOrder uint `json:"sortOrder" gorm:"column:sort_order;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Partner) TableName() string {
|
||||
return "partners"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (p *Partner) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if p.CreatedAt == 0 {
|
||||
p.CreatedAt = now
|
||||
}
|
||||
if p.UpdatedAt == 0 {
|
||||
p.UpdatedAt = now
|
||||
}
|
||||
if p.DeletedAt == 0 {
|
||||
p.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (p *Partner) BeforeUpdate(tx *gorm.DB) error {
|
||||
p.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,14 +1,46 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Setting 系统配置模型
|
||||
type Setting struct {
|
||||
ID uint `json:"id"`
|
||||
KeyName string `json:"keyName"`
|
||||
Value string `json:"value"`
|
||||
Description string `json:"description"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
KeyName string `json:"keyName" gorm:"column:key_name;uniqueIndex"`
|
||||
Value string `json:"value" gorm:"column:value;type:text"`
|
||||
Description string `json:"description" gorm:"column:description"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Setting) TableName() string {
|
||||
return "settings"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (s *Setting) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if s.CreatedAt == 0 {
|
||||
s.CreatedAt = now
|
||||
}
|
||||
if s.UpdatedAt == 0 {
|
||||
s.UpdatedAt = now
|
||||
}
|
||||
if s.DeletedAt == 0 {
|
||||
s.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (s *Setting) BeforeUpdate(tx *gorm.DB) error {
|
||||
s.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
// SettingResponse 系统配置响应模型
|
||||
|
||||
@@ -1,16 +1,48 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Snippet 代码片段模型
|
||||
type Snippet struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Code string `json:"code"`
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
ViewCount uint `json:"viewCount"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID string `json:"id" gorm:"primaryKey;column:id"`
|
||||
Title string `json:"title" gorm:"column:title"`
|
||||
Code string `json:"code" gorm:"column:code;type:text"`
|
||||
Type string `json:"type" gorm:"column:type"`
|
||||
Description string `json:"description" gorm:"column:description;type:text"`
|
||||
ViewCount uint `json:"viewCount" gorm:"column:view_count;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Snippet) TableName() string {
|
||||
return "snippets"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (s *Snippet) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if s.CreatedAt == 0 {
|
||||
s.CreatedAt = now
|
||||
}
|
||||
if s.UpdatedAt == 0 {
|
||||
s.UpdatedAt = now
|
||||
}
|
||||
if s.DeletedAt == 0 {
|
||||
s.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (s *Snippet) BeforeUpdate(tx *gorm.DB) error {
|
||||
s.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
// SnippetResponse 代码片段响应模型
|
||||
|
||||
@@ -1,18 +1,63 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Tag 标签模型
|
||||
type Tag struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Name string `json:"name" gorm:"column:name"`
|
||||
Slug string `json:"slug" gorm:"column:slug;uniqueIndex"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Tag) TableName() string {
|
||||
return "tags"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (t *Tag) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if t.CreatedAt == 0 {
|
||||
t.CreatedAt = now
|
||||
}
|
||||
if t.UpdatedAt == 0 {
|
||||
t.UpdatedAt = now
|
||||
}
|
||||
if t.DeletedAt == 0 {
|
||||
t.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (t *Tag) BeforeUpdate(tx *gorm.DB) error {
|
||||
t.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostTag 文章标签关联模型
|
||||
type PostTag struct {
|
||||
PostID string `json:"postId"`
|
||||
TagID uint `json:"tagId"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
PostID uint `json:"postId" gorm:"primaryKey;column:post_id"`
|
||||
TagID uint `json:"tagId" gorm:"primaryKey;column:tag_id"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (PostTag) TableName() string {
|
||||
return "post_tags"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (pt *PostTag) BeforeCreate(tx *gorm.DB) error {
|
||||
if pt.CreatedAt == 0 {
|
||||
pt.CreatedAt = time.Now().Unix()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,18 +1,50 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// User 用户模型
|
||||
type User struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Username string `json:"username" gorm:"column:username;uniqueIndex;not null"`
|
||||
Email string `json:"email" gorm:"column:email"`
|
||||
Password string `json:"password,omitempty" gorm:"-"` // Virtual field for input
|
||||
PasswordHash string `json:"-"`
|
||||
RoleID uint `json:"roleId"`
|
||||
Role string `json:"role"` // 保持兼容,或者作为Role Name
|
||||
IsActive int `json:"isActive"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
PasswordHash string `json:"-" gorm:"column:password_hash"`
|
||||
RoleID uint `json:"roleId" gorm:"column:role_id"`
|
||||
Role string `json:"role" gorm:"column:role"` // 保持兼容,或者作为Role Name
|
||||
IsActive int `json:"isActive" gorm:"column:is_active;default:1"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (User) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (u *User) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if u.CreatedAt == 0 {
|
||||
u.CreatedAt = now
|
||||
}
|
||||
if u.UpdatedAt == 0 {
|
||||
u.UpdatedAt = now
|
||||
}
|
||||
if u.DeletedAt == 0 {
|
||||
u.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (u *User) BeforeUpdate(tx *gorm.DB) error {
|
||||
u.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserResponse 用户响应模型
|
||||
|
||||
@@ -1,12 +1,34 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserAccessLog 用户访问日志模型
|
||||
type UserAccessLog struct {
|
||||
ID uint `json:"id"`
|
||||
UserID uint `json:"user_id"` // 用户ID(未登录用户为0)
|
||||
UserIP string `json:"user_ip"` // 用户IP地址
|
||||
UserLocation string `json:"user_location"` // 用户归属地
|
||||
ArticleID uint `json:"article_id"` // 访问的文章ID
|
||||
AccessTime int64 `json:"access_time"` // 访问时间
|
||||
DeletedAt int64 `json:"deleted_at"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
UserID uint `json:"user_id" gorm:"column:user_id;index"` // 用户ID(未登录用户为0)
|
||||
UserIP string `json:"user_ip" gorm:"column:user_ip;index"` // 用户IP地址
|
||||
UserLocation string `json:"user_location" gorm:"column:user_location"` // 用户归属地
|
||||
ArticleID uint `json:"article_id" gorm:"column:article_id;index"` // 访问的文章ID
|
||||
AccessTime int64 `json:"access_time" gorm:"column:access_time"` // 访问时间
|
||||
DeletedAt int64 `json:"deleted_at" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (UserAccessLog) TableName() string {
|
||||
return "user_access_logs"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (u *UserAccessLog) BeforeCreate(tx *gorm.DB) error {
|
||||
if u.AccessTime == 0 {
|
||||
u.AccessTime = time.Now().Unix()
|
||||
}
|
||||
if u.DeletedAt == 0 {
|
||||
u.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,38 +1,102 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Work 作品模型
|
||||
type Work struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Category string `json:"category"`
|
||||
Year string `json:"year"`
|
||||
HeroImg string `json:"heroImg"`
|
||||
Description string `json:"desc"`
|
||||
IsFeatured int `json:"isFeatured"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID string `json:"id" gorm:"primaryKey;column:id"`
|
||||
Title string `json:"title" gorm:"column:title"`
|
||||
Category string `json:"category" gorm:"column:category"`
|
||||
Year string `json:"year" gorm:"column:year"`
|
||||
HeroImg string `json:"heroImg" gorm:"column:hero_img"`
|
||||
Description string `json:"desc" gorm:"column:description;type:text"`
|
||||
IsFeatured int `json:"isFeatured" gorm:"column:is_featured;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Work) TableName() string {
|
||||
return "works"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (w *Work) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if w.CreatedAt == 0 {
|
||||
w.CreatedAt = now
|
||||
}
|
||||
if w.UpdatedAt == 0 {
|
||||
w.UpdatedAt = now
|
||||
}
|
||||
if w.DeletedAt == 0 {
|
||||
w.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (w *Work) BeforeUpdate(tx *gorm.DB) error {
|
||||
w.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
// WorkTechStack 作品技术栈模型
|
||||
type WorkTechStack struct {
|
||||
ID uint `json:"id"`
|
||||
WorkID string `json:"workId"`
|
||||
Category string `json:"category"`
|
||||
Item string `json:"item"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
WorkID string `json:"workId" gorm:"column:work_id;index"`
|
||||
Category string `json:"category" gorm:"column:category"`
|
||||
Item string `json:"item" gorm:"column:item"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (WorkTechStack) TableName() string {
|
||||
return "work_tech_stack"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (wts *WorkTechStack) BeforeCreate(tx *gorm.DB) error {
|
||||
if wts.CreatedAt == 0 {
|
||||
wts.CreatedAt = time.Now().Unix()
|
||||
}
|
||||
if wts.DeletedAt == 0 {
|
||||
wts.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WorkGallery 作品图库模型
|
||||
type WorkGallery struct {
|
||||
ID uint `json:"id"`
|
||||
WorkID string `json:"workId"`
|
||||
ImageURL string `json:"imageUrl"`
|
||||
SortOrder uint `json:"sortOrder"`
|
||||
Description string `json:"description"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
DeletedAt int64 `json:"deletedAt"`
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
WorkID string `json:"workId" gorm:"column:work_id;index"`
|
||||
ImageURL string `json:"imageUrl" gorm:"column:image_url"`
|
||||
SortOrder uint `json:"sortOrder" gorm:"column:sort_order;default:0"`
|
||||
Description string `json:"description" gorm:"column:description"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (WorkGallery) TableName() string {
|
||||
return "work_gallery"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (wg *WorkGallery) BeforeCreate(tx *gorm.DB) error {
|
||||
if wg.CreatedAt == 0 {
|
||||
wg.CreatedAt = time.Now().Unix()
|
||||
}
|
||||
if wg.DeletedAt == 0 {
|
||||
wg.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WorkResponse 作品响应模型,包含关联数据
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"strconv"
|
||||
@@ -10,6 +9,7 @@ import (
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// unescapeJSONString 解码转义的 JSON 字符串
|
||||
@@ -60,35 +60,17 @@ func unescapeJSONString(s string) (string, error) {
|
||||
|
||||
// GetPrimaryAboutProfile 获取主页个人资料
|
||||
func GetPrimaryAboutProfile() (*models.AboutProfile, error) {
|
||||
query := `
|
||||
SELECT id, name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at, deleted_at
|
||||
FROM about_profiles
|
||||
WHERE is_primary = TRUE AND deleted_at = 0
|
||||
LIMIT 1
|
||||
`
|
||||
row := config.DB.QueryRow(query)
|
||||
|
||||
var profile models.AboutProfile
|
||||
if err := row.Scan(
|
||||
&profile.ID,
|
||||
&profile.Name,
|
||||
&profile.Avatar,
|
||||
&profile.Location,
|
||||
&profile.Bio,
|
||||
&profile.Email,
|
||||
&profile.Wechat,
|
||||
&profile.TechStack,
|
||||
&profile.ExperiencesStr,
|
||||
&profile.IsPrimary,
|
||||
&profile.CreatedAt,
|
||||
&profile.UpdatedAt,
|
||||
&profile.DeletedAt,
|
||||
); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
err := config.DB.Model(&models.AboutProfile{}).
|
||||
Where("is_primary = ? AND deleted_at = ?", true, 0).
|
||||
First(&profile).Error
|
||||
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
// If no primary profile, try to get the first one
|
||||
return GetFirstAboutProfile()
|
||||
}
|
||||
log.Printf("Error scanning primary about profile: %v", err)
|
||||
log.Printf("Error getting primary about profile: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -115,35 +97,17 @@ func GetPrimaryAboutProfile() (*models.AboutProfile, error) {
|
||||
|
||||
// GetFirstAboutProfile 获取第一个个人资料(备用)
|
||||
func GetFirstAboutProfile() (*models.AboutProfile, error) {
|
||||
query := `
|
||||
SELECT id, name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at, deleted_at
|
||||
FROM about_profiles
|
||||
WHERE deleted_at = 0
|
||||
ORDER BY id ASC
|
||||
LIMIT 1
|
||||
`
|
||||
row := config.DB.QueryRow(query)
|
||||
|
||||
var profile models.AboutProfile
|
||||
if err := row.Scan(
|
||||
&profile.ID,
|
||||
&profile.Name,
|
||||
&profile.Avatar,
|
||||
&profile.Location,
|
||||
&profile.Bio,
|
||||
&profile.Email,
|
||||
&profile.Wechat,
|
||||
&profile.TechStack,
|
||||
&profile.ExperiencesStr,
|
||||
&profile.IsPrimary,
|
||||
&profile.CreatedAt,
|
||||
&profile.UpdatedAt,
|
||||
&profile.DeletedAt,
|
||||
); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
err := config.DB.Model(&models.AboutProfile{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("id ASC").
|
||||
First(&profile).Error
|
||||
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error scanning first about profile: %v", err)
|
||||
log.Printf("Error getting first about profile: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -182,34 +146,16 @@ func GetFirstAboutProfile() (*models.AboutProfile, error) {
|
||||
|
||||
// GetAboutProfileByID 根据 ID 获取个人资料
|
||||
func GetAboutProfileByID(id uint) (*models.AboutProfile, error) {
|
||||
query := `
|
||||
SELECT id, name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at, deleted_at
|
||||
FROM about_profiles
|
||||
WHERE id = ? AND deleted_at = 0
|
||||
LIMIT 1
|
||||
`
|
||||
row := config.DB.QueryRow(query, id)
|
||||
|
||||
var profile models.AboutProfile
|
||||
if err := row.Scan(
|
||||
&profile.ID,
|
||||
&profile.Name,
|
||||
&profile.Avatar,
|
||||
&profile.Location,
|
||||
&profile.Bio,
|
||||
&profile.Email,
|
||||
&profile.Wechat,
|
||||
&profile.TechStack,
|
||||
&profile.ExperiencesStr,
|
||||
&profile.IsPrimary,
|
||||
&profile.CreatedAt,
|
||||
&profile.UpdatedAt,
|
||||
&profile.DeletedAt,
|
||||
); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
err := config.DB.Model(&models.AboutProfile{}).
|
||||
Where("id = ? AND deleted_at = ?", id, 0).
|
||||
First(&profile).Error
|
||||
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error scanning about profile by ID: %v", err)
|
||||
log.Printf("Error getting about profile by ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -248,34 +194,17 @@ func GetAboutProfileByID(id uint) (*models.AboutProfile, error) {
|
||||
|
||||
// GetAllAboutProfiles 获取所有个人资料(管理用)
|
||||
func GetAllAboutProfiles() ([]models.AboutProfile, error) {
|
||||
query := "SELECT id, name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at, deleted_at FROM about_profiles WHERE deleted_at = 0"
|
||||
rows, err := config.DB.Query(query)
|
||||
var profiles []models.AboutProfile
|
||||
err := config.DB.Model(&models.AboutProfile{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Find(&profiles).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying about profiles: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var profiles []models.AboutProfile
|
||||
for rows.Next() {
|
||||
var p models.AboutProfile
|
||||
if err := rows.Scan(
|
||||
&p.ID,
|
||||
&p.Name,
|
||||
&p.Avatar,
|
||||
&p.Location,
|
||||
&p.Bio,
|
||||
&p.Email,
|
||||
&p.Wechat,
|
||||
&p.TechStack,
|
||||
&p.ExperiencesStr,
|
||||
&p.IsPrimary,
|
||||
&p.CreatedAt,
|
||||
&p.UpdatedAt,
|
||||
&p.DeletedAt,
|
||||
); err != nil {
|
||||
continue
|
||||
}
|
||||
for i := range profiles {
|
||||
var p = &profiles[i]
|
||||
// Unmarshal JSON - 确保 TechList 和 ExperienceList 始终是数组而不是 nil
|
||||
p.TechList = []string{}
|
||||
p.ExperienceList = []models.Experience{}
|
||||
@@ -305,7 +234,6 @@ func GetAllAboutProfiles() ([]models.AboutProfile, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
profiles = append(profiles, p)
|
||||
}
|
||||
return profiles, nil
|
||||
}
|
||||
@@ -327,41 +255,12 @@ func CreateAboutProfile(profile *models.AboutProfile) error {
|
||||
expBytes, _ := json.Marshal(profile.ExperienceList)
|
||||
profile.ExperiencesStr = string(expBytes)
|
||||
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
INSERT INTO about_profiles (name, avatar, location, bio, email, wechat, tech_stack, experiences, is_primary, created_at, updated_at, deleted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)
|
||||
`
|
||||
result, err := config.DB.Exec(
|
||||
query,
|
||||
profile.Name,
|
||||
profile.Avatar,
|
||||
profile.Location,
|
||||
profile.Bio,
|
||||
profile.Email,
|
||||
profile.Wechat,
|
||||
profile.TechStack,
|
||||
profile.ExperiencesStr,
|
||||
profile.IsPrimary,
|
||||
now,
|
||||
now,
|
||||
)
|
||||
err := config.DB.Create(profile).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating about profile: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
profile.ID = uint(id)
|
||||
profile.CreatedAt = now
|
||||
profile.UpdatedAt = now
|
||||
|
||||
// 确保返回的数据包含 TechList 和 ExperienceList(已从 JSON 解析)
|
||||
// 这些字段已经在上面被 Marshal 了,现在需要确保它们被正确设置
|
||||
// 由于我们已经 Marshal 了,TechList 和 ExperienceList 应该保持原样
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -382,26 +281,20 @@ func UpdateAboutProfile(profile *models.AboutProfile) error {
|
||||
expBytes, _ := json.Marshal(profile.ExperienceList)
|
||||
profile.ExperiencesStr = string(expBytes)
|
||||
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
UPDATE about_profiles
|
||||
SET name = ?, avatar = ?, location = ?, bio = ?, email = ?, wechat = ?, tech_stack = ?, experiences = ?, is_primary = ?, updated_at = ?
|
||||
WHERE id = ? AND deleted_at = 0
|
||||
`
|
||||
_, err := config.DB.Exec(
|
||||
query,
|
||||
profile.Name,
|
||||
profile.Avatar,
|
||||
profile.Location,
|
||||
profile.Bio,
|
||||
profile.Email,
|
||||
profile.Wechat,
|
||||
profile.TechStack,
|
||||
profile.ExperiencesStr,
|
||||
profile.IsPrimary,
|
||||
now,
|
||||
profile.ID,
|
||||
)
|
||||
err := config.DB.Model(&models.AboutProfile{}).
|
||||
Where("id = ? AND deleted_at = ?", profile.ID, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"name": profile.Name,
|
||||
"avatar": profile.Avatar,
|
||||
"location": profile.Location,
|
||||
"bio": profile.Bio,
|
||||
"email": profile.Email,
|
||||
"wechat": profile.Wechat,
|
||||
"tech_stack": profile.TechStack,
|
||||
"experiences": profile.ExperiencesStr,
|
||||
"is_primary": profile.IsPrimary,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Printf("Error updating about profile: %v", err)
|
||||
return err
|
||||
@@ -411,9 +304,9 @@ func UpdateAboutProfile(profile *models.AboutProfile) error {
|
||||
|
||||
// DeleteAboutProfile 删除个人资料 (Soft Delete)
|
||||
func DeleteAboutProfile(id uint) error {
|
||||
now := time.Now().Unix()
|
||||
query := "UPDATE about_profiles SET deleted_at = ? WHERE id = ?"
|
||||
_, err := config.DB.Exec(query, now, id)
|
||||
err := config.DB.Model(&models.AboutProfile{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting about profile: %v", err)
|
||||
return err
|
||||
|
||||
@@ -1,141 +1,77 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GetCategories 获取所有分类
|
||||
func GetCategories() ([]models.Category, error) {
|
||||
query := "SELECT id, name, slug, description, sort_order, created_at, updated_at, deleted_at FROM categories WHERE deleted_at = 0 ORDER BY sort_order ASC, created_at DESC"
|
||||
rows, err := config.DB.Query(query)
|
||||
var categories []models.Category
|
||||
err := config.DB.Model(&models.Category{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("sort_order ASC, created_at DESC").
|
||||
Find(&categories).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying categories: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var categories []models.Category
|
||||
for rows.Next() {
|
||||
var category models.Category
|
||||
var description sql.NullString // Use NullString for nullable column
|
||||
if err := rows.Scan(
|
||||
&category.ID,
|
||||
&category.Name,
|
||||
&category.Slug,
|
||||
&description, // Scan into NullString
|
||||
&category.SortOrder,
|
||||
&category.CreatedAt,
|
||||
&category.UpdatedAt,
|
||||
&category.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning category: %v", err)
|
||||
continue
|
||||
}
|
||||
if description.Valid {
|
||||
category.Description = description.String
|
||||
}
|
||||
categories = append(categories, category)
|
||||
}
|
||||
|
||||
return categories, nil
|
||||
}
|
||||
|
||||
// GetCategoryByID 根据ID获取分类
|
||||
func GetCategoryByID(id uint) (*models.Category, error) {
|
||||
query := "SELECT id, name, slug, description, sort_order, created_at, updated_at, deleted_at FROM categories WHERE id = ? AND deleted_at = 0"
|
||||
row := config.DB.QueryRow(query, id)
|
||||
|
||||
var category models.Category
|
||||
var description sql.NullString // Use NullString for nullable column
|
||||
if err := row.Scan(
|
||||
&category.ID,
|
||||
&category.Name,
|
||||
&category.Slug,
|
||||
&description, // Scan into NullString
|
||||
&category.SortOrder,
|
||||
&category.CreatedAt,
|
||||
&category.UpdatedAt,
|
||||
&category.DeletedAt,
|
||||
); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
err := config.DB.Model(&models.Category{}).
|
||||
Where("id = ? AND deleted_at = ?", id, 0).
|
||||
First(&category).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error scanning category by ID: %v", err)
|
||||
log.Printf("Error getting category by ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if description.Valid {
|
||||
category.Description = description.String
|
||||
}
|
||||
|
||||
return &category, nil
|
||||
}
|
||||
|
||||
// CreateCategory 创建分类
|
||||
func CreateCategory(category *models.Category) error {
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
INSERT INTO categories (name, slug, description, sort_order, created_at, updated_at, deleted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 0)
|
||||
`
|
||||
result, err := config.DB.Exec(
|
||||
query,
|
||||
category.Name,
|
||||
category.Slug,
|
||||
category.Description,
|
||||
category.SortOrder,
|
||||
now,
|
||||
now,
|
||||
)
|
||||
err := config.DB.Create(category).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating category: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
category.ID = uint(id)
|
||||
category.CreatedAt = now
|
||||
category.UpdatedAt = now
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateCategory 更新分类
|
||||
func UpdateCategory(category *models.Category) error {
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
UPDATE categories SET name = ?, slug = ?, description = ?, sort_order = ?, updated_at = ?
|
||||
WHERE id = ? AND deleted_at = 0
|
||||
`
|
||||
_, err := config.DB.Exec(
|
||||
query,
|
||||
category.Name,
|
||||
category.Slug,
|
||||
category.Description,
|
||||
category.SortOrder,
|
||||
now,
|
||||
category.ID,
|
||||
)
|
||||
err := config.DB.Model(&models.Category{}).
|
||||
Where("id = ? AND deleted_at = ?", category.ID, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"name": category.Name,
|
||||
"slug": category.Slug,
|
||||
"description": category.Description,
|
||||
"sort_order": category.SortOrder,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Printf("Error updating category: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteCategory 删除分类
|
||||
func DeleteCategory(id uint) error {
|
||||
now := time.Now().Unix()
|
||||
query := "UPDATE categories SET deleted_at = ? WHERE id = ?"
|
||||
_, err := config.DB.Exec(query, now, id)
|
||||
err := config.DB.Model(&models.Category{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting category: %v", err)
|
||||
return err
|
||||
|
||||
@@ -1,153 +1,78 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GetColumns 获取所有专栏
|
||||
func GetColumns() ([]models.Column, error) {
|
||||
query := "SELECT id, name, description, cover, is_active, sort_order, created_at, updated_at, deleted_at FROM columns WHERE deleted_at = 0 ORDER BY sort_order ASC, created_at DESC"
|
||||
rows, err := config.DB.Query(query)
|
||||
var columns []models.Column
|
||||
err := config.DB.Model(&models.Column{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("sort_order ASC, created_at DESC").
|
||||
Find(&columns).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying columns: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var columns []models.Column
|
||||
for rows.Next() {
|
||||
var col models.Column
|
||||
var description sql.NullString // Use NullString
|
||||
var cover sql.NullString // Use NullString
|
||||
if err := rows.Scan(
|
||||
&col.ID,
|
||||
&col.Name,
|
||||
&description,
|
||||
&cover,
|
||||
&col.IsActive,
|
||||
&col.SortOrder,
|
||||
&col.CreatedAt,
|
||||
&col.UpdatedAt,
|
||||
&col.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning column: %v", err)
|
||||
continue
|
||||
}
|
||||
if description.Valid {
|
||||
col.Description = description.String
|
||||
}
|
||||
if cover.Valid {
|
||||
col.Cover = cover.String
|
||||
}
|
||||
columns = append(columns, col)
|
||||
}
|
||||
|
||||
return columns, nil
|
||||
}
|
||||
|
||||
// GetColumnByID 根据ID获取专栏
|
||||
func GetColumnByID(id uint) (*models.Column, error) {
|
||||
query := "SELECT id, name, description, cover, is_active, sort_order, created_at, updated_at, deleted_at FROM columns WHERE id = ? AND deleted_at = 0"
|
||||
row := config.DB.QueryRow(query, id)
|
||||
|
||||
var col models.Column
|
||||
var description sql.NullString // Use NullString
|
||||
var cover sql.NullString // Use NullString
|
||||
if err := row.Scan(
|
||||
&col.ID,
|
||||
&col.Name,
|
||||
&description,
|
||||
&cover,
|
||||
&col.IsActive,
|
||||
&col.SortOrder,
|
||||
&col.CreatedAt,
|
||||
&col.UpdatedAt,
|
||||
&col.DeletedAt,
|
||||
); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
err := config.DB.Model(&models.Column{}).
|
||||
Where("id = ? AND deleted_at = ?", id, 0).
|
||||
First(&col).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error scanning column by ID: %v", err)
|
||||
log.Printf("Error getting column by ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if description.Valid {
|
||||
col.Description = description.String
|
||||
}
|
||||
if cover.Valid {
|
||||
col.Cover = cover.String
|
||||
}
|
||||
|
||||
return &col, nil
|
||||
}
|
||||
|
||||
// CreateColumn 创建专栏
|
||||
func CreateColumn(col *models.Column) error {
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
INSERT INTO columns (name, description, cover, is_active, sort_order, created_at, updated_at, deleted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, 0)
|
||||
`
|
||||
result, err := config.DB.Exec(
|
||||
query,
|
||||
col.Name,
|
||||
col.Description,
|
||||
col.Cover,
|
||||
col.IsActive,
|
||||
col.SortOrder,
|
||||
now,
|
||||
now,
|
||||
)
|
||||
err := config.DB.Create(col).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating column: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
col.ID = uint(id)
|
||||
col.CreatedAt = now
|
||||
col.UpdatedAt = now
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateColumn 更新专栏
|
||||
func UpdateColumn(col *models.Column) error {
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
UPDATE columns SET name = ?, description = ?, cover = ?, is_active = ?, sort_order = ?, updated_at = ?
|
||||
WHERE id = ? AND deleted_at = 0
|
||||
`
|
||||
_, err := config.DB.Exec(
|
||||
query,
|
||||
col.Name,
|
||||
col.Description,
|
||||
col.Cover,
|
||||
col.IsActive,
|
||||
col.SortOrder,
|
||||
now,
|
||||
col.ID,
|
||||
)
|
||||
err := config.DB.Model(&models.Column{}).
|
||||
Where("id = ? AND deleted_at = ?", col.ID, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"name": col.Name,
|
||||
"description": col.Description,
|
||||
"cover": col.Cover,
|
||||
"is_active": col.IsActive,
|
||||
"sort_order": col.SortOrder,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Printf("Error updating column: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteColumn 删除专栏
|
||||
func DeleteColumn(id uint) error {
|
||||
now := time.Now().Unix()
|
||||
query := "UPDATE columns SET deleted_at = ? WHERE id = ?"
|
||||
_, err := config.DB.Exec(query, now, id)
|
||||
err := config.DB.Model(&models.Column{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting column: %v", err)
|
||||
return err
|
||||
@@ -157,66 +82,39 @@ func DeleteColumn(id uint) error {
|
||||
|
||||
// GetPostsByColumnID 获取专栏下的文章
|
||||
func GetPostsByColumnID(columnID uint) ([]models.Post, error) {
|
||||
query := `
|
||||
SELECT p.id, p.title, p.category_id, c.name as category_name, p.excerpt, p.content, p.read_count, p.is_published, p.created_at, p.updated_at, p.deleted_at
|
||||
FROM posts p
|
||||
JOIN column_posts cp ON p.id = cp.post_id
|
||||
LEFT JOIN categories c ON p.category_id = c.id
|
||||
WHERE cp.column_id = ? AND p.deleted_at = 0 AND p.is_published = 1
|
||||
ORDER BY cp.sort_order ASC, p.created_at DESC
|
||||
`
|
||||
rows, err := config.DB.Query(query, columnID)
|
||||
var posts []models.Post
|
||||
err := config.DB.Model(&models.Post{}).
|
||||
Select("posts.*, categories.name as category_name").
|
||||
Joins("JOIN column_posts cp ON posts.id = cp.post_id").
|
||||
Joins("LEFT JOIN categories ON posts.category_id = categories.id").
|
||||
Where("cp.column_id = ? AND posts.deleted_at = ? AND posts.is_published = ?", columnID, 0, 1).
|
||||
Order("cp.sort_order ASC, posts.created_at DESC").
|
||||
Find(&posts).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying posts by column ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var posts []models.Post
|
||||
for rows.Next() {
|
||||
var post models.Post
|
||||
var categoryName sql.NullString
|
||||
if err := rows.Scan(
|
||||
&post.ID,
|
||||
&post.Title,
|
||||
&post.CategoryID,
|
||||
&categoryName,
|
||||
&post.Excerpt,
|
||||
&post.Content,
|
||||
&post.ReadCount,
|
||||
&post.IsPublished,
|
||||
&post.CreatedAt,
|
||||
&post.UpdatedAt,
|
||||
&post.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning post: %v", err)
|
||||
continue
|
||||
}
|
||||
if categoryName.Valid {
|
||||
post.Category = &models.Category{ID: post.CategoryID, Name: categoryName.String}
|
||||
}
|
||||
posts = append(posts, post)
|
||||
}
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
// AddPostToColumn 添加文章到专栏
|
||||
func AddPostToColumn(columnID, postID, sortOrder uint) error {
|
||||
now := time.Now().Unix()
|
||||
// Check if exists first to avoid duplicates or use INSERT IGNORE/REPLACE if simple
|
||||
// Assuming unique key on (column_id, post_id)
|
||||
query := `
|
||||
INSERT INTO column_posts (column_id, post_id, sort_order, created_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE sort_order = VALUES(sort_order)
|
||||
`
|
||||
_, err := config.DB.Exec(query, columnID, postID, sortOrder, now)
|
||||
columnPost := &models.ColumnPost{
|
||||
ColumnID: columnID,
|
||||
PostID: postID,
|
||||
SortOrder: sortOrder,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
}
|
||||
// 使用 Clauses 处理 ON DUPLICATE KEY UPDATE
|
||||
err := config.DB.Where("column_id = ? AND post_id = ?", columnID, postID).
|
||||
Assign(models.ColumnPost{SortOrder: sortOrder}).
|
||||
FirstOrCreate(columnPost).Error
|
||||
return err
|
||||
}
|
||||
|
||||
// RemovePostFromColumn 从专栏移除文章
|
||||
func RemovePostFromColumn(columnID, postID uint) error {
|
||||
query := "DELETE FROM column_posts WHERE column_id = ? AND post_id = ?"
|
||||
_, err := config.DB.Exec(query, columnID, postID)
|
||||
err := config.DB.Where("column_id = ? AND post_id = ?", columnID, postID).
|
||||
Delete(&models.ColumnPost{}).Error
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -10,9 +10,7 @@ import (
|
||||
|
||||
// CreateInquiry 创建咨询
|
||||
func CreateInquiry(inquiry *models.Inquiry) error {
|
||||
now := time.Now().Unix()
|
||||
query := `INSERT INTO inquiries (name, company, contact_method, contact_value, budget, description, status, created_at, updated_at, deleted_at) VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, 0)`
|
||||
_, err := config.DB.Exec(query, inquiry.Name, inquiry.Company, inquiry.ContactMethod, inquiry.ContactValue, inquiry.Budget, inquiry.Description, now, now)
|
||||
err := config.DB.Create(inquiry).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating inquiry: %v", err)
|
||||
return err
|
||||
@@ -22,130 +20,76 @@ func CreateInquiry(inquiry *models.Inquiry) error {
|
||||
|
||||
// GetInquiries 获取咨询列表 (Admin)
|
||||
func GetInquiries() ([]models.Inquiry, error) {
|
||||
// Added deleted_at check
|
||||
rows, err := config.DB.Query("SELECT id, name, company, contact_method, contact_value, budget, description, status, created_at, updated_at, deleted_at FROM inquiries WHERE deleted_at = 0 ORDER BY created_at DESC")
|
||||
var inquiries []models.Inquiry
|
||||
err := config.DB.Model(&models.Inquiry{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("created_at DESC").
|
||||
Find(&inquiries).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var inquiries []models.Inquiry
|
||||
for rows.Next() {
|
||||
var i models.Inquiry
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Company,
|
||||
&i.ContactMethod,
|
||||
&i.ContactValue,
|
||||
&i.Budget,
|
||||
&i.Description,
|
||||
&i.Status,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.DeletedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inquiries = append(inquiries, i)
|
||||
}
|
||||
return inquiries, nil
|
||||
}
|
||||
|
||||
// GetInquiryCount 获取咨询总数
|
||||
func GetInquiryCount() (int64, error) {
|
||||
var count int64
|
||||
err := config.DB.Model(&models.Inquiry{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetEmailSuffixes 获取活跃的邮箱后缀
|
||||
func GetEmailSuffixes() ([]models.EmailSuffix, error) {
|
||||
rows, err := config.DB.Query("SELECT id, suffix, is_active, sort_order, created_at, updated_at, deleted_at FROM email_suffixes WHERE is_active = 1 AND deleted_at = 0 ORDER BY sort_order ASC")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var suffixes []models.EmailSuffix
|
||||
for rows.Next() {
|
||||
var s models.EmailSuffix
|
||||
var isActive int
|
||||
if err := rows.Scan(
|
||||
&s.ID,
|
||||
&s.Suffix,
|
||||
&isActive,
|
||||
&s.SortOrder,
|
||||
&s.CreatedAt,
|
||||
&s.UpdatedAt,
|
||||
&s.DeletedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.IsActive = isActive == 1
|
||||
suffixes = append(suffixes, s)
|
||||
}
|
||||
return suffixes, nil
|
||||
err := config.DB.Model(&models.EmailSuffix{}).
|
||||
Where("is_active = ? AND deleted_at = ?", true, 0).
|
||||
Order("sort_order ASC").
|
||||
Find(&suffixes).Error
|
||||
return suffixes, err
|
||||
}
|
||||
|
||||
// AdminGetEmailSuffixes 获取所有邮箱后缀 (Admin)
|
||||
func AdminGetEmailSuffixes() ([]models.EmailSuffix, error) {
|
||||
rows, err := config.DB.Query("SELECT id, suffix, is_active, sort_order, created_at, updated_at, deleted_at FROM email_suffixes WHERE deleted_at = 0 ORDER BY sort_order ASC")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var suffixes []models.EmailSuffix
|
||||
for rows.Next() {
|
||||
var s models.EmailSuffix
|
||||
var isActive int
|
||||
if err := rows.Scan(
|
||||
&s.ID,
|
||||
&s.Suffix,
|
||||
&isActive,
|
||||
&s.SortOrder,
|
||||
&s.CreatedAt,
|
||||
&s.UpdatedAt,
|
||||
&s.DeletedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.IsActive = isActive == 1
|
||||
suffixes = append(suffixes, s)
|
||||
}
|
||||
return suffixes, nil
|
||||
err := config.DB.Model(&models.EmailSuffix{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("sort_order ASC").
|
||||
Find(&suffixes).Error
|
||||
return suffixes, err
|
||||
}
|
||||
|
||||
// CreateEmailSuffix 创建邮箱后缀
|
||||
func CreateEmailSuffix(suffix *models.EmailSuffix) error {
|
||||
now := time.Now().Unix()
|
||||
query := `INSERT INTO email_suffixes (suffix, is_active, sort_order, created_at, updated_at, deleted_at) VALUES (?, ?, ?, ?, ?, 0)`
|
||||
isActive := 0
|
||||
if suffix.IsActive {
|
||||
isActive = 1
|
||||
}
|
||||
_, err := config.DB.Exec(query, suffix.Suffix, isActive, suffix.SortOrder, now, now)
|
||||
return err
|
||||
return config.DB.Create(suffix).Error
|
||||
}
|
||||
|
||||
// UpdateEmailSuffix 更新邮箱后缀
|
||||
func UpdateEmailSuffix(suffix *models.EmailSuffix) error {
|
||||
now := time.Now().Unix()
|
||||
query := `UPDATE email_suffixes SET suffix = ?, is_active = ?, sort_order = ?, updated_at = ? WHERE id = ? AND deleted_at = 0`
|
||||
isActive := 0
|
||||
if suffix.IsActive {
|
||||
isActive = 1
|
||||
}
|
||||
_, err := config.DB.Exec(query, suffix.Suffix, isActive, suffix.SortOrder, now, suffix.ID)
|
||||
return err
|
||||
return config.DB.Model(&models.EmailSuffix{}).
|
||||
Where("id = ? AND deleted_at = ?", suffix.ID, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"suffix": suffix.Suffix,
|
||||
"is_active": suffix.IsActive,
|
||||
"sort_order": suffix.SortOrder,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
}
|
||||
|
||||
// DeleteEmailSuffix 删除邮箱后缀 (Soft Delete)
|
||||
func DeleteEmailSuffix(id uint) error {
|
||||
now := time.Now().Unix()
|
||||
query := `UPDATE email_suffixes SET deleted_at = ? WHERE id = ?`
|
||||
_, err := config.DB.Exec(query, now, id)
|
||||
return err
|
||||
return config.DB.Model(&models.EmailSuffix{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
}
|
||||
|
||||
// UpdateInquiryStatus 更新咨询状态
|
||||
func UpdateInquiryStatus(id uint, status int) error {
|
||||
now := time.Now().Unix()
|
||||
query := `UPDATE inquiries SET status = ?, updated_at = ? WHERE id = ? AND deleted_at = 0`
|
||||
_, err := config.DB.Exec(query, status, now, id)
|
||||
return err
|
||||
return config.DB.Model(&models.Inquiry{}).
|
||||
Where("id = ? AND deleted_at = ?", id, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"status": status,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
}
|
||||
|
||||
@@ -38,10 +38,7 @@ func parseDateToUnix(dateStr string, isEnd bool) int64 {
|
||||
|
||||
// CreateAccessLog 创建访问日志
|
||||
func CreateAccessLog(log *models.AccessLog) error {
|
||||
now := time.Now().Unix()
|
||||
query := `INSERT INTO access_logs (ip, user_agent, path, method, status_code, response_time, region, created_at, deleted_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)`
|
||||
_, err := config.DB.Exec(query, log.IP, log.UserAgent, log.Path, log.Method, log.StatusCode, log.ResponseTime, log.Region, now)
|
||||
return err
|
||||
return config.DB.Create(log).Error
|
||||
}
|
||||
|
||||
// UVTrendData UV趋势数据
|
||||
@@ -54,52 +51,38 @@ type UVTrendData struct {
|
||||
|
||||
// GetDailyUV 获取UV趋势
|
||||
func GetDailyUV(startDate, endDate string) ([]UVTrendData, error) {
|
||||
query := `
|
||||
SELECT FROM_UNIXTIME(access_time, '%Y-%m-%d') as date, COUNT(DISTINCT user_ip) as count
|
||||
FROM user_access_logs
|
||||
WHERE 1=1
|
||||
`
|
||||
args := []interface{}{}
|
||||
query := config.DB.Model(&models.UserAccessLog{}).
|
||||
Select("FROM_UNIXTIME(access_time, '%Y-%m-%d') as date, COUNT(DISTINCT user_ip) as count")
|
||||
|
||||
if startDate != "" {
|
||||
startUnix := parseDateToUnix(startDate, false)
|
||||
query += " AND access_time >= ?"
|
||||
args = append(args, startUnix)
|
||||
query = query.Where("access_time >= ?", startUnix)
|
||||
} else {
|
||||
// 默认最近7天
|
||||
startUnix := time.Now().AddDate(0, 0, -6).Unix()
|
||||
query += " AND access_time >= ?"
|
||||
args = append(args, startUnix)
|
||||
query = query.Where("access_time >= ?", startUnix)
|
||||
}
|
||||
|
||||
if endDate != "" {
|
||||
endUnix := parseDateToUnix(endDate, true)
|
||||
query += " AND access_time <= ?"
|
||||
args = append(args, endUnix)
|
||||
query = query.Where("access_time <= ?", endUnix)
|
||||
}
|
||||
|
||||
query += `
|
||||
GROUP BY date
|
||||
ORDER BY date ASC
|
||||
`
|
||||
var results []UVTrendData
|
||||
err := query.Group("date").
|
||||
Order("date ASC").
|
||||
Scan(&results).Error
|
||||
|
||||
rows, err := config.DB.Query(query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var results []UVTrendData
|
||||
for rows.Next() {
|
||||
var r UVTrendData
|
||||
if err := rows.Scan(&r.Date, &r.Count); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 暂未实现真实的同比环比计算逻辑,设为0
|
||||
r.YoY = 0
|
||||
r.MoM = 0
|
||||
results = append(results, r)
|
||||
for i := range results {
|
||||
results[i].YoY = 0
|
||||
results[i].MoM = 0
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -108,52 +91,27 @@ func GetUserRegions(startDate, endDate string) ([]struct {
|
||||
Region string
|
||||
Count int
|
||||
}, error) {
|
||||
query := `
|
||||
SELECT
|
||||
COALESCE(NULLIF(user_location, ''), 'Unknown') as region,
|
||||
COUNT(DISTINCT user_ip) as count
|
||||
FROM user_access_logs
|
||||
WHERE 1=1
|
||||
`
|
||||
args := []interface{}{}
|
||||
query := config.DB.Model(&models.UserAccessLog{}).
|
||||
Select("COALESCE(NULLIF(user_location, ''), 'Unknown') as region, COUNT(DISTINCT user_ip) as count")
|
||||
|
||||
if startDate != "" {
|
||||
startUnix := parseDateToUnix(startDate, false)
|
||||
query += " AND access_time >= ?"
|
||||
args = append(args, startUnix)
|
||||
query = query.Where("access_time >= ?", startUnix)
|
||||
}
|
||||
|
||||
if endDate != "" {
|
||||
endUnix := parseDateToUnix(endDate, true)
|
||||
query += " AND access_time <= ?"
|
||||
args = append(args, endUnix)
|
||||
query = query.Where("access_time <= ?", endUnix)
|
||||
}
|
||||
|
||||
query += `
|
||||
GROUP BY region
|
||||
ORDER BY count DESC
|
||||
LIMIT 20
|
||||
`
|
||||
|
||||
rows, err := config.DB.Query(query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var results []struct {
|
||||
Region string
|
||||
Count int
|
||||
}
|
||||
for rows.Next() {
|
||||
var r struct {
|
||||
Region string
|
||||
Count int
|
||||
}
|
||||
if err := rows.Scan(&r.Region, &r.Count); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, r)
|
||||
}
|
||||
return results, nil
|
||||
err := query.Group("region").
|
||||
Order("count DESC").
|
||||
Limit(20).
|
||||
Scan(&results).Error
|
||||
|
||||
return results, err
|
||||
}
|
||||
|
||||
@@ -51,15 +51,14 @@ func MigrateToBigInt() {
|
||||
}
|
||||
|
||||
func columnExists(tableName, colName string) bool {
|
||||
query := `
|
||||
var count int64
|
||||
err := config.DB.Raw(`
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = ?
|
||||
AND column_name = ?
|
||||
`
|
||||
var count int
|
||||
err := config.DB.QueryRow(query, tableName, colName).Scan(&count)
|
||||
`, tableName, colName).Scan(&count).Error
|
||||
if err != nil {
|
||||
log.Printf("Error checking column %s.%s: %v", tableName, colName, err)
|
||||
return false
|
||||
@@ -68,15 +67,14 @@ func columnExists(tableName, colName string) bool {
|
||||
}
|
||||
|
||||
func isBigInt(tableName, colName string) bool {
|
||||
query := `
|
||||
var dataType string
|
||||
err := config.DB.Raw(`
|
||||
SELECT DATA_TYPE
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = ?
|
||||
AND column_name = ?
|
||||
`
|
||||
var dataType string
|
||||
err := config.DB.QueryRow(query, tableName, colName).Scan(&dataType)
|
||||
`, tableName, colName).Scan(&dataType).Error
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -108,7 +106,7 @@ func migrateColumn(table, col string) {
|
||||
}
|
||||
|
||||
func execSQL(query string) {
|
||||
_, err := config.DB.Exec(query)
|
||||
err := config.DB.Exec(query).Error
|
||||
if err != nil {
|
||||
// Log but continue (might fail if column doesn't exist etc)
|
||||
log.Printf("SQL Error: %v | Query: %s", err, query)
|
||||
|
||||
@@ -10,80 +10,41 @@ import (
|
||||
|
||||
// CreateOperationLog 创建操作日志
|
||||
func CreateOperationLog(operationLog *models.OperationLog) error {
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
INSERT INTO operation_logs (user_id, username, ip, path, method, params, status, duration, created_at, deleted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0)
|
||||
`
|
||||
_, err := config.DB.Exec(
|
||||
query,
|
||||
operationLog.UserID,
|
||||
operationLog.Username,
|
||||
operationLog.IP,
|
||||
operationLog.Path,
|
||||
operationLog.Method,
|
||||
operationLog.Params,
|
||||
operationLog.Status,
|
||||
operationLog.Duration,
|
||||
now,
|
||||
)
|
||||
err := config.DB.Create(operationLog).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating operation log: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOperationLogs 获取操作日志列表
|
||||
func GetOperationLogs(page, pageSize int) ([]models.OperationLog, int64, error) {
|
||||
// 计算偏移量
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
// 获取总记录数
|
||||
var logs []models.OperationLog
|
||||
var total int64
|
||||
countQuery := "SELECT COUNT(*) FROM operation_logs WHERE deleted_at = 0"
|
||||
if err := config.DB.QueryRow(countQuery).Scan(&total); err != nil {
|
||||
|
||||
// 获取总记录数
|
||||
err := config.DB.Model(&models.OperationLog{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Count(&total).Error
|
||||
if err != nil {
|
||||
log.Printf("Error counting operation logs: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 获取分页数据
|
||||
query := `
|
||||
SELECT id, user_id, username, ip, path, method, params, status, duration, created_at, deleted_at
|
||||
FROM operation_logs
|
||||
WHERE deleted_at = 0
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`
|
||||
rows, err := config.DB.Query(query, pageSize, offset)
|
||||
err = config.DB.Model(&models.OperationLog{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("created_at DESC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
Find(&logs).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying operation logs: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var logs []models.OperationLog
|
||||
for rows.Next() {
|
||||
var operationLog models.OperationLog
|
||||
if err := rows.Scan(
|
||||
&operationLog.ID,
|
||||
&operationLog.UserID,
|
||||
&operationLog.Username,
|
||||
&operationLog.IP,
|
||||
&operationLog.Path,
|
||||
&operationLog.Method,
|
||||
&operationLog.Params,
|
||||
&operationLog.Status,
|
||||
&operationLog.Duration,
|
||||
&operationLog.CreatedAt,
|
||||
&operationLog.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning operation log: %v", err)
|
||||
continue
|
||||
}
|
||||
logs = append(logs, operationLog)
|
||||
}
|
||||
|
||||
return logs, total, nil
|
||||
}
|
||||
|
||||
@@ -10,53 +10,28 @@ import (
|
||||
|
||||
// GetPermissions 获取所有权限
|
||||
func GetPermissions() ([]models.Permission, error) {
|
||||
// Filter deleted_at = 0
|
||||
query := "SELECT id, name, resource, action, created_at, updated_at, deleted_at FROM permissions WHERE deleted_at = 0"
|
||||
rows, err := config.DB.Query(query)
|
||||
var permissions []models.Permission
|
||||
err := config.DB.Model(&models.Permission{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Find(&permissions).Error
|
||||
if err != nil {
|
||||
log.Printf("Error getting permissions: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var permissions []models.Permission
|
||||
for rows.Next() {
|
||||
var permission models.Permission
|
||||
if err := rows.Scan(&permission.ID, &permission.Name, &permission.Resource, &permission.Action, &permission.CreatedAt, &permission.UpdatedAt, &permission.DeletedAt); err != nil {
|
||||
log.Printf("Error scanning permission: %v", err)
|
||||
continue
|
||||
}
|
||||
permissions = append(permissions, permission)
|
||||
}
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
// GetPermissionsByRoleID 获取指定角色的权限
|
||||
func GetPermissionsByRoleID(roleID uint) ([]models.Permission, error) {
|
||||
query := `
|
||||
SELECT p.id, p.name, p.resource, p.action, p.created_at, p.updated_at, p.deleted_at
|
||||
FROM permissions p
|
||||
JOIN role_permissions rp ON p.id = rp.permission_id
|
||||
WHERE rp.role_id = ? AND p.deleted_at = 0
|
||||
`
|
||||
rows, err := config.DB.Query(query, roleID)
|
||||
var permissions []models.Permission
|
||||
err := config.DB.Model(&models.Permission{}).
|
||||
Joins("JOIN role_permissions rp ON permissions.id = rp.permission_id").
|
||||
Where("rp.role_id = ? AND permissions.deleted_at = ?", roleID, 0).
|
||||
Find(&permissions).Error
|
||||
if err != nil {
|
||||
log.Printf("Error getting permissions by role id: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var permissions []models.Permission
|
||||
for rows.Next() {
|
||||
var permission models.Permission
|
||||
if err := rows.Scan(&permission.ID, &permission.Name, &permission.Resource, &permission.Action, &permission.CreatedAt, &permission.UpdatedAt, &permission.DeletedAt); err != nil {
|
||||
log.Printf("Error scanning permission: %v", err)
|
||||
continue
|
||||
}
|
||||
permissions = append(permissions, permission)
|
||||
}
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// TrendData 趋势数据
|
||||
@@ -19,143 +19,57 @@ type TrendData struct {
|
||||
|
||||
// GetPosts 获取所有博客文章(支持搜索、分类、标签筛选)
|
||||
func GetPosts(keyword string, categoryID uint, tagID uint) ([]models.Post, error) {
|
||||
query := `
|
||||
SELECT p.id, p.title, p.category_id, c.name, c.slug, p.excerpt, p.content, p.read_count, p.is_published, p.created_at, p.updated_at, p.deleted_at
|
||||
FROM posts p
|
||||
LEFT JOIN categories c ON p.category_id = c.id
|
||||
`
|
||||
|
||||
whereClause := " WHERE p.is_published = 1 AND p.deleted_at = 0"
|
||||
args := []interface{}{}
|
||||
var posts []models.Post
|
||||
query := config.DB.Model(&models.Post{}).
|
||||
Preload("Category").
|
||||
Preload("Tags").
|
||||
Where("is_published = ? AND deleted_at = ?", 1, 0)
|
||||
|
||||
if tagID > 0 {
|
||||
query += " JOIN post_tags pt ON p.id = pt.post_id"
|
||||
whereClause += " AND pt.tag_id = ?"
|
||||
args = append(args, tagID)
|
||||
query = query.Joins("JOIN post_tags pt ON posts.id = pt.post_id").
|
||||
Where("pt.tag_id = ?", tagID)
|
||||
}
|
||||
|
||||
if categoryID > 0 {
|
||||
whereClause += " AND p.category_id = ?"
|
||||
args = append(args, categoryID)
|
||||
query = query.Where("category_id = ?", categoryID)
|
||||
}
|
||||
|
||||
if keyword != "" {
|
||||
whereClause += ` AND (
|
||||
MATCH(p.title, p.content) AGAINST(? IN BOOLEAN MODE) OR
|
||||
p.title LIKE ? OR
|
||||
p.content LIKE ?
|
||||
)`
|
||||
likeKeyword := "%" + keyword + "%"
|
||||
args = append(args, keyword, likeKeyword, likeKeyword)
|
||||
query = query.Where("(MATCH(title, content) AGAINST(? IN BOOLEAN MODE) OR title LIKE ? OR content LIKE ?)",
|
||||
keyword, likeKeyword, likeKeyword)
|
||||
}
|
||||
|
||||
query += whereClause + " ORDER BY p.created_at DESC"
|
||||
|
||||
rows, err := config.DB.Query(query, args...)
|
||||
err := query.Order("created_at DESC").Find(&posts).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying posts: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var posts []models.Post
|
||||
for rows.Next() {
|
||||
var post models.Post
|
||||
var catID sql.NullInt64
|
||||
var catName sql.NullString
|
||||
var catSlug sql.NullString
|
||||
|
||||
if err := rows.Scan(
|
||||
&post.ID,
|
||||
&post.Title,
|
||||
&catID,
|
||||
&catName,
|
||||
&catSlug,
|
||||
&post.Excerpt,
|
||||
&post.Content,
|
||||
&post.ReadCount,
|
||||
&post.IsPublished,
|
||||
&post.CreatedAt,
|
||||
&post.UpdatedAt,
|
||||
&post.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning post: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if catID.Valid {
|
||||
post.CategoryID = uint(catID.Int64)
|
||||
post.Category = &models.Category{
|
||||
ID: uint(catID.Int64),
|
||||
Name: catName.String,
|
||||
Slug: catSlug.String,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Fetch tags if needed, or lazy load
|
||||
|
||||
posts = append(posts, post)
|
||||
}
|
||||
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
// GetPostByID 根据ID获取博客文章
|
||||
func GetPostByID(id uint) (*models.Post, error) {
|
||||
query := `
|
||||
SELECT p.id, p.title, p.category_id, c.name, c.slug, p.excerpt, p.content, p.read_count, p.is_published, p.created_at, p.updated_at, p.deleted_at
|
||||
FROM posts p
|
||||
LEFT JOIN categories c ON p.category_id = c.id
|
||||
WHERE p.id = ? AND p.is_published = 1 AND p.deleted_at = 0
|
||||
`
|
||||
row := config.DB.QueryRow(query, id)
|
||||
|
||||
var post models.Post
|
||||
var catID sql.NullInt64
|
||||
var catName sql.NullString
|
||||
var catSlug sql.NullString
|
||||
err := config.DB.Model(&models.Post{}).
|
||||
Preload("Category").
|
||||
Preload("Tags").
|
||||
Where("id = ? AND is_published = ? AND deleted_at = ?", id, 1, 0).
|
||||
First(&post).Error
|
||||
|
||||
if err := row.Scan(
|
||||
&post.ID,
|
||||
&post.Title,
|
||||
&catID,
|
||||
&catName,
|
||||
&catSlug,
|
||||
&post.Excerpt,
|
||||
&post.Content,
|
||||
&post.ReadCount,
|
||||
&post.IsPublished,
|
||||
&post.CreatedAt,
|
||||
&post.UpdatedAt,
|
||||
&post.DeletedAt,
|
||||
); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error scanning post by ID: %v", err)
|
||||
log.Printf("Error getting post by ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if catID.Valid {
|
||||
post.CategoryID = uint(catID.Int64)
|
||||
post.Category = &models.Category{
|
||||
ID: uint(catID.Int64),
|
||||
Name: catName.String,
|
||||
Slug: catSlug.String,
|
||||
}
|
||||
}
|
||||
|
||||
// 获取标签
|
||||
tags, err := GetTagsByPostID(post.ID)
|
||||
if err == nil {
|
||||
post.Tags = tags
|
||||
}
|
||||
|
||||
// 更新阅读量
|
||||
updateReadCountQuery := "UPDATE posts SET read_count = read_count + 1 WHERE id = ?"
|
||||
if _, err := config.DB.Exec(updateReadCountQuery, id); err != nil {
|
||||
log.Printf("Error updating post read count: %v", err)
|
||||
}
|
||||
config.DB.Model(&models.Post{}).
|
||||
Where("id = ?", id).
|
||||
UpdateColumn("read_count", gorm.Expr("read_count + ?", 1))
|
||||
|
||||
return &post, nil
|
||||
}
|
||||
@@ -164,100 +78,49 @@ func GetPostByID(id uint) (*models.Post, error) {
|
||||
func GetAllPosts(page, pageSize int) ([]models.Post, int64, error) {
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
// Count total
|
||||
var posts []models.Post
|
||||
var total int64
|
||||
config.DB.QueryRow("SELECT COUNT(*) FROM posts WHERE deleted_at = 0").Scan(&total)
|
||||
|
||||
query := `
|
||||
SELECT p.id, p.title, p.category_id, c.name, c.slug, p.excerpt, p.content, p.read_count, p.is_published, p.created_at, p.updated_at, p.deleted_at
|
||||
FROM posts p
|
||||
LEFT JOIN categories c ON p.category_id = c.id
|
||||
WHERE p.deleted_at = 0
|
||||
ORDER BY p.created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`
|
||||
rows, err := config.DB.Query(query, pageSize, offset)
|
||||
// Count total
|
||||
err := config.DB.Model(&models.Post{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Count(&total).Error
|
||||
if err != nil {
|
||||
log.Printf("Error counting posts: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Get posts
|
||||
err = config.DB.Model(&models.Post{}).
|
||||
Preload("Category").
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("created_at DESC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
Find(&posts).Error
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error querying all posts: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var posts []models.Post
|
||||
for rows.Next() {
|
||||
var post models.Post
|
||||
var catID sql.NullInt64
|
||||
var catName sql.NullString
|
||||
var catSlug sql.NullString
|
||||
|
||||
if err := rows.Scan(
|
||||
&post.ID,
|
||||
&post.Title,
|
||||
&catID,
|
||||
&catName,
|
||||
&catSlug,
|
||||
&post.Excerpt,
|
||||
&post.Content,
|
||||
&post.ReadCount,
|
||||
&post.IsPublished,
|
||||
&post.CreatedAt,
|
||||
&post.UpdatedAt,
|
||||
&post.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning post: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if catID.Valid {
|
||||
post.CategoryID = uint(catID.Int64)
|
||||
post.Category = &models.Category{
|
||||
ID: uint(catID.Int64),
|
||||
Name: catName.String,
|
||||
Slug: catSlug.String,
|
||||
}
|
||||
}
|
||||
posts = append(posts, post)
|
||||
}
|
||||
|
||||
return posts, total, nil
|
||||
}
|
||||
|
||||
// CreatePost 创建博客文章
|
||||
func CreatePost(post *models.Post) error {
|
||||
now := time.Now().Unix()
|
||||
|
||||
// Insert Post
|
||||
query := `
|
||||
INSERT INTO posts (title, category_id, excerpt, content, is_published, created_at, updated_at, deleted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, 0)
|
||||
`
|
||||
result, err := config.DB.Exec(
|
||||
query,
|
||||
post.Title,
|
||||
post.CategoryID,
|
||||
post.Excerpt,
|
||||
post.Content,
|
||||
post.IsPublished,
|
||||
now,
|
||||
now,
|
||||
)
|
||||
err := config.DB.Create(post).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating post: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
post.ID = uint(id)
|
||||
post.CreatedAt = now
|
||||
post.UpdatedAt = now
|
||||
|
||||
// Insert Tags
|
||||
if len(post.Tags) > 0 {
|
||||
for _, tag := range post.Tags {
|
||||
AddTagToPost(post.ID, tag.ID)
|
||||
err = config.DB.Model(post).Association("Tags").Replace(post.Tags)
|
||||
if err != nil {
|
||||
log.Printf("Error associating tags: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,32 +129,35 @@ func CreatePost(post *models.Post) error {
|
||||
|
||||
// UpdatePost 更新博客文章
|
||||
func UpdatePost(post *models.Post) error {
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
UPDATE posts SET title = ?, category_id = ?, excerpt = ?, content = ?, is_published = ?, updated_at = ?
|
||||
WHERE id = ? AND deleted_at = 0
|
||||
`
|
||||
_, err := config.DB.Exec(
|
||||
query,
|
||||
post.Title,
|
||||
post.CategoryID,
|
||||
post.Excerpt,
|
||||
post.Content,
|
||||
post.IsPublished,
|
||||
now,
|
||||
post.ID,
|
||||
)
|
||||
err := config.DB.Model(&models.Post{}).
|
||||
Where("id = ? AND deleted_at = ?", post.ID, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"title": post.Title,
|
||||
"category_id": post.CategoryID,
|
||||
"excerpt": post.Excerpt,
|
||||
"content": post.Content,
|
||||
"is_published": post.IsPublished,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error updating post: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update Tags: Delete all and re-insert
|
||||
// Note: This is a simple approach. Better approach is to diff.
|
||||
config.DB.Exec("DELETE FROM post_tags WHERE post_id = ?", post.ID)
|
||||
// Update Tags
|
||||
if len(post.Tags) > 0 {
|
||||
for _, tag := range post.Tags {
|
||||
AddTagToPost(post.ID, tag.ID)
|
||||
err = config.DB.Model(&models.Post{ID: post.ID}).Association("Tags").Replace(post.Tags)
|
||||
if err != nil {
|
||||
log.Printf("Error updating tags: %v", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Clear all tags
|
||||
err = config.DB.Model(&models.Post{ID: post.ID}).Association("Tags").Clear()
|
||||
if err != nil {
|
||||
log.Printf("Error clearing tags: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,17 +166,20 @@ func UpdatePost(post *models.Post) error {
|
||||
|
||||
// UpdatePostStatus 更新文章状态
|
||||
func UpdatePostStatus(id uint, status int) error {
|
||||
now := time.Now().Unix()
|
||||
query := "UPDATE posts SET is_published = ?, updated_at = ? WHERE id = ? AND deleted_at = 0"
|
||||
_, err := config.DB.Exec(query, status, now, id)
|
||||
err := config.DB.Model(&models.Post{}).
|
||||
Where("id = ? AND deleted_at = ?", id, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"is_published": status,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
return err
|
||||
}
|
||||
|
||||
// DeletePost 删除博客文章 (Soft Delete)
|
||||
func DeletePost(id uint) error {
|
||||
now := time.Now().Unix()
|
||||
query := "UPDATE posts SET deleted_at = ? WHERE id = ?"
|
||||
_, err := config.DB.Exec(query, now, id)
|
||||
err := config.DB.Model(&models.Post{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting post: %v", err)
|
||||
return err
|
||||
@@ -320,17 +189,15 @@ func DeletePost(id uint) error {
|
||||
|
||||
// GetPostCount 获取文章总数
|
||||
func GetPostCount() (int, error) {
|
||||
var count int
|
||||
query := "SELECT COUNT(*) FROM posts WHERE deleted_at = 0"
|
||||
row := config.DB.QueryRow(query)
|
||||
|
||||
err := row.Scan(&count)
|
||||
var count int64
|
||||
err := config.DB.Model(&models.Post{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Count(&count).Error
|
||||
if err != nil {
|
||||
log.Printf("Error getting post count: %v", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
return int(count), nil
|
||||
}
|
||||
|
||||
// BuildPostResponse 构建博客文章响应
|
||||
@@ -375,33 +242,28 @@ func BuildPostsResponse(posts []models.Post) []models.PostResponse {
|
||||
// SavePostHistory 保存文章历史记录
|
||||
func SavePostHistory(post *models.Post, modifiedBy uint) error {
|
||||
// 获取当前最大版本号
|
||||
var maxVersion uint
|
||||
query := "SELECT COALESCE(MAX(version), 0) FROM post_history WHERE post_id = ?"
|
||||
if err := config.DB.QueryRow(query, post.ID).Scan(&maxVersion); err != nil {
|
||||
var maxVersion int
|
||||
err := config.DB.Model(&models.PostHistory{}).
|
||||
Where("post_id = ?", post.ID).
|
||||
Select("COALESCE(MAX(version), 0)").
|
||||
Scan(&maxVersion).Error
|
||||
if err != nil {
|
||||
log.Printf("Error getting max version: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
insertQuery := `
|
||||
INSERT INTO post_history (
|
||||
post_id, version, title, category_id, excerpt, content,
|
||||
is_published, modified_by, modified_at, created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`
|
||||
_, err := config.DB.Exec(
|
||||
insertQuery,
|
||||
post.ID,
|
||||
maxVersion+1,
|
||||
post.Title,
|
||||
post.CategoryID,
|
||||
post.Excerpt,
|
||||
post.Content,
|
||||
post.IsPublished,
|
||||
modifiedBy,
|
||||
now,
|
||||
now,
|
||||
)
|
||||
history := &models.PostHistory{
|
||||
PostID: post.ID,
|
||||
Version: maxVersion + 1,
|
||||
Title: post.Title,
|
||||
CategoryID: post.CategoryID,
|
||||
Excerpt: post.Excerpt,
|
||||
Content: post.Content,
|
||||
IsPublished: post.IsPublished,
|
||||
ModifiedBy: modifiedBy,
|
||||
}
|
||||
|
||||
err = config.DB.Create(history).Error
|
||||
if err != nil {
|
||||
log.Printf("Error saving post history: %v", err)
|
||||
return err
|
||||
@@ -412,119 +274,79 @@ func SavePostHistory(post *models.Post, modifiedBy uint) error {
|
||||
|
||||
// GetTopPosts 获取热门文章 (按阅读量)
|
||||
func GetTopPosts(limit int) ([]models.Post, error) {
|
||||
// Simple query without category join for dashboard to avoid complexity if not needed
|
||||
// Or join if needed. Dashboard usually needs Title.
|
||||
query := "SELECT id, title, read_count FROM posts WHERE is_published = 1 AND deleted_at = 0 ORDER BY read_count DESC LIMIT ?"
|
||||
rows, err := config.DB.Query(query, limit)
|
||||
var posts []models.Post
|
||||
err := config.DB.Model(&models.Post{}).
|
||||
Select("id, title, read_count").
|
||||
Where("is_published = ? AND deleted_at = ?", 1, 0).
|
||||
Order("read_count DESC").
|
||||
Limit(limit).
|
||||
Find(&posts).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var posts []models.Post
|
||||
for rows.Next() {
|
||||
var post models.Post
|
||||
if err := rows.Scan(&post.ID, &post.Title, &post.ReadCount); err != nil {
|
||||
continue
|
||||
}
|
||||
posts = append(posts, post)
|
||||
}
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
// GetNewPostsTrend 获取新增文章趋势
|
||||
func GetNewPostsTrend(startDate, endDate string) ([]TrendData, error) {
|
||||
// Same as before
|
||||
query := `
|
||||
SELECT FROM_UNIXTIME(created_at, '%Y-%m-%d') as date, COUNT(*) as count
|
||||
FROM posts
|
||||
WHERE deleted_at = 0
|
||||
`
|
||||
args := []interface{}{}
|
||||
query := config.DB.Model(&models.Post{}).
|
||||
Select("FROM_UNIXTIME(created_at, '%Y-%m-%d') as date, COUNT(*) as count").
|
||||
Where("deleted_at = ?", 0)
|
||||
|
||||
if startDate != "" {
|
||||
startUnix := parseDateToUnix(startDate, false)
|
||||
query += " AND created_at >= ?"
|
||||
args = append(args, startUnix)
|
||||
query = query.Where("created_at >= ?", startUnix)
|
||||
} else {
|
||||
startUnix := time.Now().AddDate(0, 0, -6).Unix()
|
||||
query += " AND created_at >= ?"
|
||||
args = append(args, startUnix)
|
||||
query = query.Where("created_at >= ?", startUnix)
|
||||
}
|
||||
|
||||
if endDate != "" {
|
||||
endUnix := parseDateToUnix(endDate, true)
|
||||
query += " AND created_at <= ?"
|
||||
args = append(args, endUnix)
|
||||
query = query.Where("created_at <= ?", endUnix)
|
||||
}
|
||||
|
||||
query += `
|
||||
GROUP BY date
|
||||
ORDER BY date ASC
|
||||
`
|
||||
var results []TrendData
|
||||
err := query.Group("date").
|
||||
Order("date ASC").
|
||||
Scan(&results).Error
|
||||
|
||||
rows, err := config.DB.Query(query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var results []TrendData
|
||||
for rows.Next() {
|
||||
var r TrendData
|
||||
if err := rows.Scan(&r.Date, &r.Count); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.YoY = 0
|
||||
r.MoM = 0
|
||||
results = append(results, r)
|
||||
// Set YoY and MoM to 0
|
||||
for i := range results {
|
||||
results[i].YoY = 0
|
||||
results[i].MoM = 0
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// GetPostHistory 获取文章修改历史
|
||||
func GetPostHistory(postID uint) ([]models.PostHistory, error) {
|
||||
query := `
|
||||
SELECT id, post_id, version, title, category_id, excerpt, content, is_published, modified_by, modified_at, created_at
|
||||
FROM post_history
|
||||
WHERE post_id = ?
|
||||
ORDER BY version DESC
|
||||
`
|
||||
rows, err := config.DB.Query(query, postID)
|
||||
var history []models.PostHistory
|
||||
err := config.DB.Model(&models.PostHistory{}).
|
||||
Where("post_id = ?", postID).
|
||||
Order("version DESC").
|
||||
Find(&history).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var history []models.PostHistory
|
||||
for rows.Next() {
|
||||
var h models.PostHistory
|
||||
if err := rows.Scan(
|
||||
&h.ID, &h.PostID, &h.Version, &h.Title, &h.CategoryID,
|
||||
&h.Excerpt, &h.Content, &h.IsPublished,
|
||||
&h.ModifiedBy, &h.ModifiedAt, &h.CreatedAt,
|
||||
); err != nil {
|
||||
continue
|
||||
}
|
||||
history = append(history, h)
|
||||
}
|
||||
return history, nil
|
||||
}
|
||||
|
||||
// GetPostHistoryByVersion 获取特定版本的历史记录
|
||||
func GetPostHistoryByVersion(postID uint, version uint) (*models.PostHistory, error) {
|
||||
query := `
|
||||
SELECT id, post_id, version, title, category_id, excerpt, content, is_published, modified_by, modified_at, created_at
|
||||
FROM post_history
|
||||
WHERE post_id = ? AND version = ?
|
||||
`
|
||||
var h models.PostHistory
|
||||
err := config.DB.QueryRow(query, postID, version).Scan(
|
||||
&h.ID, &h.PostID, &h.Version, &h.Title, &h.CategoryID,
|
||||
&h.Excerpt, &h.Content, &h.IsPublished,
|
||||
&h.ModifiedBy, &h.ModifiedAt, &h.CreatedAt,
|
||||
)
|
||||
err := config.DB.Model(&models.PostHistory{}).
|
||||
Where("post_id = ? AND version = ?", postID, version).
|
||||
First(&h).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &h, nil
|
||||
|
||||
@@ -6,94 +6,75 @@ import (
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GetRoles 获取所有角色
|
||||
func GetRoles() ([]models.Role, error) {
|
||||
query := "SELECT id, name, description, created_at, updated_at, deleted_at FROM roles WHERE deleted_at = 0"
|
||||
rows, err := config.DB.Query(query)
|
||||
var roles []models.Role
|
||||
err := config.DB.Model(&models.Role{}).
|
||||
Preload("Permissions").
|
||||
Where("deleted_at = ?", 0).
|
||||
Find(&roles).Error
|
||||
if err != nil {
|
||||
log.Printf("Error getting roles: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var roles []models.Role
|
||||
for rows.Next() {
|
||||
var role models.Role
|
||||
if err := rows.Scan(&role.ID, &role.Name, &role.Description, &role.CreatedAt, &role.UpdatedAt, &role.DeletedAt); err != nil {
|
||||
log.Printf("Error scanning role: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取权限
|
||||
permissions, _ := GetPermissionsByRoleID(role.ID)
|
||||
role.Permissions = permissions
|
||||
|
||||
roles = append(roles, role)
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
// GetRoleByID 根据ID获取角色
|
||||
func GetRoleByID(id uint) (*models.Role, error) {
|
||||
query := "SELECT id, name, description, created_at, updated_at, deleted_at FROM roles WHERE id = ? AND deleted_at = 0"
|
||||
row := config.DB.QueryRow(query, id)
|
||||
|
||||
var role models.Role
|
||||
if err := row.Scan(&role.ID, &role.Name, &role.Description, &role.CreatedAt, &role.UpdatedAt, &role.DeletedAt); err != nil {
|
||||
err := config.DB.Model(&models.Role{}).
|
||||
Preload("Permissions").
|
||||
Where("id = ? AND deleted_at = ?", id, 0).
|
||||
First(&role).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error getting role by ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取权限
|
||||
permissions, _ := GetPermissionsByRoleID(role.ID)
|
||||
role.Permissions = permissions
|
||||
|
||||
return &role, nil
|
||||
}
|
||||
|
||||
// GetRoleByName 根据名称获取角色
|
||||
func GetRoleByName(name string) (*models.Role, error) {
|
||||
query := "SELECT id, name, description, created_at, updated_at, deleted_at FROM roles WHERE name = ? AND deleted_at = 0"
|
||||
row := config.DB.QueryRow(query, name)
|
||||
|
||||
var role models.Role
|
||||
if err := row.Scan(&role.ID, &role.Name, &role.Description, &role.CreatedAt, &role.UpdatedAt, &role.DeletedAt); err != nil {
|
||||
err := config.DB.Model(&models.Role{}).
|
||||
Where("name = ? AND deleted_at = ?", name, 0).
|
||||
First(&role).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error getting role by name: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &role, nil
|
||||
}
|
||||
|
||||
// CreateRole 创建角色
|
||||
func CreateRole(role *models.Role) error {
|
||||
now := time.Now().Unix()
|
||||
query := "INSERT INTO roles (name, description, created_at, updated_at, deleted_at) VALUES (?, ?, ?, ?, 0)"
|
||||
result, err := config.DB.Exec(query, role.Name, role.Description, now, now)
|
||||
err := config.DB.Create(role).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating role: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
role.ID = uint(id)
|
||||
role.CreatedAt = now
|
||||
role.UpdatedAt = now
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateRole 更新角色
|
||||
func UpdateRole(role *models.Role) error {
|
||||
now := time.Now().Unix()
|
||||
query := "UPDATE roles SET name = ?, description = ?, updated_at = ? WHERE id = ? AND deleted_at = 0"
|
||||
_, err := config.DB.Exec(query, role.Name, role.Description, now, role.ID)
|
||||
err := config.DB.Model(&models.Role{}).
|
||||
Where("id = ? AND deleted_at = ?", role.ID, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"name": role.Name,
|
||||
"description": role.Description,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Printf("Error updating role: %v", err)
|
||||
return err
|
||||
@@ -103,9 +84,9 @@ func UpdateRole(role *models.Role) error {
|
||||
|
||||
// DeleteRole 删除角色 (Soft Delete)
|
||||
func DeleteRole(id uint) error {
|
||||
now := time.Now().Unix()
|
||||
query := "UPDATE roles SET deleted_at = ? WHERE id = ?"
|
||||
_, err := config.DB.Exec(query, now, id)
|
||||
err := config.DB.Model(&models.Role{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting role: %v", err)
|
||||
return err
|
||||
@@ -115,36 +96,25 @@ func DeleteRole(id uint) error {
|
||||
|
||||
// AssignPermissionsToRole 给角色分配权限
|
||||
func AssignPermissionsToRole(roleID uint, permissionIDs []uint) error {
|
||||
// 开启事务
|
||||
tx, err := config.DB.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return config.DB.Transaction(func(tx *gorm.DB) error {
|
||||
// 先删除旧权限
|
||||
_, err = tx.Exec("DELETE FROM role_permissions WHERE role_id = ?", roleID)
|
||||
err := tx.Exec("DELETE FROM role_permissions WHERE role_id = ?", roleID).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
// 插入新权限
|
||||
stmt, err := tx.Prepare("INSERT INTO role_permissions (role_id, permission_id) VALUES (?, ?)")
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if len(permissionIDs) > 0 {
|
||||
for _, permID := range permissionIDs {
|
||||
_, err = stmt.Exec(roleID, permID)
|
||||
err = tx.Exec("INSERT INTO role_permissions (role_id, permission_id) VALUES (?, ?)", roleID, permID).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// BuildRoleResponse 构建角色响应
|
||||
|
||||
@@ -2,6 +2,7 @@ package repositories
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
@@ -9,52 +10,21 @@ import (
|
||||
|
||||
// GetTestimonials 获取所有客户评价
|
||||
func GetTestimonials() ([]models.Testimonial, error) {
|
||||
query := "SELECT id, name, role, content, avatar, rating, sort_order, created_at, updated_at FROM testimonials ORDER BY sort_order ASC, created_at DESC"
|
||||
rows, err := config.DB.Query(query)
|
||||
var testimonials []models.Testimonial
|
||||
err := config.DB.Model(&models.Testimonial{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("sort_order ASC, created_at DESC").
|
||||
Find(&testimonials).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying testimonials: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var testimonials []models.Testimonial
|
||||
for rows.Next() {
|
||||
var t models.Testimonial
|
||||
if err := rows.Scan(
|
||||
&t.ID,
|
||||
&t.Name,
|
||||
&t.Role,
|
||||
&t.Content,
|
||||
&t.Avatar,
|
||||
&t.Rating,
|
||||
&t.SortOrder,
|
||||
&t.CreatedAt,
|
||||
&t.UpdatedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning testimonial: %v", err)
|
||||
continue
|
||||
}
|
||||
testimonials = append(testimonials, t)
|
||||
}
|
||||
|
||||
return testimonials, nil
|
||||
}
|
||||
|
||||
// CreateTestimonial 创建客户评价
|
||||
func CreateTestimonial(t *models.Testimonial) error {
|
||||
query := `
|
||||
INSERT INTO testimonials (name, role, content, avatar, rating, sort_order, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
`
|
||||
_, err := config.DB.Exec(
|
||||
query,
|
||||
t.Name,
|
||||
t.Role,
|
||||
t.Content,
|
||||
t.Avatar,
|
||||
t.Rating,
|
||||
t.SortOrder,
|
||||
)
|
||||
err := config.DB.Create(t).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating testimonial: %v", err)
|
||||
return err
|
||||
@@ -64,20 +34,17 @@ func CreateTestimonial(t *models.Testimonial) error {
|
||||
|
||||
// UpdateTestimonial 更新客户评价
|
||||
func UpdateTestimonial(t *models.Testimonial) error {
|
||||
query := `
|
||||
UPDATE testimonials SET name = ?, role = ?, content = ?, avatar = ?, rating = ?, sort_order = ?, updated_at = NOW()
|
||||
WHERE id = ?
|
||||
`
|
||||
_, err := config.DB.Exec(
|
||||
query,
|
||||
t.Name,
|
||||
t.Role,
|
||||
t.Content,
|
||||
t.Avatar,
|
||||
t.Rating,
|
||||
t.SortOrder,
|
||||
t.ID,
|
||||
)
|
||||
err := config.DB.Model(&models.Testimonial{}).
|
||||
Where("id = ?", t.ID).
|
||||
Updates(map[string]interface{}{
|
||||
"name": t.Name,
|
||||
"role": t.Role,
|
||||
"content": t.Content,
|
||||
"avatar": t.Avatar,
|
||||
"rating": t.Rating,
|
||||
"sort_order": t.SortOrder,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Printf("Error updating testimonial: %v", err)
|
||||
return err
|
||||
@@ -87,8 +54,9 @@ func UpdateTestimonial(t *models.Testimonial) error {
|
||||
|
||||
// DeleteTestimonial 删除客户评价
|
||||
func DeleteTestimonial(id uint) error {
|
||||
query := "DELETE FROM testimonials WHERE id = ?"
|
||||
_, err := config.DB.Exec(query, id)
|
||||
err := config.DB.Model(&models.Testimonial{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting testimonial: %v", err)
|
||||
return err
|
||||
@@ -98,50 +66,21 @@ func DeleteTestimonial(id uint) error {
|
||||
|
||||
// GetPartners 获取所有合作伙伴
|
||||
func GetPartners() ([]models.Partner, error) {
|
||||
query := "SELECT id, name, logo, description, url, sort_order, created_at, updated_at FROM partners ORDER BY sort_order ASC, created_at DESC"
|
||||
rows, err := config.DB.Query(query)
|
||||
var partners []models.Partner
|
||||
err := config.DB.Model(&models.Partner{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("sort_order ASC, created_at DESC").
|
||||
Find(&partners).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying partners: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var partners []models.Partner
|
||||
for rows.Next() {
|
||||
var p models.Partner
|
||||
if err := rows.Scan(
|
||||
&p.ID,
|
||||
&p.Name,
|
||||
&p.Logo,
|
||||
&p.Description,
|
||||
&p.URL,
|
||||
&p.SortOrder,
|
||||
&p.CreatedAt,
|
||||
&p.UpdatedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning partner: %v", err)
|
||||
continue
|
||||
}
|
||||
partners = append(partners, p)
|
||||
}
|
||||
|
||||
return partners, nil
|
||||
}
|
||||
|
||||
// CreatePartner 创建合作伙伴
|
||||
func CreatePartner(p *models.Partner) error {
|
||||
query := `
|
||||
INSERT INTO partners (name, logo, description, url, sort_order, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, NOW(), NOW())
|
||||
`
|
||||
_, err := config.DB.Exec(
|
||||
query,
|
||||
p.Name,
|
||||
p.Logo,
|
||||
p.Description,
|
||||
p.URL,
|
||||
p.SortOrder,
|
||||
)
|
||||
err := config.DB.Create(p).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating partner: %v", err)
|
||||
return err
|
||||
@@ -151,19 +90,16 @@ func CreatePartner(p *models.Partner) error {
|
||||
|
||||
// UpdatePartner 更新合作伙伴
|
||||
func UpdatePartner(p *models.Partner) error {
|
||||
query := `
|
||||
UPDATE partners SET name = ?, logo = ?, description = ?, url = ?, sort_order = ?, updated_at = NOW()
|
||||
WHERE id = ?
|
||||
`
|
||||
_, err := config.DB.Exec(
|
||||
query,
|
||||
p.Name,
|
||||
p.Logo,
|
||||
p.Description,
|
||||
p.URL,
|
||||
p.SortOrder,
|
||||
p.ID,
|
||||
)
|
||||
err := config.DB.Model(&models.Partner{}).
|
||||
Where("id = ?", p.ID).
|
||||
Updates(map[string]interface{}{
|
||||
"name": p.Name,
|
||||
"logo": p.Logo,
|
||||
"description": p.Description,
|
||||
"url": p.URL,
|
||||
"sort_order": p.SortOrder,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Printf("Error updating partner: %v", err)
|
||||
return err
|
||||
@@ -173,8 +109,9 @@ func UpdatePartner(p *models.Partner) error {
|
||||
|
||||
// DeletePartner 删除合作伙伴
|
||||
func DeletePartner(id uint) error {
|
||||
query := "DELETE FROM partners WHERE id = ?"
|
||||
_, err := config.DB.Exec(query, id)
|
||||
err := config.DB.Model(&models.Partner{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting partner: %v", err)
|
||||
return err
|
||||
|
||||
@@ -1,135 +1,79 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GetSettings 获取所有系统配置
|
||||
func GetSettings() ([]models.Setting, error) {
|
||||
query := "SELECT id, key_name, value, description, created_at, updated_at, deleted_at FROM settings WHERE deleted_at = 0 ORDER BY key_name"
|
||||
rows, err := config.DB.Query(query)
|
||||
var settings []models.Setting
|
||||
err := config.DB.Model(&models.Setting{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("key_name").
|
||||
Find(&settings).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying settings: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var settings []models.Setting
|
||||
for rows.Next() {
|
||||
var setting models.Setting
|
||||
if err := rows.Scan(
|
||||
&setting.ID,
|
||||
&setting.KeyName,
|
||||
&setting.Value,
|
||||
&setting.Description,
|
||||
&setting.CreatedAt,
|
||||
&setting.UpdatedAt,
|
||||
&setting.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning setting: %v", err)
|
||||
continue
|
||||
}
|
||||
settings = append(settings, setting)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
// GetSettingByKey 根据键名获取系统配置
|
||||
func GetSettingByKey(keyName string) (*models.Setting, error) {
|
||||
query := "SELECT id, key_name, value, description, created_at, updated_at, deleted_at FROM settings WHERE key_name = ? AND deleted_at = 0"
|
||||
row := config.DB.QueryRow(query, keyName)
|
||||
|
||||
var setting models.Setting
|
||||
if err := row.Scan(
|
||||
&setting.ID,
|
||||
&setting.KeyName,
|
||||
&setting.Value,
|
||||
&setting.Description,
|
||||
&setting.CreatedAt,
|
||||
&setting.UpdatedAt,
|
||||
&setting.DeletedAt,
|
||||
); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
err := config.DB.Model(&models.Setting{}).
|
||||
Where("key_name = ? AND deleted_at = ?", keyName, 0).
|
||||
First(&setting).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error scanning setting by key: %v", err)
|
||||
log.Printf("Error getting setting by key: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &setting, nil
|
||||
}
|
||||
|
||||
// UpdateSetting 更新系统配置
|
||||
func UpdateSetting(setting *models.Setting) error {
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
UPDATE settings SET value = ?, description = ?, updated_at = ?
|
||||
WHERE key_name = ? AND deleted_at = 0
|
||||
`
|
||||
_, err := config.DB.Exec(
|
||||
query,
|
||||
setting.Value,
|
||||
setting.Description,
|
||||
now,
|
||||
setting.KeyName,
|
||||
)
|
||||
err := config.DB.Model(&models.Setting{}).
|
||||
Where("key_name = ? AND deleted_at = ?", setting.KeyName, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"value": setting.Value,
|
||||
"description": setting.Description,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Printf("Error updating setting: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateSetting 创建系统配置
|
||||
func CreateSetting(setting *models.Setting) error {
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
INSERT INTO settings (key_name, value, description, created_at, updated_at, deleted_at)
|
||||
VALUES (?, ?, ?, ?, ?, 0)
|
||||
`
|
||||
result, err := config.DB.Exec(
|
||||
query,
|
||||
setting.KeyName,
|
||||
setting.Value,
|
||||
setting.Description,
|
||||
now,
|
||||
now,
|
||||
)
|
||||
err := config.DB.Create(setting).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating setting: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 获取自增ID
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
log.Printf("Error getting last insert ID: %v", err)
|
||||
return err
|
||||
}
|
||||
setting.ID = uint(id)
|
||||
setting.CreatedAt = now
|
||||
setting.UpdatedAt = now
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteSetting 删除系统配置 (Soft Delete)
|
||||
func DeleteSetting(keyName string) error {
|
||||
now := time.Now().Unix()
|
||||
query := "UPDATE settings SET deleted_at = ? WHERE key_name = ?"
|
||||
_, err := config.DB.Exec(query, now, keyName)
|
||||
err := config.DB.Model(&models.Setting{}).
|
||||
Where("key_name = ?", keyName).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting setting: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -161,22 +105,20 @@ func GetAllSettings() (map[string]string, error) {
|
||||
|
||||
// UpdateSettings 批量更新系统配置
|
||||
func UpdateSettings(settings map[string]string) error {
|
||||
tx, err := config.DB.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return config.DB.Transaction(func(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
query := "UPDATE settings SET value = ?, updated_at = ? WHERE key_name = ? AND deleted_at = 0"
|
||||
|
||||
for key, value := range settings {
|
||||
_, err := tx.Exec(query, value, now, key)
|
||||
err := tx.Model(&models.Setting{}).
|
||||
Where("key_name = ? AND deleted_at = ?", key, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"value": value,
|
||||
"updated_at": now,
|
||||
}).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
log.Printf("Error updating setting %s: %v", key, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,76 +1,46 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GetSnippets 获取所有代码片段
|
||||
func GetSnippets() ([]models.Snippet, error) {
|
||||
query := "SELECT id, title, code, type, description, view_count, created_at, updated_at, deleted_at FROM snippets WHERE deleted_at = 0 ORDER BY created_at DESC"
|
||||
rows, err := config.DB.Query(query)
|
||||
var snippets []models.Snippet
|
||||
err := config.DB.Model(&models.Snippet{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("created_at DESC").
|
||||
Find(&snippets).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying snippets: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var snippets []models.Snippet
|
||||
for rows.Next() {
|
||||
var snippet models.Snippet
|
||||
if err := rows.Scan(
|
||||
&snippet.ID,
|
||||
&snippet.Title,
|
||||
&snippet.Code,
|
||||
&snippet.Type,
|
||||
&snippet.Description,
|
||||
&snippet.ViewCount,
|
||||
&snippet.CreatedAt,
|
||||
&snippet.UpdatedAt,
|
||||
&snippet.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning snippet: %v", err)
|
||||
continue
|
||||
}
|
||||
snippets = append(snippets, snippet)
|
||||
}
|
||||
|
||||
return snippets, nil
|
||||
}
|
||||
|
||||
// GetSnippetByID 根据ID获取代码片段
|
||||
func GetSnippetByID(id string) (*models.Snippet, error) {
|
||||
query := "SELECT id, title, code, type, description, view_count, created_at, updated_at, deleted_at FROM snippets WHERE id = ? AND deleted_at = 0"
|
||||
row := config.DB.QueryRow(query, id)
|
||||
|
||||
var snippet models.Snippet
|
||||
if err := row.Scan(
|
||||
&snippet.ID,
|
||||
&snippet.Title,
|
||||
&snippet.Code,
|
||||
&snippet.Type,
|
||||
&snippet.Description,
|
||||
&snippet.ViewCount,
|
||||
&snippet.CreatedAt,
|
||||
&snippet.UpdatedAt,
|
||||
&snippet.DeletedAt,
|
||||
); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
err := config.DB.Model(&models.Snippet{}).
|
||||
Where("id = ? AND deleted_at = ?", id, 0).
|
||||
First(&snippet).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error scanning snippet by ID: %v", err)
|
||||
log.Printf("Error getting snippet by ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 更新查看次数
|
||||
updateViewCountQuery := "UPDATE snippets SET view_count = view_count + 1 WHERE id = ?"
|
||||
if _, err := config.DB.Exec(updateViewCountQuery, id); err != nil {
|
||||
log.Printf("Error updating snippet view count: %v", err)
|
||||
}
|
||||
config.DB.Model(&models.Snippet{}).
|
||||
Where("id = ?", id).
|
||||
UpdateColumn("view_count", gorm.Expr("view_count + ?", 1))
|
||||
|
||||
return &snippet, nil
|
||||
}
|
||||
@@ -96,63 +66,41 @@ func BuildSnippetsResponse(snippets []models.Snippet) []models.SnippetResponse {
|
||||
|
||||
// CreateSnippet 创建代码片段
|
||||
func CreateSnippet(snippet *models.Snippet) error {
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
INSERT INTO snippets (id, title, code, type, description, view_count, created_at, updated_at, deleted_at)
|
||||
VALUES (?, ?, ?, ?, ?, 0, ?, ?, 0)
|
||||
`
|
||||
_, err := config.DB.Exec(
|
||||
query,
|
||||
snippet.ID,
|
||||
snippet.Title,
|
||||
snippet.Code,
|
||||
snippet.Type,
|
||||
snippet.Description,
|
||||
now,
|
||||
now,
|
||||
)
|
||||
err := config.DB.Create(snippet).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating snippet: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateSnippet 更新代码片段
|
||||
func UpdateSnippet(snippet *models.Snippet) error {
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
UPDATE snippets SET title = ?, code = ?, type = ?, description = ?, updated_at = ?
|
||||
WHERE id = ? AND deleted_at = 0
|
||||
`
|
||||
_, err := config.DB.Exec(
|
||||
query,
|
||||
snippet.Title,
|
||||
snippet.Code,
|
||||
snippet.Type,
|
||||
snippet.Description,
|
||||
now,
|
||||
snippet.ID,
|
||||
)
|
||||
err := config.DB.Model(&models.Snippet{}).
|
||||
Where("id = ? AND deleted_at = ?", snippet.ID, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"title": snippet.Title,
|
||||
"code": snippet.Code,
|
||||
"type": snippet.Type,
|
||||
"description": snippet.Description,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Printf("Error updating snippet: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteSnippet 删除代码片段 (Soft Delete)
|
||||
func DeleteSnippet(id string) error {
|
||||
now := time.Now().Unix()
|
||||
query := "UPDATE snippets SET deleted_at = ? WHERE id = ?"
|
||||
_, err := config.DB.Exec(query, now, id)
|
||||
err := config.DB.Model(&models.Snippet{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting snippet: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -160,49 +108,29 @@ func DeleteSnippet(id string) error {
|
||||
func GetAdminSnippets(page, pageSize int) ([]models.Snippet, int, error) {
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var snippets []models.Snippet
|
||||
var total int64
|
||||
|
||||
// 获取总数
|
||||
var total int
|
||||
countQuery := "SELECT COUNT(*) FROM snippets WHERE deleted_at = 0"
|
||||
err := config.DB.QueryRow(countQuery).Scan(&total)
|
||||
err := config.DB.Model(&models.Snippet{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Count(&total).Error
|
||||
if err != nil {
|
||||
log.Printf("Error getting snippet count: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 获取列表
|
||||
query := `
|
||||
SELECT id, title, code, type, description, view_count, created_at, updated_at, deleted_at
|
||||
FROM snippets
|
||||
WHERE deleted_at = 0
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`
|
||||
rows, err := config.DB.Query(query, pageSize, offset)
|
||||
err = config.DB.Model(&models.Snippet{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("created_at DESC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
Find(&snippets).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying admin snippets: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var snippets []models.Snippet
|
||||
for rows.Next() {
|
||||
var snippet models.Snippet
|
||||
if err := rows.Scan(
|
||||
&snippet.ID,
|
||||
&snippet.Title,
|
||||
&snippet.Code,
|
||||
&snippet.Type,
|
||||
&snippet.Description,
|
||||
&snippet.ViewCount,
|
||||
&snippet.CreatedAt,
|
||||
&snippet.UpdatedAt,
|
||||
&snippet.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning snippet: %v", err)
|
||||
continue
|
||||
}
|
||||
snippets = append(snippets, snippet)
|
||||
}
|
||||
|
||||
return snippets, total, nil
|
||||
return snippets, int(total), nil
|
||||
}
|
||||
|
||||
@@ -1,172 +1,101 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GetTags 获取所有标签
|
||||
func GetTags() ([]models.Tag, error) {
|
||||
query := "SELECT id, name, slug, created_at, updated_at, deleted_at FROM tags WHERE deleted_at = 0 ORDER BY name ASC"
|
||||
rows, err := config.DB.Query(query)
|
||||
var tags []models.Tag
|
||||
err := config.DB.Model(&models.Tag{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("name ASC").
|
||||
Find(&tags).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying tags: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var tags []models.Tag
|
||||
for rows.Next() {
|
||||
var tag models.Tag
|
||||
if err := rows.Scan(
|
||||
&tag.ID,
|
||||
&tag.Name,
|
||||
&tag.Slug,
|
||||
&tag.CreatedAt,
|
||||
&tag.UpdatedAt,
|
||||
&tag.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning tag: %v", err)
|
||||
continue
|
||||
}
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// GetTagByID 根据ID获取标签
|
||||
func GetTagByID(id uint) (*models.Tag, error) {
|
||||
query := "SELECT id, name, slug, created_at, updated_at, deleted_at FROM tags WHERE id = ? AND deleted_at = 0"
|
||||
row := config.DB.QueryRow(query, id)
|
||||
|
||||
var tag models.Tag
|
||||
if err := row.Scan(
|
||||
&tag.ID,
|
||||
&tag.Name,
|
||||
&tag.Slug,
|
||||
&tag.CreatedAt,
|
||||
&tag.UpdatedAt,
|
||||
&tag.DeletedAt,
|
||||
); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
err := config.DB.Model(&models.Tag{}).
|
||||
Where("id = ? AND deleted_at = ?", id, 0).
|
||||
First(&tag).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error scanning tag by ID: %v", err)
|
||||
log.Printf("Error getting tag by ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tag, nil
|
||||
}
|
||||
|
||||
// GetTagBySlug 根据Slug获取标签
|
||||
func GetTagBySlug(slug string) (*models.Tag, error) {
|
||||
query := "SELECT id, name, slug, created_at, updated_at, deleted_at FROM tags WHERE slug = ? AND deleted_at = 0"
|
||||
row := config.DB.QueryRow(query, slug)
|
||||
|
||||
var tag models.Tag
|
||||
if err := row.Scan(
|
||||
&tag.ID,
|
||||
&tag.Name,
|
||||
&tag.Slug,
|
||||
&tag.CreatedAt,
|
||||
&tag.UpdatedAt,
|
||||
&tag.DeletedAt,
|
||||
); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
err := config.DB.Model(&models.Tag{}).
|
||||
Where("slug = ? AND deleted_at = ?", slug, 0).
|
||||
First(&tag).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error scanning tag by slug: %v", err)
|
||||
log.Printf("Error getting tag by slug: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tag, nil
|
||||
}
|
||||
|
||||
// CreateTag 创建标签
|
||||
func CreateTag(tag *models.Tag) error {
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
INSERT INTO tags (name, slug, created_at, updated_at, deleted_at)
|
||||
VALUES (?, ?, ?, ?, 0)
|
||||
`
|
||||
result, err := config.DB.Exec(
|
||||
query,
|
||||
tag.Name,
|
||||
tag.Slug,
|
||||
now,
|
||||
now,
|
||||
)
|
||||
err := config.DB.Create(tag).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating tag: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 获取自增ID
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
log.Printf("Error getting last insert ID: %v", err)
|
||||
return err
|
||||
}
|
||||
tag.ID = uint(id)
|
||||
tag.CreatedAt = now
|
||||
tag.UpdatedAt = now
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateTag 更新标签
|
||||
func UpdateTag(tag *models.Tag) error {
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
UPDATE tags SET name = ?, slug = ?, updated_at = ?
|
||||
WHERE id = ? AND deleted_at = 0
|
||||
`
|
||||
_, err := config.DB.Exec(
|
||||
query,
|
||||
tag.Name,
|
||||
tag.Slug,
|
||||
now,
|
||||
tag.ID,
|
||||
)
|
||||
err := config.DB.Model(&models.Tag{}).
|
||||
Where("id = ? AND deleted_at = ?", tag.ID, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"name": tag.Name,
|
||||
"slug": tag.Slug,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Printf("Error updating tag: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteTag 删除标签 (Soft Delete)
|
||||
func DeleteTag(id uint) error {
|
||||
now := time.Now().Unix()
|
||||
// 软删除标签
|
||||
query := "UPDATE tags SET deleted_at = ? WHERE id = ?"
|
||||
_, err := config.DB.Exec(query, now, id)
|
||||
err := config.DB.Model(&models.Tag{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting tag: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 注意:post_tags 关联表通常不做软删除,或者可以级联删除,或者在查询时过滤。
|
||||
// 为了保持数据一致性,我们可以物理删除 post_tags 中的关联,或者也软删除(如果支持)。
|
||||
// 这里选择物理删除关联,因为关联关系是“从属”的,标签没了,关系也没意义。
|
||||
// 但如果是“软删除”,也许想保留恢复可能?
|
||||
// 如果保留恢复可能,post_tags 也应该软删除。
|
||||
// 但 post_tags 没有 deleted_at。
|
||||
// 所以:物理删除关联,或者不处理关联(查询时 JOIN tags 会过滤掉)。
|
||||
// 最好是物理删除关联,或者保留关联但因为 tag 被软删除而不可见。
|
||||
// 这里保留原有逻辑:物理删除关联。
|
||||
|
||||
deleteRelQuery := "DELETE FROM post_tags WHERE tag_id = ?"
|
||||
_, err = config.DB.Exec(deleteRelQuery, id)
|
||||
// 物理删除关联关系
|
||||
err = config.DB.Where("tag_id = ?", id).Delete(&models.PostTag{}).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting post-tag relationships: %v", err)
|
||||
// Continue even if relation delete fails? No, return error.
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -175,105 +104,58 @@ func DeleteTag(id uint) error {
|
||||
|
||||
// GetTagsByPostID 根据文章ID获取标签
|
||||
func GetTagsByPostID(postID uint) ([]models.Tag, error) {
|
||||
query := `
|
||||
SELECT t.id, t.name, t.slug, t.created_at, t.updated_at, t.deleted_at
|
||||
FROM tags t
|
||||
JOIN post_tags pt ON t.id = pt.tag_id
|
||||
WHERE pt.post_id = ? AND t.deleted_at = 0
|
||||
ORDER BY t.name ASC
|
||||
`
|
||||
rows, err := config.DB.Query(query, postID)
|
||||
var tags []models.Tag
|
||||
err := config.DB.Model(&models.Tag{}).
|
||||
Joins("JOIN post_tags pt ON tags.id = pt.tag_id").
|
||||
Where("pt.post_id = ? AND tags.deleted_at = ?", postID, 0).
|
||||
Order("tags.name ASC").
|
||||
Find(&tags).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying tags by post ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var tags []models.Tag
|
||||
for rows.Next() {
|
||||
var tag models.Tag
|
||||
if err := rows.Scan(
|
||||
&tag.ID,
|
||||
&tag.Name,
|
||||
&tag.Slug,
|
||||
&tag.CreatedAt,
|
||||
&tag.UpdatedAt,
|
||||
&tag.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning tag: %v", err)
|
||||
continue
|
||||
}
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// AddTagToPost 为文章添加标签
|
||||
func AddTagToPost(postID uint, tagID uint) error {
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
INSERT IGNORE INTO post_tags (post_id, tag_id, created_at)
|
||||
VALUES (?, ?, ?)
|
||||
`
|
||||
_, err := config.DB.Exec(query, postID, tagID, now)
|
||||
postTag := &models.PostTag{
|
||||
PostID: postID,
|
||||
TagID: tagID,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
}
|
||||
err := config.DB.Where("post_id = ? AND tag_id = ?", postID, tagID).
|
||||
FirstOrCreate(postTag).Error
|
||||
if err != nil {
|
||||
log.Printf("Error adding tag to post: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveTagFromPost 从文章移除标签
|
||||
func RemoveTagFromPost(postID uint, tagID uint) error {
|
||||
query := "DELETE FROM post_tags WHERE post_id = ? AND tag_id = ?"
|
||||
_, err := config.DB.Exec(query, postID, tagID)
|
||||
err := config.DB.Where("post_id = ? AND tag_id = ?", postID, tagID).
|
||||
Delete(&models.PostTag{}).Error
|
||||
if err != nil {
|
||||
log.Printf("Error removing tag from post: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPostsByTagID 根据标签ID获取文章
|
||||
func GetPostsByTagID(tagID uint) ([]models.Post, error) {
|
||||
query := `
|
||||
SELECT p.id, p.title, p.category, p.excerpt, p.content, p.read_count, p.is_published, p.created_at, p.updated_at, p.deleted_at
|
||||
FROM posts p
|
||||
JOIN post_tags pt ON p.id = pt.post_id
|
||||
WHERE pt.tag_id = ? AND p.is_published = 1 AND p.deleted_at = 0
|
||||
ORDER BY p.created_at DESC
|
||||
`
|
||||
rows, err := config.DB.Query(query, tagID)
|
||||
var posts []models.Post
|
||||
err := config.DB.Model(&models.Post{}).
|
||||
Joins("JOIN post_tags pt ON posts.id = pt.post_id").
|
||||
Where("pt.tag_id = ? AND posts.is_published = ? AND posts.deleted_at = ?", tagID, 1, 0).
|
||||
Order("posts.created_at DESC").
|
||||
Find(&posts).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying posts by tag ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var posts []models.Post
|
||||
for rows.Next() {
|
||||
var post models.Post
|
||||
if err := rows.Scan(
|
||||
&post.ID,
|
||||
&post.Title,
|
||||
&post.Category,
|
||||
&post.Excerpt,
|
||||
&post.Content,
|
||||
&post.ReadCount,
|
||||
&post.IsPublished,
|
||||
&post.CreatedAt,
|
||||
&post.UpdatedAt,
|
||||
&post.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning post: %v", err)
|
||||
continue
|
||||
}
|
||||
posts = append(posts, post)
|
||||
}
|
||||
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package repositories
|
||||
|
||||
import (
|
||||
"log"
|
||||
// "time"
|
||||
"time"
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
@@ -10,11 +10,10 @@ import (
|
||||
|
||||
// CreateUserAccessLog 创建用户访问日志
|
||||
func CreateUserAccessLog(logEntry *models.UserAccessLog) error {
|
||||
query := `
|
||||
INSERT INTO user_access_logs (user_id, user_ip, user_location, article_id, access_time)
|
||||
VALUES (?, ?, ?, ?, NOW())
|
||||
`
|
||||
_, err := config.DB.Exec(query, logEntry.UserID, logEntry.UserIP, logEntry.UserLocation, logEntry.ArticleID)
|
||||
if logEntry.AccessTime == 0 {
|
||||
logEntry.AccessTime = time.Now().Unix()
|
||||
}
|
||||
err := config.DB.Create(logEntry).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating user access log: %v", err)
|
||||
return err
|
||||
@@ -30,42 +29,24 @@ type AccessStats struct {
|
||||
|
||||
// GetArticleAccessTrend 获取文章访问趋势
|
||||
func GetArticleAccessTrend(startDate, endDate string) ([]AccessStats, error) {
|
||||
query := `
|
||||
SELECT DATE_FORMAT(access_time, '%Y-%m-%d') as date, COUNT(*) as count
|
||||
FROM user_access_logs
|
||||
WHERE 1=1
|
||||
`
|
||||
args := []interface{}{}
|
||||
query := config.DB.Model(&models.UserAccessLog{}).
|
||||
Select("FROM_UNIXTIME(access_time, '%Y-%m-%d') as date, COUNT(*) as count")
|
||||
|
||||
if startDate != "" {
|
||||
query += " AND access_time >= ?"
|
||||
args = append(args, startDate)
|
||||
startUnix := parseDateToUnix(startDate, false)
|
||||
query = query.Where("access_time >= ?", startUnix)
|
||||
}
|
||||
if endDate != "" {
|
||||
query += " AND access_time <= ?"
|
||||
args = append(args, endDate+" 23:59:59")
|
||||
endUnix := parseDateToUnix(endDate, true)
|
||||
query = query.Where("access_time <= ?", endUnix)
|
||||
}
|
||||
|
||||
query += `
|
||||
GROUP BY date
|
||||
ORDER BY date ASC
|
||||
`
|
||||
|
||||
rows, err := config.DB.Query(query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var results []AccessStats
|
||||
for rows.Next() {
|
||||
var s AccessStats
|
||||
if err := rows.Scan(&s.Date, &s.Count); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, s)
|
||||
}
|
||||
return results, nil
|
||||
err := query.Group("date").
|
||||
Order("date ASC").
|
||||
Scan(&results).Error
|
||||
|
||||
return results, err
|
||||
}
|
||||
|
||||
// GetTopArticlesByAccess 获取访问量最高的文章
|
||||
@@ -74,35 +55,18 @@ func GetTopArticlesByAccess(limit int) ([]struct {
|
||||
Title string `json:"title"`
|
||||
Count int `json:"count"`
|
||||
}, error) {
|
||||
query := `
|
||||
SELECT l.article_id, p.title, COUNT(*) as count
|
||||
FROM user_access_logs l
|
||||
JOIN posts p ON l.article_id = p.id
|
||||
GROUP BY l.article_id, p.title
|
||||
ORDER BY count DESC
|
||||
LIMIT ?
|
||||
`
|
||||
rows, err := config.DB.Query(query, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var results []struct {
|
||||
ArticleID int `json:"article_id"`
|
||||
Title string `json:"title"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
for rows.Next() {
|
||||
var r struct {
|
||||
ArticleID int `json:"article_id"`
|
||||
Title string `json:"title"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
if err := rows.Scan(&r.ArticleID, &r.Title, &r.Count); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, r)
|
||||
}
|
||||
return results, nil
|
||||
err := config.DB.Model(&models.UserAccessLog{}).
|
||||
Select("user_access_logs.article_id, posts.title, COUNT(*) as count").
|
||||
Joins("JOIN posts ON user_access_logs.article_id = posts.id").
|
||||
Group("user_access_logs.article_id, posts.title").
|
||||
Order("count DESC").
|
||||
Limit(limit).
|
||||
Scan(&results).Error
|
||||
|
||||
return results, err
|
||||
}
|
||||
|
||||
@@ -1,97 +1,51 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GetUserByUsername 根据用户名获取用户
|
||||
func GetUserByUsername(username string) (*models.User, error) {
|
||||
query := `
|
||||
SELECT u.id, u.username, u.email, u.password_hash, u.role_id, COALESCE(r.name, u.role), u.is_active, u.created_at, u.updated_at, u.deleted_at
|
||||
FROM users u
|
||||
LEFT JOIN roles r ON u.role_id = r.id
|
||||
WHERE u.username = ? AND u.deleted_at = 0
|
||||
`
|
||||
row := config.DB.QueryRow(query, username)
|
||||
|
||||
var user models.User
|
||||
var roleID sql.NullInt64 // Handle nullable role_id
|
||||
var roleName sql.NullString // Handle nullable role name
|
||||
err := config.DB.Model(&models.User{}).
|
||||
Select("users.*, COALESCE(roles.name, users.role) as role").
|
||||
Joins("LEFT JOIN roles ON users.role_id = roles.id").
|
||||
Where("users.username = ? AND users.deleted_at = ?", username, 0).
|
||||
First(&user).Error
|
||||
|
||||
if err := row.Scan(
|
||||
&user.ID,
|
||||
&user.Username,
|
||||
&user.Email,
|
||||
&user.PasswordHash,
|
||||
&roleID,
|
||||
&roleName,
|
||||
&user.IsActive,
|
||||
&user.CreatedAt,
|
||||
&user.UpdatedAt,
|
||||
&user.DeletedAt,
|
||||
); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error scanning user by username: %v", err)
|
||||
log.Printf("Error getting user by username: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if roleID.Valid {
|
||||
user.RoleID = uint(roleID.Int64)
|
||||
}
|
||||
if roleName.Valid {
|
||||
user.Role = roleName.String
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetUserByID 根据ID获取用户
|
||||
func GetUserByID(id uint) (*models.User, error) {
|
||||
query := `
|
||||
SELECT u.id, u.username, u.email, u.password_hash, u.role_id, COALESCE(r.name, u.role), u.is_active, u.created_at, u.updated_at, u.deleted_at
|
||||
FROM users u
|
||||
LEFT JOIN roles r ON u.role_id = r.id
|
||||
WHERE u.id = ? AND u.deleted_at = 0
|
||||
`
|
||||
row := config.DB.QueryRow(query, id)
|
||||
|
||||
var user models.User
|
||||
var roleID sql.NullInt64
|
||||
var roleName sql.NullString
|
||||
err := config.DB.Model(&models.User{}).
|
||||
Select("users.*, COALESCE(roles.name, users.role) as role").
|
||||
Joins("LEFT JOIN roles ON users.role_id = roles.id").
|
||||
Where("users.id = ? AND users.deleted_at = ?", id, 0).
|
||||
First(&user).Error
|
||||
|
||||
if err := row.Scan(
|
||||
&user.ID,
|
||||
&user.Username,
|
||||
&user.Email,
|
||||
&user.PasswordHash,
|
||||
&roleID,
|
||||
&roleName,
|
||||
&user.IsActive,
|
||||
&user.CreatedAt,
|
||||
&user.UpdatedAt,
|
||||
&user.DeletedAt,
|
||||
); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error scanning user by ID: %v", err)
|
||||
log.Printf("Error getting user by ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if roleID.Valid {
|
||||
user.RoleID = uint(roleID.Int64)
|
||||
}
|
||||
if roleName.Valid {
|
||||
user.Role = roleName.String
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
@@ -99,63 +53,34 @@ func GetUserByID(id uint) (*models.User, error) {
|
||||
func GetUsers(page, pageSize int) ([]models.User, int, error) {
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var users []models.User
|
||||
var total int64
|
||||
|
||||
// 获取总数
|
||||
var total int
|
||||
countQuery := "SELECT COUNT(*) FROM users WHERE deleted_at = 0"
|
||||
err := config.DB.QueryRow(countQuery).Scan(&total)
|
||||
err := config.DB.Model(&models.User{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Count(&total).Error
|
||||
if err != nil {
|
||||
log.Printf("Error getting user count: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT u.id, u.username, u.email, u.password_hash, u.role_id, COALESCE(r.name, u.role), u.is_active, u.created_at, u.updated_at, u.deleted_at
|
||||
FROM users u
|
||||
LEFT JOIN roles r ON u.role_id = r.id
|
||||
WHERE u.deleted_at = 0
|
||||
ORDER BY u.created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`
|
||||
rows, err := config.DB.Query(query, pageSize, offset)
|
||||
// 获取用户列表
|
||||
err = config.DB.Model(&models.User{}).
|
||||
Select("users.*, COALESCE(roles.name, users.role) as role").
|
||||
Joins("LEFT JOIN roles ON users.role_id = roles.id").
|
||||
Where("users.deleted_at = ?", 0).
|
||||
Order("users.created_at DESC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
Find(&users).Error
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error querying users: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var users []models.User
|
||||
for rows.Next() {
|
||||
var user models.User
|
||||
var roleID sql.NullInt64
|
||||
var roleName sql.NullString
|
||||
|
||||
if err := rows.Scan(
|
||||
&user.ID,
|
||||
&user.Username,
|
||||
&user.Email,
|
||||
&user.PasswordHash,
|
||||
&roleID,
|
||||
&roleName,
|
||||
&user.IsActive,
|
||||
&user.CreatedAt,
|
||||
&user.UpdatedAt,
|
||||
&user.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning user: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if roleID.Valid {
|
||||
user.RoleID = uint(roleID.Int64)
|
||||
}
|
||||
if roleName.Valid {
|
||||
user.Role = roleName.String
|
||||
}
|
||||
|
||||
users = append(users, user)
|
||||
}
|
||||
|
||||
return users, total, nil
|
||||
return users, int(total), nil
|
||||
}
|
||||
|
||||
// CreateUser 创建用户
|
||||
@@ -168,45 +93,12 @@ func CreateUser(user *models.User) error {
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
INSERT INTO users (username, email, password_hash, role_id, role, is_active, created_at, updated_at, deleted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)
|
||||
`
|
||||
|
||||
var roleID interface{}
|
||||
if user.RoleID != 0 {
|
||||
roleID = user.RoleID
|
||||
} else {
|
||||
roleID = nil
|
||||
}
|
||||
|
||||
result, err := config.DB.Exec(
|
||||
query,
|
||||
user.Username,
|
||||
user.Email,
|
||||
user.PasswordHash,
|
||||
roleID,
|
||||
user.Role, // Fallback legacy column
|
||||
user.IsActive,
|
||||
now,
|
||||
now,
|
||||
)
|
||||
err := config.DB.Create(user).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating user: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 获取自增ID
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
log.Printf("Error getting last insert ID: %v", err)
|
||||
return err
|
||||
}
|
||||
user.ID = uint(id)
|
||||
user.CreatedAt = now
|
||||
user.UpdatedAt = now
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -220,29 +112,23 @@ func UpdateUser(user *models.User) error {
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
UPDATE users SET username = ?, email = ?, role_id = ?, role = ?, is_active = ?, updated_at = ?
|
||||
WHERE id = ? AND deleted_at = 0
|
||||
`
|
||||
|
||||
var roleID interface{}
|
||||
if user.RoleID != 0 {
|
||||
roleID = user.RoleID
|
||||
} else {
|
||||
roleID = nil
|
||||
updates := map[string]interface{}{
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"role": user.Role,
|
||||
"is_active": user.IsActive,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}
|
||||
|
||||
_, err := config.DB.Exec(
|
||||
query,
|
||||
user.Username,
|
||||
user.Email,
|
||||
roleID,
|
||||
user.Role,
|
||||
user.IsActive,
|
||||
now,
|
||||
user.ID,
|
||||
)
|
||||
if user.RoleID != 0 {
|
||||
updates["role_id"] = user.RoleID
|
||||
} else {
|
||||
updates["role_id"] = nil
|
||||
}
|
||||
|
||||
err := config.DB.Model(&models.User{}).
|
||||
Where("id = ? AND deleted_at = ?", user.ID, 0).
|
||||
Updates(updates).Error
|
||||
if err != nil {
|
||||
log.Printf("Error updating user: %v", err)
|
||||
return err
|
||||
@@ -253,12 +139,12 @@ func UpdateUser(user *models.User) error {
|
||||
|
||||
// UpdateUserPassword 更新用户密码
|
||||
func UpdateUserPassword(id uint, passwordHash string) error {
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
UPDATE users SET password_hash = ?, updated_at = ?
|
||||
WHERE id = ? AND deleted_at = 0
|
||||
`
|
||||
_, err := config.DB.Exec(query, passwordHash, now, id)
|
||||
err := config.DB.Model(&models.User{}).
|
||||
Where("id = ? AND deleted_at = ?", id, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"password_hash": passwordHash,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Printf("Error updating user password: %v", err)
|
||||
return err
|
||||
@@ -269,9 +155,9 @@ func UpdateUserPassword(id uint, passwordHash string) error {
|
||||
|
||||
// DeleteUser 删除用户 (Soft Delete)
|
||||
func DeleteUser(id uint) error {
|
||||
now := time.Now().Unix()
|
||||
query := "UPDATE users SET deleted_at = ? WHERE id = ?"
|
||||
_, err := config.DB.Exec(query, now, id)
|
||||
err := config.DB.Model(&models.User{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting user: %v", err)
|
||||
return err
|
||||
@@ -282,17 +168,16 @@ func DeleteUser(id uint) error {
|
||||
|
||||
// GetUserCount 获取用户总数
|
||||
func GetUserCount() (int, error) {
|
||||
var count int
|
||||
query := "SELECT COUNT(*) FROM users WHERE deleted_at = 0"
|
||||
row := config.DB.QueryRow(query)
|
||||
|
||||
err := row.Scan(&count)
|
||||
var count int64
|
||||
err := config.DB.Model(&models.User{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Count(&count).Error
|
||||
if err != nil {
|
||||
log.Printf("Error getting user count: %v", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
return int(count), nil
|
||||
}
|
||||
|
||||
// BuildUserResponse 构建用户响应
|
||||
|
||||
@@ -1,136 +1,67 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GetWorks 获取所有作品
|
||||
func GetWorks() ([]models.Work, error) {
|
||||
query := "SELECT id, title, category, year, hero_img, description, is_featured, created_at, updated_at, deleted_at FROM works WHERE deleted_at = 0"
|
||||
rows, err := config.DB.Query(query)
|
||||
var works []models.Work
|
||||
err := config.DB.Model(&models.Work{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Find(&works).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying works: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var works []models.Work
|
||||
for rows.Next() {
|
||||
var work models.Work
|
||||
if err := rows.Scan(
|
||||
&work.ID,
|
||||
&work.Title,
|
||||
&work.Category,
|
||||
&work.Year,
|
||||
&work.HeroImg,
|
||||
&work.Description,
|
||||
&work.IsFeatured,
|
||||
&work.CreatedAt,
|
||||
&work.UpdatedAt,
|
||||
&work.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning work: %v", err)
|
||||
continue
|
||||
}
|
||||
works = append(works, work)
|
||||
}
|
||||
|
||||
return works, nil
|
||||
}
|
||||
|
||||
// GetWorkByID 根据ID获取作品
|
||||
func GetWorkByID(id string) (*models.Work, error) {
|
||||
query := "SELECT id, title, category, year, hero_img, description, is_featured, created_at, updated_at, deleted_at FROM works WHERE id = ? AND deleted_at = 0"
|
||||
row := config.DB.QueryRow(query, id)
|
||||
|
||||
var work models.Work
|
||||
if err := row.Scan(
|
||||
&work.ID,
|
||||
&work.Title,
|
||||
&work.Category,
|
||||
&work.Year,
|
||||
&work.HeroImg,
|
||||
&work.Description,
|
||||
&work.IsFeatured,
|
||||
&work.CreatedAt,
|
||||
&work.UpdatedAt,
|
||||
&work.DeletedAt,
|
||||
); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
err := config.DB.Model(&models.Work{}).
|
||||
Where("id = ? AND deleted_at = ?", id, 0).
|
||||
First(&work).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error scanning work by ID: %v", err)
|
||||
log.Printf("Error getting work by ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &work, nil
|
||||
}
|
||||
|
||||
// GetWorkTechStack 获取作品的技术栈
|
||||
func GetWorkTechStack(workID string) ([]models.WorkTechStack, error) {
|
||||
// work_tech_stack has deleted_at? I added it to all tables.
|
||||
query := "SELECT id, work_id, category, item, created_at, deleted_at FROM work_tech_stack WHERE work_id = ? AND deleted_at = 0"
|
||||
rows, err := config.DB.Query(query, workID)
|
||||
var techStacks []models.WorkTechStack
|
||||
err := config.DB.Model(&models.WorkTechStack{}).
|
||||
Where("work_id = ? AND deleted_at = ?", workID, 0).
|
||||
Find(&techStacks).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying work tech stack: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var techStacks []models.WorkTechStack
|
||||
for rows.Next() {
|
||||
var techStack models.WorkTechStack
|
||||
if err := rows.Scan(
|
||||
&techStack.ID,
|
||||
&techStack.WorkID,
|
||||
&techStack.Category,
|
||||
&techStack.Item,
|
||||
&techStack.CreatedAt,
|
||||
&techStack.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning work tech stack: %v", err)
|
||||
continue
|
||||
}
|
||||
techStacks = append(techStacks, techStack)
|
||||
}
|
||||
|
||||
return techStacks, nil
|
||||
}
|
||||
|
||||
// GetWorkGallery 获取作品的图库
|
||||
func GetWorkGallery(workID string) ([]models.WorkGallery, error) {
|
||||
// work_gallery has deleted_at
|
||||
query := "SELECT id, work_id, image_url, sort_order, description, created_at, deleted_at FROM work_gallery WHERE work_id = ? AND deleted_at = 0 ORDER BY sort_order"
|
||||
rows, err := config.DB.Query(query, workID)
|
||||
var galleries []models.WorkGallery
|
||||
err := config.DB.Model(&models.WorkGallery{}).
|
||||
Where("work_id = ? AND deleted_at = ?", workID, 0).
|
||||
Order("sort_order").
|
||||
Find(&galleries).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying work gallery: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var galleries []models.WorkGallery
|
||||
for rows.Next() {
|
||||
var gallery models.WorkGallery
|
||||
if err := rows.Scan(
|
||||
&gallery.ID,
|
||||
&gallery.WorkID,
|
||||
&gallery.ImageURL,
|
||||
&gallery.SortOrder,
|
||||
&gallery.Description,
|
||||
&gallery.CreatedAt,
|
||||
&gallery.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning work gallery: %v", err)
|
||||
continue
|
||||
}
|
||||
galleries = append(galleries, gallery)
|
||||
}
|
||||
|
||||
return galleries, nil
|
||||
}
|
||||
|
||||
@@ -194,22 +125,14 @@ func BuildWorkResponse(work *models.Work) (*models.WorkResponse, error) {
|
||||
|
||||
// GetNextWorkID 获取下一个作品ID(简单实现,实际可能需要更复杂的逻辑)
|
||||
func GetNextWorkID(currentID string) (string, error) {
|
||||
// 获取所有作品ID
|
||||
query := "SELECT id FROM works WHERE deleted_at = 0"
|
||||
rows, err := config.DB.Query(query)
|
||||
var ids []string
|
||||
err := config.DB.Model(&models.Work{}).
|
||||
Select("id").
|
||||
Where("deleted_at = ?", 0).
|
||||
Pluck("id", &ids).Error
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var ids []string
|
||||
for rows.Next() {
|
||||
var id string
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
// 找到当前ID的索引
|
||||
index := -1
|
||||
@@ -234,67 +157,43 @@ func GetNextWorkID(currentID string) (string, error) {
|
||||
|
||||
// CreateWork 创建作品
|
||||
func CreateWork(work *models.Work) error {
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
INSERT INTO works (id, title, category, year, hero_img, description, is_featured, created_at, updated_at, deleted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0)
|
||||
`
|
||||
_, err := config.DB.Exec(
|
||||
query,
|
||||
work.ID,
|
||||
work.Title,
|
||||
work.Category,
|
||||
work.Year,
|
||||
work.HeroImg,
|
||||
work.Description,
|
||||
work.IsFeatured,
|
||||
now,
|
||||
now,
|
||||
)
|
||||
err := config.DB.Create(work).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating work: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateWork 更新作品
|
||||
func UpdateWork(work *models.Work) error {
|
||||
now := time.Now().Unix()
|
||||
query := `
|
||||
UPDATE works SET title = ?, category = ?, year = ?, hero_img = ?, description = ?, is_featured = ?, updated_at = ?
|
||||
WHERE id = ? AND deleted_at = 0
|
||||
`
|
||||
_, err := config.DB.Exec(
|
||||
query,
|
||||
work.Title,
|
||||
work.Category,
|
||||
work.Year,
|
||||
work.HeroImg,
|
||||
work.Description,
|
||||
work.IsFeatured,
|
||||
now,
|
||||
work.ID,
|
||||
)
|
||||
err := config.DB.Model(&models.Work{}).
|
||||
Where("id = ? AND deleted_at = ?", work.ID, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"title": work.Title,
|
||||
"category": work.Category,
|
||||
"year": work.Year,
|
||||
"hero_img": work.HeroImg,
|
||||
"description": work.Description,
|
||||
"is_featured": work.IsFeatured,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Printf("Error updating work: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteWork 删除作品 (Soft Delete)
|
||||
func DeleteWork(id string) error {
|
||||
now := time.Now().Unix()
|
||||
query := "UPDATE works SET deleted_at = ? WHERE id = ?"
|
||||
_, err := config.DB.Exec(query, now, id)
|
||||
err := config.DB.Model(&models.Work{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting work: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -302,51 +201,31 @@ func DeleteWork(id string) error {
|
||||
func GetAdminWorks(page, pageSize int) ([]models.Work, int, error) {
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var works []models.Work
|
||||
var total int64
|
||||
|
||||
// 获取总数
|
||||
var total int
|
||||
countQuery := "SELECT COUNT(*) FROM works WHERE deleted_at = 0"
|
||||
err := config.DB.QueryRow(countQuery).Scan(&total)
|
||||
err := config.DB.Model(&models.Work{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Count(&total).Error
|
||||
if err != nil {
|
||||
log.Printf("Error getting work count: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT id, title, category, year, hero_img, description, is_featured, created_at, updated_at, deleted_at
|
||||
FROM works
|
||||
WHERE deleted_at = 0
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`
|
||||
rows, err := config.DB.Query(query, pageSize, offset)
|
||||
// 获取列表
|
||||
err = config.DB.Model(&models.Work{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("created_at DESC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
Find(&works).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying admin works: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var works []models.Work
|
||||
for rows.Next() {
|
||||
var work models.Work
|
||||
if err := rows.Scan(
|
||||
&work.ID,
|
||||
&work.Title,
|
||||
&work.Category,
|
||||
&work.Year,
|
||||
&work.HeroImg,
|
||||
&work.Description,
|
||||
&work.IsFeatured,
|
||||
&work.CreatedAt,
|
||||
&work.UpdatedAt,
|
||||
&work.DeletedAt,
|
||||
); err != nil {
|
||||
log.Printf("Error scanning work: %v", err)
|
||||
continue
|
||||
}
|
||||
works = append(works, work)
|
||||
}
|
||||
|
||||
return works, total, nil
|
||||
return works, int(total), nil
|
||||
}
|
||||
|
||||
// BuildWorksResponse 构建作品列表响应
|
||||
@@ -368,15 +247,13 @@ func BuildWorksResponse(works []models.Work) []models.WorkResponse {
|
||||
|
||||
// GetWorkCount 获取作品总数
|
||||
func GetWorkCount() (int, error) {
|
||||
var count int
|
||||
query := "SELECT COUNT(*) FROM works WHERE deleted_at = 0"
|
||||
row := config.DB.QueryRow(query)
|
||||
|
||||
err := row.Scan(&count)
|
||||
var count int64
|
||||
err := config.DB.Model(&models.Work{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Count(&count).Error
|
||||
if err != nil {
|
||||
log.Printf("Error getting work count: %v", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
return int(count), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user