all repos — weimar @ master

Unnamed repository; edit this file 'description' to name the repository.

internal/api/images.go (view raw)

  1package api
  2
  3import (
  4	"encoding/json"
  5	"net/http"
  6	"strconv"
  7
  8	"codeberg.org/maxwelljensen/weimar/internal/db"
  9)
 10
 11func (a *API) HandleListImages(w http.ResponseWriter, r *http.Request) {
 12	tags := r.URL.Query()["tags"]
 13	uploader := r.URL.Query().Get("uploader")
 14	sortGems := r.URL.Query().Get("sort") == "gems"
 15	filterGems := r.URL.Query().Get("gems") == "true"
 16
 17	page, _ := strconv.Atoi(r.URL.Query().Get("page"))
 18	if page < 1 {
 19		page = 1
 20	}
 21	perPage, _ := strconv.Atoi(r.URL.Query().Get("per_page"))
 22	if perPage < 1 || perPage > 100 {
 23		perPage = 20
 24	}
 25	offset := (page - 1) * perPage
 26
 27	result, err := a.DB.ListImages(r.Context(), tags, uploader, perPage, offset, sortGems, filterGems)
 28	if err != nil {
 29		writeError(w, http.StatusInternalServerError, "failed to list images")
 30		return
 31	}
 32
 33	// Load tags and uploader for each image.
 34	for i := range result.Images {
 35		img := &result.Images[i]
 36
 37		tags, err := a.DB.GetImageTags(r.Context(), img.ID)
 38		if err == nil {
 39			img.Tags = tags
 40		}
 41
 42		user, err := a.DB.GetUserByID(r.Context(), img.UploadedBy)
 43		if err == nil {
 44			img.UploaderUsername = user.Username
 45			if user.AvatarPath != nil {
 46				img.AvatarURL = "/api/users/" + strconv.FormatInt(img.UploadedBy, 10) + "/avatar"
 47			}
 48		}
 49	}
 50
 51	writeJSON(w, http.StatusOK, map[string]any{
 52		"images":   result.Images,
 53		"total":    result.Total,
 54		"page":     page,
 55		"per_page": perPage,
 56	})
 57}
 58
 59func (a *API) HandleGetImage(w http.ResponseWriter, r *http.Request) {
 60	idStr := r.PathValue("id")
 61	id, err := strconv.ParseInt(idStr, 10, 64)
 62	if err != nil {
 63		writeError(w, http.StatusBadRequest, "invalid image id")
 64		return
 65	}
 66
 67	img, err := a.DB.GetImageByID(r.Context(), id)
 68	if err != nil {
 69		writeError(w, http.StatusNotFound, "image not found")
 70		return
 71	}
 72
 73	tagsDetail, err := a.DB.GetImageTagsWithCounts(r.Context(), id)
 74	if err != nil {
 75		tagsDetail = make([]db.Tag, 0)
 76	}
 77
 78	uploaderUsername := ""
 79	avatarURL := ""
 80	user, err := a.DB.GetUserByID(r.Context(), img.UploadedBy)
 81	if err == nil {
 82		uploaderUsername = user.Username
 83		if user.AvatarPath != nil {
 84			avatarURL = "/api/users/" + strconv.FormatInt(img.UploadedBy, 10) + "/avatar"
 85		}
 86	}
 87
 88	authUser := UserFromContext(r.Context())
 89	isOwner := authUser != nil && authUser.ID == img.UploadedBy
 90
 91	// Check gem status for authenticated users.
 92	gemmed := false
 93	if authUser != nil {
 94		gemmed, _ = a.DB.GetUserGemStatus(r.Context(), id, authUser.ID)
 95	}
 96
 97	writeJSON(w, http.StatusOK, map[string]any{
 98		"id":                img.ID,
 99		"filename":          img.Filename,
100		"storage_path":      img.StoragePath,
101		"thumbnail_path":    img.ThumbnailPath,
102		"uploaded_by":       img.UploadedBy,
103		"uploader_username": uploaderUsername,
104		"avatar_url":        avatarURL,
105		"is_owner":          isOwner,
106		"file_size":         img.FileSize,
107		"width":             img.Width,
108		"height":            img.Height,
109		"mime_type":         img.MimeType,
110		"innate_tags":       img.InnateTags,
111		"duration":          img.Duration,
112		"gem_count":         img.GemCount,
113		"gemmed":            gemmed,
114		"created_at":        img.CreatedAt,
115		"updated_at":        img.UpdatedAt,
116		"tags":              img.Tags,
117		"tags_detail":       tagsDetail,
118	})
119}
120
121// HandleToggleGem toggles the gem status for an image by the current user.
122// Returns {"gemmed": bool, "gem_count": int}.
123func (a *API) HandleToggleGem(w http.ResponseWriter, r *http.Request) {
124	user := UserFromContext(r.Context())
125	if user == nil {
126		writeError(w, http.StatusUnauthorized, "unauthorized")
127		return
128	}
129
130	idStr := r.PathValue("id")
131	id, err := strconv.ParseInt(idStr, 10, 64)
132	if err != nil {
133		writeError(w, http.StatusBadRequest, "invalid image id")
134		return
135	}
136
137	// Verify image exists.
138	if _, err := a.DB.GetImageByID(r.Context(), id); err != nil {
139		writeError(w, http.StatusNotFound, "image not found")
140		return
141	}
142
143	gemmed, gemCount, err := a.DB.ToggleGem(r.Context(), id, user.ID)
144	if err != nil {
145		writeError(w, http.StatusInternalServerError, "failed to toggle gem")
146		return
147	}
148
149	writeJSON(w, http.StatusOK, map[string]any{
150		"gemmed":    gemmed,
151		"gem_count": gemCount,
152	})
153}
154
155type tagsRequest struct {
156	Tags []string `json:"tags"`
157}
158
159func (a *API) HandleAddTags(w http.ResponseWriter, r *http.Request) {
160	user := UserFromContext(r.Context())
161	if user == nil {
162		writeError(w, http.StatusUnauthorized, "unauthorized")
163		return
164	}
165
166	idStr := r.PathValue("id")
167	id, err := strconv.ParseInt(idStr, 10, 64)
168	if err != nil {
169		writeError(w, http.StatusBadRequest, "invalid image id")
170		return
171	}
172
173	img, err := a.DB.GetImageByID(r.Context(), id)
174	if err != nil {
175		writeError(w, http.StatusNotFound, "image not found")
176		return
177	}
178	if img.UploadedBy != user.ID {
179		writeError(w, http.StatusForbidden, "you can only edit tags on your own uploads")
180		return
181	}
182
183	var req tagsRequest
184	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
185		writeError(w, http.StatusBadRequest, "invalid JSON body")
186		return
187	}
188
189	if len(req.Tags) == 0 {
190		writeJSON(w, http.StatusOK, map[string]any{"tags_detail": img.Tags})
191		return
192	}
193
194	if err := a.DB.AddImageTags(r.Context(), id, req.Tags); err != nil {
195		writeError(w, http.StatusInternalServerError, "failed to add tags")
196		return
197	}
198
199	tagsDetail, err := a.DB.GetImageTagsWithCounts(r.Context(), id)
200	if err != nil {
201		tagsDetail = make([]db.Tag, 0)
202	}
203
204	writeJSON(w, http.StatusOK, map[string]any{"tags_detail": tagsDetail})
205}
206
207func (a *API) HandleRemoveTags(w http.ResponseWriter, r *http.Request) {
208	user := UserFromContext(r.Context())
209	if user == nil {
210		writeError(w, http.StatusUnauthorized, "unauthorized")
211		return
212	}
213
214	idStr := r.PathValue("id")
215	id, err := strconv.ParseInt(idStr, 10, 64)
216	if err != nil {
217		writeError(w, http.StatusBadRequest, "invalid image id")
218		return
219	}
220
221	img, err := a.DB.GetImageByID(r.Context(), id)
222	if err != nil {
223		writeError(w, http.StatusNotFound, "image not found")
224		return
225	}
226	if img.UploadedBy != user.ID {
227		writeError(w, http.StatusForbidden, "you can only edit tags on your own uploads")
228		return
229	}
230
231	var req tagsRequest
232	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
233		writeError(w, http.StatusBadRequest, "invalid JSON body")
234		return
235	}
236
237	if len(req.Tags) == 0 {
238		writeJSON(w, http.StatusOK, map[string]any{"tags_detail": img.Tags})
239		return
240	}
241
242	if err := a.DB.RemoveImageTags(r.Context(), id, req.Tags); err != nil {
243		writeError(w, http.StatusInternalServerError, "failed to remove tags")
244		return
245	}
246
247	tagsDetail, err := a.DB.GetImageTagsWithCounts(r.Context(), id)
248	if err != nil {
249		tagsDetail = make([]db.Tag, 0)
250	}
251
252	writeJSON(w, http.StatusOK, map[string]any{"tags_detail": tagsDetail})
253}