mega update storage
This commit is contained in:
parent
c517dbcc7a
commit
48b31a5491
77 changed files with 2037 additions and 1716 deletions
|
|
@ -14,15 +14,6 @@ import (
|
||||||
|
|
||||||
// Config holds all application configuration
|
// Config holds all application configuration
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Database struct {
|
|
||||||
Host string
|
|
||||||
Port int
|
|
||||||
User string
|
|
||||||
Password string
|
|
||||||
DBName string
|
|
||||||
SSLMode string
|
|
||||||
}
|
|
||||||
|
|
||||||
Images struct {
|
Images struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
ConcurrentLimit int
|
ConcurrentLimit int
|
||||||
|
|
@ -61,14 +52,6 @@ func Load() *Config {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Database config
|
|
||||||
Cfg.Database.Host = getEnv("PGHOST", "localhost")
|
|
||||||
Cfg.Database.Port = getEnvAsInt("PGPORT", 5432)
|
|
||||||
Cfg.Database.User = getEnv("POSTGRES_USER", "postgres")
|
|
||||||
Cfg.Database.Password = getEnv("POSTGRES_PASSWORD", "")
|
|
||||||
Cfg.Database.DBName = getEnv("POSTGRES_DB", "postgres")
|
|
||||||
Cfg.Database.SSLMode = getEnv("PGSSLMODE", "true")
|
|
||||||
|
|
||||||
// Image processing config
|
// Image processing config
|
||||||
Cfg.Images.Enabled = getEnvAsBool("IMAGEOPTION", true)
|
Cfg.Images.Enabled = getEnvAsBool("IMAGEOPTION", true)
|
||||||
Cfg.Images.ConcurrentLimit = getEnvAsInt("CONCURRENT_IMAGES", 5)
|
Cfg.Images.ConcurrentLimit = getEnvAsInt("CONCURRENT_IMAGES", 5)
|
||||||
|
|
@ -86,10 +69,6 @@ func Load() *Config {
|
||||||
// Server config
|
// Server config
|
||||||
Cfg.Server.Port = getEnvAsInt("PORT", 1323)
|
Cfg.Server.Port = getEnvAsInt("PORT", 1323)
|
||||||
|
|
||||||
// Validate required config
|
|
||||||
if Cfg.Database.Password == "" {
|
|
||||||
fmt.Printf("required environment variable POSTGRES_PASSWORD not set\n")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := exec.Command("ffmpeg", "-version").Run(); err != nil {
|
if err := exec.Command("ffmpeg", "-version").Run(); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
|
|
||||||
pgxuuid "github.com/jackc/pgx-gofrs-uuid"
|
|
||||||
"github.com/jackc/pgx/v5"
|
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DbHandler struct {
|
|
||||||
Dbpool *pgxpool.Pool
|
|
||||||
Ctx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
type Configs struct {
|
|
||||||
Host string
|
|
||||||
Dbname string
|
|
||||||
Port string
|
|
||||||
User string
|
|
||||||
Password string
|
|
||||||
LoggerEnabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDbHandler(configs Configs) *DbHandler {
|
|
||||||
fmt.Println("Connecting to db")
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
if configs.Host == "" || configs.Dbname == "" || configs.Port == "" || configs.User == "" || configs.Password == "" {
|
|
||||||
fmt.Println("Environment variables for database connection are not set")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
connStr := fmt.Sprintf("postgresql://%s:%s@%s:%s/%s?sslmode=disable", configs.User, configs.Password, configs.Host, configs.Port, configs.Dbname)
|
|
||||||
fmt.Printf("Db connection string: %s\n", connStr)
|
|
||||||
config, err := pgxpool.ParseConfig(connStr)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if configs.LoggerEnabled {
|
|
||||||
fmt.Println("Database query logging is enabled")
|
|
||||||
config.ConnConfig.Tracer = NewMultiQueryTracer(NewLoggingQueryTracer(slog.Default()))
|
|
||||||
}
|
|
||||||
|
|
||||||
config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
|
|
||||||
pgxuuid.Register(conn.TypeMap())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
dbpool, err := pgxpool.NewWithConfig(ctx, config)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Println("Connected to db")
|
|
||||||
|
|
||||||
return &DbHandler{Dbpool: dbpool, Ctx: ctx}
|
|
||||||
}
|
|
||||||
|
|
@ -1,136 +0,0 @@
|
||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5"
|
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
|
||||||
)
|
|
||||||
|
|
||||||
// obfuscate replaces the first n characters of a string with asterisks.
|
|
||||||
func obfuscate(s string, n int) string {
|
|
||||||
if len(s) <= n {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return s[:n] + strings.Repeat("*", len(s)-n)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectToDatabaseWithLogging connects to the database with logging. It tests
|
|
||||||
// the connection and returns the connection pool.
|
|
||||||
func ConnectToDatabaseWithLogging(ctx context.Context, connectionString string, loggingEnabled bool) (*pgxpool.Pool, error) {
|
|
||||||
// Parse the connection string
|
|
||||||
slog.
|
|
||||||
Info("parsing connection string",
|
|
||||||
slog.String("connection_string", obfuscate(connectionString, 10)),
|
|
||||||
)
|
|
||||||
config, err := pgxpool.ParseConfig(connectionString)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse connection string: %w", err)
|
|
||||||
}
|
|
||||||
// If logging is enabled, set the tracer
|
|
||||||
if loggingEnabled {
|
|
||||||
config.ConnConfig.Tracer = NewMultiQueryTracer(NewLoggingQueryTracer(slog.Default()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the connection pool
|
|
||||||
slog.Info("creating connection pool")
|
|
||||||
pool, err := pgxpool.NewWithConfig(ctx, config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create connection pool: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ping the database
|
|
||||||
slog.Info("pinging database")
|
|
||||||
if err := pool.Ping(ctx); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to ping database: %w", err)
|
|
||||||
}
|
|
||||||
return pool, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
replaceTabs = regexp.MustCompile(`\t+`)
|
|
||||||
replaceSpacesBeforeOpeningParens = regexp.MustCompile(`\s+\(`)
|
|
||||||
replaceSpacesAfterOpeningParens = regexp.MustCompile(`\(\s+`)
|
|
||||||
replaceSpacesBeforeClosingParens = regexp.MustCompile(`\s+\)`)
|
|
||||||
replaceSpacesAfterClosingParens = regexp.MustCompile(`\)\s+`)
|
|
||||||
replaceSpaces = regexp.MustCompile(`\s+`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// prettyPrintSQL removes empty lines and trims spaces.
|
|
||||||
func prettyPrintSQL(sql string) string {
|
|
||||||
lines := strings.Split(sql, "\n")
|
|
||||||
|
|
||||||
pretty := strings.Join(lines, " ")
|
|
||||||
pretty = replaceTabs.ReplaceAllString(pretty, "")
|
|
||||||
pretty = replaceSpacesBeforeOpeningParens.ReplaceAllString(pretty, "(")
|
|
||||||
pretty = replaceSpacesAfterOpeningParens.ReplaceAllString(pretty, "(")
|
|
||||||
pretty = replaceSpacesAfterClosingParens.ReplaceAllString(pretty, ")")
|
|
||||||
pretty = replaceSpacesBeforeClosingParens.ReplaceAllString(pretty, ")")
|
|
||||||
|
|
||||||
// Finally, replace multiple spaces with a single space
|
|
||||||
pretty = replaceSpaces.ReplaceAllString(pretty, " ")
|
|
||||||
|
|
||||||
return strings.TrimSpace(pretty)
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/jackc/pgx/issues/1061#issuecomment-1186250809
|
|
||||||
type LoggingQueryTracer struct {
|
|
||||||
logger *slog.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLoggingQueryTracer(logger *slog.Logger) *LoggingQueryTracer {
|
|
||||||
return &LoggingQueryTracer{logger: logger}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LoggingQueryTracer) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context {
|
|
||||||
l.logger.
|
|
||||||
Info("query start",
|
|
||||||
slog.String("sql", prettyPrintSQL(data.SQL)),
|
|
||||||
slog.Any("args", data.Args),
|
|
||||||
)
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LoggingQueryTracer) TraceQueryEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData) {
|
|
||||||
// Failure
|
|
||||||
if data.Err != nil {
|
|
||||||
l.logger.
|
|
||||||
Error("query end",
|
|
||||||
slog.String("error", data.Err.Error()),
|
|
||||||
slog.String("command_tag", data.CommandTag.String()),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success
|
|
||||||
l.logger.
|
|
||||||
Info("query end",
|
|
||||||
slog.String("command_tag", data.CommandTag.String()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/jackc/pgx/discussions/1677#discussioncomment-8815982
|
|
||||||
type MultiQueryTracer struct {
|
|
||||||
Tracers []pgx.QueryTracer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMultiQueryTracer(tracers ...pgx.QueryTracer) *MultiQueryTracer {
|
|
||||||
return &MultiQueryTracer{Tracers: tracers}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MultiQueryTracer) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context {
|
|
||||||
for _, t := range m.Tracers {
|
|
||||||
ctx = t.TraceQueryStart(ctx, conn, data)
|
|
||||||
}
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MultiQueryTracer) TraceQueryEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData) {
|
|
||||||
for _, t := range m.Tracers {
|
|
||||||
t.TraceQueryEnd(ctx, conn, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
68
apps/backend/gen/postgres/postgres/public/model/annunci.go
Normal file
68
apps/backend/gen/postgres/postgres/public/model/annunci.go
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
//
|
||||||
|
// Code generated by go-jet DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// WARNING: Changes to this file may cause incorrect behavior
|
||||||
|
// and will be lost if the code is regenerated
|
||||||
|
//
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/lib/pq"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Annunci struct {
|
||||||
|
ID int32 `sql:"primary_key"`
|
||||||
|
Codice string
|
||||||
|
Locatore *string
|
||||||
|
Numero *string
|
||||||
|
Idlocatore *string
|
||||||
|
Indirizzo *string
|
||||||
|
Civico *string
|
||||||
|
Comune *string
|
||||||
|
Cap *string
|
||||||
|
Provincia *string
|
||||||
|
Regione *string
|
||||||
|
Lat *string
|
||||||
|
Lon *string
|
||||||
|
IndirizzoSecondario *string
|
||||||
|
CivicoSecondario *string
|
||||||
|
LatSecondario *string
|
||||||
|
LonSecondario *string
|
||||||
|
Tipo *string
|
||||||
|
Categorie *pq.StringArray
|
||||||
|
Prezzo int32
|
||||||
|
Anno *string
|
||||||
|
Classe *string
|
||||||
|
Mq *float64
|
||||||
|
Piano *string
|
||||||
|
PianoPalazzo *int32
|
||||||
|
UnitaCondominio *int32
|
||||||
|
NumeroVani *int32
|
||||||
|
NumeroCamere *int32
|
||||||
|
NumeroBagni *int32
|
||||||
|
NumeroBalconi *int32
|
||||||
|
NumeroTerrazzi *int32
|
||||||
|
NumeroBox *int32
|
||||||
|
NumeroPostiauto *int32
|
||||||
|
Accessori *pq.StringArray
|
||||||
|
TitoloIt *string
|
||||||
|
DescIt *string
|
||||||
|
TitoloEn *string
|
||||||
|
DescEn *string
|
||||||
|
Stato *string
|
||||||
|
Web *bool
|
||||||
|
Caratteristiche *string // @type(Caratteristiche, 'src/utils/kanel-types.ts', false, false, true)
|
||||||
|
Homepage *bool
|
||||||
|
ExternalVideos *pq.StringArray
|
||||||
|
Email *string
|
||||||
|
CreatoIl *time.Time
|
||||||
|
ModificatoIl *time.Time
|
||||||
|
Consegna *int32
|
||||||
|
DisponibileDa *time.Time
|
||||||
|
Permanenza *pq.Int32Array
|
||||||
|
Persone *pq.StringArray
|
||||||
|
MediaUpdatedAt *time.Time
|
||||||
|
TipoLocatore *string
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
// Code generated by go-jet DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// WARNING: Changes to this file may cause incorrect behavior
|
||||||
|
// and will be lost if the code is regenerated
|
||||||
|
//
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ImagesRefs struct {
|
||||||
|
Codice string
|
||||||
|
Ordine int32
|
||||||
|
Img uuid.UUID
|
||||||
|
Thumb uuid.UUID
|
||||||
|
OgURL string
|
||||||
|
}
|
||||||
24
apps/backend/gen/postgres/postgres/public/model/storage.go
Normal file
24
apps/backend/gen/postgres/postgres/public/model/storage.go
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
//
|
||||||
|
// Code generated by go-jet DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// WARNING: Changes to this file may cause incorrect behavior
|
||||||
|
// and will be lost if the code is regenerated
|
||||||
|
//
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Storage struct {
|
||||||
|
ID uuid.UUID `sql:"primary_key"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
Filename string
|
||||||
|
MimeType string
|
||||||
|
FileSizeBytes int32
|
||||||
|
FileData []byte
|
||||||
|
ExpiresAt *time.Time
|
||||||
|
Tag *string
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
// Code generated by go-jet DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// WARNING: Changes to this file may cause incorrect behavior
|
||||||
|
// and will be lost if the code is regenerated
|
||||||
|
//
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VideosRefs struct {
|
||||||
|
Codice string
|
||||||
|
Ordine int32
|
||||||
|
Video uuid.UUID
|
||||||
|
Thumb uuid.UUID
|
||||||
|
OgURL string
|
||||||
|
}
|
||||||
231
apps/backend/gen/postgres/postgres/public/table/annunci.go
Normal file
231
apps/backend/gen/postgres/postgres/public/table/annunci.go
Normal file
|
|
@ -0,0 +1,231 @@
|
||||||
|
//
|
||||||
|
// Code generated by go-jet DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// WARNING: Changes to this file may cause incorrect behavior
|
||||||
|
// and will be lost if the code is regenerated
|
||||||
|
//
|
||||||
|
|
||||||
|
package table
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-jet/jet/v2/postgres"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Annunci = newAnnunciTable("public", "annunci", "")
|
||||||
|
|
||||||
|
type annunciTable struct {
|
||||||
|
postgres.Table
|
||||||
|
|
||||||
|
// Columns
|
||||||
|
ID postgres.ColumnInteger
|
||||||
|
Codice postgres.ColumnString
|
||||||
|
Locatore postgres.ColumnString
|
||||||
|
Numero postgres.ColumnString
|
||||||
|
Idlocatore postgres.ColumnString
|
||||||
|
Indirizzo postgres.ColumnString
|
||||||
|
Civico postgres.ColumnString
|
||||||
|
Comune postgres.ColumnString
|
||||||
|
Cap postgres.ColumnString
|
||||||
|
Provincia postgres.ColumnString
|
||||||
|
Regione postgres.ColumnString
|
||||||
|
Lat postgres.ColumnString
|
||||||
|
Lon postgres.ColumnString
|
||||||
|
IndirizzoSecondario postgres.ColumnString
|
||||||
|
CivicoSecondario postgres.ColumnString
|
||||||
|
LatSecondario postgres.ColumnString
|
||||||
|
LonSecondario postgres.ColumnString
|
||||||
|
Tipo postgres.ColumnString
|
||||||
|
Categorie postgres.ColumnStringArray
|
||||||
|
Prezzo postgres.ColumnInteger
|
||||||
|
Anno postgres.ColumnString
|
||||||
|
Classe postgres.ColumnString
|
||||||
|
Mq postgres.ColumnFloat
|
||||||
|
Piano postgres.ColumnString
|
||||||
|
PianoPalazzo postgres.ColumnInteger
|
||||||
|
UnitaCondominio postgres.ColumnInteger
|
||||||
|
NumeroVani postgres.ColumnInteger
|
||||||
|
NumeroCamere postgres.ColumnInteger
|
||||||
|
NumeroBagni postgres.ColumnInteger
|
||||||
|
NumeroBalconi postgres.ColumnInteger
|
||||||
|
NumeroTerrazzi postgres.ColumnInteger
|
||||||
|
NumeroBox postgres.ColumnInteger
|
||||||
|
NumeroPostiauto postgres.ColumnInteger
|
||||||
|
Accessori postgres.ColumnStringArray
|
||||||
|
TitoloIt postgres.ColumnString
|
||||||
|
DescIt postgres.ColumnString
|
||||||
|
TitoloEn postgres.ColumnString
|
||||||
|
DescEn postgres.ColumnString
|
||||||
|
Stato postgres.ColumnString
|
||||||
|
Web postgres.ColumnBool
|
||||||
|
Caratteristiche postgres.ColumnString // @type(Caratteristiche, 'src/utils/kanel-types.ts', false, false, true)
|
||||||
|
Homepage postgres.ColumnBool
|
||||||
|
ExternalVideos postgres.ColumnStringArray
|
||||||
|
Email postgres.ColumnString
|
||||||
|
CreatoIl postgres.ColumnTimestamp
|
||||||
|
ModificatoIl postgres.ColumnTimestamp
|
||||||
|
Consegna postgres.ColumnInteger
|
||||||
|
DisponibileDa postgres.ColumnDate
|
||||||
|
Permanenza postgres.ColumnIntegerArray
|
||||||
|
Persone postgres.ColumnStringArray
|
||||||
|
MediaUpdatedAt postgres.ColumnTimestamp
|
||||||
|
TipoLocatore postgres.ColumnString
|
||||||
|
|
||||||
|
AllColumns postgres.ColumnList
|
||||||
|
MutableColumns postgres.ColumnList
|
||||||
|
DefaultColumns postgres.ColumnList
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnnunciTable struct {
|
||||||
|
annunciTable
|
||||||
|
|
||||||
|
EXCLUDED annunciTable
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS creates new AnnunciTable with assigned alias
|
||||||
|
func (a AnnunciTable) AS(alias string) *AnnunciTable {
|
||||||
|
return newAnnunciTable(a.SchemaName(), a.TableName(), alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema creates new AnnunciTable with assigned schema name
|
||||||
|
func (a AnnunciTable) FromSchema(schemaName string) *AnnunciTable {
|
||||||
|
return newAnnunciTable(schemaName, a.TableName(), a.Alias())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPrefix creates new AnnunciTable with assigned table prefix
|
||||||
|
func (a AnnunciTable) WithPrefix(prefix string) *AnnunciTable {
|
||||||
|
return newAnnunciTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSuffix creates new AnnunciTable with assigned table suffix
|
||||||
|
func (a AnnunciTable) WithSuffix(suffix string) *AnnunciTable {
|
||||||
|
return newAnnunciTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAnnunciTable(schemaName, tableName, alias string) *AnnunciTable {
|
||||||
|
return &AnnunciTable{
|
||||||
|
annunciTable: newAnnunciTableImpl(schemaName, tableName, alias),
|
||||||
|
EXCLUDED: newAnnunciTableImpl("", "excluded", ""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAnnunciTableImpl(schemaName, tableName, alias string) annunciTable {
|
||||||
|
var (
|
||||||
|
IDColumn = postgres.IntegerColumn("id")
|
||||||
|
CodiceColumn = postgres.StringColumn("codice")
|
||||||
|
LocatoreColumn = postgres.StringColumn("locatore")
|
||||||
|
NumeroColumn = postgres.StringColumn("numero")
|
||||||
|
IdlocatoreColumn = postgres.StringColumn("idlocatore")
|
||||||
|
IndirizzoColumn = postgres.StringColumn("indirizzo")
|
||||||
|
CivicoColumn = postgres.StringColumn("civico")
|
||||||
|
ComuneColumn = postgres.StringColumn("comune")
|
||||||
|
CapColumn = postgres.StringColumn("cap")
|
||||||
|
ProvinciaColumn = postgres.StringColumn("provincia")
|
||||||
|
RegioneColumn = postgres.StringColumn("regione")
|
||||||
|
LatColumn = postgres.StringColumn("lat")
|
||||||
|
LonColumn = postgres.StringColumn("lon")
|
||||||
|
IndirizzoSecondarioColumn = postgres.StringColumn("indirizzo_secondario")
|
||||||
|
CivicoSecondarioColumn = postgres.StringColumn("civico_secondario")
|
||||||
|
LatSecondarioColumn = postgres.StringColumn("lat_secondario")
|
||||||
|
LonSecondarioColumn = postgres.StringColumn("lon_secondario")
|
||||||
|
TipoColumn = postgres.StringColumn("tipo")
|
||||||
|
CategorieColumn = postgres.StringArrayColumn("categorie")
|
||||||
|
PrezzoColumn = postgres.IntegerColumn("prezzo")
|
||||||
|
AnnoColumn = postgres.StringColumn("anno")
|
||||||
|
ClasseColumn = postgres.StringColumn("classe")
|
||||||
|
MqColumn = postgres.FloatColumn("mq")
|
||||||
|
PianoColumn = postgres.StringColumn("piano")
|
||||||
|
PianoPalazzoColumn = postgres.IntegerColumn("piano_palazzo")
|
||||||
|
UnitaCondominioColumn = postgres.IntegerColumn("unita_condominio")
|
||||||
|
NumeroVaniColumn = postgres.IntegerColumn("numero_vani")
|
||||||
|
NumeroCamereColumn = postgres.IntegerColumn("numero_camere")
|
||||||
|
NumeroBagniColumn = postgres.IntegerColumn("numero_bagni")
|
||||||
|
NumeroBalconiColumn = postgres.IntegerColumn("numero_balconi")
|
||||||
|
NumeroTerrazziColumn = postgres.IntegerColumn("numero_terrazzi")
|
||||||
|
NumeroBoxColumn = postgres.IntegerColumn("numero_box")
|
||||||
|
NumeroPostiautoColumn = postgres.IntegerColumn("numero_postiauto")
|
||||||
|
AccessoriColumn = postgres.StringArrayColumn("accessori")
|
||||||
|
TitoloItColumn = postgres.StringColumn("titolo_it")
|
||||||
|
DescItColumn = postgres.StringColumn("desc_it")
|
||||||
|
TitoloEnColumn = postgres.StringColumn("titolo_en")
|
||||||
|
DescEnColumn = postgres.StringColumn("desc_en")
|
||||||
|
StatoColumn = postgres.StringColumn("stato")
|
||||||
|
WebColumn = postgres.BoolColumn("web")
|
||||||
|
CaratteristicheColumn = postgres.StringColumn("caratteristiche")
|
||||||
|
HomepageColumn = postgres.BoolColumn("homepage")
|
||||||
|
ExternalVideosColumn = postgres.StringArrayColumn("external_videos")
|
||||||
|
EmailColumn = postgres.StringColumn("email")
|
||||||
|
CreatoIlColumn = postgres.TimestampColumn("creato_il")
|
||||||
|
ModificatoIlColumn = postgres.TimestampColumn("modificato_il")
|
||||||
|
ConsegnaColumn = postgres.IntegerColumn("consegna")
|
||||||
|
DisponibileDaColumn = postgres.DateColumn("disponibile_da")
|
||||||
|
PermanenzaColumn = postgres.IntegerArrayColumn("permanenza")
|
||||||
|
PersoneColumn = postgres.StringArrayColumn("persone")
|
||||||
|
MediaUpdatedAtColumn = postgres.TimestampColumn("media_updated_at")
|
||||||
|
TipoLocatoreColumn = postgres.StringColumn("tipo_locatore")
|
||||||
|
allColumns = postgres.ColumnList{IDColumn, CodiceColumn, LocatoreColumn, NumeroColumn, IdlocatoreColumn, IndirizzoColumn, CivicoColumn, ComuneColumn, CapColumn, ProvinciaColumn, RegioneColumn, LatColumn, LonColumn, IndirizzoSecondarioColumn, CivicoSecondarioColumn, LatSecondarioColumn, LonSecondarioColumn, TipoColumn, CategorieColumn, PrezzoColumn, AnnoColumn, ClasseColumn, MqColumn, PianoColumn, PianoPalazzoColumn, UnitaCondominioColumn, NumeroVaniColumn, NumeroCamereColumn, NumeroBagniColumn, NumeroBalconiColumn, NumeroTerrazziColumn, NumeroBoxColumn, NumeroPostiautoColumn, AccessoriColumn, TitoloItColumn, DescItColumn, TitoloEnColumn, DescEnColumn, StatoColumn, WebColumn, CaratteristicheColumn, HomepageColumn, ExternalVideosColumn, EmailColumn, CreatoIlColumn, ModificatoIlColumn, ConsegnaColumn, DisponibileDaColumn, PermanenzaColumn, PersoneColumn, MediaUpdatedAtColumn, TipoLocatoreColumn}
|
||||||
|
mutableColumns = postgres.ColumnList{CodiceColumn, LocatoreColumn, NumeroColumn, IdlocatoreColumn, IndirizzoColumn, CivicoColumn, ComuneColumn, CapColumn, ProvinciaColumn, RegioneColumn, LatColumn, LonColumn, IndirizzoSecondarioColumn, CivicoSecondarioColumn, LatSecondarioColumn, LonSecondarioColumn, TipoColumn, CategorieColumn, PrezzoColumn, AnnoColumn, ClasseColumn, MqColumn, PianoColumn, PianoPalazzoColumn, UnitaCondominioColumn, NumeroVaniColumn, NumeroCamereColumn, NumeroBagniColumn, NumeroBalconiColumn, NumeroTerrazziColumn, NumeroBoxColumn, NumeroPostiautoColumn, AccessoriColumn, TitoloItColumn, DescItColumn, TitoloEnColumn, DescEnColumn, StatoColumn, WebColumn, CaratteristicheColumn, HomepageColumn, ExternalVideosColumn, EmailColumn, CreatoIlColumn, ModificatoIlColumn, ConsegnaColumn, DisponibileDaColumn, PermanenzaColumn, PersoneColumn, MediaUpdatedAtColumn, TipoLocatoreColumn}
|
||||||
|
defaultColumns = postgres.ColumnList{IDColumn, PrezzoColumn}
|
||||||
|
)
|
||||||
|
|
||||||
|
return annunciTable{
|
||||||
|
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||||
|
|
||||||
|
//Columns
|
||||||
|
ID: IDColumn,
|
||||||
|
Codice: CodiceColumn,
|
||||||
|
Locatore: LocatoreColumn,
|
||||||
|
Numero: NumeroColumn,
|
||||||
|
Idlocatore: IdlocatoreColumn,
|
||||||
|
Indirizzo: IndirizzoColumn,
|
||||||
|
Civico: CivicoColumn,
|
||||||
|
Comune: ComuneColumn,
|
||||||
|
Cap: CapColumn,
|
||||||
|
Provincia: ProvinciaColumn,
|
||||||
|
Regione: RegioneColumn,
|
||||||
|
Lat: LatColumn,
|
||||||
|
Lon: LonColumn,
|
||||||
|
IndirizzoSecondario: IndirizzoSecondarioColumn,
|
||||||
|
CivicoSecondario: CivicoSecondarioColumn,
|
||||||
|
LatSecondario: LatSecondarioColumn,
|
||||||
|
LonSecondario: LonSecondarioColumn,
|
||||||
|
Tipo: TipoColumn,
|
||||||
|
Categorie: CategorieColumn,
|
||||||
|
Prezzo: PrezzoColumn,
|
||||||
|
Anno: AnnoColumn,
|
||||||
|
Classe: ClasseColumn,
|
||||||
|
Mq: MqColumn,
|
||||||
|
Piano: PianoColumn,
|
||||||
|
PianoPalazzo: PianoPalazzoColumn,
|
||||||
|
UnitaCondominio: UnitaCondominioColumn,
|
||||||
|
NumeroVani: NumeroVaniColumn,
|
||||||
|
NumeroCamere: NumeroCamereColumn,
|
||||||
|
NumeroBagni: NumeroBagniColumn,
|
||||||
|
NumeroBalconi: NumeroBalconiColumn,
|
||||||
|
NumeroTerrazzi: NumeroTerrazziColumn,
|
||||||
|
NumeroBox: NumeroBoxColumn,
|
||||||
|
NumeroPostiauto: NumeroPostiautoColumn,
|
||||||
|
Accessori: AccessoriColumn,
|
||||||
|
TitoloIt: TitoloItColumn,
|
||||||
|
DescIt: DescItColumn,
|
||||||
|
TitoloEn: TitoloEnColumn,
|
||||||
|
DescEn: DescEnColumn,
|
||||||
|
Stato: StatoColumn,
|
||||||
|
Web: WebColumn,
|
||||||
|
Caratteristiche: CaratteristicheColumn,
|
||||||
|
Homepage: HomepageColumn,
|
||||||
|
ExternalVideos: ExternalVideosColumn,
|
||||||
|
Email: EmailColumn,
|
||||||
|
CreatoIl: CreatoIlColumn,
|
||||||
|
ModificatoIl: ModificatoIlColumn,
|
||||||
|
Consegna: ConsegnaColumn,
|
||||||
|
DisponibileDa: DisponibileDaColumn,
|
||||||
|
Permanenza: PermanenzaColumn,
|
||||||
|
Persone: PersoneColumn,
|
||||||
|
MediaUpdatedAt: MediaUpdatedAtColumn,
|
||||||
|
TipoLocatore: TipoLocatoreColumn,
|
||||||
|
|
||||||
|
AllColumns: allColumns,
|
||||||
|
MutableColumns: mutableColumns,
|
||||||
|
DefaultColumns: defaultColumns,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
//
|
||||||
|
// Code generated by go-jet DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// WARNING: Changes to this file may cause incorrect behavior
|
||||||
|
// and will be lost if the code is regenerated
|
||||||
|
//
|
||||||
|
|
||||||
|
package table
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-jet/jet/v2/postgres"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ImagesRefs = newImagesRefsTable("public", "images_refs", "")
|
||||||
|
|
||||||
|
type imagesRefsTable struct {
|
||||||
|
postgres.Table
|
||||||
|
|
||||||
|
// Columns
|
||||||
|
Codice postgres.ColumnString
|
||||||
|
Ordine postgres.ColumnInteger
|
||||||
|
Img postgres.ColumnString
|
||||||
|
Thumb postgres.ColumnString
|
||||||
|
OgURL postgres.ColumnString
|
||||||
|
|
||||||
|
AllColumns postgres.ColumnList
|
||||||
|
MutableColumns postgres.ColumnList
|
||||||
|
DefaultColumns postgres.ColumnList
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImagesRefsTable struct {
|
||||||
|
imagesRefsTable
|
||||||
|
|
||||||
|
EXCLUDED imagesRefsTable
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS creates new ImagesRefsTable with assigned alias
|
||||||
|
func (a ImagesRefsTable) AS(alias string) *ImagesRefsTable {
|
||||||
|
return newImagesRefsTable(a.SchemaName(), a.TableName(), alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema creates new ImagesRefsTable with assigned schema name
|
||||||
|
func (a ImagesRefsTable) FromSchema(schemaName string) *ImagesRefsTable {
|
||||||
|
return newImagesRefsTable(schemaName, a.TableName(), a.Alias())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPrefix creates new ImagesRefsTable with assigned table prefix
|
||||||
|
func (a ImagesRefsTable) WithPrefix(prefix string) *ImagesRefsTable {
|
||||||
|
return newImagesRefsTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSuffix creates new ImagesRefsTable with assigned table suffix
|
||||||
|
func (a ImagesRefsTable) WithSuffix(suffix string) *ImagesRefsTable {
|
||||||
|
return newImagesRefsTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
||||||
|
}
|
||||||
|
|
||||||
|
func newImagesRefsTable(schemaName, tableName, alias string) *ImagesRefsTable {
|
||||||
|
return &ImagesRefsTable{
|
||||||
|
imagesRefsTable: newImagesRefsTableImpl(schemaName, tableName, alias),
|
||||||
|
EXCLUDED: newImagesRefsTableImpl("", "excluded", ""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newImagesRefsTableImpl(schemaName, tableName, alias string) imagesRefsTable {
|
||||||
|
var (
|
||||||
|
CodiceColumn = postgres.StringColumn("codice")
|
||||||
|
OrdineColumn = postgres.IntegerColumn("ordine")
|
||||||
|
ImgColumn = postgres.StringColumn("img")
|
||||||
|
ThumbColumn = postgres.StringColumn("thumb")
|
||||||
|
OgURLColumn = postgres.StringColumn("og_url")
|
||||||
|
allColumns = postgres.ColumnList{CodiceColumn, OrdineColumn, ImgColumn, ThumbColumn, OgURLColumn}
|
||||||
|
mutableColumns = postgres.ColumnList{CodiceColumn, OrdineColumn, ImgColumn, ThumbColumn, OgURLColumn}
|
||||||
|
defaultColumns = postgres.ColumnList{}
|
||||||
|
)
|
||||||
|
|
||||||
|
return imagesRefsTable{
|
||||||
|
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||||
|
|
||||||
|
//Columns
|
||||||
|
Codice: CodiceColumn,
|
||||||
|
Ordine: OrdineColumn,
|
||||||
|
Img: ImgColumn,
|
||||||
|
Thumb: ThumbColumn,
|
||||||
|
OgURL: OgURLColumn,
|
||||||
|
|
||||||
|
AllColumns: allColumns,
|
||||||
|
MutableColumns: mutableColumns,
|
||||||
|
DefaultColumns: defaultColumns,
|
||||||
|
}
|
||||||
|
}
|
||||||
99
apps/backend/gen/postgres/postgres/public/table/storage.go
Normal file
99
apps/backend/gen/postgres/postgres/public/table/storage.go
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
//
|
||||||
|
// Code generated by go-jet DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// WARNING: Changes to this file may cause incorrect behavior
|
||||||
|
// and will be lost if the code is regenerated
|
||||||
|
//
|
||||||
|
|
||||||
|
package table
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-jet/jet/v2/postgres"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Storage = newStorageTable("public", "storage", "")
|
||||||
|
|
||||||
|
type storageTable struct {
|
||||||
|
postgres.Table
|
||||||
|
|
||||||
|
// Columns
|
||||||
|
ID postgres.ColumnString
|
||||||
|
CreatedAt postgres.ColumnTimestamp
|
||||||
|
Filename postgres.ColumnString
|
||||||
|
MimeType postgres.ColumnString
|
||||||
|
FileSizeBytes postgres.ColumnInteger
|
||||||
|
FileData postgres.ColumnBytea
|
||||||
|
ExpiresAt postgres.ColumnTimestamp
|
||||||
|
Tag postgres.ColumnString
|
||||||
|
|
||||||
|
AllColumns postgres.ColumnList
|
||||||
|
MutableColumns postgres.ColumnList
|
||||||
|
DefaultColumns postgres.ColumnList
|
||||||
|
}
|
||||||
|
|
||||||
|
type StorageTable struct {
|
||||||
|
storageTable
|
||||||
|
|
||||||
|
EXCLUDED storageTable
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS creates new StorageTable with assigned alias
|
||||||
|
func (a StorageTable) AS(alias string) *StorageTable {
|
||||||
|
return newStorageTable(a.SchemaName(), a.TableName(), alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema creates new StorageTable with assigned schema name
|
||||||
|
func (a StorageTable) FromSchema(schemaName string) *StorageTable {
|
||||||
|
return newStorageTable(schemaName, a.TableName(), a.Alias())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPrefix creates new StorageTable with assigned table prefix
|
||||||
|
func (a StorageTable) WithPrefix(prefix string) *StorageTable {
|
||||||
|
return newStorageTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSuffix creates new StorageTable with assigned table suffix
|
||||||
|
func (a StorageTable) WithSuffix(suffix string) *StorageTable {
|
||||||
|
return newStorageTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStorageTable(schemaName, tableName, alias string) *StorageTable {
|
||||||
|
return &StorageTable{
|
||||||
|
storageTable: newStorageTableImpl(schemaName, tableName, alias),
|
||||||
|
EXCLUDED: newStorageTableImpl("", "excluded", ""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStorageTableImpl(schemaName, tableName, alias string) storageTable {
|
||||||
|
var (
|
||||||
|
IDColumn = postgres.StringColumn("id")
|
||||||
|
CreatedAtColumn = postgres.TimestampColumn("created_at")
|
||||||
|
FilenameColumn = postgres.StringColumn("filename")
|
||||||
|
MimeTypeColumn = postgres.StringColumn("mime_type")
|
||||||
|
FileSizeBytesColumn = postgres.IntegerColumn("file_size_bytes")
|
||||||
|
FileDataColumn = postgres.ByteaColumn("file_data")
|
||||||
|
ExpiresAtColumn = postgres.TimestampColumn("expires_at")
|
||||||
|
TagColumn = postgres.StringColumn("tag")
|
||||||
|
allColumns = postgres.ColumnList{IDColumn, CreatedAtColumn, FilenameColumn, MimeTypeColumn, FileSizeBytesColumn, FileDataColumn, ExpiresAtColumn, TagColumn}
|
||||||
|
mutableColumns = postgres.ColumnList{CreatedAtColumn, FilenameColumn, MimeTypeColumn, FileSizeBytesColumn, FileDataColumn, ExpiresAtColumn, TagColumn}
|
||||||
|
defaultColumns = postgres.ColumnList{IDColumn, CreatedAtColumn}
|
||||||
|
)
|
||||||
|
|
||||||
|
return storageTable{
|
||||||
|
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||||
|
|
||||||
|
//Columns
|
||||||
|
ID: IDColumn,
|
||||||
|
CreatedAt: CreatedAtColumn,
|
||||||
|
Filename: FilenameColumn,
|
||||||
|
MimeType: MimeTypeColumn,
|
||||||
|
FileSizeBytes: FileSizeBytesColumn,
|
||||||
|
FileData: FileDataColumn,
|
||||||
|
ExpiresAt: ExpiresAtColumn,
|
||||||
|
Tag: TagColumn,
|
||||||
|
|
||||||
|
AllColumns: allColumns,
|
||||||
|
MutableColumns: mutableColumns,
|
||||||
|
DefaultColumns: defaultColumns,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
//
|
||||||
|
// Code generated by go-jet DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// WARNING: Changes to this file may cause incorrect behavior
|
||||||
|
// and will be lost if the code is regenerated
|
||||||
|
//
|
||||||
|
|
||||||
|
package table
|
||||||
|
|
||||||
|
// UseSchema sets a new schema name for all generated table SQL builder types. It is recommended to invoke
|
||||||
|
// this method only once at the beginning of the program.
|
||||||
|
func UseSchema(schema string) {
|
||||||
|
Annunci = Annunci.FromSchema(schema)
|
||||||
|
ImagesRefs = ImagesRefs.FromSchema(schema)
|
||||||
|
Storage = Storage.FromSchema(schema)
|
||||||
|
VideosRefs = VideosRefs.FromSchema(schema)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
//
|
||||||
|
// Code generated by go-jet DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// WARNING: Changes to this file may cause incorrect behavior
|
||||||
|
// and will be lost if the code is regenerated
|
||||||
|
//
|
||||||
|
|
||||||
|
package table
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-jet/jet/v2/postgres"
|
||||||
|
)
|
||||||
|
|
||||||
|
var VideosRefs = newVideosRefsTable("public", "videos_refs", "")
|
||||||
|
|
||||||
|
type videosRefsTable struct {
|
||||||
|
postgres.Table
|
||||||
|
|
||||||
|
// Columns
|
||||||
|
Codice postgres.ColumnString
|
||||||
|
Ordine postgres.ColumnInteger
|
||||||
|
Video postgres.ColumnString
|
||||||
|
Thumb postgres.ColumnString
|
||||||
|
OgURL postgres.ColumnString
|
||||||
|
|
||||||
|
AllColumns postgres.ColumnList
|
||||||
|
MutableColumns postgres.ColumnList
|
||||||
|
DefaultColumns postgres.ColumnList
|
||||||
|
}
|
||||||
|
|
||||||
|
type VideosRefsTable struct {
|
||||||
|
videosRefsTable
|
||||||
|
|
||||||
|
EXCLUDED videosRefsTable
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS creates new VideosRefsTable with assigned alias
|
||||||
|
func (a VideosRefsTable) AS(alias string) *VideosRefsTable {
|
||||||
|
return newVideosRefsTable(a.SchemaName(), a.TableName(), alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema creates new VideosRefsTable with assigned schema name
|
||||||
|
func (a VideosRefsTable) FromSchema(schemaName string) *VideosRefsTable {
|
||||||
|
return newVideosRefsTable(schemaName, a.TableName(), a.Alias())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPrefix creates new VideosRefsTable with assigned table prefix
|
||||||
|
func (a VideosRefsTable) WithPrefix(prefix string) *VideosRefsTable {
|
||||||
|
return newVideosRefsTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSuffix creates new VideosRefsTable with assigned table suffix
|
||||||
|
func (a VideosRefsTable) WithSuffix(suffix string) *VideosRefsTable {
|
||||||
|
return newVideosRefsTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVideosRefsTable(schemaName, tableName, alias string) *VideosRefsTable {
|
||||||
|
return &VideosRefsTable{
|
||||||
|
videosRefsTable: newVideosRefsTableImpl(schemaName, tableName, alias),
|
||||||
|
EXCLUDED: newVideosRefsTableImpl("", "excluded", ""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVideosRefsTableImpl(schemaName, tableName, alias string) videosRefsTable {
|
||||||
|
var (
|
||||||
|
CodiceColumn = postgres.StringColumn("codice")
|
||||||
|
OrdineColumn = postgres.IntegerColumn("ordine")
|
||||||
|
VideoColumn = postgres.StringColumn("video")
|
||||||
|
ThumbColumn = postgres.StringColumn("thumb")
|
||||||
|
OgURLColumn = postgres.StringColumn("og_url")
|
||||||
|
allColumns = postgres.ColumnList{CodiceColumn, OrdineColumn, VideoColumn, ThumbColumn, OgURLColumn}
|
||||||
|
mutableColumns = postgres.ColumnList{CodiceColumn, OrdineColumn, VideoColumn, ThumbColumn, OgURLColumn}
|
||||||
|
defaultColumns = postgres.ColumnList{}
|
||||||
|
)
|
||||||
|
|
||||||
|
return videosRefsTable{
|
||||||
|
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||||
|
|
||||||
|
//Columns
|
||||||
|
Codice: CodiceColumn,
|
||||||
|
Ordine: OrdineColumn,
|
||||||
|
Video: VideoColumn,
|
||||||
|
Thumb: ThumbColumn,
|
||||||
|
OgURL: OgURLColumn,
|
||||||
|
|
||||||
|
AllColumns: allColumns,
|
||||||
|
MutableColumns: mutableColumns,
|
||||||
|
DefaultColumns: defaultColumns,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/google/uuid"
|
||||||
"github.com/nfnt/resize"
|
"github.com/nfnt/resize"
|
||||||
"github.com/nickalie/go-webpbin"
|
"github.com/nickalie/go-webpbin"
|
||||||
)
|
)
|
||||||
|
|
@ -101,12 +101,12 @@ func ProcessImagePool(toProcess *[]typesdefs.MediaToProcess) ([]typesdefs.Upload
|
||||||
defer uploadWg.Done()
|
defer uploadWg.Done()
|
||||||
defer func() { <-uploadSem }() // release semaphore
|
defer func() { <-uploadSem }() // release semaphore
|
||||||
|
|
||||||
imgId, err := UploadFile(img.Path, "image/webp", "images")
|
imgId, err := queries.UploadFile(img.Path, "image/webp", "images")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to upload image: %v", err)
|
log.Printf("failed to upload image: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
thumbId, err := UploadFile(img.ThumnailPath, "image/webp", "images")
|
thumbId, err := queries.UploadFile(img.ThumnailPath, "image/webp", "images")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to upload thumbnail: %v", err)
|
log.Printf("failed to upload thumbnail: %v", err)
|
||||||
return
|
return
|
||||||
|
|
@ -272,28 +272,19 @@ func thumbnail_creation(img image.Image, output string, outputdir string) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func FallbackImage(c echo.Context) error {
|
|
||||||
// Serve a fallback image if the requested image is not found
|
|
||||||
fallbackPath := "fallback.webp" // Path to your fallback image
|
|
||||||
if _, err := os.Stat(fallbackPath); os.IsNotExist(err) {
|
|
||||||
return c.String(http.StatusNotFound, "Fallback image not found")
|
|
||||||
}
|
|
||||||
return c.File(fallbackPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
type imgRef struct {
|
type imgRef struct {
|
||||||
Og_url string
|
Og_url string
|
||||||
Img string
|
Img uuid.UUID
|
||||||
Thumb string
|
Thumb uuid.UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) {
|
func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) {
|
||||||
currentBucketState, err := ListBucket("images")
|
currentBucketState, err := queries.GetStorageByTag("images")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to list bucket: %w", err)
|
return nil, fmt.Errorf("failed to list bucket: %w", err)
|
||||||
}
|
}
|
||||||
//make a list of bucket ids
|
//make a list of bucket ids
|
||||||
bucketIdsMap := make(map[string]bool)
|
bucketIdsMap := make(map[uuid.UUID]bool)
|
||||||
for _, item := range currentBucketState {
|
for _, item := range currentBucketState {
|
||||||
bucketIdsMap[item.ID] = true
|
bucketIdsMap[item.ID] = true
|
||||||
}
|
}
|
||||||
|
|
@ -306,7 +297,7 @@ func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) {
|
||||||
dbImagesMap := make(map[string][]imgRef)
|
dbImagesMap := make(map[string][]imgRef)
|
||||||
for _, ref := range db_images {
|
for _, ref := range db_images {
|
||||||
dbImagesMap[ref.Codice] = append(dbImagesMap[ref.Codice], imgRef{
|
dbImagesMap[ref.Codice] = append(dbImagesMap[ref.Codice], imgRef{
|
||||||
Og_url: ref.Og_url,
|
Og_url: ref.OgURL,
|
||||||
Img: ref.Img,
|
Img: ref.Img,
|
||||||
Thumb: ref.Thumb,
|
Thumb: ref.Thumb,
|
||||||
})
|
})
|
||||||
|
|
@ -351,7 +342,7 @@ func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ForceClearImages(codici []string) error {
|
func ForceClearImages(codici []string) error {
|
||||||
var idsToDelete []string
|
var idsToDelete uuid.UUIDs
|
||||||
for _, codice := range codici {
|
for _, codice := range codici {
|
||||||
refs, err := queries.GetCodiceImagesFromDB(codice)
|
refs, err := queries.GetCodiceImagesFromDB(codice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -363,7 +354,7 @@ func ForceClearImages(codici []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
err := DeleteIdsFromStorage(idsToDelete)
|
err := queries.DeleteMultFile(idsToDelete)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete images from storage: %w", err)
|
return fmt.Errorf("failed to delete images from storage: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -378,12 +369,12 @@ func ForceClearImages(codici []string) error {
|
||||||
|
|
||||||
// Removes stranded images (in storage but not in DB)
|
// Removes stranded images (in storage but not in DB)
|
||||||
func ReconcileImages() error {
|
func ReconcileImages() error {
|
||||||
currentBucketState, err := ListBucket("images")
|
currentBucketState, err := queries.GetStorageByTag("images")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to list bucket: %w", err)
|
return fmt.Errorf("failed to list bucket: %w", err)
|
||||||
}
|
}
|
||||||
//make a list of bucket ids
|
//make a list of bucket ids
|
||||||
bucketIdsMap := make(map[string]bool)
|
bucketIdsMap := make(map[uuid.UUID]bool)
|
||||||
for _, item := range currentBucketState {
|
for _, item := range currentBucketState {
|
||||||
bucketIdsMap[item.ID] = true
|
bucketIdsMap[item.ID] = true
|
||||||
}
|
}
|
||||||
|
|
@ -391,13 +382,13 @@ func ReconcileImages() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get images from DB: %w", err)
|
return fmt.Errorf("failed to get images from DB: %w", err)
|
||||||
}
|
}
|
||||||
knownStorageIds := make(map[string]bool)
|
knownStorageIds := make(map[uuid.UUID]bool)
|
||||||
for _, ref := range db_images {
|
for _, ref := range db_images {
|
||||||
knownStorageIds[ref.Img] = true
|
knownStorageIds[ref.Img] = true
|
||||||
knownStorageIds[ref.Thumb] = true
|
knownStorageIds[ref.Thumb] = true
|
||||||
}
|
}
|
||||||
// reconcile: delete storage files not referenced in DB
|
// reconcile: delete storage files not referenced in DB
|
||||||
var strandedIds []string
|
var strandedIds uuid.UUIDs
|
||||||
for _, item := range currentBucketState {
|
for _, item := range currentBucketState {
|
||||||
if !knownStorageIds[item.ID] {
|
if !knownStorageIds[item.ID] {
|
||||||
strandedIds = append(strandedIds, item.ID)
|
strandedIds = append(strandedIds, item.ID)
|
||||||
|
|
@ -405,7 +396,7 @@ func ReconcileImages() error {
|
||||||
}
|
}
|
||||||
if len(strandedIds) > 0 {
|
if len(strandedIds) > 0 {
|
||||||
log.Printf("Reconciliation: found %d stranded files, deleting...", len(strandedIds))
|
log.Printf("Reconciliation: found %d stranded files, deleting...", len(strandedIds))
|
||||||
if err := DeleteIdsFromStorage(strandedIds); err != nil {
|
if err := queries.DeleteMultFile(strandedIds); err != nil {
|
||||||
log.Printf("Warning: failed to delete stranded files: %v", err) // non-fatal
|
log.Printf("Warning: failed to delete stranded files: %v", err) // non-fatal
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
3
apps/backend/jet.sh
Normal file
3
apps/backend/jet.sh
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
jet -dsn=postgresql://postgres:rootpost@localhost:5433/postgres?sslmode=disable -schema=public -path=./gen/postgres -tables=storage,annunci,images_refs,videos_refs -ignore-enums=bantype,genericstatusenum,ordertypeenum,paymentstatusenum,statusconfermaenum,tipologiaposizioneenum
|
||||||
|
|
||||||
|
|
@ -487,14 +487,14 @@ func processDatiImmobile(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.Annu
|
||||||
tmp.Numero_postiauto = annuncio.PostiAuto
|
tmp.Numero_postiauto = annuncio.PostiAuto
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCaratteristiche(wrkrecord *typesdefs.AnnuncioXML, m *MiogestHandler) map[string]interface{} {
|
func parseCaratteristiche(wrkrecord *typesdefs.AnnuncioXML, m *MiogestHandler) map[string]any {
|
||||||
if reflect.TypeOf(wrkrecord).Kind() != reflect.Ptr || reflect.ValueOf(wrkrecord).Elem().Kind() != reflect.Struct {
|
if reflect.TypeFor[*typesdefs.AnnuncioXML]().Kind() != reflect.Pointer || reflect.ValueOf(wrkrecord).Elem().Kind() != reflect.Struct {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tmpCaratteristiche := make(map[string]interface{})
|
tmpCaratteristiche := make(map[string]any)
|
||||||
val := reflect.ValueOf(wrkrecord).Elem()
|
val := reflect.ValueOf(wrkrecord).Elem()
|
||||||
numField := val.NumField()
|
numField := val.NumField()
|
||||||
for i := 0; i < numField; i++ {
|
for i := range numField {
|
||||||
if strings.HasPrefix(val.Type().Field(i).Name, "Scheda_") {
|
if strings.HasPrefix(val.Type().Field(i).Name, "Scheda_") {
|
||||||
nameField := val.Type().Field(i).Name
|
nameField := val.Type().Field(i).Name
|
||||||
valueField := val.Field(i)
|
valueField := val.Field(i)
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,22 @@
|
||||||
package queries
|
package queries
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
models "backend/gen/postgres/postgres/public/model"
|
||||||
|
. "backend/gen/postgres/postgres/public/table"
|
||||||
"backend/typesdefs"
|
"backend/typesdefs"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/georgysavva/scany/v2/pgxscan"
|
. "github.com/go-jet/jet/v2/postgres"
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AnnunciGetActive() ([]string, error) {
|
func AnnunciGetActive() ([]string, error) {
|
||||||
var annunci []string
|
var annunci []string
|
||||||
err := pgxscan.Select(Db.Ctx, Db.Dbpool, &annunci, "SELECT codice FROM public.annunci WHERE web = true and stato = 'Attivo' ")
|
stmt := SELECT(Annunci.Codice).FROM(Annunci).WHERE(AND(Annunci.Web.IS_TRUE(), Annunci.Stato.EQ(Text("Attivo"))))
|
||||||
if err != nil && err != pgx.ErrNoRows {
|
err := stmt.Query(postgres, &annunci)
|
||||||
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get active annunci: %w", err)
|
return nil, fmt.Errorf("failed to get active annunci: %w", err)
|
||||||
}
|
}
|
||||||
return annunci, nil
|
return annunci, nil
|
||||||
|
|
@ -22,7 +26,8 @@ func AnnunciInsert(annunci typesdefs.AnnunciParsed, resetAllBeforeUpdate bool) e
|
||||||
var err error
|
var err error
|
||||||
if resetAllBeforeUpdate {
|
if resetAllBeforeUpdate {
|
||||||
//SET ALL WEB TO FALSE
|
//SET ALL WEB TO FALSE
|
||||||
_, err = Db.Dbpool.Exec(Db.Ctx, "UPDATE public.annunci SET web = false, homepage = false, stato='Sospeso'")
|
stmt := Annunci.UPDATE(Annunci.Web, Annunci.Homepage, Annunci.Stato).SET(false, false, "Sospeso").WHERE(Annunci.Codice.IS_NOT_NULL())
|
||||||
|
_, err = stmt.Exec(postgres)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to set all web to false: %w", err)
|
return fmt.Errorf("failed to set all web to false: %w", err)
|
||||||
|
|
||||||
|
|
@ -30,164 +35,97 @@ func AnnunciInsert(annunci typesdefs.AnnunciParsed, resetAllBeforeUpdate bool) e
|
||||||
}
|
}
|
||||||
//GET ALL CODICI
|
//GET ALL CODICI
|
||||||
var codici []string
|
var codici []string
|
||||||
err = pgxscan.Select(Db.Ctx, Db.Dbpool, &codici, "SELECT DISTINCT codice FROM public.annunci")
|
stmt := SELECT(Annunci.Codice).DISTINCT(Annunci.Codice).FROM(Annunci)
|
||||||
|
err = stmt.Query(postgres, &codici)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get all codici: %w", err)
|
return fmt.Errorf("failed to get all codici: %w", err)
|
||||||
}
|
}
|
||||||
batch := &pgx.Batch{}
|
|
||||||
for _, annuncio := range annunci.Annuncio {
|
|
||||||
|
|
||||||
|
var to_update []models.Annunci
|
||||||
|
var to_insert []models.Annunci
|
||||||
|
for _, annuncio := range annunci.Annuncio {
|
||||||
|
value := models.Annunci{
|
||||||
|
Locatore: annuncio.Locatore,
|
||||||
|
Numero: annuncio.Numero,
|
||||||
|
Idlocatore: annuncio.Idlocatore,
|
||||||
|
Email: annuncio.Email,
|
||||||
|
CreatoIl: annuncio.Creato_il,
|
||||||
|
ModificatoIl: annuncio.Modificato_il,
|
||||||
|
Indirizzo: annuncio.Indirizzo,
|
||||||
|
Civico: annuncio.Civico,
|
||||||
|
Comune: annuncio.Comune,
|
||||||
|
Cap: annuncio.Cap,
|
||||||
|
Provincia: annuncio.Provincia,
|
||||||
|
Regione: annuncio.Regione,
|
||||||
|
Lat: annuncio.Lat,
|
||||||
|
Lon: annuncio.Lon,
|
||||||
|
IndirizzoSecondario: annuncio.Indirizzo_secondario,
|
||||||
|
CivicoSecondario: annuncio.Civico_secondario,
|
||||||
|
LatSecondario: annuncio.Lat_secondario,
|
||||||
|
LonSecondario: annuncio.Lon_secondario,
|
||||||
|
Tipo: annuncio.Tipo,
|
||||||
|
Categorie: (*pq.StringArray)(annuncio.Categorie),
|
||||||
|
Prezzo: int32(annuncio.Prezzo),
|
||||||
|
Anno: annuncio.Anno,
|
||||||
|
Consegna: intToint32Ptr(annuncio.Consegna),
|
||||||
|
Classe: annuncio.Classe,
|
||||||
|
Mq: annuncio.Mq,
|
||||||
|
Piano: annuncio.Piano,
|
||||||
|
PianoPalazzo: strPtrToInt32Ptr(annuncio.Piano_palazzo),
|
||||||
|
UnitaCondominio: strPtrToInt32Ptr(annuncio.Unita_condominio),
|
||||||
|
NumeroVani: strPtrToInt32Ptr(annuncio.Numero_vani),
|
||||||
|
NumeroCamere: strPtrToInt32Ptr(annuncio.Numero_camere),
|
||||||
|
NumeroBagni: strPtrToInt32Ptr(annuncio.Numero_bagni),
|
||||||
|
NumeroBalconi: strPtrToInt32Ptr(annuncio.Numero_balconi),
|
||||||
|
NumeroTerrazzi: strPtrToInt32Ptr(annuncio.Numero_terrazzi),
|
||||||
|
NumeroBox: strPtrToInt32Ptr(annuncio.Numero_box),
|
||||||
|
NumeroPostiauto: strPtrToInt32Ptr(annuncio.Numero_postiauto),
|
||||||
|
Caratteristiche: mapToJSONStrPtr(annuncio.Caratteristiche),
|
||||||
|
Accessori: (*pq.StringArray)(annuncio.Accessori),
|
||||||
|
TitoloIt: annuncio.Titolo_it,
|
||||||
|
DescIt: annuncio.Desc_it,
|
||||||
|
TitoloEn: annuncio.Titolo_en,
|
||||||
|
DescEn: annuncio.Desc_en,
|
||||||
|
Stato: annuncio.Stato,
|
||||||
|
Web: annuncio.Web,
|
||||||
|
Homepage: annuncio.Homepage,
|
||||||
|
ExternalVideos: (*pq.StringArray)(annuncio.External_videos),
|
||||||
|
Codice: annuncio.Codice,
|
||||||
|
DisponibileDa: strPtrToTimePtrSilent(annuncio.Disponibile_da),
|
||||||
|
Permanenza: intSliceToPQInt32Array(annuncio.Permanenza),
|
||||||
|
Persone: (*pq.StringArray)(annuncio.Persone),
|
||||||
|
TipoLocatore: annuncio.Tipo_locatore,
|
||||||
|
MediaUpdatedAt: Ptr(time.Now()),
|
||||||
|
}
|
||||||
if slices.Contains(codici, annuncio.Codice) {
|
if slices.Contains(codici, annuncio.Codice) {
|
||||||
batch.Queue(`
|
to_update = append(to_update, value)
|
||||||
UPDATE public.annunci
|
|
||||||
SET locatore = $1,
|
|
||||||
numero = $2,
|
|
||||||
idlocatore = $3,
|
|
||||||
email = $4,
|
|
||||||
creato_il = $5,
|
|
||||||
modificato_il = $6,
|
|
||||||
indirizzo = $7,
|
|
||||||
civico = $8,
|
|
||||||
comune = $9,
|
|
||||||
cap = $10,
|
|
||||||
provincia = $11,
|
|
||||||
regione = $12,
|
|
||||||
lat = $13,
|
|
||||||
lon = $14,
|
|
||||||
indirizzo_secondario = $15,
|
|
||||||
civico_secondario = $16,
|
|
||||||
lat_secondario = $17,
|
|
||||||
lon_secondario = $18,
|
|
||||||
tipo = $19,
|
|
||||||
categorie = $20,
|
|
||||||
prezzo = $21,
|
|
||||||
anno = $22,
|
|
||||||
consegna = $23,
|
|
||||||
classe = $24,
|
|
||||||
mq = $25,
|
|
||||||
piano = $26,
|
|
||||||
piano_palazzo = $27,
|
|
||||||
unita_condominio = $28,
|
|
||||||
numero_vani = $29,
|
|
||||||
numero_camere = $30,
|
|
||||||
numero_bagni = $31,
|
|
||||||
numero_balconi = $32,
|
|
||||||
numero_terrazzi = $33,
|
|
||||||
numero_box = $34,
|
|
||||||
numero_postiauto = $35,
|
|
||||||
caratteristiche = $36,
|
|
||||||
accessori = $37,
|
|
||||||
titolo_it = $38,
|
|
||||||
desc_it = $39,
|
|
||||||
titolo_en = $40,
|
|
||||||
desc_en = $41,
|
|
||||||
stato = $42,
|
|
||||||
web = $43,
|
|
||||||
homepage = $44,
|
|
||||||
external_videos = $45,
|
|
||||||
disponibile_da = $47,
|
|
||||||
permanenza = $48,
|
|
||||||
persone = $49,
|
|
||||||
media_updated_at = NOW(),
|
|
||||||
tipo_locatore = $50
|
|
||||||
WHERE codice = $46
|
|
||||||
`, annuncio.Locatore, annuncio.Numero, annuncio.Idlocatore,
|
|
||||||
annuncio.Email, annuncio.Creato_il, annuncio.Modificato_il,
|
|
||||||
annuncio.Indirizzo, annuncio.Civico, annuncio.Comune, annuncio.Cap,
|
|
||||||
annuncio.Provincia, annuncio.Regione, annuncio.Lat, annuncio.Lon,
|
|
||||||
annuncio.Indirizzo_secondario, annuncio.Civico_secondario, annuncio.Lat_secondario,
|
|
||||||
annuncio.Lon_secondario, annuncio.Tipo, annuncio.Categorie, annuncio.Prezzo, annuncio.Anno,
|
|
||||||
annuncio.Consegna, annuncio.Classe, annuncio.Mq, annuncio.Piano, annuncio.Piano_palazzo,
|
|
||||||
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.Caratteristiche, annuncio.Accessori, annuncio.Titolo_it, annuncio.Desc_it, annuncio.Titolo_en,
|
|
||||||
annuncio.Desc_en, annuncio.Stato, annuncio.Web, annuncio.Homepage,
|
|
||||||
annuncio.External_videos, annuncio.Codice, annuncio.Disponibile_da, annuncio.Permanenza, annuncio.Persone, annuncio.Tipo_locatore)
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
batch.Queue(`
|
to_insert = append(to_insert, value)
|
||||||
INSERT INTO public.annunci(
|
}
|
||||||
locatore,
|
|
||||||
numero,
|
}
|
||||||
idlocatore,
|
if len(to_insert) > 0 {
|
||||||
email,
|
stmt := Annunci.INSERT(Annunci.MutableColumns).MODELS(to_insert)
|
||||||
creato_il,
|
_, err = stmt.Exec(postgres)
|
||||||
modificato_il,
|
if err != nil {
|
||||||
indirizzo,
|
return fmt.Errorf("failed to insert/update annunci batch: %w", err)
|
||||||
civico,
|
|
||||||
comune,
|
|
||||||
cap,
|
|
||||||
provincia,
|
|
||||||
regione,
|
|
||||||
lat,
|
|
||||||
lon,
|
|
||||||
indirizzo_secondario,
|
|
||||||
civico_secondario,
|
|
||||||
lat_secondario,
|
|
||||||
lon_secondario,
|
|
||||||
tipo,
|
|
||||||
categorie,
|
|
||||||
prezzo,
|
|
||||||
anno,
|
|
||||||
consegna,
|
|
||||||
classe,
|
|
||||||
mq,
|
|
||||||
piano,
|
|
||||||
piano_palazzo,
|
|
||||||
unita_condominio,
|
|
||||||
numero_vani,
|
|
||||||
numero_camere,
|
|
||||||
numero_bagni,
|
|
||||||
numero_balconi,
|
|
||||||
numero_terrazzi,
|
|
||||||
numero_box,
|
|
||||||
numero_postiauto,
|
|
||||||
caratteristiche,
|
|
||||||
accessori,
|
|
||||||
titolo_it,
|
|
||||||
desc_it,
|
|
||||||
titolo_en,
|
|
||||||
desc_en,
|
|
||||||
stato,
|
|
||||||
web,
|
|
||||||
homepage,
|
|
||||||
external_videos,
|
|
||||||
codice,
|
|
||||||
disponibile_da,
|
|
||||||
permanenza,
|
|
||||||
persone,
|
|
||||||
media_updated_at,
|
|
||||||
tipo_locatore
|
|
||||||
)
|
|
||||||
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(), $50)
|
|
||||||
`, annuncio.Locatore, annuncio.Numero, annuncio.Idlocatore,
|
|
||||||
annuncio.Email, annuncio.Creato_il, annuncio.Modificato_il,
|
|
||||||
annuncio.Indirizzo, annuncio.Civico, annuncio.Comune, annuncio.Cap,
|
|
||||||
annuncio.Provincia, annuncio.Regione, annuncio.Lat, annuncio.Lon,
|
|
||||||
annuncio.Indirizzo_secondario, annuncio.Civico_secondario, annuncio.Lat_secondario,
|
|
||||||
annuncio.Lon_secondario, annuncio.Tipo, annuncio.Categorie, annuncio.Prezzo, annuncio.Anno,
|
|
||||||
annuncio.Consegna, annuncio.Classe, annuncio.Mq, annuncio.Piano, annuncio.Piano_palazzo,
|
|
||||||
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.Caratteristiche, annuncio.Accessori, annuncio.Titolo_it, annuncio.Desc_it, annuncio.Titolo_en,
|
|
||||||
annuncio.Desc_en, annuncio.Stato, annuncio.Web, annuncio.Homepage,
|
|
||||||
annuncio.External_videos, annuncio.Codice, annuncio.Disponibile_da, annuncio.Permanenza, annuncio.Persone, annuncio.Tipo_locatore)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = Db.Dbpool.SendBatch(Db.Ctx, batch).Close()
|
|
||||||
|
if len(to_update) > 0 {
|
||||||
|
for _, upd := range to_update {
|
||||||
|
stmt := Annunci.UPDATE(Annunci.MutableColumns).MODEL(upd).WHERE(Annunci.Codice.EQ(Text(upd.Codice)))
|
||||||
|
_, err = stmt.Exec(postgres)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to insert/update annunci batch: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to insert/update annunci batch: %w", err)
|
return fmt.Errorf("failed to insert/update annunci batch: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Println("Annunci insert/update done")
|
fmt.Println("Annunci insert/update done")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearRecords() {
|
|
||||||
|
|
||||||
//SET ALL WEB TO FALSE
|
|
||||||
_, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.annunci")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
package queries
|
package queries
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
models "backend/gen/postgres/postgres/public/model"
|
||||||
|
. "backend/gen/postgres/postgres/public/table"
|
||||||
"backend/typesdefs"
|
"backend/typesdefs"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/georgysavva/scany/v2/pgxscan"
|
. "github.com/go-jet/jet/v2/postgres"
|
||||||
"github.com/jackc/pgx/v5"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetImagesToDB(pool *[]typesdefs.UploadedMedia) error {
|
func SetImagesToDB(pool *[]typesdefs.UploadedMedia) error {
|
||||||
|
|
@ -14,43 +15,53 @@ func SetImagesToDB(pool *[]typesdefs.UploadedMedia) error {
|
||||||
log.Println("No new images to save to DB.")
|
log.Println("No new images to save to DB.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
batch := &pgx.Batch{}
|
|
||||||
|
var to_insert []models.ImagesRefs
|
||||||
|
|
||||||
for _, data := range *pool {
|
for _, data := range *pool {
|
||||||
batch.Queue(`
|
|
||||||
INSERT INTO public.images_refs (codice, ordine, img, thumb, og_url)
|
|
||||||
VALUES ($1, $2, $3, $4, $5)
|
|
||||||
ON CONFLICT (codice, ordine) DO UPDATE SET img = $3, thumb = $4
|
|
||||||
`, data.Codice, data.Order, data.MediaId, data.ThumbId, data.OGUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := Db.Dbpool.SendBatch(Db.Ctx, batch).Close(); err != nil {
|
to_insert = append(to_insert, models.ImagesRefs{
|
||||||
|
Codice: data.Codice,
|
||||||
|
Ordine: int32(data.Order),
|
||||||
|
Img: data.MediaId,
|
||||||
|
Thumb: data.ThumbId,
|
||||||
|
OgURL: data.OGUrl,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
stmt := ImagesRefs.INSERT(ImagesRefs.AllColumns).MODELS(to_insert).ON_CONFLICT(ImagesRefs.Codice, ImagesRefs.Ordine).DO_UPDATE(SET(ImagesRefs.Img.SET(ImagesRefs.EXCLUDED.Img), ImagesRefs.Thumb.SET(ImagesRefs.EXCLUDED.Thumb)))
|
||||||
|
|
||||||
|
_, err := stmt.Exec(postgres)
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to insert/update images batch: %w", err)
|
return fmt.Errorf("failed to insert/update images batch: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetImagesFromDB() ([]typesdefs.ImagesRefs, error) {
|
func GetImagesFromDB() ([]models.ImagesRefs, error) {
|
||||||
var images []typesdefs.ImagesRefs
|
var images []models.ImagesRefs
|
||||||
err := pgxscan.Select(Db.Ctx, Db.Dbpool, &images, "SELECT * FROM public.images_refs ORDER BY ordine ASC")
|
stmt := SELECT(ImagesRefs.AllColumns).FROM(ImagesRefs).ORDER_BY(ImagesRefs.Ordine.ASC())
|
||||||
if err != nil && err != pgx.ErrNoRows {
|
err := stmt.Query(postgres, &images)
|
||||||
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get images: %w", err)
|
return nil, fmt.Errorf("failed to get images: %w", err)
|
||||||
}
|
}
|
||||||
return images, nil
|
return images, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCodiceImagesFromDB(codice string) ([]typesdefs.ImagesRefs, error) {
|
func GetCodiceImagesFromDB(codice string) ([]models.ImagesRefs, error) {
|
||||||
var images []typesdefs.ImagesRefs
|
var images []models.ImagesRefs
|
||||||
err := pgxscan.Select(Db.Ctx, Db.Dbpool, &images, "SELECT * FROM public.images_refs WHERE codice = $1 ORDER BY ordine ASC", codice)
|
stmt := SELECT(ImagesRefs.AllColumns).FROM(ImagesRefs).WHERE(ImagesRefs.Codice.EQ(Text(codice))).ORDER_BY(ImagesRefs.Ordine.ASC())
|
||||||
if err != nil && err != pgx.ErrNoRows {
|
err := stmt.Query(postgres, &images)
|
||||||
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get images: %w", err)
|
return nil, fmt.Errorf("failed to get images: %w", err)
|
||||||
}
|
}
|
||||||
return images, nil
|
return images, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ClearImagesRefsByCodice(codice string) error {
|
func ClearImagesRefsByCodice(codice string) error {
|
||||||
_, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.images_refs WHERE codice = $1", codice)
|
stmt := ImagesRefs.DELETE().WHERE(ImagesRefs.Codice.EQ(Text(codice)))
|
||||||
|
_, err := stmt.Exec(postgres)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to clear images refs by codice: %w", err)
|
return fmt.Errorf("failed to clear images refs by codice: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,34 @@
|
||||||
package queries
|
package queries
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"backend/db"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Db *db.DbHandler
|
postgres *sql.DB
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
_ = godotenv.Load(".env")
|
_ = godotenv.Load(".env")
|
||||||
|
|
||||||
Db = db.NewDbHandler(db.Configs{
|
var err error
|
||||||
Host: os.Getenv("PGHOST"),
|
connStr := fmt.Sprintf("postgresql://%s:%s@%s:%s/%s?sslmode=disable", os.Getenv("POSTGRES_USER"), os.Getenv("POSTGRES_PASSWORD"), os.Getenv("PGHOST"), os.Getenv("PGPORT"), os.Getenv("POSTGRES_DB"))
|
||||||
Dbname: os.Getenv("POSTGRES_DB"),
|
postgres, err = sql.Open("postgres", connStr)
|
||||||
Port: os.Getenv("PGPORT"),
|
if err != nil {
|
||||||
User: os.Getenv("POSTGRES_USER"),
|
panic(err)
|
||||||
Password: os.Getenv("POSTGRES_PASSWORD"),
|
}
|
||||||
LoggerEnabled: os.Getenv("DB_LOGGER") == "true",
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func HealthCheck() bool {
|
func HealthCheck() bool {
|
||||||
if Db == nil || Db.Dbpool == nil {
|
if postgres == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
err := Db.Dbpool.Ping(Db.Ctx)
|
err := postgres.Ping()
|
||||||
return err == nil
|
return err == nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
92
apps/backend/queries/storage.go
Normal file
92
apps/backend/queries/storage.go
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
package queries
|
||||||
|
|
||||||
|
import (
|
||||||
|
models "backend/gen/postgres/postgres/public/model"
|
||||||
|
. "backend/gen/postgres/postgres/public/table"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
. "github.com/go-jet/jet/v2/postgres"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// func DeleteFile(id uuid.UUID) error {
|
||||||
|
// stmt := Storage.DELETE().WHERE(Storage.ID.EQ(UUID(id)))
|
||||||
|
// _, err := stmt.Exec(postgres)
|
||||||
|
// if err != nil {
|
||||||
|
// return fmt.Errorf("failed to delete file: %w", err)
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
func DeleteMultFile(ids uuid.UUIDs) error {
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var sqlIDs []Expression
|
||||||
|
for _, id := range ids {
|
||||||
|
sqlIDs = append(sqlIDs, UUID(id))
|
||||||
|
}
|
||||||
|
stmt := Storage.DELETE().WHERE(Storage.ID.IN(sqlIDs...))
|
||||||
|
_, err := stmt.Exec(postgres)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete files: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStorageByTag(tag string) ([]models.Storage, error) {
|
||||||
|
var rows []models.Storage
|
||||||
|
stmt := SELECT(Storage.AllColumns).FROM(Storage).WHERE(Storage.Tag.EQ(Text(tag)))
|
||||||
|
err := stmt.Query(postgres, &rows)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get storage by tag: %w", err)
|
||||||
|
}
|
||||||
|
return rows, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func UploadFile(path string, mimeType string, tag string) (uuid.UUID, error) {
|
||||||
|
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return uuid.UUID{}, fmt.Errorf("failed to open file: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
bytes, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return uuid.UUID{}, fmt.Errorf("failed to read file: %w", err)
|
||||||
|
}
|
||||||
|
to_insert := models.Storage{
|
||||||
|
MimeType: mimeType,
|
||||||
|
Tag: &tag,
|
||||||
|
FileData: bytes,
|
||||||
|
Filename: filepath.Base(file.Name()),
|
||||||
|
FileSizeBytes: int32(len(bytes)),
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt := Storage.INSERT(Storage.MimeType,
|
||||||
|
Storage.Tag,
|
||||||
|
Storage.FileData,
|
||||||
|
Storage.Filename,
|
||||||
|
Storage.FileSizeBytes).MODEL(to_insert).RETURNING(Storage.ID)
|
||||||
|
var newIdStrings []string
|
||||||
|
|
||||||
|
err = stmt.Query(postgres, &newIdStrings)
|
||||||
|
if err != nil {
|
||||||
|
return uuid.UUID{}, fmt.Errorf("failed to insert file into DB: %w", err)
|
||||||
|
}
|
||||||
|
if len(newIdStrings) == 0 {
|
||||||
|
return uuid.UUID{}, fmt.Errorf("no ID was returned from insert")
|
||||||
|
}
|
||||||
|
actualUUID, err := uuid.Parse(newIdStrings[0])
|
||||||
|
if err != nil {
|
||||||
|
return uuid.UUID{}, fmt.Errorf("failed to parse UUID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return actualUUID, nil
|
||||||
|
|
||||||
|
}
|
||||||
72
apps/backend/queries/utils.go
Normal file
72
apps/backend/queries/utils.go
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
package queries
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func intToint32Ptr(i *int) *int32 {
|
||||||
|
if i == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
v := int32(*i)
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ptr[T any](v T) *T {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
func strPtrToInt32Ptr(s *string) *int32 {
|
||||||
|
if s == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := strconv.ParseInt(*s, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result := int32(val)
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
func intSliceToPQInt32Array(v *[]int) *pq.Int32Array {
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the pq.Int32Array with the correct capacity
|
||||||
|
arr := make(pq.Int32Array, len(*v))
|
||||||
|
for i, val := range *v {
|
||||||
|
arr[i] = int32(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &arr
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapToJSONStrPtr(m *map[string]interface{}) *string {
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str := string(bytes)
|
||||||
|
return &str
|
||||||
|
}
|
||||||
|
func strPtrToTimePtrSilent(s *string) *time.Time {
|
||||||
|
if s == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
parsedTime, err := time.Parse(time.DateOnly, *s)
|
||||||
|
if err != nil {
|
||||||
|
return nil // Silently returns NULL to the DB if parsing fails
|
||||||
|
}
|
||||||
|
return &parsedTime
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
package queries
|
package queries
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
models "backend/gen/postgres/postgres/public/model"
|
||||||
|
. "backend/gen/postgres/postgres/public/table"
|
||||||
"backend/typesdefs"
|
"backend/typesdefs"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/georgysavva/scany/v2/pgxscan"
|
. "github.com/go-jet/jet/v2/postgres"
|
||||||
"github.com/jackc/pgx/v5"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetVideosToDB(pool *[]typesdefs.UploadedMedia) error {
|
func SetVideosToDB(pool *[]typesdefs.UploadedMedia) error {
|
||||||
|
|
@ -14,43 +15,49 @@ func SetVideosToDB(pool *[]typesdefs.UploadedMedia) error {
|
||||||
log.Println("No new videos to save to DB.")
|
log.Println("No new videos to save to DB.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
batch := &pgx.Batch{}
|
var to_insert []models.VideosRefs
|
||||||
|
|
||||||
for _, data := range *pool {
|
for _, data := range *pool {
|
||||||
batch.Queue(`
|
to_insert = append(to_insert, models.VideosRefs{
|
||||||
INSERT INTO public.videos_refs (codice, ordine, video, thumb, og_url)
|
Codice: data.Codice,
|
||||||
VALUES ($1, $2, $3, $4, $5)
|
Ordine: int32(data.Order),
|
||||||
ON CONFLICT (codice, ordine) DO UPDATE SET video = $3, thumb = $4
|
Video: data.MediaId,
|
||||||
`, data.Codice, data.Order, data.MediaId, data.ThumbId, data.OGUrl)
|
Thumb: data.ThumbId,
|
||||||
}
|
OgURL: data.OGUrl,
|
||||||
|
})
|
||||||
|
|
||||||
if err := Db.Dbpool.SendBatch(Db.Ctx, batch).Close(); err != nil {
|
|
||||||
return fmt.Errorf("failed to insert/update video batch: %w", err)
|
|
||||||
}
|
}
|
||||||
|
stmt := VideosRefs.INSERT(VideosRefs.AllColumns).MODELS(to_insert).ON_CONFLICT(VideosRefs.Codice, VideosRefs.Ordine).DO_UPDATE(SET(VideosRefs.Video.SET(VideosRefs.EXCLUDED.Video), VideosRefs.Thumb.SET(VideosRefs.EXCLUDED.Thumb)))
|
||||||
|
_, err := stmt.Exec(postgres)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to insert video refs: %w", err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetVideosFromDB() ([]typesdefs.VideosRefs, error) {
|
func GetVideosFromDB() ([]models.VideosRefs, error) {
|
||||||
var videos []typesdefs.VideosRefs
|
var videos []models.VideosRefs
|
||||||
err := pgxscan.Select(Db.Ctx, Db.Dbpool, &videos, "SELECT * FROM public.videos_refs ORDER BY ordine ASC")
|
stmt := VideosRefs.SELECT(VideosRefs.AllColumns).FROM(VideosRefs).ORDER_BY(VideosRefs.Ordine.ASC())
|
||||||
if err != nil && err != pgx.ErrNoRows {
|
err := stmt.Query(postgres, &videos)
|
||||||
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get videos: %w", err)
|
return nil, fmt.Errorf("failed to get videos: %w", err)
|
||||||
}
|
}
|
||||||
return videos, nil
|
return videos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCodiceVideosFromDB(codice string) ([]typesdefs.VideosRefs, error) {
|
func GetCodiceVideosFromDB(codice string) ([]models.VideosRefs, error) {
|
||||||
var videos []typesdefs.VideosRefs
|
var videos []models.VideosRefs
|
||||||
err := pgxscan.Select(Db.Ctx, Db.Dbpool, &videos, "SELECT * FROM public.videos_refs WHERE codice = $1 ORDER BY ordine ASC", codice)
|
stmt := VideosRefs.SELECT(VideosRefs.AllColumns).FROM(VideosRefs).WHERE(VideosRefs.Codice.EQ(Text(codice))).ORDER_BY(VideosRefs.Ordine.ASC())
|
||||||
if err != nil && err != pgx.ErrNoRows {
|
err := stmt.Query(postgres, &videos)
|
||||||
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get videos: %w", err)
|
return nil, fmt.Errorf("failed to get videos: %w", err)
|
||||||
}
|
}
|
||||||
return videos, nil
|
return videos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ClearVideosRefsByCodice(codice string) error {
|
func ClearVideosRefsByCodice(codice string) error {
|
||||||
_, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.videos_refs WHERE codice = $1", codice)
|
stmt := VideosRefs.DELETE().WHERE(VideosRefs.Codice.EQ(Text(codice)))
|
||||||
|
_, err := stmt.Exec(postgres)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to clear videos refs by codice: %w", err)
|
return fmt.Errorf("failed to clear videos refs by codice: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,14 +61,10 @@ 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())
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSONPretty(http.StatusOK,
|
return c.JSONPretty(http.StatusOK,
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"files": files,
|
"files": "",
|
||||||
}, " ")
|
}, " ")
|
||||||
})
|
})
|
||||||
e.POST("/testpost", func(c echo.Context) error {
|
e.POST("/testpost", func(c echo.Context) error {
|
||||||
|
|
@ -79,15 +75,7 @@ func (s *Server) SetupRoutes() *echo.Echo {
|
||||||
})
|
})
|
||||||
e.GET("/health", func(c echo.Context) error {
|
e.GET("/health", func(c echo.Context) error {
|
||||||
h := queries.HealthCheck()
|
h := queries.HealthCheck()
|
||||||
|
return c.JSONPretty(http.StatusOK, fmt.Sprintf("DB Connection: %v", h), " ")
|
||||||
s_error := HealthCheckStorage()
|
|
||||||
var s string
|
|
||||||
if s_error != nil {
|
|
||||||
s = s_error.Error()
|
|
||||||
} else {
|
|
||||||
s = "OK"
|
|
||||||
}
|
|
||||||
return c.JSONPretty(http.StatusOK, fmt.Sprintf("DB Connection: %v, Storage Connection: %v", h, s), " ")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// MIOGEST ROUTES
|
// MIOGEST ROUTES
|
||||||
|
|
|
||||||
|
|
@ -1,223 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"backend/config"
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
|
||||||
"net/textproto"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func HealthCheckStorage() error {
|
|
||||||
resp, err := http.Get(config.Cfg.Storage.Endpoint + "/health")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("storage health check failed: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("storage health check failed: %s", resp.Status)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteFile(id string) error {
|
|
||||||
req, err := http.NewRequest("DELETE", config.Cfg.Storage.Endpoint+"/delete/"+id+"?token="+config.Cfg.Storage.Token, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create delete request: %w", err)
|
|
||||||
}
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("delete request failed: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("delete failed: %s", resp.Status)
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetFile(path string) ([]byte, error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileMetadata struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
OriginalName string `json:"originalName"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
MIMEType string `json:"mimeType"`
|
|
||||||
BlockCount int `json:"blockCount"`
|
|
||||||
UploadedAt time.Time `json:"uploadedAt"`
|
|
||||||
ExpiresAt *time.Time `json:"expiresAt"`
|
|
||||||
Bucket string `json:"bucket,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// func ListFiles() ([]FileMetadata, error) {
|
|
||||||
// //get list of files from api
|
|
||||||
// resp, err := http.Get(config.Cfg.Storage.Endpoint + "/files?token=" + config.Cfg.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
|
|
||||||
// }
|
|
||||||
// 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 ListBucket(bucketName string) ([]FileMetadata, error) {
|
|
||||||
//get list of files from api
|
|
||||||
resp, err := http.Get(config.Cfg.Storage.Endpoint + "/bucket/" + bucketName + "?token=" + config.Cfg.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
|
|
||||||
}
|
|
||||||
var data []FileMetadata
|
|
||||||
err = json.Unmarshal(body, &data)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error unmarshalling response body:", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// type CheckFileResponse struct {
|
|
||||||
// Available []FileMetadata `json:"available"`
|
|
||||||
// Unavailable []string `json:"unavailable"`
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func CheckFiles(ids []string) ([]FileMetadata, []string, error) {
|
|
||||||
|
|
||||||
// //application/json with array of ids as body of post request to /files-check
|
|
||||||
// jsonData, err := json.Marshal(ids)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, nil, err
|
|
||||||
// }
|
|
||||||
// resp, err := http.Post(config.Cfg.Storage.Endpoint+"/files-check?token="+config.Cfg.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, mimeType string, bucket string) (string, error) {
|
|
||||||
|
|
||||||
file, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to open file: %w", err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
var body bytes.Buffer
|
|
||||||
writer := multipart.NewWriter(&body)
|
|
||||||
|
|
||||||
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 {
|
|
||||||
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", bucket)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to write bucket field: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := writer.Close(); err != nil {
|
|
||||||
return "", fmt.Errorf("failed to close writer: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", config.Cfg.Storage.Endpoint+"/upload?token="+config.Cfg.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,8 +1,9 @@
|
||||||
package typesdefs
|
package typesdefs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AnnunciXML struct {
|
type AnnunciXML struct {
|
||||||
|
|
@ -186,67 +187,6 @@ type AnnuncioParsed struct {
|
||||||
Persone *[]string
|
Persone *[]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type AnnunciDB struct {
|
|
||||||
Id int
|
|
||||||
Codice string
|
|
||||||
Locatore *string
|
|
||||||
Tipo_locatore *string
|
|
||||||
Numero *string
|
|
||||||
Idlocatore *string
|
|
||||||
Indirizzo *string
|
|
||||||
Civico *string
|
|
||||||
Comune *string
|
|
||||||
Cap *string
|
|
||||||
Provincia *string
|
|
||||||
Regione *string
|
|
||||||
Lat *string
|
|
||||||
Lon *string
|
|
||||||
Indirizzo_secondario *string
|
|
||||||
Civico_secondario *string
|
|
||||||
Lat_secondario *string
|
|
||||||
Lon_secondario *string
|
|
||||||
Tipo *string
|
|
||||||
Categorie *[]string
|
|
||||||
Prezzo int
|
|
||||||
Anno *string
|
|
||||||
Consegna *string
|
|
||||||
Classe *string
|
|
||||||
Mq *float64
|
|
||||||
Piano *string
|
|
||||||
Piano_palazzo *int
|
|
||||||
Unita_condominio *int
|
|
||||||
Numero_vani *int
|
|
||||||
Numero_camere *int
|
|
||||||
Numero_bagni *int
|
|
||||||
Numero_balconi *int
|
|
||||||
Numero_terrazzi *int
|
|
||||||
Numero_box *int
|
|
||||||
Numero_postiauto *int
|
|
||||||
Accessori *[]string
|
|
||||||
Titolo_it *string
|
|
||||||
Desc_it *string
|
|
||||||
Titolo_en *string
|
|
||||||
Desc_en *string
|
|
||||||
Stato *string
|
|
||||||
Web *bool
|
|
||||||
Caratteristiche *map[string]interface{}
|
|
||||||
Homepage *bool
|
|
||||||
External_videos *[]string
|
|
||||||
Email *string
|
|
||||||
Creato_il *time.Time
|
|
||||||
Modificato_il *time.Time
|
|
||||||
Disponibile_da *time.Time
|
|
||||||
Permanenza *[]int
|
|
||||||
Persone *[]string
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
Name string
|
|
||||||
Size int64
|
|
||||||
Mode os.FileMode
|
|
||||||
ModTime string
|
|
||||||
IsDir bool
|
|
||||||
}
|
|
||||||
type DefaultCaratteristiche struct {
|
type DefaultCaratteristiche struct {
|
||||||
Scheda []struct {
|
Scheda []struct {
|
||||||
Id string `xml:"id"`
|
Id string `xml:"id"`
|
||||||
|
|
@ -273,22 +213,6 @@ type Categoria struct {
|
||||||
Nome string
|
Nome string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImagesRefs struct {
|
|
||||||
Codice string
|
|
||||||
Ordine int
|
|
||||||
Img string
|
|
||||||
Thumb string
|
|
||||||
Og_url string
|
|
||||||
}
|
|
||||||
|
|
||||||
type VideosRefs struct {
|
|
||||||
Codice string
|
|
||||||
Ordine int
|
|
||||||
Video string
|
|
||||||
Thumb string
|
|
||||||
Og_url string
|
|
||||||
}
|
|
||||||
|
|
||||||
type MediaToProcess struct {
|
type MediaToProcess struct {
|
||||||
Codice string
|
Codice string
|
||||||
Url string
|
Url string
|
||||||
|
|
@ -306,7 +230,7 @@ type ProcessedMedia struct {
|
||||||
type UploadedMedia struct {
|
type UploadedMedia struct {
|
||||||
Codice string
|
Codice string
|
||||||
Order int
|
Order int
|
||||||
MediaId string
|
MediaId uuid.UUID
|
||||||
ThumbId string
|
ThumbId uuid.UUID
|
||||||
OGUrl string
|
OGUrl string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,24 +22,24 @@ func MakeUppercase(s string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
return []byte{}, fmt.Errorf("GET error: %v", err)
|
// return []byte{}, fmt.Errorf("GET error: %v", err)
|
||||||
}
|
// }
|
||||||
defer resp.Body.Close()
|
// defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
// if resp.StatusCode != http.StatusOK {
|
||||||
return []byte{}, fmt.Errorf("status error: %v", resp.StatusCode)
|
// return []byte{}, fmt.Errorf("status error: %v", resp.StatusCode)
|
||||||
}
|
// }
|
||||||
|
|
||||||
data, err := io.ReadAll(resp.Body)
|
// data, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return []byte{}, fmt.Errorf("read body: %v", err)
|
// return []byte{}, fmt.Errorf("read body: %v", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return data, nil
|
// return data, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func GetXMLFromFile[T any](filePath string) (T, error) {
|
func GetXMLFromFile[T any](filePath string) (T, error) {
|
||||||
var result T
|
var result T
|
||||||
|
|
@ -144,11 +144,3 @@ func GetStringValue(strPtr *string) string {
|
||||||
func RemoveWhitespace(str string) string {
|
func RemoveWhitespace(str string) string {
|
||||||
return strings.ReplaceAll(str, " ", "")
|
return strings.ReplaceAll(str, " ", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func CommaToDot(str *string) *string {
|
|
||||||
if str != nil {
|
|
||||||
result := strings.ReplaceAll(*str, ",", ".")
|
|
||||||
return &result
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/google/uuid"
|
||||||
"github.com/nfnt/resize"
|
"github.com/nfnt/resize"
|
||||||
"github.com/nickalie/go-webpbin"
|
"github.com/nickalie/go-webpbin"
|
||||||
)
|
)
|
||||||
|
|
@ -111,12 +111,12 @@ func ProcessVideoPool(toProcess *[]typesdefs.MediaToProcess) ([]typesdefs.Upload
|
||||||
defer uploadWg.Done()
|
defer uploadWg.Done()
|
||||||
defer func() { <-uploadSem }() // release semaphore
|
defer func() { <-uploadSem }() // release semaphore
|
||||||
|
|
||||||
videoId, err := UploadFile(video.Path, "video/mp4", "videos")
|
videoId, err := queries.UploadFile(video.Path, "video/mp4", "videos")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to upload video: %v", err)
|
log.Printf("failed to upload video: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
thumbId, err := UploadFile(video.ThumnailPath, "image/webp", "videos")
|
thumbId, err := queries.UploadFile(video.ThumnailPath, "image/webp", "videos")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to upload thumbnail: %v", err)
|
log.Printf("failed to upload thumbnail: %v", err)
|
||||||
return
|
return
|
||||||
|
|
@ -324,29 +324,20 @@ func extractThumbnail(input, output string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func FallbackVideo(c echo.Context) error {
|
|
||||||
// Serve a fallback image if the requested image is not found
|
|
||||||
fallbackPath := "fallback.webp" // Path to your fallback image
|
|
||||||
if _, err := os.Stat(fallbackPath); os.IsNotExist(err) {
|
|
||||||
return c.String(http.StatusNotFound, "Fallback image not found")
|
|
||||||
}
|
|
||||||
return c.File(fallbackPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
type videRef struct {
|
type videRef struct {
|
||||||
Og_url string
|
Og_url string
|
||||||
Video string
|
Video uuid.UUID
|
||||||
Thumb string
|
Thumb uuid.UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
func CodiciToProcessVideos(annunci *typesdefs.AnnunciXML) ([]string, error) {
|
func CodiciToProcessVideos(annunci *typesdefs.AnnunciXML) ([]string, error) {
|
||||||
currentBucketState, err := ListBucket("videos")
|
currentBucketState, err := queries.GetStorageByTag("videos")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to list bucket: %w", err)
|
return nil, fmt.Errorf("failed to list bucket: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//make a list of bucket ids
|
//make a list of bucket ids
|
||||||
bucketIdsMap := make(map[string]bool)
|
bucketIdsMap := make(map[uuid.UUID]bool)
|
||||||
for _, item := range currentBucketState {
|
for _, item := range currentBucketState {
|
||||||
bucketIdsMap[item.ID] = true
|
bucketIdsMap[item.ID] = true
|
||||||
}
|
}
|
||||||
|
|
@ -359,7 +350,7 @@ func CodiciToProcessVideos(annunci *typesdefs.AnnunciXML) ([]string, error) {
|
||||||
dbVideosMap := make(map[string][]videRef) // Convert slice to map for faster lookup
|
dbVideosMap := make(map[string][]videRef) // Convert slice to map for faster lookup
|
||||||
for _, ref := range db_videos {
|
for _, ref := range db_videos {
|
||||||
dbVideosMap[ref.Codice] = append(dbVideosMap[ref.Codice], videRef{
|
dbVideosMap[ref.Codice] = append(dbVideosMap[ref.Codice], videRef{
|
||||||
Og_url: ref.Og_url,
|
Og_url: ref.OgURL,
|
||||||
Video: ref.Video,
|
Video: ref.Video,
|
||||||
Thumb: ref.Thumb,
|
Thumb: ref.Thumb,
|
||||||
})
|
})
|
||||||
|
|
@ -422,7 +413,7 @@ func CodiciToProcessVideos(annunci *typesdefs.AnnunciXML) ([]string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ForceClearVideos(codici []string) error {
|
func ForceClearVideos(codici []string) error {
|
||||||
var idsToDelete []string
|
var idsToDelete uuid.UUIDs
|
||||||
for _, codice := range codici {
|
for _, codice := range codici {
|
||||||
refs, err := queries.GetCodiceVideosFromDB(codice)
|
refs, err := queries.GetCodiceVideosFromDB(codice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -434,7 +425,7 @@ func ForceClearVideos(codici []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
err := DeleteIdsFromStorage(idsToDelete)
|
err := queries.DeleteMultFile(idsToDelete)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete videos from storage: %w", err)
|
return fmt.Errorf("failed to delete videos from storage: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -449,12 +440,12 @@ func ForceClearVideos(codici []string) error {
|
||||||
|
|
||||||
// ReconcileVideos removes stranded videos (in storage but not in DB)
|
// ReconcileVideos removes stranded videos (in storage but not in DB)
|
||||||
func ReconcileVideos() error {
|
func ReconcileVideos() error {
|
||||||
currentBucketState, err := ListBucket("videos")
|
currentBucketState, err := queries.GetStorageByTag("videos")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to list bucket: %w", err)
|
return fmt.Errorf("failed to list bucket: %w", err)
|
||||||
}
|
}
|
||||||
//make a list of bucket ids
|
//make a list of bucket ids
|
||||||
bucketIdsMap := make(map[string]bool)
|
bucketIdsMap := make(map[uuid.UUID]bool)
|
||||||
for _, item := range currentBucketState {
|
for _, item := range currentBucketState {
|
||||||
bucketIdsMap[item.ID] = true
|
bucketIdsMap[item.ID] = true
|
||||||
}
|
}
|
||||||
|
|
@ -462,7 +453,7 @@ func ReconcileVideos() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get videos from DB: %w", err)
|
return fmt.Errorf("failed to get videos from DB: %w", err)
|
||||||
}
|
}
|
||||||
var strandedIds []string
|
var strandedIds uuid.UUIDs
|
||||||
for _, ref := range db_videos {
|
for _, ref := range db_videos {
|
||||||
if !bucketIdsMap[ref.Video] {
|
if !bucketIdsMap[ref.Video] {
|
||||||
strandedIds = append(strandedIds, ref.Video)
|
strandedIds = append(strandedIds, ref.Video)
|
||||||
|
|
@ -472,7 +463,7 @@ func ReconcileVideos() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(strandedIds) > 0 {
|
if len(strandedIds) > 0 {
|
||||||
err := DeleteIdsFromStorage(strandedIds)
|
err := queries.DeleteMultFile(strandedIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete stranded videos from storage: %w", err)
|
return fmt.Errorf("failed to delete stranded videos from storage: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
190
apps/db/migrations/56_storage.up.sql
Normal file
190
apps/db/migrations/56_storage.up.sql
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS public.storage (
|
||||||
|
id uuid NOT NULL DEFAULT uuidv7(),
|
||||||
|
created_at timestamp without time zone NOT NULL DEFAULT now(),
|
||||||
|
filename text NOT NULL,
|
||||||
|
mime_type text NOT NULL,
|
||||||
|
file_size_bytes INT NOT NULL,
|
||||||
|
file_data BYTEA NOT NULL,
|
||||||
|
expires_at timestamp without time zone,
|
||||||
|
tag text
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
DO $$ BEGIN IF EXISTS (
|
||||||
|
SELECT
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'storage'
|
||||||
|
AND column_name = 'file_data'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.storage
|
||||||
|
ALTER COLUMN file_data SET STORAGE EXTERNAL;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
DO $$ BEGIN IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'storage_pkey'
|
||||||
|
AND conrelid = 'public.storage'::regclass
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.storage
|
||||||
|
ADD CONSTRAINT storage_pkey PRIMARY KEY (id);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
--- users_storage
|
||||||
|
DO $$ BEGIN IF EXISTS (
|
||||||
|
SELECT
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'users_storage'
|
||||||
|
) THEN
|
||||||
|
TRUNCATE TABLE users_storage CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
ALTER TABLE IF EXISTS public.users_storage
|
||||||
|
DROP COLUMN IF EXISTS "storageId";
|
||||||
|
|
||||||
|
ALTER TABLE IF EXISTS public.users_storage
|
||||||
|
ADD COLUMN IF NOT EXISTS storage_id uuid;
|
||||||
|
|
||||||
|
DO $$ BEGIN IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint
|
||||||
|
WHERE conname = 'userstorage_storage'
|
||||||
|
AND conrelid = 'public.users_storage'::regclass
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.users_storage
|
||||||
|
ADD CONSTRAINT userstorage_storage
|
||||||
|
FOREIGN KEY (storage_id) REFERENCES public.storage (id)
|
||||||
|
ON UPDATE CASCADE ON DELETE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
ALTER TABLE users_storage
|
||||||
|
DROP CONSTRAINT IF EXISTS users_storage_user_storage_key;
|
||||||
|
|
||||||
|
|
||||||
|
DO $$ BEGIN IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint
|
||||||
|
WHERE conname = 'users_storage_user_storage_key'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.users_storage
|
||||||
|
ADD CONSTRAINT users_storage_user_storage_key UNIQUE ("userId", storage_id);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
---IMAGES
|
||||||
|
|
||||||
|
DO $$ BEGIN IF EXISTS (
|
||||||
|
SELECT
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'images_refs'
|
||||||
|
) THEN
|
||||||
|
TRUNCATE TABLE images_refs;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
DO $$ BEGIN IF EXISTS (
|
||||||
|
SELECT
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'images_refs'
|
||||||
|
AND column_name = 'img'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE images_refs
|
||||||
|
ALTER COLUMN img TYPE uuid USING img::uuid;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
DO $$ BEGIN IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint
|
||||||
|
WHERE conname = 'images_refs_img_storage'
|
||||||
|
AND conrelid = 'public.images_refs'::regclass
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.images_refs
|
||||||
|
ADD CONSTRAINT images_refs_img_storage
|
||||||
|
FOREIGN KEY (img) REFERENCES public.storage (id)
|
||||||
|
ON UPDATE CASCADE ON DELETE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
DO $$ BEGIN IF EXISTS (
|
||||||
|
SELECT
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'images_refs'
|
||||||
|
AND column_name = 'thumb'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE images_refs
|
||||||
|
ALTER COLUMN thumb TYPE uuid USING thumb::uuid;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
DO $$ BEGIN IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint
|
||||||
|
WHERE conname = 'images_refs_thumb_storage'
|
||||||
|
AND conrelid = 'public.images_refs'::regclass
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.images_refs
|
||||||
|
ADD CONSTRAINT images_refs_thumb_storage
|
||||||
|
FOREIGN KEY (thumb) REFERENCES public.storage (id)
|
||||||
|
ON UPDATE CASCADE ON DELETE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
--- VIDEOS
|
||||||
|
|
||||||
|
DO $$ BEGIN IF EXISTS (
|
||||||
|
SELECT
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'videos_refs'
|
||||||
|
) THEN
|
||||||
|
TRUNCATE TABLE videos_refs;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
|
||||||
|
DO $$ BEGIN IF EXISTS (
|
||||||
|
SELECT
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'videos_refs'
|
||||||
|
AND column_name = 'video'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE videos_refs
|
||||||
|
ALTER COLUMN video TYPE uuid USING video::uuid;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
DO $$ BEGIN IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint
|
||||||
|
WHERE conname = 'videos_refs_video_storage'
|
||||||
|
AND conrelid = 'public.videos_refs'::regclass
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.videos_refs
|
||||||
|
ADD CONSTRAINT videos_refs_video_storage
|
||||||
|
FOREIGN KEY (video) REFERENCES public.storage (id)
|
||||||
|
ON UPDATE CASCADE ON DELETE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
DO $$ BEGIN IF EXISTS (
|
||||||
|
SELECT
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'videos_refs'
|
||||||
|
AND column_name = 'thumb'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE videos_refs
|
||||||
|
ALTER COLUMN thumb TYPE uuid USING thumb::uuid;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
DO $$ BEGIN IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint
|
||||||
|
WHERE conname = 'videos_refs_thumb_storage'
|
||||||
|
AND conrelid = 'public.videos_refs'::regclass
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.videos_refs
|
||||||
|
ADD CONSTRAINT videos_refs_thumb_storage
|
||||||
|
FOREIGN KEY (thumb) REFERENCES public.storage (id)
|
||||||
|
ON UPDATE CASCADE ON DELETE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
|
@ -75,11 +75,6 @@ ENV SKEBBY_USER=$SKEBBY_USER
|
||||||
ARG SKEBBY_PASS
|
ARG SKEBBY_PASS
|
||||||
ENV SKEBBY_PASS=$SKEBBY_PASS
|
ENV SKEBBY_PASS=$SKEBBY_PASS
|
||||||
ENV REVALIDATION_SECRET="SWKpgaaLfsyeqV1eOT0WG7TUFBewir8kJXure3O37ki8Lt4Z4IiEgBW4zPHDdM5c"
|
ENV REVALIDATION_SECRET="SWKpgaaLfsyeqV1eOT0WG7TUFBewir8kJXure3O37ki8Lt4Z4IiEgBW4zPHDdM5c"
|
||||||
ARG STORAGE_URL
|
|
||||||
ENV STORAGE_URL=$STORAGE_URL
|
|
||||||
ARG STORAGE_TOKEN
|
|
||||||
ENV STORAGE_TOKEN=$STORAGE_TOKEN
|
|
||||||
|
|
||||||
|
|
||||||
RUN apk add --no-cache curl typst
|
RUN apk add --no-cache curl typst
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ TODOS:
|
||||||
- pre attivazione se admin mostrare tastino contatti in preview
|
- pre attivazione se admin mostrare tastino contatti in preview
|
||||||
- onboarding submit (toast e email che invitano a pagare)
|
- onboarding submit (toast e email che invitano a pagare)
|
||||||
- migliorare onboarding e pag guida
|
- migliorare onboarding e pag guida
|
||||||
|
- rendere opzionale email e numero?
|
||||||
|
|
||||||
AFTER MVP:
|
AFTER MVP:
|
||||||
- TODO migrazione app router https://nextjs.org/docs/pages/building-your-application/upgrading/app-router-migration#migrating-from-pages-to-app
|
- TODO migrazione app router https://nextjs.org/docs/pages/building-your-application/upgrading/app-router-migration#migrating-from-pages-to-app
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
services:
|
|
||||||
storage:
|
|
||||||
image: "ghcr.io/marcopedone/go_fs:latest"
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
environment:
|
|
||||||
PORT: 8080
|
|
||||||
AUTH_TOKEN: ${STORAGE_TOKEN}
|
|
||||||
pull_policy: "always"
|
|
||||||
volumes:
|
|
||||||
- storage_data:/app/storage
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
start_period: 10s
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
storage_data:
|
|
||||||
|
|
@ -3,5 +3,3 @@
|
||||||
# Start the pgAdmin service using its Docker Compose file
|
# Start the pgAdmin service using its Docker Compose file
|
||||||
docker compose -p pgadmin -f pgadmin-docker-compose.yml up -d
|
docker compose -p pgadmin -f pgadmin-docker-compose.yml up -d
|
||||||
|
|
||||||
# Start the KeyDB service using its Docker Compose file
|
|
||||||
docker compose -p infoalloggi-dev -f dev-docker-compose.yml up -d
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@
|
||||||
"ignoreDependencies": [
|
"ignoreDependencies": [
|
||||||
"pdfjs-dist",
|
"pdfjs-dist",
|
||||||
"kysely-plugin-serialize",
|
"kysely-plugin-serialize",
|
||||||
"sharp",
|
|
||||||
"@react-email/components",
|
"@react-email/components",
|
||||||
"kanel-kysely",
|
"kanel-kysely",
|
||||||
"kanel-zod",
|
"kanel-zod",
|
||||||
|
|
|
||||||
|
|
@ -17,51 +17,31 @@ async function createNextConfig(): Promise<NextConfig> {
|
||||||
|
|
||||||
await jiti.import("./src/env.ts");
|
await jiti.import("./src/env.ts");
|
||||||
|
|
||||||
const buildId = `${Date.now().toString(36)}`;
|
|
||||||
const apiVersion = `v.${buildId}`;
|
|
||||||
|
|
||||||
const configs: NextConfig = {
|
const configs: NextConfig = {
|
||||||
compiler: {
|
compiler: {
|
||||||
removeConsole: false,
|
removeConsole: false,
|
||||||
},
|
},
|
||||||
experimental: {
|
experimental: {
|
||||||
scrollRestoration: true,
|
scrollRestoration: true,
|
||||||
|
proxyClientMaxBodySize: "300mb",
|
||||||
// esmExternals: "loose", // This if react-pdf gives compilation issues
|
// esmExternals: "loose", // This if react-pdf gives compilation issues
|
||||||
},
|
},
|
||||||
allowedDevOrigins: ["host.docker.internal"],
|
allowedDevOrigins: ["host.docker.internal"],
|
||||||
generateBuildId: async () => buildId,
|
|
||||||
devIndicators: false,
|
devIndicators: false,
|
||||||
headers: async () => {
|
// headers: async () => {
|
||||||
return [
|
// return [
|
||||||
{
|
// {
|
||||||
source: "/:path*",
|
// source: "/api/trpc/:path*",
|
||||||
headers: [
|
// headers: [
|
||||||
{
|
// // {
|
||||||
key: "X-Frame-Options",
|
// // key: "Cache-Control",
|
||||||
value: "",
|
// // value:
|
||||||
},
|
// // "no-cache, no-store, must-revalidate, max-age=0, proxy-revalidate",
|
||||||
],
|
// // },
|
||||||
},
|
// ],
|
||||||
{
|
// },
|
||||||
source: "/api/trpc/:path*",
|
// ];
|
||||||
headers: [
|
// },
|
||||||
{
|
|
||||||
key: "Cache-Control",
|
|
||||||
value:
|
|
||||||
"no-cache, no-store, must-revalidate, max-age=0, proxy-revalidate",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "X-API-Version",
|
|
||||||
value: apiVersion,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "X-Build-ID",
|
|
||||||
value: buildId,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultLocale: "it",
|
defaultLocale: "it",
|
||||||
locales: ["it", "en"],
|
locales: ["it", "en"],
|
||||||
|
|
@ -70,17 +50,6 @@ async function createNextConfig(): Promise<NextConfig> {
|
||||||
images: {
|
images: {
|
||||||
unoptimized: true,
|
unoptimized: true,
|
||||||
minimumCacheTTL: 7200,
|
minimumCacheTTL: 7200,
|
||||||
remotePatterns: [
|
|
||||||
{
|
|
||||||
hostname: "*.googleusercontent.com",
|
|
||||||
pathname: "**",
|
|
||||||
port: "",
|
|
||||||
protocol: "https",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hostname: `${env.STORAGE_URL}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
output: env.NODE_ENV === "production" ? "standalone" : undefined, // This is for docker builds
|
output: env.NODE_ENV === "production" ? "standalone" : undefined, // This is for docker builds
|
||||||
transpilePackages: ["@t3-oss/env-nextjs", "@t3-oss/env-core"],
|
transpilePackages: ["@t3-oss/env-nextjs", "@t3-oss/env-core"],
|
||||||
|
|
|
||||||
51
apps/infoalloggi/package-lock.json
generated
51
apps/infoalloggi/package-lock.json
generated
|
|
@ -81,7 +81,6 @@
|
||||||
"react-reverse-portal": "^2.3.0",
|
"react-reverse-portal": "^2.3.0",
|
||||||
"react-select": "^5.10.2",
|
"react-select": "^5.10.2",
|
||||||
"react-use": "^17.6.0",
|
"react-use": "^17.6.0",
|
||||||
"sharp": "^0.34.5",
|
|
||||||
"stripe": "^22.1.1",
|
"stripe": "^22.1.1",
|
||||||
"superjson": "^2.2.6",
|
"superjson": "^2.2.6",
|
||||||
"tailwind-merge": "^3.6.0",
|
"tailwind-merge": "^3.6.0",
|
||||||
|
|
@ -1376,6 +1375,7 @@
|
||||||
"node_modules/@img/colour": {
|
"node_modules/@img/colour": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
|
|
@ -1387,6 +1387,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1409,6 +1410,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1431,6 +1433,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1447,6 +1450,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1463,6 +1467,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1479,6 +1484,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1495,6 +1501,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1511,6 +1518,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1527,6 +1535,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1543,6 +1552,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1559,6 +1569,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1575,6 +1586,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1591,6 +1603,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1613,6 +1626,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1635,6 +1649,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1657,6 +1672,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1679,6 +1695,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1701,6 +1718,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1723,6 +1741,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1745,6 +1764,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1767,6 +1787,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"wasm32"
|
"wasm32"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -1786,6 +1807,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1805,6 +1827,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1822,6 +1845,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -5776,6 +5800,29 @@
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": {
|
||||||
|
"version": "1.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
|
||||||
|
"integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/wasi-threads": "1.2.1",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||||
|
"version": "1.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
|
||||||
|
"integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||||
"version": "1.0.0-rc.15",
|
"version": "1.0.0-rc.15",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz",
|
||||||
|
|
@ -12459,6 +12506,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.4",
|
"version": "7.7.4",
|
||||||
|
"devOptional": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
|
|
@ -12521,6 +12569,7 @@
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@img/colour": "^1.0.0",
|
"@img/colour": "^1.0.0",
|
||||||
"detect-libc": "^2.1.2",
|
"detect-libc": "^2.1.2",
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,6 @@
|
||||||
"react-reverse-portal": "^2.3.0",
|
"react-reverse-portal": "^2.3.0",
|
||||||
"react-select": "^5.10.2",
|
"react-select": "^5.10.2",
|
||||||
"react-use": "^17.6.0",
|
"react-use": "^17.6.0",
|
||||||
"sharp": "^0.34.5",
|
|
||||||
"stripe": "^22.1.1",
|
"stripe": "^22.1.1",
|
||||||
"superjson": "^2.2.6",
|
"superjson": "^2.2.6",
|
||||||
"tailwind-merge": "^3.6.0",
|
"tailwind-merge": "^3.6.0",
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export const AllegatoIframe = ({
|
||||||
setIsIOS(/iphone|ipad|ipod/i.test(userAgent));
|
setIsIOS(/iphone|ipad|ipod/i.test(userAgent));
|
||||||
|
|
||||||
// Try to detect if file is PDF from extension
|
// Try to detect if file is PDF from extension
|
||||||
setIsPDF(allegato.mimeType === "application/pdf");
|
setIsPDF(allegato.mime_type === "application/pdf");
|
||||||
}, [allegato]);
|
}, [allegato]);
|
||||||
|
|
||||||
const handleIframeLoad = () => {
|
const handleIframeLoad = () => {
|
||||||
|
|
@ -52,7 +52,7 @@ export const AllegatoIframe = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestUrl = getStorageUrl({
|
const requestUrl = getStorageUrl({
|
||||||
storageId: allegato.id,
|
id: allegato.id,
|
||||||
params: { mode: "inline" },
|
params: { mode: "inline" },
|
||||||
});
|
});
|
||||||
// For PDF on iOS, provide download link instead
|
// For PDF on iOS, provide download link instead
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ const SelectedComp = memo(
|
||||||
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={getStorageUrl({
|
src={getStorageUrl({
|
||||||
storageId: selected.images[0].img,
|
id: selected.images[0].img,
|
||||||
params: {
|
params: {
|
||||||
media: "image",
|
media: "image",
|
||||||
cacheKey: selected.media_updated_at?.toISOString(),
|
cacheKey: selected.media_updated_at?.toISOString(),
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,6 @@ export const CardAnnuncio = ({
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -219,7 +218,7 @@ export const CardAnnuncio = ({
|
||||||
<Image
|
<Image
|
||||||
alt={t.card.alt_immagine}
|
alt={t.card.alt_immagine}
|
||||||
blurDataURL={getStorageUrl({
|
blurDataURL={getStorageUrl({
|
||||||
storageId: img.thumb,
|
id: img.thumb,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: media_updated_at?.toISOString(),
|
cacheKey: media_updated_at?.toISOString(),
|
||||||
media: "image",
|
media: "image",
|
||||||
|
|
@ -232,7 +231,7 @@ export const CardAnnuncio = ({
|
||||||
height={1080}
|
height={1080}
|
||||||
priority={idx === 0}
|
priority={idx === 0}
|
||||||
src={getStorageUrl({
|
src={getStorageUrl({
|
||||||
storageId: img.img,
|
id: img.img,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: media_updated_at?.toISOString(),
|
cacheKey: media_updated_at?.toISOString(),
|
||||||
media: "image",
|
media: "image",
|
||||||
|
|
@ -255,7 +254,7 @@ export const CardAnnuncio = ({
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
className="h-64 object-cover"
|
className="h-64 object-cover"
|
||||||
coverImage={getStorageUrl({
|
coverImage={getStorageUrl({
|
||||||
storageId: video.thumb,
|
id: video.thumb,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: media_updated_at?.toISOString(),
|
cacheKey: media_updated_at?.toISOString(),
|
||||||
media: "image",
|
media: "image",
|
||||||
|
|
@ -263,7 +262,7 @@ export const CardAnnuncio = ({
|
||||||
})}
|
})}
|
||||||
key={`videoplayer-${video}`}
|
key={`videoplayer-${video}`}
|
||||||
videoSrc={getStorageUrl({
|
videoSrc={getStorageUrl({
|
||||||
storageId: video.video,
|
id: video.video,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: media_updated_at?.toISOString(),
|
cacheKey: media_updated_at?.toISOString(),
|
||||||
media: "video",
|
media: "video",
|
||||||
|
|
@ -301,7 +300,7 @@ export const CardAnnuncio = ({
|
||||||
<Image
|
<Image
|
||||||
alt={t.card.alt_immagine}
|
alt={t.card.alt_immagine}
|
||||||
blurDataURL={getStorageUrl({
|
blurDataURL={getStorageUrl({
|
||||||
storageId: images[0].thumb,
|
id: images[0].thumb,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: media_updated_at?.toISOString(),
|
cacheKey: media_updated_at?.toISOString(),
|
||||||
media: "image",
|
media: "image",
|
||||||
|
|
@ -311,7 +310,7 @@ export const CardAnnuncio = ({
|
||||||
height={1080}
|
height={1080}
|
||||||
priority={true}
|
priority={true}
|
||||||
src={getStorageUrl({
|
src={getStorageUrl({
|
||||||
storageId: images[0].img,
|
id: images[0].img,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: media_updated_at?.toISOString(),
|
cacheKey: media_updated_at?.toISOString(),
|
||||||
media: "image",
|
media: "image",
|
||||||
|
|
@ -454,17 +453,17 @@ export const CarouselAnnuncio = ({
|
||||||
"aspect-square max-h-92 w-full rounded-md bg-[#e6e9ec] object-cover sm:max-h-80 xl:max-h-100",
|
"aspect-square max-h-92 w-full rounded-md bg-[#e6e9ec] object-cover sm:max-h-80 xl:max-h-100",
|
||||||
immagini.length === 1 && "object-contain",
|
immagini.length === 1 && "object-contain",
|
||||||
)}
|
)}
|
||||||
height={400}
|
height={500}
|
||||||
onClick={() => handleOpenModal(idx)}
|
onClick={() => handleOpenModal(idx)}
|
||||||
priority={idx === 0}
|
priority={idx === 0}
|
||||||
src={getStorageUrl({
|
src={getStorageUrl({
|
||||||
storageId: img.img,
|
id: img.img,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: updated_at?.toISOString(),
|
cacheKey: updated_at?.toISOString(),
|
||||||
media: "image",
|
media: "image",
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
width={800}
|
width={500}
|
||||||
/>
|
/>
|
||||||
<Maximize2
|
<Maximize2
|
||||||
className="absolute right-2 bottom-2 size-7 origin-bottom-right cursor-pointer rounded-md bg-muted-foreground/60 stroke-2 stroke-muted p-1 transition-all duration-300 ease-in-out hover:scale-150 group-hover:opacity-100 sm:opacity-0"
|
className="absolute right-2 bottom-2 size-7 origin-bottom-right cursor-pointer rounded-md bg-muted-foreground/60 stroke-2 stroke-muted p-1 transition-all duration-300 ease-in-out hover:scale-150 group-hover:opacity-100 sm:opacity-0"
|
||||||
|
|
@ -486,7 +485,7 @@ export const CarouselAnnuncio = ({
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
className="aspect-square max-h-92 object-cover sm:max-h-80 xl:max-h-100"
|
className="aspect-square max-h-92 object-cover sm:max-h-80 xl:max-h-100"
|
||||||
coverImage={getStorageUrl({
|
coverImage={getStorageUrl({
|
||||||
storageId: video.thumb,
|
id: video.thumb,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: updated_at?.toISOString(),
|
cacheKey: updated_at?.toISOString(),
|
||||||
media: "image",
|
media: "image",
|
||||||
|
|
@ -497,7 +496,7 @@ export const CarouselAnnuncio = ({
|
||||||
idx
|
idx
|
||||||
}-${openModal}`}
|
}-${openModal}`}
|
||||||
videoSrc={getStorageUrl({
|
videoSrc={getStorageUrl({
|
||||||
storageId: video.video,
|
id: video.video,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: updated_at?.toISOString(),
|
cacheKey: updated_at?.toISOString(),
|
||||||
media: "video",
|
media: "video",
|
||||||
|
|
@ -544,7 +543,7 @@ export const CarouselAnnuncio = ({
|
||||||
className="mx-auto h-[90vh] w-full rounded-md bg-[#e6e9ec] object-contain sm:w-[80vw]"
|
className="mx-auto h-[90vh] w-full rounded-md bg-[#e6e9ec] object-contain sm:w-[80vw]"
|
||||||
height={1080}
|
height={1080}
|
||||||
src={getStorageUrl({
|
src={getStorageUrl({
|
||||||
storageId: img.img,
|
id: img.img,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: updated_at?.toISOString(),
|
cacheKey: updated_at?.toISOString(),
|
||||||
media: "image",
|
media: "image",
|
||||||
|
|
@ -563,14 +562,14 @@ export const CarouselAnnuncio = ({
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
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={getStorageUrl({
|
coverImage={getStorageUrl({
|
||||||
storageId: video.thumb,
|
id: video.thumb,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: updated_at?.toISOString(),
|
cacheKey: updated_at?.toISOString(),
|
||||||
media: "image",
|
media: "image",
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
videoSrc={getStorageUrl({
|
videoSrc={getStorageUrl({
|
||||||
storageId: video.video,
|
id: video.video,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: updated_at?.toISOString(),
|
cacheKey: updated_at?.toISOString(),
|
||||||
media: "video",
|
media: "video",
|
||||||
|
|
|
||||||
|
|
@ -309,7 +309,7 @@ function FileList({
|
||||||
>
|
>
|
||||||
<div className="col-span-6 flex items-center gap-2 md:col-span-8">
|
<div className="col-span-6 flex items-center gap-2 md:col-span-8">
|
||||||
<div className="shrink-0">
|
<div className="shrink-0">
|
||||||
<ExtIcon mimeType={file.mimeType} />
|
<ExtIcon mimeType={file.mime_type} />
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
aria-label="Allegato"
|
aria-label="Allegato"
|
||||||
|
|
@ -317,17 +317,17 @@ function FileList({
|
||||||
href={`/area-riservata/allegato-view/${file.id}`}
|
href={`/area-riservata/allegato-view/${file.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
{file.originalName}
|
{file.filename}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-span-4 text-muted-foreground md:col-span-3">
|
<div className="col-span-4 text-muted-foreground md:col-span-3">
|
||||||
{format(new Date(file.uploadedAt), "d MMM yyyy")}
|
{format(new Date(file.created_at), "d MMM yyyy")}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-2 flex justify-end gap-1 md:col-span-1">
|
<div className="col-span-2 flex justify-end gap-1 md:col-span-1">
|
||||||
<Link
|
<Link
|
||||||
href={getStorageUrl({
|
href={getStorageUrl({
|
||||||
storageId: file.id,
|
id: file.id,
|
||||||
params: { mode: "download" },
|
params: { mode: "download" },
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|
@ -352,7 +352,7 @@ function FileList({
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
aria-label="Rinomina"
|
aria-label="Rinomina"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
selectHandler(file.id, file.originalName || "")
|
selectHandler(file.id, file.filename || "")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Pencil className="mr-2 size-4" />
|
<Pencil className="mr-2 size-4" />
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import toast from "react-hot-toast";
|
||||||
import { getStorageUrl } from "~/lib/storage_utils";
|
import { getStorageUrl } from "~/lib/storage_utils";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import { useSession } from "~/providers/SessionProvider";
|
import { useSession } from "~/providers/SessionProvider";
|
||||||
|
import type { StorageId } from "~/schemas/public/Storage";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
||||||
import type { CompleteUserData } from "~/server/services/user.service";
|
import type { CompleteUserData } from "~/server/services/user.service";
|
||||||
|
|
@ -133,7 +134,7 @@ export const DocumentiUploadSection = ({
|
||||||
}: {
|
}: {
|
||||||
title: string;
|
title: string;
|
||||||
document: {
|
document: {
|
||||||
storageId: string;
|
storage_id: StorageId | null;
|
||||||
ref: UsersStorageUserStorageId | null;
|
ref: UsersStorageUserStorageId | null;
|
||||||
} | null;
|
} | null;
|
||||||
onUpload: (id: UsersStorageUserStorageId) => void;
|
onUpload: (id: UsersStorageUserStorageId) => void;
|
||||||
|
|
@ -145,7 +146,7 @@ export const DocumentiUploadSection = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { locale } = useTranslation();
|
const { locale } = useTranslation();
|
||||||
const [imageError, setImageError] = useState(false);
|
const [imageError, setImageError] = useState(false);
|
||||||
if (document?.storageId) {
|
if (document?.storage_id) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<h4 className="font-medium">{title}</h4>
|
<h4 className="font-medium">{title}</h4>
|
||||||
|
|
@ -167,7 +168,7 @@ export const DocumentiUploadSection = ({
|
||||||
height={96}
|
height={96}
|
||||||
onError={() => setImageError(true)}
|
onError={() => setImageError(true)}
|
||||||
src={getStorageUrl({
|
src={getStorageUrl({
|
||||||
storageId: document.storageId,
|
id: document.storage_id,
|
||||||
})}
|
})}
|
||||||
width={96}
|
width={96}
|
||||||
/>
|
/>
|
||||||
|
|
@ -175,7 +176,7 @@ export const DocumentiUploadSection = ({
|
||||||
<div className="flex size-24 items-center justify-center overflow-clip rounded-md border bg-muted">
|
<div className="flex size-24 items-center justify-center overflow-clip rounded-md border bg-muted">
|
||||||
<iframe
|
<iframe
|
||||||
className="size-full"
|
className="size-full"
|
||||||
src={getStorageUrl({ storageId: document.storageId })}
|
src={getStorageUrl({ id: document.storage_id })}
|
||||||
title="doc"
|
title="doc"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -188,7 +189,7 @@ export const DocumentiUploadSection = ({
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col justify-center gap-2">
|
<div className="flex flex-col justify-center gap-2">
|
||||||
<Link
|
<Link
|
||||||
href={`/area-riservata/allegato-view/${document.storageId}`}
|
href={`/area-riservata/allegato-view/${document.storage_id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<Button size="sm" variant="outline">
|
<Button size="sm" variant="outline">
|
||||||
|
|
@ -231,8 +232,8 @@ export const DocumentiUploadSection = ({
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<h4 className="font-medium">{title}</h4>
|
<h4 className="font-medium">{title}</h4>
|
||||||
<UploadModal
|
<UploadModal
|
||||||
cb_onUpload={({ userStorageIds }) => {
|
cb_onUpload={(uploaded) => {
|
||||||
const id = userStorageIds[0];
|
const id = uploaded[0]?.userStorageId;
|
||||||
if (id) onUpload(id);
|
if (id) onUpload(id);
|
||||||
}}
|
}}
|
||||||
isAdmin={isAdmin}
|
isAdmin={isAdmin}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export const ChatAttachments = ({
|
||||||
>
|
>
|
||||||
<div className="flex grow basis-full items-center gap-2 md:col-span-8">
|
<div className="flex grow basis-full items-center gap-2 md:col-span-8">
|
||||||
<div className="shrink-0">
|
<div className="shrink-0">
|
||||||
<ExtIcon mimeType={file.mimeType} />
|
<ExtIcon mimeType={file.mime_type} />
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
aria-label="Allegato"
|
aria-label="Allegato"
|
||||||
|
|
@ -46,14 +46,14 @@ export const ChatAttachments = ({
|
||||||
href={`/area-riservata/allegato-view/${file.id}`}
|
href={`/area-riservata/allegato-view/${file.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
{file.originalName}
|
{file.filename}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end gap-1 md:col-span-1">
|
<div className="flex justify-end gap-1 md:col-span-1">
|
||||||
<Link
|
<Link
|
||||||
href={getStorageUrl({
|
href={getStorageUrl({
|
||||||
storageId: file.id,
|
id: file.id,
|
||||||
params: { mode: "download" },
|
params: { mode: "download" },
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ import { uploadHandler } from "~/hooks/storageClienSideHooks";
|
||||||
import { useMediaQuery } from "~/hooks/use-media-query";
|
import { useMediaQuery } from "~/hooks/use-media-query";
|
||||||
import { useChatContext } from "~/providers/ChatProvider";
|
import { useChatContext } from "~/providers/ChatProvider";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
export default function ChatBottombar() {
|
export default function ChatBottombar() {
|
||||||
|
|
@ -68,56 +67,32 @@ export default function ChatBottombar() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { mutateAsync: setUserStorage } =
|
|
||||||
api.storage.addUserStorageEntry.useMutation({
|
|
||||||
onMutate: () => {
|
|
||||||
toast(t.allegati.allegato_caricato, {
|
|
||||||
icon: "📤",
|
|
||||||
toasterId: "uploading-toast",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onSettled: async () => {
|
|
||||||
await utils.storage.getAll.invalidate();
|
|
||||||
await utils.storage.retrieveUserFileData.invalidate({
|
|
||||||
userId: userinfo.id,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const uploadFiles = async () => {
|
const uploadFiles = async () => {
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const uploadedFiles: {
|
toast(t.allegati.allegato_caricato, {
|
||||||
storageId: string;
|
icon: "📤",
|
||||||
userStorageId: UsersStorageUserStorageId;
|
toasterId: "uploading-toast",
|
||||||
}[] = [];
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const from_admin = session.isAdmin;
|
const from_admin = session.isAdmin;
|
||||||
|
|
||||||
const { status, attachments } = await uploadHandler({
|
const { attachments, failed } = await uploadHandler({
|
||||||
files,
|
files,
|
||||||
from_admin,
|
from_admin,
|
||||||
});
|
});
|
||||||
if (!status) {
|
|
||||||
toast.error(t.allegati.errore_caricamento, {
|
for (const file of failed) {
|
||||||
|
toast.error(`Errore caricamento: ${file}`, {
|
||||||
icon: "❌",
|
icon: "❌",
|
||||||
toasterId: "uploading-toast",
|
toasterId: "uploading-toast",
|
||||||
});
|
});
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
for (const storageId of attachments) {
|
|
||||||
const res = await setUserStorage({
|
|
||||||
storageId,
|
|
||||||
userId: userinfo.id,
|
|
||||||
from_admin,
|
|
||||||
});
|
|
||||||
if (!res) continue;
|
|
||||||
uploadedFiles.push({ storageId, userStorageId: res.user_storage_id });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return uploadedFiles;
|
return attachments;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(
|
toast.error(
|
||||||
error instanceof Error ? error.message : t.allegati.errore_sconosciuto,
|
error instanceof Error ? error.message : t.allegati.errore_sconosciuto,
|
||||||
|
|
@ -125,7 +100,16 @@ export default function ChatBottombar() {
|
||||||
);
|
);
|
||||||
return [];
|
return [];
|
||||||
} finally {
|
} finally {
|
||||||
|
toast.success(t.allegati.success_caricamento, {
|
||||||
|
icon: "✅",
|
||||||
|
toasterId: "uploading-toast",
|
||||||
|
});
|
||||||
setFiles([]);
|
setFiles([]);
|
||||||
|
await utils.storage.getAll.invalidate();
|
||||||
|
|
||||||
|
await utils.storage.retrieveUserFileData.invalidate({
|
||||||
|
userId: userinfo.id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ const ContrattoSection = () => {
|
||||||
});
|
});
|
||||||
if (selectedDoc) {
|
if (selectedDoc) {
|
||||||
setUserStorage({
|
setUserStorage({
|
||||||
storageId: selectedDoc,
|
storage_id: selectedDoc,
|
||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -156,9 +156,9 @@ const ContrattoSection = () => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<UploadModal
|
<UploadModal
|
||||||
cb_onUpload={({ storageIds }) => {
|
cb_onUpload={(uploaded) => {
|
||||||
if (storageIds.length === 1 && storageIds[0]) {
|
if (uploaded.length === 1 && uploaded[0]) {
|
||||||
setSelectedDoc(storageIds[0]);
|
setSelectedDoc(uploaded[0].storageId);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
isAdmin
|
isAdmin
|
||||||
|
|
@ -499,7 +499,7 @@ const RicevutaSection = () => {
|
||||||
});
|
});
|
||||||
if (selectedDoc) {
|
if (selectedDoc) {
|
||||||
setUserStorage({
|
setUserStorage({
|
||||||
storageId: selectedDoc,
|
storage_id: selectedDoc,
|
||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -545,9 +545,9 @@ const RicevutaSection = () => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<UploadModal
|
<UploadModal
|
||||||
cb_onUpload={({ storageIds }) => {
|
cb_onUpload={(uploaded) => {
|
||||||
if (storageIds.length === 1 && storageIds[0]) {
|
if (uploaded.length === 1 && uploaded[0]) {
|
||||||
setSelectedDoc(storageIds[0]);
|
setSelectedDoc(uploaded[0].storageId);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
isAdmin
|
isAdmin
|
||||||
|
|
|
||||||
|
|
@ -329,7 +329,7 @@ export const AnnuncioDisplay = ({
|
||||||
src={
|
src={
|
||||||
(data?.images[0] &&
|
(data?.images[0] &&
|
||||||
getStorageUrl({
|
getStorageUrl({
|
||||||
storageId: data.images[0].img,
|
id: data.images[0].img,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: data.media_updated_at?.toISOString(),
|
cacheKey: data.media_updated_at?.toISOString(),
|
||||||
media: "image",
|
media: "image",
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,9 @@ export const DocInfo = ({
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<Button className="w-fit grow" variant="outline">
|
<Button className="w-fit grow" variant="outline">
|
||||||
<ExtIcon mimeType={file_info.mimeType} />
|
<ExtIcon mimeType={file_info.mime_type} />
|
||||||
<span className="max-w-40 truncate sm:max-w-80">
|
<span className="max-w-40 truncate sm:max-w-80">
|
||||||
{file_info.originalName}
|
{file_info.filename}
|
||||||
</span>
|
</span>
|
||||||
<ExternalLink className="size-4" />
|
<ExternalLink className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -103,10 +103,8 @@ export const DocCombo = ({
|
||||||
value={file.id}
|
value={file.id}
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<ExtIcon mimeType={file.mimeType} />
|
<ExtIcon mimeType={file.mime_type} />
|
||||||
<span className="block font-medium">
|
<span className="block font-medium">{file.filename}</span>
|
||||||
{file.originalName}
|
|
||||||
</span>
|
|
||||||
{current === file.id && (
|
{current === file.id && (
|
||||||
<Check className="size-4 text-accent-foreground" />
|
<Check className="size-4 text-accent-foreground" />
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { ColumnDef, Table } from "@tanstack/react-table";
|
import type { ColumnDef } from "@tanstack/react-table";
|
||||||
import { ExternalLink, MoreHorizontal, Pen, Trash } from "lucide-react";
|
import { ExternalLink, MoreHorizontal, Pen, Trash } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { type FormEvent, useEffect, useState } from "react";
|
import { type FormEvent, useEffect, useState } from "react";
|
||||||
|
|
@ -36,7 +36,10 @@ import {
|
||||||
} from "~/components/ui/dropdown-menu";
|
} from "~/components/ui/dropdown-menu";
|
||||||
import Input from "~/components/ui/input";
|
import Input from "~/components/ui/input";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import { useStorageTable } from "~/providers/StorageTableProvider";
|
import {
|
||||||
|
type StorageTableType,
|
||||||
|
useStorageTable,
|
||||||
|
} from "~/providers/StorageTableProvider";
|
||||||
import type { FileMetadataWithAdmin } from "~/server/services/storage.service";
|
import type { FileMetadataWithAdmin } from "~/server/services/storage.service";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
|
|
@ -56,8 +59,18 @@ export const StorageTable = ({
|
||||||
allegatoId: string;
|
allegatoId: string;
|
||||||
filename: string | null;
|
filename: string | null;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
const tabledata = data;
|
||||||
|
|
||||||
const [tableInstance, setTableInstance] = useState<Table<Column> | null>(
|
const columns_titles = {
|
||||||
|
actions: "Azioni",
|
||||||
|
created_at: "Data creazione",
|
||||||
|
mime_type: "Tipo file",
|
||||||
|
filename: "Nome file",
|
||||||
|
from_admin: "Caricato da",
|
||||||
|
id: "Codice",
|
||||||
|
};
|
||||||
|
type Column = (typeof tabledata)[number];
|
||||||
|
const [tableInstance, setTableInstance] = useState<StorageTableType | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -68,18 +81,6 @@ export const StorageTable = ({
|
||||||
}
|
}
|
||||||
}, [tableInstance, setTable]);
|
}, [tableInstance, setTable]);
|
||||||
|
|
||||||
const tabledata = data;
|
|
||||||
|
|
||||||
const columns_titles = {
|
|
||||||
actions: "Azioni",
|
|
||||||
uploadedAt: "Data creazione",
|
|
||||||
type: "Tipo file",
|
|
||||||
originalName: "Nome file",
|
|
||||||
from_admin: "Caricato da",
|
|
||||||
id: "Codice",
|
|
||||||
};
|
|
||||||
type Column = (typeof tabledata)[number];
|
|
||||||
|
|
||||||
const columns: ColumnDef<Column>[] = [
|
const columns: ColumnDef<Column>[] = [
|
||||||
{
|
{
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
|
|
@ -104,7 +105,7 @@ export const StorageTable = ({
|
||||||
id: "select",
|
id: "select",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "originalName",
|
accessorKey: "filename",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
|
|
@ -113,7 +114,7 @@ export const StorageTable = ({
|
||||||
href={`/area-riservata/allegato-view/${row.original.id}`}
|
href={`/area-riservata/allegato-view/${row.original.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
{row.original.originalName}
|
{row.original.filename}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -121,14 +122,14 @@ export const StorageTable = ({
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<DataTableColumnHeader
|
<DataTableColumnHeader
|
||||||
column={column}
|
column={column}
|
||||||
title={columns_titles.originalName}
|
title={columns_titles.filename}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "uploadedAt",
|
accessorKey: "created_at",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const date = row.getValue("uploadedAt");
|
const date = row.getValue("created_at");
|
||||||
return new Date(date as Date).toLocaleDateString();
|
return new Date(date as Date).toLocaleDateString();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -136,7 +137,7 @@ export const StorageTable = ({
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<DataTableColumnHeader
|
<DataTableColumnHeader
|
||||||
column={column}
|
column={column}
|
||||||
title={columns_titles.uploadedAt}
|
title={columns_titles.created_at}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -187,7 +188,7 @@ export const StorageTable = ({
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setRenameTarget({
|
setRenameTarget({
|
||||||
allegatoId: data.id,
|
allegatoId: data.id,
|
||||||
filename: data.originalName,
|
filename: data.filename,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
role="button"
|
role="button"
|
||||||
|
|
@ -215,7 +216,7 @@ export const StorageTable = ({
|
||||||
];
|
];
|
||||||
|
|
||||||
const searchFiltro: SearchFiltro = {
|
const searchFiltro: SearchFiltro = {
|
||||||
columnName: "originalName",
|
columnName: "filename",
|
||||||
placeholder: "Cerca file ...",
|
placeholder: "Cerca file ...",
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
|
@ -224,7 +225,7 @@ export const StorageTable = ({
|
||||||
columns={columns}
|
columns={columns}
|
||||||
columns_titles={columns_titles}
|
columns_titles={columns_titles}
|
||||||
data={tabledata}
|
data={tabledata}
|
||||||
defaultSort={[{ desc: true, id: "uploadedAt" }]}
|
defaultSort={[{ desc: true, id: "created_at" }]}
|
||||||
onStateChange={cb_onStateChange}
|
onStateChange={cb_onStateChange}
|
||||||
onTableInit={setTableInstance}
|
onTableInit={setTableInstance}
|
||||||
pinnedFiltri={undefined}
|
pinnedFiltri={undefined}
|
||||||
|
|
|
||||||
|
|
@ -22,16 +22,12 @@ import {
|
||||||
FileUploadTrigger,
|
FileUploadTrigger,
|
||||||
} from "~/components/custom_ui/fileUpload";
|
} from "~/components/custom_ui/fileUpload";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { uploadHandler } from "~/hooks/storageClienSideHooks";
|
import { type Attachment, uploadHandler } from "~/hooks/storageClienSideHooks";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
type UploadCallback = (args: {
|
type UploadCallback = (uploaded: Attachment[]) => void;
|
||||||
storageIds: string[];
|
|
||||||
userStorageIds: UsersStorageUserStorageId[];
|
|
||||||
}) => void;
|
|
||||||
export const UploadModal = (props: UploadComponentProps) => {
|
export const UploadModal = (props: UploadComponentProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|
@ -78,22 +74,6 @@ const UploadComponent = ({
|
||||||
const [files, setFiles] = useState<File[]>([]);
|
const [files, setFiles] = useState<File[]>([]);
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
|
|
||||||
const { mutateAsync: setUserStorage } =
|
|
||||||
api.storage.addUserStorageEntry.useMutation({
|
|
||||||
onMutate: () => {
|
|
||||||
toast(t.allegati.allegato_caricato, {
|
|
||||||
icon: "📤",
|
|
||||||
toasterId: "uploading-toast",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onSettled: async () => {
|
|
||||||
await utils.storage.getAll.invalidate();
|
|
||||||
await utils.storage.retrieveUserFileData.invalidate({
|
|
||||||
userId,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const acceptedMimeTypes = [
|
const acceptedMimeTypes = [
|
||||||
"image/jpeg", // jpg, jpeg
|
"image/jpeg", // jpg, jpeg
|
||||||
"image/png", // png
|
"image/png", // png
|
||||||
|
|
@ -161,37 +141,29 @@ const UploadComponent = ({
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let storageIds: string[] = [];
|
toast(t.allegati.allegato_caricato, {
|
||||||
const userStorageIds: UsersStorageUserStorageId[] = [];
|
icon: "📤",
|
||||||
|
toasterId: "uploading-toast",
|
||||||
|
});
|
||||||
|
let uploaded: Attachment[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const from_admin = isAdmin ?? true;
|
const from_admin = isAdmin ?? true;
|
||||||
setIsUploading(true);
|
setIsUploading(true);
|
||||||
|
|
||||||
const { status, attachments } = await uploadHandler({
|
const { attachments, failed } = await uploadHandler({
|
||||||
files,
|
files,
|
||||||
from_admin,
|
from_admin,
|
||||||
});
|
});
|
||||||
if (!status) {
|
|
||||||
toast.error(t.allegati.errore_caricamento, {
|
for (const file of failed) {
|
||||||
|
toast.error(`Errore caricamento: ${file}`, {
|
||||||
icon: "❌",
|
icon: "❌",
|
||||||
toasterId: "uploading-toast",
|
toasterId: "uploading-toast",
|
||||||
});
|
});
|
||||||
setIsUploading(false);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
storageIds = attachments;
|
|
||||||
|
|
||||||
if (userId) {
|
uploaded = attachments;
|
||||||
for (const storageId of attachments) {
|
|
||||||
const res = await setUserStorage({
|
|
||||||
storageId,
|
|
||||||
userId,
|
|
||||||
from_admin,
|
|
||||||
});
|
|
||||||
if (!res) continue;
|
|
||||||
userStorageIds.push(res.user_storage_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
const randomDelay = Math.random() * 1000 + 500; // Random delay between 500ms and 1500ms
|
const randomDelay = Math.random() * 1000 + 500; // Random delay between 500ms and 1500ms
|
||||||
|
|
@ -206,11 +178,6 @@ const UploadComponent = ({
|
||||||
onProgress(file, progress);
|
onProgress(file, progress);
|
||||||
}, randomDelay / 1); // Adjust interval based on random delay
|
}, randomDelay / 1); // Adjust interval based on random delay
|
||||||
});
|
});
|
||||||
|
|
||||||
toast.success(t.allegati.success_caricamento, {
|
|
||||||
icon: "✅",
|
|
||||||
toasterId: "uploading-toast",
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
|
|
||||||
|
|
@ -221,10 +188,19 @@ const UploadComponent = ({
|
||||||
{ icon: "❌", toasterId: "uploading-toast" },
|
{ icon: "❌", toasterId: "uploading-toast" },
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
|
toast.success(t.allegati.success_caricamento, {
|
||||||
|
icon: "✅",
|
||||||
|
toasterId: "uploading-toast",
|
||||||
|
});
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
setFiles([]);
|
setFiles([]);
|
||||||
cb_onUpload?.({ storageIds, userStorageIds });
|
cb_onUpload?.(uploaded);
|
||||||
await utils.storage.getAll.invalidate();
|
await utils.storage.getAll.invalidate();
|
||||||
|
if (userId) {
|
||||||
|
await utils.storage.retrieveUserFileData.invalidate({
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,6 @@ export const env = createEnv({
|
||||||
SKEBBY_USER: z.string(),
|
SKEBBY_USER: z.string(),
|
||||||
SKEBBY_PASS: z.string(),
|
SKEBBY_PASS: z.string(),
|
||||||
REVALIDATION_SECRET: z.string(),
|
REVALIDATION_SECRET: z.string(),
|
||||||
STORAGE_URL: z.string(),
|
|
||||||
STORAGE_TOKEN: z.string(),
|
|
||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
NEXT_PUBLIC_BASE_URL: z.string(),
|
NEXT_PUBLIC_BASE_URL: z.string(),
|
||||||
|
|
|
||||||
|
|
@ -1875,7 +1875,7 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => {
|
||||||
height={400}
|
height={400}
|
||||||
key={img.img}
|
key={img.img}
|
||||||
src={getStorageUrl({
|
src={getStorageUrl({
|
||||||
storageId: img.img,
|
id: img.img,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: data.media_updated_at?.toISOString(),
|
cacheKey: data.media_updated_at?.toISOString(),
|
||||||
media: "image",
|
media: "image",
|
||||||
|
|
@ -1906,7 +1906,7 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => {
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
className="aspect-square size-52 object-cover"
|
className="aspect-square size-52 object-cover"
|
||||||
coverImage={getStorageUrl({
|
coverImage={getStorageUrl({
|
||||||
storageId: video.thumb,
|
id: video.thumb,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: data.media_updated_at?.toISOString(),
|
cacheKey: data.media_updated_at?.toISOString(),
|
||||||
media: "image",
|
media: "image",
|
||||||
|
|
@ -1914,7 +1914,7 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => {
|
||||||
})}
|
})}
|
||||||
key={`videoplayer-${video.video}`}
|
key={`videoplayer-${video.video}`}
|
||||||
videoSrc={getStorageUrl({
|
videoSrc={getStorageUrl({
|
||||||
storageId: video.video,
|
id: video.video,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: data.media_updated_at?.toISOString(),
|
cacheKey: data.media_updated_at?.toISOString(),
|
||||||
media: "video",
|
media: "video",
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { deleteCookie } from "cookies-next";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
const STALE_KEY = "stale-resource";
|
|
||||||
const RELOAD_STORAGE_KEY = "stale-img-reload";
|
|
||||||
const RELOAD_COOLDOWN_MS = 10 * 1000;
|
|
||||||
|
|
||||||
export const useStaleImageReload = () => {
|
|
||||||
useEffect(() => {
|
|
||||||
// Check for stale resource cookie
|
|
||||||
const checkStaleCookie = () => {
|
|
||||||
const hasStale = document.cookie.includes(`${STALE_KEY}=1`);
|
|
||||||
|
|
||||||
if (hasStale) {
|
|
||||||
//console.log("Stale resource cookie detected");
|
|
||||||
// Clear the cookie
|
|
||||||
deleteCookie(STALE_KEY);
|
|
||||||
attemptReload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check on mount
|
|
||||||
checkStaleCookie();
|
|
||||||
|
|
||||||
// Also check periodically for dynamically loaded images
|
|
||||||
const interval = setInterval(checkStaleCookie, 1000);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
function attemptReload() {
|
|
||||||
const lastAttempt = sessionStorage.getItem(RELOAD_STORAGE_KEY);
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
if (!lastAttempt) {
|
|
||||||
sessionStorage.setItem(RELOAD_STORAGE_KEY, now.toString());
|
|
||||||
window.location.reload();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeSinceLastAttempt = now - parseInt(lastAttempt, 10);
|
|
||||||
|
|
||||||
if (timeSinceLastAttempt > RELOAD_COOLDOWN_MS) {
|
|
||||||
sessionStorage.setItem(RELOAD_STORAGE_KEY, now.toString());
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,19 @@
|
||||||
import { add } from "date-fns";
|
import { add } from "date-fns";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
import { storageId } from "~/schemas/public/Storage";
|
||||||
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
|
import { usersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
||||||
|
|
||||||
|
const attachmentSchema = z.object({
|
||||||
|
storageId: storageId,
|
||||||
|
userStorageId: usersStorageUserStorageId.nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Attachment = z.infer<typeof attachmentSchema>;
|
||||||
type UploadResponse =
|
type UploadResponse =
|
||||||
| {
|
| (Attachment & {
|
||||||
success: true;
|
success: true;
|
||||||
id: string;
|
})
|
||||||
}
|
|
||||||
| {
|
| {
|
||||||
success: false;
|
success: false;
|
||||||
error: string;
|
error: string;
|
||||||
|
|
@ -14,6 +22,7 @@ type UploadResponse =
|
||||||
async function uploadFile(
|
async function uploadFile(
|
||||||
file: File,
|
file: File,
|
||||||
expiresAt: string | undefined,
|
expiresAt: string | undefined,
|
||||||
|
userId?: UsersId,
|
||||||
): Promise<UploadResponse> {
|
): Promise<UploadResponse> {
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
@ -21,27 +30,25 @@ async function uploadFile(
|
||||||
if (expiresAt) {
|
if (expiresAt) {
|
||||||
formData.append("expires_at", expiresAt);
|
formData.append("expires_at", expiresAt);
|
||||||
}
|
}
|
||||||
|
if (userId) {
|
||||||
|
formData.append("userId", userId);
|
||||||
|
}
|
||||||
formData.append("bucket", "storage");
|
formData.append("bucket", "storage");
|
||||||
|
|
||||||
const response = await fetch(`/storage-api/upload`, {
|
const response = await fetch(`/api/storage/upload`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const res = await response.json();
|
const res = await response.json();
|
||||||
const parsed = z
|
const parsed = attachmentSchema.parse(res);
|
||||||
.object({
|
return { success: true, ...parsed };
|
||||||
id: z.string(),
|
|
||||||
})
|
|
||||||
.parse(res);
|
|
||||||
const fileID = parsed.id;
|
|
||||||
return { success: true, id: fileID };
|
|
||||||
}
|
}
|
||||||
|
const res = (await response.json()) as { message: string };
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `Caricamento fallito con status: ${response.status}`,
|
error: `Caricamento fallito con status: ${response.status}, ${res.message}`,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -54,30 +61,33 @@ async function uploadFile(
|
||||||
export const uploadHandler = async ({
|
export const uploadHandler = async ({
|
||||||
files,
|
files,
|
||||||
from_admin,
|
from_admin,
|
||||||
|
userId,
|
||||||
}: {
|
}: {
|
||||||
files: File[];
|
files: File[];
|
||||||
from_admin: boolean;
|
from_admin: boolean;
|
||||||
|
userId?: UsersId;
|
||||||
}) => {
|
}) => {
|
||||||
const attachments = [];
|
const attachments: Attachment[] = [];
|
||||||
const failed: string[] = [];
|
const failed: string[] = [];
|
||||||
const expirationDate = add(new Date(), { days: 60 }).toISOString();
|
const expirationDate = add(new Date(), { days: 60 }).toISOString();
|
||||||
if (files.length === 0) {
|
|
||||||
console.error("No files provided");
|
|
||||||
return { attachments: [], error: "No files provided", status: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const upload = await uploadFile(
|
const upload = await uploadFile(
|
||||||
file,
|
file,
|
||||||
from_admin ? expirationDate : undefined,
|
from_admin ? expirationDate : undefined,
|
||||||
|
userId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (upload.success) {
|
if (upload.success) {
|
||||||
attachments.push(upload.id);
|
attachments.push({
|
||||||
|
storageId: upload.storageId,
|
||||||
|
userStorageId: upload.userStorageId,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error(`Error uploading file ${file.name}: ${upload.error}`);
|
console.error(`Error uploading file ${file.name}: ${upload.error}`);
|
||||||
failed.push(file.name);
|
failed.push(file.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { attachments, failed, status: true };
|
return { attachments, failed };
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,22 @@
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
|
import type { StorageId } from "~/schemas/public/Storage";
|
||||||
|
|
||||||
type Params = {
|
type StorageParams = {
|
||||||
cacheKey: string;
|
cacheKey: string;
|
||||||
mode: "download" | "inline";
|
mode: "download" | "inline";
|
||||||
media: "image" | "video";
|
media: "image" | "video";
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getStorageUrl({
|
export function getStorageUrl({
|
||||||
storageId,
|
id,
|
||||||
params,
|
params,
|
||||||
host = env.NEXT_PUBLIC_BASE_URL,
|
host = env.NEXT_PUBLIC_BASE_URL,
|
||||||
}: {
|
}: {
|
||||||
storageId: string;
|
id: StorageId;
|
||||||
params?: Partial<Params>;
|
params?: Partial<StorageParams>;
|
||||||
host?: string;
|
host?: string;
|
||||||
}) {
|
}) {
|
||||||
const url = new URL(`${host}/storage-api/get/${storageId}`);
|
const url = new URL(`${host}/api/storage/get/${id}`);
|
||||||
if (params) {
|
if (params) {
|
||||||
Object.entries(params).forEach(([key, value]) => {
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
url.searchParams.append(key, value);
|
url.searchParams.append(key, value);
|
||||||
|
|
|
||||||
|
|
@ -327,9 +327,10 @@ const AnnunciList = () => {
|
||||||
maxBudget: maxBudget || undefined,
|
maxBudget: maxBudget || undefined,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
gcTime: 60 * 1000,
|
||||||
enabled: !map && !isUpdatingRicerca,
|
enabled: !map && !isUpdatingRicerca,
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
staleTime: 5000,
|
staleTime: 60 * 1000,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ import { Label } from "~/components/ui/label";
|
||||||
import { VideoPlayer } from "~/components/videoPlayer";
|
import { VideoPlayer } from "~/components/videoPlayer";
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
import { filteredCaratteristiche } from "~/hooks/schedaAnnuncioUtils";
|
import { filteredCaratteristiche } from "~/hooks/schedaAnnuncioUtils";
|
||||||
import { useStaleImageReload } from "~/hooks/staleImage";
|
|
||||||
import { useMediaQuery } from "~/hooks/use-media-query";
|
import { useMediaQuery } from "~/hooks/use-media-query";
|
||||||
import { handleConsegna } from "~/lib/annuncio_details";
|
import { handleConsegna } from "~/lib/annuncio_details";
|
||||||
import { replaceWithBr } from "~/lib/newlineToBr";
|
import { replaceWithBr } from "~/lib/newlineToBr";
|
||||||
|
|
@ -120,8 +119,6 @@ const AnnuncioDettaglio: NextPage<AnnuncioProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const AnnuncioView = ({ data, flag }: AnnuncioProps) => {
|
const AnnuncioView = ({ data, flag }: AnnuncioProps) => {
|
||||||
useStaleImageReload();
|
|
||||||
|
|
||||||
const isDesktop = useMediaQuery("(min-width: 768px)");
|
const isDesktop = useMediaQuery("(min-width: 768px)");
|
||||||
|
|
||||||
if (!data || (data && data.stato === "Sospeso")) {
|
if (!data || (data && data.stato === "Sospeso")) {
|
||||||
|
|
@ -662,7 +659,7 @@ const AnnuncioFooter = () => {
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
className="h-96 max-w-96"
|
className="h-96 max-w-96"
|
||||||
coverImage={getStorageUrl({
|
coverImage={getStorageUrl({
|
||||||
storageId: video.thumb,
|
id: video.thumb,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: media_updated_at || undefined,
|
cacheKey: media_updated_at || undefined,
|
||||||
media: "image",
|
media: "image",
|
||||||
|
|
@ -670,7 +667,7 @@ const AnnuncioFooter = () => {
|
||||||
})}
|
})}
|
||||||
key={`videoplayer-${video.video}`} // Cache busting
|
key={`videoplayer-${video.video}`} // Cache busting
|
||||||
videoSrc={getStorageUrl({
|
videoSrc={getStorageUrl({
|
||||||
storageId: video.video,
|
id: video.video,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: media_updated_at || undefined,
|
cacheKey: media_updated_at || undefined,
|
||||||
media: "video",
|
media: "video",
|
||||||
|
|
|
||||||
144
apps/infoalloggi/src/pages/api/storage/get/[id].ts
Normal file
144
apps/infoalloggi/src/pages/api/storage/get/[id].ts
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { storageId } from "~/schemas/public/Storage";
|
||||||
|
import { TOKEN_CONFIG } from "~/server/auth/configs";
|
||||||
|
import { verifyToken } from "~/server/auth/jwt";
|
||||||
|
import { getFile } from "~/server/services/storage.service";
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse,
|
||||||
|
) {
|
||||||
|
if (req.method !== "GET") {
|
||||||
|
res.setHeader("Allow", ["GET"]);
|
||||||
|
return res.status(405).json({ error: `Method ${req.method} Not Allowed` });
|
||||||
|
}
|
||||||
|
const { id, cacheKey, mode, media } = req.query;
|
||||||
|
if (!id || Array.isArray(id)) {
|
||||||
|
return res.status(400).json({ error: "Invalid or missing file ID" });
|
||||||
|
}
|
||||||
|
const referer = req.headers.referer;
|
||||||
|
const purpose = req.headers.purpose;
|
||||||
|
const isNextImageRequest =
|
||||||
|
purpose === "prefetch" || req.headers["x-vercel-id"];
|
||||||
|
const isImageRequest = media === "image";
|
||||||
|
const isVideoRequest = media === "video";
|
||||||
|
const isDownloadRequest = mode === "download";
|
||||||
|
const cookies = req.cookies;
|
||||||
|
|
||||||
|
const eTag = `W/"${cacheKey}"`;
|
||||||
|
const ifNoneMatch = req.headers["if-none-match"];
|
||||||
|
const ifModifiedSince = req.headers["if-modified-since"];
|
||||||
|
if (isImageRequest || isNextImageRequest || isVideoRequest) {
|
||||||
|
if (ifNoneMatch === eTag || ifModifiedSince === cacheKey) {
|
||||||
|
// 🎉 Cache HIT! Tell the browser to reuse its local copy.
|
||||||
|
// We send NO body payload here, saving massive server bandwidth and CPU.
|
||||||
|
res.status(304).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!isNextImageRequest && !isImageRequest && !isVideoRequest) {
|
||||||
|
const accessToken = cookies[TOKEN_CONFIG.ACCESS_TOKEN_COOKIE_NAME];
|
||||||
|
if (!accessToken) {
|
||||||
|
console.log(
|
||||||
|
"No access token found, referrer:",
|
||||||
|
referer,
|
||||||
|
" url:",
|
||||||
|
req.url,
|
||||||
|
);
|
||||||
|
return res.status(401);
|
||||||
|
}
|
||||||
|
const { expired, session } = await verifyToken(accessToken);
|
||||||
|
if (!session || expired) {
|
||||||
|
console.log("Invalid token, referrer:", referer, " url:", req.url);
|
||||||
|
return res.status(401);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const parsedId = storageId.safeParse(id);
|
||||||
|
if (!parsedId.success) {
|
||||||
|
if (isImageRequest || isNextImageRequest) {
|
||||||
|
return getFallback("fallback-image.png", req, res);
|
||||||
|
}
|
||||||
|
if (isVideoRequest) {
|
||||||
|
return getFallback("fallback-video.png", req, res);
|
||||||
|
}
|
||||||
|
return res.status(404).json({ error: "Invalid input id" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = await getFile(parsedId.data);
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
if (isImageRequest || isNextImageRequest) {
|
||||||
|
return getFallback("fallback-image.png", req, res);
|
||||||
|
}
|
||||||
|
if (isVideoRequest) {
|
||||||
|
return getFallback("fallback-video.png", req, res);
|
||||||
|
}
|
||||||
|
return res.status(404).json({ error: "File not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.setHeader("Content-Type", file.mime_type);
|
||||||
|
res.setHeader("Content-Length", file.file_size_bytes);
|
||||||
|
res.setHeader("Accept-Ranges", "bytes");
|
||||||
|
res.setHeader("ETag", eTag);
|
||||||
|
res.setHeader("Last-Modified", String(cacheKey));
|
||||||
|
res.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
|
||||||
|
|
||||||
|
if (isDownloadRequest) {
|
||||||
|
res.setHeader(
|
||||||
|
"Content-Disposition",
|
||||||
|
`attachment; filename="${file.filename}"`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
res.setHeader(
|
||||||
|
"Content-Disposition",
|
||||||
|
`inline; filename="${file.filename}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return res.status(200).send(file.file_data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Pages Router Media API Error:", error);
|
||||||
|
if (isImageRequest || isNextImageRequest) {
|
||||||
|
return getFallback("fallback-image.png", req, res);
|
||||||
|
}
|
||||||
|
if (isVideoRequest) {
|
||||||
|
return getFallback("fallback-video.png", req, res);
|
||||||
|
}
|
||||||
|
return res.status(500).json({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFallback(
|
||||||
|
img: string,
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse,
|
||||||
|
) {
|
||||||
|
if (req.headers["if-none-match"] === '"fallback-v1"') {
|
||||||
|
res.status(304).end(); // Instantly reuse client-side fallback
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const fallbackPath = path.join(process.cwd(), "public", img);
|
||||||
|
const imageBuffer = fs.readFileSync(fallbackPath);
|
||||||
|
res.setHeader("Content-Type", "image/png");
|
||||||
|
res.setHeader(
|
||||||
|
"Cache-Control",
|
||||||
|
"public, max-age=0, no-cache, must-revalidate",
|
||||||
|
);
|
||||||
|
res.setHeader("Content-Length", imageBuffer.length);
|
||||||
|
res.setHeader("ETag", '"fallback-v1"');
|
||||||
|
return res.status(200).send(imageBuffer);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return res
|
||||||
|
.status(404)
|
||||||
|
.json({ error: "File and fallback placeholder not found" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
responseLimit: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
80
apps/infoalloggi/src/pages/api/storage/upload.ts
Normal file
80
apps/infoalloggi/src/pages/api/storage/upload.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
import fs from "node:fs";
|
||||||
|
import { IncomingForm } from "formidable";
|
||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import z from "zod";
|
||||||
|
import { usersId } from "~/schemas/public/Users";
|
||||||
|
import { uploadFile } from "~/server/services/storage.service";
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
userId: usersId.optional(),
|
||||||
|
expires_at: z.date().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse,
|
||||||
|
) {
|
||||||
|
if (req.method !== "POST") {
|
||||||
|
return res.status(405).json({ message: "Method not allowed" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = new IncomingForm();
|
||||||
|
form.parse(req, async (err, fields, files) => {
|
||||||
|
if (err) {
|
||||||
|
return res.status(500).json({ message: "Form parsing failed" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the file safely from formidable payload
|
||||||
|
const uploadedFile = Array.isArray(files.file) ? files.file[0] : files.file;
|
||||||
|
if (!uploadedFile) {
|
||||||
|
return res.status(400).json({ message: "No file found in payload" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Read raw uncorrupted bytes straight from temporary disk space
|
||||||
|
const fileBuffer = fs.readFileSync(uploadedFile.filepath);
|
||||||
|
|
||||||
|
// Clean up optional fields and map variables correctly
|
||||||
|
const expiresAtRaw = Array.isArray(fields.expires_at)
|
||||||
|
? fields.expires_at[0]
|
||||||
|
: fields.expires_at;
|
||||||
|
|
||||||
|
const userIdRaw = Array.isArray(fields.userId)
|
||||||
|
? fields.userId[0]
|
||||||
|
: fields.userId;
|
||||||
|
|
||||||
|
const meta = schema.safeParse({
|
||||||
|
userId: userIdRaw,
|
||||||
|
expires_at: expiresAtRaw ? new Date(expiresAtRaw) : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (meta.success) {
|
||||||
|
// Call database insertion code
|
||||||
|
const { storageId, userStorageId } = await uploadFile({
|
||||||
|
buffer: fileBuffer,
|
||||||
|
filename: uploadedFile.originalFilename || "unknown_file",
|
||||||
|
mime_type: uploadedFile.mimetype || "application/octet-stream",
|
||||||
|
expires_at: meta.data.expires_at,
|
||||||
|
userId: meta.data.userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete local temporary file to free host disk space
|
||||||
|
fs.unlinkSync(uploadedFile.filepath);
|
||||||
|
|
||||||
|
return res.status(200).json({ storageId, userStorageId });
|
||||||
|
}
|
||||||
|
fs.unlinkSync(uploadedFile.filepath);
|
||||||
|
return res.status(500).json({ message: meta.error.message });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return res.status(500).json({ message: (error as Error).message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
responseLimit: false,
|
||||||
|
bodyParser: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -36,7 +36,7 @@ import type { NextPageWithLayout } from "~/pages/_app";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import {
|
import {
|
||||||
StorageTableProvider,
|
StorageTableProvider,
|
||||||
type StorageTable as StorageTableType,
|
type StorageTableType,
|
||||||
useStorageTable,
|
useStorageTable,
|
||||||
} from "~/providers/StorageTableProvider";
|
} from "~/providers/StorageTableProvider";
|
||||||
|
|
||||||
|
|
@ -286,7 +286,7 @@ const VisibComponent = ({
|
||||||
onSelect={(user) => {
|
onSelect={(user) => {
|
||||||
for (const allegato of storageIds) {
|
for (const allegato of storageIds) {
|
||||||
add({
|
add({
|
||||||
storageId: allegato,
|
storage_id: allegato,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
from_admin: true,
|
from_admin: true,
|
||||||
});
|
});
|
||||||
|
|
@ -311,7 +311,7 @@ const VisibComponent = ({
|
||||||
key={allegato.id}
|
key={allegato.id}
|
||||||
>
|
>
|
||||||
<p className="font-semibold text-lg">
|
<p className="font-semibold text-lg">
|
||||||
File: {allegato.originalName}
|
File: {allegato.filename}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<p>Visibile a:</p>
|
<p>Visibile a:</p>
|
||||||
|
|
|
||||||
|
|
@ -72,19 +72,19 @@ const AllegatoView: NextPageWithLayout<AllegatoViewProps> = ({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Infoalloggi.it | {fileInfos.originalName}</title>
|
<title>Infoalloggi.it | {fileInfos.filename}</title>
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<div className="flex h-full w-full grow flex-col md:flex-row">
|
<div className="flex h-full w-full grow flex-col md:flex-row">
|
||||||
<div className="flex grow flex-col justify-center space-y-2 p-4">
|
<div className="flex grow flex-col justify-center space-y-2 p-4">
|
||||||
<div className="mx-auto flex flex-wrap items-center gap-4">
|
<div className="mx-auto flex flex-wrap items-center gap-4">
|
||||||
<span>{fileInfos.originalName}</span>
|
<span>{fileInfos.filename}</span>
|
||||||
<span>
|
<span>
|
||||||
caricato il {new Date(fileInfos.uploadedAt).toLocaleString("it")}
|
caricato il {new Date(fileInfos.created_at).toLocaleString("it")}
|
||||||
</span>
|
</span>
|
||||||
<Link
|
<Link
|
||||||
href={getStorageUrl({
|
href={getStorageUrl({
|
||||||
storageId: fileInfos.id,
|
id: fileInfos.id,
|
||||||
params: { mode: "download" },
|
params: { mode: "download" },
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|
@ -95,7 +95,7 @@ const AllegatoView: NextPageWithLayout<AllegatoViewProps> = ({
|
||||||
</Link>
|
</Link>
|
||||||
<RinominaDialog
|
<RinominaDialog
|
||||||
allegatoId={allegatoId}
|
allegatoId={allegatoId}
|
||||||
filename={fileInfos.originalName}
|
filename={fileInfos.filename}
|
||||||
/>
|
/>
|
||||||
{session.user.isAdmin && (
|
{session.user.isAdmin && (
|
||||||
<Confirm
|
<Confirm
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ const ContrattoViewer: NextPageWithLayout<ContrattoViewerProps> = ({
|
||||||
<Link
|
<Link
|
||||||
className="underline underline-offset-1 after:content-['_↗']"
|
className="underline underline-offset-1 after:content-['_↗']"
|
||||||
href={getStorageUrl({
|
href={getStorageUrl({
|
||||||
storageId: fileInfos.id,
|
id: fileInfos.id,
|
||||||
params: { mode: "download" },
|
params: { mode: "download" },
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ const ContrattoViewer: NextPageWithLayout<ContrattoViewerProps> = ({
|
||||||
<Link
|
<Link
|
||||||
className="underline underline-offset-1 after:content-['_↗']"
|
className="underline underline-offset-1 after:content-['_↗']"
|
||||||
href={getStorageUrl({
|
href={getStorageUrl({
|
||||||
storageId: fileInfos.id,
|
id: fileInfos.id,
|
||||||
params: { mode: "download" },
|
params: { mode: "download" },
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,11 @@ import type { FileMetadataWithAdmin } from "~/server/services/storage.service";
|
||||||
|
|
||||||
type TData = FileMetadataWithAdmin;
|
type TData = FileMetadataWithAdmin;
|
||||||
|
|
||||||
export type StorageTable = Table<TData>;
|
export type StorageTableType = Table<TData>;
|
||||||
|
|
||||||
const StorageTableContext = createContext<{
|
const StorageTableContext = createContext<{
|
||||||
table: StorageTable | undefined;
|
table: StorageTableType | undefined;
|
||||||
setTable: (table: StorageTable | undefined) => void;
|
setTable: (table: StorageTableType | undefined) => void;
|
||||||
selectedRows: TData[];
|
selectedRows: TData[];
|
||||||
cb_onStateChange: () => void;
|
cb_onStateChange: () => void;
|
||||||
}>({
|
}>({
|
||||||
|
|
@ -26,7 +26,7 @@ const StorageTableContext = createContext<{
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StorageTableProvider = ({ children }: { children: ReactNode }) => {
|
export const StorageTableProvider = ({ children }: { children: ReactNode }) => {
|
||||||
const [table, setTable] = useState<StorageTable | undefined>(undefined);
|
const [table, setTable] = useState<StorageTableType | undefined>(undefined);
|
||||||
|
|
||||||
const [selectedRows, setSelectedRows] = useState<TData[]>([]);
|
const [selectedRows, setSelectedRows] = useState<TData[]>([]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,216 +0,0 @@
|
||||||
import { type NextRequest, NextResponse } from "next/server";
|
|
||||||
import { env } from "~/env";
|
|
||||||
import type { ProxyFn } from "~/proxy";
|
|
||||||
import { TOKEN_CONFIG } from "~/server/auth/configs";
|
|
||||||
import { verifyToken } from "~/server/auth/jwt";
|
|
||||||
|
|
||||||
const STALE_KEY = "stale-resource";
|
|
||||||
export const apisProxy: ProxyFn = async (req: NextRequest) => {
|
|
||||||
const { pathname, searchParams } = req.nextUrl;
|
|
||||||
|
|
||||||
// Handle Storage API requests
|
|
||||||
if (pathname.startsWith("/storage-api/")) {
|
|
||||||
const params = new URLSearchParams(searchParams);
|
|
||||||
const isImageRequest = params.get("media") === "image";
|
|
||||||
const isVideoRequest = params.get("media") === "video";
|
|
||||||
|
|
||||||
const purpose = req.headers.get("purpose");
|
|
||||||
const isNextImageRequest =
|
|
||||||
purpose === "prefetch" || req.headers.get("x-vercel-id");
|
|
||||||
|
|
||||||
if (!isNextImageRequest && !isImageRequest && !isVideoRequest) {
|
|
||||||
const accessToken = req.cookies.get(
|
|
||||||
TOKEN_CONFIG.ACCESS_TOKEN_COOKIE_NAME,
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!accessToken) {
|
|
||||||
console.log(
|
|
||||||
"No access token found, referrer:",
|
|
||||||
req.referrer,
|
|
||||||
" url:",
|
|
||||||
req.url,
|
|
||||||
);
|
|
||||||
return new NextResponse("Unauthorized", { status: 401 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { expired, session } = await verifyToken(accessToken);
|
|
||||||
if (!session || expired) {
|
|
||||||
console.log("Invalid token, referrer:", req.referrer, " url:", req.url);
|
|
||||||
return new NextResponse("Unauthorized", { status: 401 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const slug = pathname.replace("/storage-api/", "");
|
|
||||||
params.set("token", env.STORAGE_TOKEN);
|
|
||||||
const storageUrl = `${env.STORAGE_URL}/${slug}?${params.toString()}`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const headers = new Headers(req.headers);
|
|
||||||
headers.delete("host");
|
|
||||||
headers.delete("cookie");
|
|
||||||
const rangeHeader = req.headers.get("range");
|
|
||||||
|
|
||||||
const response = await fetch(storageUrl, {
|
|
||||||
method: req.method,
|
|
||||||
headers: headers,
|
|
||||||
body:
|
|
||||||
req.method !== "GET" && req.method !== "HEAD" ? req.body : undefined,
|
|
||||||
// @ts-expect-error - duplex needed for request streaming
|
|
||||||
duplex: "half",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const isStaleResource =
|
|
||||||
response.status === 404 || response.status === 410;
|
|
||||||
|
|
||||||
if (isImageRequest || isNextImageRequest) {
|
|
||||||
const url = req.nextUrl.clone();
|
|
||||||
url.pathname = "/fallback-image.png";
|
|
||||||
url.search = "";
|
|
||||||
const response = NextResponse.rewrite(url);
|
|
||||||
if (isStaleResource) {
|
|
||||||
response.cookies.set(STALE_KEY, "1", {
|
|
||||||
maxAge: 5,
|
|
||||||
path: "/",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isVideoRequest) {
|
|
||||||
const url = req.nextUrl.clone();
|
|
||||||
url.pathname = "/fallback-video.png";
|
|
||||||
url.search = "";
|
|
||||||
const response = NextResponse.rewrite(url);
|
|
||||||
if (isStaleResource) {
|
|
||||||
response.cookies.set(STALE_KEY, "1", {
|
|
||||||
maxAge: 5,
|
|
||||||
path: "/",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isStaleResource) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ message: "Stale resource, please reload." },
|
|
||||||
{ status: 410 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return new NextResponse(`Storage API error: ${response.status}`, {
|
|
||||||
status: response.status,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status === 204) {
|
|
||||||
return new NextResponse(null, { status: 200 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.arrayBuffer();
|
|
||||||
|
|
||||||
if (!data || data.byteLength === 0) {
|
|
||||||
console.error(
|
|
||||||
"Received empty response from storage API, referrer:",
|
|
||||||
req.referrer,
|
|
||||||
" url:",
|
|
||||||
req.url,
|
|
||||||
);
|
|
||||||
|
|
||||||
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 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const status =
|
|
||||||
rangeHeader && response.status === 206 ? 206 : response.status;
|
|
||||||
|
|
||||||
const proxyResponse = new NextResponse(data, {
|
|
||||||
status: status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
});
|
|
||||||
|
|
||||||
const contentType = response.headers.get("Content-Type");
|
|
||||||
const contentDisposition = response.headers.get("Content-Disposition");
|
|
||||||
const contentLength = response.headers.get("Content-Length");
|
|
||||||
const acceptRanges = response.headers.get("Accept-Ranges");
|
|
||||||
const contentRange = response.headers.get("Content-Range");
|
|
||||||
|
|
||||||
if (contentType) {
|
|
||||||
proxyResponse.headers.set("Content-Type", contentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contentDisposition) {
|
|
||||||
proxyResponse.headers.set("Content-Disposition", contentDisposition);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contentLength) {
|
|
||||||
proxyResponse.headers.set("Content-Length", contentLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (acceptRanges) {
|
|
||||||
proxyResponse.headers.set("Accept-Ranges", acceptRanges);
|
|
||||||
} else if (isVideoRequest) {
|
|
||||||
proxyResponse.headers.set("Accept-Ranges", "bytes");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contentRange) {
|
|
||||||
proxyResponse.headers.set("Content-Range", contentRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyResponse.headers.set("Access-Control-Allow-Origin", "*");
|
|
||||||
|
|
||||||
if (isVideoRequest) {
|
|
||||||
proxyResponse.headers.set(
|
|
||||||
"Cache-Control",
|
|
||||||
"public, max-age=31536000, immutable",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
proxyResponse.headers.set(
|
|
||||||
"Cache-Control",
|
|
||||||
"public, max-age=31536000, immutable",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyResponse.headers.delete("X-Frame-Options");
|
|
||||||
proxyResponse.headers.set(
|
|
||||||
"Content-Security-Policy",
|
|
||||||
"frame-ancestors 'self'",
|
|
||||||
);
|
|
||||||
|
|
||||||
return proxyResponse;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Storage proxy error:", 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;
|
|
||||||
};
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { type NextRequest, NextResponse } from "next/server";
|
import { type NextRequest, NextResponse } from "next/server";
|
||||||
import { authProxy } from "~/proxies/auth";
|
import { authProxy } from "~/proxies/auth";
|
||||||
import { apisProxy } from "./proxies/api";
|
|
||||||
import { pswChangeProxy } from "./proxies/psw_change";
|
import { pswChangeProxy } from "./proxies/psw_change";
|
||||||
import { TestRedirectProxy } from "./proxies/test_redirect";
|
import { TestRedirectProxy } from "./proxies/test_redirect";
|
||||||
|
|
||||||
|
|
@ -10,7 +9,6 @@ export async function proxy(request: NextRequest) {
|
||||||
return await chainProxies(request, [
|
return await chainProxies(request, [
|
||||||
TestRedirectProxy,
|
TestRedirectProxy,
|
||||||
//updaterProxy,
|
//updaterProxy,
|
||||||
apisProxy,
|
|
||||||
pswChangeProxy,
|
pswChangeProxy,
|
||||||
authProxy,
|
authProxy,
|
||||||
]);
|
]);
|
||||||
|
|
@ -18,7 +16,6 @@ export async function proxy(request: NextRequest) {
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: [
|
matcher: [
|
||||||
"/storage-api/:path*",
|
|
||||||
{
|
{
|
||||||
source: "/",
|
source: "/",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { storageId, type StorageId } from './Storage';
|
||||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
|
@ -7,9 +8,9 @@ export default interface ImagesRefsTable {
|
||||||
|
|
||||||
ordine: ColumnType<number, number, number>;
|
ordine: ColumnType<number, number, number>;
|
||||||
|
|
||||||
img: ColumnType<string, string, string>;
|
img: ColumnType<StorageId, StorageId, StorageId>;
|
||||||
|
|
||||||
thumb: ColumnType<string, string, string>;
|
thumb: ColumnType<StorageId, StorageId, StorageId>;
|
||||||
|
|
||||||
og_url: ColumnType<string, string, string>;
|
og_url: ColumnType<string, string, string>;
|
||||||
}
|
}
|
||||||
|
|
@ -23,23 +24,23 @@ export type ImagesRefsUpdate = Updateable<ImagesRefsTable>;
|
||||||
export const ImagesRefsSchema = z.object({
|
export const ImagesRefsSchema = z.object({
|
||||||
codice: z.string(),
|
codice: z.string(),
|
||||||
ordine: z.number(),
|
ordine: z.number(),
|
||||||
img: z.string(),
|
img: storageId,
|
||||||
thumb: z.string(),
|
thumb: storageId,
|
||||||
og_url: z.string(),
|
og_url: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const NewImagesRefsSchema = z.object({
|
export const NewImagesRefsSchema = z.object({
|
||||||
codice: z.string(),
|
codice: z.string(),
|
||||||
ordine: z.number(),
|
ordine: z.number(),
|
||||||
img: z.string(),
|
img: storageId,
|
||||||
thumb: z.string(),
|
thumb: storageId,
|
||||||
og_url: z.string(),
|
og_url: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ImagesRefsUpdateSchema = z.object({
|
export const ImagesRefsUpdateSchema = z.object({
|
||||||
codice: z.string().optional(),
|
codice: z.string().optional(),
|
||||||
ordine: z.number().optional(),
|
ordine: z.number().optional(),
|
||||||
img: z.string().optional(),
|
img: storageId.optional(),
|
||||||
thumb: z.string().optional(),
|
thumb: storageId.optional(),
|
||||||
og_url: z.string().optional(),
|
og_url: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
@ -26,6 +26,7 @@ import type { default as ProvincieTable } from './Provincie';
|
||||||
import type { default as VideosRefsTable } from './VideosRefs';
|
import type { default as VideosRefsTable } from './VideosRefs';
|
||||||
import type { default as BanlistTable } from './Banlist';
|
import type { default as BanlistTable } from './Banlist';
|
||||||
import type { default as NazioniTable } from './Nazioni';
|
import type { default as NazioniTable } from './Nazioni';
|
||||||
|
import type { default as StorageTable } from './Storage';
|
||||||
import type { default as AppuntiGroupsTable } from './AppuntiGroups';
|
import type { default as AppuntiGroupsTable } from './AppuntiGroups';
|
||||||
import type { default as ImagesRefsTable } from './ImagesRefs';
|
import type { default as ImagesRefsTable } from './ImagesRefs';
|
||||||
import type { default as BannersTable } from './Banners';
|
import type { default as BannersTable } from './Banners';
|
||||||
|
|
@ -89,6 +90,8 @@ export default interface PublicSchema {
|
||||||
|
|
||||||
nazioni: NazioniTable;
|
nazioni: NazioniTable;
|
||||||
|
|
||||||
|
storage: StorageTable;
|
||||||
|
|
||||||
appunti_groups: AppuntiGroupsTable;
|
appunti_groups: AppuntiGroupsTable;
|
||||||
|
|
||||||
images_refs: ImagesRefsTable;
|
images_refs: ImagesRefsTable;
|
||||||
|
|
|
||||||
65
apps/infoalloggi/src/schemas/public/Storage.ts
Normal file
65
apps/infoalloggi/src/schemas/public/Storage.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
/** Identifier type for public.storage */
|
||||||
|
export type StorageId = string & { __brand: 'public.storage' };
|
||||||
|
|
||||||
|
/** Represents the table public.storage */
|
||||||
|
export default interface StorageTable {
|
||||||
|
id: ColumnType<StorageId, StorageId | undefined, StorageId>;
|
||||||
|
|
||||||
|
created_at: ColumnType<Date, Date | string | undefined, Date | string>;
|
||||||
|
|
||||||
|
filename: ColumnType<string, string, string>;
|
||||||
|
|
||||||
|
mime_type: ColumnType<string, string, string>;
|
||||||
|
|
||||||
|
file_size_bytes: ColumnType<number, number, number>;
|
||||||
|
|
||||||
|
file_data: ColumnType<Buffer, Buffer, Buffer>;
|
||||||
|
|
||||||
|
expires_at: ColumnType<Date | null, Date | string | null, Date | string | null>;
|
||||||
|
|
||||||
|
tag: ColumnType<string | null, string | null, string | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Storage = Selectable<StorageTable>;
|
||||||
|
|
||||||
|
export type NewStorage = Insertable<StorageTable>;
|
||||||
|
|
||||||
|
export type StorageUpdate = Updateable<StorageTable>;
|
||||||
|
|
||||||
|
export const storageId = z.uuid().transform(value => value as StorageId);
|
||||||
|
|
||||||
|
export const StorageSchema = z.object({
|
||||||
|
id: storageId,
|
||||||
|
created_at: z.date(),
|
||||||
|
filename: z.string(),
|
||||||
|
mime_type: z.string(),
|
||||||
|
file_size_bytes: z.number(),
|
||||||
|
file_data: z.instanceof(Buffer),
|
||||||
|
expires_at: z.date().nullable(),
|
||||||
|
tag: z.string().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const NewStorageSchema = z.object({
|
||||||
|
id: storageId.optional(),
|
||||||
|
created_at: z.union([z.date(), z.string()]).pipe(z.coerce.date()).optional(),
|
||||||
|
filename: z.string(),
|
||||||
|
mime_type: z.string(),
|
||||||
|
file_size_bytes: z.number(),
|
||||||
|
file_data: z.instanceof(Buffer),
|
||||||
|
expires_at: z.union([z.date(), z.string()]).pipe(z.coerce.date()).optional().nullable(),
|
||||||
|
tag: z.string().optional().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StorageUpdateSchema = z.object({
|
||||||
|
id: storageId.optional(),
|
||||||
|
created_at: z.union([z.date(), z.string()]).pipe(z.coerce.date()).optional(),
|
||||||
|
filename: z.string().optional(),
|
||||||
|
mime_type: z.string().optional(),
|
||||||
|
file_size_bytes: z.number().optional(),
|
||||||
|
file_data: z.instanceof(Buffer).optional(),
|
||||||
|
expires_at: z.union([z.date(), z.string()]).pipe(z.coerce.date()).optional().nullable(),
|
||||||
|
tag: z.string().optional().nullable(),
|
||||||
|
});
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { usersId, type UsersId } from './Users';
|
import { usersId, type UsersId } from './Users';
|
||||||
|
import { storageId, type StorageId } from './Storage';
|
||||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
|
@ -11,9 +12,9 @@ export default interface UsersStorageTable {
|
||||||
|
|
||||||
userId: ColumnType<UsersId, UsersId, UsersId>;
|
userId: ColumnType<UsersId, UsersId, UsersId>;
|
||||||
|
|
||||||
storageId: ColumnType<string, string, string>;
|
|
||||||
|
|
||||||
from_admin: ColumnType<boolean, boolean | undefined, boolean>;
|
from_admin: ColumnType<boolean, boolean | undefined, boolean>;
|
||||||
|
|
||||||
|
storage_id: ColumnType<StorageId | null, StorageId | null, StorageId | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UsersStorage = Selectable<UsersStorageTable>;
|
export type UsersStorage = Selectable<UsersStorageTable>;
|
||||||
|
|
@ -27,20 +28,20 @@ export const usersStorageUserStorageId = z.uuid().transform(value => value as Us
|
||||||
export const UsersStorageSchema = z.object({
|
export const UsersStorageSchema = z.object({
|
||||||
user_storage_id: usersStorageUserStorageId,
|
user_storage_id: usersStorageUserStorageId,
|
||||||
userId: usersId,
|
userId: usersId,
|
||||||
storageId: z.string(),
|
|
||||||
from_admin: z.boolean(),
|
from_admin: z.boolean(),
|
||||||
|
storage_id: storageId.nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const NewUsersStorageSchema = z.object({
|
export const NewUsersStorageSchema = z.object({
|
||||||
user_storage_id: usersStorageUserStorageId.optional(),
|
user_storage_id: usersStorageUserStorageId.optional(),
|
||||||
userId: usersId,
|
userId: usersId,
|
||||||
storageId: z.string(),
|
|
||||||
from_admin: z.boolean().optional(),
|
from_admin: z.boolean().optional(),
|
||||||
|
storage_id: storageId.optional().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UsersStorageUpdateSchema = z.object({
|
export const UsersStorageUpdateSchema = z.object({
|
||||||
user_storage_id: usersStorageUserStorageId.optional(),
|
user_storage_id: usersStorageUserStorageId.optional(),
|
||||||
userId: usersId.optional(),
|
userId: usersId.optional(),
|
||||||
storageId: z.string().optional(),
|
|
||||||
from_admin: z.boolean().optional(),
|
from_admin: z.boolean().optional(),
|
||||||
|
storage_id: storageId.optional().nullable(),
|
||||||
});
|
});
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { storageId, type StorageId } from './Storage';
|
||||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
|
@ -7,9 +8,9 @@ export default interface VideosRefsTable {
|
||||||
|
|
||||||
ordine: ColumnType<number, number, number>;
|
ordine: ColumnType<number, number, number>;
|
||||||
|
|
||||||
video: ColumnType<string, string, string>;
|
video: ColumnType<StorageId, StorageId, StorageId>;
|
||||||
|
|
||||||
thumb: ColumnType<string, string, string>;
|
thumb: ColumnType<StorageId, StorageId, StorageId>;
|
||||||
|
|
||||||
og_url: ColumnType<string, string, string>;
|
og_url: ColumnType<string, string, string>;
|
||||||
}
|
}
|
||||||
|
|
@ -23,23 +24,23 @@ export type VideosRefsUpdate = Updateable<VideosRefsTable>;
|
||||||
export const VideosRefsSchema = z.object({
|
export const VideosRefsSchema = z.object({
|
||||||
codice: z.string(),
|
codice: z.string(),
|
||||||
ordine: z.number(),
|
ordine: z.number(),
|
||||||
video: z.string(),
|
video: storageId,
|
||||||
thumb: z.string(),
|
thumb: storageId,
|
||||||
og_url: z.string(),
|
og_url: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const NewVideosRefsSchema = z.object({
|
export const NewVideosRefsSchema = z.object({
|
||||||
codice: z.string(),
|
codice: z.string(),
|
||||||
ordine: z.number(),
|
ordine: z.number(),
|
||||||
video: z.string(),
|
video: storageId,
|
||||||
thumb: z.string(),
|
thumb: storageId,
|
||||||
og_url: z.string(),
|
og_url: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const VideosRefsUpdateSchema = z.object({
|
export const VideosRefsUpdateSchema = z.object({
|
||||||
codice: z.string().optional(),
|
codice: z.string().optional(),
|
||||||
ordine: z.number().optional(),
|
ordine: z.number().optional(),
|
||||||
video: z.string().optional(),
|
video: storageId.optional(),
|
||||||
thumb: z.string().optional(),
|
thumb: storageId.optional(),
|
||||||
og_url: z.string().optional(),
|
og_url: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
@ -8,8 +8,12 @@ import {
|
||||||
type MessagesMessageid,
|
type MessagesMessageid,
|
||||||
messagesMessageid,
|
messagesMessageid,
|
||||||
} from "~/schemas/public/Messages";
|
} from "~/schemas/public/Messages";
|
||||||
|
import { storageId } from "~/schemas/public/Storage";
|
||||||
import { usersId } from "~/schemas/public/Users";
|
import { usersId } from "~/schemas/public/Users";
|
||||||
import { usersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
import {
|
||||||
|
type UsersStorageUserStorageId,
|
||||||
|
usersStorageUserStorageId,
|
||||||
|
} from "~/schemas/public/UsersStorage";
|
||||||
import {
|
import {
|
||||||
adminProcedure,
|
adminProcedure,
|
||||||
createTRPCRouter,
|
createTRPCRouter,
|
||||||
|
|
@ -260,8 +264,8 @@ export const chatSSERouter = createTRPCRouter({
|
||||||
reply_to_id: messagesMessageid.nullable().default(null),
|
reply_to_id: messagesMessageid.nullable().default(null),
|
||||||
attachments: z
|
attachments: z
|
||||||
.object({
|
.object({
|
||||||
userStorageId: usersStorageUserStorageId,
|
userStorageId: usersStorageUserStorageId.nullable(),
|
||||||
storageId: z.string(),
|
storageId: storageId,
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.default([]),
|
.default([]),
|
||||||
|
|
@ -285,15 +289,19 @@ export const chatSSERouter = createTRPCRouter({
|
||||||
);
|
);
|
||||||
let hasAttachments = false;
|
let hasAttachments = false;
|
||||||
if (input.attachments.length > 0) {
|
if (input.attachments.length > 0) {
|
||||||
|
const values = input.attachments
|
||||||
|
.filter((a) => a.userStorageId !== null)
|
||||||
|
.map(({ userStorageId }) => ({
|
||||||
|
messageId: newMessage.messageid,
|
||||||
|
userStorageId,
|
||||||
|
})) as {
|
||||||
|
messageId: MessagesMessageid;
|
||||||
|
userStorageId: UsersStorageUserStorageId;
|
||||||
|
}[];
|
||||||
const newAttachments = await db
|
const newAttachments = await db
|
||||||
.$pickTables<"messages_attachments">()
|
.$pickTables<"messages_attachments">()
|
||||||
.insertInto("messages_attachments")
|
.insertInto("messages_attachments")
|
||||||
.values(
|
.values(values)
|
||||||
input.attachments.map(({ userStorageId }) => ({
|
|
||||||
messageId: newMessage.messageid,
|
|
||||||
userStorageId,
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.returning("userStorageId")
|
.returning("userStorageId")
|
||||||
.execute();
|
.execute();
|
||||||
hasAttachments = newAttachments.length > 0;
|
hasAttachments = newAttachments.length > 0;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
import { storageId } from "~/schemas/public/Storage";
|
||||||
import { usersId } from "~/schemas/public/Users";
|
import { usersId } from "~/schemas/public/Users";
|
||||||
import { NewUsersStorageSchema } from "~/schemas/public/UsersStorage";
|
import { NewUsersStorageSchema } from "~/schemas/public/UsersStorage";
|
||||||
import {
|
import {
|
||||||
adminProcedure,
|
adminProcedure,
|
||||||
createTRPCRouter,
|
createTRPCRouter,
|
||||||
protectedProcedure,
|
protectedProcedure,
|
||||||
|
publicProcedure,
|
||||||
} from "~/server/api/trpc";
|
} from "~/server/api/trpc";
|
||||||
import {
|
import {
|
||||||
addUserStorage,
|
addUserStorage,
|
||||||
|
|
@ -19,36 +20,32 @@ import { db } from "~/server/db";
|
||||||
import {
|
import {
|
||||||
deleteFile,
|
deleteFile,
|
||||||
editFile,
|
editFile,
|
||||||
|
getFile,
|
||||||
getFileInfo,
|
getFileInfo,
|
||||||
} from "~/server/services/storage.service";
|
} from "~/server/services/storage.service";
|
||||||
|
|
||||||
export const storageRouter = createTRPCRouter({
|
export const storageRouter = createTRPCRouter({
|
||||||
getFileInfos: protectedProcedure
|
getFileInfos: protectedProcedure
|
||||||
.input(z.object({ storageId: z.string() }))
|
.input(z.object({ storageId: storageId }))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
return (await getFileInfo(input.storageId)) || undefined;
|
return (await getFileInfo(input.storageId)) || undefined;
|
||||||
}),
|
}),
|
||||||
deleteFile: protectedProcedure
|
deleteFile: protectedProcedure
|
||||||
.input(z.object({ storageId: z.string() }))
|
.input(z.object({ storageId: storageId }))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
const deletion = await deleteFile(input.storageId);
|
await deleteFile(input.storageId);
|
||||||
if (!deletion.success) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message: deletion.error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await purgeFileFromUserStorages({
|
await purgeFileFromUserStorages({
|
||||||
...input,
|
...input,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
deleteUserStorageEntry: protectedProcedure
|
deleteUserStorageEntry: protectedProcedure
|
||||||
.input(z.object({ storageId: z.string(), userId: usersId }))
|
.input(z.object({ storageId: storageId, userId: usersId }))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await deleteUserStorageEntry(input);
|
return await deleteUserStorageEntry(input);
|
||||||
}),
|
}),
|
||||||
deleteFilesFromUserStorage: adminProcedure
|
deleteFilesFromUserStorage: adminProcedure
|
||||||
.input(z.object({ storageIds: z.string().array() }))
|
.input(z.object({ storageIds: storageId.array() }))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
for (const storageId of input.storageIds) {
|
for (const storageId of input.storageIds) {
|
||||||
await purgeFileFromUserStorages({ storageId });
|
await purgeFileFromUserStorages({ storageId });
|
||||||
|
|
@ -61,7 +58,7 @@ export const storageRouter = createTRPCRouter({
|
||||||
return await retrieveUserFiles(input);
|
return await retrieveUserFiles(input);
|
||||||
}),
|
}),
|
||||||
getStorageAccessDetails: adminProcedure
|
getStorageAccessDetails: adminProcedure
|
||||||
.input(z.object({ storageIds: z.string().array() }))
|
.input(z.object({ storageIds: storageId.array() }))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
return await getMultipleWithRelations({
|
return await getMultipleWithRelations({
|
||||||
db,
|
db,
|
||||||
|
|
@ -79,8 +76,11 @@ export const storageRouter = createTRPCRouter({
|
||||||
}),
|
}),
|
||||||
|
|
||||||
renameFile: adminProcedure
|
renameFile: adminProcedure
|
||||||
.input(z.object({ fileId: z.string(), name: z.string() }))
|
.input(z.object({ fileId: storageId, name: z.string() }))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await editFile(input.fileId, input.name);
|
return await editFile(input.fileId, { filename: input.name });
|
||||||
}),
|
}),
|
||||||
|
getFile: publicProcedure.input(storageId).query(async ({ input }) => {
|
||||||
|
return await getFile(input);
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ export const getAnnuncioData = async ({
|
||||||
const ogImage =
|
const ogImage =
|
||||||
annuncio.images && annuncio.images.length > 0 && annuncio.images[0]
|
annuncio.images && annuncio.images.length > 0 && annuncio.images[0]
|
||||||
? getStorageUrl({
|
? getStorageUrl({
|
||||||
storageId: annuncio.images[0].img,
|
id: annuncio.images[0].img,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: annuncio.media_updated_at?.toISOString(),
|
cacheKey: annuncio.media_updated_at?.toISOString(),
|
||||||
media: "image",
|
media: "image",
|
||||||
|
|
@ -411,7 +411,9 @@ export const getAnnunciRicerca = async ({
|
||||||
|
|
||||||
query = query.orderBy("id", "asc");
|
query = query.orderBy("id", "asc");
|
||||||
|
|
||||||
return await query.execute();
|
const a = await query.execute();
|
||||||
|
console.log(a[0]);
|
||||||
|
return a;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { z } from "zod/v4";
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
import { withParsedDates } from "~/lib/dateParserJsonKysely";
|
import { withParsedDates } from "~/lib/dateParserJsonKysely";
|
||||||
import type { Annunci, AnnunciId } from "~/schemas/public/Annunci";
|
import type { Annunci, AnnunciId } from "~/schemas/public/Annunci";
|
||||||
|
import type { ImagesRefs } from "~/schemas/public/ImagesRefs";
|
||||||
import type { Ordini, OrdiniOrdineId } from "~/schemas/public/Ordini";
|
import type { Ordini, OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||||
import type { Prezziario } from "~/schemas/public/Prezziario";
|
import type { Prezziario } from "~/schemas/public/Prezziario";
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -13,11 +14,13 @@ import type {
|
||||||
ServizioUpdate,
|
ServizioUpdate,
|
||||||
} from "~/schemas/public/Servizio";
|
} from "~/schemas/public/Servizio";
|
||||||
import type { ServizioAnnunci } from "~/schemas/public/ServizioAnnunci";
|
import type { ServizioAnnunci } from "~/schemas/public/ServizioAnnunci";
|
||||||
|
import type { StorageId } from "~/schemas/public/Storage";
|
||||||
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
||||||
import type TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
import type TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||||
import type { Users, UsersId } from "~/schemas/public/Users";
|
import type { Users, UsersId } from "~/schemas/public/Users";
|
||||||
import type { UsersAnagraficaUpdate } from "~/schemas/public/UsersAnagrafica";
|
import type { UsersAnagraficaUpdate } from "~/schemas/public/UsersAnagrafica";
|
||||||
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
||||||
|
import type { VideosRefs } from "~/schemas/public/VideosRefs";
|
||||||
import {
|
import {
|
||||||
addEventToQueue,
|
addEventToQueue,
|
||||||
canSendNotification,
|
canSendNotification,
|
||||||
|
|
@ -325,7 +328,7 @@ export const AdminAttivaServizio_Ufficio = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
type DocRef = {
|
type DocRef = {
|
||||||
storageId: string;
|
storage_id: StorageId | null;
|
||||||
ref: UsersStorageUserStorageId | null;
|
ref: UsersStorageUserStorageId | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -355,14 +358,8 @@ export type ServizioAnnuncioData = ServizioAnnunci &
|
||||||
| "persone"
|
| "persone"
|
||||||
| "permanenza"
|
| "permanenza"
|
||||||
> & {
|
> & {
|
||||||
images: {
|
images: Pick<ImagesRefs, "img" | "thumb">[];
|
||||||
img: string;
|
videos: Pick<VideosRefs, "video" | "thumb">[];
|
||||||
thumb: string;
|
|
||||||
}[];
|
|
||||||
videos: {
|
|
||||||
thumb: string;
|
|
||||||
video: string;
|
|
||||||
}[];
|
|
||||||
};
|
};
|
||||||
export type ServizioData = Servizio & {
|
export type ServizioData = Servizio & {
|
||||||
doc_personale_fronte: DocRef | null;
|
doc_personale_fronte: DocRef | null;
|
||||||
|
|
@ -400,7 +397,7 @@ export const getAllServizioAnnunci = async (
|
||||||
eb
|
eb
|
||||||
.selectFrom("users_storage")
|
.selectFrom("users_storage")
|
||||||
.select([
|
.select([
|
||||||
"users_storage.storageId as storageId",
|
"users_storage.storage_id",
|
||||||
"users_anagrafica.doc_personale_fronte_ref as ref",
|
"users_anagrafica.doc_personale_fronte_ref as ref",
|
||||||
])
|
])
|
||||||
.whereRef(
|
.whereRef(
|
||||||
|
|
@ -413,7 +410,7 @@ export const getAllServizioAnnunci = async (
|
||||||
eb
|
eb
|
||||||
.selectFrom("users_storage")
|
.selectFrom("users_storage")
|
||||||
.select([
|
.select([
|
||||||
"users_storage.storageId as storageId",
|
"users_storage.storage_id",
|
||||||
"users_anagrafica.doc_personale_retro_ref as ref",
|
"users_anagrafica.doc_personale_retro_ref as ref",
|
||||||
])
|
])
|
||||||
.whereRef(
|
.whereRef(
|
||||||
|
|
@ -426,7 +423,7 @@ export const getAllServizioAnnunci = async (
|
||||||
eb
|
eb
|
||||||
.selectFrom("users_storage")
|
.selectFrom("users_storage")
|
||||||
.select([
|
.select([
|
||||||
"users_storage.storageId as storageId",
|
"users_storage.storage_id",
|
||||||
"servizio.doc_motivazione_ref as ref",
|
"servizio.doc_motivazione_ref as ref",
|
||||||
])
|
])
|
||||||
.whereRef(
|
.whereRef(
|
||||||
|
|
@ -546,7 +543,7 @@ export const getServizioDataById = async (
|
||||||
eb
|
eb
|
||||||
.selectFrom("users_storage")
|
.selectFrom("users_storage")
|
||||||
.select([
|
.select([
|
||||||
"users_storage.storageId as storageId",
|
"users_storage.storage_id",
|
||||||
"users_anagrafica.doc_personale_fronte_ref as ref",
|
"users_anagrafica.doc_personale_fronte_ref as ref",
|
||||||
])
|
])
|
||||||
.whereRef(
|
.whereRef(
|
||||||
|
|
@ -559,7 +556,7 @@ export const getServizioDataById = async (
|
||||||
eb
|
eb
|
||||||
.selectFrom("users_storage")
|
.selectFrom("users_storage")
|
||||||
.select([
|
.select([
|
||||||
"users_storage.storageId as storageId",
|
"users_storage.storage_id",
|
||||||
"users_anagrafica.doc_personale_retro_ref as ref",
|
"users_anagrafica.doc_personale_retro_ref as ref",
|
||||||
])
|
])
|
||||||
.whereRef(
|
.whereRef(
|
||||||
|
|
@ -572,7 +569,7 @@ export const getServizioDataById = async (
|
||||||
eb
|
eb
|
||||||
.selectFrom("users_storage")
|
.selectFrom("users_storage")
|
||||||
.select([
|
.select([
|
||||||
"users_storage.storageId as storageId",
|
"users_storage.storage_id",
|
||||||
"servizio.doc_motivazione_ref as ref",
|
"servizio.doc_motivazione_ref as ref",
|
||||||
])
|
])
|
||||||
.whereRef(
|
.whereRef(
|
||||||
|
|
|
||||||
|
|
@ -1,84 +1,69 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import { sql } from "kysely";
|
||||||
|
import { jsonArrayFrom } from "kysely/helpers/postgres";
|
||||||
import type { MessagesMessageid } from "~/schemas/public/Messages";
|
import type { MessagesMessageid } from "~/schemas/public/Messages";
|
||||||
|
import type { StorageId } from "~/schemas/public/Storage";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import type { NewUsersStorage } from "~/schemas/public/UsersStorage";
|
import type { NewUsersStorage } from "~/schemas/public/UsersStorage";
|
||||||
import { db, type Querier } from "~/server/db";
|
import { db, type Querier } from "~/server/db";
|
||||||
import { fetchFiles, retriveFilesSafely } from "../services/storage.service";
|
|
||||||
|
|
||||||
export const getMultipleWithRelations = async ({
|
export const getMultipleWithRelations = async ({
|
||||||
db,
|
db,
|
||||||
ids,
|
ids,
|
||||||
}: {
|
}: {
|
||||||
db: Querier;
|
db: Querier;
|
||||||
ids: string[];
|
ids: StorageId[];
|
||||||
}) => {
|
}) => {
|
||||||
const files = await fetchFiles();
|
try {
|
||||||
const userStorage = await db
|
return await db
|
||||||
.$pickTables<"users_storage" | "users">()
|
.$pickTables<"users_storage" | "users" | "storage">()
|
||||||
.selectFrom("users_storage")
|
.selectFrom("storage")
|
||||||
.selectAll("users_storage")
|
.select([
|
||||||
.innerJoin("users", "users.id", "users_storage.userId")
|
"storage.id",
|
||||||
.select("users.username")
|
"storage.created_at",
|
||||||
.where(
|
"storage.expires_at",
|
||||||
"storageId",
|
"storage.file_size_bytes",
|
||||||
"in",
|
"storage.filename",
|
||||||
files.map((f) => f.id),
|
"storage.mime_type",
|
||||||
)
|
"storage.tag",
|
||||||
|
])
|
||||||
.execute();
|
.select((eb) => [
|
||||||
|
jsonArrayFrom(
|
||||||
return ids
|
eb
|
||||||
.map((id) => {
|
.selectFrom("users_storage")
|
||||||
const file = files.find((f) => f.id === id);
|
.innerJoin("users", "users.id", "users_storage.userId")
|
||||||
if (!file) return null;
|
.select(["users.username", "users.id"])
|
||||||
const users = userStorage
|
.whereRef("users_storage.storage_id", "=", "storage.id"),
|
||||||
.filter((us) => us.storageId === id)
|
).as("users"),
|
||||||
.map((us) => ({
|
])
|
||||||
id: us.userId,
|
.where("storage.id", "in", ids)
|
||||||
username: us.username,
|
.execute();
|
||||||
}));
|
} catch (e) {
|
||||||
return {
|
throw new TRPCError({
|
||||||
...file,
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
users,
|
message: `Error retriving users-storage relations: ${(e as Error).message}`,
|
||||||
};
|
});
|
||||||
})
|
}
|
||||||
.filter((f) => f !== null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const retrieveUserFiles = async ({ userId }: { userId: UsersId }) => {
|
export const retrieveUserFiles = async ({ userId }: { userId: UsersId }) => {
|
||||||
try {
|
try {
|
||||||
//all files linked to the user
|
return await db
|
||||||
const files = await db
|
.$pickTables<"users_storage" | "storage">()
|
||||||
.$pickTables<"users_storage">()
|
|
||||||
.selectFrom("users_storage")
|
.selectFrom("users_storage")
|
||||||
.select(["storageId", "from_admin"])
|
.select("users_storage.from_admin")
|
||||||
.where("userId", "=", userId)
|
.innerJoin("storage", "storage.id", "users_storage.storage_id")
|
||||||
|
.select([
|
||||||
|
"storage.id",
|
||||||
|
"storage.created_at",
|
||||||
|
"storage.expires_at",
|
||||||
|
"storage.file_size_bytes",
|
||||||
|
"storage.filename",
|
||||||
|
"storage.mime_type",
|
||||||
|
"storage.tag",
|
||||||
|
])
|
||||||
|
.where("users_storage.userId", "=", userId)
|
||||||
.execute();
|
.execute();
|
||||||
const { available, unavailable } = await retriveFilesSafely(
|
|
||||||
files.map((f) => f.storageId),
|
|
||||||
);
|
|
||||||
|
|
||||||
//remove entries in users_storage for files that no longer exist in storage
|
|
||||||
await db
|
|
||||||
.$pickTables<"users_storage">()
|
|
||||||
.deleteFrom("users_storage")
|
|
||||||
.where("userId", "=", userId)
|
|
||||||
.where("storageId", "in", unavailable)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
//merge file metadata with users_storage info
|
|
||||||
const userFiles = files
|
|
||||||
.map((uf) => {
|
|
||||||
const file = available.find((f) => f.id === uf.storageId);
|
|
||||||
if (!file) return null;
|
|
||||||
return {
|
|
||||||
...file,
|
|
||||||
from_admin: uf.from_admin,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter((f) => f !== null);
|
|
||||||
|
|
||||||
return userFiles;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
|
@ -93,31 +78,27 @@ export const retrieveChatMessageFiles = async ({
|
||||||
messageId: MessagesMessageid;
|
messageId: MessagesMessageid;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
const messageFiles = await db
|
return await db
|
||||||
.$pickTables<"messages_attachments" | "users_storage">()
|
.$pickTables<"messages_attachments" | "storage" | "users_storage">()
|
||||||
.selectFrom("messages_attachments")
|
.selectFrom("messages_attachments")
|
||||||
.innerJoin(
|
.innerJoin(
|
||||||
"users_storage",
|
"users_storage",
|
||||||
"users_storage.user_storage_id",
|
"users_storage.user_storage_id",
|
||||||
"messages_attachments.userStorageId",
|
"messages_attachments.userStorageId",
|
||||||
)
|
)
|
||||||
.select("users_storage.storageId")
|
.innerJoin("storage", "storage.id", "users_storage.storage_id")
|
||||||
|
.select([
|
||||||
|
"storage.id",
|
||||||
|
"storage.created_at",
|
||||||
|
"storage.expires_at",
|
||||||
|
"storage.file_size_bytes",
|
||||||
|
"storage.filename",
|
||||||
|
"storage.mime_type",
|
||||||
|
"storage.tag",
|
||||||
|
])
|
||||||
.orderBy("messages_attachments.messageId", "asc")
|
.orderBy("messages_attachments.messageId", "asc")
|
||||||
.where("messages_attachments.messageId", "=", messageId)
|
.where("messages_attachments.messageId", "=", messageId)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const { available, unavailable } = await retriveFilesSafely(
|
|
||||||
messageFiles.map((f) => f.storageId),
|
|
||||||
);
|
|
||||||
|
|
||||||
//remove entries in users_storage for files that no longer exist in storage
|
|
||||||
await db
|
|
||||||
.$pickTables<"users_storage">()
|
|
||||||
.deleteFrom("users_storage")
|
|
||||||
.where("storageId", "in", unavailable)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
return available;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
|
@ -128,22 +109,22 @@ export const retrieveChatMessageFiles = async ({
|
||||||
|
|
||||||
export const getAllWithFromAdmin = async () => {
|
export const getAllWithFromAdmin = async () => {
|
||||||
try {
|
try {
|
||||||
const files = await db
|
return await db
|
||||||
.$pickTables<"users_storage">()
|
.$pickTables<"users_storage" | "storage">()
|
||||||
.selectFrom("users_storage")
|
.selectFrom("storage")
|
||||||
.select(["storageId", "from_admin"])
|
.leftJoin("users_storage", "users_storage.storage_id", "storage.id")
|
||||||
|
.select([
|
||||||
|
"storage.id",
|
||||||
|
"storage.created_at",
|
||||||
|
"storage.expires_at",
|
||||||
|
"storage.file_size_bytes",
|
||||||
|
"storage.filename",
|
||||||
|
"storage.mime_type",
|
||||||
|
"storage.tag",
|
||||||
|
sql<boolean>`coalesce(users_storage.from_admin, true)`.as("from_admin"),
|
||||||
|
])
|
||||||
|
.where("tag", "is", null)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const allFiles = await fetchFiles();
|
|
||||||
const allFilesWithFromAdmin = allFiles.map((file) => {
|
|
||||||
const userStorageEntry = files.find((f) => f.storageId === file.id);
|
|
||||||
return {
|
|
||||||
...file,
|
|
||||||
from_admin: userStorageEntry ? userStorageEntry.from_admin : true,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return allFilesWithFromAdmin;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
|
@ -158,7 +139,7 @@ export const addUserStorage = async (data: NewUsersStorage) => {
|
||||||
.$pickTables<"users_storage">()
|
.$pickTables<"users_storage">()
|
||||||
.insertInto("users_storage")
|
.insertInto("users_storage")
|
||||||
.values(data)
|
.values(data)
|
||||||
.onConflict((oc) => oc.columns(["userId", "storageId"]).doNothing())
|
.onConflict((oc) => oc.columns(["userId", "storage_id"]).doNothing())
|
||||||
.returning("user_storage_id")
|
.returning("user_storage_id")
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -174,14 +155,14 @@ export const deleteUserStorageEntry = async ({
|
||||||
storageId,
|
storageId,
|
||||||
}: {
|
}: {
|
||||||
userId: UsersId;
|
userId: UsersId;
|
||||||
storageId: string;
|
storageId: StorageId;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
await db
|
await db
|
||||||
.$pickTables<"users_storage">()
|
.$pickTables<"users_storage">()
|
||||||
.deleteFrom("users_storage")
|
.deleteFrom("users_storage")
|
||||||
.where("userId", "=", userId)
|
.where("userId", "=", userId)
|
||||||
.where("storageId", "=", storageId)
|
.where("storage_id", "=", storageId)
|
||||||
.execute();
|
.execute();
|
||||||
return "ok";
|
return "ok";
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -195,13 +176,13 @@ export const deleteUserStorageEntry = async ({
|
||||||
export const purgeFileFromUserStorages = async ({
|
export const purgeFileFromUserStorages = async ({
|
||||||
storageId,
|
storageId,
|
||||||
}: {
|
}: {
|
||||||
storageId: string;
|
storageId: StorageId;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
await db
|
await db
|
||||||
.$pickTables<"users_storage">()
|
.$pickTables<"users_storage">()
|
||||||
.deleteFrom("users_storage")
|
.deleteFrom("users_storage")
|
||||||
.where("storageId", "=", storageId)
|
.where("storage_id", "=", storageId)
|
||||||
.execute();
|
.execute();
|
||||||
return "ok";
|
return "ok";
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ const pool = new Pool({
|
||||||
max: 10,
|
max: 10,
|
||||||
password: env.POSTGRES_PASSWORD,
|
password: env.POSTGRES_PASSWORD,
|
||||||
port: Number.parseInt(env.PGPORT),
|
port: Number.parseInt(env.PGPORT),
|
||||||
user: env.POSTGRES_USER
|
user: env.POSTGRES_USER,
|
||||||
});
|
});
|
||||||
|
|
||||||
pool.on("error", (err) => {
|
pool.on("error", (err) => {
|
||||||
|
|
@ -83,4 +83,4 @@ const _DbLogger = (event: LogEvent) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pgPool = pool
|
export const pgPool = pool;
|
||||||
|
|
|
||||||
|
|
@ -1,209 +1,142 @@
|
||||||
import z from "zod";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { env } from "~/env";
|
import type {
|
||||||
|
Storage,
|
||||||
const FileMetadataSchema = z.object({
|
StorageId,
|
||||||
id: z.string(),
|
StorageUpdate,
|
||||||
originalName: z.string(),
|
} from "~/schemas/public/Storage";
|
||||||
size: z.number(),
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
mimeType: z.string(),
|
import { db } from "~/server/db";
|
||||||
blockCount: z.number(),
|
export type FileMetadata = Omit<Storage, "file_data">;
|
||||||
uploadedAt: z.string(),
|
|
||||||
expiresAt: z.string().nullable(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type FileMetadata = z.infer<typeof FileMetadataSchema>;
|
|
||||||
|
|
||||||
export type FileMetadataWithAdmin = FileMetadata & {
|
export type FileMetadataWithAdmin = FileMetadata & {
|
||||||
from_admin: boolean;
|
from_admin: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper to fetch the list of all files
|
export const getFileInfo = async (fileId: StorageId) => {
|
||||||
export async function fetchFiles(): Promise<FileMetadata[]> {
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const file = await db
|
||||||
`${env.STORAGE_URL}/bucket/storage?token=${env.STORAGE_TOKEN}`,
|
.$pickTables<"storage">()
|
||||||
);
|
.selectFrom("storage")
|
||||||
if (!response.ok) {
|
.select([
|
||||||
console.error("Failed to fetch file list:", response.statusText);
|
"id",
|
||||||
return [];
|
"created_at",
|
||||||
}
|
"expires_at",
|
||||||
const parse = FileMetadataSchema.array().safeParse(await response.json());
|
"file_size_bytes",
|
||||||
|
"filename",
|
||||||
if (parse.success) {
|
"mime_type",
|
||||||
return parse.data;
|
"tag",
|
||||||
}
|
])
|
||||||
console.error("Failed to parse file metadata:", parse.error);
|
.where("id", "=", fileId)
|
||||||
return [];
|
.executeTakeFirst();
|
||||||
} catch (error) {
|
return file;
|
||||||
console.error("Error fetching file list:", error);
|
} catch (e) {
|
||||||
return [];
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: `Failed to get file info: ${(e as Error).message}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function getFileInfo(id: string): Promise<FileMetadata | null> {
|
export const uploadFile = async ({
|
||||||
|
buffer,
|
||||||
|
filename,
|
||||||
|
mime_type,
|
||||||
|
expires_at,
|
||||||
|
userId,
|
||||||
|
tag,
|
||||||
|
}: {
|
||||||
|
buffer: Buffer;
|
||||||
|
filename: string;
|
||||||
|
mime_type: string;
|
||||||
|
expires_at?: Date;
|
||||||
|
userId?: UsersId;
|
||||||
|
tag?: string;
|
||||||
|
}) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const storage = await db
|
||||||
`${env.STORAGE_URL}/info/${id}?token=${env.STORAGE_TOKEN}`,
|
.$pickTables<"storage">()
|
||||||
);
|
.insertInto("storage")
|
||||||
if (!response.ok) {
|
.values({
|
||||||
console.error("Failed to fetch file list:", response.statusText);
|
filename,
|
||||||
return null;
|
mime_type,
|
||||||
}
|
expires_at,
|
||||||
const parse = FileMetadataSchema.safeParse(await response.json());
|
file_data: buffer,
|
||||||
|
file_size_bytes: buffer.length,
|
||||||
if (parse.success) {
|
tag,
|
||||||
return parse.data;
|
})
|
||||||
}
|
.returning("id")
|
||||||
console.error("Failed to parse file metadata:", parse.error);
|
.executeTakeFirstOrThrow();
|
||||||
return null;
|
if (userId) {
|
||||||
} catch (error) {
|
const userStorageId = await db
|
||||||
console.error("Error fetching file list:", error);
|
.$pickTables<"users_storage">()
|
||||||
return null;
|
.insertInto("users_storage")
|
||||||
}
|
.values({
|
||||||
}
|
userId,
|
||||||
|
storage_id: storage.id,
|
||||||
type EditResponse =
|
|
||||||
| {
|
|
||||||
success: true;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
success: false;
|
|
||||||
error: string;
|
|
||||||
};
|
|
||||||
export async function editFile(
|
|
||||||
id: string,
|
|
||||||
filename?: string,
|
|
||||||
expiresAt?: string,
|
|
||||||
): Promise<EditResponse> {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("id", id);
|
|
||||||
if (expiresAt) {
|
|
||||||
formData.append("expires_at", expiresAt);
|
|
||||||
}
|
|
||||||
if (filename) {
|
|
||||||
formData.append("filename", filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`${env.STORAGE_URL}/edit?token=${env.STORAGE_TOKEN}`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
body: formData,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const res = await response.json();
|
|
||||||
const parsed = z
|
|
||||||
.object({
|
|
||||||
id: z.string(),
|
|
||||||
})
|
})
|
||||||
.parse(res);
|
.returning("user_storage_id")
|
||||||
const fileID = parsed.id;
|
.executeTakeFirstOrThrow();
|
||||||
|
return {
|
||||||
return { success: true, id: fileID };
|
storageId: storage.id,
|
||||||
|
userStorageId: userStorageId.user_storage_id,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
storageId: storage.id,
|
||||||
error: `Modifica fallita con status: ${response.status}`,
|
userStorageId: null,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return {
|
throw new TRPCError({
|
||||||
success: false,
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
error: `Errore Modifica file: ${(e as Error).message}`,
|
message: `Failed to upload file: ${(e as Error).message}`,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
type DeleteResponse =
|
export const getFile = async (fileId: StorageId) => {
|
||||||
| {
|
|
||||||
success: true;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
success: false;
|
|
||||||
error: string;
|
|
||||||
};
|
|
||||||
// Helper to handle file deletion
|
|
||||||
export async function deleteFile(fileID: string): Promise<DeleteResponse> {
|
|
||||||
try {
|
try {
|
||||||
// The Go server accepts POST with form data for delete
|
return await db
|
||||||
const response = await fetch(
|
.$pickTables<"storage">()
|
||||||
`${env.STORAGE_URL}/delete/${fileID}?token=${env.STORAGE_TOKEN}`,
|
.selectFrom("storage")
|
||||||
{
|
.selectAll()
|
||||||
method: "DELETE",
|
.where("id", "=", fileId)
|
||||||
},
|
.executeTakeFirst();
|
||||||
);
|
|
||||||
|
|
||||||
// Treat redirect as success
|
|
||||||
if (response.ok || (response.status >= 300 && response.status < 400)) {
|
|
||||||
return { success: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: `Eliminazione fallita con status: ${response.status}`,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error during file deletion:", error);
|
|
||||||
return { success: false, error: (error as Error).message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// export const handleDownload = async (file: FileMetadata) => {
|
|
||||||
// try {
|
|
||||||
// const response = await fetch(
|
|
||||||
// `${env.STORAGE_URL}/get/${file.id}?token=${env.STORAGE_TOKEN}&mode=download`,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// if (!response.ok) {
|
|
||||||
// throw new Error("File fetch failed");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const blob = await response.blob();
|
|
||||||
// const url = window.URL.createObjectURL(blob);
|
|
||||||
// const a = document.createElement("a");
|
|
||||||
// a.href = url;
|
|
||||||
// a.download = file.originalName;
|
|
||||||
// document.body.appendChild(a);
|
|
||||||
// a.click();
|
|
||||||
// a.remove();
|
|
||||||
// window.URL.revokeObjectURL(url);
|
|
||||||
// } catch {
|
|
||||||
// throw new Error("File download failed");
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
const safeRetrive = z.object({
|
|
||||||
available: z.array(FileMetadataSchema),
|
|
||||||
unavailable: z.array(z.string()),
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function retriveFilesSafely(ids: string[]) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`${env.STORAGE_URL}/files-check?token=${env.STORAGE_TOKEN}`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(ids),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("File fetch failed");
|
|
||||||
}
|
|
||||||
const parse = safeRetrive.safeParse(await response.json());
|
|
||||||
|
|
||||||
if (parse.success) {
|
|
||||||
return parse.data;
|
|
||||||
}
|
|
||||||
throw new Error("File fetch parse failed");
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`File fetch failed: ${(e as Error).message}`);
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: `Failed to get file: ${(e as Error).message}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export const editFile = async (fileId: StorageId, data: StorageUpdate) => {
|
||||||
|
try {
|
||||||
|
await db
|
||||||
|
.$pickTables<"storage">()
|
||||||
|
.updateTable("storage")
|
||||||
|
.set(data)
|
||||||
|
.where("id", "=", fileId)
|
||||||
|
.execute();
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: `Failed to update file info: ${(e as Error).message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteFile = async (fileId: StorageId) => {
|
||||||
|
try {
|
||||||
|
await db
|
||||||
|
.$pickTables<"storage">()
|
||||||
|
.deleteFrom("storage")
|
||||||
|
.where("id", "=", fileId)
|
||||||
|
.execute();
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: `Failed to delete file info: ${(e as Error).message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ export const getClientProfilo = async ({
|
||||||
eb
|
eb
|
||||||
.selectFrom("users_storage")
|
.selectFrom("users_storage")
|
||||||
.select([
|
.select([
|
||||||
"users_storage.storageId as storageId",
|
"users_storage.storage_id",
|
||||||
"users_anagrafica.doc_personale_fronte_ref as ref",
|
"users_anagrafica.doc_personale_fronte_ref as ref",
|
||||||
])
|
])
|
||||||
.whereRef(
|
.whereRef(
|
||||||
|
|
@ -160,7 +160,7 @@ export const getClientProfilo = async ({
|
||||||
eb
|
eb
|
||||||
.selectFrom("users_storage")
|
.selectFrom("users_storage")
|
||||||
.select([
|
.select([
|
||||||
"users_storage.storageId as storageId",
|
"users_storage.storage_id",
|
||||||
"users_anagrafica.doc_personale_retro_ref as ref",
|
"users_anagrafica.doc_personale_retro_ref as ref",
|
||||||
])
|
])
|
||||||
.whereRef(
|
.whereRef(
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ export const api = createTRPCNext<AppRouter>({
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether tRPC should await queries when server rendering pages.
|
* Whether tRPC should await queries when server rendering pages.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue