|
@@ -0,0 +1,129 @@
|
|
|
+// Ниже реализован сервис бронирования номеров в отеле. В предметной области
|
|
|
+// выделены два понятия: Order — заказ, который включает в себя даты бронирования
|
|
|
+// и контакты пользователя, и RoomAvailability — количество свободных номеров на
|
|
|
+// конкретный день.
|
|
|
+//
|
|
|
+// Задание:
|
|
|
+// - провести рефакторинг кода с выделением слоев и абстракций
|
|
|
+// - применить best-practices там где это имеет смысл
|
|
|
+// - исправить имеющиеся в реализации логические и технические ошибки и неточности
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "encoding/json"
|
|
|
+ "errors"
|
|
|
+ "fmt"
|
|
|
+ "log"
|
|
|
+ "net/http"
|
|
|
+ "os"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+type Order struct {
|
|
|
+ HotelID string `json:"hotel_id"`
|
|
|
+ RoomID string `json:"room_id"`
|
|
|
+ UserEmail string `json:"email"`
|
|
|
+ From time.Time `json:"from"`
|
|
|
+ To time.Time `json:"to"`
|
|
|
+}
|
|
|
+
|
|
|
+var Orders = []Order{}
|
|
|
+
|
|
|
+type RoomAvailability struct {
|
|
|
+ HotelID string `json:"hotel_id"`
|
|
|
+ RoomID string `json:"room_id"`
|
|
|
+ Date time.Time `json:"date"`
|
|
|
+ Quota int `json:"quota"`
|
|
|
+}
|
|
|
+
|
|
|
+var Availability = []RoomAvailability{
|
|
|
+ {"reddison", "lux", date(2024, 1, 1), 1},
|
|
|
+ {"reddison", "lux", date(2024, 1, 2), 1},
|
|
|
+ {"reddison", "lux", date(2024, 1, 3), 1},
|
|
|
+ {"reddison", "lux", date(2024, 1, 4), 1},
|
|
|
+ {"reddison", "lux", date(2024, 1, 5), 0},
|
|
|
+}
|
|
|
+
|
|
|
+func main() {
|
|
|
+ mux := http.NewServeMux()
|
|
|
+ mux.HandleFunc("/orders", createOrder)
|
|
|
+
|
|
|
+ LogInfo("Server listening on localhost:8080")
|
|
|
+ err := http.ListenAndServe(":8080", mux)
|
|
|
+ if errors.Is(err, http.ErrServerClosed) {
|
|
|
+ LogInfo("Server closed")
|
|
|
+ } else if err != nil {
|
|
|
+ LogErrorf("Server failed: %s", err)
|
|
|
+ os.Exit(1)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func createOrder(w http.ResponseWriter, r *http.Request) {
|
|
|
+ var newOrder Order
|
|
|
+ json.NewDecoder(r.Body).Decode(&newOrder)
|
|
|
+
|
|
|
+ daysToBook := daysBetween(newOrder.From, newOrder.To)
|
|
|
+
|
|
|
+ unavailableDays := make(map[time.Time]struct{})
|
|
|
+ for _, day := range daysToBook {
|
|
|
+ unavailableDays[day] = struct{}{}
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, dayToBook := range daysToBook {
|
|
|
+ for i, availability := range Availability {
|
|
|
+ if !availability.Date.Equal(dayToBook) || availability.Quota < 1 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ availability.Quota -= 1
|
|
|
+ Availability[i] = availability
|
|
|
+ delete(unavailableDays, dayToBook)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(unavailableDays) != 0 {
|
|
|
+ http.Error(w, "Hotel room is not available for selected dates", http.StatusInternalServerError)
|
|
|
+ LogErrorf("Hotel room is not available for selected dates:\n%v\n%v", newOrder, unavailableDays)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ Orders = append(Orders, newOrder)
|
|
|
+
|
|
|
+ w.Header().Set("Content-Type", "application/json")
|
|
|
+ w.WriteHeader(http.StatusCreated)
|
|
|
+ json.NewEncoder(w).Encode(newOrder)
|
|
|
+
|
|
|
+ LogInfo("Order successfully created: %v", newOrder)
|
|
|
+}
|
|
|
+
|
|
|
+func daysBetween(from time.Time, to time.Time) []time.Time {
|
|
|
+ if from.After(to) {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ days := make([]time.Time, 0)
|
|
|
+ for d := toDay(from); !d.After(toDay(to)); d = d.AddDate(0, 0, 1) {
|
|
|
+ days = append(days, d)
|
|
|
+ }
|
|
|
+
|
|
|
+ return days
|
|
|
+}
|
|
|
+
|
|
|
+func toDay(timestamp time.Time) time.Time {
|
|
|
+ return time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), 0, 0, 0, 0, time.UTC)
|
|
|
+}
|
|
|
+
|
|
|
+func date(year, month, day int) time.Time {
|
|
|
+ return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
|
|
|
+}
|
|
|
+
|
|
|
+var logger = log.Default()
|
|
|
+
|
|
|
+func LogErrorf(format string, v ...any) {
|
|
|
+ msg := fmt.Sprintf(format, v...)
|
|
|
+ logger.Printf("[Error]: %s\n", msg)
|
|
|
+}
|
|
|
+
|
|
|
+func LogInfo(format string, v ...any) {
|
|
|
+ msg := fmt.Sprintf(format, v...)
|
|
|
+ logger.Printf("[Info]: %s\n", msg)
|
|
|
+}
|