entries.go
⎇
Raw
1package web
2
3import (
4 "net/http"
5 "os"
6 "path/filepath"
7 "strings"
8
9 "forge.gpl-nas.de/physiobase/internal/store"
10)
11
12func (s *Server) index(w http.ResponseWriter, r *http.Request) {
13 ctx := r.Context()
14 q := strings.TrimSpace(r.URL.Query().Get("q"))
15 kind := r.URL.Query().Get("art")
16 if !store.ValidKind(kind) {
17 kind = ""
18 }
19 region := strings.TrimSpace(r.URL.Query().Get("region"))
20
21 entries, err := s.st.Search(ctx, q, kind, region)
22 if err != nil {
23 s.fail(w, r, err)
24 return
25 }
26 counts, total, err := s.st.KindCounts(ctx)
27 if err != nil {
28 s.fail(w, r, err)
29 return
30 }
31 regions, err := s.st.Regions(ctx)
32 if err != nil {
33 s.fail(w, r, err)
34 return
35 }
36 s.render(w, r, "index", http.StatusOK, map[string]any{
37 "Entries": entries, "Q": q, "Art": kind, "Region": region,
38 "Counts": counts, "Total": total, "Regions": regions,
39 })
40}
41
42type group struct {
43 Label string
44 Entries []store.Entry
45}
46
47func (s *Server) showEntry(w http.ResponseWriter, r *http.Request) {
48 ctx := r.Context()
49 id, ok := pathID(r, "id")
50 if !ok {
51 http.NotFound(w, r)
52 return
53 }
54 e, err := s.st.Entry(ctx, id)
55 if err != nil {
56 s.fail(w, r, err)
57 return
58 }
59 related, err := s.st.Related(ctx, id)
60 if err != nil {
61 s.fail(w, r, err)
62 return
63 }
64 linkable, err := s.st.Linkable(ctx, id)
65 if err != nil {
66 s.fail(w, r, err)
67 return
68 }
69 sources, err := s.st.Sources(ctx, id)
70 if err != nil {
71 s.fail(w, r, err)
72 return
73 }
74 media, err := s.st.Media(ctx, id)
75 if err != nil {
76 s.fail(w, r, err)
77 return
78 }
79 var handouts []store.Handout
80 if e.Kind == "uebung" {
81 if handouts, err = s.st.Handouts(ctx); err != nil {
82 s.fail(w, r, err)
83 return
84 }
85 }
86 // Verknüpfte Einträge nach Art gruppieren, in fester Reihenfolge
87 var groups []group
88 for _, k := range store.Kinds {
89 g := group{Label: k.Plural}
90 for _, x := range related {
91 if x.Kind == k.Key {
92 g.Entries = append(g.Entries, x)
93 }
94 }
95 if len(g.Entries) > 0 {
96 groups = append(groups, g)
97 }
98 }
99 s.render(w, r, "entry", http.StatusOK, map[string]any{
100 "E": e, "Groups": groups, "Linkable": linkable, "Sources": sources,
101 "Media": media, "Handouts": handouts, "Added": r.URL.Query().Get("handout"),
102 })
103}
104
105func (s *Server) newEntry(w http.ResponseWriter, r *http.Request) {
106 regions, err := s.st.Regions(r.Context())
107 if err != nil {
108 s.fail(w, r, err)
109 return
110 }
111 kind := r.URL.Query().Get("art")
112 if !store.ValidKind(kind) {
113 kind = "krankheitsbild"
114 }
115 s.render(w, r, "edit", http.StatusOK, map[string]any{
116 "E": store.Entry{Kind: kind, Region: r.URL.Query().Get("region")}, "New": true,
117 "Regions": regions, "LinkFrom": r.URL.Query().Get("von"),
118 })
119}
120
121func (s *Server) editEntry(w http.ResponseWriter, r *http.Request) {
122 id, ok := pathID(r, "id")
123 if !ok {
124 http.NotFound(w, r)
125 return
126 }
127 e, err := s.st.Entry(r.Context(), id)
128 if err != nil {
129 s.fail(w, r, err)
130 return
131 }
132 regions, err := s.st.Regions(r.Context())
133 if err != nil {
134 s.fail(w, r, err)
135 return
136 }
137 s.render(w, r, "edit", http.StatusOK, map[string]any{"E": e, "Regions": regions})
138}
139
140func (s *Server) saveEntry(w http.ResponseWriter, r *http.Request) {
141 if !limitForm(w, r) {
142 return
143 }
144 ctx := r.Context()
145 e := store.Entry{
146 Kind: formText(r, "kind"), Title: formText(r, "title"), Summary: formText(r, "summary"),
147 Body: formText(r, "body"), Region: formText(r, "region"), Tags: formText(r, "tags"),
148 Dosage: formText(r, "dosage"), PatientText: formText(r, "patient_text"),
149 }
150 if !store.ValidKind(e.Kind) || e.Title == "" {
151 regions, _ := s.st.Regions(ctx)
152 id, _ := pathID(r, "id")
153 e.ID = id
154 s.render(w, r, "edit", http.StatusBadRequest, map[string]any{
155 "E": e, "New": id == 0, "Regions": regions, "LinkFrom": r.FormValue("link_from"),
156 "Error": "Bitte einen Titel angeben.",
157 })
158 return
159 }
160 if r.PathValue("id") == "" {
161 id, err := s.st.CreateEntry(ctx, e)
162 if err != nil {
163 s.fail(w, r, err)
164 return
165 }
166 // "Neuen Eintrag verknüpft anlegen" vom Ausgangseintrag aus
167 if from, ok := formID(r, "link_from"); ok {
168 if err := s.st.Link(ctx, from, id); err != nil {
169 s.fail(w, r, err)
170 return
171 }
172 }
173 redirect(w, r, "/eintrag/%d", id)
174 return
175 }
176 id, ok := pathID(r, "id")
177 if !ok {
178 http.NotFound(w, r)
179 return
180 }
181 e.ID = id
182 if err := s.st.UpdateEntry(ctx, e); err != nil {
183 s.fail(w, r, err)
184 return
185 }
186 redirect(w, r, "/eintrag/%d", id)
187}
188
189func (s *Server) deleteEntry(w http.ResponseWriter, r *http.Request) {
190 id, ok := pathID(r, "id")
191 if !ok {
192 http.NotFound(w, r)
193 return
194 }
195 files, err := s.st.DeleteEntry(r.Context(), id)
196 if err != nil {
197 s.fail(w, r, err)
198 return
199 }
200 for _, f := range files {
201 // Dateinamen stammen aus der Datenbank (vom Server erzeugt), nicht vom Benutzer.
202 if err := os.Remove(filepath.Join(s.cfg.MediaDir, f)); err != nil && !os.IsNotExist(err) { //nolint:gosec // G703
203 s.log.Warn("medium nicht gelöscht", "datei", f, "err", err)
204 }
205 }
206 redirect(w, r, "/")
207}
208
209func (s *Server) linkEntry(w http.ResponseWriter, r *http.Request) {
210 if !limitForm(w, r) {
211 return
212 }
213 id, ok1 := pathID(r, "id")
214 other, ok2 := formID(r, "other")
215 if !ok1 || !ok2 {
216 http.Error(w, "Bitte einen Eintrag auswählen", http.StatusBadRequest)
217 return
218 }
219 if err := s.st.Link(r.Context(), id, other); err != nil {
220 s.fail(w, r, err)
221 return
222 }
223 redirect(w, r, "/eintrag/%d#verknuepft", id)
224}
225
226func (s *Server) unlinkEntry(w http.ResponseWriter, r *http.Request) {
227 id, ok1 := pathID(r, "id")
228 other, ok2 := pathID(r, "other")
229 if !ok1 || !ok2 {
230 http.NotFound(w, r)
231 return
232 }
233 if err := s.st.Unlink(r.Context(), id, other); err != nil {
234 s.fail(w, r, err)
235 return
236 }
237 redirect(w, r, "/eintrag/%d#verknuepft", id)
238}
239
240func (s *Server) addSource(w http.ResponseWriter, r *http.Request) {
241 if !limitForm(w, r) {
242 return
243 }
244 id, ok := pathID(r, "id")
245 if !ok {
246 http.NotFound(w, r)
247 return
248 }
249 src := store.Source{EntryID: id, Ref: formText(r, "ref"), Page: formText(r, "page"), URL: formText(r, "url"), Note: formText(r, "note")}
250 // Nur http(s)-Links zulassen
251 if src.URL != "" && !strings.HasPrefix(src.URL, "http://") && !strings.HasPrefix(src.URL, "https://") {
252 src.URL = "https://" + src.URL
253 }
254 if src.Ref != "" {
255 if err := s.st.AddSource(r.Context(), src); err != nil {
256 s.fail(w, r, err)
257 return
258 }
259 }
260 redirect(w, r, "/eintrag/%d#quellen", id)
261}
262
263func (s *Server) deleteSource(w http.ResponseWriter, r *http.Request) {
264 id, ok := pathID(r, "id")
265 if !ok {
266 http.NotFound(w, r)
267 return
268 }
269 eid, err := s.st.DeleteSource(r.Context(), id)
270 if err != nil {
271 s.fail(w, r, err)
272 return
273 }
274 redirect(w, r, "/eintrag/%d#quellen", eid)
275}
276