github.com/readium/readium-lcp-server@v0.0.0-20240101192032-6e95190e99f1/frontend/server/server.go (about)

     1  // Copyright (c) 2016 Readium Foundation
     2  //
     3  // Redistribution and use in source and binary forms, with or without modification,
     4  // are permitted provided that the following conditions are met:
     5  //
     6  // 1. Redistributions of source code must retain the above copyright notice, this
     7  //    list of conditions and the following disclaimer.
     8  // 2. Redistributions in binary form must reproduce the above copyright notice,
     9  //    this list of conditions and the following disclaimer in the documentation and/or
    10  //    other materials provided with the distribution.
    11  // 3. Neither the name of the organization nor the names of its contributors may be
    12  //    used to endorse or promote products derived from this software without specific
    13  //    prior written permission
    14  //
    15  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
    16  // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    17  // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    18  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
    19  // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    20  // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    21  // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    22  // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    23  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    24  // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    25  
    26  package frontend
    27  
    28  import (
    29  	"crypto/tls"
    30  	"encoding/base64"
    31  	"fmt"
    32  	"io/ioutil"
    33  	"log"
    34  	"net/http"
    35  	"time"
    36  
    37  	auth "github.com/abbot/go-http-auth"
    38  	"github.com/claudiu/gocron"
    39  	"github.com/gorilla/mux"
    40  	"github.com/readium/readium-lcp-server/api"
    41  	"github.com/readium/readium-lcp-server/config"
    42  	staticapi "github.com/readium/readium-lcp-server/frontend/api"
    43  	"github.com/readium/readium-lcp-server/frontend/webdashboard"
    44  	"github.com/readium/readium-lcp-server/frontend/weblicense"
    45  	"github.com/readium/readium-lcp-server/frontend/webpublication"
    46  	"github.com/readium/readium-lcp-server/frontend/webpurchase"
    47  	"github.com/readium/readium-lcp-server/frontend/webrepository"
    48  	"github.com/readium/readium-lcp-server/frontend/webuser"
    49  )
    50  
    51  //Server struct contains server info and  db interfaces
    52  type Server struct {
    53  	http.Server
    54  	readonly     bool
    55  	cert         *tls.Certificate
    56  	repositories webrepository.WebRepository
    57  	publications webpublication.WebPublication
    58  	users        webuser.WebUser
    59  	dashboard    webdashboard.WebDashboard
    60  	license      weblicense.WebLicense
    61  	purchases    webpurchase.WebPurchase
    62  }
    63  
    64  // HandlerFunc defines a function handled by the server
    65  type HandlerFunc func(w http.ResponseWriter, r *http.Request, s staticapi.IServer)
    66  
    67  type HandlerPrivateFunc func(w http.ResponseWriter, r *auth.AuthenticatedRequest, s staticapi.IServer)
    68  
    69  // New creates a new webserver (basic user interface)
    70  func New(
    71  	bindAddr string,
    72  	tplPath string,
    73  	repositoryAPI webrepository.WebRepository,
    74  	publicationAPI webpublication.WebPublication,
    75  	userAPI webuser.WebUser,
    76  	dashboardAPI webdashboard.WebDashboard,
    77  	licenseAPI weblicense.WebLicense,
    78  	purchaseAPI webpurchase.WebPurchase,
    79  	basicAuth *auth.BasicAuth) *Server {
    80  
    81  	sr := api.CreateServerRouter(tplPath)
    82  	s := &Server{
    83  		Server: http.Server{
    84  			Handler:        sr.N,
    85  			Addr:           bindAddr,
    86  			WriteTimeout:   150 * time.Second,
    87  			ReadTimeout:    150 * time.Second,
    88  			MaxHeaderBytes: 1 << 20,
    89  		},
    90  		repositories: repositoryAPI,
    91  		publications: publicationAPI,
    92  		users:        userAPI,
    93  		dashboard:    dashboardAPI,
    94  		license:      licenseAPI,
    95  		purchases:    purchaseAPI}
    96  
    97  	// Cron, get license status information
    98  	gocron.Start()
    99  	gocron.Every(10).Minutes().Do(fetchLicenseStatusesTask, s)
   100  
   101  	apiURLPrefix := "/api/v1"
   102  
   103  	//
   104  	//  repositories of master files
   105  	//
   106  	repositoriesRoutesPathPrefix := apiURLPrefix + "/repositories"
   107  	repositoriesRoutes := sr.R.PathPrefix(repositoriesRoutesPathPrefix).Subrouter().StrictSlash(false)
   108  	//
   109  	s.handleFunc(repositoriesRoutes, "/master-files", staticapi.GetRepositoryMasterFiles).Methods("GET")
   110  	//
   111  	// dashboard
   112  	//
   113  	s.handleFunc(sr.R, "/dashboardInfos", staticapi.GetDashboardInfos).Methods("GET")
   114  	s.handleFunc(sr.R, "/dashboardBestSellers", staticapi.GetDashboardBestSellers).Methods("GET")
   115  	//
   116  	// publications
   117  	//
   118  	publicationsRoutesPathPrefix := apiURLPrefix + "/publications"
   119  	publicationsRoutes := sr.R.PathPrefix(publicationsRoutesPathPrefix).Subrouter().StrictSlash(false)
   120  	//
   121  	s.handleFunc(sr.R, publicationsRoutesPathPrefix, staticapi.GetPublications).Methods("GET")
   122  	//
   123  	s.handleFunc(sr.R, publicationsRoutesPathPrefix, staticapi.CreatePublication).Methods("POST")
   124  	//
   125  	s.handleFunc(sr.R, "/publicationUpload", staticapi.UploadPublication).Methods("POST")
   126  	//
   127  	s.handleFunc(publicationsRoutes, "/check-by-title", staticapi.CheckPublicationByTitle).Methods("GET")
   128  	//
   129  	s.handleFunc(publicationsRoutes, "/{id}", staticapi.GetPublication).Methods("GET")
   130  	s.handleFunc(publicationsRoutes, "/{id}", staticapi.UpdatePublication).Methods("PUT")
   131  	s.handleFunc(publicationsRoutes, "/{id}", staticapi.DeletePublication).Methods("DELETE")
   132  	//
   133  	// user functions
   134  	//
   135  	usersRoutesPathPrefix := apiURLPrefix + "/users"
   136  	usersRoutes := sr.R.PathPrefix(usersRoutesPathPrefix).Subrouter().StrictSlash(false)
   137  	//
   138  	s.handleFunc(sr.R, usersRoutesPathPrefix, staticapi.GetUsers).Methods("GET")
   139  	//
   140  	s.handleFunc(sr.R, usersRoutesPathPrefix, staticapi.CreateUser).Methods("POST")
   141  	//
   142  	s.handleFunc(usersRoutes, "/{id}", staticapi.GetUser).Methods("GET")
   143  	s.handleFunc(usersRoutes, "/{id}", staticapi.UpdateUser).Methods("PUT")
   144  	s.handleFunc(usersRoutes, "/{id}", staticapi.DeleteUser).Methods("DELETE")
   145  	// get all purchases for a given user
   146  	s.handleFunc(usersRoutes, "/{user_id}/purchases", staticapi.GetUserPurchases).Methods("GET")
   147  
   148  	//
   149  	// purchases
   150  	//
   151  	purchasesRoutesPathPrefix := apiURLPrefix + "/purchases"
   152  	purchasesRoutes := sr.R.PathPrefix(purchasesRoutesPathPrefix).Subrouter().StrictSlash(false)
   153  	// get all purchases
   154  	s.handleFunc(sr.R, purchasesRoutesPathPrefix, staticapi.GetPurchases).Methods("GET")
   155  	// create a purchase
   156  	s.handleFunc(sr.R, purchasesRoutesPathPrefix, staticapi.CreatePurchase).Methods("POST")
   157  	// update a purchase
   158  	s.handleFunc(purchasesRoutes, "/{id}", staticapi.UpdatePurchase).Methods("PUT")
   159  	// get a purchase by purchase id
   160  	s.handleFunc(purchasesRoutes, "/{id}", staticapi.GetPurchase).Methods("GET")
   161  	// get a license from the associated purchase id
   162  	s.handleFunc(purchasesRoutes, "/{id}/license", staticapi.GetPurchasedLicense).Methods("GET")
   163  	//
   164  	// licences
   165  	//
   166  	licenseRoutesPathPrefix := apiURLPrefix + "/licenses"
   167  	licenseRoutes := sr.R.PathPrefix(licenseRoutesPathPrefix).Subrouter().StrictSlash(false)
   168  	//
   169  	// get a list of licenses
   170  	s.handleFunc(sr.R, licenseRoutesPathPrefix, staticapi.GetFilteredLicenses).Methods("GET")
   171  	// get a license by id
   172  	s.handleFunc(licenseRoutes, "/{license_id}", staticapi.GetLicense).Methods("GET")
   173  	// get the user who owns a given license; this route is only set if authentication is in use
   174  	if basicAuth != nil {
   175  		s.handlePrivateFunc(licenseRoutes, "/{license_id}/user", staticapi.GetLicenseOwner, basicAuth).Methods("GET")
   176  	}
   177  
   178  	return s
   179  }
   180  
   181  // fetchLicenseStatusesTask fetchs from the Status Doc Server, and saves, locally, all license status documents.
   182  // This is optimizing the visualization of status information in the UI.
   183  func fetchLicenseStatusesTask(s *Server) {
   184  	fmt.Println("AUTOMATIC : Fetch and save all license status documents")
   185  	url := config.Config.LsdServer.PublicBaseUrl + "/licenses"
   186  	auth := config.Config.LsdNotifyAuth
   187  
   188  	// prepare the request
   189  	client := &http.Client{}
   190  	req, err := http.NewRequest("GET", url, nil)
   191  	if err != nil {
   192  		panic(err)
   193  	}
   194  	req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth.Username+":"+auth.Password)))
   195  	res, err := client.Do(req)
   196  	if err != nil {
   197  		log.Println("No http connection - no fetch this time")
   198  		return
   199  	}
   200  
   201  	// get all licence status documents from the lsd server
   202  	body, err := ioutil.ReadAll(res.Body)
   203  	if err != nil {
   204  		log.Println("Failed to read from the http connection - no fetch this time")
   205  		return
   206  	}
   207  	defer res.Body.Close()
   208  
   209  	// clear the db
   210  	err = s.license.PurgeDataBase()
   211  	if err != nil {
   212  		panic(err)
   213  	}
   214  
   215  	// fill the db
   216  	err = s.license.AddFromJSON(body)
   217  	if err != nil {
   218  		log.Printf("Unable to process JSON - no fetch this time - err %s\n", err.Error())
   219  	}
   220  }
   221  
   222  // RepositoryAPI ( staticapi.IServer ) returns interface for repositories
   223  func (server *Server) RepositoryAPI() webrepository.WebRepository {
   224  	return server.repositories
   225  }
   226  
   227  // PublicationAPI ( staticapi.IServer )returns DB interface for users
   228  func (server *Server) PublicationAPI() webpublication.WebPublication {
   229  	return server.publications
   230  }
   231  
   232  //UserAPI ( staticapi.IServer )returns DB interface for users
   233  func (server *Server) UserAPI() webuser.WebUser {
   234  	return server.users
   235  }
   236  
   237  //PurchaseAPI ( staticapi.IServer )returns DB interface for purchases
   238  func (server *Server) PurchaseAPI() webpurchase.WebPurchase {
   239  	return server.purchases
   240  }
   241  
   242  //DashboardAPI ( staticapi.IServer )returns DB interface for dashboard
   243  func (server *Server) DashboardAPI() webdashboard.WebDashboard {
   244  	return server.dashboard
   245  }
   246  
   247  //LicenseAPI ( staticapi.IServer )returns DB interface for license
   248  func (server *Server) LicenseAPI() weblicense.WebLicense {
   249  	return server.license
   250  }
   251  
   252  // mux handle functions
   253  func (server *Server) handleFunc(router *mux.Router, route string, fn HandlerFunc) *mux.Route {
   254  	return router.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
   255  		fn(w, r, server)
   256  	})
   257  }
   258  
   259  func (server *Server) handlePrivateFunc(router *mux.Router, route string, fn HandlerFunc, authenticator *auth.BasicAuth) *mux.Route {
   260  	return router.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
   261  		if api.CheckAuth(authenticator, w, r) {
   262  			fn(w, r, server)
   263  		}
   264  	})
   265  }