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  }