all repos — weimar @ link-previews

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