infoalloggi-monorepo/apps/backend/storage.go

264 lines
6.4 KiB
Go
Raw Normal View History

2025-08-04 17:45:44 +02:00
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")
})
}