github.com/readium/readium-lcp-server@v0.0.0-20240101192032-6e95190e99f1/frontend/api/purchase.go (about) 1 // Copyright 2017 European Digital Reading Lab. All rights reserved. 2 // Licensed to the Readium Foundation under one or more contributor license agreements. 3 // Use of this source code is governed by a BSD-style license 4 // that can be found in the LICENSE file exposed on Github (readium) in the project repository. 5 6 package staticapi 7 8 import ( 9 "encoding/json" 10 "log" 11 "net/http" 12 "strconv" 13 14 "github.com/gorilla/mux" 15 "github.com/readium/readium-lcp-server/api" 16 "github.com/readium/readium-lcp-server/frontend/webpurchase" 17 "github.com/readium/readium-lcp-server/problem" 18 19 "github.com/Machiel/slugify" 20 ) 21 22 // DecodeJSONPurchase transforms a json object into an golang object 23 // 24 func DecodeJSONPurchase(r *http.Request) (webpurchase.Purchase, error) { 25 var dec *json.Decoder 26 if ctype := r.Header["Content-Type"]; len(ctype) > 0 && ctype[0] == api.ContentType_JSON { 27 dec = json.NewDecoder(r.Body) 28 } 29 purchase := webpurchase.Purchase{} 30 err := dec.Decode(&purchase) 31 return purchase, err 32 } 33 34 // GetPurchases searches all purchases for a client 35 // 36 func GetPurchases(w http.ResponseWriter, r *http.Request, s IServer) { 37 38 pagination, err := ExtractPaginationFromRequest(r) 39 if err != nil { 40 // user id is not a number 41 problem.Error(w, r, problem.Problem{Detail: "Pagination error"}, http.StatusBadRequest) 42 return 43 } 44 45 purchases := make([]webpurchase.Purchase, 0) 46 fn := s.PurchaseAPI().List(pagination.PerPage, pagination.Page) 47 48 var purchase webpurchase.Purchase 49 for purchase, err = fn(); err == nil; purchase, err = fn() { 50 purchases = append(purchases, purchase) 51 } 52 if err != webpurchase.ErrNotFound { 53 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 54 return 55 } 56 57 PrepareListHeaderResponse(len(purchases), "/api/v1/purchases", pagination, w) 58 w.Header().Set("Content-Type", api.ContentType_JSON) 59 60 enc := json.NewEncoder(w) 61 err = enc.Encode(purchases) 62 if err != nil { 63 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 64 return 65 } 66 } 67 68 // GetUserPurchases searches all purchases for a client 69 // 70 func GetUserPurchases(w http.ResponseWriter, r *http.Request, s IServer) { 71 var err error 72 var userId int64 73 vars := mux.Vars(r) 74 75 if userId, err = strconv.ParseInt(vars["user_id"], 10, 64); err != nil { 76 // user id is not a number 77 problem.Error(w, r, problem.Problem{Detail: "User ID must be an integer"}, http.StatusBadRequest) 78 return 79 } 80 81 pagination, err := ExtractPaginationFromRequest(r) 82 if err != nil { 83 // user id is not a number 84 problem.Error(w, r, problem.Problem{Detail: "Pagination error"}, http.StatusBadRequest) 85 return 86 } 87 88 purchases := make([]webpurchase.Purchase, 0) 89 fn := s.PurchaseAPI().ListByUser(userId, pagination.PerPage, pagination.Page) 90 91 var purchase webpurchase.Purchase 92 for purchase, err = fn(); err == nil && purchase.ID != 0; purchase, err = fn() { 93 purchases = append(purchases, purchase) 94 } 95 if err != nil { 96 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 97 return 98 } 99 100 PrepareListHeaderResponse(len(purchases), "/api/v1/users/"+vars["user_id"]+"/purchases", pagination, w) 101 w.Header().Set("Content-Type", api.ContentType_JSON) 102 103 enc := json.NewEncoder(w) 104 err = enc.Encode(purchases) 105 if err != nil { 106 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 107 return 108 } 109 } 110 111 // CreatePurchase creates a purchase in the database 112 // 113 func CreatePurchase(w http.ResponseWriter, r *http.Request, s IServer) { 114 var purchase webpurchase.Purchase 115 var err error 116 if purchase, err = DecodeJSONPurchase(r); err != nil { 117 problem.Error(w, r, problem.Problem{Detail: "incorrect JSON Purchase " + err.Error()}, http.StatusBadRequest) 118 return 119 } 120 121 // purchase ok 122 if err = s.PurchaseAPI().Add(purchase); err != nil { 123 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 124 return 125 } 126 127 // publication added to db 128 w.WriteHeader(http.StatusCreated) 129 130 if purchase.Type == webpurchase.LOAN { 131 log.Println("user " + strconv.Itoa(int(purchase.User.ID)) + " lent publication " + strconv.Itoa(int(purchase.Publication.ID)) + " until " + purchase.EndDate.String()) 132 } else { 133 log.Println("user " + strconv.Itoa(int(purchase.User.ID)) + " bought publication " + strconv.Itoa(int(purchase.Publication.ID))) 134 } 135 } 136 137 // GetPurchasedLicense generates a new license from the corresponding purchase id (passed as a section of the REST URL). 138 // It fetches the license from the lcp server and returns it to the caller. 139 // This API method is called from the client app (angular) when a license is requested after a purchase. 140 // 141 func GetPurchasedLicense(w http.ResponseWriter, r *http.Request, s IServer) { 142 vars := mux.Vars(r) 143 var id int64 144 var err error 145 146 if id, err = strconv.ParseInt(vars["id"], 10, 64); err != nil { 147 // id is not an integer (int64) 148 problem.Error(w, r, problem.Problem{Detail: "Purchase ID must be an integer"}, http.StatusBadRequest) 149 return 150 } 151 152 purchase, err := s.PurchaseAPI().Get(id) 153 if err != nil { 154 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 155 return 156 } 157 158 fullLicense, err := s.PurchaseAPI().GenerateOrGetLicense(purchase) 159 if err != nil { 160 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 161 return 162 } 163 164 attachmentName := slugify.Slugify(purchase.Publication.Title) 165 w.Header().Set("Content-Type", api.ContentType_LCP_JSON) 166 w.Header().Set("Content-Disposition", "attachment; filename=\""+attachmentName+".lcpl\"") 167 168 enc := json.NewEncoder(w) 169 // does not escape characters 170 enc.SetEscapeHTML(false) 171 err = enc.Encode(fullLicense) 172 173 if err != nil { 174 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 175 return 176 } 177 // message to the console 178 log.Println("Return license / id " + vars["id"] + " / " + purchase.Publication.Title + " / purchase " + strconv.FormatInt(purchase.ID, 10)) 179 180 } 181 182 // GetPurchase gets a purchase by its id in the database 183 // 184 func GetPurchase(w http.ResponseWriter, r *http.Request, s IServer) { 185 vars := mux.Vars(r) 186 var id int 187 var err error 188 if id, err = strconv.Atoi(vars["id"]); err != nil { 189 // id is not a number 190 problem.Error(w, r, problem.Problem{Detail: "Purchase ID must be an integer"}, http.StatusBadRequest) 191 return 192 } 193 194 purchase, err := s.PurchaseAPI().Get(int64(id)) 195 if err != nil { 196 switch err { 197 case webpurchase.ErrNotFound: 198 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 199 default: 200 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 201 } 202 return 203 } 204 205 w.Header().Set("Content-Type", api.ContentType_JSON) 206 // json encode the purchase info into the output stream 207 enc := json.NewEncoder(w) 208 if err = enc.Encode(purchase); err != nil { 209 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 210 return 211 } 212 } 213 214 // GetPurchaseByLicenseID gets a purchase by a license id in the database 215 // 216 func GetPurchaseByLicenseID(w http.ResponseWriter, r *http.Request, s IServer) { 217 var purchase webpurchase.Purchase 218 vars := mux.Vars(r) 219 var err error 220 221 if purchase, err = s.PurchaseAPI().GetByLicenseID(vars["licenseID"]); err != nil { 222 switch err { 223 case webpurchase.ErrNotFound: 224 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 225 default: 226 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 227 } 228 return 229 } 230 // purchase found 231 w.Header().Set("Content-Type", api.ContentType_JSON) 232 enc := json.NewEncoder(w) 233 if err = enc.Encode(purchase); err != nil { 234 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 235 return 236 } 237 } 238 239 // UpdatePurchase updates a purchase in the database 240 // Only updates the license id (uuid), start and end date, status 241 func UpdatePurchase(w http.ResponseWriter, r *http.Request, s IServer) { 242 var newPurchase webpurchase.Purchase 243 vars := mux.Vars(r) 244 var id int 245 var err error 246 247 // check that the purchase id is an integer 248 if id, err = strconv.Atoi(vars["id"]); err != nil { 249 problem.Error(w, r, problem.Problem{Detail: "The purchase id must be an integer"}, http.StatusBadRequest) 250 return 251 } 252 // parse the update info 253 if newPurchase, err = DecodeJSONPurchase(r); err != nil { 254 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 255 return 256 } 257 258 // console 259 log.Printf("Update purchase %v, license id %v, start %v, end %v, status %v", newPurchase.ID, *newPurchase.LicenseUUID, newPurchase.StartDate, newPurchase.EndDate, newPurchase.Status) 260 261 // update the purchase, license id, start and end dates, status 262 if err := s.PurchaseAPI().Update(webpurchase.Purchase{ 263 ID: int64(id), 264 LicenseUUID: newPurchase.LicenseUUID, 265 StartDate: newPurchase.StartDate, 266 EndDate: newPurchase.EndDate, 267 Status: newPurchase.Status}); err != nil { 268 269 switch err { 270 case webpurchase.ErrNotFound: 271 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 272 default: 273 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 274 } 275 return 276 } 277 278 w.WriteHeader(http.StatusOK) 279 }