Browse Source

Add login with Spotify, current playlist support

Toni Fadjukoff 5 years ago
parent
commit
7d3a7b5928
6 changed files with 359 additions and 71 deletions
  1. 42 7
      auth.go
  2. 60 25
      bot.go
  3. 71 8
      db.go
  4. 115 28
      web.go
  5. 5 3
      web/index.html
  6. 66 0
      web/round.html

+ 42 - 7
auth.go View File

7
 	"crypto/sha1"
7
 	"crypto/sha1"
8
 	"encoding/base64"
8
 	"encoding/base64"
9
 	"errors"
9
 	"errors"
10
+	"github.com/zmb3/spotify"
10
 	"golang.org/x/crypto/pbkdf2"
11
 	"golang.org/x/crypto/pbkdf2"
11
 	"log"
12
 	"log"
12
 	"net/http"
13
 	"net/http"
15
 )
16
 )
16
 
17
 
17
 type SessionData struct {
18
 type SessionData struct {
18
-	sid      string
19
-	username string
20
-	spotify  string
21
-	priority time.Time
22
-	index    int
19
+	sid           string
20
+	username      string
21
+	spotify       string
22
+	priority      time.Time
23
+	index         int
24
+	spotifyClient *spotify.Client
23
 }
25
 }
24
 
26
 
25
 type SessionQueue []*SessionData
27
 type SessionQueue []*SessionData
119
 	return pbkdf2.Key(password, salt, 4096, 32, sha1.New)
121
 	return pbkdf2.Key(password, salt, 4096, 32, sha1.New)
120
 }
122
 }
121
 
123
 
124
+func spotifyOk(db *DB, spotifyID string) (string, error) {
125
+	username, err := db.FindUserBySpotifyID(spotifyID)
126
+	if err != nil {
127
+		if err.Error() == "sql: no rows in result set" {
128
+			return "", nil
129
+		} else {
130
+			return "", err
131
+		}
132
+	}
133
+	return username, nil
134
+}
135
+
122
 func userOk(db *DB, username, password string) (bool, error) {
136
 func userOk(db *DB, username, password string) (bool, error) {
123
 	hash, err := db.FindHashForUser(username)
137
 	hash, err := db.FindHashForUser(username)
124
 	if err != nil {
138
 	if err != nil {
136
 	return ok, nil
150
 	return ok, nil
137
 }
151
 }
138
 
152
 
153
+func tryLoginWithSpotify(db *DB, spotifyID string) (http.Cookie, error) {
154
+	username, err := spotifyOk(db, spotifyID)
155
+	if username == "" {
156
+		if err != nil {
157
+			return http.Cookie{}, err
158
+		}
159
+		return http.Cookie{},
160
+			errors.New("This spotify account is not connected to any account")
161
+	}
162
+
163
+	return addSessionAndReturnMatchingCookie(username, spotifyID, true)
164
+}
165
+
139
 func tryLogin(db *DB, username, password string, longerTime bool) (http.Cookie, error) {
166
 func tryLogin(db *DB, username, password string, longerTime bool) (http.Cookie, error) {
140
 	if exists, err := userOk(db, username, password); !exists {
167
 	if exists, err := userOk(db, username, password); !exists {
141
 		if err != nil {
168
 		if err != nil {
145
 			errors.New("The username or password you entered isn't correct.")
172
 			errors.New("The username or password you entered isn't correct.")
146
 	}
173
 	}
147
 
174
 
175
+	return addSessionAndReturnMatchingCookie(username, "", longerTime)
176
+}
177
+
178
+func addSessionAndReturnMatchingCookie(username, spotifyID string, longerTime bool) (http.Cookie, error) {
148
 	sid, err := randString(32)
179
 	sid, err := randString(32)
149
 	if err != nil {
180
 	if err != nil {
150
 		return http.Cookie{}, err
181
 		return http.Cookie{}, err
163
 	}
194
 	}
164
 
195
 
165
 	expiration := time.Now().Add(duration)
196
 	expiration := time.Now().Add(duration)
166
-	addSession(&SessionData{sid, username, "", expiration, 0})
197
+	addSession(&SessionData{sid, username, spotifyID, expiration, 0, nil})
167
 
198
 
168
 	return loginCookie, nil
199
 	return loginCookie, nil
200
+
169
 }
201
 }
170
 
202
 
171
 func getSession(req *http.Request) (*SessionData, error) {
203
 func getSession(req *http.Request) (*SessionData, error) {
173
 	if err != nil {
205
 	if err != nil {
174
 		return nil, err
206
 		return nil, err
175
 	}
207
 	}
208
+	return getSessionById(cookie.Value)
209
+}
176
 
210
 
177
-	session, exists := sessions[cookie.Value]
211
+func getSessionById(sid string) (*SessionData, error) {
212
+	session, exists := sessions[sid]
178
 	if !exists {
213
 	if !exists {
179
 		return nil, errors.New("Session expired from server")
214
 		return nil, errors.New("Session expired from server")
180
 	}
215
 	}

+ 60 - 25
bot.go View File

308
 		weekStr := numberReg.FindString(section.title)
308
 		weekStr := numberReg.FindString(section.title)
309
 		if weekStr != "" {
309
 		if weekStr != "" {
310
 			weekNumber, _ := strconv.Atoi(weekStr)
310
 			weekNumber, _ := strconv.Atoi(weekStr)
311
-			if weekNumber < currentWeek-1 {
311
+			if weekNumber < currentWeek-4 {
312
 				continue
312
 				continue
313
 			}
313
 			}
314
 			if weekNumber > currentWeek {
314
 			if weekNumber > currentWeek {
340
 						log.Println("Failed to find tracks from DB", err)
340
 						log.Println("Failed to find tracks from DB", err)
341
 					}
341
 					}
342
 					log.Println("Checking if playlist needs updating", currentTracks, tracks, err)
342
 					log.Println("Checking if playlist needs updating", currentTracks, tracks, err)
343
-					if len(tracks) > 0 && (err != nil || !tracksEqual(tracks, currentTracks)) {
344
-
345
-						if playlistID == "" {
346
-							log.Println("Creating new playlist")
347
-							info, err := app.spotify.client.CreatePlaylistForUser(app.credentials.SpotifyUser, title+" "+section.title, true)
348
-							if err != nil {
349
-								log.Println("Error creating playlist to Spotify")
350
-								return err
351
-							}
352
-							playlistID = info.ID
353
-							changedWikiText = appendPlaylist(changedWikiText, info)
354
-							message = message + fmt.Sprintf("Added link to Spotify playlist for week %d.", weekNumber)
355
-						}
356
-						log.Printf("Updating playlist %s for %s with tracks %s\n", playlistID, app.credentials.SpotifyUser, tracks)
357
-						err := app.spotify.client.ReplacePlaylistTracks(app.credentials.SpotifyUser, playlistID, tracks...)
343
+					if app.ShouldUpdate(tracks, currentTracks, err != nil) {
344
+						playlistID, changedWikiText, message, err = app.CreatePlaylist(title+" "+section.title, weekNumber, playlistID, changedWikiText, message)
358
 						if err != nil {
345
 						if err != nil {
359
-							log.Println("Error updating playlist to Spotify")
360
 							return err
346
 							return err
361
 						}
347
 						}
362
-
363
-						stringTracks := make([]string, len(tracks))
364
-						for i, t := range tracks {
365
-							stringTracks[i] = string(t)
366
-						}
367
-
368
-						_, err = app.db.UpdatePlaylistBySection(section.title, stringTracks)
348
+						err = app.UpdatePlaylist(section.title, playlistID, tracks, currentTracks)
369
 						if err != nil {
349
 						if err != nil {
370
-							log.Println("Error updating playlist to DB")
371
 							return err
350
 							return err
372
 						}
351
 						}
373
 					}
352
 					}
353
+					if weekNumber == currentWeek {
354
+						// This playlist is same for every week
355
+						// So people don't have to find the new ones from wiki
356
+						playlistID = "4piDy2DQ35Y43yUaInyWfN"
357
+						currentTracks, err = app.db.FindPlaylistBySection("current week")
358
+						if err != nil {
359
+							log.Println("Failed to find current week tracks from DB", err)
360
+						}
361
+						log.Println("Checking if playlist needs updating", currentTracks, tracks, err)
362
+						if app.ShouldUpdate(tracks, currentTracks, err != nil) {
363
+							err = app.UpdatePlaylist("current week", playlistID, tracks, currentTracks)
364
+							if err != nil {
365
+								return err
366
+							}
367
+						}
368
+					}
374
 				}
369
 				}
375
 			}
370
 			}
376
 
371
 
388
 	return nil
383
 	return nil
389
 }
384
 }
390
 
385
 
386
+func (app *App) ShouldUpdate(tracks []spotify.ID, currentTracks []string, isNew bool) bool {
387
+	return len(tracks) > 0 && (isNew || !tracksEqual(tracks, currentTracks))
388
+}
389
+
390
+func (app *App) CreatePlaylist(title string, weekNumber int, playlistID spotify.ID, changedWikiText, message string) (spotify.ID, string, string, error) {
391
+	if playlistID == "" {
392
+		log.Println("Creating new playlist")
393
+		info, err := app.spotify.client.CreatePlaylistForUser(app.credentials.SpotifyUser, title, true)
394
+		if err != nil {
395
+			log.Println("Error creating playlist to Spotify")
396
+			return "", "", "", err
397
+		}
398
+		playlistID = info.ID
399
+		changedWikiText = appendPlaylist(changedWikiText, info)
400
+		message = message + fmt.Sprintf("Added link to Spotify playlist for week %d.", weekNumber)
401
+	}
402
+	return playlistID, changedWikiText, message, nil
403
+}
404
+
405
+func (app *App) UpdatePlaylist(section string, playlistID spotify.ID, tracks []spotify.ID, currentTracks []string) error {
406
+	log.Printf("Updating playlist %s for %s with tracks %s\n", playlistID, app.credentials.SpotifyUser, tracks)
407
+	err := app.spotify.client.ReplacePlaylistTracks(app.credentials.SpotifyUser, playlistID, tracks...)
408
+	if err != nil {
409
+		log.Println("Error updating playlist to Spotify")
410
+		return err
411
+	}
412
+
413
+	stringTracks := make([]string, len(tracks))
414
+	for i, t := range tracks {
415
+		stringTracks[i] = string(t)
416
+	}
417
+
418
+	_, err = app.db.UpdatePlaylistBySection(section, stringTracks)
419
+	if err != nil {
420
+		log.Println("Error updating playlist to DB")
421
+		return err
422
+	}
423
+	return nil
424
+}
425
+
391
 func (app *App) AutomateSectionTask() {
426
 func (app *App) AutomateSectionTask() {
392
 	panels, err := app.db.FindAllPanels()
427
 	panels, err := app.db.FindAllPanels()
393
 	if err != nil {
428
 	if err != nil {

+ 71 - 8
db.go View File

7
 	_ "github.com/lib/pq"
7
 	_ "github.com/lib/pq"
8
 	"log"
8
 	"log"
9
 	"os"
9
 	"os"
10
+	"time"
10
 )
11
 )
11
 
12
 
12
 type DB struct {
13
 type DB struct {
34
 	return &DB{database}
35
 	return &DB{database}
35
 }
36
 }
36
 
37
 
38
+func (db *DB) FindUserBySpotifyID(username string) (string, error) {
39
+	var user string
40
+	err := db.database.QueryRow("SELECT u.username FROM public.user u WHERE u.spotify_id = $1", username).Scan(&user)
41
+	return user, err
42
+}
43
+
37
 func (db *DB) FindHashForUser(username string) (string, error) {
44
 func (db *DB) FindHashForUser(username string) (string, error) {
38
 	var hash string
45
 	var hash string
39
 	err := db.database.QueryRow("SELECT u.password FROM public.user u WHERE lower(u.username) = lower($1)", username).Scan(&hash)
46
 	err := db.database.QueryRow("SELECT u.password FROM public.user u WHERE lower(u.username) = lower($1)", username).Scan(&hash)
67
 	JOIN public."user" u ON u.id = e.user_id
74
 	JOIN public."user" u ON u.id = e.user_id
68
 	JOIN public.round r ON r.id = e.round_id
75
 	JOIN public.round r ON r.id = e.round_id
69
 	JOIN public.panel p ON p.id = r.panel_id
76
 	JOIN public.panel p ON p.id = r.panel_id
70
-	WHERE r.start < current_timestamp AND e.synced = false`
77
+	WHERE r.start < current_timestamp AND e.synced = false AND p.sync_enabled = true`
71
 	rows, err := db.database.Query(query)
78
 	rows, err := db.database.Query(query)
72
 
79
 
73
 	if err != nil {
80
 	if err != nil {
101
 	artist, title, spotifyURL, article, username, section string
108
 	artist, title, spotifyURL, article, username, section string
102
 }
109
 }
103
 
110
 
111
+func (db *DB) FindRoundInfo(roundNum int) (*RoundInfo, error) {
112
+	query := `
113
+		SELECT r.section, p.name, p.article, r.start, r.end
114
+		FROM public.round r
115
+                JOIN public.panel p ON p.id = r.panel_id
116
+		WHERE r.id = $1`
117
+	rows, err := db.database.Query(query, roundNum)
118
+	if err != nil {
119
+		return nil, err
120
+	}
121
+	defer rows.Close()
122
+	var (
123
+		section, panelName, panelArticle string
124
+		start, end                       *time.Time
125
+	)
126
+	if !rows.Next() {
127
+		return nil, nil
128
+	}
129
+	err = rows.Scan(&section, &panelName, &panelArticle, &start, &end)
130
+	if err != nil {
131
+		log.Println("Error while scanning row:", err)
132
+		return nil, err
133
+	}
134
+	return &RoundInfo{section, panelName, panelArticle, start, end}, nil
135
+}
136
+
137
+type RoundInfo struct {
138
+	Section, PanelName, PanelArticle string
139
+	Start, End                       *time.Time
140
+}
141
+
142
+func (db *DB) FindAllRoundEntries(round int) ([]*Song, error) {
143
+	query := `
144
+		SELECT r.id, r.section, e.artist, e.title, e.spotify_url, e.synced, u.username
145
+		FROM public.round r
146
+                JOIN public.panel p ON p.id = r.panel_id
147
+		JOIN public.entry e ON r.id = e.round_id
148
+		JOIN public."user" u ON u.id = e.user_id
149
+		WHERE r.id = $1`
150
+	rows, err := db.database.Query(query, round)
151
+	if err != nil {
152
+		return nil, err
153
+	}
154
+	defer rows.Close()
155
+	return db.rowsToSong(rows)
156
+}
157
+
104
 func (db *DB) FindAllEntries(username string) ([]*Song, error) {
158
 func (db *DB) FindAllEntries(username string) ([]*Song, error) {
105
-	var songs []*Song
106
 	query := `
159
 	query := `
107
-		SELECT r.id, r.section, e.artist, e.title, e.spotify_url, e.synced
160
+		SELECT r.id, r.section, e.artist, e.title, e.spotify_url, e.synced, u.username
108
 		FROM public.round r
161
 		FROM public.round r
109
-		LEFT JOIN public.entry e ON r.id = e.round_id
110
-		LEFT JOIN public."user" u ON u.id = e.user_id AND lower(u.username) = lower($1)
162
+		LEFT JOIN public.entry e ON r.id = e.round_id AND e.user_id = 
163
+                  (SELECT u2.id FROM public."user" u2 WHERE lower(u2.username) = lower($1))
164
+		LEFT JOIN public."user" u ON r.id = e.round_id AND u.id = e.user_id
111
 		ORDER BY r.start ASC`
165
 		ORDER BY r.start ASC`
112
 	rows, err := db.database.Query(query, username)
166
 	rows, err := db.database.Query(query, username)
113
 	if err != nil {
167
 	if err != nil {
114
 		return nil, err
168
 		return nil, err
115
 	}
169
 	}
116
 	defer rows.Close()
170
 	defer rows.Close()
171
+	return db.rowsToSong(rows)
172
+}
173
+
174
+func (db *DB) rowsToSong(rows *sql.Rows) ([]*Song, error) {
175
+	var songs []*Song
117
 	for i := 0; rows.Next(); i++ {
176
 	for i := 0; rows.Next(); i++ {
118
 		song := &Song{}
177
 		song := &Song{}
119
 		songs = append(songs, song)
178
 		songs = append(songs, song)
120
 		var (
179
 		var (
121
-			artist, title, url *string
122
-			sync               *bool
180
+			artist, title, url, username *string
181
+			sync                         *bool
123
 		)
182
 		)
124
-		err = rows.Scan(&songs[i].RoundID, &songs[i].RoundName, &artist, &title, &url, &sync)
183
+		err := rows.Scan(&songs[i].RoundID, &songs[i].RoundName, &artist, &title, &url, &sync, &username)
125
 		if err != nil {
184
 		if err != nil {
126
 			return nil, err
185
 			return nil, err
127
 		}
186
 		}
137
 		if sync != nil {
196
 		if sync != nil {
138
 			song.Sync = *sync
197
 			song.Sync = *sync
139
 		}
198
 		}
199
+		if username != nil {
200
+			song.Submitter = *username
201
+		}
140
 	}
202
 	}
141
 	return songs, nil
203
 	return songs, nil
142
 }
204
 }
148
 	Artist    string
210
 	Artist    string
149
 	URL       string
211
 	URL       string
150
 	Sync      bool
212
 	Sync      bool
213
+	Submitter string
151
 }
214
 }
152
 
215
 
153
 func (db *DB) UpdateEntry(username, round, artist, title, url string) (bool, error) {
216
 func (db *DB) UpdateEntry(username, round, artist, title, url string) (bool, error) {

+ 115 - 28
web.go View File

5
 	"html/template"
5
 	"html/template"
6
 	"log"
6
 	"log"
7
 	"net/http"
7
 	"net/http"
8
+	"strconv"
8
 	"strings"
9
 	"strings"
9
 	"time"
10
 	"time"
10
 )
11
 )
11
 
12
 
12
 var (
13
 var (
13
-	cachedTemplates = template.Must(template.ParseFiles("web/index.html"))
14
+	cachedTemplates = template.Must(template.ParseFiles("web/index.html", "web/round.html"))
14
 )
15
 )
15
 
16
 
17
+func (s *WebService) serveTemplate(w http.ResponseWriter, data interface{}, templateName string) {
18
+	var templates = cachedTemplates
19
+	if s.noCache {
20
+		templates = template.Must(template.ParseFiles("web/index.html", "web/round.html"))
21
+	}
22
+
23
+	err := templates.ExecuteTemplate(w, templateName, data)
24
+	if err != nil {
25
+		log.Println("Error while rendering template", err)
26
+		http.Error(w, err.Error(), http.StatusInternalServerError)
27
+	}
28
+
29
+}
30
+
16
 func (s *WebService) IndexHandler(w http.ResponseWriter, r *http.Request) {
31
 func (s *WebService) IndexHandler(w http.ResponseWriter, r *http.Request) {
17
 	if r.URL.Path != "/" {
32
 	if r.URL.Path != "/" {
18
 		http.Error(w, "Not Found", http.StatusNotFound)
33
 		http.Error(w, "Not Found", http.StatusNotFound)
31
 		make([]*Song, 0),
46
 		make([]*Song, 0),
32
 	}
47
 	}
33
 
48
 
34
-	session, err := getSession(r)
49
+	session, _ := getSession(r)
35
 	if session != nil {
50
 	if session != nil {
51
+		log.Println("Find all songs for user", session.username)
36
 		songs, err := s.db.FindAllEntries(session.username)
52
 		songs, err := s.db.FindAllEntries(session.username)
37
 		if err != nil {
53
 		if err != nil {
38
 			log.Println("Error while reading entries from database", err)
54
 			log.Println("Error while reading entries from database", err)
42
 		data.Songs = songs
58
 		data.Songs = songs
43
 		data.Username = session.username
59
 		data.Username = session.username
44
 		data.Spotify = session.spotify
60
 		data.Spotify = session.spotify
45
-	}
46
 
61
 
47
-	if s.spotify.client != nil {
48
-		token, err := s.spotify.client.Token()
49
-		if err == nil {
50
-			data.SpotifyExpiry = token.Expiry.Format(time.RFC822Z)
62
+		if session.spotifyClient != nil {
63
+			token, err := session.spotifyClient.Token()
64
+			if err == nil {
65
+				data.SpotifyExpiry = token.Expiry.Format(time.RFC822Z)
66
+			}
51
 		}
67
 		}
52
 	}
68
 	}
53
-
54
-	var templates = cachedTemplates
55
-	if s.noCache {
56
-		templates = template.Must(template.ParseFiles("web/index.html"))
57
-	}
58
-
59
-	err = templates.ExecuteTemplate(w, "index.html", data)
60
-	if err != nil {
61
-		log.Println("Error while rendering template", err)
62
-		http.Error(w, err.Error(), http.StatusInternalServerError)
63
-	}
69
+	s.serveTemplate(w, data, "index.html")
64
 }
70
 }
65
 
71
 
66
 func getTrackID(url string) string {
72
 func getTrackID(url string) string {
96
 	url := r.Form.Get("url")
102
 	url := r.Form.Get("url")
97
 
103
 
98
 	if artist == "" && title == "" && url != "" {
104
 	if artist == "" && title == "" && url != "" {
99
-		if s.spotify.client == nil {
105
+		var client = s.spotify.client
106
+		if client == nil {
107
+			client = session.spotifyClient
108
+		}
109
+		if client == nil {
100
 			http.Error(w, "No Spotify token available", http.StatusInternalServerError)
110
 			http.Error(w, "No Spotify token available", http.StatusInternalServerError)
101
 			return
111
 			return
102
 		}
112
 		}
103
-		token, err := s.spotify.client.Token()
113
+		token, err := client.Token()
104
 		if err != nil {
114
 		if err != nil {
105
 			http.Error(w, err.Error(), http.StatusInternalServerError)
115
 			http.Error(w, err.Error(), http.StatusInternalServerError)
106
 			return
116
 			return
111
 
121
 
112
 		log.Println("Resolving Spotify URL")
122
 		log.Println("Resolving Spotify URL")
113
 		trackID := getTrackID(url)
123
 		trackID := getTrackID(url)
114
-		track, err := s.spotify.client.GetTrack(spotify.ID(trackID))
124
+		track, err := client.GetTrack(spotify.ID(trackID))
115
 
125
 
116
 		if err != nil {
126
 		if err != nil {
117
 			http.Error(w, err.Error(), http.StatusInternalServerError)
127
 			http.Error(w, err.Error(), http.StatusInternalServerError)
182
 		http.Error(w, "Couldn't get token", http.StatusForbidden)
192
 		http.Error(w, "Couldn't get token", http.StatusForbidden)
183
 		return
193
 		return
184
 	}
194
 	}
195
+	client := s.spotify.auth.NewClient(token)
196
+	profile, err := client.CurrentUser()
197
+	if err != nil {
198
+		http.Error(w, "Couldn't load profile from Spotify", http.StatusInternalServerError)
199
+		return
200
+	}
185
 	session, err := getSession(r)
201
 	session, err := getSession(r)
202
+	if session == nil {
203
+		if err != nil {
204
+			log.Println("Error while trying to read current session", err)
205
+		}
206
+		log.Println("Trying to login with spotify ID", profile.ID)
207
+		cookie, err := tryLoginWithSpotify(s.db, profile.ID)
208
+		if err != nil {
209
+			http.Error(w, "Couldn't login with Spotify profile", http.StatusInternalServerError)
210
+			return
211
+		}
212
+		session, _ := getSessionById(cookie.Value)
213
+		session.spotifyClient = &client
214
+
215
+		log.Println("Logged in user spotify ID", profile.ID)
216
+		http.SetCookie(w, &cookie)
217
+		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
218
+		return
219
+	} else {
220
+		session.spotify = profile.ID
221
+		session.spotifyClient = &client
222
+		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
223
+		return
224
+	}
225
+	// TODO: when?
226
+	if profile.ID == "lamperi" {
227
+		s.spotify.client = &client
228
+	}
229
+}
230
+
231
+func (s *WebService) RoundHandler(w http.ResponseWriter, r *http.Request) {
232
+	data := struct {
233
+		Username        string
234
+		Spotify         string
235
+		SpotifyExpiry   string
236
+		Songs           []*Song
237
+		SubmitterPublic bool
238
+		RoundInfo       *RoundInfo
239
+	}{
240
+		"",
241
+		"",
242
+		"",
243
+		make([]*Song, 0),
244
+		false,
245
+		nil,
246
+	}
247
+
248
+	round := strings.TrimPrefix(r.URL.Path, "/round/")
249
+	roundNum, err := strconv.Atoi(round)
186
 	if err != nil {
250
 	if err != nil {
187
-		http.Error(w, "Couldn't get session", http.StatusInternalServerError)
251
+		http.Error(w, err.Error(), http.StatusBadRequest)
188
 		return
252
 		return
189
 	}
253
 	}
190
-	client := s.spotify.auth.NewClient(token)
191
-	s.spotify.client = &client
192
-	profile, err := s.spotify.client.CurrentUser()
254
+	roundInfo, err := s.db.FindRoundInfo(roundNum)
193
 	if err != nil {
255
 	if err != nil {
194
-		http.Error(w, "Couldn't load profile from Spotify", http.StatusInternalServerError)
256
+		http.Error(w, err.Error(), http.StatusInternalServerError)
195
 		return
257
 		return
196
 	}
258
 	}
197
-	session.spotify = profile.ID
198
-	http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
259
+	if roundInfo == nil {
260
+		http.Error(w, "No such panel", http.StatusNotFound)
261
+		return
262
+	}
263
+	data.RoundInfo = roundInfo
264
+	session, err := getSession(r)
265
+	if session != nil {
266
+		data.Username = session.username
267
+		data.Spotify = session.spotify
268
+	}
269
+	songs, err := s.db.FindAllRoundEntries(roundNum)
270
+	if err != nil {
271
+		log.Println("Error while reading entries from database", err)
272
+		http.Error(w, err.Error(), http.StatusInternalServerError)
273
+		return
274
+	}
275
+	data.Songs = songs
276
+	data.SubmitterPublic = roundInfo.End.Before(time.Now())
277
+
278
+	if s.spotify.client != nil {
279
+		token, err := s.spotify.client.Token()
280
+		if err == nil {
281
+			data.SpotifyExpiry = token.Expiry.Format(time.RFC822Z)
282
+		}
283
+	}
284
+	s.serveTemplate(w, data, "round.html")
199
 }
285
 }
200
 
286
 
201
 type WebService struct {
287
 type WebService struct {
215
 	mux.Handle("/js/", fs)
301
 	mux.Handle("/js/", fs)
216
 	mux.Handle("/favicon.ico", fs)
302
 	mux.Handle("/favicon.ico", fs)
217
 	mux.HandleFunc("/", service.IndexHandler)
303
 	mux.HandleFunc("/", service.IndexHandler)
304
+	mux.HandleFunc("/round/", service.RoundHandler)
218
 	mux.HandleFunc("/update", service.UpdateHandler)
305
 	mux.HandleFunc("/update", service.UpdateHandler)
219
 	mux.HandleFunc("/login", service.LoginHandler)
306
 	mux.HandleFunc("/login", service.LoginHandler)
220
 	mux.HandleFunc("/spotify", service.SpotifyHandler)
307
 	mux.HandleFunc("/spotify", service.SpotifyHandler)

+ 5 - 3
web/index.html View File

40
         {{end}}
40
         {{end}}
41
         {{range .Songs }}
41
         {{range .Songs }}
42
         <div class="row">
42
         <div class="row">
43
-            <small style="width: 70px">{{ .RoundName }}</small>
43
+            <small style="width: 70px"><a href="/round/{{ .RoundID }}">{{ .RoundName }}</a></small>
44
             <form class="form-inline" method="post" action="/update">
44
             <form class="form-inline" method="post" action="/update">
45
                 <label class="sr-only" for="artist">Artist</label>
45
                 <label class="sr-only" for="artist">Artist</label>
46
                 <input class="form-control mb-2 mr-sm-2" name="artist" placeholder="Enter Artist"  value="{{ .Artist }}">
46
                 <input class="form-control mb-2 mr-sm-2" name="artist" placeholder="Enter Artist"  value="{{ .Artist }}">
49
                 <input class="form-control mb-2 mr-sm-2" name="title" placeholder="Enter Track title"  value="{{ .Title }}">
49
                 <input class="form-control mb-2 mr-sm-2" name="title" placeholder="Enter Track title"  value="{{ .Title }}">
50
                 
50
                 
51
                 <label class="sr-only" for="url">Track URL</label>
51
                 <label class="sr-only" for="url">Track URL</label>
52
-                <input class="form-control mb-2 mr-sm-2" name="url" placeholder="Enter Track title" value="{{ .URL }}">
52
+                <input class="form-control mb-2 mr-sm-2" name="url" placeholder="Enter Track URL" value="{{ .URL }}">
53
                 
53
                 
54
                 <div class="form-check mb-2 mr-sm-2">
54
                 <div class="form-check mb-2 mr-sm-2">
55
                     <input class="form-check-input" style="width: 100px" type="checkbox" name="synced" {{ if .Sync }} checked {{ else }} disabled {{ end }}>
55
                     <input class="form-check-input" style="width: 100px" type="checkbox" name="synced" {{ if .Sync }} checked {{ else }} disabled {{ end }}>
76
             </div>
76
             </div>
77
             <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
77
             <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
78
         </form>
78
         </form>
79
+        <hr/>
80
+        <a href="/spotify"><button class="btn btn-lg btn-primary btn-block" style="margin-bottom: 1em">Sign in with Spotify</button></a>
79
     {{end}}
81
     {{end}}
80
 
82
 
81
       <hr>
83
       <hr>
82
 
84
 
83
       <footer>
85
       <footer>
84
-        <p>&copy; Lamperi 2018</p>
86
+        <p>&copy; Lamperi 2018-2019</p>
85
       </footer>
87
       </footer>
86
     </div> <!-- /container -->
88
     </div> <!-- /container -->
87
 
89
 

+ 66 - 0
web/round.html View File

1
+<!DOCTYPE html>
2
+<html lang="en">
3
+  <head>
4
+    <meta charset="utf-8">
5
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6
+    <meta name="description" content="">
7
+    <meta name="author" content="">
8
+    <link rel="icon" href="favicon.ico">
9
+
10
+    <title>LevyraatiBot</title>
11
+
12
+    <base href="..">
13
+    <!-- Bootstrap core CSS -->
14
+    <link href="css/bootstrap.min.css" rel="stylesheet">
15
+
16
+    <!-- Custom styles for this template -->
17
+    <link href="css/jumbotron.css" rel="stylesheet">
18
+  </head>
19
+
20
+  <body>
21
+
22
+    <!-- Main jumbotron for a primary marketing message or call to action -->
23
+    <div class="jumbotron">
24
+      <div class="container">
25
+        {{if .RoundInfo}}
26
+        <h1 class="display-3">{{ .RoundInfo.PanelName }} {{ .RoundInfo.Section }}</h1>
27
+        {{if or (gt (len .Songs) 3) .SubmitterPublic }}
28
+        <p>Tässä viikon kattaus, olkaa hyvä</p>
29
+        {{else}}
30
+        <p>Odotetaan vielä muutamaa laulua.</p>
31
+        {{end}}
32
+        {{else}}
33
+        <h1 class="display-3">Please log in</h1>
34
+        <p>Login now.</p>
35
+        {{end}}
36
+      </div>
37
+    </div>
38
+
39
+    <div class="container">
40
+        {{if or (gt (len .Songs) 3) .SubmitterPublic }}
41
+	{{ $submitterPublic := .SubmitterPublic }}
42
+        {{range .Songs }}
43
+        <div class="row">
44
+                <span>{{ .Artist }} / {{ .Title }} / <a href="{{ .URL }}">Kuuntele</a>
45
+    		{{ if $submitterPublic }}({{ .Submitter }}){{ end }}
46
+        </div>
47
+        {{end}}
48
+        {{else}}
49
+	<p>Vasta {{ len .Songs }} laulu{{if ne (len .Songs) 1}}a{{end}} lisätty.</p>
50
+        {{end}}
51
+      <hr>
52
+
53
+      <footer>
54
+        <p>&copy; Lamperi 2018-2019</p>
55
+      </footer>
56
+    </div> <!-- /container -->
57
+
58
+
59
+    <!-- Bootstrap core JavaScript
60
+    ================================================== -->
61
+    <!-- Placed at the end of the document so the pages load faster -->
62
+    <script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
63
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
64
+    <script src="js/vendor/bootstrap.min.js"></script>
65
+  </body>
66
+</html>