@@ -3,7 +3,10 @@ package routes
33import (
44 "context"
55 "fmt"
6+ "html/template"
67 "net/http"
8+ "os"
9+ "path"
710 "strconv"
811
912 "github.com/jackc/pgx/v5/pgxpool"
@@ -19,15 +22,46 @@ type messageResponse struct {
1922}
2023
2124func RegisterRoutes (mux * http.ServeMux , logger logging.Logger ) {
22- mux .Handle ("GET /api/v1" , rootGetHandler (logger ))
23- mux .Handle ("GET /api/v1/health" , rootGetHandler (logger ))
25+ mux .Handle ("GET /api/v1" , healthcheckHandler (logger ))
26+ mux .Handle ("GET /api/v1/health" , healthcheckHandler (logger ))
2427 mux .Handle ("POST /api/v1/recitals" , createRecitalHandler (logger ))
2528 mux .Handle ("GET /api/v1/recitals" , listRecitalsHandler (logger ))
2629 mux .Handle ("GET /api/v1/recitals/{id}" , getRecitalHandler (logger ))
2730 mux .Handle ("DELETE /api/v1/recitals/{id}" , deleteRecitalHandler (logger ))
31+ mux .Handle ("GET /api/v1/recitals/{id}/listen" , mainpageHandler (logger ))
32+ mux .Handle ("GET /api/v1/recitals/{id}/audio" , streamRecitalAudioHandler (logger ))
2833}
2934
30- func rootGetHandler (logger logging.Logger ) http.Handler {
35+ func mainpageHandler (logger logging.Logger ) http.Handler {
36+ page := template .Must (template .New ("listen" ).Parse (`
37+ <!doctype html>
38+ <meta charset="utf-8" />
39+ <title>Listen</title>
40+ <body style="font-family: system-ui; padding: 2rem;">
41+ <h1>Listen</h1>
42+ <p>Now playing: {{.ID}}</p>
43+ <audio controls preload="auto" style="width: 100%;" src="/api/v1/recitals/{{.ID}}/audio"></audio>
44+ </body>
45+ ` ))
46+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
47+ id , err := strconv .Atoi (r .PathValue ("id" ))
48+ if err != nil {
49+ res := messageResponse {Message : "invalid id" }
50+ if err := marshal .Encode (r .Context (), w , r , http .StatusBadRequest , res ); err != nil {
51+ writeErrHeader (w , err , logger )
52+ }
53+ return
54+ }
55+ w .Header ().Set ("Content-Type" , "text/html; charset=utf-8" )
56+ w .WriteHeader (http .StatusOK )
57+ if err := page .Execute (w , struct { ID int }{ID : id }); err != nil {
58+ http .Error (w , err .Error (), http .StatusInternalServerError )
59+ return
60+ }
61+ })
62+ }
63+
64+ func healthcheckHandler (logger logging.Logger ) http.Handler {
3165 return http .HandlerFunc (
3266 func (w http.ResponseWriter , r * http.Request ) {
3367 w .WriteHeader (http .StatusOK )
@@ -188,6 +222,42 @@ func deleteRecitalHandler(logger logging.Logger) http.Handler {
188222 })
189223}
190224
225+ func streamRecitalAudioHandler (logger logging.Logger ) http.Handler {
226+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
227+ id , err := strconv .Atoi (r .PathValue ("id" ))
228+ if err != nil {
229+ res := messageResponse {Message : "invalid id" }
230+ if err := marshal .Encode (r .Context (), w , r , http .StatusBadRequest , res ); err != nil {
231+ writeErrHeader (w , err , logger )
232+ }
233+ return
234+ }
235+
236+ filename := path .Join (services .BaseOutputPath , fmt .Sprintf ("%d_out.wav" , id ))
237+ file , err := os .Open (filename )
238+ if err != nil {
239+ if os .IsNotExist (err ) {
240+ http .NotFound (w , r )
241+ } else {
242+ http .Error (w , "open failed" , http .StatusInternalServerError )
243+ }
244+ return
245+ }
246+ defer file .Close ()
247+
248+ stat , err := file .Stat ()
249+ if err != nil {
250+ http .Error (w , "stat failed" , http .StatusInternalServerError )
251+ return
252+ }
253+
254+ w .Header ().Set ("Content-Type" , "audio/wav" )
255+ w .Header ().Set ("Accept-Ranges" , "bytes" )
256+ w .Header ().Set ("Cache-Control" , "public, max-age=3600" )
257+ http .ServeContent (w , r , stat .Name (), stat .ModTime (), file )
258+ })
259+ }
260+
191261func repondWithErrorMessage (w http.ResponseWriter , r * http.Request , message string , code int , logger logging.Logger ) {
192262 res := messageResponse {
193263 Message : message ,
0 commit comments