package web import ( "log" "net/http" "net/url" "os" "sort" "strconv" "strings" "time" "unicode" "list_app/controllers" "list_app/models" ) type mediaView struct { Code string Title string Released string WebImg string } type group struct { Label string Items []mediaView } type pageData struct { ListType string Sort string Total int Groups []group Error string } func tableFor(listType string) (models.Table, bool) { switch listType { case "movies": return models.Movies, true case "series": return models.Series, true case "games": return models.Games, true } return "", false } // resolveWebImg is decided at render time rather than falling back via // client-side JS: if the poster hasn't been downloaded yet (or the file's // missing), the fallback image is used directly in the rendered `src`. func resolveWebImg(m models.Media) string { if m.WebImg == "" { return "/no_poster.jpg" } if _, err := os.Stat(controllers.PosterDiskPath(m.WebImg)); err != nil { return "/no_poster.jpg" } return m.WebImg } func toView(m models.Media) mediaView { return mediaView{ Code: m.Code, Title: m.Title, Released: m.Released, WebImg: resolveWebImg(m), } } func toViews(media []models.Media) []mediaView { views := make([]mediaView, len(media)) for i, m := range media { views[i] = toView(m) } return views } func parseReleased(released string) time.Time { t, err := time.Parse("2 Jan 2006", released) if err != nil { return time.Time{} } return t } func groupLetter(title string) string { if title == "" { return "#" } r := []rune(strings.ToUpper(title))[0] if unicode.IsDigit(r) { return "#" } return string(r) } func buildGroups(sortType string, media []models.Media) []group { switch sortType { case "year": sort.SliceStable(media, func(i, j int) bool { return parseReleased(media[i].Released).Before(parseReleased(media[j].Released)) }) byYear := map[string][]models.Media{} for _, m := range media { key := strconv.Itoa(parseReleased(m.Released).Year()) byYear[key] = append(byYear[key], m) } keys := make([]string, 0, len(byYear)) for k := range byYear { keys = append(keys, k) } sort.Sort(sort.Reverse(sort.StringSlice(keys))) groups := make([]group, 0, len(keys)) for _, k := range keys { groups = append(groups, group{Label: k, Items: toViews(byYear[k])}) } return groups case "id": sort.SliceStable(media, func(i, j int) bool { return media[i].ID > media[j].ID }) return []group{{Label: "added", Items: toViews(media)}} default: // title sort.SliceStable(media, func(i, j int) bool { return strings.ToLower(media[i].Title) < strings.ToLower(media[j].Title) }) byLetter := map[string][]models.Media{} for _, m := range media { key := groupLetter(m.Title) byLetter[key] = append(byLetter[key], m) } keys := make([]string, 0, len(byLetter)) for k := range byLetter { keys = append(keys, k) } sort.Strings(keys) groups := make([]group, 0, len(keys)) for _, k := range keys { groups = append(groups, group{Label: k, Items: toViews(byLetter[k])}) } return groups } } func Index(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() listType := q.Get("listType") table, ok := tableFor(listType) if !ok { listType = "movies" table = models.Movies } sortType := q.Get("sortType") if sortType != "title" && sortType != "year" && sortType != "id" { sortType = "title" } media := models.Find(table) data := pageData{ ListType: listType, Sort: sortType, Total: len(media), Groups: buildGroups(sortType, media), Error: q.Get("error"), } w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := pageTemplate.Execute(w, data); err != nil { log.Println(err) } } func redirectToList(w http.ResponseWriter, r *http.Request, listType, sortType, errMsg string) { vals := url.Values{} vals.Set("listType", listType) vals.Set("sortType", sortType) if errMsg != "" { vals.Set("error", errMsg) } http.Redirect(w, r, "/?"+vals.Encode(), http.StatusSeeOther) } // Add handles the plain HTML form submission for adding new media: no JS, // so it always ends in a redirect back to the list, with an `error` query // param set for the page to render as a flash message on failure. func Add(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { http.Error(w, "bad form", http.StatusBadRequest) return } pass := r.FormValue("pass") code := r.FormValue("code") listType := r.FormValue("listType") sortType := r.FormValue("sortType") if pass == "" || code == "" { redirectToList(w, r, listType, sortType, "") return } if !models.CheckPassword(pass) { redirectToList(w, r, listType, sortType, "Incorrect password") return } if _, err := controllers.Create(code); err != nil { redirectToList(w, r, listType, sortType, err.Error()) return } redirectToList(w, r, listType, sortType, "") }