github.com/readium/readium-lcp-server@v0.0.0-20240101192032-6e95190e99f1/frontend/api/publication.go (about) 1 // Copyright 2020 Readium Foundation. All rights reserved. 2 // Use of this source code is governed by a BSD-style license 3 // that can be found in the LICENSE file exposed on Github (readium) in the project repository. 4 5 package staticapi 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "log" 11 "net/http" 12 "path/filepath" 13 "strconv" 14 15 "github.com/gorilla/mux" 16 "github.com/readium/readium-lcp-server/api" 17 "github.com/readium/readium-lcp-server/frontend/webpublication" 18 "github.com/readium/readium-lcp-server/problem" 19 ) 20 21 // GetPublications returns a list of publications 22 func GetPublications(w http.ResponseWriter, r *http.Request, s IServer) { 23 24 var page int64 25 var perPage int64 26 var err error 27 28 if r.FormValue("page") != "" { 29 page, err = strconv.ParseInt((r).FormValue("page"), 10, 32) 30 if err != nil { 31 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 32 return 33 } 34 } else { 35 page = 1 36 } 37 38 if r.FormValue("per_page") != "" { 39 perPage, err = strconv.ParseInt((r).FormValue("per_page"), 10, 32) 40 if err != nil { 41 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 42 return 43 } 44 } else { 45 perPage = 100 46 } 47 48 if page > 0 { 49 page-- //pagenum starting at 0 in code, but user interface starting at 1 50 } 51 52 if page < 0 { 53 problem.Error(w, r, problem.Problem{Detail: "page must be positive integer"}, http.StatusBadRequest) 54 return 55 } 56 57 pubs := make([]webpublication.Publication, 0) 58 fn := s.PublicationAPI().List(int(perPage), int(page)) 59 for it, err := fn(); err == nil; it, err = fn() { 60 pubs = append(pubs, it) 61 } 62 if len(pubs) > 0 { 63 nextPage := strconv.Itoa(int(page) + 1) 64 w.Header().Set("Link", "</publications/?page="+nextPage+">; rel=\"next\"; title=\"next\"") 65 } 66 if page > 1 { 67 previousPage := strconv.Itoa(int(page) - 1) 68 w.Header().Set("Link", "</publications/?page="+previousPage+">; rel=\"previous\"; title=\"previous\"") 69 } 70 w.Header().Set("Content-Type", api.ContentType_JSON) 71 72 enc := json.NewEncoder(w) 73 err = enc.Encode(pubs) 74 if err != nil { 75 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 76 return 77 } 78 } 79 80 // GetPublication returns a publication from its numeric id, given as part of the calling url 81 func GetPublication(w http.ResponseWriter, r *http.Request, s IServer) { 82 83 vars := mux.Vars(r) 84 var id int 85 var err error 86 if id, err = strconv.Atoi(vars["id"]); err != nil { 87 // id is not a number 88 problem.Error(w, r, problem.Problem{Detail: "The publication id must be an integer"}, http.StatusBadRequest) 89 } 90 91 if pub, err := s.PublicationAPI().Get(int64(id)); err == nil { 92 enc := json.NewEncoder(w) 93 if err = enc.Encode(pub); err == nil { 94 // send a json serialization of the publication 95 w.Header().Set("Content-Type", api.ContentType_JSON) 96 return 97 } 98 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 99 } else { 100 switch err { 101 case webpublication.ErrNotFound: 102 { 103 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 104 } 105 default: 106 { 107 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 108 } 109 } 110 } 111 } 112 113 // CheckPublicationByTitle checks if a publication with this title exists 114 func CheckPublicationByTitle(w http.ResponseWriter, r *http.Request, s IServer) { 115 116 title := r.URL.Query()["title"][0] 117 118 log.Println("Check the existence of a publication named " + string(title)) 119 120 if pub, err := s.PublicationAPI().CheckByTitle(string(title)); err == nil { 121 enc := json.NewEncoder(w) 122 if err = enc.Encode(pub); err == nil { 123 // send a json serialization of the boolean response 124 w.Header().Set("Content-Type", api.ContentType_JSON) 125 return 126 } 127 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 128 } else { 129 switch err { 130 case webpublication.ErrNotFound: 131 { 132 log.Println("No publication stored with name " + string(title)) 133 // problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 134 } 135 default: 136 { 137 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 138 } 139 } 140 } 141 } 142 143 // DecodeJSONPublication transforms a json string to a User struct 144 func DecodeJSONPublication(r *http.Request) (webpublication.Publication, error) { 145 146 var dec *json.Decoder 147 if ctype := r.Header["Content-Type"]; len(ctype) > 0 && ctype[0] == api.ContentType_JSON { 148 dec = json.NewDecoder(r.Body) 149 } 150 pub := webpublication.Publication{} 151 err := dec.Decode(&pub) 152 return pub, err 153 } 154 155 // CreatePublication creates a publication in the database 156 func CreatePublication(w http.ResponseWriter, r *http.Request, s IServer) { 157 158 var pub webpublication.Publication 159 var err error 160 if pub, err = DecodeJSONPublication(r); err != nil { 161 problem.Error(w, r, problem.Problem{Detail: "incorrect JSON Publication " + err.Error()}, http.StatusBadRequest) 162 return 163 } 164 165 log.Println("Create a publication named " + pub.Title) 166 167 // add publication 168 if err := s.PublicationAPI().Add(pub); err != nil { 169 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 170 return 171 } 172 173 // publication added to db 174 w.WriteHeader(http.StatusCreated) 175 } 176 177 // UploadPublication creates a new publication via a POST request 178 // This function is called from the frontend test client 179 func UploadPublication(w http.ResponseWriter, r *http.Request, s IServer) { 180 181 var pub webpublication.Publication 182 183 // get the title of the publication from the 'title' URL query param 184 pub.Title = r.URL.Query()["title"][0] 185 if len(r.URL.Query()["uuid"]) > 0 { 186 pub.UUID = r.URL.Query()["uuid"][0] 187 } 188 189 log.Println("Upload a publication named " + pub.Title) 190 191 // get the file handle 192 file, header, err := r.FormFile("file") 193 if err != nil { 194 log.Println("Error in FormFile") 195 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 196 return 197 } 198 199 // get the input file extension (will be used to select the proper encryption process) 200 extension := filepath.Ext(header.Filename) 201 202 err = s.PublicationAPI().Upload(file, extension, &pub) 203 204 if err != nil { 205 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 206 return 207 } 208 w.WriteHeader(http.StatusOK) 209 w.Header().Add("Content-Type", "application/json") 210 211 res := fmt.Sprintf("{\"id\":\"%s\"}", pub.UUID) 212 w.Write([]byte(res)) 213 } 214 215 // UpdatePublication updates an identified publication (id) in the database 216 func UpdatePublication(w http.ResponseWriter, r *http.Request, s IServer) { 217 218 vars := mux.Vars(r) 219 var id int 220 var err error 221 var pub webpublication.Publication 222 if id, err = strconv.Atoi(vars["id"]); err != nil { 223 // id is not a number 224 problem.Error(w, r, problem.Problem{Detail: "Plublication ID must be an integer"}, http.StatusBadRequest) 225 return 226 } 227 // ID is a number, check publication (json) 228 if pub, err = DecodeJSONPublication(r); err != nil { 229 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 230 return 231 } 232 // publication ok, id is a number, search publication to update 233 if foundPub, err := s.PublicationAPI().Get(int64(id)); err != nil { 234 switch err { 235 case webpublication.ErrNotFound: 236 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 237 default: 238 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 239 } 240 } else { 241 // publication is found! 242 if err := s.PublicationAPI().Update(webpublication.Publication{ 243 ID: foundPub.ID, 244 Title: pub.Title, 245 Status: foundPub.Status}); err != nil { 246 //update failed! 247 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 248 } 249 //database update ok 250 w.WriteHeader(http.StatusOK) 251 } 252 } 253 254 // DeletePublication removes a publication in the database 255 func DeletePublication(w http.ResponseWriter, r *http.Request, s IServer) { 256 257 vars := mux.Vars(r) 258 id, err := strconv.ParseInt(vars["id"], 10, 64) 259 if err != nil { 260 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 261 return 262 } 263 if err := s.PublicationAPI().Delete(id); err != nil { 264 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 265 return 266 } 267 // publication deleted from db 268 w.WriteHeader(http.StatusOK) 269 }