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  }