all repos — weimar @ cd624b1b90061a10147950a7633dd7579c78a61d

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	"github.com/mjensen/weimar/internal/db"
  9)
 10
 11func (a *API) HandleListImages(w http.ResponseWriter, r *http.Request) {
 12	tags := r.URL.Query()["tags"]
 13
 14	page, _ := strconv.Atoi(r.URL.Query().Get("page"))
 15	if page < 1 {
 16		page = 1
 17	}
 18	perPage, _ := strconv.Atoi(r.URL.Query().Get("per_page"))
 19	if perPage < 1 || perPage > 100 {
 20		perPage = 20
 21	}
 22	offset := (page - 1) * perPage
 23
 24	result, err := a.DB.ListImages(r.Context(), tags, perPage, offset)
 25	if err != nil {
 26		writeError(w, http.StatusInternalServerError, "failed to list images")
 27		return
 28	}
 29
 30	// Load tags and uploader for each image.
 31	for i := range result.Images {
 32		img := &result.Images[i]
 33
 34		tags, err := a.DB.GetImageTags(r.Context(), img.ID)
 35		if err == nil {
 36			img.Tags = tags
 37		}
 38
 39		user, err := a.DB.GetUserByID(r.Context(), img.UploadedBy)
 40		if err == nil {
 41			img.UploaderUsername = user.Username
 42			if user.AvatarPath != nil {
 43				img.AvatarURL = "/api/users/" + strconv.FormatInt(img.UploadedBy, 10) + "/avatar"
 44			}
 45		}
 46	}
 47
 48	writeJSON(w, http.StatusOK, map[string]any{
 49		"images":   result.Images,
 50		"total":    result.Total,
 51		"page":     page,
 52		"per_page": perPage,
 53	})
 54}
 55
 56func (a *API) HandleGetImage(w http.ResponseWriter, r *http.Request) {
 57	idStr := r.PathValue("id")
 58	id, err := strconv.ParseInt(idStr, 10, 64)
 59	if err != nil {
 60		writeError(w, http.StatusBadRequest, "invalid image id")
 61		return
 62	}
 63
 64	img, err := a.DB.GetImageByID(r.Context(), id)
 65	if err != nil {
 66		writeError(w, http.StatusNotFound, "image not found")
 67		return
 68	}
 69
 70	tagsDetail, err := a.DB.GetImageTagsWithCounts(r.Context(), id)
 71	if err != nil {
 72		tagsDetail = make([]db.Tag, 0)
 73	}
 74
 75	uploaderUsername := ""
 76	avatarURL := ""
 77	user, err := a.DB.GetUserByID(r.Context(), img.UploadedBy)
 78	if err == nil {
 79		uploaderUsername = user.Username
 80		if user.AvatarPath != nil {
 81			avatarURL = "/api/users/" + strconv.FormatInt(img.UploadedBy, 10) + "/avatar"
 82		}
 83	}
 84
 85	authUser := UserFromContext(r.Context())
 86	isOwner := authUser != nil && authUser.ID == img.UploadedBy
 87
 88	writeJSON(w, http.StatusOK, map[string]any{
 89		"id":                img.ID,
 90		"filename":          img.Filename,
 91		"storage_path":      img.StoragePath,
 92		"thumbnail_path":    img.ThumbnailPath,
 93		"uploaded_by":       img.UploadedBy,
 94		"uploader_username": uploaderUsername,
 95		"avatar_url":        avatarURL,
 96		"is_owner":          isOwner,
 97		"file_size":         img.FileSize,
 98		"width":             img.Width,
 99		"height":            img.Height,
100		"mime_type":         img.MimeType,
101		"created_at":        img.CreatedAt,
102		"updated_at":        img.UpdatedAt,
103		"tags":              img.Tags,
104		"tags_detail":       tagsDetail,
105	})
106}
107
108type tagsRequest struct {
109	Tags []string `json:"tags"`
110}
111
112func (a *API) HandleAddTags(w http.ResponseWriter, r *http.Request) {
113	user := UserFromContext(r.Context())
114	if user == nil {
115		writeError(w, http.StatusUnauthorized, "unauthorized")
116		return
117	}
118
119	idStr := r.PathValue("id")
120	id, err := strconv.ParseInt(idStr, 10, 64)
121	if err != nil {
122		writeError(w, http.StatusBadRequest, "invalid image id")
123		return
124	}
125
126	img, err := a.DB.GetImageByID(r.Context(), id)
127	if err != nil {
128		writeError(w, http.StatusNotFound, "image not found")
129		return
130	}
131	if img.UploadedBy != user.ID {
132		writeError(w, http.StatusForbidden, "you can only edit tags on your own uploads")
133		return
134	}
135
136	var req tagsRequest
137	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
138		writeError(w, http.StatusBadRequest, "invalid JSON body")
139		return
140	}
141
142	if len(req.Tags) == 0 {
143		writeJSON(w, http.StatusOK, map[string]any{"tags_detail": img.Tags})
144		return
145	}
146
147	if err := a.DB.AddImageTags(r.Context(), id, req.Tags); err != nil {
148		writeError(w, http.StatusInternalServerError, "failed to add tags")
149		return
150	}
151
152	tagsDetail, err := a.DB.GetImageTagsWithCounts(r.Context(), id)
153	if err != nil {
154		tagsDetail = make([]db.Tag, 0)
155	}
156
157	writeJSON(w, http.StatusOK, map[string]any{"tags_detail": tagsDetail})
158}
159
160func (a *API) HandleRemoveTags(w http.ResponseWriter, r *http.Request) {
161	user := UserFromContext(r.Context())
162	if user == nil {
163		writeError(w, http.StatusUnauthorized, "unauthorized")
164		return
165	}
166
167	idStr := r.PathValue("id")
168	id, err := strconv.ParseInt(idStr, 10, 64)
169	if err != nil {
170		writeError(w, http.StatusBadRequest, "invalid image id")
171		return
172	}
173
174	img, err := a.DB.GetImageByID(r.Context(), id)
175	if err != nil {
176		writeError(w, http.StatusNotFound, "image not found")
177		return
178	}
179	if img.UploadedBy != user.ID {
180		writeError(w, http.StatusForbidden, "you can only edit tags on your own uploads")
181		return
182	}
183
184	var req tagsRequest
185	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
186		writeError(w, http.StatusBadRequest, "invalid JSON body")
187		return
188	}
189
190	if len(req.Tags) == 0 {
191		writeJSON(w, http.StatusOK, map[string]any{"tags_detail": img.Tags})
192		return
193	}
194
195	if err := a.DB.RemoveImageTags(r.Context(), id, req.Tags); err != nil {
196		writeError(w, http.StatusInternalServerError, "failed to remove tags")
197		return
198	}
199
200	tagsDetail, err := a.DB.GetImageTagsWithCounts(r.Context(), id)
201	if err != nil {
202		tagsDetail = make([]db.Tag, 0)
203	}
204
205	writeJSON(w, http.StatusOK, map[string]any{"tags_detail": tagsDetail})
206}