- Updated `fs.go` and `minio.go` to replace hardcoded storage paths with values from the new config package. - Introduced a new `config` package to manage application configuration, including database and storage settings. - Removed the `models.go` file from `typedefs` as it was no longer needed. - Added a new `config.go` file to handle loading environment variables and application settings. - Created a new `types.go` file in `typedefs` to define storage strategies. - Updated `storage_handler.go` to utilize the new configuration structure.
365 lines
9.7 KiB
Go
365 lines
9.7 KiB
Go
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("<p>File %s uploaded successfully.</p>", 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)
|
|
}
|