internalhttp.*Server.Start   A
last analyzed

Complexity

Conditions 4

Size

Total Lines 39
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 28
dl 0
loc 39
c 0
b 0
f 0
rs 9.208
nop 1
1
package internalhttp
2
3
import (
4
	"context"
5
	"encoding/json"
6
	"fmt"
7
	"io"
8
	"net"
9
	"net/http"
10
	"time"
11
12
	_ "github.com/cronnoss/tickets-api/docs" // nolint: revive
13
	"github.com/cronnoss/tk-api/internal/common/srv"
14
	"github.com/cronnoss/tk-api/internal/model"
15
	"github.com/cronnoss/tk-api/internal/server"
16
	"github.com/cronnoss/tk-api/internal/storage/models"
17
	"github.com/gorilla/mux"
18
	httpSwagger "github.com/swaggo/http-swagger"
19
)
20
21
type ctxKeyID int
22
23
const (
24
	KeyLoggerID ctxKeyID = iota
25
)
26
27
type Server struct {
28
	srv  http.Server
29
	app  server.Application
30
	log  Logger
31
	host string
32
	port string
33
}
34
35
type Logger interface {
36
	Fatalf(format string, a ...interface{})
37
	Errorf(format string, a ...interface{})
38
	Warningf(format string, a ...interface{})
39
	Infof(format string, a ...interface{})
40
	Debugf(format string, a ...interface{})
41
}
42
43
func NewServer(log Logger, app server.Application, host, port string) *Server {
44
	return &Server{log: log, app: app, host: host, port: port}
45
}
46
47
func (s *Server) helperDecode(stream io.ReadCloser, w http.ResponseWriter, data interface{}) error { // nolint: unused
48
	decoder := json.NewDecoder(stream)
49
	if err := decoder.Decode(&data); err != nil {
50
		s.log.Errorf("Can't decode json:%v\n", err)
51
		w.WriteHeader(http.StatusBadRequest)
52
		w.Write([]byte(fmt.Sprintf("{\"error\": \"Can't decode json:%v\"}\n", err)))
53
		return err
54
	}
55
	return nil
56
}
57
58
// @Summary Get shows
59
// @Tags shows
60
// @Description Get shows from remote API and store them in the local service
61
// @ID get-shows
62
// @Accept  json
63
// @Produce  json
64
// @Success 200 {object} ShowListResponse
65
// @Failure 400,404 {object} server.ErrorResponse
66
// @Failure 500 {object} server.ErrorResponse
67
// @Router /shows [get].
68
func (s *Server) GetShows(w http.ResponseWriter, r *http.Request) {
69
	// Step 1: Make a GET request to the remote API
70
	remoteURL := "https://leadbook.ru/test-task-api/shows"
71
	req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, remoteURL, nil)
72
	if err != nil {
73
		srv.RespondWithError(fmt.Errorf("failed to create request: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
74
		return
75
	}
76
	resp, err := http.DefaultClient.Do(req)
77
	if err != nil {
78
		srv.RespondWithError(fmt.Errorf("failed to do request: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
79
		return
80
	}
81
	defer resp.Body.Close()
82
83
	// Step 2: Decode the response
84
	body, err := io.ReadAll(resp.Body)
85
	if err != nil {
86
		srv.RespondWithError(fmt.Errorf("failed to read response body: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
87
		return
88
	}
89
90
	var showListResponse model.ShowListResponse
91
	if err := json.Unmarshal(body, &showListResponse); err != nil {
92
		srv.RespondWithError(fmt.Errorf("failed to decode response: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
93
		return
94
	}
95
96
	// Step 3: Iterate over shows and store them in the local service
97
	for _, show := range showListResponse.Response {
98
		if err := showListResponse.ShowListResponseValidate(); err != nil {
99
			srv.RespondWithError(fmt.Errorf("failed to validate response: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
100
			return
101
		}
102
103
		_, err := s.app.CreateShow(r.Context(), models.Show{
104
			ID:   show.ID,
105
			Name: show.Name,
106
		})
107
		if err != nil {
108
			srv.RespondWithError(fmt.Errorf("failed to create show: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
109
			return
110
		}
111
	}
112
113
	srv.RespondOK(showListResponse.Response, w, r)
114
}
115
116
// @Summary Get events
117
// @Tags events
118
// @Description Get events by show ID
119
// @ID get-events
120
// @Accept  json
121
// @Produce  json
122
// @Param id path int true "show ID"
123
// @Success 200 {object} EventListResponse
124
// @Failure 400,404 {object} server.ErrorResponse
125
// @Failure 500 {object} server.ErrorResponse
126
// @Router /shows/{id}/events [get].
127
func (s *Server) GetEvents(w http.ResponseWriter, r *http.Request) {
128
	// Step 1: Make a GET request to the remote API
129
	vars := mux.Vars(r)
130
	id := vars["id"]
131
	remoteURL := "https://leadbook.ru/test-task-api/shows/" + id + "/events"
132
	req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, remoteURL, nil)
133
	if err != nil {
134
		srv.RespondWithError(fmt.Errorf("failed to create request: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
135
		return
136
	}
137
	resp, err := http.DefaultClient.Do(req)
138
	if err != nil {
139
		srv.RespondWithError(fmt.Errorf("failed to do request: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
140
		return
141
	}
142
	defer resp.Body.Close()
143
144
	// Step 2: Decode the response
145
	body, err := io.ReadAll(resp.Body)
146
	if err != nil {
147
		srv.RespondWithError(fmt.Errorf("failed to read response body: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
148
		return
149
	}
150
151
	var eventListResponse model.EventListResponse
152
	if err := json.Unmarshal(body, &eventListResponse); err != nil {
153
		srv.RespondWithError(fmt.Errorf("failed to decode response: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
154
		return
155
	}
156
157
	// Step 3: Iterate over events and store them in the local service
158
	for _, event := range eventListResponse.Response {
159
		if err := eventListResponse.EventListResponseValidate(); err != nil {
160
			srv.RespondWithError(fmt.Errorf("failed to validate response: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
161
			return
162
		}
163
164
		_, err := s.app.CreateEvent(r.Context(), models.Event{
165
			ID:     event.ID,
166
			ShowID: event.ShowID,
167
			Date:   event.Date,
168
		})
169
		if err != nil {
170
			srv.RespondWithError(fmt.Errorf("failed to create event: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
171
			return
172
		}
173
	}
174
175
	srv.RespondOK(eventListResponse.Response, w, r)
176
}
177
178
// @Summary Get places
179
// @Tags places
180
// @Description Get places by event ID
181
// @ID get-places
182
// @Accept  json
183
// @Produce  json
184
// @Param id path int true "event ID"
185
// @Success 200 {object} PlaceListResponse
186
// @Failure 400,404 {object} server.ErrorResponse
187
// @Failure 500 {object} server.ErrorResponse
188
// @Router /events/{id}/places [get].
189
func (s *Server) GetPlaces(w http.ResponseWriter, r *http.Request) {
190
	// Step 1: Make a GET request to the remote API
191
	vars := mux.Vars(r)
192
	id := vars["id"]
193
	remoteURL := "https://leadbook.ru/test-task-api/events/" + id + "/places"
194
	req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, remoteURL, nil)
195
	if err != nil {
196
		srv.RespondWithError(fmt.Errorf("failed to create request: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
197
		return
198
	}
199
	resp, err := http.DefaultClient.Do(req)
200
	if err != nil {
201
		srv.RespondWithError(fmt.Errorf("failed to do request: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
202
		return
203
	}
204
	defer resp.Body.Close()
205
206
	// Step 2: Decode the response
207
	body, err := io.ReadAll(resp.Body)
208
	if err != nil {
209
		srv.RespondWithError(fmt.Errorf("failed to read response body: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
210
		return
211
	}
212
213
	var placeListResponse model.PlaceListResponse
214
	if err := json.Unmarshal(body, &placeListResponse); err != nil {
215
		srv.RespondWithError(fmt.Errorf("failed to decode response: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
216
		return
217
	}
218
219
	// Step 3: Iterate over places and store them in the local service
220
	for _, place := range placeListResponse.Response {
221
		if err := placeListResponse.PlaceListResponseValidate(); err != nil {
222
			srv.RespondWithError(fmt.Errorf("failed to validate response: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
223
			return
224
		}
225
226
		_, err := s.app.CreatePlace(r.Context(), models.Place{
227
			ID:          place.ID,
228
			X:           place.X,
229
			Y:           place.Y,
230
			Width:       place.Width,
231
			Height:      place.Height,
232
			IsAvailable: place.IsAvailable,
233
		})
234
		if err != nil {
235
			srv.RespondWithError(fmt.Errorf("failed to create place: %w", err), w, r)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
236
			return
237
		}
238
	}
239
240
	srv.RespondOK(placeListResponse.Response, w, r)
241
}
242
243
// @title Ticket API
244
// @version 1
245
// @description API Server for remote Tickets Application.
246
func (s *Server) Start(ctx context.Context) error {
247
	addr := net.JoinHostPort(s.host, s.port)
248
	midLogger := NewMiddlewareLogger()
249
250
	router := mux.NewRouter()
251
252
	router.Handle("/healthz", midLogger.setCommonHeadersMiddleware(
253
		midLogger.loggingMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
254
			w.WriteHeader(http.StatusOK)
255
			w.Write([]byte("OK healthz\n"))
256
		}))))
257
258
	router.Handle("/readiness", midLogger.setCommonHeadersMiddleware(
259
		midLogger.loggingMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
260
			w.WriteHeader(http.StatusOK)
261
			w.Write([]byte("OK readiness\n"))
262
		}))))
263
264
	router.PathPrefix("/swagger/").Handler(httpSwagger.WrapHandler)
265
266
	router.Handle("/shows", midLogger.setCommonHeadersMiddleware(
267
		midLogger.loggingMiddleware(http.HandlerFunc(s.GetShows))))
268
	router.Handle("/shows/{id:[0-9]+}/events", midLogger.setCommonHeadersMiddleware(
269
		midLogger.loggingMiddleware(http.HandlerFunc(s.GetEvents))))
270
	router.Handle("/events/{id:[0-9]+}/places", midLogger.setCommonHeadersMiddleware(
271
		midLogger.loggingMiddleware(http.HandlerFunc(s.GetPlaces))))
272
273
	s.srv = http.Server{
274
		Addr:              addr,
275
		Handler:           router,
276
		ReadHeaderTimeout: 2 * time.Second,
277
		BaseContext: func(_ net.Listener) context.Context {
278
			bCtx := context.WithValue(ctx, KeyLoggerID, s.log)
279
			return bCtx
280
		},
281
	}
282
283
	s.log.Infof("http server started on %s:%s\n", s.host, s.port)
284
	return s.srv.ListenAndServe()
285
}
286
287
func (s *Server) Stop(ctx context.Context) error {
288
	if err := s.srv.Shutdown(ctx); err != nil {
289
		return err
290
	}
291
	s.log.Infof("http server shutdown\n")
292
	return nil
293
}
294