package morningalarm import ( "net/http" "os" "path/filepath" "time" "github.com/gin-gonic/gin" ) type Device struct { Name string `json:"name"` ID string `json:"id"` } type AlarmPatch struct { Disabled bool `json:"disabled"` } func (ma *MorningAlarm) setupWebserver() { // TODO: This is stupid execPath, err := os.Executable() if err != nil { panic("Shit") } // Serve static files ma.ro.NoRoute(gin.WrapH(http.FileServer(http.Dir(filepath.Join(filepath.Dir(execPath), "public"))))) // Create a new alarm ma.ro.POST("/api/alarm", func(c *gin.Context) { var body alarm if err := c.BindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Check if alarm name is valid if body.Name == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Alarm name cannot be empty"}) return } // Chekc if alarm name contains only alphanumeric characters for _, char := range body.Name { if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9')) { c.JSON(http.StatusBadRequest, gin.H{"error": "Alarm name must contain only alphanumeric characters"}) return } } _, err := ma.addAlarm(body.Time, body.Name) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } else { err = ma.saveAlarmsToFile() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.Header("Location", "/api/alarm/"+body.Name) c.JSON(http.StatusCreated, gin.H{}) } }) // Get an alarm ma.ro.GET("/api/alarm/:id", func(c *gin.Context) { id := c.Param("id") alarm := ma.getAlarm(id) if alarm == nil { c.JSON(http.StatusNotFound, gin.H{"error": "Alarm not found"}) return } c.JSON(http.StatusOK, alarm) }) // Get all alarms ma.ro.GET("/api/alarm", func(c *gin.Context) { c.JSON(http.StatusOK, ma.alarms) }) // Delete an alarm ma.ro.DELETE("/api/alarm/:id", func(c *gin.Context) { id := c.Param("id") if ma.removeAlarm(id) { err := ma.saveAlarmsToFile() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{}) } else { c.JSON(http.StatusNotFound, gin.H{"error": "Alarm not found"}) } }) // Get info about the server and the alarms ma.ro.GET("/api/info", func(c *gin.Context) { spotifyUser, err := ma.sp.CurrentUser(c) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } currentTime := time.Now() zone, offset := currentTime.Zone() nextIn := ma.nextAlarm() response := gin.H{ "spotifyUser": spotifyUser.User.DisplayName, "serverTime": currentTime.Format("15:04:05"), "timezone": zone, "timezoneOffsetInH": offset / 60 / 60, "alarms": len(ma.cr.Entries()), "wakeupPlaylist": ma.config.PlaylistID, "deviceName": ma.config.DeviceName, } if nextIn != nil { response["nextAlarmAt"] = nextIn.String() response["nextAlarmIn"] = nextIn.Sub(currentTime).String() } c.JSON(http.StatusOK, response) }) // Get available spotify devices ma.ro.GET("/api/device", func(c *gin.Context) { devices, err := ma.getAvailableDevices() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, devices) }) // Trigger an alarm immediately ma.ro.POST("/api/trigger", func(c *gin.Context) { if _, err := ma.playWakeUpMusic(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{}) }) ma.ro.PATCH("/api/alarm/:id", func(c *gin.Context) { id := c.Param("id") alarm := ma.getAlarm(id) if alarm == nil { c.JSON(http.StatusNotFound, gin.H{"error": "Alarm not found"}) return } var body AlarmPatch if err := c.BindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } ma.setDisabled(id, body.Disabled) err := ma.saveAlarmsToFile() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{}) }) }