package main import ( "backend/config" "backend/queries" storagehandlers "backend/storage_handlers" "bytes" "fmt" "io" "log" "math/rand" "net/http" "os" "path/filepath" "strconv" "strings" "text/template" "time" "github.com/labstack/echo/v4" "github.com/nfnt/resize" "github.com/nickalie/go-webpbin" ) type Server struct { miogest *MiogestHandler imageManager *ImageRefManager storage storagehandlers.StorageHandler config *config.Config } func NewServer(cfg *config.Config) (*Server, error) { miogest := NewMiogestHandler() if miogest == nil { return nil, fmt.Errorf("error connecting to miogest") } imageManager := NewImageRefManager() if imageManager == nil { return nil, fmt.Errorf("error creating image manager") } storage, err := storagehandlers.NewStorageHandler(cfg.Storage.Strategy) if err != nil { return nil, fmt.Errorf("error creating storage handler: %w", err) } return &Server{ miogest: miogest, imageManager: imageManager, storage: storage, config: cfg, }, nil } func (s *Server) SetupRoutes() *echo.Echo { funcs := map[string]any{ "contains": strings.Contains, "hasPrefix": strings.HasPrefix, "hasSuffix": strings.HasSuffix, "getImage": func(txt string) string { return txt[7:] }, "isWebp": func(txt string) bool { return strings.Contains(txt, "webp") }, } t := &Template{ templates: template.Must(template.New("").Funcs(funcs).ParseGlob("public/views/*.html")), } e := echo.New() e.Renderer = t // MISC e.Static("/static", "public/static") e.GET("/", func(c echo.Context) error { return c.Render(http.StatusOK, "main", map[string]any{ "imageOption": s.config.Images.Enabled, "concurrentImages": s.config.Images.ConcurrentLimit, }) }) e.GET("/test", func(c echo.Context) error { return c.String(http.StatusOK, "OK") }) e.POST("/testpost", func(c echo.Context) error { // Get the form value test := c.FormValue("test") fmt.Println("test:", test) return c.String(http.StatusOK, "OK") }) e.GET("/health", func(c echo.Context) error { h := queries.HealthCheck() return c.String(http.StatusOK, fmt.Sprintf("DB Connection: %v", h)) }) // MIOGEST ROUTES e.GET("/cats", func(c echo.Context) error { var data = s.miogest.GetCaratteristiche() return c.JSONPretty(http.StatusOK, data, " ") }) e.GET("/categ", func(c echo.Context) error { var data = s.miogest.GetCategorie() return c.JSONPretty(http.StatusOK, data, " ") }) e.GET("/miogest", func(c echo.Context) error { data := s.miogest.GetAnnunci() return c.JSONPretty(http.StatusOK, data, " ") }) e.GET("/miogest-sample", func(c echo.Context) error { xml := s.miogest.GetAnnunci() rand := rand.Intn(len(xml.Annuncio)) data := xml.Annuncio[rand] return c.JSONPretty(http.StatusOK, data, " ") }) e.GET("/parse", func(c echo.Context) error { start := time.Now() var data = s.miogest.GetAnnunciParsed(s.imageManager) end := time.Now() fmt.Println("time taken:", end.Sub(start).String()) return c.JSONPretty(http.StatusOK, data, " ") }) e.GET("/update", func(c echo.Context) error { var data = s.miogest.GetAnnunciParsed(s.imageManager) err := queries.AnnunciInsert(data) if err != nil { return c.String(http.StatusInternalServerError, err.Error()) } return c.String(http.StatusOK, "Updated") }) // BACKUP ROUTES e.GET("/upload-backup", func(c echo.Context) error { return c.Render(http.StatusOK, "backup-upload", nil) }) e.POST("/upload-backup", func(c echo.Context) error { file, err := c.FormFile("file") if err != nil { return err } src, err := file.Open() if err != nil { return err } defer src.Close() dst, err := os.Create("bkp.xml") if err != nil { return err } defer dst.Close() if _, err = io.Copy(dst, src); err != nil { return err } return c.HTML(http.StatusOK, fmt.Sprintf("

File %s uploaded successfully.

", file.Filename)) }) e.GET("/bkp", func(c echo.Context) error { data := From_Backup() return c.JSONPretty(http.StatusOK, data, " ") }) e.GET("/parsebkp", func(c echo.Context) error { var data = ParseUpdateBkp(s.miogest) return c.JSONPretty(http.StatusOK, data, " ") }) e.GET("/setbkp", func(c echo.Context) error { var data = ParseUpdateBkp(s.miogest) err := queries.SetFromBkp(data) if err != nil { return c.String(http.StatusInternalServerError, err.Error()) } return c.String(http.StatusOK, "Setted") }) //STORAGE ROUTES e.GET("/storage/list", func(c echo.Context) error { filenames, err := s.storage.List() if err != nil { return c.String(http.StatusInternalServerError, err.Error()) } return c.JSON(http.StatusOK, filenames) }) e.GET("/storage/get/:filename", func(c echo.Context) error { err := TempFolderCleaner(s.config.Storage.TempLimit) if err != nil { return c.String(http.StatusInternalServerError, "Failed to clean temp folder: "+err.Error()) } err = StorageAuth(c) if err != nil { return c.JSON(http.StatusUnauthorized, "Unauthorized: "+err.Error()) } filename := c.Param("filename") path, err := s.storage.Get(filename) if err != nil { return c.String(http.StatusInternalServerError, "Failed to get file: "+err.Error()) } return c.File(path) }) e.POST("/storage/upload", func(c echo.Context) error { fmt.Println("Uploading file") err := StorageAuth(c) if err != nil { return c.String(http.StatusInternalServerError, "Failed to verify Auth: "+err.Error()) } from_admin := c.QueryParam("from_admin") == "true" file, err := c.FormFile("file") if err != nil { return err } fileId, err := Upload(file, from_admin, s.storage) if err != nil { return c.String(http.StatusInternalServerError, "Failed to upload file: "+err.Error()) } return c.String(http.StatusOK, fileId) }) e.DELETE("/storage/delete/:filename", func(c echo.Context) error { err := StorageAuth(c) if err != nil { return c.String(http.StatusInternalServerError, "Failed to verify Auth: "+err.Error()) } filename := c.Param("filename") err = s.storage.Delete(filename) if err != nil { return c.String(http.StatusInternalServerError, "Failed to remove file / file not found: "+err.Error()) } return c.String(http.StatusOK, "File removed") }) e.GET("/storage/remove-expired", func(c echo.Context) error { err := RemoveExpired(s.storage) if err != nil { return c.String(http.StatusInternalServerError, "Failed to remove files / files not found: "+err.Error()) } return c.String(http.StatusOK, "File removed") }) e.GET("/storage/test", func(c echo.Context) error { err := CheckConcruency(s.storage) if err != nil { return c.String(http.StatusInternalServerError, "Failed to check concruency: "+err.Error()) } return c.String(http.StatusOK, "OK") }) //IMAGE ROUTES e.GET("/initcwebp", func(c echo.Context) error { Conversion("test.jpg") return c.String(http.StatusOK, "Cwebp initialized") }) e.GET("/images/list", func(c echo.Context) error { annunci, err := queries.AnnunciGetActive() if err != nil { return c.String(http.StatusInternalServerError, err.Error()) } return c.JSON(http.StatusOK, annunci) }) e.GET("/images/get/:cod/:imgNum", func(c echo.Context) error { cod := c.Param("cod") imgNum := c.Param("imgNum") thumb := c.QueryParam("thumbnail") == "true" widthStr := c.QueryParam("w") //quality := c.QueryParam("q") fmt.Printf("request for %s/%s, thumbnail=%t, w=%s\n", cod, imgNum, thumb, widthStr) prefix := "" if thumb { prefix = "thumbnail-" } Path := filepath.Join(config.Cfg.Images.Path, cod, imgNum, fmt.Sprintf("%s%s_%s.webp", prefix, cod, imgNum)) fmt.Println(Path) if _, err := os.Stat(Path); os.IsNotExist(err) { fmt.Println("Image does not exist:", Path) // Serve a fallback image if the requested image is not found return FallbackImage(c) } if widthStr != "" { width, err := strconv.Atoi(widthStr) if err != nil { fmt.Println("Invalid width parameter:", widthStr) return FallbackImage(c) } f, err := os.Open(Path) if err != nil { return err } img, err := webpbin.Decode(f) if err != nil { fmt.Println("Failed to decode image:", err) return FallbackImage(c) } resizedImg := resize.Resize(uint(width), 0, img, resize.Lanczos3) buffer := new(bytes.Buffer) err = webpbin.Encode(buffer, resizedImg) if err != nil { fmt.Println("Failed to encode image:", err) return FallbackImage(c) } return c.Blob(http.StatusOK, "image/webp", buffer.Bytes()) } return c.File(Path) }) e.GET("/images/resetimages", func(c echo.Context) error { err := s.imageManager.Reset() if err != nil { return c.String(http.StatusInternalServerError, err.Error()) } return c.String(http.StatusOK, "OK") }) e.POST("/image-option-toggle", func(c echo.Context) error { // Toggle the imageOption value s.config.Images.Enabled = !s.config.Images.Enabled // Convert the boolean value to a string and set the environment variable os.Setenv("IMAGEOPTION", strconv.FormatBool(s.config.Images.Enabled)) return c.String(http.StatusOK, "OK") }) return e } func (s *Server) Start() { e := s.SetupRoutes() Conversion("test.jpg") fmt.Println("Image Option: ", s.config.Images.Enabled) fmt.Println("Concurrent Images: ", s.config.Images.ConcurrentLimit) fmt.Println("Server url: http://localhost:" + strconv.Itoa(s.config.Server.Port)) e.Logger.Info(e.Start(":" + strconv.Itoa(s.config.Server.Port))) } func main() { cfg := config.Load() server, err := NewServer(cfg) if err != nil { log.Fatalf("Server initialization error: %v", err) } server.Start() } type Template struct { templates *template.Template } func (t *Template) Render(w io.Writer, name string, data any, c echo.Context) error { return t.templates.ExecuteTemplate(w, name, data) }