package main import ( "errors" "fmt" "log" "mime/multipart" "net/http" "os" "path/filepath" "slices" "time" "github.com/georgysavva/scany/v2/pgxscan" "github.com/labstack/echo/v4" ) // Upload saves a file to the storage directory. func Upload(file *multipart.FileHeader, from_admin bool) (string, error) { log.Printf("Uploading file: %s", file.Filename) log.Printf("DB instance: %v", Db.Dbpool) var newId string err := Db.Dbpool.QueryRow(Db.Ctx, "INSERT INTO public.storageindex(filename)VALUES (null) RETURNING id;").Scan(&newId) if err != nil { log.Fatal(err) } if newId == "" { return "", errors.New("failed to create new id") } err = Storage.Add(file, newId) if err != nil { return "", err } //if !from_admin , expires in 60 days var expiresAt *time.Time if !from_admin { exp := time.Now().AddDate(0, 2, 0) // 60 days expiresAt = &exp } else { expiresAt = nil } _, err = Db.Dbpool.Exec(Db.Ctx, "UPDATE public.storageindex set filename= $2, ext = $3, from_admin = $4, expires_at = $5 where id = $1", newId, file.Filename, filepath.Ext(file.Filename), from_admin, expiresAt) if err != nil { log.Fatal(err) } _, err = Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.storageindex WHERE filename IS NULL") if err != nil { log.Fatal(err) } return newId, nil } func StorageAuth(c echo.Context) error { token := c.Request().URL.Query().Get("token") if len(token) < 1 { return c.String(http.StatusUnauthorized, "Unauthorized") } var tk []string err := pgxscan.Select(Db.Ctx, Db.Dbpool, &tk, "SELECT token FROM public.temp_tokens WHERE token = $1 and elapse > $2", token, time.Now()) if err != nil { return err } if tk == nil { return errors.New("invalid token") } _, err = Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.temp_tokens WHERE elapse < $1", time.Now()) if err != nil { return err } return nil } func RemoveFromDb(ref string) error { _, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.storageindex WHERE id = $1", ref) if err != nil { return err } return nil } func RemoveExpired() error { //get all expired files var items_db []Storageindex err := pgxscan.Select(Db.Ctx, Db.Dbpool, &items_db, "SELECT * FROM public.storageindex WHERE expires_at is not null and expires_at < NOW()") if err != nil { return fmt.Errorf("failed to get expired items: %w", err) } for _, item := range items_db { formattedName := item.Id + item.Ext err = Storage.Delete(formattedName) if err != nil { return fmt.Errorf("failed to delete file %s: %w", formattedName, err) } err = RemoveFromDb(item.Id) if err != nil { return fmt.Errorf("failed to remove item from db: %w", err) } } return nil } func CheckConcruency() error { var items_db []struct { Id string Ext string } err := pgxscan.Select(Db.Ctx, Db.Dbpool, &items_db, "SELECT id, ext FROM public.storageindex") if err != nil { return err } files, err := Storage.List() if err != nil { return err } if len(items_db) != len(files) { var founds []string for _, item := range items_db { formattedName := item.Id + item.Ext found := slices.Contains(files, formattedName) if found { founds = append(founds, formattedName) } else { _, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.storageindex WHERE id = $1", item.Id) if err != nil { return err } } } for _, file := range files { found := slices.Contains(founds, file) if !found { fmt.Printf("File %v not found in db\n", file) //TODO decidere se eliminarlo } } } return nil } func TempFolderCleaner() error { files, err := os.ReadDir(filepath.Join(storagepath, "/tmp/")) if err != nil { return err } fmt.Println("Temp files: ", len(files)) if len(files) >= tempStorageLimit { var oldest_Time time.Time var oldest_File string for _, file := range files { file_infos, err := file.Info() if err != nil { return err } if oldest_Time.IsZero() || file_infos.ModTime().Before(oldest_Time) { oldest_Time = file_infos.ModTime() oldest_File = file.Name() } } err = os.Remove(filepath.Join(storagepath, "/tmp/", oldest_File)) if err != nil { return err } } return nil } // ROUTES func StorageRoutes(e *echo.Echo) { e.GET("/storage/list", func(c echo.Context) error { filenames, err := 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() 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 := 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) 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 = 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() 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() if err != nil { return c.String(http.StatusInternalServerError, "Failed to check concruency: "+err.Error()) } return c.String(http.StatusOK, "OK") }) }