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 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}