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}