package main import ( "github.com/zmb3/spotify" "html/template" "log" "net/http" "strconv" "strings" "time" ) var ( cachedTemplates = template.Must(template.ParseFiles("web/index.html", "web/round.html")) ) func (s *WebService) serveTemplate(w http.ResponseWriter, data interface{}, templateName string) { var templates = cachedTemplates if s.noCache { templates = template.Must(template.ParseFiles("web/index.html", "web/round.html")) } err := templates.ExecuteTemplate(w, templateName, data) if err != nil { log.Println("Error while rendering template", err) http.Error(w, err.Error(), http.StatusInternalServerError) } } func (s *WebService) IndexHandler(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.Error(w, "Not Found", http.StatusNotFound) return } data := struct { Username string Spotify string SpotifyExpiry string Songs []*Song }{ "", "", "", make([]*Song, 0), } session, _ := getSession(r) if session != nil { log.Println("Find all songs for user", session.username) songs, err := s.db.FindAllEntries(session.username) if err != nil { log.Println("Error while reading entries from database", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } data.Songs = songs data.Username = session.username data.Spotify = session.spotify if session.spotifyClient != nil { token, err := session.spotifyClient.Token() if err == nil { data.SpotifyExpiry = token.Expiry.Format(time.RFC822Z) } } } s.serveTemplate(w, data, "index.html") } func getTrackID(url string) string { const externalURLPrefix = "https://open.spotify.com/track/" const trackURIPrefix = "spotify:track:" if strings.HasPrefix(url, externalURLPrefix) { return strings.Split(url, externalURLPrefix)[1] } else if strings.HasPrefix(url, trackURIPrefix) { return strings.Split(url, trackURIPrefix)[1] } return url } func (s *WebService) UpdateHandler(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/update" { http.Error(w, "Forbidden", http.StatusForbidden) return } session, err := getSession(r) if session == nil { http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } err = r.ParseForm() if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } round := r.Form.Get("round") artist := r.Form.Get("artist") title := r.Form.Get("title") url := r.Form.Get("url") if artist == "" && title == "" && url != "" { var client = s.spotify.client if client == nil { client = session.spotifyClient } if client == nil { http.Error(w, "No Spotify token available", http.StatusInternalServerError) return } token, err := client.Token() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } else if token.Expiry.Before(time.Now()) { http.Error(w, "Spotify token expired", http.StatusInternalServerError) return } log.Println("Resolving Spotify URL") trackID := getTrackID(url) track, err := client.GetTrack(spotify.ID(trackID)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } log.Printf("Found Track with name %v and artists %v\n", track.Name, track.Artists) for index, trackArtist := range track.Artists { if index == 0 { artist = trackArtist.Name } else if index == 1 { artist = artist + " feat. " + trackArtist.Name } else { artist = artist + ", " + trackArtist.Name } } title = track.Name url = track.ExternalURLs["spotify"] } updated, err := s.db.UpdateEntry(session.username, round, artist, title, url) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if !updated { http.Error(w, "No round in DB", http.StatusNotFound) return } log.Printf("Updated song for round %s, artist: %s, title: %s, URL: %s", round, artist, title, url) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } func (s *WebService) LoginHandler(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } username := r.Form.Get("username") password := r.Form.Get("password") remember := r.Form.Get("remember") longer := remember == "remember-me" cookie, err := tryLogin(s.db, username, password, longer) if err != nil { log.Println("Error while trying to login", err.Error()) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } http.SetCookie(w, &cookie) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } func (s *WebService) SpotifyHandler(w http.ResponseWriter, r *http.Request) { url := s.spotify.auth.AuthURL("foobar") http.Redirect(w, r, url, http.StatusTemporaryRedirect) } func (s *WebService) CallbackHandler(w http.ResponseWriter, r *http.Request) { state := "foobar" token, err := s.spotify.auth.Token(state, r) if err != nil { http.Error(w, "Couldn't get token", http.StatusForbidden) return } client := s.spotify.auth.NewClient(token) profile, err := client.CurrentUser() if err != nil { http.Error(w, "Couldn't load profile from Spotify", http.StatusInternalServerError) return } session, err := getSession(r) if session == nil { if err != nil { log.Println("Error while trying to read current session", err) } log.Println("Trying to login with spotify ID", profile.ID) cookie, err := tryLoginWithSpotify(s.db, profile.ID) if err != nil { http.Error(w, "Couldn't login with Spotify profile", http.StatusInternalServerError) return } session, _ := getSessionById(cookie.Value) session.spotifyClient = &client log.Println("Logged in user spotify ID", profile.ID) http.SetCookie(w, &cookie) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } else { session.spotify = profile.ID session.spotifyClient = &client http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } // TODO: when? if profile.ID == "lamperi" { s.spotify.client = &client } } func (s *WebService) RoundHandler(w http.ResponseWriter, r *http.Request) { data := struct { Username string Spotify string SpotifyExpiry string Songs []*Song SubmitterPublic bool RoundInfo *RoundInfo }{ "", "", "", make([]*Song, 0), false, nil, } round := strings.TrimPrefix(r.URL.Path, "/round/") roundNum, err := strconv.Atoi(round) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } roundInfo, err := s.db.FindRoundInfo(roundNum) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if roundInfo == nil { http.Error(w, "No such panel", http.StatusNotFound) return } data.RoundInfo = roundInfo session, err := getSession(r) if session != nil { data.Username = session.username data.Spotify = session.spotify } songs, err := s.db.FindAllRoundEntries(roundNum) if err != nil { log.Println("Error while reading entries from database", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } data.Songs = songs data.SubmitterPublic = roundInfo.End.Before(time.Now()) if s.spotify.client != nil { token, err := s.spotify.client.Token() if err == nil { data.SpotifyExpiry = token.Expiry.Format(time.RFC822Z) } } s.serveTemplate(w, data, "round.html") } type WebService struct { db *DB spotify *AppSpotify noCache bool } func webStart(listenAddr string, db *DB, spotify *AppSpotify) { service := &WebService{db, spotify, true} mux := http.NewServeMux() fs := http.FileServer(http.Dir("web")) mux.Handle("/css/", fs) mux.Handle("/fonts/", fs) mux.Handle("/js/", fs) mux.Handle("/favicon.ico", fs) mux.HandleFunc("/", service.IndexHandler) mux.HandleFunc("/round/", service.RoundHandler) mux.HandleFunc("/update", service.UpdateHandler) mux.HandleFunc("/login", service.LoginHandler) mux.HandleFunc("/spotify", service.SpotifyHandler) mux.HandleFunc("/callback", service.CallbackHandler) http.ListenAndServe(listenAddr, mux) }