From 0e6e1c88dc209ca5f1f49d20bf29b180de3d8ea1 Mon Sep 17 00:00:00 2001 From: Guntram Date: Fri, 4 Oct 2024 22:31:28 +0200 Subject: [PATCH] Determ return date (#12) #11 Determine return date for not available movies --- domain/game.go | 6 - domain/media.go | 23 +++- domain/movie.go | 7 -- go.mod | 1 + go.sum | 2 + library-le/client.go | 6 + library-le/request.go | 54 +++++++-- library-le/request_test.go | 24 +++- library-le/search.go | 147 ++++++++++++++++++++++--- library-le/search_test.go | 64 ++++++++++- main.go | 2 +- readme.md | 2 +- web/server.go | 56 ++++++++-- web/server_test.go | 6 + web/static/movies.html | 1 - web/templates/item-list-by-branch.html | 13 ++- 16 files changed, 351 insertions(+), 63 deletions(-) delete mode 100644 domain/game.go delete mode 100644 domain/movie.go diff --git a/domain/game.go b/domain/game.go deleted file mode 100644 index d6dff24..0000000 --- a/domain/game.go +++ /dev/null @@ -1,6 +0,0 @@ -package domain - -type Game struct { - Title string `json:"title"` - Branch string `json:"branch"` -} diff --git a/domain/media.go b/domain/media.go index 01ec619..c8b65c6 100644 --- a/domain/media.go +++ b/domain/media.go @@ -1,7 +1,26 @@ package domain -type Media struct { +const MOVIE string = "movie" +const GAME string = "game" + +type Movie struct { + Title string `json:"title"` + Branch string `json:"branch"` + IsAvailable string `json:"isAvailable"` +} + +//Platform als DVD/Bluray verwenden? -> Gleich zu behandeln, ggf vorteile bei geneuer Suche + +type Game struct { Title string `json:"title"` Branch string `json:"branch"` - IsAvailable bool `json:"isAvailable"` + Platform string `json:"platform"` + IsAvailable string `json:"isAvailable"` +} + +type Media struct { + Title string + Branch string + Platform string + IsAvailable bool } diff --git a/domain/movie.go b/domain/movie.go deleted file mode 100644 index 88bd33f..0000000 --- a/domain/movie.go +++ /dev/null @@ -1,7 +0,0 @@ -package domain - -type Movie struct { - Title string `json:"title"` - Branch string `json:"branch"` - IsAvailable bool `json:"isAvailable"` -} diff --git a/go.mod b/go.mod index 25e02ca..f7cc908 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22 require ( github.com/PuerkitoBio/goquery v1.9.1 + github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.9.0 ) diff --git a/go.sum b/go.sum index 124d901..5632621 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsVi github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= diff --git a/library-le/client.go b/library-le/client.go index 30c9509..189c457 100644 --- a/library-le/client.go +++ b/library-le/client.go @@ -18,6 +18,12 @@ type webOpacSession struct { userSessionId string } +func NewClientWithSession() Client { + client := Client{} + client.openSession() + return client +} + func (client *Client) openSession() error { resp, err := http.Get(LIB_BASE_URL + "/webOPACClient") if err != nil { diff --git a/library-le/request.go b/library-le/request.go index 040ffb9..915ccd5 100644 --- a/library-le/request.go +++ b/library-le/request.go @@ -20,9 +20,19 @@ func createRequest(libSession webOpacSession, path string) *http.Request { return request } -func NewMovieSearchRequest(searchString string, libSession webOpacSession) *http.Request { +func NewReturnDateRequest(title string, platform string, branchCode int, libSession webOpacSession) *http.Request { request := createRequest(libSession, "/webOPACClient/search.do") - request.URL.RawQuery = createMovieSearchQuery(*request, searchString, libSession.userSessionId) + if platform == "dvd" || platform == "bluray" { + request.URL.RawQuery = createSinglePlatformMovieSearchQuery(*request, title, platform, branchCode, libSession.userSessionId) + } else { + request.URL.RawQuery = createGameSearchQuery(*request, title, platform, branchCode, libSession.userSessionId) + } + return request +} + +func NewMovieSearchRequest(title string, branchCode int, libSession webOpacSession) *http.Request { + request := createRequest(libSession, "/webOPACClient/search.do") + request.URL.RawQuery = createMovieSearchQuery(*request, title, branchCode, libSession.userSessionId) return request } @@ -32,13 +42,13 @@ func NewGameIndexRequest(branchCode int, platform string, libSession webOpacSess return request } -func NewGameSearchRequest(title string, platform string, libSession webOpacSession) *http.Request { +func NewGameSearchRequest(title string, platform string, branchCode int, libSession webOpacSession) *http.Request { request := createRequest(libSession, "/webOPACClient/search.do") - request.URL.RawQuery = createGameSearchQuery(*request, title, platform, libSession.userSessionId) + request.URL.RawQuery = createGameSearchQuery(*request, title, platform, branchCode, libSession.userSessionId) return request } -func createGameSearchQuery(request http.Request, title string, platform string, userSessionId string) string { +func createGameSearchQuery(request http.Request, title string, platform string, branchCode int, userSessionId string) string { query := request.URL.Query() query.Add("methodToCall", "submit") query.Add("methodToCallParameter", "submitSearch") @@ -47,8 +57,8 @@ func createGameSearchQuery(request http.Request, title string, platform string, query.Add("numberOfHits", "500") query.Add("timeOut", "20") query.Add("CSId", userSessionId) - //Search for Stadtbibliothek, collect actual branch information un further requests - query.Add("selectedViewBranchlib", strconv.FormatInt(int64(0), 10)) + query.Add("selectedSearchBranchlib", strconv.FormatInt(int64(branchCode), 10)) + query.Add("selectedViewBranchlib", strconv.FormatInt(int64(branchCode), 10)) //Search for category title query.Add("searchString[0]", title) query.Add("searchCategories[0]", "331") @@ -61,7 +71,30 @@ func createGameSearchQuery(request http.Request, title string, platform string, return query.Encode() } -func createMovieSearchQuery(request http.Request, searchString string, userSessionId string) string { +func createSinglePlatformMovieSearchQuery(request http.Request, title string, platform string, branchCode int, userSessionId string) string { + query := request.URL.Query() + query.Add("methodToCall", "submit") + query.Add("methodToCallParameter", "submitSearch") + query.Add("submitSearch", "Suchen") + query.Add("callingPage", "searchPreferences") + query.Add("numberOfHits", "500") + query.Add("timeOut", "20") + query.Add("CSId", userSessionId) + query.Add("selectedSearchBranchlib", strconv.FormatInt(int64(branchCode), 10)) + query.Add("selectedViewBranchlib", strconv.FormatInt(int64(branchCode), 10)) + //Search for category title + query.Add("searchString[0]", title) + query.Add("searchCategories[0]", "331") + //Search for one specific mediatype dvd or bluray + query.Add("searchString[1]", platform) + query.Add("searchCategories[1]", "800") + //Restrict search to dvd/bluray + query.Add("searchRestrictionID[2]", "3") + query.Add("searchRestrictionValue1[2]", "29") + return query.Encode() +} + +func createMovieSearchQuery(request http.Request, title string, branchCode int, userSessionId string) string { query := request.URL.Query() query.Add("methodToCall", "submit") query.Add("methodToCallParameter", "submitSearch") @@ -71,8 +104,9 @@ func createMovieSearchQuery(request http.Request, searchString string, userSessi query.Add("numberOfHits", "500") query.Add("timeOut", "20") query.Add("CSId", userSessionId) - query.Add("searchString[0]", searchString) - query.Add("selectedViewBranchlib", strconv.FormatInt(int64(0), 10)) + query.Add("searchString[0]", title) + query.Add("selectedSearchBranchlib", strconv.FormatInt(int64(branchCode), 10)) + query.Add("selectedViewBranchlib", strconv.FormatInt(int64(branchCode), 10)) //Search for category title query.Add("searchCategories[0]", "331") //Restrict search to dvd/bluray diff --git a/library-le/request_test.go b/library-le/request_test.go index fa985d0..e524a7b 100644 --- a/library-le/request_test.go +++ b/library-le/request_test.go @@ -8,18 +8,36 @@ import ( func TestMovieSearchRequestHasCookiesSet(t *testing.T) { session := webOpacSession{jSessionId: jSessionId, userSessionId: userSessionId} - request := NewMovieSearchRequest("Terminator", session) + request := NewMovieSearchRequest("Terminator", 0, session) assertSessionCookiesExists(request, t) } func TestMovieSearchRequestHasQueryParamsSet(t *testing.T) { session := webOpacSession{jSessionId: jSessionId, userSessionId: userSessionId} - request := NewMovieSearchRequest("Terminator", session) + request := NewMovieSearchRequest("Terminator", 0, session) Equal(t, "submit", request.URL.Query().Get("methodToCall")) Equal(t, "331", request.URL.Query().Get("searchCategories[0]")) Equal(t, "500", request.URL.Query().Get("numberOfHits")) Equal(t, "3", request.URL.Query().Get("searchRestrictionID[2]")) Equal(t, "29", request.URL.Query().Get("searchRestrictionValue1[2]")) Equal(t, "0", request.URL.Query().Get("selectedViewBranchlib")) - Empty(t, request.URL.Query().Get("selectedSearchBranchlib")) + Equal(t, "0", request.URL.Query().Get("selectedSearchBranchlib")) +} + +func TestMovieReturnDateRequest(t *testing.T) { + session := webOpacSession{jSessionId: jSessionId, userSessionId: userSessionId} + request := NewReturnDateRequest("Terminator", "dvd", 41, session) + //Expect results to be restricted to dvd/bluray + Equal(t, "29", request.URL.Query().Get("searchRestrictionValue1[2]")) + Equal(t, "dvd", request.URL.Query().Get("searchString[1]")) + Equal(t, "800", request.URL.Query().Get("searchCategories[1]")) +} + +func TestGameReturnDateRequest(t *testing.T) { + session := webOpacSession{jSessionId: jSessionId, userSessionId: userSessionId} + request := NewReturnDateRequest("Mario", "switch", 41, session) + //Expect results to be restricted to games + Equal(t, "27", request.URL.Query().Get("searchRestrictionValue1[2]")) + Equal(t, "switch", request.URL.Query().Get("searchString[1]")) + Equal(t, "902", request.URL.Query().Get("searchCategories[1]")) } diff --git a/library-le/search.go b/library-le/search.go index 3027420..b6eedc8 100644 --- a/library-le/search.go +++ b/library-le/search.go @@ -10,10 +10,12 @@ import ( "github.com/PuerkitoBio/goquery" "github.com/gunni1/leipzig-library-game-stock-api/domain" + "github.com/pkg/errors" ) const ( - copiesSelector string = "#tab-content > div > div:nth-child(n+2)" + copiesSelector string = "#tab-content > div > div:nth-child(n+2)" + mediaTypeSelector string = "div.results-teaser > div > div > ul > li:nth-child(4)" ) type searchResult struct { @@ -28,14 +30,19 @@ func (libClient Client) FindMovies(title string) []domain.Media { fmt.Println(sessionErr) return nil } - searchRequest := NewMovieSearchRequest(title, libClient.session) + searchRequest := NewMovieSearchRequest(title, 0, libClient.session) httpClient := http.Client{} searchResponse, err := httpClient.Do(searchRequest) if err != nil { log.Println(err) return nil } - resultTitles := extractTitles(searchResponse.Body) + doc, docErr := goquery.NewDocumentFromReader(searchResponse.Body) + if docErr != nil { + log.Println("Could not create document from response.") + return nil + } + resultTitles := extractTitles(doc) movies := make([]domain.Media, 0) //TODO: Parallel Ergbnislinks folgen und Details sammeln @@ -52,14 +59,19 @@ func (libClient Client) FindGames(title string, platform string) []domain.Media fmt.Println(sessionErr) return nil } - searchRequest := NewGameSearchRequest(title, platform, libClient.session) + searchRequest := NewGameSearchRequest(title, platform, 0, libClient.session) httpClient := http.Client{} searchResponse, err := httpClient.Do(searchRequest) if err != nil { log.Println(err) return nil } - resultTitles := extractTitles(searchResponse.Body) + doc, docErr := goquery.NewDocumentFromReader(searchResponse.Body) + if docErr != nil { + log.Println("Could not create document from response.") + return nil + } + resultTitles := extractTitles(doc) games := make([]domain.Media, 0) for _, resultTitle := range resultTitles { games = append(games, resultTitle.loadMediaCopies(libClient.session)...) @@ -67,6 +79,34 @@ func (libClient Client) FindGames(title string, platform string) []domain.Media return games } +func (libClient Client) RetrieveReturnDate(branchCode int, platform string, title string) (string, error) { + request := NewReturnDateRequest(title, platform, branchCode, libClient.session) + httpClient := http.Client{} + searchResponse, err := httpClient.Do(request) + if err != nil { + log.Printf("Error during search: %s", err.Error()) + return "-", err + } + doc, docErr := goquery.NewDocumentFromReader(searchResponse.Body) + if docErr != nil { + log.Println("Could not create document from response.") + return "", docErr + } + + if isSingleResultPage(doc) { + return findReturnDateInCopiesPage(doc) + } else { + resultTitles := extractTitles(doc) + exactMatchTitles := filterExactTitle(title, resultTitles) + return loadMediaReturnDate(exactMatchTitles, libClient.session) + } +} + +func isSingleResultPage(doc *goquery.Document) bool { + pageTitle := doc.Find("title").Text() + return strings.TrimSpace(pageTitle) == "Einzeltreffer" +} + // Load all existing copys of a result title over all library branches func (result searchResult) loadMediaCopies(libSession webOpacSession) []domain.Media { request := createRequest(libSession, result.resultUrl) @@ -74,20 +114,82 @@ func (result searchResult) loadMediaCopies(libSession webOpacSession) []domain.M httpClient := http.Client{} mediaResponse, err := httpClient.Do(request) if err != nil { - log.Println("error during search") + log.Printf("Error during search: %s", err.Error()) return nil } return parseMediaCopiesPage(result.title, mediaResponse.Body) } -// Go through the search overview page and create a result object for each title found. -// The result contain details of each copie availabile of the media. -func extractTitles(searchResponse io.Reader) []searchResult { - doc, docErr := goquery.NewDocumentFromReader(searchResponse) +// load the return date for a searched title. Return the date of the first copy found. +func (result searchResult) loadReturnDate(libSession webOpacSession) (string, error) { + request := createRequest(libSession, result.resultUrl) + httpClient := http.Client{} + mediaResponse, err := httpClient.Do(request) + if err != nil { + log.Printf("Error during search: %s", err.Error()) + return "", nil + } + doc, docErr := goquery.NewDocumentFromReader(mediaResponse.Body) if docErr != nil { log.Println("Could not create document from response.") - return nil + return "", docErr + } + return findReturnDateInCopiesPage(doc) +} + +func loadMediaReturnDate(titles []searchResult, libSession webOpacSession) (string, error) { + //do a request for every searchresult + //TODO: find earliest date + for _, title := range titles { + returnDate, err := title.loadReturnDate(libSession) + if err == nil { + return returnDate, nil + } + log.Printf("No return date found for title %s ", title.title) + } + return "", errors.New("No return date found") +} + +// find a return date for a copy or return an error instead. +func findReturnDateInCopiesPage(doc *goquery.Document) (string, error) { + returnDate := "" + doc.Find(copiesSelector).Each(func(i int, copy *goquery.Selection) { + rentalStateLink := copy.Find("div:nth-child(5) > div > a") + dateStr, findErr := extractDate(rentalStateLink.Text()) + if findErr == nil { + returnDate = dateStr + } + }) + if returnDate != "" { + return returnDate, nil + } else { + return "", errors.New("found no copy with a return date") + } +} + +// find a date string inside a string. Format DD.MM.YYYY +func extractDate(text string) (string, error) { + dateForm := regexp.MustCompile(`\d{2}\.\d{2}\.\d{4}`) + date := dateForm.FindString(text) + if date == "" { + return "", fmt.Errorf("no date found in: %s", text) } + return date, nil +} + +func filterExactTitle(title string, results []searchResult) []searchResult { + filtered := make([]searchResult, 0) + for _, result := range results { + if result.title == title { + filtered = append(filtered, result) + } + } + return filtered +} + +// Go through the search overview page and create a result object for each title found. +// The result contain details of each copie availabile of the media. +func extractTitles(doc *goquery.Document) []searchResult { titles := make([]searchResult, 0) doc.Find(resultItemSelector).Each(func(i int, resultItem *goquery.Selection) { title := clearTitle(resultItem.Find(titleSelector).Text()) @@ -106,16 +208,33 @@ func parseMediaCopiesPage(title string, page io.Reader) []domain.Media { return nil } movies := make([]domain.Media, 0) + platformIndicator := doc.Find(mediaTypeSelector).Text() + platform := determinePlatform(platformIndicator) doc.Find(copiesSelector).Each(func(i int, copy *goquery.Selection) { branch := copy.Find("div.col-12.col-md-4.my-md-2 > b").Text() status := isMediaAvailable(copy) - movies = append(movies, domain.Media{Title: title, Branch: branch, IsAvailable: status}) + movies = append(movies, domain.Media{Title: title, Branch: removeBranchSuffix(branch), Platform: platform, IsAvailable: status}) }) - return movies } +// Look for DVD or Blu-Ray in a String to decide a movie platform +func determinePlatform(platformIndicator string) string { + platform := strings.ToLower(platformIndicator) + if strings.Contains(platform, "dvd") { + return "dvd" + } else if strings.Contains(platform, "blu-ray") { + return "bluray" + } + return "" +} + +// Remove location detail suffix from branch name +func removeBranchSuffix(branchName string) string { + return strings.TrimSpace(strings.Split(branchName, "/")[0]) +} + // Remove additional media information from titles in square brackets func clearTitle(title string) string { brackets := regexp.MustCompile(`\[.*\]`) @@ -129,5 +248,5 @@ func isMediaAvailable(copy *goquery.Selection) bool { return false } statusText := copy.Find("div:nth-child(5)").Text() - return strings.Contains(statusText, "ausleihbar") + return strings.Contains(statusText, "ausleihbar") || strings.Contains(statusText, "frei") } diff --git a/library-le/search_test.go b/library-le/search_test.go index 7333f84..402b3ce 100644 --- a/library-le/search_test.go +++ b/library-le/search_test.go @@ -1,8 +1,11 @@ package libraryle import ( + "io" + "strings" "testing" + "github.com/PuerkitoBio/goquery" "github.com/gunni1/leipzig-library-game-stock-api/domain" . "github.com/stretchr/testify/assert" ) @@ -12,10 +15,10 @@ func TestParseGameCopiesResult(t *testing.T) { games := parseMediaCopiesPage("Monster Hunter Rise", testResponse) Equal(t, 4, len(games)) - mediaEqualTo(t, games[0], "Monster Hunter Rise", "Stadtbibliothek / Jugendbereich - 2.OG", false) - mediaEqualTo(t, games[1], "Monster Hunter Rise", "Stadtbibliothek / Jugendbereich - 2.OG", false) - mediaEqualTo(t, games[2], "Monster Hunter Rise", "Bibliothek Südvorstadt / Erwachsenenbibliothek - EG", true) - mediaEqualTo(t, games[3], "Monster Hunter Rise", "Bibliothek Gohlis / Kinderbibliothek", false) + mediaEqualTo(t, games[0], "Monster Hunter Rise", "Stadtbibliothek", false) + mediaEqualTo(t, games[1], "Monster Hunter Rise", "Stadtbibliothek", false) + mediaEqualTo(t, games[2], "Monster Hunter Rise", "Bibliothek Südvorstadt", true) + mediaEqualTo(t, games[3], "Monster Hunter Rise", "Bibliothek Gohlis", false) } func mediaEqualTo(t *testing.T, media domain.Media, exptTitle string, exptBranch string, exptAvalia bool) { @@ -40,7 +43,7 @@ func TestParseMovieCopiesResult(t *testing.T) { func TestParseSearchResultMovies(t *testing.T) { testResponse := loadTestData("testdata/movie_search_result.html") - results := extractTitles(testResponse) + results := extractTitles(asDoc(testResponse)) Equal(t, 3, len(results)) Equal(t, "Der Clou", results[0].title) @@ -56,7 +59,7 @@ func TestParseSearchResultMovies(t *testing.T) { func TestParseSearchResultGames(t *testing.T) { testResponse := loadTestData("testdata/game_search_result.html") - results := extractTitles(testResponse) + results := extractTitles(asDoc(testResponse)) Equal(t, 3, len(results)) Equal(t, "Monster hunter generations ultimate", results[0].title) @@ -73,3 +76,52 @@ func TestClearTitle(t *testing.T) { Equal(t, "Terminator", clearTitle("Terminator [Bildtonträger]")) Equal(t, "Mad Max - Fury Road", clearTitle("Mad Max - Fury Road [blu-ray]")) } + +func TestRemoveBranchSuffix(t *testing.T) { + Equal(t, "Bibliothek Gohlis", removeBranchSuffix("Bibliothek Gohlis / Erwachsenenbibliothek")) + Equal(t, "Bibliothek Grünau-Nord", removeBranchSuffix("Bibliothek Grünau-Nord / Erwachsenenbibliothek")) + Equal(t, "Fahrbibliothek", removeBranchSuffix("Fahrbibliothek")) + Equal(t, "", removeBranchSuffix("")) +} + +func TestDetermPlatform(t *testing.T) { + Equal(t, "dvd", determinePlatform("Umfang:\n 1 DVD-Video (131 Min.)")) + Equal(t, "bluray", determinePlatform("Umfang:\n 1 Blu-ray Disc (138 min)")) + Equal(t, "", determinePlatform("nix")) +} + +func TestFilterSearchResult(t *testing.T) { + search := []searchResult{ + {title: "Terminator"}, + {title: "Terminator 2"}, + } + filtered := filterExactTitle("Terminator", search) + Equal(t, 1, len(filtered)) + Equal(t, "Terminator", filtered[0].title) +} + +func TestExtractDate(t *testing.T) { + date, emptyErr := extractDate("Today is the 20.08.2024.") + Equal(t, "20.08.2024", date) + Nil(t, emptyErr) + + _, err := extractDate("Whops, this date has a formatting issue: 11.11,2011") + NotNil(t, err) +} + +func TestIsSinglePageResultTRUE(t *testing.T) { + data := strings.NewReader(" \n Einzeltreffer \n ") + result := isSingleResultPage(asDoc(data)) + True(t, result) +} + +func TestIsSinglePageResultFALSE(t *testing.T) { + data := strings.NewReader(" Trefferliste ") + result := isSingleResultPage(asDoc(data)) + False(t, result) +} + +func asDoc(reader io.Reader) *goquery.Document { + doc, _ := goquery.NewDocumentFromReader(reader) + return doc +} diff --git a/main.go b/main.go index 697d430..c056f77 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,7 @@ import ( func main() { port := flag.Int("port", 8080, "Webserver Port") flag.Parse() - fmt.Printf("listening on port: %d", *port) + fmt.Printf("listening on port: %d \n", *port) mux := web.InitMux() log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), mux)) } diff --git a/readme.md b/readme.md index 7f68899..b0085e8 100644 --- a/readme.md +++ b/readme.md @@ -4,7 +4,7 @@ Die Stadt- und Stadtteilbibliotheken in Leipzig haben zahlreiche aktuelle Videospiele für Switch, PS5, XBox usw. im Katalog. Leider ist der WebOPAC-Katalog recht sperrig zu benutzen und es ist mühsam herauszufinden, welche Spiele in welcher Bibliothek derzeit ausleihbar sind. Spiele sind idR einer bestimmten Zweigstelle zugeordnet, werden vom OPAC aber auch über andere Zweigstellen als "woanders verfügbar" angezeigt. Darüber hinaus, wechselt das Angebot regelmäßig. ## Filmsuche -Die Stadt- und Stadtteilbibliotheken haben eine Vielzahl an Filmen und Serien +Die Stadt- und Stadtteilbibliotheken haben eine Vielzahl an Filmen und Serien .... ## Suche nach Videospielen diff --git a/web/server.go b/web/server.go index b64ddf5..9d84f06 100644 --- a/web/server.go +++ b/web/server.go @@ -6,6 +6,8 @@ import ( "io/fs" "log" "net/http" + "net/url" + "strconv" "strings" "text/template" @@ -19,6 +21,9 @@ var htmlTemplates embed.FS //go:embed static/* var staticHtml embed.FS +const MOVIE string = "movie" +const GAME string = "game" + // Create Mux and setup routes func InitMux() *http.ServeMux { mux := http.NewServeMux() @@ -28,6 +33,7 @@ func InitMux() *http.ServeMux { mux.HandleFunc("/games-index/", gameIndexHandler) mux.HandleFunc("/movies-search/", movieSearchHandler) mux.HandleFunc("/games-search/", gameSearchHandler) + mux.HandleFunc("GET /return-date/{branchCode}/{platform}/{title}", returnDateHandler) return mux } @@ -36,6 +42,11 @@ type MediaByBranch struct { Media []domain.Media } +type MediaTemplateData struct { + MediaType string + Branches []MediaByBranch +} + func gameSearchHandler(respWriter http.ResponseWriter, request *http.Request) { title := strings.ToLower(request.PostFormValue("title")) platform := strings.ToLower(request.PostFormValue("platform")) @@ -46,7 +57,7 @@ func gameSearchHandler(respWriter http.ResponseWriter, request *http.Request) { if !showNotAvailable { games = filterAvailable(games) } - renderMediaResults(games, respWriter) + renderMediaResults(games, domain.GAME, respWriter) } func movieSearchHandler(respWriter http.ResponseWriter, request *http.Request) { @@ -58,20 +69,49 @@ func movieSearchHandler(respWriter http.ResponseWriter, request *http.Request) { if !showNotAvailable { movies = filterAvailable(movies) } - renderMediaResults(movies, respWriter) + renderMediaResults(movies, domain.MOVIE, respWriter) +} + +func returnDateHandler(respWriter http.ResponseWriter, request *http.Request) { + branchCode, _ := strconv.Atoi(request.PathValue("branchCode")) + platform := request.PathValue("platform") + title, _ := url.QueryUnescape(request.PathValue("title")) + client := libClient.NewClientWithSession() + returnDate, err := client.RetrieveReturnDate(branchCode, platform, title) + if err != nil { + fmt.Fprint(respWriter, "unbekannt") + return + } + fmt.Fprint(respWriter, returnDate) } -func renderMediaResults(media []domain.Media, respWriter http.ResponseWriter) { +func renderMediaResults(media []domain.Media, mediaType string, respWriter http.ResponseWriter) { if len(media) == 0 { fmt.Fprint(respWriter, "

Es wurden keine Titel gefunden.

") return } byBranch := arrangeByBranch(media) - data := map[string][]MediaByBranch{ - "Branches": byBranch, + data := MediaTemplateData{ + Branches: byBranch, + MediaType: mediaType, } - templ := template.Must(template.ParseFS(htmlTemplates, "templates/item-list-by-branch.html")) - templ.Execute(respWriter, data) + templ, _ := template.New("item-list-by-branch.html").Funcs(template.FuncMap{ + "encodeBranch": encodeBranch, + }).ParseFS(htmlTemplates, "templates/item-list-by-branch.html") + err := templ.Execute(respWriter, data) + log.Println(err) +} + +func encodeBranch(branchName string) int { + tokens := strings.Split(branchName, " ") + var branch string + if len(tokens) > 1 { + branch = tokens[1] + } else { + branch = tokens[0] + } + code, _ := libClient.GetBranchCode(branch) + return code } func filterAvailable(medias []domain.Media) []domain.Media { @@ -95,7 +135,6 @@ func arrangeByBranch(medias []domain.Media) []MediaByBranch { byBranch[media.Branch] = []domain.Media{media} } } - for branch, mds := range byBranch { result = append(result, MediaByBranch{Branch: branch, Media: mds}) } @@ -117,7 +156,6 @@ func gameIndexHandler(respWriter http.ResponseWriter, request *http.Request) { fmt.Fprint(respWriter, "

Es wurden keine ausleihbaren Titel gefunden.

") return } - data := map[string][]domain.Game{ "Items": games, } diff --git a/web/server_test.go b/web/server_test.go index 4871334..3fab20d 100644 --- a/web/server_test.go +++ b/web/server_test.go @@ -24,3 +24,9 @@ func TestArrangeByBranch(t *testing.T) { Equal(t, 2, len(result)) ElementsMatch(t, result, expected) } + +func TestEncodeBranchName(t *testing.T) { + Equal(t, 20, encodeBranch("Bibliothek Plagwitz")) + Equal(t, 0, encodeBranch("Stadtbibliothek")) + Equal(t, 41, encodeBranch("Bibliothek Gohlis")) +} diff --git a/web/static/movies.html b/web/static/movies.html index 8e97b47..000823f 100644 --- a/web/static/movies.html +++ b/web/static/movies.html @@ -19,7 +19,6 @@ Nicht ausleihbare anzeigen -
diff --git a/web/templates/item-list-by-branch.html b/web/templates/item-list-by-branch.html index 946a7e5..34673b9 100644 --- a/web/templates/item-list-by-branch.html +++ b/web/templates/item-list-by-branch.html @@ -1,4 +1,4 @@ -{{ range $i, $e := .Branches }} +{{ range .Branches }}

@@ -8,8 +8,15 @@

    - {{ range .Media}} -
  • {{ .Title }}
  • + {{ range .Media }} +
  • + {{ .Title }} + {{ if not .IsAvailable }} + + {{ end }} +
  • {{ end }}