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 "innate_tags": img.InnateTags,
103 "created_at": img.CreatedAt,
104 "updated_at": img.UpdatedAt,
105 "tags": img.Tags,
106 "tags_detail": tagsDetail,
107 })
108}
109
110type tagsRequest struct {
111 Tags []string `json:"tags"`
112}
113
114func (a *API) HandleAddTags(w http.ResponseWriter, r *http.Request) {
115 user := UserFromContext(r.Context())
116 if user == nil {
117 writeError(w, http.StatusUnauthorized, "unauthorized")
118 return
119 }
120
121 idStr := r.PathValue("id")
122 id, err := strconv.ParseInt(idStr, 10, 64)
123 if err != nil {
124 writeError(w, http.StatusBadRequest, "invalid image id")
125 return
126 }
127
128 img, err := a.DB.GetImageByID(r.Context(), id)
129 if err != nil {
130 writeError(w, http.StatusNotFound, "image not found")
131 return
132 }
133 if img.UploadedBy != user.ID {
134 writeError(w, http.StatusForbidden, "you can only edit tags on your own uploads")
135 return
136 }
137
138 var req tagsRequest
139 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
140 writeError(w, http.StatusBadRequest, "invalid JSON body")
141 return
142 }
143
144 if len(req.Tags) == 0 {
145 writeJSON(w, http.StatusOK, map[string]any{"tags_detail": img.Tags})
146 return
147 }
148
149 if err := a.DB.AddImageTags(r.Context(), id, req.Tags); err != nil {
150 writeError(w, http.StatusInternalServerError, "failed to add tags")
151 return
152 }
153
154 tagsDetail, err := a.DB.GetImageTagsWithCounts(r.Context(), id)
155 if err != nil {
156 tagsDetail = make([]db.Tag, 0)
157 }
158
159 writeJSON(w, http.StatusOK, map[string]any{"tags_detail": tagsDetail})
160}
161
162func (a *API) HandleRemoveTags(w http.ResponseWriter, r *http.Request) {
163 user := UserFromContext(r.Context())
164 if user == nil {
165 writeError(w, http.StatusUnauthorized, "unauthorized")
166 return
167 }
168
169 idStr := r.PathValue("id")
170 id, err := strconv.ParseInt(idStr, 10, 64)
171 if err != nil {
172 writeError(w, http.StatusBadRequest, "invalid image id")
173 return
174 }
175
176 img, err := a.DB.GetImageByID(r.Context(), id)
177 if err != nil {
178 writeError(w, http.StatusNotFound, "image not found")
179 return
180 }
181 if img.UploadedBy != user.ID {
182 writeError(w, http.StatusForbidden, "you can only edit tags on your own uploads")
183 return
184 }
185
186 var req tagsRequest
187 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
188 writeError(w, http.StatusBadRequest, "invalid JSON body")
189 return
190 }
191
192 if len(req.Tags) == 0 {
193 writeJSON(w, http.StatusOK, map[string]any{"tags_detail": img.Tags})
194 return
195 }
196
197 if err := a.DB.RemoveImageTags(r.Context(), id, req.Tags); err != nil {
198 writeError(w, http.StatusInternalServerError, "failed to remove tags")
199 return
200 }
201
202 tagsDetail, err := a.DB.GetImageTagsWithCounts(r.Context(), id)
203 if err != nil {
204 tagsDetail = make([]db.Tag, 0)
205 }
206
207 writeJSON(w, http.StatusOK, map[string]any{"tags_detail": tagsDetail})
208}