@@ -0,0 +1,132 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/basketikun/infinite-canvas/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ListAssets 按查询条件返回素材分页列表。
|
||||
func ListAssets(q model.Query) ([]model.Asset, int64, error) {
|
||||
db, err := DB()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
q.Normalize()
|
||||
tx := applyAssetFilters(db.Model(&model.Asset{}), q)
|
||||
|
||||
var total int64
|
||||
if err := tx.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
var items []model.Asset
|
||||
err = tx.Order("updated_at desc").Offset(q.Offset()).Limit(q.PageSize).Find(&items).Error
|
||||
return items, total, err
|
||||
}
|
||||
|
||||
// ListAssetTags 返回当前素材查询条件下的全部标签。
|
||||
func ListAssetTags(q model.Query) ([]string, error) {
|
||||
db, err := DB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.Normalize()
|
||||
q.Tags = nil
|
||||
tx := applyAssetFilters(db.Model(&model.Asset{}), q)
|
||||
|
||||
var items []model.Asset
|
||||
if err := tx.Select("tags").Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return assetTagsFromItems(items), nil
|
||||
}
|
||||
|
||||
// SaveAsset 保存素材,并在更新时保留原创建时间。
|
||||
func SaveAsset(item model.Asset) (model.Asset, error) {
|
||||
db, err := DB()
|
||||
if err != nil {
|
||||
return item, err
|
||||
}
|
||||
if saved, ok, err := findAsset(db, item.ID); err != nil {
|
||||
return item, err
|
||||
} else if ok && item.CreatedAt == "" {
|
||||
item.CreatedAt = saved.CreatedAt
|
||||
}
|
||||
return item, db.Save(&item).Error
|
||||
}
|
||||
|
||||
// DeleteAsset 删除指定素材。
|
||||
func DeleteAsset(id string) error {
|
||||
db, err := DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Delete(&model.Asset{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// applyAssetFilters 应用素材列表的搜索条件。
|
||||
func applyAssetFilters(tx *gorm.DB, q model.Query) *gorm.DB {
|
||||
if q.Keyword != "" {
|
||||
like := "%" + q.Keyword + "%"
|
||||
tx = tx.Where("title LIKE ? OR description LIKE ? OR content LIKE ?", like, like, like)
|
||||
}
|
||||
if isActiveAssetOption(q.Type) {
|
||||
tx = tx.Where("type = ?", q.Type)
|
||||
}
|
||||
return applyAssetTagsFilter(tx, q.Tags)
|
||||
}
|
||||
|
||||
// findAsset 根据 ID 查询素材。
|
||||
func findAsset(db *gorm.DB, id string) (model.Asset, bool, error) {
|
||||
item := model.Asset{}
|
||||
err := db.Where("id = ?", id).First(&item).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return model.Asset{}, false, nil
|
||||
}
|
||||
return item, err == nil, err
|
||||
}
|
||||
|
||||
// applyAssetTagsFilter 应用 JSON 标签条件。
|
||||
func applyAssetTagsFilter(tx *gorm.DB, tags []string) *gorm.DB {
|
||||
if len(tags) == 0 {
|
||||
return tx
|
||||
}
|
||||
condition := tx.Session(&gorm.Session{NewDB: true})
|
||||
for _, tag := range tags {
|
||||
condition = condition.Or(assetJSONTagsContains(tx), tag)
|
||||
}
|
||||
return tx.Where(condition)
|
||||
}
|
||||
|
||||
func assetTagsFromItems(items []model.Asset) []string {
|
||||
seen := map[string]bool{}
|
||||
tags := []string{}
|
||||
for _, item := range items {
|
||||
for _, tag := range item.Tags {
|
||||
if tag != "" && !seen[tag] {
|
||||
seen[tag] = true
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
// assetJSONTagsContains 返回素材 tags 的 JSON 包含条件。
|
||||
func assetJSONTagsContains(tx *gorm.DB) string {
|
||||
switch tx.Dialector.Name() {
|
||||
case "mysql":
|
||||
return "JSON_CONTAINS(tags, JSON_QUOTE(?))"
|
||||
case "postgres":
|
||||
return "jsonb_exists(tags::jsonb, ?)"
|
||||
default:
|
||||
return "EXISTS (SELECT 1 FROM json_each(tags) WHERE value = ?)"
|
||||
}
|
||||
}
|
||||
|
||||
// isActiveAssetOption 判断素材筛选项有效状态。
|
||||
func isActiveAssetOption(value string) bool {
|
||||
return value != "" && value != "全部" && value != "all"
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/basketikun/infinite-canvas/config"
|
||||
"github.com/basketikun/infinite-canvas/model"
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var promptCategories = []model.PromptCategory{
|
||||
{Category: "system", Name: "系统", Description: "系统提示词分类"},
|
||||
{Category: "gpt-image-2-prompts", Name: "GPT Image 2 Prompts", Description: "EvoLinkAI 的 GPT Image 2 案例提示词分类", GithubURL: "https://github.com/EvoLinkAI/awesome-gpt-image-2-API-and-Prompts", Remote: true},
|
||||
{Category: "awesome-gpt-image", Name: "Awesome GPT Image", Description: "ZeroLu 的中文 GPT Image 提示词分类", GithubURL: "https://github.com/ZeroLu/awesome-gpt-image", Remote: true},
|
||||
{Category: "awesome-gpt4o-image-prompts", Name: "Awesome GPT4o Image Prompts", Description: "ImgEdify 的 GPT-4o 图像提示词分类", GithubURL: "https://github.com/ImgEdify/Awesome-GPT4o-Image-Prompts", Remote: true},
|
||||
{Category: "youmind-gpt-image-2", Name: "YouMind GPT Image 2", Description: "YouMind OpenLab 的 GPT Image 2 中文提示词分类", GithubURL: "https://github.com/YouMind-OpenLab/awesome-gpt-image-2", Remote: true},
|
||||
{Category: "youmind-nano-banana-pro", Name: "YouMind Nano Banana Pro", Description: "YouMind OpenLab 的 Nano Banana Pro 中文提示词分类", GithubURL: "https://github.com/YouMind-OpenLab/awesome-nano-banana-pro-prompts", Remote: true},
|
||||
}
|
||||
|
||||
var (
|
||||
db *gorm.DB
|
||||
dbOnce sync.Once
|
||||
dbErr error
|
||||
)
|
||||
|
||||
// DB 初始化并返回全局数据库连接。
|
||||
func DB() (*gorm.DB, error) {
|
||||
dbOnce.Do(func() {
|
||||
driver := strings.ToLower(strings.TrimSpace(config.Cfg.StorageDriver))
|
||||
if driver == "" {
|
||||
driver = "sqlite"
|
||||
}
|
||||
dsn := config.Cfg.DatabaseDSN
|
||||
if driver == "sqlite" && dsn != ":memory:" {
|
||||
_ = os.MkdirAll(filepath.Dir(dsn), 0755)
|
||||
}
|
||||
db, dbErr = gorm.Open(dialector(driver, dsn), &gorm.Config{})
|
||||
if dbErr != nil {
|
||||
return
|
||||
}
|
||||
dbErr = db.AutoMigrate(
|
||||
&model.User{},
|
||||
&model.Prompt{},
|
||||
&model.Asset{},
|
||||
)
|
||||
})
|
||||
return db, dbErr
|
||||
}
|
||||
|
||||
func dialector(driver string, dsn string) gorm.Dialector {
|
||||
switch driver {
|
||||
case "mysql":
|
||||
return mysql.Open(dsn)
|
||||
case "postgres", "postgresql":
|
||||
return postgres.Open(dsn)
|
||||
default:
|
||||
return sqlite.Open(dsn)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/basketikun/infinite-canvas/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// PromptCategories 返回内置提示词分类的副本。
|
||||
func PromptCategories() []model.PromptCategory {
|
||||
result := make([]model.PromptCategory, len(promptCategories))
|
||||
copy(result, promptCategories)
|
||||
return result
|
||||
}
|
||||
|
||||
// PromptCategoryByCode 根据分类编码查找内置提示词分类。
|
||||
func PromptCategoryByCode(category string) (model.PromptCategory, bool) {
|
||||
for _, item := range promptCategories {
|
||||
if item.Category == category {
|
||||
return item, true
|
||||
}
|
||||
}
|
||||
return model.PromptCategory{}, false
|
||||
}
|
||||
|
||||
// ListPromptCategories 返回内置提示词分类。
|
||||
func ListPromptCategories() ([]model.PromptCategory, error) {
|
||||
return PromptCategories(), nil
|
||||
}
|
||||
|
||||
// ListPrompts 按查询条件返回提示词分页列表。
|
||||
func ListPrompts(q model.Query) ([]model.Prompt, int64, error) {
|
||||
db, err := DB()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
q.Normalize()
|
||||
tx := applyPromptFilters(db.Model(&model.Prompt{}), q)
|
||||
|
||||
var total int64
|
||||
if err := tx.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
var items []model.Prompt
|
||||
if err := tx.Order("updated_at desc").Offset(q.Offset()).Limit(q.PageSize).Find(&items).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
categories, _ := ListPromptCategories()
|
||||
githubURLs := map[string]string{}
|
||||
for _, item := range categories {
|
||||
githubURLs[item.Category] = item.GithubURL
|
||||
}
|
||||
for i := range items {
|
||||
items[i].GithubURL = githubURLs[items[i].Category]
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
// ListPromptTags 返回当前提示词查询条件下的全部标签。
|
||||
func ListPromptTags(q model.Query) ([]string, error) {
|
||||
db, err := DB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.Normalize()
|
||||
q.Tags = nil
|
||||
tx := applyPromptFilters(db.Model(&model.Prompt{}), q)
|
||||
|
||||
var items []model.Prompt
|
||||
if err := tx.Select("tags").Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return promptTagsFromItems(items), nil
|
||||
}
|
||||
|
||||
// SavePrompt 保存提示词,并在更新时保留原创建时间。
|
||||
func SavePrompt(item model.Prompt) (model.Prompt, error) {
|
||||
db, err := DB()
|
||||
if err != nil {
|
||||
return item, err
|
||||
}
|
||||
if saved, ok, err := findPrompt(db, item.ID); err != nil {
|
||||
return item, err
|
||||
} else if ok && item.CreatedAt == "" {
|
||||
item.CreatedAt = saved.CreatedAt
|
||||
}
|
||||
item.GithubURL = ""
|
||||
return item, db.Save(&item).Error
|
||||
}
|
||||
|
||||
// DeletePrompt 删除指定提示词。
|
||||
func DeletePrompt(id string) error {
|
||||
db, err := DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Delete(&model.Prompt{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// ReplacePromptCategory 用远程同步结果替换整个提示词分类。
|
||||
func ReplacePromptCategory(category model.PromptCategory, items []model.Prompt) error {
|
||||
db, err := DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("category = ?", category.Category).Delete(&model.Prompt{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
for i := range items {
|
||||
items[i].Category = category.Category
|
||||
items[i].GithubURL = ""
|
||||
}
|
||||
return tx.Create(&items).Error
|
||||
})
|
||||
}
|
||||
|
||||
// applyPromptFilters 应用提示词列表的搜索条件。
|
||||
func applyPromptFilters(tx *gorm.DB, q model.Query) *gorm.DB {
|
||||
if q.Keyword != "" {
|
||||
like := "%" + q.Keyword + "%"
|
||||
tx = tx.Where("title LIKE ? OR prompt LIKE ?", like, like)
|
||||
}
|
||||
if isActivePromptOption(q.Category) {
|
||||
tx = tx.Where("category = ?", q.Category)
|
||||
}
|
||||
return applyPromptTagsFilter(tx, q.Tags)
|
||||
}
|
||||
|
||||
// findPrompt 根据 ID 查询提示词。
|
||||
func findPrompt(db *gorm.DB, id string) (model.Prompt, bool, error) {
|
||||
item := model.Prompt{}
|
||||
err := db.Where("id = ?", id).First(&item).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return model.Prompt{}, false, nil
|
||||
}
|
||||
return item, err == nil, err
|
||||
}
|
||||
|
||||
// applyPromptTagsFilter 应用 JSON 标签条件。
|
||||
func applyPromptTagsFilter(tx *gorm.DB, tags []string) *gorm.DB {
|
||||
if len(tags) == 0 {
|
||||
return tx
|
||||
}
|
||||
condition := tx.Session(&gorm.Session{NewDB: true})
|
||||
for _, tag := range tags {
|
||||
condition = condition.Or(promptJSONTagsContains(tx), tag)
|
||||
}
|
||||
return tx.Where(condition)
|
||||
}
|
||||
|
||||
func promptTagsFromItems(items []model.Prompt) []string {
|
||||
seen := map[string]bool{}
|
||||
tags := []string{}
|
||||
for _, item := range items {
|
||||
for _, tag := range item.Tags {
|
||||
if tag != "" && !seen[tag] {
|
||||
seen[tag] = true
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
// promptJSONTagsContains 返回提示词 tags 的 JSON 包含条件。
|
||||
func promptJSONTagsContains(tx *gorm.DB) string {
|
||||
switch tx.Dialector.Name() {
|
||||
case "mysql":
|
||||
return "JSON_CONTAINS(tags, JSON_QUOTE(?))"
|
||||
case "postgres":
|
||||
return "jsonb_exists(tags::jsonb, ?)"
|
||||
default:
|
||||
return "EXISTS (SELECT 1 FROM json_each(tags) WHERE value = ?)"
|
||||
}
|
||||
}
|
||||
|
||||
// isActivePromptOption 判断提示词筛选项有效状态。
|
||||
func isActivePromptOption(value string) bool {
|
||||
return value != "" && value != "全部" && value != "all"
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/basketikun/infinite-canvas/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ListUsers 分页查询用户。
|
||||
func ListUsers(q model.Query) ([]model.User, int64, error) {
|
||||
db, err := DB()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
q.Normalize()
|
||||
tx := db.Model(&model.User{})
|
||||
|
||||
var total int64
|
||||
if err := tx.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
var users []model.User
|
||||
err = tx.Order("created_at desc").Offset(q.Offset()).Limit(q.PageSize).Find(&users).Error
|
||||
return users, total, err
|
||||
}
|
||||
|
||||
// CountUsers 返回用户总数。
|
||||
func CountUsers() (int64, error) {
|
||||
db, err := DB()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var total int64
|
||||
return total, db.Model(&model.User{}).Count(&total).Error
|
||||
}
|
||||
|
||||
// HasAdmin 判断系统中是否存在管理员。
|
||||
func HasAdmin() (bool, error) {
|
||||
db, err := DB()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var total int64
|
||||
err = db.Model(&model.User{}).Where("role = ?", model.UserRoleAdmin).Count(&total).Error
|
||||
return total > 0, err
|
||||
}
|
||||
|
||||
// GetUserByID 根据 ID 查询用户。
|
||||
func GetUserByID(id string) (model.User, bool, error) {
|
||||
db, err := DB()
|
||||
if err != nil {
|
||||
return model.User{}, false, err
|
||||
}
|
||||
return findUser(db, "id = ?", id)
|
||||
}
|
||||
|
||||
// GetUserByUsername 根据用户名查询用户。
|
||||
func GetUserByUsername(username string) (model.User, bool, error) {
|
||||
db, err := DB()
|
||||
if err != nil {
|
||||
return model.User{}, false, err
|
||||
}
|
||||
return findUser(db, "username = ?", username)
|
||||
}
|
||||
|
||||
// SaveUser 保存用户信息。
|
||||
func SaveUser(user model.User) (model.User, error) {
|
||||
db, err := DB()
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
return user, db.Save(&user).Error
|
||||
}
|
||||
|
||||
// DeleteUser 删除指定用户。
|
||||
func DeleteUser(id string) error {
|
||||
db, err := DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Delete(&model.User{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// findUser 查询单个用户,并将未命中转换为 ok=false。
|
||||
func findUser(db *gorm.DB, query string, args ...any) (model.User, bool, error) {
|
||||
user := model.User{}
|
||||
err := db.Where(query, args...).First(&user).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return model.User{}, false, nil
|
||||
}
|
||||
return user, err == nil, err
|
||||
}
|
||||
Reference in New Issue
Block a user