Refactor image handling in announcement components
- Updated CardAnnuncio and related components to use a new images structure instead of url_immagini. - Removed references to url_immagini and replaced them with images array containing img and thumb properties. - Adjusted API responses to include images in the new format. - Modified middleware and storage service to accommodate new image handling. - Cleaned up unused code and schemas related to old image references. - Updated Docker configuration for storage service.
This commit is contained in:
parent
7f93bdeb98
commit
208deeac28
46 changed files with 880 additions and 1621 deletions
|
|
@ -7,7 +7,7 @@ tmp_dir = "tmp"
|
||||||
bin = "tmp\\main.exe"
|
bin = "tmp\\main.exe"
|
||||||
cmd = "go build -o ./tmp/main.exe ."
|
cmd = "go build -o ./tmp/main.exe ."
|
||||||
delay = 1000
|
delay = 1000
|
||||||
exclude_dir = ["assets", "tmp", "vendor", "testdata", "images", ".bin", "storage"]
|
exclude_dir = ["assets", "tmp", "vendor", "testdata", "images", ".bin"]
|
||||||
exclude_file = []
|
exclude_file = []
|
||||||
exclude_regex = ["_test.go"]
|
exclude_regex = ["_test.go"]
|
||||||
exclude_unchanged = false
|
exclude_unchanged = false
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
images
|
images
|
||||||
storage
|
|
||||||
tmp
|
tmp
|
||||||
vscode
|
vscode
|
||||||
.vscode
|
.vscode
|
||||||
|
|
|
||||||
1
apps/backend/.gitignore
vendored
1
apps/backend/.gitignore
vendored
|
|
@ -1,7 +1,6 @@
|
||||||
tmp
|
tmp
|
||||||
images
|
images
|
||||||
bkp.xml
|
bkp.xml
|
||||||
storage
|
|
||||||
.bin
|
.bin
|
||||||
web.config
|
web.config
|
||||||
.env
|
.env
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ ARG PGHOST
|
||||||
ARG PGPORT
|
ARG PGPORT
|
||||||
ARG IMAGEOPTION
|
ARG IMAGEOPTION
|
||||||
ARG CONCURRENT_IMAGES
|
ARG CONCURRENT_IMAGES
|
||||||
ARG STORAGE_STRATEGY
|
ARG STORAGE_URL
|
||||||
ARG MINIO_ENDPOINT
|
ARG MINIO_ENDPOINT
|
||||||
ARG MINIO_PORT
|
ARG MINIO_PORT
|
||||||
ARG MINIO_ROOT_USER
|
ARG MINIO_ROOT_USER
|
||||||
|
|
@ -42,7 +42,7 @@ ENV POSTGRES_USER=$POSTGRES_USER
|
||||||
ENV POSTGRES_PASSWORD=$POSTGRES_PASSWORD
|
ENV POSTGRES_PASSWORD=$POSTGRES_PASSWORD
|
||||||
ENV IMAGEOPTION=$IMAGEOPTION
|
ENV IMAGEOPTION=$IMAGEOPTION
|
||||||
ENV CONCURRENT_IMAGES=$CONCURRENT_IMAGES
|
ENV CONCURRENT_IMAGES=$CONCURRENT_IMAGES
|
||||||
ENV STORAGE_STRATEGY=$STORAGE_STRATEGY
|
ENV STORAGE_URL=$STORAGE_URL
|
||||||
ENV MINIO_ENDPOINT=$MINIO_ENDPOINT
|
ENV MINIO_ENDPOINT=$MINIO_ENDPOINT
|
||||||
ENV MINIO_PORT=$MINIO_PORT
|
ENV MINIO_PORT=$MINIO_PORT
|
||||||
ENV MINIO_ROOT_USER=$MINIO_ROOT_USER
|
ENV MINIO_ROOT_USER=$MINIO_ROOT_USER
|
||||||
|
|
@ -58,10 +58,8 @@ COPY --from=builder /app/scripts /app/scripts
|
||||||
RUN chmod +x /app/scripts/*.sh
|
RUN chmod +x /app/scripts/*.sh
|
||||||
|
|
||||||
#COPY --from=builder /app/.env /app/.env
|
#COPY --from=builder /app/.env /app/.env
|
||||||
# create images and storage folder
|
# create images
|
||||||
RUN mkdir images
|
RUN mkdir images
|
||||||
RUN mkdir storage
|
|
||||||
RUN mkdir storage/tmp
|
|
||||||
|
|
||||||
RUN apk add curl
|
RUN apk add curl
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"backend/typesdefs"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -28,18 +27,8 @@ type Config struct {
|
||||||
Path string
|
Path string
|
||||||
}
|
}
|
||||||
Storage struct {
|
Storage struct {
|
||||||
Strategy typesdefs.StorageStrategy
|
|
||||||
Path string
|
|
||||||
TempLimit int
|
|
||||||
Minio struct {
|
|
||||||
URL string
|
|
||||||
RootUser string
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
GOFS struct {
|
|
||||||
Endpoint string
|
Endpoint string
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Server struct {
|
Server struct {
|
||||||
Port int
|
Port int
|
||||||
|
|
@ -78,28 +67,7 @@ func Load() *Config {
|
||||||
Cfg.Images.Path = filepath.Join(pwd, "images")
|
Cfg.Images.Path = filepath.Join(pwd, "images")
|
||||||
|
|
||||||
// Storage config
|
// Storage config
|
||||||
Cfg.Storage.Path = filepath.Join(pwd, "storage")
|
Cfg.Storage.Endpoint = filepath.Join(pwd, "STORAGE_URL")
|
||||||
Cfg.Storage.TempLimit = getEnvAsInt("TEMP_STORAGE_LIMIT", 5)
|
|
||||||
strategy := getEnv("STORAGE_STRATEGY", "fs")
|
|
||||||
|
|
||||||
switch strategy {
|
|
||||||
case "s3", "minio":
|
|
||||||
Cfg.Storage.Minio.URL = getEnv("MINIO_URL", "")
|
|
||||||
Cfg.Storage.Minio.RootUser = getEnv("MINIO_ROOT_USER", "")
|
|
||||||
Cfg.Storage.Minio.Password = getEnv("MINIO_ROOT_PASSWORD", "")
|
|
||||||
|
|
||||||
if Cfg.Storage.Minio.URL == "" || Cfg.Storage.Minio.RootUser == "" || Cfg.Storage.Minio.Password == "" {
|
|
||||||
fmt.Println("Error: MINIO_ROOT_USER, MINIO_ROOT_PASSWORD and MINIO_URL must be set")
|
|
||||||
Cfg.Storage.Strategy = typesdefs.FS
|
|
||||||
} else {
|
|
||||||
Cfg.Storage.Strategy = typesdefs.S3
|
|
||||||
}
|
|
||||||
case "gofs":
|
|
||||||
Cfg.Storage.GOFS.Endpoint = getEnv("GOFS_ENDPOINT", "localhost:8080")
|
|
||||||
Cfg.Storage.Strategy = typesdefs.GOFS
|
|
||||||
default:
|
|
||||||
Cfg.Storage.Strategy = typesdefs.FS
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server config
|
// Server config
|
||||||
Cfg.Server.Port = getEnvAsInt("PORT", 1323)
|
Cfg.Server.Port = getEnvAsInt("PORT", 1323)
|
||||||
|
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
services:
|
|
||||||
minio:
|
|
||||||
image: minio/minio
|
|
||||||
expose:
|
|
||||||
- "9000"
|
|
||||||
- "9001"
|
|
||||||
volumes:
|
|
||||||
- minio_storage:/data
|
|
||||||
networks:
|
|
||||||
- dokploy-network
|
|
||||||
environment:
|
|
||||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
|
||||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
|
||||||
command: server --console-address ":9001" /data
|
|
||||||
healthcheck:
|
|
||||||
test: [ "CMD", "curl", "-f", "http://minio:9000/minio/health/live" ]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 5
|
|
||||||
|
|
||||||
tiles:
|
|
||||||
image: eqalpha/keydb:latest
|
|
||||||
volumes:
|
|
||||||
- tiles-data:/data
|
|
||||||
networks:
|
|
||||||
- dokploy-network
|
|
||||||
expose:
|
|
||||||
- "6379"
|
|
||||||
command: keydb-server /etc/keydb/keydb.conf --maxmemory 100mb --maxmemory-policy volatile-lru --maxmemory-samples 5
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "keydb-cli", "-h", "localhost", "ping"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 5
|
|
||||||
restart: unless-stopped
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpus: "0.5"
|
|
||||||
|
|
||||||
keydb:
|
|
||||||
image: eqalpha/keydb:latest
|
|
||||||
volumes:
|
|
||||||
- keydb-data:/data
|
|
||||||
networks:
|
|
||||||
- dokploy-network
|
|
||||||
expose:
|
|
||||||
- "6379"
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "keydb-cli", "-h", "localhost", "ping"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 5
|
|
||||||
restart: unless-stopped
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpus: "0.5"
|
|
||||||
|
|
||||||
backend:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
networks:
|
|
||||||
- dokploy-network
|
|
||||||
expose:
|
|
||||||
- 1323
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- images_volume:/app/images
|
|
||||||
- storage_volume:/app/storage
|
|
||||||
environment:
|
|
||||||
STORAGE_STRATEGY: "minio"
|
|
||||||
MINIO_URL: minio:9000
|
|
||||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
|
||||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
|
||||||
PGHOST: db
|
|
||||||
PGPORT: "5432"
|
|
||||||
IMAGEOPTION: "true"
|
|
||||||
CONCURRENT_IMAGES: ${CONCURRENT_IMAGES}
|
|
||||||
depends_on:
|
|
||||||
minio:
|
|
||||||
condition: service_healthy
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
minio_storage:
|
|
||||||
images_volume:
|
|
||||||
storage_volume:
|
|
||||||
tiles-data:
|
|
||||||
keydb-data:
|
|
||||||
|
|
||||||
|
|
||||||
networks:
|
|
||||||
dokploy-network:
|
|
||||||
external: true
|
|
||||||
|
|
@ -230,7 +230,6 @@ func extractData_bkp(annuncio typesdefs.AnnuncioBKP, extractedData chan typesdef
|
||||||
}
|
}
|
||||||
tmp.Caratteristiche = &tmpCaratteristiche
|
tmp.Caratteristiche = &tmpCaratteristiche
|
||||||
|
|
||||||
tmp.Url_foto = nil
|
|
||||||
tmp.Url_video = nil
|
tmp.Url_video = nil
|
||||||
|
|
||||||
extractedData <- tmp
|
extractedData <- tmp
|
||||||
|
|
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"backend/config"
|
|
||||||
"backend/queries"
|
|
||||||
"backend/typesdefs"
|
|
||||||
"backend/utils"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"slices"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ImageRefManager struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewImageRefManager() *ImageRefManager {
|
|
||||||
return &ImageRefManager{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add or update the miogest url in the miogest_images_ref table to keep track of the images associated with each codice
|
|
||||||
func (i *ImageRefManager) InsertImagesMiogestRef(annunci *typesdefs.AnnunciXML) error {
|
|
||||||
|
|
||||||
return queries.ImagesBatchInsert(annunci)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *ImageRefManager) GetImageCodesForProcessing(annunci *typesdefs.AnnunciXML) ([]string, error) {
|
|
||||||
db_images, err := queries.ImagesGetAll()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
dbImagesMap := make(map[string][]string) // Convert slice to map for faster lookup
|
|
||||||
for _, ref := range db_images {
|
|
||||||
if ref.Foto != nil {
|
|
||||||
dbImagesMap[ref.Codice] = *ref.Foto
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var result []string
|
|
||||||
for _, annuncio := range annunci.Annuncio {
|
|
||||||
if annuncio.Codice != nil && annuncio.Foto != nil {
|
|
||||||
refFoto, existsInDb := dbImagesMap[*annuncio.Codice]
|
|
||||||
if !existsInDb {
|
|
||||||
fmt.Println("new codice", *annuncio.Codice)
|
|
||||||
result = append(result, *annuncio.Codice)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(*annuncio.Foto) != len(refFoto) {
|
|
||||||
fmt.Println("Different length", *annuncio.Foto, refFoto)
|
|
||||||
result = append(result, *annuncio.Codice)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, foto := range *annuncio.Foto {
|
|
||||||
if foto != refFoto[i] {
|
|
||||||
fmt.Println("Different content", *annuncio.Foto, refFoto)
|
|
||||||
result = append(result, *annuncio.Codice)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *ImageRefManager) ClearImages(to_clear []string) error {
|
|
||||||
|
|
||||||
for _, codice := range to_clear {
|
|
||||||
folder := filepath.Join(config.Cfg.Images.Path, codice)
|
|
||||||
err := utils.SafeRemoveAll(folder)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *ImageRefManager) RemoveImage(to_clear string) error {
|
|
||||||
|
|
||||||
folder := filepath.Join(config.Cfg.Images.Path, to_clear)
|
|
||||||
fmt.Println("Removing folder:", folder)
|
|
||||||
exists, err := utils.PathExists(folder)
|
|
||||||
fmt.Println("Folder exists:", exists)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if folder != "" && exists {
|
|
||||||
fmt.Println("Removing folder:", folder)
|
|
||||||
err := os.RemoveAll(folder)
|
|
||||||
fmt.Println("Folder removed:", folder)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
err = queries.ImagesClearByCodice(to_clear)
|
|
||||||
fmt.Println("DB entries removed for codice:", to_clear)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func difference(a, b []string) []string {
|
|
||||||
var diff []string
|
|
||||||
for _, v := range a {
|
|
||||||
if !slices.Contains(b, v) {
|
|
||||||
diff = append(diff, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return diff
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *ImageRefManager) MissingImageFolders() ([]string, error) {
|
|
||||||
|
|
||||||
// Get all image folders
|
|
||||||
folders := utils.GetPathDirsArray(config.Cfg.Images.Path)
|
|
||||||
// Get all image codes from DB
|
|
||||||
db_images, err := queries.ImagesGetAll()
|
|
||||||
if err != nil {
|
|
||||||
return []string{}, err
|
|
||||||
}
|
|
||||||
dbArray := make([]string, len(db_images))
|
|
||||||
for i, ref := range db_images {
|
|
||||||
dbArray[i] = ref.Codice
|
|
||||||
}
|
|
||||||
|
|
||||||
return difference(dbArray, folders), nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *ImageRefManager) Reset() error {
|
|
||||||
err := queries.ImagesClear()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = queries.AnnunciResetImages()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.RemoveAll(config.Cfg.Images.Path)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
err = os.Mkdir(config.Cfg.Images.Path, 0755)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +1,162 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"backend/config"
|
||||||
|
"backend/queries"
|
||||||
|
"backend/typesdefs"
|
||||||
|
"backend/utils"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
"image/png"
|
"image/png"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/nfnt/resize"
|
"github.com/nfnt/resize"
|
||||||
"github.com/nickalie/go-webpbin"
|
"github.com/nickalie/go-webpbin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Conversion(input string) error {
|
type ImageProcessing struct {
|
||||||
|
ImagesToProcess []typesdefs.ImagesToProcess
|
||||||
|
ProcessedImages []typesdefs.ProcessedImage
|
||||||
|
UploadedImages []typesdefs.UploadedImage
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewImageProcessing() *ImageProcessing {
|
||||||
|
return &ImageProcessing{
|
||||||
|
ImagesToProcess: []typesdefs.ImagesToProcess{},
|
||||||
|
ProcessedImages: []typesdefs.ProcessedImage{},
|
||||||
|
UploadedImages: []typesdefs.UploadedImage{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddToImageProcessing(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.AnnuncioXML, p *[]typesdefs.ImagesToProcess) {
|
||||||
|
|
||||||
|
if annuncio.Foto == nil || len(*annuncio.Foto) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for idx, url := range *annuncio.Foto {
|
||||||
|
*p = append(*p, typesdefs.ImagesToProcess{
|
||||||
|
Codice: tmp.Codice,
|
||||||
|
Url: url,
|
||||||
|
Order: idx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProcessImagePool(toProcess *[]typesdefs.ImagesToProcess) ([]typesdefs.UploadedImage, error) {
|
||||||
|
var (
|
||||||
|
ProcessedImages []typesdefs.ProcessedImage
|
||||||
|
UploadedImages []typesdefs.UploadedImage
|
||||||
|
mu sync.Mutex
|
||||||
|
wg sync.WaitGroup
|
||||||
|
)
|
||||||
|
sem := make(chan struct{}, config.Cfg.Images.ConcurrentLimit)
|
||||||
|
|
||||||
|
// clear images folder
|
||||||
|
err := clearImageFolder()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to initialize image processing: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// process images
|
||||||
|
for _, img := range *toProcess {
|
||||||
|
sem <- struct{}{} // acquire semaphore
|
||||||
|
wg.Add(1)
|
||||||
|
go func(img typesdefs.ImagesToProcess) {
|
||||||
|
defer wg.Done()
|
||||||
|
defer func() { <-sem }() // release semaphore
|
||||||
|
imgPath := filepath.Join(config.Cfg.Images.Path, fmt.Sprintf("%s_%d%s", img.Codice, img.Order, filepath.Ext(img.Url)))
|
||||||
|
downloadImage(img.Url, imgPath)
|
||||||
|
webpPath, thumbnailwebpPath, err := Conversion(imgPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to convert image: %v", err)
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
ProcessedImages = append(ProcessedImages, typesdefs.ProcessedImage{
|
||||||
|
Codice: img.Codice,
|
||||||
|
Order: img.Order,
|
||||||
|
Path: webpPath,
|
||||||
|
ThumnailPath: thumbnailwebpPath,
|
||||||
|
OGUrl: img.Url,
|
||||||
|
})
|
||||||
|
mu.Unlock()
|
||||||
|
}(img)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
uploadSem := make(chan struct{}, config.Cfg.Images.ConcurrentLimit)
|
||||||
|
var uploadWg sync.WaitGroup
|
||||||
|
for _, img := range ProcessedImages {
|
||||||
|
uploadSem <- struct{}{} // acquire semaphore
|
||||||
|
uploadWg.Add(1)
|
||||||
|
go func(img typesdefs.ProcessedImage) {
|
||||||
|
defer uploadWg.Done()
|
||||||
|
defer func() { <-uploadSem }() // release semaphore
|
||||||
|
|
||||||
|
imgId, err := UploadFile(img.Path)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to upload image: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
thumbId, err := UploadFile(img.ThumnailPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to upload thumbnail: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
UploadedImages = append(UploadedImages, typesdefs.UploadedImage{
|
||||||
|
Codice: img.Codice,
|
||||||
|
Order: img.Order,
|
||||||
|
ImgId: imgId,
|
||||||
|
ThumbId: thumbId,
|
||||||
|
OGUrl: img.OGUrl,
|
||||||
|
})
|
||||||
|
mu.Unlock()
|
||||||
|
}(img)
|
||||||
|
}
|
||||||
|
uploadWg.Wait()
|
||||||
|
|
||||||
|
return UploadedImages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearImageFolder() error {
|
||||||
|
err := utils.SafeRemoveAll(config.Cfg.Images.Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.Mkdir(config.Cfg.Images.Path, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func downloadImage(url, filename string) error {
|
||||||
|
response, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to download image: %v", err)
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to create image file: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
_, err = io.Copy(file, response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to save image file: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Conversion(input string) (string, string, error) {
|
||||||
|
var empty string
|
||||||
var width uint = 1920
|
var width uint = 1920
|
||||||
output := filepath.Base(input)
|
output := filepath.Base(input)
|
||||||
outputdir := filepath.Dir(input)
|
outputdir := filepath.Dir(input)
|
||||||
|
|
@ -24,7 +165,7 @@ func Conversion(input string) error {
|
||||||
log.Println("Converting", input, "to", output+".webp")
|
log.Println("Converting", input, "to", output+".webp")
|
||||||
f, err := os.Open(input)
|
f, err := os.Open(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return empty, empty, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
var img image.Image
|
var img image.Image
|
||||||
|
|
@ -34,37 +175,37 @@ func Conversion(input string) error {
|
||||||
log.Println("Image already in WebP format")
|
log.Println("Image already in WebP format")
|
||||||
img, err = webpbin.Decode(f)
|
img, err = webpbin.Decode(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return empty, empty, err
|
||||||
}
|
}
|
||||||
case ".png", ".PNG":
|
case ".png", ".PNG":
|
||||||
// Convert from PNG
|
// Convert from PNG
|
||||||
img, err = png.Decode(f)
|
img, err = png.Decode(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return empty, empty, err
|
||||||
}
|
}
|
||||||
case ".jpeg", ".jpg", ".JPG", ".JPEG":
|
case ".jpeg", ".jpg", ".JPG", ".JPEG":
|
||||||
// Convert from JPEG
|
// Convert from JPEG
|
||||||
img, err = jpeg.Decode(f)
|
img, err = jpeg.Decode(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return empty, empty, err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("error: Unsupported file type: %s", ext)
|
return empty, empty, fmt.Errorf("error: Unsupported file type: %s", ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = image_creation(img, width, output, outputdir)
|
err = image_creation(img, width, output, outputdir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return empty, empty, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = thumbnail_creation(img, output, outputdir)
|
err = thumbnail_creation(img, output, outputdir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return empty, empty, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Conversion completed successfully")
|
fmt.Println("Conversion completed successfully")
|
||||||
|
|
||||||
return nil
|
return fmt.Sprintf("%s/%s.webp", outputdir, output), fmt.Sprintf("%s/thumbnail-%s.webp", outputdir, output), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func image_creation(img image.Image, width uint, output string, outputdir string) error {
|
func image_creation(img image.Image, width uint, output string, outputdir string) error {
|
||||||
|
|
@ -117,3 +258,111 @@ func FallbackImage(c echo.Context) error {
|
||||||
}
|
}
|
||||||
return c.File(fallbackPath)
|
return c.File(fallbackPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type imgRef struct {
|
||||||
|
Og_url string
|
||||||
|
Img string
|
||||||
|
Thumb string
|
||||||
|
}
|
||||||
|
|
||||||
|
func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) {
|
||||||
|
currentBucketState, err := ListBucket("images")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to list bucket: %w", err)
|
||||||
|
}
|
||||||
|
//make a list of bucket ids
|
||||||
|
bucketIds := []string{}
|
||||||
|
for _, item := range currentBucketState {
|
||||||
|
bucketIds = append(bucketIds, item.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
db_images, err := queries.GetImagesFromDB()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dbImagesMap := make(map[string][]imgRef) // Convert slice to map for faster lookup
|
||||||
|
for _, ref := range db_images {
|
||||||
|
dbImagesMap[ref.Codice] = append(dbImagesMap[ref.Codice], imgRef{
|
||||||
|
Og_url: ref.Og_url,
|
||||||
|
Img: ref.Img,
|
||||||
|
Thumb: ref.Thumb,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var codiciToProcess []string
|
||||||
|
for _, annuncio := range annunci.Annuncio {
|
||||||
|
|
||||||
|
if annuncio.Codice != nil && annuncio.Foto != nil {
|
||||||
|
refFoto, existsInDb := dbImagesMap[*annuncio.Codice]
|
||||||
|
if !existsInDb {
|
||||||
|
codiciToProcess = append(codiciToProcess, *annuncio.Codice)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
needProcess := false
|
||||||
|
for _, fotoUrl := range *annuncio.Foto {
|
||||||
|
found := false
|
||||||
|
for _, ref := range refFoto {
|
||||||
|
if fotoUrl == ref.Og_url {
|
||||||
|
if slices.Contains(bucketIds, ref.Img) && slices.Contains(bucketIds, ref.Thumb) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
needProcess = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
needProcess = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if needProcess {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if needProcess {
|
||||||
|
codiciToProcess = append(codiciToProcess, *annuncio.Codice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return codiciToProcess, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func ForceClearImages(codici []string) error {
|
||||||
|
var idsToDelete []string
|
||||||
|
for _, codice := range codici {
|
||||||
|
refs, err := queries.GetCodiceImagesFromDB(codice)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get images by codice %s: %w", codice, err)
|
||||||
|
}
|
||||||
|
for _, ref := range refs {
|
||||||
|
idsToDelete = append(idsToDelete, ref.Img)
|
||||||
|
idsToDelete = append(idsToDelete, ref.Thumb)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
err := DeleteIdsFromStorage(idsToDelete)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete images from storage: %w", err)
|
||||||
|
}
|
||||||
|
for _, codice := range codici {
|
||||||
|
err := queries.ClearImagesRefsByCodice(codice)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to clear images by codice %s: %w", codice, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteIdsFromStorage(ids []string) error {
|
||||||
|
for _, id := range ids {
|
||||||
|
err := DeleteFile(id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete file with id %s: %w", id, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,11 @@ import (
|
||||||
"backend/typesdefs"
|
"backend/typesdefs"
|
||||||
"backend/utils"
|
"backend/utils"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -95,15 +90,15 @@ func (m *MiogestHandler) GenerateCategorie() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ANNUNCI PARSED
|
// ANNUNCI PARSED
|
||||||
func (m *MiogestHandler) GetAnnunciParsed(i *ImageRefManager) typesdefs.AnnunciParsed {
|
func (m *MiogestHandler) GetAnnunciParsed(p *[]typesdefs.ImagesToProcess) typesdefs.AnnunciParsed {
|
||||||
// salvo il parsing nello struct nel caso voglio implementare un caching e non fare il parsing ad ogni chiamata
|
// salvo il parsing nello struct nel caso voglio implementare un caching e non fare il parsing ad ogni chiamata
|
||||||
m.AnnunciParsed = m.ParseAnnunci("", i)
|
m.AnnunciParsed = m.ParseAnnunci("", p)
|
||||||
return m.AnnunciParsed
|
return m.AnnunciParsed
|
||||||
}
|
}
|
||||||
|
|
||||||
// versione per singolo annuncio
|
// versione per singolo annuncio
|
||||||
func (m *MiogestHandler) GetAnnuncioParsed(codFilter string, i *ImageRefManager) typesdefs.AnnunciParsed {
|
func (m *MiogestHandler) GetAnnuncioParsed(codFilter string, p *[]typesdefs.ImagesToProcess) typesdefs.AnnunciParsed {
|
||||||
data := m.ParseAnnunci(codFilter, i)
|
data := m.ParseAnnunci(codFilter, p)
|
||||||
if len(data.Annuncio) == 0 {
|
if len(data.Annuncio) == 0 {
|
||||||
return typesdefs.AnnunciParsed{}
|
return typesdefs.AnnunciParsed{}
|
||||||
}
|
}
|
||||||
|
|
@ -111,7 +106,7 @@ func (m *MiogestHandler) GetAnnuncioParsed(codFilter string, i *ImageRefManager)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MiogestHandler) ParseAnnunci(codFilter string, i *ImageRefManager) typesdefs.AnnunciParsed {
|
func (m *MiogestHandler) ParseAnnunci(codFilter string, p *[]typesdefs.ImagesToProcess) typesdefs.AnnunciParsed {
|
||||||
annunci := m.GetAnnunci()
|
annunci := m.GetAnnunci()
|
||||||
|
|
||||||
if len(annunci.Annuncio) == 0 {
|
if len(annunci.Annuncio) == 0 {
|
||||||
|
|
@ -131,64 +126,34 @@ func (m *MiogestHandler) ParseAnnunci(codFilter string, i *ImageRefManager) type
|
||||||
image_codes_to_process := []string{}
|
image_codes_to_process := []string{}
|
||||||
if config.Cfg.Images.Enabled {
|
if config.Cfg.Images.Enabled {
|
||||||
var err error
|
var err error
|
||||||
image_codes_to_process, err = i.GetImageCodesForProcessing(&annunci)
|
image_codes_to_process, err = CodiciToProcessImages(&annunci)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to find images to update: %v", err.Error())
|
log.Fatalf("Failed to find images to update: %v", err.Error())
|
||||||
}
|
}
|
||||||
if codFilter == "" {
|
if codFilter != "" {
|
||||||
// if processing all, also check for missing folders
|
|
||||||
missing_codes, err := i.MissingImageFolders()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to find missing image folders: %v", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(missing_codes) > 0 {
|
|
||||||
|
|
||||||
image_codes_to_process = append(image_codes_to_process, missing_codes...)
|
|
||||||
image_codes_to_process = slices.Compact(image_codes_to_process)
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if processing single codice, ensure it's in the list
|
// if processing single codice, ensure it's in the list
|
||||||
if !slices.Contains(image_codes_to_process, codFilter) {
|
if !slices.Contains(image_codes_to_process, codFilter) {
|
||||||
image_codes_to_process = append(image_codes_to_process, codFilter)
|
image_codes_to_process = append(image_codes_to_process, codFilter)
|
||||||
}
|
}
|
||||||
|
err := ForceClearImages([]string{codFilter})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to clear images for codice %s: %v", codFilter, err.Error())
|
||||||
}
|
}
|
||||||
// Clears images folder of codes that need to be updated
|
|
||||||
if err := i.ClearImages(image_codes_to_process); err != nil {
|
|
||||||
log.Fatalf("Failed to clear images: %v", err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
i.InsertImagesMiogestRef(&annunci)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var AnnunciArray typesdefs.AnnunciParsed
|
var AnnunciArray typesdefs.AnnunciParsed
|
||||||
AnnunciArray.Annuncio = make([]typesdefs.AnnuncioParsed, 0, len(annunci.Annuncio))
|
AnnunciArray.Annuncio = make([]typesdefs.AnnuncioParsed, 0, len(annunci.Annuncio))
|
||||||
m.InitDefaults()
|
m.InitDefaults()
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(len(annunci.Annuncio))
|
|
||||||
sem := make(chan struct{}, config.Cfg.Images.ConcurrentLimit) //Limit concurrent goroutines
|
|
||||||
lista_codici := []string{}
|
|
||||||
for i := range annunci.Annuncio {
|
|
||||||
|
|
||||||
lista_codici = append(lista_codici, utils.RemoveWhitespace(*annunci.Annuncio[i].Codice))
|
for i := range annunci.Annuncio {
|
||||||
sem <- struct{}{}
|
imgToUpdate := slices.Contains(image_codes_to_process, *annunci.Annuncio[i].Codice)
|
||||||
go func(i int) {
|
result := extractData_update(&annunci.Annuncio[i], imgToUpdate, m, p)
|
||||||
defer wg.Done()
|
|
||||||
defer func() { <-sem }()
|
|
||||||
result := extractData_update(&annunci.Annuncio[i], slices.Contains(image_codes_to_process, *annunci.Annuncio[i].Codice), m)
|
|
||||||
AnnunciArray.Annuncio = append(AnnunciArray.Annuncio, result)
|
AnnunciArray.Annuncio = append(AnnunciArray.Annuncio, result)
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
if config.Cfg.Images.Enabled {
|
|
||||||
for _, codice := range lista_codici {
|
|
||||||
err := removeNonWebpImages(codice)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to cleanup folder: %v", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return AnnunciArray
|
return AnnunciArray
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,7 +167,7 @@ func strUpd(s string) *string {
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractData_update(annuncio *typesdefs.AnnuncioXML, updateImages bool, m *MiogestHandler) typesdefs.AnnuncioParsed {
|
func extractData_update(annuncio *typesdefs.AnnuncioXML, updateImages bool, m *MiogestHandler, p *[]typesdefs.ImagesToProcess) typesdefs.AnnuncioParsed {
|
||||||
var tmp typesdefs.AnnuncioParsed = typesdefs.AnnuncioParsed{}
|
var tmp typesdefs.AnnuncioParsed = typesdefs.AnnuncioParsed{}
|
||||||
tmp.Codice = utils.RemoveWhitespace(utils.GetStringValue(annuncio.Codice))
|
tmp.Codice = utils.RemoveWhitespace(utils.GetStringValue(annuncio.Codice))
|
||||||
processLocatore(&tmp, annuncio)
|
processLocatore(&tmp, annuncio)
|
||||||
|
|
@ -275,39 +240,9 @@ func extractData_update(annuncio *typesdefs.AnnuncioXML, updateImages bool, m *M
|
||||||
}
|
}
|
||||||
tmpcaratt := parseCaratteristiche(annuncio, m)
|
tmpcaratt := parseCaratteristiche(annuncio, m)
|
||||||
tmp.Caratteristiche = &tmpcaratt
|
tmp.Caratteristiche = &tmpcaratt
|
||||||
tmp.Og_url = nil
|
|
||||||
if len(*annuncio.Foto) != 0 {
|
|
||||||
fotos := *annuncio.Foto
|
|
||||||
first := fotos[0]
|
|
||||||
tmp.Og_url = &first
|
|
||||||
}
|
|
||||||
if updateImages {
|
if updateImages {
|
||||||
processImages(&tmp, annuncio)
|
AddToImageProcessing(&tmp, annuncio, p)
|
||||||
}
|
|
||||||
folders := utils.GetPathDirsArray(filepath.Join(config.Cfg.Images.Path, tmp.Codice))
|
|
||||||
for _, folder := range folders {
|
|
||||||
folderpath := filepath.Join(filepath.Join(config.Cfg.Images.Path, tmp.Codice), folder)
|
|
||||||
exists, err := utils.PathExists(folderpath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to check if folder exists: %v", err)
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
// something went wrong, skip
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
files, err := os.ReadDir(folderpath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to read dir: %v", err)
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if strings.Contains(file.Name(), "webp") && !strings.HasPrefix(file.Name(), "thumbnail") {
|
|
||||||
tmpImgUrl := tmp.Codice + "/" + folder
|
|
||||||
if tmp.Url_foto == nil {
|
|
||||||
tmp.Url_foto = &[]string{}
|
|
||||||
}
|
|
||||||
*tmp.Url_foto = append(*tmp.Url_foto, tmpImgUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tmp
|
return tmp
|
||||||
|
|
@ -396,124 +331,6 @@ func processDatiImmobile(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.Annu
|
||||||
tmp.Numero_postiauto = annuncio.PostiAuto
|
tmp.Numero_postiauto = annuncio.PostiAuto
|
||||||
}
|
}
|
||||||
|
|
||||||
func processImages(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.AnnuncioXML) {
|
|
||||||
path := filepath.Join(config.Cfg.Images.Path, tmp.Codice)
|
|
||||||
folderExists, err := utils.PathExists(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to check if folder exists: %v", err)
|
|
||||||
}
|
|
||||||
if folderExists {
|
|
||||||
// Remove existing folder to avoid stale images
|
|
||||||
err := utils.SafeRemoveAll(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to remove existing folder: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Create directory if it doesn't exist
|
|
||||||
err = os.MkdirAll(path, 0755)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed to create directory: %v\n", err)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
sem := make(chan struct{}, config.Cfg.Images.ConcurrentLimit) // Limit concurrent goroutines
|
|
||||||
nfoto := 0
|
|
||||||
var fotos []string
|
|
||||||
if annuncio.Foto != nil {
|
|
||||||
fotos = *annuncio.Foto
|
|
||||||
}
|
|
||||||
for _, url := range fotos {
|
|
||||||
fmt.Println("Processing image", url)
|
|
||||||
imgpath := filepath.Join(path, fmt.Sprintf("%d", nfoto))
|
|
||||||
if _, err := os.Stat(imgpath); os.IsNotExist(err) {
|
|
||||||
os.Mkdir(imgpath, 0755)
|
|
||||||
}
|
|
||||||
filename := filepath.Join(imgpath, fmt.Sprintf("%s_%d%s", tmp.Codice, nfoto, filepath.Ext(url)))
|
|
||||||
wg.Add(1)
|
|
||||||
sem <- struct{}{}
|
|
||||||
go func(url, filename string) {
|
|
||||||
defer wg.Done()
|
|
||||||
defer func() { <-sem }()
|
|
||||||
downloadImage(url, filename)
|
|
||||||
}(url, filename)
|
|
||||||
nfoto++
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
processWebpImages(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadImage(url, filename string) {
|
|
||||||
response, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to download image: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
file, err := os.Create(filename)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to create file: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
_, err = io.Copy(file, response.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to save image: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func processWebpImages(path string) {
|
|
||||||
array, err := utils.GetPathsArray(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
sem := make(chan struct{}, config.Cfg.Images.ConcurrentLimit) // Limit concurrent goroutines
|
|
||||||
for _, img := range array {
|
|
||||||
wg.Add(1)
|
|
||||||
sem <- struct{}{}
|
|
||||||
go func(img string) {
|
|
||||||
defer wg.Done()
|
|
||||||
defer func() { <-sem }()
|
|
||||||
err := Conversion(img)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}(img)
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeNonWebpImages(codice string) error {
|
|
||||||
folders := utils.GetPathDirsArray(filepath.Join(config.Cfg.Images.Path, codice))
|
|
||||||
non_webp := []string{}
|
|
||||||
for _, folder := range folders {
|
|
||||||
folderpath := filepath.Join(config.Cfg.Images.Path, codice, folder)
|
|
||||||
exists, err := utils.PathExists(folderpath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
files, err := os.ReadDir(folderpath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if !strings.Contains(file.Name(), "webp") {
|
|
||||||
non_webp = append(non_webp, filepath.Join(folderpath, file.Name()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, file := range non_webp {
|
|
||||||
err := utils.SafeRemove(file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseCaratteristiche(wrkrecord *typesdefs.AnnuncioXML, m *MiogestHandler) map[string]interface{} {
|
func parseCaratteristiche(wrkrecord *typesdefs.AnnuncioXML, m *MiogestHandler) map[string]interface{} {
|
||||||
if reflect.TypeOf(wrkrecord).Kind() != reflect.Ptr || reflect.ValueOf(wrkrecord).Elem().Kind() != reflect.Struct {
|
if reflect.TypeOf(wrkrecord).Kind() != reflect.Ptr || reflect.ValueOf(wrkrecord).Elem().Kind() != reflect.Struct {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -84,14 +84,12 @@ func AnnunciInsert(annunci typesdefs.AnnunciParsed, resetAllBeforeUpdate bool) e
|
||||||
stato = $42,
|
stato = $42,
|
||||||
web = $43,
|
web = $43,
|
||||||
homepage = $44,
|
homepage = $44,
|
||||||
url_immagini = $45,
|
url_video = $45,
|
||||||
url_video = $46,
|
disponibile_da = $47,
|
||||||
og_url = $48,
|
permanenza = $48,
|
||||||
disponibile_da = $49,
|
persone = $49,
|
||||||
permanenza = $50,
|
|
||||||
persone = $51,
|
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE codice = $47
|
WHERE codice = $46
|
||||||
`, annuncio.Locatore, annuncio.Numero, annuncio.Idlocatore,
|
`, annuncio.Locatore, annuncio.Numero, annuncio.Idlocatore,
|
||||||
annuncio.Email, annuncio.Creato_il, annuncio.Modificato_il,
|
annuncio.Email, annuncio.Creato_il, annuncio.Modificato_il,
|
||||||
annuncio.Indirizzo, annuncio.Civico, annuncio.Comune, annuncio.Cap,
|
annuncio.Indirizzo, annuncio.Civico, annuncio.Comune, annuncio.Cap,
|
||||||
|
|
@ -102,8 +100,8 @@ func AnnunciInsert(annunci typesdefs.AnnunciParsed, resetAllBeforeUpdate bool) e
|
||||||
annuncio.Unita_condominio, annuncio.Numero_vani, annuncio.Numero_camere, annuncio.Numero_bagni,
|
annuncio.Unita_condominio, annuncio.Numero_vani, annuncio.Numero_camere, annuncio.Numero_bagni,
|
||||||
annuncio.Numero_balconi, annuncio.Numero_terrazzi, annuncio.Numero_box, annuncio.Numero_postiauto,
|
annuncio.Numero_balconi, annuncio.Numero_terrazzi, annuncio.Numero_box, annuncio.Numero_postiauto,
|
||||||
annuncio.Caratteristiche, annuncio.Accessori, annuncio.Titolo_it, annuncio.Desc_it, annuncio.Titolo_en,
|
annuncio.Caratteristiche, annuncio.Accessori, annuncio.Titolo_it, annuncio.Desc_it, annuncio.Titolo_en,
|
||||||
annuncio.Desc_en, annuncio.Stato, annuncio.Web, annuncio.Homepage, annuncio.Url_foto,
|
annuncio.Desc_en, annuncio.Stato, annuncio.Web, annuncio.Homepage,
|
||||||
annuncio.Url_video, annuncio.Codice, annuncio.Og_url, annuncio.Disponibile_da, annuncio.Permanenza, annuncio.Persone)
|
annuncio.Url_video, annuncio.Codice, annuncio.Disponibile_da, annuncio.Permanenza, annuncio.Persone)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
batch.Queue(`
|
batch.Queue(`
|
||||||
|
|
@ -152,16 +150,14 @@ func AnnunciInsert(annunci typesdefs.AnnunciParsed, resetAllBeforeUpdate bool) e
|
||||||
stato,
|
stato,
|
||||||
web,
|
web,
|
||||||
homepage,
|
homepage,
|
||||||
url_immagini,
|
|
||||||
url_video,
|
url_video,
|
||||||
codice,
|
codice,
|
||||||
og_url,
|
|
||||||
disponibile_da,
|
disponibile_da,
|
||||||
permanenza,
|
permanenza,
|
||||||
persone,
|
persone,
|
||||||
updated_at
|
updated_at
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $50, $51, NOW())
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44, $45, $46, $47, $48, $49, NOW())
|
||||||
`, annuncio.Locatore, annuncio.Numero, annuncio.Idlocatore,
|
`, annuncio.Locatore, annuncio.Numero, annuncio.Idlocatore,
|
||||||
annuncio.Email, annuncio.Creato_il, annuncio.Modificato_il,
|
annuncio.Email, annuncio.Creato_il, annuncio.Modificato_il,
|
||||||
annuncio.Indirizzo, annuncio.Civico, annuncio.Comune, annuncio.Cap,
|
annuncio.Indirizzo, annuncio.Civico, annuncio.Comune, annuncio.Cap,
|
||||||
|
|
@ -172,8 +168,8 @@ func AnnunciInsert(annunci typesdefs.AnnunciParsed, resetAllBeforeUpdate bool) e
|
||||||
annuncio.Unita_condominio, annuncio.Numero_vani, annuncio.Numero_camere, annuncio.Numero_bagni,
|
annuncio.Unita_condominio, annuncio.Numero_vani, annuncio.Numero_camere, annuncio.Numero_bagni,
|
||||||
annuncio.Numero_balconi, annuncio.Numero_terrazzi, annuncio.Numero_box, annuncio.Numero_postiauto,
|
annuncio.Numero_balconi, annuncio.Numero_terrazzi, annuncio.Numero_box, annuncio.Numero_postiauto,
|
||||||
annuncio.Caratteristiche, annuncio.Accessori, annuncio.Titolo_it, annuncio.Desc_it, annuncio.Titolo_en,
|
annuncio.Caratteristiche, annuncio.Accessori, annuncio.Titolo_it, annuncio.Desc_it, annuncio.Titolo_en,
|
||||||
annuncio.Desc_en, annuncio.Stato, annuncio.Web, annuncio.Homepage, annuncio.Url_foto,
|
annuncio.Desc_en, annuncio.Stato, annuncio.Web, annuncio.Homepage,
|
||||||
annuncio.Url_video, annuncio.Codice, annuncio.Og_url, annuncio.Disponibile_da, annuncio.Permanenza, annuncio.Persone)
|
annuncio.Url_video, annuncio.Codice, annuncio.Disponibile_da, annuncio.Permanenza, annuncio.Persone)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = Db.Dbpool.SendBatch(Db.Ctx, batch).Close()
|
err = Db.Dbpool.SendBatch(Db.Ctx, batch).Close()
|
||||||
|
|
@ -245,11 +241,10 @@ func SetFromBkp(annunci typesdefs.AnnunciParsed) error {
|
||||||
stato,
|
stato,
|
||||||
web,
|
web,
|
||||||
homepage,
|
homepage,
|
||||||
url_immagini,
|
|
||||||
url_video,
|
url_video,
|
||||||
codice
|
codice
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44, $45, $46, $47)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44, $45, $46)
|
||||||
`, annuncio.Locatore, annuncio.Numero, annuncio.Idlocatore,
|
`, annuncio.Locatore, annuncio.Numero, annuncio.Idlocatore,
|
||||||
annuncio.Email, annuncio.Creato_il, annuncio.Modificato_il,
|
annuncio.Email, annuncio.Creato_il, annuncio.Modificato_il,
|
||||||
annuncio.Indirizzo, annuncio.Civico, annuncio.Comune, annuncio.Cap,
|
annuncio.Indirizzo, annuncio.Civico, annuncio.Comune, annuncio.Cap,
|
||||||
|
|
@ -260,7 +255,7 @@ func SetFromBkp(annunci typesdefs.AnnunciParsed) error {
|
||||||
annuncio.Unita_condominio, annuncio.Numero_vani, annuncio.Numero_camere, annuncio.Numero_bagni,
|
annuncio.Unita_condominio, annuncio.Numero_vani, annuncio.Numero_camere, annuncio.Numero_bagni,
|
||||||
annuncio.Numero_balconi, annuncio.Numero_terrazzi, annuncio.Numero_box, annuncio.Numero_postiauto,
|
annuncio.Numero_balconi, annuncio.Numero_terrazzi, annuncio.Numero_box, annuncio.Numero_postiauto,
|
||||||
annuncio.Caratteristiche, annuncio.Accessori, annuncio.Titolo_it, annuncio.Desc_it, annuncio.Titolo_en,
|
annuncio.Caratteristiche, annuncio.Accessori, annuncio.Titolo_it, annuncio.Desc_it, annuncio.Titolo_en,
|
||||||
annuncio.Desc_en, annuncio.Stato, annuncio.Web, annuncio.Homepage, annuncio.Url_foto,
|
annuncio.Desc_en, annuncio.Stato, annuncio.Web, annuncio.Homepage,
|
||||||
annuncio.Url_video, annuncio.Codice)
|
annuncio.Url_video, annuncio.Codice)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,24 +8,15 @@ import (
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ImagesGetAll() ([]typesdefs.MiogestImagesRef_Table, error) {
|
func SetImagesToDB(pool *[]typesdefs.UploadedImage) error {
|
||||||
var images []typesdefs.MiogestImagesRef_Table
|
|
||||||
err := pgxscan.Select(Db.Ctx, Db.Dbpool, &images, "SELECT * FROM public.miogest_images_ref")
|
|
||||||
if err != nil && err != pgx.ErrNoRows {
|
|
||||||
return nil, fmt.Errorf("failed to get all images: %w", err)
|
|
||||||
}
|
|
||||||
return images, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ImagesBatchInsert(annunci *typesdefs.AnnunciXML) error {
|
|
||||||
batch := &pgx.Batch{}
|
batch := &pgx.Batch{}
|
||||||
|
|
||||||
for _, annuncio := range annunci.Annuncio {
|
for _, data := range *pool {
|
||||||
batch.Queue(`
|
batch.Queue(`
|
||||||
INSERT INTO public.miogest_images_ref (codice, foto)
|
INSERT INTO public.images_refs (codice, ordine, img, thumb, og_url)
|
||||||
VALUES ($1, $2)
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
ON CONFLICT (codice) DO UPDATE SET foto = $2
|
ON CONFLICT (codice, ordine) DO UPDATE SET img = $3, thumb = $4
|
||||||
`, &annuncio.Codice, &annuncio.Foto)
|
`, data.Codice, data.Order, data.ImgId, data.ThumbId, data.OGUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := Db.Dbpool.SendBatch(Db.Ctx, batch).Close(); err != nil {
|
if err := Db.Dbpool.SendBatch(Db.Ctx, batch).Close(); err != nil {
|
||||||
|
|
@ -35,18 +26,28 @@ func ImagesBatchInsert(annunci *typesdefs.AnnunciXML) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ImagesClear() error {
|
func GetImagesFromDB() ([]typesdefs.ImagesRefs, error) {
|
||||||
_, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.miogest_images_ref")
|
var images []typesdefs.ImagesRefs
|
||||||
if err != nil {
|
err := pgxscan.Select(Db.Ctx, Db.Dbpool, &images, "SELECT * FROM public.images_refs ORDER BY ordine ASC")
|
||||||
return fmt.Errorf("failed to clear images: %w", err)
|
if err != nil && err != pgx.ErrNoRows {
|
||||||
|
return nil, fmt.Errorf("failed to get images: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return images, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ImagesClearByCodice(codice string) error {
|
func GetCodiceImagesFromDB(codice string) ([]typesdefs.ImagesRefs, error) {
|
||||||
_, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.miogest_images_ref WHERE codice = $1", codice)
|
var images []typesdefs.ImagesRefs
|
||||||
|
err := pgxscan.Select(Db.Ctx, Db.Dbpool, &images, "SELECT * FROM public.images_refs WHERE codice = $1 ORDER BY ordine ASC", codice)
|
||||||
|
if err != nil && err != pgx.ErrNoRows {
|
||||||
|
return nil, fmt.Errorf("failed to get images: %w", err)
|
||||||
|
}
|
||||||
|
return images, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClearImagesRefsByCodice(codice string) error {
|
||||||
|
_, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.images_refs WHERE codice = $1", codice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to clear images by codice: %w", err)
|
return fmt.Errorf("failed to clear images refs by codice: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
package queries
|
|
||||||
|
|
||||||
import (
|
|
||||||
"backend/typesdefs"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/georgysavva/scany/v2/pgxscan"
|
|
||||||
)
|
|
||||||
|
|
||||||
// STORAGE
|
|
||||||
|
|
||||||
func StorageNewId() (string, error) {
|
|
||||||
var newId string
|
|
||||||
err := Db.Dbpool.QueryRow(Db.Ctx, "INSERT INTO public.storageindex(filename)VALUES (null) RETURNING id;").Scan(&newId)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if newId == "" {
|
|
||||||
return "", fmt.Errorf("failed to create new id")
|
|
||||||
}
|
|
||||||
return newId, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func StorageGet_min() ([]struct {
|
|
||||||
Id string
|
|
||||||
Ext string
|
|
||||||
}, 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 nil, fmt.Errorf("failed to get items: %w", err)
|
|
||||||
}
|
|
||||||
return items_db, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func StorageUpdate(id, filename, ext string, from_admin bool, expires_at *time.Time) error {
|
|
||||||
_, err := Db.Dbpool.Exec(Db.Ctx, "UPDATE public.storageindex set filename= $2, ext = $3, from_admin = $4, expires_at = $5 where id = $1", id, filename, ext, from_admin, expires_at)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update storage index: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func StorageDelete(id string) error {
|
|
||||||
_, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.storageindex WHERE id = $1", id)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete storage index: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func StorageDeleteUnused() error {
|
|
||||||
_, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.storageindex WHERE filename IS NULL")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete storage index: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func StorageCheckToken(token string) error {
|
|
||||||
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 fmt.Errorf("failed to check token: %w", err)
|
|
||||||
}
|
|
||||||
if tk == nil {
|
|
||||||
return fmt.Errorf("invalid or expired token")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func StorageCleanExpiredTokens(token string) error {
|
|
||||||
_, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.temp_tokens WHERE elapse < $1", time.Now())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func StorageGetExpiredFiles() ([]typesdefs.Storageindex, error) {
|
|
||||||
|
|
||||||
var items_db []typesdefs.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 nil, fmt.Errorf("failed to get expired items: %w", err)
|
|
||||||
}
|
|
||||||
return items_db, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
set -e # Exit on any error
|
|
||||||
|
|
||||||
echo "Starting expired storage cleanup..."
|
|
||||||
if curl -f --connect-timeout 30 --max-time 300 "http://localhost:1323/storage/remove-expired"; then
|
|
||||||
echo "Expired storage cleanup completed successfully"
|
|
||||||
else
|
|
||||||
echo "Failed to clean expired storage" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
@ -3,30 +3,24 @@ package main
|
||||||
import (
|
import (
|
||||||
"backend/config"
|
"backend/config"
|
||||||
"backend/queries"
|
"backend/queries"
|
||||||
storagehandlers "backend/storage_handlers"
|
"backend/typesdefs"
|
||||||
"backend/utils"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/nfnt/resize"
|
|
||||||
"github.com/nickalie/go-webpbin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
miogest *MiogestHandler
|
miogest *MiogestHandler
|
||||||
imageManager *ImageRefManager
|
|
||||||
storage storagehandlers.StorageHandler
|
|
||||||
config *config.Config
|
config *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,20 +30,8 @@ func NewServer(cfg *config.Config) (*Server, error) {
|
||||||
return nil, fmt.Errorf("error connecting to miogest")
|
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{
|
return &Server{
|
||||||
miogest: miogest,
|
miogest: miogest,
|
||||||
imageManager: imageManager,
|
|
||||||
storage: storage,
|
|
||||||
config: cfg,
|
config: cfg,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -80,9 +62,15 @@ func (s *Server) SetupRoutes() *echo.Echo {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
e.GET("/test", func(c echo.Context) error {
|
e.GET("/test", func(c echo.Context) error {
|
||||||
|
files, err := ListBucket("images")
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
s.imageManager.MissingImageFolders()
|
return c.JSONPretty(http.StatusOK,
|
||||||
return c.String(http.StatusOK, "OK")
|
map[string]any{
|
||||||
|
"files": files,
|
||||||
|
}, " ")
|
||||||
})
|
})
|
||||||
e.POST("/testpost", func(c echo.Context) error {
|
e.POST("/testpost", func(c echo.Context) error {
|
||||||
// Get the form value
|
// Get the form value
|
||||||
|
|
@ -117,14 +105,24 @@ func (s *Server) SetupRoutes() *echo.Echo {
|
||||||
})
|
})
|
||||||
e.GET("/parse", func(c echo.Context) error {
|
e.GET("/parse", func(c echo.Context) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
var data = s.miogest.GetAnnunciParsed(s.imageManager)
|
var data = s.miogest.GetAnnunciParsed(nil)
|
||||||
end := time.Now()
|
end := time.Now()
|
||||||
fmt.Println("time taken:", end.Sub(start).String())
|
fmt.Println("time taken:", end.Sub(start).String())
|
||||||
return c.JSONPretty(http.StatusOK, data, " ")
|
return c.JSONPretty(http.StatusOK, data, " ")
|
||||||
})
|
})
|
||||||
e.GET("/update", func(c echo.Context) error {
|
e.GET("/update", func(c echo.Context) error {
|
||||||
var data = s.miogest.GetAnnunciParsed(s.imageManager)
|
imgPool := []typesdefs.ImagesToProcess{}
|
||||||
err := queries.AnnunciInsert(data, true)
|
var data = s.miogest.GetAnnunciParsed(&imgPool)
|
||||||
|
imgRefs, err := ProcessImagePool(&imgPool)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = queries.AnnunciInsert(data, true)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
err = queries.SetImagesToDB(&imgRefs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.String(http.StatusInternalServerError, err.Error())
|
return c.String(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -133,12 +131,21 @@ func (s *Server) SetupRoutes() *echo.Echo {
|
||||||
e.GET("/update-cod/:cod", func(c echo.Context) error {
|
e.GET("/update-cod/:cod", func(c echo.Context) error {
|
||||||
|
|
||||||
cod := c.Param("cod")
|
cod := c.Param("cod")
|
||||||
|
imgPool := []typesdefs.ImagesToProcess{}
|
||||||
var data = s.miogest.GetAnnuncioParsed(cod, s.imageManager)
|
var data = s.miogest.GetAnnuncioParsed(cod, &imgPool)
|
||||||
err := queries.AnnunciInsert(data, false)
|
imgRefs, err := ProcessImagePool(&imgPool)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.String(http.StatusInternalServerError, err.Error())
|
return c.String(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
err = queries.AnnunciInsert(data, false)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
err = queries.SetImagesToDB(&imgRefs)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
return c.String(http.StatusOK, "Updated")
|
return c.String(http.StatusOK, "Updated")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -183,174 +190,12 @@ func (s *Server) SetupRoutes() *echo.Echo {
|
||||||
return c.String(http.StatusOK, "Setted")
|
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
|
//IMAGE ROUTES
|
||||||
e.GET("/initcwebp", func(c echo.Context) error {
|
e.GET("/initcwebp", func(c echo.Context) error {
|
||||||
Conversion("test.jpg")
|
Conversion("test.jpg")
|
||||||
return c.String(http.StatusOK, "Cwebp initialized")
|
return c.String(http.StatusOK, "Cwebp initialized")
|
||||||
})
|
})
|
||||||
|
|
||||||
e.GET("/images/list", func(c echo.Context) error {
|
|
||||||
folders := utils.GetPathDirsArray(config.Cfg.Images.Path)
|
|
||||||
return c.JSON(http.StatusOK, folders)
|
|
||||||
|
|
||||||
// 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.GET("/images/remove/:cod", func(c echo.Context) error {
|
|
||||||
cod := c.Param("cod")
|
|
||||||
|
|
||||||
err := s.imageManager.RemoveImage(cod)
|
|
||||||
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 {
|
e.POST("/image-option-toggle", func(c echo.Context) error {
|
||||||
// Toggle the imageOption value
|
// Toggle the imageOption value
|
||||||
s.config.Images.Enabled = !s.config.Images.Enabled
|
s.config.Images.Enabled = !s.config.Images.Enabled
|
||||||
|
|
|
||||||
|
|
@ -1,161 +1,205 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"backend/config"
|
"bytes"
|
||||||
"backend/queries"
|
"encoding/json"
|
||||||
storagehandlers "backend/storage_handlers"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Upload saves a file to the storage directory.
|
var EXAMPLE_STORAGE_URL = "http://localhost:8080"
|
||||||
func Upload(file *multipart.FileHeader, from_admin bool, s storagehandlers.StorageHandler) (string, error) {
|
var EXAMPLE_STORAGE_TOKEN = "test"
|
||||||
fmt.Printf("Uploading file: %s\n", file.Filename)
|
|
||||||
|
|
||||||
newId, err := queries.StorageNewId()
|
func DeleteFile(id string) error {
|
||||||
|
req, err := http.NewRequest("DELETE", EXAMPLE_STORAGE_URL+"/delete/"+id+"?token="+EXAMPLE_STORAGE_TOKEN, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return fmt.Errorf("failed to create delete request: %w", err)
|
||||||
}
|
}
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
err = s.Add(file, newId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return fmt.Errorf("delete request failed: %w", err)
|
||||||
}
|
}
|
||||||
//if !from_admin , expires in 60 days
|
defer resp.Body.Close()
|
||||||
var expiresAt *time.Time
|
if resp.StatusCode != http.StatusOK {
|
||||||
if !from_admin {
|
return fmt.Errorf("delete failed: %s", resp.Status)
|
||||||
exp := time.Now().AddDate(0, 2, 0) // 60 days
|
|
||||||
expiresAt = &exp
|
|
||||||
} else {
|
|
||||||
expiresAt = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = queries.StorageUpdate(newId, file.Filename, filepath.Ext(file.Filename), from_admin, expiresAt)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = queries.StorageDeleteUnused()
|
|
||||||
if err != nil {
|
|
||||||
return "", 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")
|
|
||||||
}
|
|
||||||
err := queries.StorageCheckToken(token)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = queries.StorageCleanExpiredTokens(token)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RemoveFromDb(ref string) error {
|
func GetFile(path string) ([]byte, error) {
|
||||||
|
panic("unimplemented")
|
||||||
return queries.StorageDelete(ref)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func RemoveExpired(s storagehandlers.StorageHandler) error {
|
type FileMetadata struct {
|
||||||
//get all expired files
|
ID string `json:"id"`
|
||||||
items_db, err := queries.StorageGetExpiredFiles()
|
OriginalName string `json:"originalName"`
|
||||||
if err != nil {
|
Size int64 `json:"size"`
|
||||||
return err
|
MIMEType string `json:"mimeType"`
|
||||||
}
|
BlockCount int `json:"blockCount"`
|
||||||
|
UploadedAt time.Time `json:"uploadedAt"`
|
||||||
for _, item := range items_db {
|
ExpiresAt *time.Time `json:"expiresAt"`
|
||||||
formattedName := item.Id + item.Ext
|
Bucket string `json:"bucket,omitempty"`
|
||||||
err = s.Delete(formattedName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete file %s: %w", formattedName, err)
|
|
||||||
}
|
|
||||||
err = queries.StorageDelete(item.Id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckConcruency(s storagehandlers.StorageHandler) error {
|
// func ListFiles() ([]FileMetadata, error) {
|
||||||
items_db, err := queries.StorageGet_min()
|
// //get list of files from api
|
||||||
if err != nil {
|
// resp, err := http.Get(EXAMPLE_STORAGE_URL + "/files?token=" + EXAMPLE_STORAGE_TOKEN)
|
||||||
return err
|
// if err != nil {
|
||||||
}
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// defer resp.Body.Close()
|
||||||
|
|
||||||
files, err := s.List()
|
// if resp.StatusCode != http.StatusOK {
|
||||||
if err != nil {
|
// return nil, fmt.Errorf("failed to list files: %s", resp.Status)
|
||||||
return err
|
// }
|
||||||
}
|
// //parse response
|
||||||
if len(items_db) != len(files) {
|
// body, err := io.ReadAll(resp.Body)
|
||||||
var founds []string
|
// if err != nil {
|
||||||
for _, item := range items_db {
|
// fmt.Println("Error reading response body:", err)
|
||||||
formattedName := item.Id + item.Ext
|
// return nil, err
|
||||||
found := slices.Contains(files, formattedName)
|
// }
|
||||||
if found {
|
// var data []FileMetadata
|
||||||
founds = append(founds, formattedName)
|
// err = json.Unmarshal(body, &data)
|
||||||
} else {
|
// if err != nil {
|
||||||
err := queries.StorageDelete(item.Id)
|
// fmt.Println("Error unmarshalling response body:", err)
|
||||||
if err != nil {
|
// return nil, err
|
||||||
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 data, nil
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
func ListBucket(bucketName string) ([]FileMetadata, error) {
|
||||||
|
//get list of files from api
|
||||||
|
resp, err := http.Get(EXAMPLE_STORAGE_URL + "/bucket/" + bucketName + "?token=" + EXAMPLE_STORAGE_TOKEN)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("failed to list files: %s", resp.Status)
|
||||||
}
|
}
|
||||||
|
//parse response
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error reading response body:", err)
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil
|
var data []FileMetadata
|
||||||
|
err = json.Unmarshal(body, &data)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error unmarshalling response body:", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TempFolderCleaner(limit int) error {
|
// type CheckFileResponse struct {
|
||||||
files, err := os.ReadDir(filepath.Join(config.Cfg.Storage.Path, "/tmp/"))
|
// Available []FileMetadata `json:"available"`
|
||||||
if err != nil {
|
// Unavailable []string `json:"unavailable"`
|
||||||
return err
|
// }
|
||||||
}
|
|
||||||
fmt.Println("Temp files: ", len(files))
|
|
||||||
if len(files) >= limit {
|
|
||||||
|
|
||||||
var oldest_Time time.Time
|
// func CheckFiles(ids []string) ([]FileMetadata, []string, error) {
|
||||||
var oldest_File string
|
|
||||||
for _, file := range files {
|
// //application/json with array of ids as body of post request to /files-check
|
||||||
file_infos, err := file.Info()
|
// jsonData, err := json.Marshal(ids)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, nil, err
|
||||||
|
// }
|
||||||
|
// resp, err := http.Post(EXAMPLE_STORAGE_URL+"/files-check?token="+EXAMPLE_STORAGE_TOKEN,
|
||||||
|
// "application/json", bytes.NewBuffer(jsonData))
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, nil, err
|
||||||
|
// }
|
||||||
|
// defer resp.Body.Close()
|
||||||
|
|
||||||
|
// if resp.StatusCode != http.StatusOK {
|
||||||
|
// return nil, nil, fmt.Errorf("failed to check files: %s", resp.Status)
|
||||||
|
// }
|
||||||
|
// //parse response
|
||||||
|
// body, err := io.ReadAll(resp.Body)
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println("Error reading response body:", err)
|
||||||
|
// return nil, nil, err
|
||||||
|
// }
|
||||||
|
// var data CheckFileResponse
|
||||||
|
// err = json.Unmarshal(body, &data)
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println("Error unmarshalling response body:", err)
|
||||||
|
// return nil, nil, err
|
||||||
|
// }
|
||||||
|
// return data.Available, data.Unavailable, nil
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
func UploadFile(path string) (string, error) {
|
||||||
|
|
||||||
|
file, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", fmt.Errorf("failed to open file: %w", err)
|
||||||
}
|
}
|
||||||
if oldest_Time.IsZero() || file_infos.ModTime().Before(oldest_Time) {
|
defer file.Close()
|
||||||
oldest_Time = file_infos.ModTime()
|
|
||||||
oldest_File = file.Name()
|
var body bytes.Buffer
|
||||||
}
|
writer := multipart.NewWriter(&body)
|
||||||
}
|
|
||||||
err = os.Remove(filepath.Join(config.Cfg.Storage.Path, "/tmp/", oldest_File))
|
mimeType := "image/webp"
|
||||||
|
|
||||||
|
h := make(textproto.MIMEHeader)
|
||||||
|
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, filepath.Base(path)))
|
||||||
|
h.Set("Content-Type", mimeType)
|
||||||
|
part, err := writer.CreatePart(h)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", fmt.Errorf("failed to create form file: %w", err)
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(part, file); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to copy file data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = writer.WriteField("bucket", "images")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to write bucket field: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to close writer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", EXAMPLE_STORAGE_URL+"/upload?token="+EXAMPLE_STORAGE_TOKEN, &body)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create upload request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("upload request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", fmt.Errorf("upload failed: %s", resp.Status)
|
||||||
|
}
|
||||||
|
//parse response
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read upload response: %w", err)
|
||||||
|
}
|
||||||
|
var data map[string]string
|
||||||
|
err = json.Unmarshal(respBody, &data)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse upload response: %w", err)
|
||||||
|
}
|
||||||
|
if id, ok := data["id"]; ok {
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
package storagehandlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"backend/config"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"mime/multipart"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ StorageHandler = (*Fs_Handler)(nil)
|
|
||||||
|
|
||||||
type Fs_Handler struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFsHandler() *Fs_Handler {
|
|
||||||
return &Fs_Handler{}
|
|
||||||
}
|
|
||||||
func (f *Fs_Handler) Init() error {
|
|
||||||
if _, err := os.Stat(config.Cfg.Storage.Path); os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fs_Handler) Add(file *multipart.FileHeader, fileId string) error {
|
|
||||||
src, err := file.Open()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error opening file: %v\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer src.Close()
|
|
||||||
|
|
||||||
extension := filepath.Ext(file.Filename)
|
|
||||||
destPath := filepath.Join(config.Cfg.Storage.Path, fileId+extension)
|
|
||||||
dst, err := os.Create(destPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error creating file")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer dst.Close()
|
|
||||||
if _, err = io.Copy(dst, src); err != nil {
|
|
||||||
log.Println("Error copying file")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fs_Handler) Delete(filename string) error {
|
|
||||||
filePath := filepath.Join(config.Cfg.Storage.Path, filename)
|
|
||||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.Remove(filePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fs_Handler) Get(filename string) (string, error) {
|
|
||||||
path := filepath.Join(config.Cfg.Storage.Path, filename)
|
|
||||||
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fs_Handler) List() ([]string, error) {
|
|
||||||
files, err := os.ReadDir(config.Cfg.Storage.Path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var filenames []string
|
|
||||||
for _, file := range files {
|
|
||||||
filenames = append(filenames, file.Name())
|
|
||||||
}
|
|
||||||
return filenames, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
package storagehandlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"backend/config"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ StorageHandler = (*GOFS_Handler)(nil)
|
|
||||||
|
|
||||||
type GOFS_Handler struct {
|
|
||||||
Endpoint string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGoFsHandler() *GOFS_Handler {
|
|
||||||
|
|
||||||
return &GOFS_Handler{
|
|
||||||
Endpoint: config.Cfg.Storage.GOFS.Endpoint,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (g *GOFS_Handler) Init() error {
|
|
||||||
resp, err := http.Get(g.Endpoint + "/health")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("storage server unreachable: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("storage server unhealthy: %s", resp.Status)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (g *GOFS_Handler) Add(file *multipart.FileHeader, fileId string) error {
|
|
||||||
f, err := file.Open()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
body := &bytes.Buffer{}
|
|
||||||
writer := multipart.NewWriter(body)
|
|
||||||
part, err := writer.CreateFormFile("file", file.Filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := io.Copy(part, f); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_ = writer.WriteField("fileId", fileId)
|
|
||||||
writer.Close()
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", g.Endpoint+"/upload", body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("upload failed: %s", resp.Status)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GOFS_Handler) Delete(fileId string) error {
|
|
||||||
req, err := http.NewRequest("DELETE", g.Endpoint+"/delete/"+fileId, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode != http.StatusNoContent {
|
|
||||||
return fmt.Errorf("delete failed: %s", resp.Status)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GOFS_Handler) Get(filename string) (string, error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GOFS_Handler) List() ([]string, error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
@ -1,135 +0,0 @@
|
||||||
package storagehandlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"backend/config"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"mime/multipart"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7"
|
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
useSSL = false
|
|
||||||
bucketName = "storage"
|
|
||||||
)
|
|
||||||
var _ StorageHandler = (*S3_Handler)(nil)
|
|
||||||
|
|
||||||
type S3_Handler struct {
|
|
||||||
minioClient *minio.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S3_Handler) Init() error {
|
|
||||||
found, err := s.minioClient.BucketExists(context.Background(), bucketName)
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
err = s.minioClient.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{})
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Println("Bucket created successfully")
|
|
||||||
}
|
|
||||||
log.Println("Bucket found")
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewS3Handler() (*S3_Handler, error) {
|
|
||||||
|
|
||||||
minioClient, err := minio.New(config.Cfg.Storage.Minio.URL, &minio.Options{
|
|
||||||
Creds: credentials.NewStaticV4(config.Cfg.Storage.Minio.RootUser, config.Cfg.Storage.Minio.Password, ""),
|
|
||||||
Secure: useSSL,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
return &S3_Handler{
|
|
||||||
minioClient,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S3_Handler) Add(file *multipart.FileHeader, fileId string) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
src, err := file.Open()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error opening file: %v\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer src.Close()
|
|
||||||
|
|
||||||
extension := filepath.Ext(file.Filename)
|
|
||||||
objectName := fileId + extension
|
|
||||||
|
|
||||||
info, err := s.minioClient.PutObject(ctx, bucketName, objectName, src, file.Size, minio.PutObjectOptions{ContentType: "application/octet-stream"})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("Successfully uploaded %s of size %d\n", objectName, info.Size)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S3_Handler) Delete(filename string) error {
|
|
||||||
opts := minio.RemoveObjectOptions{
|
|
||||||
GovernanceBypass: true,
|
|
||||||
}
|
|
||||||
err := s.minioClient.RemoveObject(context.Background(), bucketName, filename, opts)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// func (s *S3_Handler) DeleteM(filename string) error {
|
|
||||||
// opts := minio.RemoveObjectOptions{
|
|
||||||
// GovernanceBypass: true,
|
|
||||||
// }
|
|
||||||
// err := s.minioClient.RemoveObjects(context.Background(), bucketName, filename, opts)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Fatalln(err)
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (s *S3_Handler) Get(filename string) (string, error) {
|
|
||||||
localPath := filepath.Join(config.Cfg.Storage.Path, "/tmp/", filename)
|
|
||||||
|
|
||||||
err := s.minioClient.FGetObject(context.Background(), bucketName, filename, localPath, minio.GetObjectOptions{})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return "", fmt.Errorf("error downloading file %s: %w", filename, err)
|
|
||||||
}
|
|
||||||
log.Printf("Successfully downloaded %s\n", filename)
|
|
||||||
return localPath, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S3_Handler) List() ([]string, error) {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
objectCh := s.minioClient.ListObjects(ctx, bucketName, minio.ListObjectsOptions{
|
|
||||||
Prefix: "",
|
|
||||||
Recursive: true,
|
|
||||||
})
|
|
||||||
var list []string
|
|
||||||
for object := range objectCh {
|
|
||||||
if object.Err != nil {
|
|
||||||
log.Println(object.Err)
|
|
||||||
return nil, object.Err
|
|
||||||
}
|
|
||||||
list = append(list, object.Key)
|
|
||||||
}
|
|
||||||
return list, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
package storagehandlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"backend/config"
|
|
||||||
"backend/typesdefs"
|
|
||||||
|
|
||||||
"errors"
|
|
||||||
"mime/multipart"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StorageHandler interface {
|
|
||||||
Init() error
|
|
||||||
Add(file *multipart.FileHeader, fileId string) error
|
|
||||||
Delete(filename string) error
|
|
||||||
Get(filename string) (string, error)
|
|
||||||
List() ([]string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStorageHandler(strategy typesdefs.StorageStrategy) (StorageHandler, error) {
|
|
||||||
if config.Cfg.Storage.Path == "" {
|
|
||||||
return nil, errors.New("storage path not set")
|
|
||||||
}
|
|
||||||
switch strategy {
|
|
||||||
case typesdefs.FS:
|
|
||||||
h := NewFsHandler()
|
|
||||||
err := h.Init()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return h, nil
|
|
||||||
|
|
||||||
case typesdefs.S3:
|
|
||||||
h, err := NewS3Handler()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = h.Init()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return h, nil
|
|
||||||
case typesdefs.GOFS:
|
|
||||||
return nil, nil
|
|
||||||
default:
|
|
||||||
return nil, errors.New("invalid storage strategy")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -257,10 +257,8 @@ type AnnuncioParsed struct {
|
||||||
Titolo_en *string
|
Titolo_en *string
|
||||||
Unita_condominio *string
|
Unita_condominio *string
|
||||||
Numero_vani *string
|
Numero_vani *string
|
||||||
Url_foto *[]string
|
|
||||||
Url_video *[]string
|
Url_video *[]string
|
||||||
Web *bool
|
Web *bool
|
||||||
Og_url *string
|
|
||||||
Disponibile_da *string
|
Disponibile_da *string
|
||||||
Permanenza *[]int
|
Permanenza *[]int
|
||||||
Persone *[]string
|
Persone *[]string
|
||||||
|
|
@ -309,23 +307,16 @@ type AnnunciDB struct {
|
||||||
Stato *string
|
Stato *string
|
||||||
Web *bool
|
Web *bool
|
||||||
Caratteristiche *map[string]interface{}
|
Caratteristiche *map[string]interface{}
|
||||||
Url_immagini *[]string
|
|
||||||
Homepage *bool
|
Homepage *bool
|
||||||
Url_video *[]string
|
Url_video *[]string
|
||||||
Email *string
|
Email *string
|
||||||
Creato_il *time.Time
|
Creato_il *time.Time
|
||||||
Modificato_il *time.Time
|
Modificato_il *time.Time
|
||||||
Og_url *string
|
|
||||||
Disponibile_da *time.Time
|
Disponibile_da *time.Time
|
||||||
Permanenza *[]int
|
Permanenza *[]int
|
||||||
Persone *[]string
|
Persone *[]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilebrowserData struct {
|
|
||||||
Data []File
|
|
||||||
Folder *string
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
Name string
|
Name string
|
||||||
Size int64
|
Size int64
|
||||||
|
|
@ -359,16 +350,32 @@ type Categoria struct {
|
||||||
Nome string
|
Nome string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Storageindex struct {
|
type ImagesRefs struct {
|
||||||
Id string
|
Codice string
|
||||||
Created_at time.Time
|
Ordine int
|
||||||
Filename string
|
Img string
|
||||||
Ext string
|
Thumb string
|
||||||
From_admin bool
|
Og_url string
|
||||||
Expires_at *time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type MiogestImagesRef_Table struct {
|
type ImagesToProcess struct {
|
||||||
Codice string
|
Codice string
|
||||||
Foto *[]string
|
Url string
|
||||||
|
Order int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProcessedImage struct {
|
||||||
|
Codice string
|
||||||
|
Order int
|
||||||
|
Path string
|
||||||
|
ThumnailPath string
|
||||||
|
OGUrl string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadedImage struct {
|
||||||
|
Codice string
|
||||||
|
Order int
|
||||||
|
ImgId string
|
||||||
|
ThumbId string
|
||||||
|
OGUrl string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package typesdefs
|
|
||||||
|
|
||||||
type StorageStrategy string
|
|
||||||
|
|
||||||
const (
|
|
||||||
FS StorageStrategy = "fs"
|
|
||||||
S3 StorageStrategy = "s3"
|
|
||||||
GOFS StorageStrategy = "gofs"
|
|
||||||
)
|
|
||||||
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
@ -21,21 +20,6 @@ func MakeUppercase(s string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckStringIfPresent(value *string) string {
|
|
||||||
if value != nil {
|
|
||||||
return strings.TrimSpace(*value)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckStringIfPresentBool(value string) bool {
|
|
||||||
if value != "" {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchXML(url string) ([]byte, error) {
|
func fetchXML(url string) ([]byte, error) {
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -70,36 +54,6 @@ func GetDataXML[T any](url string) T {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPathsArray(path string) ([]string, error) {
|
|
||||||
var paths []string
|
|
||||||
|
|
||||||
err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !info.IsDir() {
|
|
||||||
paths = append(paths, path)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return paths, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SafeRemove(path string) error {
|
|
||||||
err := os.Remove(path)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func SafeRemoveAll(path string) error {
|
func SafeRemoveAll(path string) error {
|
||||||
err := os.RemoveAll(path)
|
err := os.RemoveAll(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -111,32 +65,6 @@ func SafeRemoveAll(path string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func PathExists(path string) (bool, error) {
|
|
||||||
_, err := os.Stat(path)
|
|
||||||
if err == nil {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPathDirsArray(path string) []string {
|
|
||||||
var folderNames []string
|
|
||||||
files, err := os.ReadDir(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
if file.IsDir() {
|
|
||||||
folderNames = append(folderNames, file.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return folderNames
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetStringValue(strPtr *string) string {
|
func GetStringValue(strPtr *string) string {
|
||||||
if strPtr != nil {
|
if strPtr != nil {
|
||||||
return *strPtr
|
return *strPtr
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ const OnboardingServizio = ({
|
||||||
alt={`Annuncio image - ${annuncio.codice}`}
|
alt={`Annuncio image - ${annuncio.codice}`}
|
||||||
className="block w-[240px] rounded-[4px] object-cover object-center"
|
className="block w-[240px] rounded-[4px] object-cover object-center"
|
||||||
height="140px"
|
height="140px"
|
||||||
src={`${env.BASE_URL}/go-api/images/get/${annuncio.immagine}`}
|
src={`${env.BASE_URL}/storage-api/get/${annuncio.immagine}?image=true`}
|
||||||
width="100%"
|
width="100%"
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ async function createNextConfig(): Promise<NextConfig> {
|
||||||
localeDetection: false,
|
localeDetection: false,
|
||||||
},
|
},
|
||||||
images: {
|
images: {
|
||||||
|
unoptimized: true,
|
||||||
minimumCacheTTL: 7200,
|
minimumCacheTTL: 7200,
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
{
|
{
|
||||||
|
|
@ -50,7 +51,7 @@ async function createNextConfig(): Promise<NextConfig> {
|
||||||
protocol: "https",
|
protocol: "https",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hostname: `${env.BACKENDSERVER_URL}`,
|
hostname: `${env.STORAGE_URL}`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -63,10 +64,6 @@ async function createNextConfig(): Promise<NextConfig> {
|
||||||
destination: env.NODE_ENV === "production" ? "/404" : "/api/panel",
|
destination: env.NODE_ENV === "production" ? "/404" : "/api/panel",
|
||||||
source: "/api/panel",
|
source: "/api/panel",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
destination: `${env.BACKENDSERVER_URL}/images/get/:slug*`,
|
|
||||||
source: "/go-api/images/get/:slug*",
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export const AnnunciGrid = ({ pagedata }: { pagedata: AnnuncioRicerca[] }) => {
|
||||||
comune={annuncio.comune}
|
comune={annuncio.comune}
|
||||||
consegna={annuncio.consegna}
|
consegna={annuncio.consegna}
|
||||||
id={annuncio.id}
|
id={annuncio.id}
|
||||||
immagini={annuncio.url_immagini || undefined}
|
images={annuncio.images}
|
||||||
key={annuncio.codice}
|
key={annuncio.codice}
|
||||||
mq={annuncio.mq}
|
mq={annuncio.mq}
|
||||||
prezzo={annuncio.prezzo}
|
prezzo={annuncio.prezzo}
|
||||||
|
|
|
||||||
|
|
@ -118,12 +118,12 @@ const SelectedComp = memo(
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{selected.url_immagini?.[0] ? (
|
{selected.images?.[0] ? (
|
||||||
<Image
|
<Image
|
||||||
alt="a"
|
alt="a"
|
||||||
className="size-24 rounded-md object-cover sm:size-40"
|
className="size-24 rounded-md object-cover sm:size-40"
|
||||||
height={500}
|
height={500}
|
||||||
src={`${selected.url_immagini[0]}?${selected.updated_at?.toString() || new Date().toString()}`}
|
src={`/storage-api/get/${selected.images[0].img}?image=true&${selected.updated_at?.toString() || new Date().toString()}`}
|
||||||
width={500}
|
width={500}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,13 @@ type CardAnnuncioProps = {
|
||||||
provincia: string | null;
|
provincia: string | null;
|
||||||
consegna: number | null;
|
consegna: number | null;
|
||||||
camere: number | null;
|
camere: number | null;
|
||||||
immagini: string[] | undefined;
|
//immagini: string[] | undefined;
|
||||||
tipo: string | null;
|
tipo: string | null;
|
||||||
stato: string | null;
|
stato: string | null;
|
||||||
className?: string;
|
className?: string;
|
||||||
videos?: string[] | undefined;
|
videos?: string[] | undefined;
|
||||||
updated_at: Date | null;
|
updated_at: Date | null;
|
||||||
|
images: { img: string; thumb: string }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CardAnnuncio = ({
|
export const CardAnnuncio = ({
|
||||||
|
|
@ -51,12 +52,13 @@ export const CardAnnuncio = ({
|
||||||
provincia,
|
provincia,
|
||||||
consegna,
|
consegna,
|
||||||
camere,
|
camere,
|
||||||
immagini,
|
//immagini,
|
||||||
tipo,
|
tipo,
|
||||||
stato,
|
stato,
|
||||||
className,
|
className,
|
||||||
videos,
|
videos,
|
||||||
updated_at,
|
updated_at,
|
||||||
|
images,
|
||||||
}: CardAnnuncioProps) => {
|
}: CardAnnuncioProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|
@ -73,7 +75,7 @@ export const CardAnnuncio = ({
|
||||||
//target="_blank"
|
//target="_blank"
|
||||||
href={`/annuncio/${codice}`} //duration-700 ease-in-out animate-in fade-in
|
href={`/annuncio/${codice}`} //duration-700 ease-in-out animate-in fade-in
|
||||||
>
|
>
|
||||||
<div className="group relative text-clip rounded-md">
|
<div className="group relative text-clip rounded-md outline outline-neutral-100">
|
||||||
{stato === "Trattativa" && (
|
{stato === "Trattativa" && (
|
||||||
<div>
|
<div>
|
||||||
<div className="absolute z-20 h-56 w-full">
|
<div className="absolute z-20 h-56 w-full">
|
||||||
|
|
@ -86,16 +88,15 @@ export const CardAnnuncio = ({
|
||||||
|
|
||||||
<Carousel opts={{ loop: true }}>
|
<Carousel opts={{ loop: true }}>
|
||||||
<CarouselContent>
|
<CarouselContent>
|
||||||
{immagini?.map((img, idx) => (
|
{images?.map((img, idx) => (
|
||||||
<CarouselItem key={`${img}-${idx}`}>
|
<CarouselItem className="" key={`${img}-${idx}`}>
|
||||||
<ImageFlbk
|
<ImageFlbk
|
||||||
alt={t.card.alt_immagine}
|
alt={t.card.alt_immagine}
|
||||||
className={
|
blurDataURL={`/storage-api/get/${img.thumb}?image=true`}
|
||||||
"h-56 w-full rounded-md object-cover outline outline-neutral-200"
|
className={"h-56 w-full rounded-md object-cover"}
|
||||||
}
|
|
||||||
height={400}
|
height={400}
|
||||||
priority={idx === 0}
|
priority={idx === 0}
|
||||||
src={`${img}?${updated_at?.toString() || new Date().toString()}`}
|
src={`/storage-api/get/${img.img}?image=true`}
|
||||||
width={800}
|
width={800}
|
||||||
/>
|
/>
|
||||||
</CarouselItem>
|
</CarouselItem>
|
||||||
|
|
@ -117,7 +118,11 @@ export const CardAnnuncio = ({
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
cacheKey={updated_at?.toString() || new Date().toString()}
|
cacheKey={updated_at?.toString() || new Date().toString()}
|
||||||
className="h-56"
|
className="h-56"
|
||||||
coverImage={immagini?.[0] ? immagini[0] : ""}
|
coverImage={
|
||||||
|
images?.[0]
|
||||||
|
? `/storage-api/get/${images[0].img}?image=true`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
key={`videoplayer-${idx}`}
|
key={`videoplayer-${idx}`}
|
||||||
videoSrc={video}
|
videoSrc={video}
|
||||||
/>
|
/>
|
||||||
|
|
@ -135,11 +140,11 @@ export const CardAnnuncio = ({
|
||||||
>
|
>
|
||||||
<CarouselPrevious
|
<CarouselPrevious
|
||||||
className="left-2 cursor-pointer"
|
className="left-2 cursor-pointer"
|
||||||
disabled={!immagini || immagini.length === 1}
|
disabled={!images || images.length === 1}
|
||||||
/>
|
/>
|
||||||
<CarouselNext
|
<CarouselNext
|
||||||
className="right-2 cursor-pointer"
|
className="right-2 cursor-pointer"
|
||||||
disabled={!immagini || immagini.length === 1}
|
disabled={!images || images.length === 1}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Carousel>
|
</Carousel>
|
||||||
|
|
@ -274,7 +279,7 @@ export const CarouselAnnuncio = ({
|
||||||
height={400}
|
height={400}
|
||||||
onClick={() => handleOpenModal(idx)}
|
onClick={() => handleOpenModal(idx)}
|
||||||
priority={idx === 0}
|
priority={idx === 0}
|
||||||
src={`${img}?${updated_at?.toString() || new Date().toString()}`}
|
src={`/storage-api/get/${img}?image=true&${updated_at?.toString() || new Date().toString()}`}
|
||||||
width={800}
|
width={800}
|
||||||
/>
|
/>
|
||||||
<Maximize2
|
<Maximize2
|
||||||
|
|
@ -301,7 +306,11 @@ export const CarouselAnnuncio = ({
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
cacheKey={updated_at?.toString() || new Date().toString()}
|
cacheKey={updated_at?.toString() || new Date().toString()}
|
||||||
className="h-72"
|
className="h-72"
|
||||||
coverImage={immagini?.[0] ? immagini[0] : ""}
|
coverImage={
|
||||||
|
immagini?.[0]
|
||||||
|
? `/storage-api/get/${immagini[0]}?image=true`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
key={`videoplayer-${idx}-${openModal}`}
|
key={`videoplayer-${idx}-${openModal}`}
|
||||||
videoSrc={video}
|
videoSrc={video}
|
||||||
/>
|
/>
|
||||||
|
|
@ -344,7 +353,7 @@ export const CarouselAnnuncio = ({
|
||||||
alt={`carousel-img-${idx}`}
|
alt={`carousel-img-${idx}`}
|
||||||
className="mx-auto h-[90vh] w-full rounded-md object-contain sm:w-[80vw]"
|
className="mx-auto h-[90vh] w-full rounded-md object-contain sm:w-[80vw]"
|
||||||
height={1080}
|
height={1080}
|
||||||
src={`${img}?${updated_at?.toString() || new Date().toString()}`}
|
src={`/storage-api/get/${img}?image=true&${updated_at?.toString() || new Date().toString()}`}
|
||||||
width={1920}
|
width={1920}
|
||||||
/>
|
/>
|
||||||
</CarouselItem>
|
</CarouselItem>
|
||||||
|
|
@ -362,7 +371,11 @@ export const CarouselAnnuncio = ({
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
cacheKey={updated_at?.toString() || new Date().toString()}
|
cacheKey={updated_at?.toString() || new Date().toString()}
|
||||||
className="absolute mx-auto h-[90vh] w-full max-w-fit rounded-md object-contain"
|
className="absolute mx-auto h-[90vh] w-full max-w-fit rounded-md object-contain"
|
||||||
coverImage={immagini?.[0] ? immagini[0] : ""}
|
coverImage={
|
||||||
|
immagini?.[0]
|
||||||
|
? `/storage-api/get/${immagini[0]}?image=true`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
videoSrc={video}
|
videoSrc={video}
|
||||||
/>
|
/>
|
||||||
</CarouselItem>
|
</CarouselItem>
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ type AnnuncioData = Pick<
|
||||||
| "prezzo"
|
| "prezzo"
|
||||||
| "desc_en"
|
| "desc_en"
|
||||||
| "desc_it"
|
| "desc_it"
|
||||||
| "url_immagini"
|
|
||||||
| "titolo_en"
|
| "titolo_en"
|
||||||
| "titolo_it"
|
| "titolo_it"
|
||||||
| "tipo"
|
| "tipo"
|
||||||
|
|
@ -45,7 +44,9 @@ type AnnuncioData = Pick<
|
||||||
| "stato"
|
| "stato"
|
||||||
| "web"
|
| "web"
|
||||||
| "updated_at"
|
| "updated_at"
|
||||||
>;
|
> & {
|
||||||
|
images: Pick<{ img: string; thumb: string }, "img">[];
|
||||||
|
};
|
||||||
export const AnnuncioCard = ({ className }: { className?: string }) => {
|
export const AnnuncioCard = ({ className }: { className?: string }) => {
|
||||||
const { data } = useServizioAnnuncio();
|
const { data } = useServizioAnnuncio();
|
||||||
|
|
||||||
|
|
@ -88,8 +89,8 @@ export const BasicAnnuncioCard = ({
|
||||||
height={1080}
|
height={1080}
|
||||||
priority
|
priority
|
||||||
src={
|
src={
|
||||||
(data.url_immagini &&
|
(data?.images[0] &&
|
||||||
`/go-api/images/get/${data.url_immagini[0]}?${data.updated_at?.toString() || new Date().toString()}`) ||
|
`/storage-api/get/${data.images[0].img}?image=true&${data.updated_at?.toString() || new Date().toISOString()}`) ||
|
||||||
"/fallback-image.png"
|
"/fallback-image.png"
|
||||||
}
|
}
|
||||||
width={1920}
|
width={1920}
|
||||||
|
|
@ -179,14 +180,14 @@ const AnnuncioDettaglio = ({ data }: { data: AnnuncioData }) => {
|
||||||
<div className="flex flex-col gap-2 px-2">
|
<div className="flex flex-col gap-2 px-2">
|
||||||
<Carousel opts={{ loop: true }}>
|
<Carousel opts={{ loop: true }}>
|
||||||
<CarouselContent>
|
<CarouselContent>
|
||||||
{data?.url_immagini?.map((img, idx) => (
|
{data?.images?.map((img, idx) => (
|
||||||
<CarouselItem key={img}>
|
<CarouselItem key={img.img}>
|
||||||
<ImageFlbk
|
<ImageFlbk
|
||||||
alt={`${data.codice} - ${idx}`}
|
alt={`${data.codice} - ${idx}`}
|
||||||
className={"h-80 w-full object-contain sm:h-[35rem]"}
|
className={"h-80 w-full object-contain sm:h-[35rem]"}
|
||||||
height={200}
|
height={200}
|
||||||
priority={idx === 0}
|
priority={idx === 0}
|
||||||
src={`/go-api/images/get/${img}?${data.updated_at?.toString() || new Date().toString()}`}
|
src={`/storage-api/get/${img.img}?image=true&${data.updated_at?.toString() || new Date().toString()}`}
|
||||||
width={400}
|
width={400}
|
||||||
/>
|
/>
|
||||||
</CarouselItem>
|
</CarouselItem>
|
||||||
|
|
@ -201,15 +202,11 @@ const AnnuncioDettaglio = ({ data }: { data: AnnuncioData }) => {
|
||||||
>
|
>
|
||||||
<CarouselPrevious
|
<CarouselPrevious
|
||||||
className="left-2 cursor-pointer"
|
className="left-2 cursor-pointer"
|
||||||
disabled={
|
disabled={!data.images || data.images.length === 1}
|
||||||
!data.url_immagini || data.url_immagini.length === 1
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<CarouselNext
|
<CarouselNext
|
||||||
className="right-2 cursor-pointer"
|
className="right-2 cursor-pointer"
|
||||||
disabled={
|
disabled={!data.images || data.images.length === 1}
|
||||||
!data.url_immagini || data.url_immagini.length === 1
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Carousel>
|
</Carousel>
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,12 @@ import { cn } from "~/lib/utils";
|
||||||
import { useZodForm } from "~/lib/zodForm";
|
import { useZodForm } from "~/lib/zodForm";
|
||||||
import { StatusBadge } from "~/pages/area-riservata/admin/edit-annuncio/[id]";
|
import { StatusBadge } from "~/pages/area-riservata/admin/edit-annuncio/[id]";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import type { Annunci } from "~/schemas/public/Annunci";
|
import type { AnnunciWithImages } from "~/server/controllers/annunci.controller";
|
||||||
import { zAnnuncioId } from "~/server/utils/zod_types";
|
import { zAnnuncioId } from "~/server/utils/zod_types";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import type { Caratteristiche } from "~/utils/kanel-types";
|
import type { Caratteristiche } from "~/utils/kanel-types";
|
||||||
|
|
||||||
export const AnnuncioEditForm = ({ data }: { data: Annunci }) => {
|
export const AnnuncioEditForm = ({ data }: { data: AnnunciWithImages }) => {
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
accessori: z.string().array().nullable(),
|
accessori: z.string().array().nullable(),
|
||||||
|
|
||||||
|
|
@ -534,11 +534,7 @@ export const AnnuncioEditForm = ({ data }: { data: Annunci }) => {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<CarouselAnnuncio
|
<CarouselAnnuncio
|
||||||
immagini={
|
immagini={data.images?.map((v) => v.img) || "fallback"}
|
||||||
data.url_immagini?.map(
|
|
||||||
(v) => `/go-api/images/get/${v}`,
|
|
||||||
) || null
|
|
||||||
}
|
|
||||||
single
|
single
|
||||||
updated_at={data.updated_at}
|
updated_at={data.updated_at}
|
||||||
videos={data.url_video}
|
videos={data.url_video}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ export async function uploadFile(
|
||||||
if (expiresAt) {
|
if (expiresAt) {
|
||||||
formData.append("expires_at", expiresAt);
|
formData.append("expires_at", expiresAt);
|
||||||
}
|
}
|
||||||
|
formData.append("bucket", "storage");
|
||||||
|
|
||||||
const response = await fetch(`/storage-api/upload`, {
|
const response = await fetch(`/storage-api/upload`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export const config = {
|
||||||
{ key: "purpose", type: "header", value: "prefetch" },
|
{ key: "purpose", type: "header", value: "prefetch" },
|
||||||
],
|
],
|
||||||
source:
|
source:
|
||||||
"/((?!api/trpc|go-api/images|api/tiles|api/auth|_next/static|_next/image|screenshots|site.webmanifest|favicon.ico|favicon|sitemap.xml|robots.txt).*)",
|
"/((?!api/trpc|storage-api|api/tiles|api/auth|_next/static|_next/image|screenshots|site.webmanifest|favicon.ico|favicon|sitemap.xml|robots.txt).*)",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,25 +6,48 @@ import { verifyToken } from "~/server/auth/jwt";
|
||||||
export const apisMiddleware = async (req: NextRequest) => {
|
export const apisMiddleware = async (req: NextRequest) => {
|
||||||
const { pathname, searchParams } = req.nextUrl;
|
const { pathname, searchParams } = req.nextUrl;
|
||||||
console.log("APIs Middleware triggered for:", pathname);
|
console.log("APIs Middleware triggered for:", pathname);
|
||||||
|
|
||||||
|
// Only handle storage API routes
|
||||||
|
if (!pathname.startsWith("/storage-api/")) {
|
||||||
|
return null; // Pass to next middleware
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams(searchParams);
|
||||||
|
const isImageRequest = params.get("image") === "true";
|
||||||
|
const isVideoRequest = params.get("video") === "true";
|
||||||
|
|
||||||
|
// Check if this is a Next.js Image Optimization request
|
||||||
|
const purpose = req.headers.get("purpose");
|
||||||
|
const isNextImageRequest =
|
||||||
|
purpose === "prefetch" || req.headers.get("x-vercel-id");
|
||||||
|
|
||||||
|
// For regular requests (not from Next Image Optimization), check auth
|
||||||
|
if (!isNextImageRequest && !isImageRequest && !isVideoRequest) {
|
||||||
const accessToken = req.cookies.get(
|
const accessToken = req.cookies.get(
|
||||||
TOKEN_CONFIG.ACCESS_TOKEN_COOKIE_NAME,
|
TOKEN_CONFIG.ACCESS_TOKEN_COOKIE_NAME,
|
||||||
)?.value;
|
)?.value;
|
||||||
|
|
||||||
if (!accessToken) {
|
if (!accessToken) {
|
||||||
|
console.log("No access token found");
|
||||||
return new NextResponse("Unauthorized", { status: 401 });
|
return new NextResponse("Unauthorized", { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = await verifyToken(accessToken);
|
const payload = await verifyToken(accessToken);
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
|
console.log("Invalid token");
|
||||||
return new NextResponse("Unauthorized", { status: 401 });
|
return new NextResponse("Unauthorized", { status: 401 });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build the storage API URL correctly
|
// Build the storage API URL correctly
|
||||||
const slug = pathname.replace("/storage-api/", "");
|
const slug = pathname.replace("/storage-api/", "");
|
||||||
|
|
||||||
// Preserve existing query params and add token
|
|
||||||
const params = new URLSearchParams(searchParams);
|
|
||||||
params.set("token", env.STORAGE_TOKEN);
|
params.set("token", env.STORAGE_TOKEN);
|
||||||
|
|
||||||
const storageUrl = `${env.STORAGE_URL}/${slug}?${params.toString()}`;
|
const storageUrl = `${env.STORAGE_URL}/${slug}?${params.toString()}`;
|
||||||
|
|
||||||
|
console.log("Proxying to:", storageUrl);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Forward the request to storage API
|
// Forward the request to storage API
|
||||||
const headers = new Headers(req.headers);
|
const headers = new Headers(req.headers);
|
||||||
|
|
@ -40,14 +63,53 @@ export const apisMiddleware = async (req: NextRequest) => {
|
||||||
duplex: "half",
|
duplex: "half",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle failed responses
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error("Storage API error:", response.status);
|
console.error(`Storage API error: ${response.status} for ${storageUrl}`);
|
||||||
|
|
||||||
|
// Return fallback for image requests
|
||||||
|
if (isImageRequest || isNextImageRequest) {
|
||||||
|
const url = req.nextUrl.clone();
|
||||||
|
url.pathname = "/fallback-image.png";
|
||||||
|
url.search = "";
|
||||||
|
return NextResponse.rewrite(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return fallback for video requests
|
||||||
|
if (isVideoRequest) {
|
||||||
|
const url = req.nextUrl.clone();
|
||||||
|
url.pathname = "/fallback-video.png";
|
||||||
|
url.search = "";
|
||||||
|
return NextResponse.rewrite(url);
|
||||||
|
}
|
||||||
|
|
||||||
return new NextResponse("Storage API error", { status: response.status });
|
return new NextResponse("Storage API error", { status: response.status });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get response data
|
// Get response data
|
||||||
const data = await response.arrayBuffer();
|
const data = await response.arrayBuffer();
|
||||||
|
|
||||||
|
// Check if we got valid data
|
||||||
|
if (!data || data.byteLength === 0) {
|
||||||
|
console.error("Received empty response from storage API");
|
||||||
|
|
||||||
|
if (isImageRequest || isNextImageRequest) {
|
||||||
|
const url = req.nextUrl.clone();
|
||||||
|
url.pathname = "/fallback-image.png";
|
||||||
|
url.search = "";
|
||||||
|
return NextResponse.rewrite(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isVideoRequest) {
|
||||||
|
const url = req.nextUrl.clone();
|
||||||
|
url.pathname = "/fallback-video.png";
|
||||||
|
url.search = "";
|
||||||
|
return NextResponse.rewrite(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NextResponse("Empty response", { status: 500 });
|
||||||
|
}
|
||||||
|
|
||||||
// Create response with proper headers
|
// Create response with proper headers
|
||||||
const proxyResponse = new NextResponse(data, {
|
const proxyResponse = new NextResponse(data, {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
|
|
@ -59,16 +121,24 @@ export const apisMiddleware = async (req: NextRequest) => {
|
||||||
const contentDisposition = response.headers.get("Content-Disposition");
|
const contentDisposition = response.headers.get("Content-Disposition");
|
||||||
const contentLength = response.headers.get("Content-Length");
|
const contentLength = response.headers.get("Content-Length");
|
||||||
|
|
||||||
if (contentType) proxyResponse.headers.set("Content-Type", contentType);
|
if (contentType) {
|
||||||
if (contentDisposition)
|
proxyResponse.headers.set("Content-Type", contentType);
|
||||||
proxyResponse.headers.set("Content-Disposition", contentDisposition);
|
}
|
||||||
if (contentLength)
|
|
||||||
proxyResponse.headers.set("Content-Length", contentLength);
|
|
||||||
|
|
||||||
// Set CORS headers
|
if (contentDisposition) {
|
||||||
proxyResponse.headers.set("Access-Control-Allow-Origin", env.BASE_URL);
|
proxyResponse.headers.set("Content-Disposition", contentDisposition);
|
||||||
proxyResponse.headers.set("Access-Control-Allow-Credentials", "true");
|
}
|
||||||
proxyResponse.headers.delete("X-Frame-Options"); // Remove if set by storage API
|
if (contentLength) {
|
||||||
|
proxyResponse.headers.set("Content-Length", contentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set CORS and caching headers
|
||||||
|
proxyResponse.headers.set("Access-Control-Allow-Origin", "*");
|
||||||
|
proxyResponse.headers.set(
|
||||||
|
"Cache-Control",
|
||||||
|
"public, max-age=31536000, immutable",
|
||||||
|
);
|
||||||
|
proxyResponse.headers.delete("X-Frame-Options");
|
||||||
proxyResponse.headers.set(
|
proxyResponse.headers.set(
|
||||||
"Content-Security-Policy",
|
"Content-Security-Policy",
|
||||||
"frame-ancestors 'self'",
|
"frame-ancestors 'self'",
|
||||||
|
|
@ -77,6 +147,22 @@ export const apisMiddleware = async (req: NextRequest) => {
|
||||||
return proxyResponse;
|
return proxyResponse;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Storage proxy error:", error);
|
console.error("Storage proxy error:", error);
|
||||||
|
|
||||||
|
// Return fallback on error
|
||||||
|
if (isImageRequest || isNextImageRequest) {
|
||||||
|
const url = req.nextUrl.clone();
|
||||||
|
url.pathname = "/fallback-image.png";
|
||||||
|
url.search = "";
|
||||||
|
return NextResponse.rewrite(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isVideoRequest) {
|
||||||
|
const url = req.nextUrl.clone();
|
||||||
|
url.pathname = "/fallback-video.png";
|
||||||
|
url.search = "";
|
||||||
|
return NextResponse.rewrite(url);
|
||||||
|
}
|
||||||
|
|
||||||
return new NextResponse("Failed to proxy request", { status: 500 });
|
return new NextResponse("Failed to proxy request", { status: 500 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ const AnnuncioView = ({ cod, flag }: Omit<AnnuncioProps, "meta">) => {
|
||||||
<TouchProvider>
|
<TouchProvider>
|
||||||
<div className="mx-auto w-full px-2 sm:px-8">
|
<div className="mx-auto w-full px-2 sm:px-8">
|
||||||
<CarouselAnnuncio
|
<CarouselAnnuncio
|
||||||
immagini={data.url_immagini}
|
immagini={data.images.map((img) => img.img)}
|
||||||
updated_at={data.updated_at}
|
updated_at={data.updated_at}
|
||||||
videos={data.url_video}
|
videos={data.url_video}
|
||||||
/>
|
/>
|
||||||
|
|
@ -202,7 +202,11 @@ const AnnuncioView = ({ cod, flag }: Omit<AnnuncioProps, "meta">) => {
|
||||||
<AnnuncioFooter
|
<AnnuncioFooter
|
||||||
comune={data.comune}
|
comune={data.comune}
|
||||||
consegna={data.consegna}
|
consegna={data.consegna}
|
||||||
first_image={data.url_immagini?.[0] ? data.url_immagini[0] : ""}
|
first_image={
|
||||||
|
data.images?.[0]
|
||||||
|
? `/storage-api/get/${data.images[0]}?image=true`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
tipo={data.tipo}
|
tipo={data.tipo}
|
||||||
updated_at={data.updated_at}
|
updated_at={data.updated_at}
|
||||||
url_video={data.url_video || []}
|
url_video={data.url_video || []}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
@ -147,6 +148,15 @@ export default Test;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const Test: NextPage = () => {
|
const Test: NextPage = () => {
|
||||||
return <div>Test page</div>;
|
return (
|
||||||
|
<div>
|
||||||
|
Test page
|
||||||
|
<Image
|
||||||
|
alt="asdad"
|
||||||
|
fill
|
||||||
|
src="/storage-api/get/0134fd282f36395a?image=true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export default Test;
|
export default Test;
|
||||||
|
|
|
||||||
|
|
@ -91,8 +91,6 @@ export default interface AnnunciTable {
|
||||||
|
|
||||||
caratteristiche: ColumnType<Caratteristiche | null, Caratteristiche | null, Caratteristiche | null>;
|
caratteristiche: ColumnType<Caratteristiche | null, Caratteristiche | null, Caratteristiche | null>;
|
||||||
|
|
||||||
url_immagini: ColumnType<string[] | null, string[] | null, string[] | null>;
|
|
||||||
|
|
||||||
homepage: ColumnType<boolean | null, boolean | null, boolean | null>;
|
homepage: ColumnType<boolean | null, boolean | null, boolean | null>;
|
||||||
|
|
||||||
url_video: ColumnType<string[] | null, string[] | null, string[] | null>;
|
url_video: ColumnType<string[] | null, string[] | null, string[] | null>;
|
||||||
|
|
@ -105,8 +103,6 @@ export default interface AnnunciTable {
|
||||||
|
|
||||||
consegna: ColumnType<number | null, number | null, number | null>;
|
consegna: ColumnType<number | null, number | null, number | null>;
|
||||||
|
|
||||||
og_url: ColumnType<string | null, string | null, string | null>;
|
|
||||||
|
|
||||||
disponibile_da: ColumnType<Date | null, Date | string | null, Date | string | null>;
|
disponibile_da: ColumnType<Date | null, Date | string | null, Date | string | null>;
|
||||||
|
|
||||||
permanenza: ColumnType<number[] | null, number[] | null, number[] | null>;
|
permanenza: ColumnType<number[] | null, number[] | null, number[] | null>;
|
||||||
|
|
|
||||||
23
apps/infoalloggi/src/schemas/public/ImagesRefs.ts
Normal file
23
apps/infoalloggi/src/schemas/public/ImagesRefs.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
// @generated
|
||||||
|
// This file is automatically generated by Kanel. Do not modify manually.
|
||||||
|
|
||||||
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
|
|
||||||
|
/** Represents the table public.images_refs */
|
||||||
|
export default interface ImagesRefsTable {
|
||||||
|
codice: ColumnType<string, string, string>;
|
||||||
|
|
||||||
|
ordine: ColumnType<number, number, number>;
|
||||||
|
|
||||||
|
img: ColumnType<string, string, string>;
|
||||||
|
|
||||||
|
thumb: ColumnType<string, string, string>;
|
||||||
|
|
||||||
|
og_url: ColumnType<string, string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ImagesRefs = Selectable<ImagesRefsTable>;
|
||||||
|
|
||||||
|
export type NewImagesRefs = Insertable<ImagesRefsTable>;
|
||||||
|
|
||||||
|
export type ImagesRefsUpdate = Updateable<ImagesRefsTable>;
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
// @generated
|
|
||||||
// This file is automatically generated by Kanel. Do not modify manually.
|
|
||||||
|
|
||||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
|
||||||
|
|
||||||
/** Identifier type for public.miogest_images_ref */
|
|
||||||
export type MiogestImagesRefCodice = string & { __brand: 'public.miogest_images_ref' };
|
|
||||||
|
|
||||||
/** Represents the table public.miogest_images_ref */
|
|
||||||
export default interface MiogestImagesRefTable {
|
|
||||||
codice: ColumnType<MiogestImagesRefCodice, MiogestImagesRefCodice, MiogestImagesRefCodice>;
|
|
||||||
|
|
||||||
foto: ColumnType<string[] | null, string[] | null, string[] | null>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MiogestImagesRef = Selectable<MiogestImagesRefTable>;
|
|
||||||
|
|
||||||
export type NewMiogestImagesRef = Insertable<MiogestImagesRefTable>;
|
|
||||||
|
|
||||||
export type MiogestImagesRefUpdate = Updateable<MiogestImagesRefTable>;
|
|
||||||
|
|
@ -11,7 +11,6 @@ import type { default as UsersStorageTable } from './UsersStorage';
|
||||||
import type { default as ServizioAnnunciTable } from './ServizioAnnunci';
|
import type { default as ServizioAnnunciTable } from './ServizioAnnunci';
|
||||||
import type { default as EmailsTable } from './Emails';
|
import type { default as EmailsTable } from './Emails';
|
||||||
import type { default as PaymentsTable } from './Payments';
|
import type { default as PaymentsTable } from './Payments';
|
||||||
import type { default as MiogestImagesRefTable } from './MiogestImagesRef';
|
|
||||||
import type { default as TempTokensTable } from './TempTokens';
|
import type { default as TempTokensTable } from './TempTokens';
|
||||||
import type { default as EventQueueTable } from './EventQueue';
|
import type { default as EventQueueTable } from './EventQueue';
|
||||||
import type { default as OrdiniTable } from './Ordini';
|
import type { default as OrdiniTable } from './Ordini';
|
||||||
|
|
@ -22,6 +21,7 @@ import type { default as EtichetteTable } from './Etichette';
|
||||||
import type { default as MessagesTable } from './Messages';
|
import type { default as MessagesTable } from './Messages';
|
||||||
import type { default as UsersAnagraficaTable } from './UsersAnagrafica';
|
import type { default as UsersAnagraficaTable } from './UsersAnagrafica';
|
||||||
import type { default as BanlistTable } from './Banlist';
|
import type { default as BanlistTable } from './Banlist';
|
||||||
|
import type { default as ImagesRefsTable } from './ImagesRefs';
|
||||||
import type { default as BannersTable } from './Banners';
|
import type { default as BannersTable } from './Banners';
|
||||||
import type { default as ServizioTable } from './Servizio';
|
import type { default as ServizioTable } from './Servizio';
|
||||||
|
|
||||||
|
|
@ -46,8 +46,6 @@ export default interface PublicSchema {
|
||||||
|
|
||||||
payments: PaymentsTable;
|
payments: PaymentsTable;
|
||||||
|
|
||||||
miogest_images_ref: MiogestImagesRefTable;
|
|
||||||
|
|
||||||
temp_tokens: TempTokensTable;
|
temp_tokens: TempTokensTable;
|
||||||
|
|
||||||
event_queue: EventQueueTable;
|
event_queue: EventQueueTable;
|
||||||
|
|
@ -68,6 +66,8 @@ export default interface PublicSchema {
|
||||||
|
|
||||||
banlist: BanlistTable;
|
banlist: BanlistTable;
|
||||||
|
|
||||||
|
images_refs: ImagesRefsTable;
|
||||||
|
|
||||||
banners: BannersTable;
|
banners: BannersTable;
|
||||||
|
|
||||||
servizio: ServizioTable;
|
servizio: ServizioTable;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import { jsonArrayFrom } from "kysely/helpers/postgres";
|
||||||
import z from "zod/v4";
|
import z from "zod/v4";
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
||||||
|
|
@ -89,13 +90,12 @@ export const intrestsRouter = createTRPCRouter({
|
||||||
}
|
}
|
||||||
const annunci = await db
|
const annunci = await db
|
||||||
.selectFrom("annunci")
|
.selectFrom("annunci")
|
||||||
.select([
|
.select((eb) => [
|
||||||
"annunci.id",
|
"annunci.id",
|
||||||
"annunci.codice",
|
"annunci.codice",
|
||||||
"annunci.prezzo",
|
"annunci.prezzo",
|
||||||
"annunci.desc_en",
|
"annunci.desc_en",
|
||||||
"annunci.desc_it",
|
"annunci.desc_it",
|
||||||
"annunci.url_immagini",
|
|
||||||
"annunci.titolo_en",
|
"annunci.titolo_en",
|
||||||
"annunci.titolo_it",
|
"annunci.titolo_it",
|
||||||
"annunci.tipo",
|
"annunci.tipo",
|
||||||
|
|
@ -103,6 +103,13 @@ export const intrestsRouter = createTRPCRouter({
|
||||||
"annunci.stato",
|
"annunci.stato",
|
||||||
"annunci.web",
|
"annunci.web",
|
||||||
"annunci.updated_at",
|
"annunci.updated_at",
|
||||||
|
jsonArrayFrom(
|
||||||
|
eb
|
||||||
|
.selectFrom("images_refs")
|
||||||
|
.select(["images_refs.img", "images_refs.thumb"])
|
||||||
|
.whereRef("images_refs.codice", "=", "annunci.codice")
|
||||||
|
.orderBy("images_refs.ordine", "asc"),
|
||||||
|
).as("images"),
|
||||||
])
|
])
|
||||||
.where("web", "=", true)
|
.where("web", "=", true)
|
||||||
.where("stato", "!=", "Sospeso")
|
.where("stato", "!=", "Sospeso")
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import { jsonArrayFrom } from "kysely/helpers/postgres";
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
import type {
|
import type {
|
||||||
Annunci,
|
Annunci,
|
||||||
AnnunciId,
|
AnnunciId,
|
||||||
AnnunciUpdate,
|
AnnunciUpdate,
|
||||||
} from "~/schemas/public/Annunci";
|
} from "~/schemas/public/Annunci";
|
||||||
|
import type { ImagesRefs } from "~/schemas/public/ImagesRefs";
|
||||||
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
||||||
import { db } from "~/server/db";
|
import { db } from "~/server/db";
|
||||||
import { AnnuncioObjectWithImages } from "~/server/services/annunci.service";
|
|
||||||
import { createSrcset } from "~/server/services/imageServer";
|
|
||||||
import { revalidate } from "../utils/revalidationHelper";
|
import { revalidate } from "../utils/revalidationHelper";
|
||||||
|
|
||||||
// const ratelimit = new RateLimiterHandler({
|
// const ratelimit = new RateLimiterHandler({
|
||||||
|
|
@ -16,6 +16,9 @@ import { revalidate } from "../utils/revalidationHelper";
|
||||||
// maxRequests: 10,
|
// maxRequests: 10,
|
||||||
// analytics: true,
|
// analytics: true,
|
||||||
// });
|
// });
|
||||||
|
export type AnnunciWithImages = Annunci & {
|
||||||
|
images: Pick<ImagesRefs, "img" | "thumb">[];
|
||||||
|
};
|
||||||
|
|
||||||
export const getAnnunciListHandler = async (): Promise<
|
export const getAnnunciListHandler = async (): Promise<
|
||||||
Pick<
|
Pick<
|
||||||
|
|
@ -69,11 +72,20 @@ export const getAnnunciByCod = async ({
|
||||||
cod,
|
cod,
|
||||||
}: {
|
}: {
|
||||||
cod: string;
|
cod: string;
|
||||||
}): Promise<Annunci | null> => {
|
}): Promise<AnnunciWithImages | null> => {
|
||||||
try {
|
try {
|
||||||
const annuncio = await db
|
const annuncio = await db
|
||||||
.selectFrom("annunci")
|
.selectFrom("annunci")
|
||||||
.selectAll()
|
.selectAll()
|
||||||
|
.select((eb) =>
|
||||||
|
jsonArrayFrom(
|
||||||
|
eb
|
||||||
|
.selectFrom("images_refs")
|
||||||
|
.whereRef("images_refs.codice", "=", "annunci.codice")
|
||||||
|
.select(["img", "thumb"])
|
||||||
|
.orderBy("images_refs.ordine", "asc"),
|
||||||
|
).as("images"),
|
||||||
|
)
|
||||||
.where("annunci.web", "=", true)
|
.where("annunci.web", "=", true)
|
||||||
.where("codice", "=", cod)
|
.where("codice", "=", cod)
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
|
|
@ -82,7 +94,7 @@ export const getAnnunciByCod = async ({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return AnnuncioObjectWithImages<typeof annuncio>(annuncio);
|
return annuncio;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
cause: (e as Error).cause,
|
cause: (e as Error).cause,
|
||||||
|
|
@ -95,7 +107,17 @@ export const getAnnunciMetaByCod = async ({ cod }: { cod: string }) => {
|
||||||
try {
|
try {
|
||||||
const annuncio = await db
|
const annuncio = await db
|
||||||
.selectFrom("annunci")
|
.selectFrom("annunci")
|
||||||
.select(["titolo_it", "desc_it", "og_url", "url_immagini"])
|
.select((eb) => [
|
||||||
|
"titolo_it",
|
||||||
|
"desc_it",
|
||||||
|
jsonArrayFrom(
|
||||||
|
eb
|
||||||
|
.selectFrom("images_refs")
|
||||||
|
.whereRef("images_refs.codice", "=", "annunci.codice")
|
||||||
|
.select(["img", "thumb"])
|
||||||
|
.orderBy("images_refs.ordine", "asc"),
|
||||||
|
).as("images"),
|
||||||
|
])
|
||||||
.where("annunci.web", "=", true)
|
.where("annunci.web", "=", true)
|
||||||
.where("codice", "=", cod)
|
.where("codice", "=", cod)
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
|
|
@ -103,11 +125,9 @@ export const getAnnunciMetaByCod = async ({ cod }: { cod: string }) => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const ogImage =
|
const ogImage =
|
||||||
annuncio.url_immagini &&
|
annuncio.images && annuncio.images.length > 0 && annuncio.images[0]
|
||||||
annuncio.url_immagini.length > 0 &&
|
? `${env.BASE_URL}/storage-api/get/${annuncio.images[0]}?image=true`
|
||||||
annuncio.url_immagini[0]
|
: `${env.BASE_URL}/og.jpg`;
|
||||||
? `${env.BASE_URL}/go-api/images/get/${annuncio.url_immagini[0]}`
|
|
||||||
: annuncio.og_url || "";
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
description: annuncio.desc_it || "",
|
description: annuncio.desc_it || "",
|
||||||
|
|
@ -127,11 +147,20 @@ export const getAnnunciById_rawImgUrls = async ({
|
||||||
id,
|
id,
|
||||||
}: {
|
}: {
|
||||||
id: AnnunciId;
|
id: AnnunciId;
|
||||||
}): Promise<Annunci | null> => {
|
}): Promise<AnnunciWithImages | null> => {
|
||||||
try {
|
try {
|
||||||
const annuncio = await db
|
const annuncio = await db
|
||||||
.selectFrom("annunci")
|
.selectFrom("annunci")
|
||||||
.selectAll()
|
.selectAll()
|
||||||
|
.select((eb) => [
|
||||||
|
jsonArrayFrom(
|
||||||
|
eb
|
||||||
|
.selectFrom("images_refs")
|
||||||
|
.select(["images_refs.img", "images_refs.thumb"])
|
||||||
|
.whereRef("images_refs.codice", "=", "annunci.codice")
|
||||||
|
.orderBy("images_refs.ordine", "asc"),
|
||||||
|
).as("images"),
|
||||||
|
])
|
||||||
.where("id", "=", id)
|
.where("id", "=", id)
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
if (!annuncio) {
|
if (!annuncio) {
|
||||||
|
|
@ -167,7 +196,7 @@ export const get_AnnunciPositionsHandler = async ({
|
||||||
try {
|
try {
|
||||||
let query = db
|
let query = db
|
||||||
.selectFrom("annunci")
|
.selectFrom("annunci")
|
||||||
.select([
|
.select((eb) => [
|
||||||
"id",
|
"id",
|
||||||
"codice",
|
"codice",
|
||||||
"comune",
|
"comune",
|
||||||
|
|
@ -179,13 +208,19 @@ export const get_AnnunciPositionsHandler = async ({
|
||||||
"tipo",
|
"tipo",
|
||||||
"titolo_it",
|
"titolo_it",
|
||||||
"titolo_en",
|
"titolo_en",
|
||||||
"url_immagini",
|
|
||||||
"modificato_il",
|
"modificato_il",
|
||||||
"stato",
|
"stato",
|
||||||
"url_video",
|
"url_video",
|
||||||
"lon_secondario",
|
"lon_secondario",
|
||||||
"lat_secondario",
|
"lat_secondario",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
|
jsonArrayFrom(
|
||||||
|
eb
|
||||||
|
.selectFrom("images_refs")
|
||||||
|
.whereRef("images_refs.codice", "=", "annunci.codice")
|
||||||
|
.select(["img", "thumb"])
|
||||||
|
.orderBy("images_refs.ordine", "asc"),
|
||||||
|
).as("images"),
|
||||||
])
|
])
|
||||||
|
|
||||||
.where("web", "=", true)
|
.where("web", "=", true)
|
||||||
|
|
@ -206,11 +241,6 @@ export const get_AnnunciPositionsHandler = async ({
|
||||||
if (!annunci) {
|
if (!annunci) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
for (const annuncio of annunci) {
|
|
||||||
annuncio.url_immagini = annuncio.url_immagini
|
|
||||||
? createSrcset(annuncio.url_immagini)
|
|
||||||
: [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return annunci;
|
return annunci;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -234,12 +264,13 @@ export type AnnuncioRicerca = Pick<
|
||||||
| "tipo"
|
| "tipo"
|
||||||
| "titolo_it"
|
| "titolo_it"
|
||||||
| "titolo_en"
|
| "titolo_en"
|
||||||
| "url_immagini"
|
|
||||||
| "modificato_il"
|
| "modificato_il"
|
||||||
| "stato"
|
| "stato"
|
||||||
| "url_video"
|
| "url_video"
|
||||||
| "updated_at"
|
| "updated_at"
|
||||||
>;
|
> & {
|
||||||
|
images: Pick<ImagesRefs, "img" | "thumb">[];
|
||||||
|
};
|
||||||
|
|
||||||
export const getCursor_AnnunciHandler = async ({
|
export const getCursor_AnnunciHandler = async ({
|
||||||
page = 0,
|
page = 0,
|
||||||
|
|
@ -259,7 +290,7 @@ export const getCursor_AnnunciHandler = async ({
|
||||||
try {
|
try {
|
||||||
let query = db
|
let query = db
|
||||||
.selectFrom("annunci")
|
.selectFrom("annunci")
|
||||||
.select([
|
.select((eb) => [
|
||||||
"id",
|
"id",
|
||||||
"codice",
|
"codice",
|
||||||
"comune",
|
"comune",
|
||||||
|
|
@ -271,11 +302,17 @@ export const getCursor_AnnunciHandler = async ({
|
||||||
"tipo",
|
"tipo",
|
||||||
"titolo_it",
|
"titolo_it",
|
||||||
"titolo_en",
|
"titolo_en",
|
||||||
"url_immagini",
|
|
||||||
"modificato_il",
|
"modificato_il",
|
||||||
"stato",
|
"stato",
|
||||||
"url_video",
|
"url_video",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
|
jsonArrayFrom(
|
||||||
|
eb
|
||||||
|
.selectFrom("images_refs")
|
||||||
|
.whereRef("images_refs.codice", "=", "annunci.codice")
|
||||||
|
.select(["img", "thumb"])
|
||||||
|
.orderBy("images_refs.ordine", "asc"),
|
||||||
|
).as("images"),
|
||||||
])
|
])
|
||||||
|
|
||||||
.where("web", "=", true)
|
.where("web", "=", true)
|
||||||
|
|
@ -319,12 +356,6 @@ export const getCursor_AnnunciHandler = async ({
|
||||||
annunci.pop(); // Remove the last item if it exceeds the page size
|
annunci.pop(); // Remove the last item if it exceeds the page size
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const annuncio of annunci) {
|
|
||||||
annuncio.url_immagini = annuncio.url_immagini
|
|
||||||
? createSrcset(annuncio.url_immagini)
|
|
||||||
: [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
annunci,
|
annunci,
|
||||||
hasMore,
|
hasMore,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/postgres";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
import type { PurchaseData } from "~/components/acquisto_receipt";
|
import type { PurchaseData } from "~/components/acquisto_receipt";
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
import type { Annunci, AnnunciId } from "~/schemas/public/Annunci";
|
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||||
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
||||||
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||||
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
||||||
|
|
@ -632,13 +632,12 @@ export const getAllServizioAnnunci = async (userId: UsersId) => {
|
||||||
.selectFrom("servizio_annunci")
|
.selectFrom("servizio_annunci")
|
||||||
.selectAll("servizio_annunci")
|
.selectAll("servizio_annunci")
|
||||||
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
|
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
|
||||||
.select([
|
.select((eb) => [
|
||||||
"annunci.id",
|
"annunci.id",
|
||||||
"annunci.codice",
|
"annunci.codice",
|
||||||
"annunci.prezzo",
|
"annunci.prezzo",
|
||||||
"annunci.desc_en",
|
"annunci.desc_en",
|
||||||
"annunci.desc_it",
|
"annunci.desc_it",
|
||||||
"annunci.url_immagini",
|
|
||||||
"annunci.titolo_en",
|
"annunci.titolo_en",
|
||||||
"annunci.titolo_it",
|
"annunci.titolo_it",
|
||||||
"annunci.tipo",
|
"annunci.tipo",
|
||||||
|
|
@ -646,6 +645,13 @@ export const getAllServizioAnnunci = async (userId: UsersId) => {
|
||||||
"annunci.stato",
|
"annunci.stato",
|
||||||
"annunci.web",
|
"annunci.web",
|
||||||
"annunci.updated_at",
|
"annunci.updated_at",
|
||||||
|
jsonArrayFrom(
|
||||||
|
eb
|
||||||
|
.selectFrom("images_refs")
|
||||||
|
.select(["images_refs.img", "images_refs.thumb"])
|
||||||
|
.whereRef("images_refs.codice", "=", "annunci.codice")
|
||||||
|
.orderBy("images_refs.ordine", "asc"),
|
||||||
|
).as("images"),
|
||||||
])
|
])
|
||||||
//ignora annunci che non sono disponibili
|
//ignora annunci che non sono disponibili
|
||||||
.where("annunci.web", "=", true)
|
.where("annunci.web", "=", true)
|
||||||
|
|
@ -1146,11 +1152,17 @@ export const sendServizioEmail = async ({
|
||||||
const annunci = await db
|
const annunci = await db
|
||||||
.selectFrom("servizio_annunci")
|
.selectFrom("servizio_annunci")
|
||||||
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
|
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
|
||||||
.select([
|
.select((eb) => [
|
||||||
"annunci.titolo_it",
|
"annunci.titolo_it",
|
||||||
"annunci.url_immagini",
|
|
||||||
"annunci.codice",
|
"annunci.codice",
|
||||||
"annunci.prezzo",
|
"annunci.prezzo",
|
||||||
|
jsonArrayFrom(
|
||||||
|
eb
|
||||||
|
.selectFrom("images_refs")
|
||||||
|
.select(["images_refs.img", "images_refs.thumb"])
|
||||||
|
.whereRef("images_refs.codice", "=", "annunci.codice")
|
||||||
|
.orderBy("images_refs.ordine", "asc"),
|
||||||
|
).as("images"),
|
||||||
])
|
])
|
||||||
.where("servizio_annunci.servizio_id", "=", servizioId)
|
.where("servizio_annunci.servizio_id", "=", servizioId)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
@ -1161,9 +1173,9 @@ export const sendServizioEmail = async ({
|
||||||
props: {
|
props: {
|
||||||
annunci: annunci.map((annuncio) => ({
|
annunci: annunci.map((annuncio) => ({
|
||||||
codice: annuncio.codice,
|
codice: annuncio.codice,
|
||||||
immagine: annuncio.url_immagini
|
immagine: annuncio?.images
|
||||||
? annuncio.url_immagini[0] || null
|
? annuncio.images[0]?.img || "fallback"
|
||||||
: null,
|
: "fallback",
|
||||||
prezzo: annuncio.prezzo,
|
prezzo: annuncio.prezzo,
|
||||||
titolo: annuncio.titolo_it,
|
titolo: annuncio.titolo_it,
|
||||||
})),
|
})),
|
||||||
|
|
@ -1464,23 +1476,6 @@ export const SendContactEmail = async ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AnnuncioData = Pick<
|
|
||||||
Annunci,
|
|
||||||
| "id"
|
|
||||||
| "codice"
|
|
||||||
| "prezzo"
|
|
||||||
| "desc_en"
|
|
||||||
| "desc_it"
|
|
||||||
| "url_immagini"
|
|
||||||
| "titolo_en"
|
|
||||||
| "titolo_it"
|
|
||||||
| "tipo"
|
|
||||||
| "consegna"
|
|
||||||
| "stato"
|
|
||||||
| "web"
|
|
||||||
| "updated_at"
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const getCompatibileAnnunci = async (servizioId: ServizioServizioId) => {
|
export const getCompatibileAnnunci = async (servizioId: ServizioServizioId) => {
|
||||||
try {
|
try {
|
||||||
const servizio = await getServizioById(servizioId);
|
const servizio = await getServizioById(servizioId);
|
||||||
|
|
@ -1493,13 +1488,12 @@ export const getCompatibileAnnunci = async (servizioId: ServizioServizioId) => {
|
||||||
|
|
||||||
let qry = db
|
let qry = db
|
||||||
.selectFrom("annunci")
|
.selectFrom("annunci")
|
||||||
.select([
|
.select((eb) => [
|
||||||
"annunci.id",
|
"annunci.id",
|
||||||
"annunci.codice",
|
"annunci.codice",
|
||||||
"annunci.prezzo",
|
"annunci.prezzo",
|
||||||
"annunci.desc_en",
|
"annunci.desc_en",
|
||||||
"annunci.desc_it",
|
"annunci.desc_it",
|
||||||
"annunci.url_immagini",
|
|
||||||
"annunci.titolo_en",
|
"annunci.titolo_en",
|
||||||
"annunci.titolo_it",
|
"annunci.titolo_it",
|
||||||
"annunci.tipo",
|
"annunci.tipo",
|
||||||
|
|
@ -1507,6 +1501,13 @@ export const getCompatibileAnnunci = async (servizioId: ServizioServizioId) => {
|
||||||
"annunci.stato",
|
"annunci.stato",
|
||||||
"annunci.web",
|
"annunci.web",
|
||||||
"annunci.updated_at",
|
"annunci.updated_at",
|
||||||
|
jsonArrayFrom(
|
||||||
|
eb
|
||||||
|
.selectFrom("images_refs")
|
||||||
|
.select(["images_refs.img", "images_refs.thumb"])
|
||||||
|
.whereRef("images_refs.codice", "=", "annunci.codice")
|
||||||
|
.orderBy("images_refs.ordine", "asc"),
|
||||||
|
).as("images"),
|
||||||
])
|
])
|
||||||
.where("web", "=", true)
|
.where("web", "=", true)
|
||||||
.where("stato", "!=", "Sospeso")
|
.where("stato", "!=", "Sospeso")
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import { createSrcset } from "~/server/services/imageServer";
|
|
||||||
|
|
||||||
type AnnuncioBase = {
|
|
||||||
url_immagini: string[] | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AnnuncioObjectWithImages = <T extends AnnuncioBase>(
|
|
||||||
annuncio: T,
|
|
||||||
): T => {
|
|
||||||
return {
|
|
||||||
...annuncio,
|
|
||||||
url_immagini: annuncio.url_immagini
|
|
||||||
? createSrcset(annuncio.url_immagini)
|
|
||||||
: [],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
import { env } from "~/env";
|
|
||||||
|
|
||||||
export const createSrcset = (url_immagini: string[]) => {
|
|
||||||
const finalSrcset = [];
|
|
||||||
for (const url of url_immagini) {
|
|
||||||
//finalSrcset.push(`https://${env.BACKENDSERVER_URL}/images/get/${url}`);
|
|
||||||
finalSrcset.push(`/go-api/images/get/${url}`);
|
|
||||||
}
|
|
||||||
return finalSrcset;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createSrc = (immagine: string, ext?: boolean) => {
|
|
||||||
return `${ext ? "https://infoalloggi.it" : env.NEXT_PUBLIC_BASE_URL}/go-api/images/get/${immagine}`;
|
|
||||||
};
|
|
||||||
|
|
@ -21,7 +21,7 @@ export type FileMetadataWithAdmin = FileMetadata & {
|
||||||
export async function fetchFiles(): Promise<FileMetadata[]> {
|
export async function fetchFiles(): Promise<FileMetadata[]> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${env.STORAGE_URL}/files?token=${env.STORAGE_TOKEN}`,
|
`${env.STORAGE_URL}/bucket/storage?token=${env.STORAGE_TOKEN}`,
|
||||||
);
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error("Failed to fetch file list:", response.statusText);
|
console.error("Failed to fetch file list:", response.statusText);
|
||||||
|
|
|
||||||
|
|
@ -19,24 +19,41 @@ services:
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|
||||||
minio:
|
# minio:
|
||||||
image: minio/minio
|
# image: minio/minio
|
||||||
expose:
|
# expose:
|
||||||
- "9000"
|
# - "9000"
|
||||||
- "9001"
|
# - "9001"
|
||||||
|
# volumes:
|
||||||
|
# - minio_storage:/data
|
||||||
|
# networks:
|
||||||
|
# - dokploy-network
|
||||||
|
# environment:
|
||||||
|
# MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||||
|
# MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||||
|
# command: server --console-address ":9001" /data
|
||||||
|
# healthcheck:
|
||||||
|
# test: [ "CMD", "curl", "-f", "http://minio:9000/minio/health/live" ]
|
||||||
|
# interval: 10s
|
||||||
|
# timeout: 10s
|
||||||
|
# retries: 5
|
||||||
|
storage:
|
||||||
|
image: "${IMAGE_REGISTRY:-ghcr.io}/${OWNER:-marcopedone}/go_fs:${TAG:-latest}"
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
environment:
|
||||||
|
PORT: 8080
|
||||||
|
AUTH_TOKEN: ${STORAGE_TOKEN}
|
||||||
volumes:
|
volumes:
|
||||||
- minio_storage:/data
|
- storage_data:/app/storage
|
||||||
networks:
|
networks:
|
||||||
- dokploy-network
|
- dokploy-network
|
||||||
environment:
|
|
||||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
|
||||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
|
||||||
command: server --console-address ":9001" /data
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "curl", "-f", "http://minio:9000/minio/health/live" ]
|
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 10s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
tiles:
|
tiles:
|
||||||
image: eqalpha/keydb:latest
|
image: eqalpha/keydb:latest
|
||||||
|
|
@ -144,6 +161,7 @@ services:
|
||||||
JWT_SECRET: ${JWT_SECRET}
|
JWT_SECRET: ${JWT_SECRET}
|
||||||
SKEBBY_USER: ${SKEBBY_USER}
|
SKEBBY_USER: ${SKEBBY_USER}
|
||||||
SKEBBY_PASS: ${SKEBBY_PASS}
|
SKEBBY_PASS: ${SKEBBY_PASS}
|
||||||
|
STORAGE_URL: http://storage:8080
|
||||||
STORAGE_TOKEN: ${STORAGE_TOKEN}
|
STORAGE_TOKEN: ${STORAGE_TOKEN}
|
||||||
networks:
|
networks:
|
||||||
- dokploy-network
|
- dokploy-network
|
||||||
|
|
@ -164,7 +182,7 @@ networks:
|
||||||
external: true
|
external: true
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
minio_storage:
|
storage_data:
|
||||||
images_volume:
|
images_volume:
|
||||||
storage_volume:
|
storage_volume:
|
||||||
tiles-data:
|
tiles-data:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue