package api import ( "encoding/json" "fmt" "log" "net/http" "os" "path" "strconv" "strings" "time" "github.com/gin-gonic/gin" ) var DashboardDir string func declarePublicRoutes(router *gin.RouterGroup) { router.GET("/public/", listPublic) router.GET("/public/:sid", getPublic) router.DELETE("/public/:sid", deletePublic) router.PUT("/public/:sid", savePublic) } type FICPublicScene struct { Type string `json:"type"` Params map[string]interface{} `json:"params"` } type FICPublicDisplay struct { Scenes []FICPublicScene `json:"scenes"` Side []FICPublicScene `json:"side"` CustomCountdown map[string]interface{} `json:"customCountdown"` HideEvents bool `json:"hideEvents"` HideCountdown bool `json:"hideCountdown"` HideCarousel bool `json:"hideCarousel"` PropagationTime *time.Time `json:"propagationTime,omitempty"` } func InitDashboardPresets(dir string) error { return nil } func readPublic(path string) (FICPublicDisplay, error) { var s FICPublicDisplay if fd, err := os.Open(path); err != nil { return s, err } else { defer fd.Close() jdec := json.NewDecoder(fd) if err := jdec.Decode(&s); err != nil { return s, err } return s, nil } } func savePublicTo(path string, s FICPublicDisplay) error { if fd, err := os.Create(path); err != nil { return err } else { defer fd.Close() jenc := json.NewEncoder(fd) if err := jenc.Encode(s); err != nil { return err } return nil } } type DashboardFiles struct { Presets []string `json:"presets"` Nexts []*NextDashboardFile `json:"nexts"` } type NextDashboardFile struct { Name string `json:"name"` Screen int `json:"screen"` Date time.Time `json:"date"` } func listPublic(c *gin.Context) { files, err := os.ReadDir(DashboardDir) if err != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) return } var ret DashboardFiles for _, file := range files { if strings.HasPrefix(file.Name(), "preset-") { ret.Presets = append(ret.Presets, strings.TrimSuffix(strings.TrimPrefix(file.Name(), "preset-"), ".json")) continue } if !strings.HasPrefix(file.Name(), "public") || len(file.Name()) < 18 { continue } ts, err := strconv.ParseInt(file.Name()[8:18], 10, 64) if err == nil { s, _ := strconv.Atoi(file.Name()[6:7]) ret.Nexts = append(ret.Nexts, &NextDashboardFile{ Name: file.Name()[6:18], Screen: s, Date: time.Unix(ts, 0), }) } } c.JSON(http.StatusOK, ret) } func getPublic(c *gin.Context) { if strings.Contains(c.Params.ByName("sid"), "/") { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "sid cannot contains /"}) return } filename := fmt.Sprintf("public%s.json", c.Params.ByName("sid")) if strings.HasPrefix(c.Params.ByName("sid"), "preset-") { filename = fmt.Sprintf("%s.json", c.Params.ByName("sid")) } if _, err := os.Stat(path.Join(DashboardDir, filename)); !os.IsNotExist(err) { p, err := readPublic(path.Join(DashboardDir, filename)) if err != nil { log.Println("Unable to readPublic in getPublic:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene retrieval."}) return } c.JSON(http.StatusOK, p) return } c.JSON(http.StatusOK, FICPublicDisplay{Scenes: []FICPublicScene{}, Side: []FICPublicScene{}}) } func deletePublic(c *gin.Context) { if strings.Contains(c.Params.ByName("sid"), "/") { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "sid cannot contains /"}) return } filename := fmt.Sprintf("public%s.json", c.Params.ByName("sid")) if strings.HasPrefix(c.Params.ByName("sid"), "preset-") { filename = fmt.Sprintf("%s.json", c.Params.ByName("sid")) } if len(filename) == 12 { if err := savePublicTo(path.Join(DashboardDir, filename), FICPublicDisplay{}); err != nil { log.Println("Unable to deletePublic:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene deletion."}) return } } else { if err := os.Remove(path.Join(DashboardDir, filename)); err != nil { log.Println("Unable to deletePublic:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene deletion."}) return } } c.JSON(http.StatusOK, FICPublicDisplay{Scenes: []FICPublicScene{}, Side: []FICPublicScene{}}) } func savePublic(c *gin.Context) { if strings.Contains(c.Params.ByName("sid"), "/") { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "sid cannot contains /"}) return } var scenes FICPublicDisplay err := c.ShouldBindJSON(&scenes) if err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) return } filename := fmt.Sprintf("public%s.json", c.Params.ByName("sid")) if c.Request.URL.Query().Has("t") { t, err := time.Parse(time.RFC3339, c.Request.URL.Query().Get("t")) if err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) return } filename = fmt.Sprintf("public%s-%d.json", c.Params.ByName("sid"), t.Unix()) } else if c.Request.URL.Query().Has("p") { filename = fmt.Sprintf("preset-%s.json", c.Request.URL.Query().Get("p")) } if err := savePublicTo(path.Join(DashboardDir, filename), scenes); err != nil { log.Println("Unable to savePublicTo:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene saving."}) return } c.JSON(http.StatusOK, scenes) }