first commit

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
HouYunFei
2026-05-19 07:53:53 +08:00
commit 472bf8b732
133 changed files with 16869 additions and 0 deletions
+132
View File
@@ -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"
}
+65
View File
@@ -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)
}
}
+186
View File
@@ -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"
}
+94
View File
@@ -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
}