+2
-2
.air.toml
+2
-2
.air.toml
···
14
14
follow_symlink = false
15
15
full_bin = ""
16
16
include_dir = []
17
-
include_ext = ["go", "tpl", "tmpl", "html", "gohtml"]
17
+
include_ext = ["go", "tpl", "tmpl", "html", "gohtml", "css", "js"]
18
18
include_file = []
19
19
kill_delay = "0s"
20
20
log = "build-errors.log"
···
48
48
proxy_port = 0
49
49
50
50
[screen]
51
-
clear_on_rebuild = false
51
+
clear_on_rebuild = true
52
52
keep_scroll = true
+17
-30
cmd/handlers.go
+17
-30
cmd/handlers.go
···
50
50
}
51
51
}
52
52
53
-
func handleLinkLastfmForm(database *db.DB) http.HandlerFunc {
53
+
func handleLinkLastfmForm(database *db.DB, pg *pages.Pages) http.HandlerFunc {
54
54
return func(w http.ResponseWriter, r *http.Request) {
55
-
userID, _ := session.GetUserID(r.Context())
55
+
userID, authenticated := session.GetUserID(r.Context())
56
56
if r.Method == http.MethodPost {
57
57
if err := r.ParseForm(); err != nil {
58
58
http.Error(w, "Failed to parse form", http.StatusBadRequest)
···
87
87
}
88
88
89
89
w.Header().Set("Content-Type", "text/html")
90
-
fmt.Fprintf(w, `
91
-
<html>
92
-
<head><title>Link Last.fm Account</title>
93
-
<style>
94
-
body { font-family: Arial, sans-serif; max-width: 600px; margin: 20px auto; padding: 20px; border: 1px solid #ddd; border-radius: 8px; }
95
-
label, input { display: block; margin-bottom: 10px; }
96
-
input[type='text'] { width: 95%%; padding: 8px; } /* Corrected width */
97
-
input[type='submit'] { padding: 10px 15px; background-color: #d51007; color: white; border: none; border-radius: 4px; cursor: pointer; }
98
-
.nav { margin-bottom: 20px; }
99
-
.nav a { margin-right: 10px; text-decoration: none; color: #1DB954; font-weight: bold; }
100
-
.error { color: red; margin-bottom: 10px; }
101
-
</style>
102
-
</head>
103
-
<body>
104
-
<div class="nav">
105
-
<a href="/">Home</a>
106
-
<a href="/link-lastfm">Link Last.fm</a>
107
-
<a href="/logout">Logout</a>
108
-
</div>
109
-
<h2>Link Your Last.fm Account</h2>
110
-
<p>Enter your Last.fm username to start tracking your scrobbles.</p>
111
-
<form method="post" action="/link-lastfm">
112
-
<label for="lastfm_username">Last.fm Username:</label>
113
-
<input type="text" id="lastfm_username" name="lastfm_username" value="%s" required>
114
-
<input type="submit" value="Save Username">
115
-
</form>
116
-
</body>
117
-
</html>`, currentUsername)
90
+
91
+
pageParams := struct {
92
+
NavBar pages.NavBar
93
+
CurrentUsername string
94
+
}{
95
+
NavBar: pages.NavBar{
96
+
IsLoggedIn: authenticated,
97
+
LastFMUsername: currentUsername,
98
+
},
99
+
CurrentUsername: currentUsername,
100
+
}
101
+
err = pg.Execute("lastFMForm", w, pageParams)
102
+
if err != nil {
103
+
log.Printf("Error executing template: %v", err)
104
+
}
118
105
}
119
106
}
120
107
-3
cmd/main.go
-3
cmd/main.go
···
116
116
lastfmInterval = 30 * time.Second
117
117
}
118
118
119
-
//if err := spotifyService.LoadAllUsers(); err != nil {
120
-
// log.Printf("Warning: Failed to preload Spotify users: %v", err)
121
-
//}
122
119
go spotifyService.StartListeningTracker(trackerInterval)
123
120
124
121
go lastfmService.StartListeningTracker(lastfmInterval)
+4
-8
cmd/routes.go
+4
-8
cmd/routes.go
···
10
10
11
11
func (app *application) routes() http.Handler {
12
12
mux := http.NewServeMux()
13
-
// Redirect /static to /static/ so it doesn't fall through to "/" handler
13
+
14
+
//Handles static file routes
14
15
mux.Handle("/static/{file_name}", app.pages.Static())
15
-
//mux.HandleFunc("/static/{file_name}", func(w http.ResponseWriter, r *http.Request) {
16
-
// w.Header().Set("Content-Type", "text/html")
17
-
//
18
-
// w.Write([]byte("Static files are served from /static/"))
19
-
//})
20
16
21
17
mux.HandleFunc("/", session.WithPossibleAuth(home(app.database, app.pages), app.sessionManager))
22
18
···
30
26
mux.HandleFunc("/current-track", session.WithAuth(app.spotifyService.HandleCurrentTrack, app.sessionManager))
31
27
mux.HandleFunc("/history", session.WithAuth(app.spotifyService.HandleTrackHistory, app.sessionManager))
32
28
mux.HandleFunc("/api-keys", session.WithAuth(app.apiKeyService.HandleAPIKeyManagement(app.database, app.pages), app.sessionManager))
33
-
mux.HandleFunc("/link-lastfm", session.WithAuth(handleLinkLastfmForm(app.database), app.sessionManager)) // GET form
34
-
mux.HandleFunc("/link-lastfm/submit", session.WithAuth(handleLinkLastfmSubmit(app.database), app.sessionManager)) // POST submit - Changed route slightly
29
+
mux.HandleFunc("/link-lastfm", session.WithAuth(handleLinkLastfmForm(app.database, app.pages), app.sessionManager)) // GET form
30
+
mux.HandleFunc("/link-lastfm/submit", session.WithAuth(handleLinkLastfmSubmit(app.database), app.sessionManager)) // POST submit - Changed route slightly
35
31
mux.HandleFunc("/logout", app.sessionManager.HandleLogout)
36
32
mux.HandleFunc("/debug/", session.WithAuth(app.sessionManager.HandleDebug, app.sessionManager))
37
33
+4
-9
pages/pages.go
+4
-9
pages/pages.go
···
119
119
}
120
120
121
121
func (p *Pages) Static() http.Handler {
122
-
//if p.dev {
123
-
// return http.StripPrefix("/static/", http.FileServer(http.Dir("appview/pages/static")))
124
-
//}
125
122
126
123
sub, err := fs.Sub(Files, "static")
127
124
if err != nil {
128
-
//p.logger.Error("no static dir found? that's crazy", "err", err)
129
125
panic(err)
130
126
}
131
-
return http.StripPrefix("/static/", http.FileServer(http.FS(sub)))
132
-
// Custom handler to apply Cache-Control headers for font files
133
-
//return http.FileServer(http.FS(sub))
134
-
//return http.FileServer(http.FS(sub))
127
+
128
+
return Cache(http.StripPrefix("/static/", http.FileServer(http.FS(sub))))
135
129
}
136
130
137
131
func Cache(h http.Handler) http.Handler {
138
132
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
139
133
path := strings.Split(r.URL.Path, "?")[0]
140
-
134
+
135
+
//We may want to change these, just took what tangled has and allows browser side caching
141
136
if strings.HasSuffix(path, ".css") {
142
137
// on day for css files
143
138
w.Header().Set("Cache-Control", "public, max-age=86400")
-32
pages/static/style.css
-32
pages/static/style.css
···
1
-
body {
2
-
font-family: Arial, sans-serif;
3
-
max-width: 800px;
4
-
margin: 0 auto;
5
-
padding: 20px;
6
-
line-height: 1.6;
7
-
}
8
-
h1 {
9
-
color: #1DB954; /* Spotify green */
10
-
}
11
-
.nav {
12
-
display: flex;
13
-
flex-wrap: wrap; /* Allow wrapping on smaller screens */
14
-
margin-bottom: 20px;
15
-
}
16
-
.nav a {
17
-
margin-right: 15px;
18
-
margin-bottom: 5px; /* Add spacing below links */
19
-
text-decoration: none;
20
-
color: #1DB954;
21
-
font-weight: bold;
22
-
}
23
-
.card {
24
-
border: 1px solid #ddd;
25
-
border-radius: 8px;
26
-
padding: 20px;
27
-
margin-bottom: 20px;
28
-
}
29
-
.service-status {
30
-
font-style: italic;
31
-
color: #555;
32
-
}
+124
pages/static/styles.css
+124
pages/static/styles.css
···
1
+
body {
2
+
font-family: Arial, sans-serif;
3
+
max-width: 800px;
4
+
margin: 0 auto;
5
+
padding: 20px;
6
+
line-height: 1.6;
7
+
}
8
+
9
+
10
+
h1 {
11
+
color: #1DB954; /* Spotify green */
12
+
}
13
+
14
+
.nav {
15
+
display: flex;
16
+
flex-wrap: wrap; /* Allow wrapping on smaller screens */
17
+
margin-bottom: 20px;
18
+
}
19
+
20
+
.nav a {
21
+
margin-right: 15px;
22
+
margin-bottom: 5px; /* Add spacing below links */
23
+
text-decoration: none;
24
+
color: #1DB954;
25
+
font-weight: bold;
26
+
}
27
+
28
+
.card {
29
+
border: 1px solid #ddd;
30
+
border-radius: 8px;
31
+
padding: 20px;
32
+
margin-bottom: 20px;
33
+
}
34
+
35
+
.service-status {
36
+
font-style: italic;
37
+
color: #555;
38
+
}
39
+
40
+
41
+
label, input {
42
+
display: block;
43
+
margin-bottom: 10px;
44
+
}
45
+
46
+
input[type='text'] {
47
+
width: 95%;
48
+
padding: 8px;
49
+
}
50
+
51
+
/* Corrected width */
52
+
input[type='submit'] {
53
+
padding: 10px 15px;
54
+
color: white;
55
+
border: none;
56
+
border-radius: 4px;
57
+
cursor: pointer;
58
+
}
59
+
60
+
.last-fm-input {
61
+
background-color: #d51007;
62
+
}
63
+
64
+
.teal-input {
65
+
background-color: #1DB954;
66
+
}
67
+
68
+
.error {
69
+
color: red;
70
+
margin-bottom: 10px;
71
+
}
72
+
73
+
.lastfm-form {
74
+
max-width: 600px;
75
+
margin: 20px auto;
76
+
padding: 20px;
77
+
border: 1px solid #ddd;
78
+
border-radius: 8px;
79
+
}
80
+
81
+
.card {
82
+
border: 1px solid #ddd;
83
+
border-radius: 8px;
84
+
padding: 20px;
85
+
margin-bottom: 20px;
86
+
}
87
+
table {
88
+
width: 100%;
89
+
border-collapse: collapse;
90
+
}
91
+
table th, table td {
92
+
padding: 8px;
93
+
text-align: left;
94
+
border-bottom: 1px solid #ddd;
95
+
}
96
+
.key-value {
97
+
font-family: monospace;
98
+
padding: 10px;
99
+
background-color: #f5f5f5;
100
+
border: 1px solid #ddd;
101
+
border-radius: 4px;
102
+
word-break: break-all;
103
+
}
104
+
.new-key-alert {
105
+
background-color: #f8f9fa;
106
+
border-left: 4px solid #1DB954;
107
+
padding: 15px;
108
+
margin-bottom: 20px;
109
+
}
110
+
.btn {
111
+
padding: 8px 16px;
112
+
background-color: #1DB954;
113
+
color: white;
114
+
border: none;
115
+
border-radius: 4px;
116
+
cursor: pointer;
117
+
}
118
+
.btn-danger {
119
+
background-color: #dc3545;
120
+
}
121
+
122
+
.teal-header {
123
+
color: #1DB954;
124
+
}
+4
-4
pages/templates/apiKeys.gohtml
+4
-4
pages/templates/apiKeys.gohtml
···
7
7
<h1>API Key Management</h1>
8
8
9
9
<div class="card">
10
-
<h2>Create New API Key</h2>
10
+
<h2 class="teal-header">Create New API Key</h2>
11
11
<p>API keys allow programmatic access to your Piper account data.</p>
12
12
<form method="POST" action="/api-keys">
13
13
<div style="margin-bottom: 15px;">
···
20
20
21
21
{{if .NewKeyID}} <!-- Changed from .NewKey to .NewKeyID for clarity -->
22
22
<div class="new-key-alert">
23
-
<h3>Your new API key (ID: {{.NewKeyID}}) has been created</h3>
23
+
<h3 class="teal-header">Your new API key (ID: {{.NewKeyID}}) has been created</h3>
24
24
<!-- The message below is misleading if only the ID is shown.
25
25
Consider changing this text or modifying the flow to show the actual key once for HTML. -->
26
26
<p><strong>Important:</strong> If this is an ID, ensure you have copied the actual key if it was displayed previously. For keys generated via the API, the key is returned in the API response.</p>
···
28
28
{{end}}
29
29
30
30
<div class="card">
31
-
<h2>Your API Keys</h2>
31
+
<h2 class="teal-header">Your API Keys</h2>
32
32
{{if .Keys}}
33
33
<table>
34
34
<thead>
···
60
60
</div>
61
61
62
62
<div class="card">
63
-
<h2>API Usage</h2>
63
+
<h2 class="teal-header">API Usage</h2>
64
64
<p>To use your API key, include it in the Authorization header of your HTTP requests:</p>
65
65
<pre>Authorization: Bearer YOUR_API_KEY</pre>
66
66
<p>Or include it as a query parameter (less secure for the key itself):</p>
+2
-17
pages/templates/home.gohtml
+2
-17
pages/templates/home.gohtml
···
2
2
{{ define "content" }}
3
3
4
4
<h1>Piper - Multi-User Spotify & Last.fm Tracker via ATProto</h1>
5
-
<div class="nav">
6
-
<a href="/">Home</a>
5
+
{{ template "components/navBar" .NavBar }}
7
6
8
-
{{if .NavBar.IsLoggedIn}}
9
-
<a href="/current-track">Spotify Current</a>
10
-
<a href="/history">Spotify History</a>
11
-
<a href="/link-lastfm">Link Last.fm</a>
12
-
{{ if .NavBar.LastFMUsername }}
13
-
<a href="/lastfm/recent">Last.fm Recent</a>
14
-
{{ end }}
15
-
<a href="/api-keys">API Keys</a>
16
-
<a href="/login/spotify">Connect Spotify Account</a>
17
-
<a href="/logout">Logout</a>
18
-
{{ else }}
19
-
<a href="/login/atproto">Login with ATProto</a>
20
-
{{ end }}
21
-
</div>
22
7
23
8
<div class="card">
24
9
<h2>Welcome to Piper</h2>
···
53
38
<form action="/login/atproto">
54
39
<label for="handle">handle:</label>
55
40
<input type="text" id="handle" name="handle" >
56
-
<input type="submit" value="submit">
41
+
<input class="teal-input" type="submit" value="submit">
57
42
</form>
58
43
59
44
+11
-7
pages/templates/lastFMForm.gohtml
+11
-7
pages/templates/lastFMForm.gohtml
···
1
1
{{ define "content" }}
2
-
<h2>Link Your Last.fm Account</h2>
3
-
<p>Enter your Last.fm username to start tracking your scrobbles.</p>
4
-
<form method="post" action="/link-lastfm">
5
-
<label for="lastfm_username">Last.fm Username:</label>
6
-
<input type="text" id="lastfm_username" name="lastfm_username" value="%s" required>
7
-
<input type="submit" value="Save Username">
8
-
</form>
2
+
{{ template "components/navBar" .NavBar }}
3
+
4
+
<div class="lastfm-form">
5
+
<h2>Link Your Last.fm Account</h2>
6
+
<p>Enter your Last.fm username to start tracking your scrobbles.</p>
7
+
<form method="post" action="/link-lastfm">
8
+
<label for="lastfm_username">Last.fm Username:</label>
9
+
<input type="text" id="lastfm_username" name="lastfm_username" value="{{.CurrentUsername}}" required>
10
+
<input class="last-fm-input" type="submit" value="Save Username">
11
+
</form>
12
+
</div>
9
13
10
14
{{ end }}
+1
-35
pages/templates/layouts/base.gohtml
+1
-35
pages/templates/layouts/base.gohtml
···
3
3
<html lang="en">
4
4
<head>
5
5
<title>Piper - Spotify & Last.fm Tracker</title>
6
-
<link rel="stylesheet" href="~/static/style.css">
7
-
<style>
8
-
body {
9
-
font-family: Arial, sans-serif;
10
-
max-width: 800px;
11
-
margin: 0 auto;
12
-
padding: 20px;
13
-
line-height: 1.6;
14
-
}
15
-
h1 {
16
-
color: #1DB954; /* Spotify green */
17
-
}
18
-
.nav {
19
-
display: flex;
20
-
flex-wrap: wrap; /* Allow wrapping on smaller screens */
21
-
margin-bottom: 20px;
22
-
}
23
-
.nav a {
24
-
margin-right: 15px;
25
-
margin-bottom: 5px; /* Add spacing below links */
26
-
text-decoration: none;
27
-
color: #1DB954;
28
-
font-weight: bold;
29
-
}
30
-
.card {
31
-
border: 1px solid #ddd;
32
-
border-radius: 8px;
33
-
padding: 20px;
34
-
margin-bottom: 20px;
35
-
}
36
-
.service-status {
37
-
font-style: italic;
38
-
color: #555;
39
-
}
40
-
</style>
6
+
<link rel="stylesheet" href="/static/styles.css">
41
7
</head>
42
8
<body>
43
9
{{ block "content" . }}{{ end }}