@@ -3,12 +3,12 @@ package routes
33import (
44 "context"
55 "fmt"
6- "log"
76 "net/http"
87 "strconv"
98
109 "github.com/jackc/pgx/v5/pgxpool"
1110 constants "github.com/simondanielsson/recite/cmd/internal"
11+ "github.com/simondanielsson/recite/cmd/internal/logging"
1212 "github.com/simondanielsson/recite/cmd/internal/marshal"
1313 "github.com/simondanielsson/recite/cmd/internal/queries"
1414 "github.com/simondanielsson/recite/cmd/internal/services"
@@ -18,26 +18,28 @@ type messageResponse struct {
1818 Message string `json:"message"`
1919}
2020
21- func RegisterRoutes (mux * http.ServeMux , logger * log .Logger ) {
21+ func RegisterRoutes (mux * http.ServeMux , logger logging .Logger ) {
2222 mux .Handle ("GET /api/v1" , rootGetHandler (logger ))
2323 mux .Handle ("GET /api/v1/health" , rootGetHandler (logger ))
24- mux .Handle ("POST /api/v1/recitals" , recitalPostHandler (logger ))
25- mux .Handle ("GET /api/v1/recitals/{id}" , recitalGetHandler (logger ))
24+ mux .Handle ("POST /api/v1/recitals" , createRecitalHandler (logger ))
25+ mux .Handle ("GET /api/v1/recitals" , listRecitalsHandler (logger ))
26+ mux .Handle ("GET /api/v1/recitals/{id}" , getRecitalHandler (logger ))
27+ mux .Handle ("DELETE /api/v1/recitals/{id}" , deleteRecitalHandler (logger ))
2628}
2729
28- func rootGetHandler (logger * log .Logger ) http.Handler {
30+ func rootGetHandler (logger logging .Logger ) http.Handler {
2931 return http .HandlerFunc (
3032 func (w http.ResponseWriter , r * http.Request ) {
3133 w .WriteHeader (http .StatusOK )
3234 if _ , err := w .Write ([]byte ("ok\n " )); err != nil {
33- logger .Println (err )
35+ logger .Err . Println (err )
3436 w .WriteHeader (http .StatusInternalServerError )
3537 }
3638 },
3739 )
3840}
3941
40- func recitalPostHandler (logger * log .Logger ) http.Handler {
42+ func createRecitalHandler (logger logging .Logger ) http.Handler {
4143 type request struct {
4244 Url string `json:"url"`
4345 }
@@ -49,12 +51,8 @@ func recitalPostHandler(logger *log.Logger) http.Handler {
4951 func (w http.ResponseWriter , r * http.Request ) {
5052 req , err := marshal.Decode [request ](r )
5153 if err != nil || req .Url == "" {
52- res := messageResponse {
53- Message : fmt .Sprintf ("bad request, should contain url. Got %s" , req .Url ),
54- }
55- if err := marshal .Encode (r .Context (), w , r , http .StatusBadRequest , res ); err != nil {
56- writeErrHeader (w , err , logger )
57- }
54+ message := fmt .Sprintf ("bad request, should contain url. Got %s" , req .Url )
55+ repondWithErrorMessage (w , r , message , http .StatusBadRequest , logger )
5856 return
5957 }
6058
@@ -67,24 +65,25 @@ func recitalPostHandler(logger *log.Logger) http.Handler {
6765 ctx := r .Context ()
6866 repository , ok := ctx .Value (constants .RepositoryKey ).(* queries.Queries )
6967 if ! ok {
70- logGenericInternalServiceError (ctx , w , r , fmt .Errorf ("failed loading repository" ), logger )
68+ respondWithOpaqueMessage (ctx , w , r , fmt .Errorf ("failed loading repository" ), logger )
7169 return
7270 }
7371 pool , ok := ctx .Value (constants .DBConnPool ).(* pgxpool.Pool )
7472 if ! ok {
75- logGenericInternalServiceError (ctx , w , r , fmt .Errorf ("failed loading db connection pool" ), logger )
73+ respondWithOpaqueMessage (ctx , w , r , fmt .Errorf ("failed loading db connection pool" ), logger )
7674 return
7775 }
7876
7977 id , err := services .CreateRecital (ctx , req .Url , repository , pool , logger )
8078 if err != nil {
81- logGenericInternalServiceError (ctx , w , r , err , logger )
79+ respondWithOpaqueMessage (ctx , w , r , err , logger )
8280 return
8381 }
8482
8583 res := response {
8684 Id : id ,
8785 }
86+ w .Header ().Add ("Location" , fmt .Sprintf ("/api/v1/recitals/%d" , id ))
8887 if err := marshal .Encode (ctx , w , r , http .StatusCreated , res ); err != nil {
8988 writeErrHeader (w , err , logger )
9089 return
@@ -93,10 +92,40 @@ func recitalPostHandler(logger *log.Logger) http.Handler {
9392 )
9493}
9594
96- func recitalGetHandler (logger * log.Logger ) http.Handler {
97- type response struct {
98- queries.Recital
99- }
95+ func listRecitalsHandler (logger logging.Logger ) http.Handler {
96+ type response []queries.Recital
97+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
98+ offset , err := readIntQueryParam ("offset" , 0 , w , r , logger )
99+ if err != nil {
100+ return
101+ }
102+
103+ limit , err := readIntQueryParam ("limit" , 30 , w , r , logger )
104+ if err != nil {
105+ return
106+ }
107+
108+ ctx := r .Context ()
109+ repository , ok := ctx .Value (constants .RepositoryKey ).(* queries.Queries )
110+ if ! ok {
111+ respondWithOpaqueMessage (ctx , w , r , fmt .Errorf ("failed loading repository" ), logger )
112+ return
113+ }
114+
115+ recitals , err := services .ListRecitals (ctx , int32 (limit ), int32 (offset ), repository , logger )
116+ if err != nil {
117+ respondWithOpaqueMessage (ctx , w , r , err , logger )
118+ return
119+ }
120+
121+ if err := marshal .Encode (ctx , w , r , int (http .StatusOK ), recitals ); err != nil {
122+ writeErrHeader (w , err , logger )
123+ return
124+ }
125+ })
126+ }
127+
128+ func getRecitalHandler (logger logging.Logger ) http.Handler {
100129 return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
101130 id , err := strconv .Atoi (r .PathValue ("id" ))
102131 if err != nil {
@@ -110,7 +139,7 @@ func recitalGetHandler(logger *log.Logger) http.Handler {
110139 ctx := r .Context ()
111140 repository , ok := ctx .Value (constants .RepositoryKey ).(* queries.Queries )
112141 if ! ok {
113- logGenericInternalServiceError (ctx , w , r , fmt .Errorf ("failed loading repository" ), logger )
142+ respondWithOpaqueMessage (ctx , w , r , fmt .Errorf ("failed loading repository" ), logger )
114143 }
115144
116145 recital , err := services .GetRecital (ctx , int32 (id ), repository , logger )
@@ -122,35 +151,82 @@ func recitalGetHandler(logger *log.Logger) http.Handler {
122151 return
123152 }
124153
125- res := response {
126- Recital : recital ,
127- }
128- if err := marshal .Encode (ctx , w , r , http .StatusOK , res ); err != nil {
154+ if err := marshal .Encode (ctx , w , r , http .StatusOK , recital ); err != nil {
129155 logFailedEncodingResponse (ctx , w , r , err , logger )
130156 }
131157 })
132158}
133159
134- func writeErrHeader (w http.ResponseWriter , err error , logger * log.Logger ) {
135- w .WriteHeader (http .StatusInternalServerError )
136- if _ , err := w .Write ([]byte (err .Error ())); err != nil {
137- logger .Print ("failed writing error" )
138- }
160+ func deleteRecitalHandler (logger logging.Logger ) http.Handler {
161+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
162+ id , err := strconv .Atoi (r .PathValue ("id" ))
163+ if err != nil {
164+ res := messageResponse {Message : "invalid id" }
165+ if err := marshal .Encode (r .Context (), w , r , http .StatusBadRequest , res ); err != nil {
166+ writeErrHeader (w , err , logger )
167+ }
168+ return
169+ }
170+
171+ ctx := r .Context ()
172+ repository , ok := ctx .Value (constants .RepositoryKey ).(* queries.Queries )
173+ if ! ok {
174+ respondWithOpaqueMessage (ctx , w , r , fmt .Errorf ("failed loading repository" ), logger )
175+ }
176+ if err := services .DeleteRecital (ctx , int32 (id ), repository , logger ); err != nil {
177+ res := messageResponse {Message : fmt .Sprintf ("Could not find recital with id %d" , id )}
178+ if err := marshal .Encode (ctx , w , r , http .StatusNotFound , res ); err != nil {
179+ logFailedEncodingResponse (ctx , w , r , err , logger )
180+ }
181+ return
182+ }
183+ status := http .StatusNoContent
184+ w .WriteHeader (status )
185+ // A bit hacky way towrite override the existing context
186+ ctx = context .WithValue (ctx , constants .StatusCodeKey , status )
187+ * r = * (r .WithContext (ctx ))
188+ })
139189}
140190
141- func logGenericInternalServiceError (ctx context.Context , w http.ResponseWriter , r * http.Request , err error , logger * log.Logger ) {
142- res := messageResponse {Message : "Something went wrong." }
143- logger .Print (err )
144- if err := marshal .Encode (ctx , w , r , int (http .StatusInternalServerError ), res ); err != nil {
191+ func repondWithErrorMessage (w http.ResponseWriter , r * http.Request , message string , code int , logger logging.Logger ) {
192+ res := messageResponse {
193+ Message : message ,
194+ }
195+ if err := marshal .Encode (r .Context (), w , r , code , res ); err != nil {
145196 writeErrHeader (w , err , logger )
146- return
147197 }
148198}
149199
150- func logFailedEncodingResponse (ctx context.Context , w http.ResponseWriter , r * http.Request , err error , logger * log.Logger ) {
151- logger .Println (err )
200+ func respondWithOpaqueMessage (ctx context.Context , w http.ResponseWriter , r * http.Request , err error , logger logging.Logger ) {
201+ logger .Err .Println (err )
202+ repondWithErrorMessage (w , r , "Something went wrong." , int (http .StatusInternalServerError ), logger )
203+ }
204+
205+ func writeErrHeader (w http.ResponseWriter , err error , logger logging.Logger ) {
206+ w .WriteHeader (http .StatusInternalServerError )
207+ if _ , err := w .Write ([]byte (err .Error ())); err != nil {
208+ logger .Err .Println ("failed writing error" )
209+ }
210+ }
211+
212+ func logFailedEncodingResponse (ctx context.Context , w http.ResponseWriter , r * http.Request , err error , logger logging.Logger ) {
213+ logger .Err .Println (err )
152214 res := messageResponse {"failed to encode response" }
153215 if err := marshal .Encode (ctx , w , r , http .StatusInternalServerError , res ); err != nil {
154216 writeErrHeader (w , err , logger )
155217 }
156218}
219+
220+ func readIntQueryParam (name string , otherwise int , w http.ResponseWriter , r * http.Request , logger logging.Logger ) (int , error ) {
221+ valueString := r .URL .Query ().Get (name )
222+ if valueString == "" {
223+ return otherwise , nil
224+ } else {
225+ value , err := strconv .Atoi (valueString )
226+ if err != nil {
227+ repondWithErrorMessage (w , r , fmt .Sprintf ("Invalid %s: expected integer" , name ), http .StatusBadRequest , logger )
228+ return 0 , err
229+ }
230+ return value , nil
231+ }
232+ }
0 commit comments