aboutsummaryrefslogtreecommitdiff
path: root/database/tags.go
blob: 0891dfc525a8ac7d7f9748f6c9280f28918cae2f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
package database

import (
	"fmt"
	"imageboard/config"
	"imageboard/models"
	"strings"
)

func GetTotalTagsCount() (int64, error) {
	var count int64
	err := DB.Model(&models.Tag{}).Where("is_deleted = ?", false).Count(&count).Error
	return count, err
}

func GetPopularTags(limit int) ([]models.Tag, error) {
	var tags []models.Tag
	err := DB.Where("is_deleted = ?", false).Order("count DESC").Limit(limit).Find(&tags).Error
	return tags, err
}

func GetRecentTags(limit int) ([]models.Tag, error) {
	var tags []models.Tag
	err := DB.Where("is_deleted = ?", false).Order("created_at DESC").Limit(limit).Find(&tags).Error
	return tags, err
}

func SearchTags(query string, limit int, offset int, tagType *config.TagType) ([]models.Tag, error) {
	var tags []models.Tag
	searchPattern := "%" + strings.TrimSpace(strings.ToLower(query)) + "%"

	dbQuery := DB.Where("name LIKE ? AND is_deleted = ?", searchPattern, false)
	if tagType != nil && strings.ToLower(string(*tagType)) != "" {
		dbQuery = dbQuery.Where("type = ?", strings.ToLower(string(*tagType)))
	}
	dbQuery = dbQuery.Order("count DESC, name ASC").Limit(limit).Offset(offset)

	err := dbQuery.Find(&tags).Error
	return tags, err
}

func SearchTagsExcluding(query string, imageID uint, limit int, tagType *config.TagType) ([]models.Tag, error) {
	var tags []models.Tag
	searchPattern := "%" + strings.TrimSpace(strings.ToLower(query)) + "%"

	dbQuery := DB.Where("name LIKE ? AND is_deleted = ? AND id NOT IN (?)",
		searchPattern, false,
		DB.Table("image_tags").Select("tag_id").Where("image_id = ?", imageID))

	if tagType != nil && strings.ToLower(string(*tagType)) != "" {
		dbQuery = dbQuery.Where("type = ?", strings.ToLower(string(*tagType)))
	}

	err := dbQuery.Order("count DESC, name ASC").Limit(limit).Find(&tags).Error

	return tags, err
}

func FindOrCreateTag(name string, tagType config.TagType) (*models.Tag, error) {
	name = strings.TrimSpace(strings.ToLower(name))

	// First check for active tag with exact name and type match
	var tag models.Tag
	if err := DB.Where("name = ? AND type = ? AND is_deleted = ?", name, tagType, false).First(&tag).Error; err == nil {
		return &tag, nil
	}

	// Check if a tag with the same name but different type exists
	var existingTag models.Tag
	if err := DB.Where("name = ? AND is_deleted = ?", name, false).First(&existingTag).Error; err == nil {
		if existingTag.Type != tagType {
			return nil, fmt.Errorf("tag '%s' already exists as %s type", name, existingTag.Type)
		}
	}

	// Check for deleted tag with same name and type and restore it
	if err := DB.Where("name = ? AND type = ? AND is_deleted = ?", name, tagType, true).First(&tag).Error; err == nil {
		tag.IsDeleted = false
		if err := DB.Save(&tag).Error; err != nil {
			return nil, fmt.Errorf("failed to restore tag: %v", err)
		}
		return &tag, nil
	}

	// Check if a deleted tag with same name but different type exists
	var deletedTag models.Tag
	if err := DB.Where("name = ? AND is_deleted = ?", name, true).First(&deletedTag).Error; err == nil {
		if deletedTag.Type != tagType {
			return nil, fmt.Errorf("tag '%s' previously existed as %s type", name, deletedTag.Type)
		}
	}

	// Create new tag
	tag = models.Tag{
		Name: name,
		Type: tagType,
	}

	if err := DB.Create(&tag).Error; err != nil {
		return nil, err
	}

	return &tag, nil
}

func AddTagToImage(imageID uint, tagID uint) error {
	// First get the tag to validate it exists and is not deleted
	var tag models.Tag
	if err := DB.Where("id = ? AND is_deleted = ?", tagID, false).First(&tag).Error; err != nil {
		return fmt.Errorf("tag not found or is deleted")
	}

	// Check if the association already exists
	var count int64
	err := DB.Table("image_tags").Where("image_id = ? AND tag_id = ?", imageID, tagID).Count(&count).Error
	if err != nil {
		return err
	}

	// If it doesn't exist, create it
	if count == 0 {
		err := DB.Exec("INSERT INTO image_tags (image_id, tag_id) VALUES (?, ?)", imageID, tagID).Error
		if err != nil {
			return err
		}
		// Increment tag count by 1
		return DB.Model(&models.Tag{}).Where("id = ?", tagID).Update("count", DB.Raw("count + 1")).Error
	}

	return nil // Already exists
}

func RemoveTagFromImage(imageID uint, tagID uint) error {
	err := DB.Exec("DELETE FROM image_tags WHERE image_id = ? AND tag_id = ?", imageID, tagID).Error
	if err != nil {
		return err
	}
	// Decrement tag count by 1
	return DB.Model(&models.Tag{}).Where("id = ?", tagID).Update("count", DB.Raw("count - 1")).Error
}

func GetImageTags(imageID uint) (map[string][]models.Tag, error) {
	var tags []models.Tag
	err := DB.Joins("JOIN image_tags ON image_tags.tag_id = tags.id").
		Where("image_tags.image_id = ? AND tags.is_deleted = ?", imageID, false).
		Preload("Parent").Preload("Children").Find(&tags).Error

	if err != nil {
		return nil, err
	}

	result := map[string][]models.Tag{
		"general":   {},
		"artist":    {},
		"character": {},
		"copyright": {},
		"meta":      {},
	}

	for _, tag := range tags {
		switch tag.Type {
		case config.TagTypeGeneral:
			result["general"] = append(result["general"], tag)
		case config.TagTypeArtist:
			result["artist"] = append(result["artist"], tag)
		case config.TagTypeCharacter:
			result["character"] = append(result["character"], tag)
		case config.TagTypeCopyright:
			result["copyright"] = append(result["copyright"], tag)
		case config.TagTypeMeta:
			result["meta"] = append(result["meta"], tag)
		}
	}

	return result, nil
}

func GetTagWithAncestors(tagID uint) (*models.Tag, []models.Tag, error) {
	var tag models.Tag
	if err := DB.Preload("Parent").Preload("Children").First(&tag, tagID).Error; err != nil {
		return nil, nil, err
	}

	var ancestors []models.Tag
	current := &tag
	for current.Parent != nil {
		ancestors = append(ancestors, *current.Parent)
		current = current.Parent
	}

	return &tag, ancestors, nil
}

func GetTagWithDescendants(tagID uint) (*models.Tag, []models.Tag, error) {
	var tag models.Tag
	if err := DB.Preload("Children").First(&tag, tagID).Error; err != nil {
		return nil, nil, err
	}

	var descendants []models.Tag
	var getChildren func(t *models.Tag)
	getChildren = func(t *models.Tag) {
		for _, child := range t.Children {
			descendants = append(descendants, child)
			childWithChildren := models.Tag{}
			DB.Preload("Children").First(&childWithChildren, child.ID)
			getChildren(&childWithChildren)
		}
	}

	getChildren(&tag)
	return &tag, descendants, nil
}