web.go 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. package main
  2. import (
  3. "github.com/zmb3/spotify"
  4. "html/template"
  5. "log"
  6. "net/http"
  7. "strconv"
  8. "strings"
  9. "time"
  10. )
  11. var (
  12. cachedTemplates = template.Must(template.ParseFiles("web/index.html", "web/round.html"))
  13. )
  14. func (s *WebService) serveTemplate(w http.ResponseWriter, data interface{}, templateName string) {
  15. var templates = cachedTemplates
  16. if s.noCache {
  17. templates = template.Must(template.ParseFiles("web/index.html", "web/round.html"))
  18. }
  19. err := templates.ExecuteTemplate(w, templateName, data)
  20. if err != nil {
  21. log.Println("Error while rendering template", err)
  22. http.Error(w, err.Error(), http.StatusInternalServerError)
  23. }
  24. }
  25. func (s *WebService) IndexHandler(w http.ResponseWriter, r *http.Request) {
  26. if r.URL.Path != "/" {
  27. http.Error(w, "Not Found", http.StatusNotFound)
  28. return
  29. }
  30. data := struct {
  31. Username string
  32. Spotify string
  33. SpotifyExpiry string
  34. Songs []*Song
  35. }{
  36. "",
  37. "",
  38. "",
  39. make([]*Song, 0),
  40. }
  41. session, _ := getSession(r)
  42. if session != nil {
  43. log.Println("Find all songs for user", session.username)
  44. songs, err := s.db.FindAllEntries(session.username)
  45. if err != nil {
  46. log.Println("Error while reading entries from database", err)
  47. http.Error(w, err.Error(), http.StatusInternalServerError)
  48. return
  49. }
  50. data.Songs = songs
  51. data.Username = session.username
  52. data.Spotify = session.spotify
  53. if session.spotifyClient != nil {
  54. token, err := session.spotifyClient.Token()
  55. if err == nil {
  56. data.SpotifyExpiry = token.Expiry.Format(time.RFC822Z)
  57. }
  58. }
  59. }
  60. s.serveTemplate(w, data, "index.html")
  61. }
  62. func getTrackID(url string) string {
  63. const externalURLPrefix = "https://open.spotify.com/track/"
  64. const trackURIPrefix = "spotify:track:"
  65. if strings.HasPrefix(url, externalURLPrefix) {
  66. return strings.Split(url, externalURLPrefix)[1]
  67. } else if strings.HasPrefix(url, trackURIPrefix) {
  68. return strings.Split(url, trackURIPrefix)[1]
  69. }
  70. return url
  71. }
  72. func (s *WebService) UpdateHandler(w http.ResponseWriter, r *http.Request) {
  73. if r.URL.Path != "/update" {
  74. http.Error(w, "Forbidden", http.StatusForbidden)
  75. return
  76. }
  77. session, err := getSession(r)
  78. if session == nil {
  79. http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
  80. return
  81. }
  82. err = r.ParseForm()
  83. if err != nil {
  84. http.Error(w, err.Error(), http.StatusBadRequest)
  85. return
  86. }
  87. round := r.Form.Get("round")
  88. artist := r.Form.Get("artist")
  89. title := r.Form.Get("title")
  90. url := r.Form.Get("url")
  91. if artist == "" && title == "" && url != "" {
  92. var client = s.spotify.client
  93. if client == nil {
  94. client = session.spotifyClient
  95. }
  96. if client == nil {
  97. http.Error(w, "No Spotify token available", http.StatusInternalServerError)
  98. return
  99. }
  100. token, err := client.Token()
  101. if err != nil {
  102. http.Error(w, err.Error(), http.StatusInternalServerError)
  103. return
  104. } else if token.Expiry.Before(time.Now()) {
  105. http.Error(w, "Spotify token expired", http.StatusInternalServerError)
  106. return
  107. }
  108. log.Println("Resolving Spotify URL")
  109. trackID := getTrackID(url)
  110. track, err := client.GetTrack(spotify.ID(trackID))
  111. if err != nil {
  112. http.Error(w, err.Error(), http.StatusInternalServerError)
  113. return
  114. }
  115. log.Printf("Found Track with name %v and artists %v\n", track.Name, track.Artists)
  116. for index, trackArtist := range track.Artists {
  117. if index == 0 {
  118. artist = trackArtist.Name
  119. } else if index == 1 {
  120. artist = artist + " feat. " + trackArtist.Name
  121. } else {
  122. artist = artist + ", " + trackArtist.Name
  123. }
  124. }
  125. title = track.Name
  126. url = track.ExternalURLs["spotify"]
  127. }
  128. updated, err := s.db.UpdateEntry(session.username, round, artist, title, url)
  129. if err != nil {
  130. http.Error(w, err.Error(), http.StatusInternalServerError)
  131. return
  132. }
  133. if !updated {
  134. http.Error(w, "No round in DB", http.StatusNotFound)
  135. return
  136. }
  137. log.Printf("Updated song for round %s, artist: %s, title: %s, URL: %s",
  138. round, artist, title, url)
  139. http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
  140. }
  141. func (s *WebService) LoginHandler(w http.ResponseWriter, r *http.Request) {
  142. err := r.ParseForm()
  143. if err != nil {
  144. http.Error(w, err.Error(), http.StatusBadRequest)
  145. return
  146. }
  147. username := r.Form.Get("username")
  148. password := r.Form.Get("password")
  149. remember := r.Form.Get("remember")
  150. longer := remember == "remember-me"
  151. cookie, err := tryLogin(s.db, username, password, longer)
  152. if err != nil {
  153. log.Println("Error while trying to login", err.Error())
  154. http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
  155. return
  156. }
  157. http.SetCookie(w, &cookie)
  158. http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
  159. }
  160. func (s *WebService) SpotifyHandler(w http.ResponseWriter, r *http.Request) {
  161. url := s.spotify.auth.AuthURL("foobar")
  162. http.Redirect(w, r, url, http.StatusTemporaryRedirect)
  163. }
  164. func (s *WebService) CallbackHandler(w http.ResponseWriter, r *http.Request) {
  165. state := "foobar"
  166. token, err := s.spotify.auth.Token(state, r)
  167. if err != nil {
  168. http.Error(w, "Couldn't get token", http.StatusForbidden)
  169. return
  170. }
  171. client := s.spotify.auth.NewClient(token)
  172. profile, err := client.CurrentUser()
  173. if err != nil {
  174. http.Error(w, "Couldn't load profile from Spotify", http.StatusInternalServerError)
  175. return
  176. }
  177. session, err := getSession(r)
  178. if session == nil {
  179. if err != nil {
  180. log.Println("Error while trying to read current session", err)
  181. }
  182. log.Println("Trying to login with spotify ID", profile.ID)
  183. cookie, err := tryLoginWithSpotify(s.db, profile.ID)
  184. if err != nil {
  185. http.Error(w, "Couldn't login with Spotify profile", http.StatusInternalServerError)
  186. return
  187. }
  188. session, _ := getSessionById(cookie.Value)
  189. session.spotifyClient = &client
  190. log.Println("Logged in user spotify ID", profile.ID)
  191. http.SetCookie(w, &cookie)
  192. http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
  193. return
  194. } else {
  195. session.spotify = profile.ID
  196. session.spotifyClient = &client
  197. http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
  198. return
  199. }
  200. // TODO: when?
  201. if profile.ID == "lamperi" {
  202. s.spotify.client = &client
  203. }
  204. }
  205. func (s *WebService) RoundHandler(w http.ResponseWriter, r *http.Request) {
  206. data := struct {
  207. Username string
  208. Spotify string
  209. SpotifyExpiry string
  210. Songs []*Song
  211. SubmitterPublic bool
  212. RoundInfo *RoundInfo
  213. }{
  214. "",
  215. "",
  216. "",
  217. make([]*Song, 0),
  218. false,
  219. nil,
  220. }
  221. round := strings.TrimPrefix(r.URL.Path, "/round/")
  222. roundNum, err := strconv.Atoi(round)
  223. if err != nil {
  224. http.Error(w, err.Error(), http.StatusBadRequest)
  225. return
  226. }
  227. roundInfo, err := s.db.FindRoundInfo(roundNum)
  228. if err != nil {
  229. http.Error(w, err.Error(), http.StatusInternalServerError)
  230. return
  231. }
  232. if roundInfo == nil {
  233. http.Error(w, "No such panel", http.StatusNotFound)
  234. return
  235. }
  236. data.RoundInfo = roundInfo
  237. session, err := getSession(r)
  238. if session != nil {
  239. data.Username = session.username
  240. data.Spotify = session.spotify
  241. }
  242. songs, err := s.db.FindAllRoundEntries(roundNum)
  243. if err != nil {
  244. log.Println("Error while reading entries from database", err)
  245. http.Error(w, err.Error(), http.StatusInternalServerError)
  246. return
  247. }
  248. data.Songs = songs
  249. data.SubmitterPublic = roundInfo.End.Before(time.Now())
  250. if s.spotify.client != nil {
  251. token, err := s.spotify.client.Token()
  252. if err == nil {
  253. data.SpotifyExpiry = token.Expiry.Format(time.RFC822Z)
  254. }
  255. }
  256. s.serveTemplate(w, data, "round.html")
  257. }
  258. type WebService struct {
  259. db *DB
  260. spotify *AppSpotify
  261. noCache bool
  262. }
  263. func webStart(listenAddr string, db *DB, spotify *AppSpotify) {
  264. service := &WebService{db, spotify, true}
  265. mux := http.NewServeMux()
  266. fs := http.FileServer(http.Dir("web"))
  267. mux.Handle("/css/", fs)
  268. mux.Handle("/fonts/", fs)
  269. mux.Handle("/js/", fs)
  270. mux.Handle("/favicon.ico", fs)
  271. mux.HandleFunc("/", service.IndexHandler)
  272. mux.HandleFunc("/round/", service.RoundHandler)
  273. mux.HandleFunc("/update", service.UpdateHandler)
  274. mux.HandleFunc("/login", service.LoginHandler)
  275. mux.HandleFunc("/spotify", service.SpotifyHandler)
  276. mux.HandleFunc("/callback", service.CallbackHandler)
  277. http.ListenAndServe(listenAddr, mux)
  278. }