1
1
package main
2
2
3
3
import (
4
+ "embed"
4
5
"encoding/json"
5
6
"fmt"
6
7
"html/template"
8
+ "io/fs"
7
9
"net/http"
8
10
"os"
11
+ "path/filepath"
9
12
"strconv"
10
13
"time"
11
14
)
12
15
16
+ //go:embed static templates
17
+ var content embed.FS
18
+
13
19
// Fast represents a fasting record
14
20
type Fast struct {
15
21
StartTime time.Time `json:"start_time"`
@@ -42,27 +48,34 @@ func loadData() (*AppData, error) {
42
48
}
43
49
file , err := os .Open (dataFile )
44
50
if err != nil {
45
- return nil , err
51
+ return nil , fmt . Errorf ( "opening data file: %w" , err )
46
52
}
47
53
defer file .Close ()
48
54
var data AppData
49
55
err = json .NewDecoder (file ).Decode (& data )
50
56
if err != nil {
51
- return nil , err
57
+ return nil , fmt . Errorf ( "decoding data file: %w" , err )
52
58
}
53
59
return & data , nil
54
60
}
55
61
56
62
// saveData writes the application data to the JSON file
57
63
func saveData (data * AppData ) error {
64
+ dir := filepath .Dir (dataFile )
65
+ if err := os .MkdirAll (dir , 0755 ); err != nil {
66
+ return fmt .Errorf ("creating data directory: %w" , err )
67
+ }
58
68
file , err := os .Create (dataFile )
59
69
if err != nil {
60
- return err
70
+ return fmt . Errorf ( "creating data file: %w" , err )
61
71
}
62
72
defer file .Close ()
63
73
encoder := json .NewEncoder (file )
64
74
encoder .SetIndent ("" , " " )
65
- return encoder .Encode (data )
75
+ if err := encoder .Encode (data ); err != nil {
76
+ return fmt .Errorf ("encoding data: %w" , err )
77
+ }
78
+ return nil
66
79
}
67
80
68
81
// homeHandler redirects to the profile page
@@ -74,11 +87,10 @@ func homeHandler(w http.ResponseWriter, r *http.Request) {
74
87
func profileHandler (w http.ResponseWriter , r * http.Request ) {
75
88
data , err := loadData ()
76
89
if err != nil {
77
- http .Error (w , "Error loading data" , http .StatusInternalServerError )
90
+ http .Error (w , fmt . Sprintf ( "Error loading data: %v" , err ) , http .StatusInternalServerError )
78
91
return
79
92
}
80
93
81
- // Pagination logic
82
94
const itemsPerPage = 5
83
95
pageStr := r .URL .Query ().Get ("page" )
84
96
page , err := strconv .Atoi (pageStr )
@@ -109,45 +121,49 @@ func profileHandler(w http.ResponseWriter, r *http.Request) {
109
121
HasNext : page < totalPages ,
110
122
}
111
123
112
- tmpl := template .New ("profile.html" ).Funcs (template.FuncMap {
124
+ tmpl , err := template .New ("profile.html" ).Funcs (template.FuncMap {
113
125
"seq" : func (start , end int ) []int {
126
+ if end < start {
127
+ return []int {}
128
+ }
114
129
s := make ([]int , end - start + 1 )
115
130
for i := range s {
116
131
s [i ] = start + i
117
132
}
118
133
return s
119
134
},
120
135
"mod" : func (a , b int ) int {
136
+ if b == 0 {
137
+ return 0
138
+ }
121
139
return a % b
122
140
},
123
141
"add" : func (a , b int ) int {
124
142
return a + b
125
143
},
126
- })
127
- tmpl , err = tmpl .ParseFiles ("templates/profile.html" )
144
+ }).ParseFS (content , "templates/profile.html" )
128
145
if err != nil {
129
- http .Error (w , "Error parsing template" , http .StatusInternalServerError )
146
+ http .Error (w , fmt . Sprintf ( "Error parsing template: %v" , err ) , http .StatusInternalServerError )
130
147
return
131
148
}
132
149
err = tmpl .Execute (w , templateData )
133
150
if err != nil {
134
- http .Error (w , "Error executing template" , http .StatusInternalServerError )
151
+ http .Error (w , fmt . Sprintf ( "Error executing template: %v" , err ) , http .StatusInternalServerError )
135
152
}
136
153
}
137
154
138
155
// fastingHandler renders the Fasting tab
139
156
func fastingHandler (w http.ResponseWriter , r * http.Request ) {
140
157
data , err := loadData ()
141
158
if err != nil {
142
- http .Error (w , "Error loading data" , http .StatusInternalServerError )
159
+ http .Error (w , fmt . Sprintf ( "Error loading data: %v" , err ) , http .StatusInternalServerError )
143
160
return
144
161
}
145
- t , err := template .ParseFiles ( "templates/fasting.html" )
162
+ t , err := template .ParseFS ( content , "templates/fasting.html" )
146
163
if err != nil {
147
- http .Error (w , "Error parsing template" , http .StatusInternalServerError )
164
+ http .Error (w , fmt . Sprintf ( "Error parsing template: %v" , err ) , http .StatusInternalServerError )
148
165
return
149
166
}
150
- // Pass error message from query parameter, if any
151
167
type FastingTemplateData struct {
152
168
* AppData
153
169
ErrorMessage string
@@ -158,7 +174,7 @@ func fastingHandler(w http.ResponseWriter, r *http.Request) {
158
174
}
159
175
err = t .Execute (w , templateData )
160
176
if err != nil {
161
- http .Error (w , "Error executing template" , http .StatusInternalServerError )
177
+ http .Error (w , fmt . Sprintf ( "Error executing template: %v" , err ) , http .StatusInternalServerError )
162
178
}
163
179
}
164
180
@@ -176,7 +192,7 @@ func startFastHandler(w http.ResponseWriter, r *http.Request) {
176
192
goalStr := r .FormValue ("goal" )
177
193
var goal int
178
194
if goalStr == "" {
179
- goal = 0 // Allow empty input as 0
195
+ goal = 0
180
196
} else {
181
197
goal , err = strconv .Atoi (goalStr )
182
198
if err != nil || goal < 0 {
@@ -199,7 +215,7 @@ func startFastHandler(w http.ResponseWriter, r *http.Request) {
199
215
}
200
216
err = saveData (data )
201
217
if err != nil {
202
- http .Redirect (w , r , "/fasting?error=Error saving data" , http .StatusSeeOther )
218
+ http .Redirect (w , r , fmt . Sprintf ( "/fasting?error=Error saving data: %v" , err ) , http .StatusSeeOther )
203
219
return
204
220
}
205
221
http .Redirect (w , r , "/fasting" , http .StatusSeeOther )
@@ -213,30 +229,37 @@ func endFastHandler(w http.ResponseWriter, r *http.Request) {
213
229
}
214
230
data , err := loadData ()
215
231
if err != nil {
216
- http .Error (w , "Error loading data" , http .StatusInternalServerError )
232
+ http .Error (w , fmt . Sprintf ( "Error loading data: %v" , err ) , http .StatusInternalServerError )
217
233
return
218
234
}
219
235
if data .CurrentFast == nil {
220
236
http .Error (w , "No fast in progress" , http .StatusBadRequest )
221
237
return
222
238
}
223
239
duration := time .Since (data .CurrentFast .StartTime ).Hours ()
224
- data . FastingHistory = append ( data . FastingHistory , Fast {
240
+ completedFast := Fast {
225
241
StartTime : data .CurrentFast .StartTime ,
242
+ GoalHours : data .CurrentFast .GoalHours ,
226
243
DurationHours : duration ,
227
- })
244
+ }
245
+ data .FastingHistory = append ([]Fast {completedFast }, data .FastingHistory ... )
228
246
data .CurrentFast = nil
229
247
err = saveData (data )
230
248
if err != nil {
231
- http .Error (w , "Error saving data" , http .StatusInternalServerError )
249
+ http .Error (w , fmt . Sprintf ( "Error saving data: %v" , err ) , http .StatusInternalServerError )
232
250
return
233
251
}
234
252
http .Redirect (w , r , "/fasting" , http .StatusSeeOther )
235
253
}
236
254
237
255
func main () {
238
- // Serve static files
239
- http .Handle ("/static/" , http .StripPrefix ("/static/" , http .FileServer (http .Dir ("static" ))))
256
+ // Serve static files from embedded FS
257
+ staticFS , err := fs .Sub (content , "static" )
258
+ if err != nil {
259
+ fmt .Println ("Error creating static FS:" , err )
260
+ os .Exit (1 )
261
+ }
262
+ http .Handle ("/static/" , http .StripPrefix ("/static/" , http .FileServer (http .FS (staticFS ))))
240
263
241
264
// Define routes
242
265
http .HandleFunc ("/" , homeHandler )
@@ -247,7 +270,7 @@ func main() {
247
270
248
271
// Start the server
249
272
fmt .Println ("Xerophagon starting on :5000..." )
250
- err : = http .ListenAndServe (":5000" , nil )
273
+ err = http .ListenAndServe (":5000" , nil )
251
274
if err != nil {
252
275
fmt .Printf ("Server failed: %v\n " , err )
253
276
}
0 commit comments