diff --git a/apps/backend/config/config.go b/apps/backend/config/config.go index 8d0fcc6..792ff7b 100644 --- a/apps/backend/config/config.go +++ b/apps/backend/config/config.go @@ -14,15 +14,6 @@ import ( // Config holds all application configuration type Config struct { - Database struct { - Host string - Port int - User string - Password string - DBName string - SSLMode string - } - Images struct { Enabled bool ConcurrentLimit int @@ -61,14 +52,6 @@ func Load() *Config { 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 Cfg.Images.Enabled = getEnvAsBool("IMAGEOPTION", true) Cfg.Images.ConcurrentLimit = getEnvAsInt("CONCURRENT_IMAGES", 5) @@ -86,10 +69,6 @@ func Load() *Config { // Server config 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 { diff --git a/apps/backend/db/handler.go b/apps/backend/db/handler.go deleted file mode 100644 index 35429ab..0000000 --- a/apps/backend/db/handler.go +++ /dev/null @@ -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} -} diff --git a/apps/backend/db/helpers.go b/apps/backend/db/helpers.go deleted file mode 100644 index a531c43..0000000 --- a/apps/backend/db/helpers.go +++ /dev/null @@ -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) - } -} diff --git a/apps/backend/gen/postgres/postgres/public/model/annunci.go b/apps/backend/gen/postgres/postgres/public/model/annunci.go new file mode 100644 index 0000000..e078ab8 --- /dev/null +++ b/apps/backend/gen/postgres/postgres/public/model/annunci.go @@ -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 +} diff --git a/apps/backend/gen/postgres/postgres/public/model/images_refs.go b/apps/backend/gen/postgres/postgres/public/model/images_refs.go new file mode 100644 index 0000000..3634307 --- /dev/null +++ b/apps/backend/gen/postgres/postgres/public/model/images_refs.go @@ -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 +} diff --git a/apps/backend/gen/postgres/postgres/public/model/storage.go b/apps/backend/gen/postgres/postgres/public/model/storage.go new file mode 100644 index 0000000..6f912ad --- /dev/null +++ b/apps/backend/gen/postgres/postgres/public/model/storage.go @@ -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 +} diff --git a/apps/backend/gen/postgres/postgres/public/model/videos_refs.go b/apps/backend/gen/postgres/postgres/public/model/videos_refs.go new file mode 100644 index 0000000..7bc2e4d --- /dev/null +++ b/apps/backend/gen/postgres/postgres/public/model/videos_refs.go @@ -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 +} diff --git a/apps/backend/gen/postgres/postgres/public/table/annunci.go b/apps/backend/gen/postgres/postgres/public/table/annunci.go new file mode 100644 index 0000000..4eeacc0 --- /dev/null +++ b/apps/backend/gen/postgres/postgres/public/table/annunci.go @@ -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, + } +} diff --git a/apps/backend/gen/postgres/postgres/public/table/images_refs.go b/apps/backend/gen/postgres/postgres/public/table/images_refs.go new file mode 100644 index 0000000..1202fc0 --- /dev/null +++ b/apps/backend/gen/postgres/postgres/public/table/images_refs.go @@ -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, + } +} diff --git a/apps/backend/gen/postgres/postgres/public/table/storage.go b/apps/backend/gen/postgres/postgres/public/table/storage.go new file mode 100644 index 0000000..6506520 --- /dev/null +++ b/apps/backend/gen/postgres/postgres/public/table/storage.go @@ -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, + } +} diff --git a/apps/backend/gen/postgres/postgres/public/table/table_use_schema.go b/apps/backend/gen/postgres/postgres/public/table/table_use_schema.go new file mode 100644 index 0000000..6c5ed5b --- /dev/null +++ b/apps/backend/gen/postgres/postgres/public/table/table_use_schema.go @@ -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) +} diff --git a/apps/backend/gen/postgres/postgres/public/table/videos_refs.go b/apps/backend/gen/postgres/postgres/public/table/videos_refs.go new file mode 100644 index 0000000..2cbc78c --- /dev/null +++ b/apps/backend/gen/postgres/postgres/public/table/videos_refs.go @@ -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, + } +} diff --git a/apps/backend/images.go b/apps/backend/images.go index e93cb1f..5547f97 100644 --- a/apps/backend/images.go +++ b/apps/backend/images.go @@ -18,7 +18,7 @@ import ( "sync" "time" - "github.com/labstack/echo/v4" + "github.com/google/uuid" "github.com/nfnt/resize" "github.com/nickalie/go-webpbin" ) @@ -101,12 +101,12 @@ func ProcessImagePool(toProcess *[]typesdefs.MediaToProcess) ([]typesdefs.Upload defer uploadWg.Done() 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 { log.Printf("failed to upload image: %v", err) return } - thumbId, err := UploadFile(img.ThumnailPath, "image/webp", "images") + thumbId, err := queries.UploadFile(img.ThumnailPath, "image/webp", "images") if err != nil { log.Printf("failed to upload thumbnail: %v", err) return @@ -272,28 +272,19 @@ func thumbnail_creation(img image.Image, output string, outputdir string) error 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 { Og_url string - Img string - Thumb string + Img uuid.UUID + Thumb uuid.UUID } func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) { - currentBucketState, err := ListBucket("images") + currentBucketState, err := queries.GetStorageByTag("images") if err != nil { return nil, fmt.Errorf("failed to list bucket: %w", err) } //make a list of bucket ids - bucketIdsMap := make(map[string]bool) + bucketIdsMap := make(map[uuid.UUID]bool) for _, item := range currentBucketState { bucketIdsMap[item.ID] = true } @@ -306,7 +297,7 @@ func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) { dbImagesMap := make(map[string][]imgRef) for _, ref := range db_images { dbImagesMap[ref.Codice] = append(dbImagesMap[ref.Codice], imgRef{ - Og_url: ref.Og_url, + Og_url: ref.OgURL, Img: ref.Img, Thumb: ref.Thumb, }) @@ -351,7 +342,7 @@ func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) { } func ForceClearImages(codici []string) error { - var idsToDelete []string + var idsToDelete uuid.UUIDs for _, codice := range codici { refs, err := queries.GetCodiceImagesFromDB(codice) if err != nil { @@ -363,7 +354,7 @@ func ForceClearImages(codici []string) error { } } - err := DeleteIdsFromStorage(idsToDelete) + err := queries.DeleteMultFile(idsToDelete) if err != nil { 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) func ReconcileImages() error { - currentBucketState, err := ListBucket("images") + currentBucketState, err := queries.GetStorageByTag("images") if err != nil { return fmt.Errorf("failed to list bucket: %w", err) } //make a list of bucket ids - bucketIdsMap := make(map[string]bool) + bucketIdsMap := make(map[uuid.UUID]bool) for _, item := range currentBucketState { bucketIdsMap[item.ID] = true } @@ -391,13 +382,13 @@ func ReconcileImages() error { if err != nil { 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 { knownStorageIds[ref.Img] = true knownStorageIds[ref.Thumb] = true } // reconcile: delete storage files not referenced in DB - var strandedIds []string + var strandedIds uuid.UUIDs for _, item := range currentBucketState { if !knownStorageIds[item.ID] { strandedIds = append(strandedIds, item.ID) @@ -405,7 +396,7 @@ func ReconcileImages() error { } if len(strandedIds) > 0 { 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 } } else { diff --git a/apps/backend/jet.sh b/apps/backend/jet.sh new file mode 100644 index 0000000..297d498 --- /dev/null +++ b/apps/backend/jet.sh @@ -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 + diff --git a/apps/backend/miogest_handler.go b/apps/backend/miogest_handler.go index 8fefd48..7184485 100644 --- a/apps/backend/miogest_handler.go +++ b/apps/backend/miogest_handler.go @@ -487,14 +487,14 @@ func processDatiImmobile(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.Annu tmp.Numero_postiauto = annuncio.PostiAuto } -func parseCaratteristiche(wrkrecord *typesdefs.AnnuncioXML, m *MiogestHandler) map[string]interface{} { - if reflect.TypeOf(wrkrecord).Kind() != reflect.Ptr || reflect.ValueOf(wrkrecord).Elem().Kind() != reflect.Struct { +func parseCaratteristiche(wrkrecord *typesdefs.AnnuncioXML, m *MiogestHandler) map[string]any { + if reflect.TypeFor[*typesdefs.AnnuncioXML]().Kind() != reflect.Pointer || reflect.ValueOf(wrkrecord).Elem().Kind() != reflect.Struct { return nil } - tmpCaratteristiche := make(map[string]interface{}) + tmpCaratteristiche := make(map[string]any) val := reflect.ValueOf(wrkrecord).Elem() numField := val.NumField() - for i := 0; i < numField; i++ { + for i := range numField { if strings.HasPrefix(val.Type().Field(i).Name, "Scheda_") { nameField := val.Type().Field(i).Name valueField := val.Field(i) diff --git a/apps/backend/queries/annunci.go b/apps/backend/queries/annunci.go index c1a1468..d8f5660 100644 --- a/apps/backend/queries/annunci.go +++ b/apps/backend/queries/annunci.go @@ -1,18 +1,22 @@ package queries import ( + models "backend/gen/postgres/postgres/public/model" + . "backend/gen/postgres/postgres/public/table" "backend/typesdefs" "fmt" "slices" + "time" - "github.com/georgysavva/scany/v2/pgxscan" - "github.com/jackc/pgx/v5" + . "github.com/go-jet/jet/v2/postgres" + "github.com/lib/pq" ) func AnnunciGetActive() ([]string, error) { var annunci []string - err := pgxscan.Select(Db.Ctx, Db.Dbpool, &annunci, "SELECT codice FROM public.annunci WHERE web = true and stato = 'Attivo' ") - if err != nil && err != pgx.ErrNoRows { + stmt := SELECT(Annunci.Codice).FROM(Annunci).WHERE(AND(Annunci.Web.IS_TRUE(), Annunci.Stato.EQ(Text("Attivo")))) + err := stmt.Query(postgres, &annunci) + if err != nil { return nil, fmt.Errorf("failed to get active annunci: %w", err) } return annunci, nil @@ -22,7 +26,8 @@ func AnnunciInsert(annunci typesdefs.AnnunciParsed, resetAllBeforeUpdate bool) e var err error if resetAllBeforeUpdate { //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 { 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 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 { 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) { - batch.Queue(` - 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) + to_update = append(to_update, value) } else { - batch.Queue(` - INSERT INTO public.annunci( - locatore, - numero, - idlocatore, - email, - creato_il, - modificato_il, - indirizzo, - 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) + to_insert = append(to_insert, value) + } + + } + if len(to_insert) > 0 { + stmt := Annunci.INSERT(Annunci.MutableColumns).MODELS(to_insert) + _, err = stmt.Exec(postgres) + if err != nil { + return fmt.Errorf("failed to insert/update annunci batch: %w", err) } } - 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 { return fmt.Errorf("failed to insert/update annunci batch: %w", err) } fmt.Println("Annunci insert/update done") return nil } - -func clearRecords() { - - //SET ALL WEB TO FALSE - _, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.annunci") - if err != nil { - panic(err) - - } -} diff --git a/apps/backend/queries/images.go b/apps/backend/queries/images.go index e33ff7f..9c6353d 100644 --- a/apps/backend/queries/images.go +++ b/apps/backend/queries/images.go @@ -1,12 +1,13 @@ package queries import ( + models "backend/gen/postgres/postgres/public/model" + . "backend/gen/postgres/postgres/public/table" "backend/typesdefs" "fmt" "log" - "github.com/georgysavva/scany/v2/pgxscan" - "github.com/jackc/pgx/v5" + . "github.com/go-jet/jet/v2/postgres" ) 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.") return nil } - batch := &pgx.Batch{} + + var to_insert []models.ImagesRefs 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 nil } -func GetImagesFromDB() ([]typesdefs.ImagesRefs, error) { - var images []typesdefs.ImagesRefs - err := pgxscan.Select(Db.Ctx, Db.Dbpool, &images, "SELECT * FROM public.images_refs ORDER BY ordine ASC") - if err != nil && err != pgx.ErrNoRows { +func GetImagesFromDB() ([]models.ImagesRefs, error) { + var images []models.ImagesRefs + stmt := SELECT(ImagesRefs.AllColumns).FROM(ImagesRefs).ORDER_BY(ImagesRefs.Ordine.ASC()) + err := stmt.Query(postgres, &images) + if err != nil { return nil, fmt.Errorf("failed to get images: %w", err) } return images, nil } -func GetCodiceImagesFromDB(codice string) ([]typesdefs.ImagesRefs, error) { - var images []typesdefs.ImagesRefs - err := pgxscan.Select(Db.Ctx, Db.Dbpool, &images, "SELECT * FROM public.images_refs WHERE codice = $1 ORDER BY ordine ASC", codice) - if err != nil && err != pgx.ErrNoRows { +func GetCodiceImagesFromDB(codice string) ([]models.ImagesRefs, error) { + var images []models.ImagesRefs + stmt := SELECT(ImagesRefs.AllColumns).FROM(ImagesRefs).WHERE(ImagesRefs.Codice.EQ(Text(codice))).ORDER_BY(ImagesRefs.Ordine.ASC()) + err := stmt.Query(postgres, &images) + if err != nil { return nil, fmt.Errorf("failed to get images: %w", err) } return images, nil } func ClearImagesRefsByCodice(codice string) error { - _, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.images_refs WHERE codice = $1", codice) + stmt := ImagesRefs.DELETE().WHERE(ImagesRefs.Codice.EQ(Text(codice))) + _, err := stmt.Exec(postgres) + if err != nil { return fmt.Errorf("failed to clear images refs by codice: %w", err) } diff --git a/apps/backend/queries/queries.go b/apps/backend/queries/queries.go index 95c5bcc..4fd329c 100644 --- a/apps/backend/queries/queries.go +++ b/apps/backend/queries/queries.go @@ -1,35 +1,34 @@ package queries import ( - "backend/db" + "database/sql" + "fmt" "os" "github.com/joho/godotenv" ) var ( - Db *db.DbHandler + postgres *sql.DB ) func init() { _ = godotenv.Load(".env") - Db = db.NewDbHandler(db.Configs{ - Host: os.Getenv("PGHOST"), - Dbname: os.Getenv("POSTGRES_DB"), - Port: os.Getenv("PGPORT"), - User: os.Getenv("POSTGRES_USER"), - Password: os.Getenv("POSTGRES_PASSWORD"), - LoggerEnabled: os.Getenv("DB_LOGGER") == "true", - }) + var err error + 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")) + postgres, err = sql.Open("postgres", connStr) + if err != nil { + panic(err) + } } func HealthCheck() bool { - if Db == nil || Db.Dbpool == nil { + if postgres == nil { return false } - err := Db.Dbpool.Ping(Db.Ctx) + err := postgres.Ping() return err == nil } diff --git a/apps/backend/queries/storage.go b/apps/backend/queries/storage.go new file mode 100644 index 0000000..ee075c1 --- /dev/null +++ b/apps/backend/queries/storage.go @@ -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 + +} diff --git a/apps/backend/queries/utils.go b/apps/backend/queries/utils.go new file mode 100644 index 0000000..6b61931 --- /dev/null +++ b/apps/backend/queries/utils.go @@ -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 +} diff --git a/apps/backend/queries/videos.go b/apps/backend/queries/videos.go index bb53315..7856add 100644 --- a/apps/backend/queries/videos.go +++ b/apps/backend/queries/videos.go @@ -1,12 +1,13 @@ package queries import ( + models "backend/gen/postgres/postgres/public/model" + . "backend/gen/postgres/postgres/public/table" "backend/typesdefs" "fmt" "log" - "github.com/georgysavva/scany/v2/pgxscan" - "github.com/jackc/pgx/v5" + . "github.com/go-jet/jet/v2/postgres" ) 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.") return nil } - batch := &pgx.Batch{} + var to_insert []models.VideosRefs for _, data := range *pool { - batch.Queue(` - INSERT INTO public.videos_refs (codice, ordine, video, thumb, og_url) - VALUES ($1, $2, $3, $4, $5) - ON CONFLICT (codice, ordine) DO UPDATE SET video = $3, thumb = $4 - `, data.Codice, data.Order, data.MediaId, data.ThumbId, data.OGUrl) - } + to_insert = append(to_insert, models.VideosRefs{ + Codice: data.Codice, + Ordine: int32(data.Order), + Video: data.MediaId, + 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 } -func GetVideosFromDB() ([]typesdefs.VideosRefs, error) { - var videos []typesdefs.VideosRefs - err := pgxscan.Select(Db.Ctx, Db.Dbpool, &videos, "SELECT * FROM public.videos_refs ORDER BY ordine ASC") - if err != nil && err != pgx.ErrNoRows { +func GetVideosFromDB() ([]models.VideosRefs, error) { + var videos []models.VideosRefs + stmt := VideosRefs.SELECT(VideosRefs.AllColumns).FROM(VideosRefs).ORDER_BY(VideosRefs.Ordine.ASC()) + err := stmt.Query(postgres, &videos) + if err != nil { return nil, fmt.Errorf("failed to get videos: %w", err) } return videos, nil } -func GetCodiceVideosFromDB(codice string) ([]typesdefs.VideosRefs, error) { - var videos []typesdefs.VideosRefs - err := pgxscan.Select(Db.Ctx, Db.Dbpool, &videos, "SELECT * FROM public.videos_refs WHERE codice = $1 ORDER BY ordine ASC", codice) - if err != nil && err != pgx.ErrNoRows { +func GetCodiceVideosFromDB(codice string) ([]models.VideosRefs, error) { + var videos []models.VideosRefs + stmt := VideosRefs.SELECT(VideosRefs.AllColumns).FROM(VideosRefs).WHERE(VideosRefs.Codice.EQ(Text(codice))).ORDER_BY(VideosRefs.Ordine.ASC()) + err := stmt.Query(postgres, &videos) + if err != nil { return nil, fmt.Errorf("failed to get videos: %w", err) } return videos, nil } 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 { return fmt.Errorf("failed to clear videos refs by codice: %w", err) } diff --git a/apps/backend/server.go b/apps/backend/server.go index af8c8a2..643fcc4 100644 --- a/apps/backend/server.go +++ b/apps/backend/server.go @@ -61,14 +61,10 @@ func (s *Server) SetupRoutes() *echo.Echo { }) }) 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, map[string]any{ - "files": files, + "files": "", }, " ") }) 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 { h := queries.HealthCheck() - - 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), " ") + return c.JSONPretty(http.StatusOK, fmt.Sprintf("DB Connection: %v", h), " ") }) // MIOGEST ROUTES diff --git a/apps/backend/storage.go b/apps/backend/storage.go deleted file mode 100644 index 2b9d701..0000000 --- a/apps/backend/storage.go +++ /dev/null @@ -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 - -} diff --git a/apps/backend/typesdefs/models.go b/apps/backend/typesdefs/models.go index 912e13e..0c82f55 100644 --- a/apps/backend/typesdefs/models.go +++ b/apps/backend/typesdefs/models.go @@ -1,8 +1,9 @@ package typesdefs import ( - "os" "time" + + "github.com/google/uuid" ) type AnnunciXML struct { @@ -186,67 +187,6 @@ type AnnuncioParsed struct { 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 { Scheda []struct { Id string `xml:"id"` @@ -273,22 +213,6 @@ type Categoria struct { 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 { Codice string Url string @@ -306,7 +230,7 @@ type ProcessedMedia struct { type UploadedMedia struct { Codice string Order int - MediaId string - ThumbId string + MediaId uuid.UUID + ThumbId uuid.UUID OGUrl string } diff --git a/apps/backend/utils/utils.go b/apps/backend/utils/utils.go index 0cf0825..56f6a7c 100644 --- a/apps/backend/utils/utils.go +++ b/apps/backend/utils/utils.go @@ -22,24 +22,24 @@ func MakeUppercase(s string) string { return "" } -func fetchXML(url string) ([]byte, error) { - resp, err := http.Get(url) - if err != nil { - return []byte{}, fmt.Errorf("GET error: %v", err) - } - defer resp.Body.Close() +// func fetchXML(url string) ([]byte, error) { +// resp, err := http.Get(url) +// if err != nil { +// return []byte{}, fmt.Errorf("GET error: %v", err) +// } +// defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return []byte{}, fmt.Errorf("status error: %v", resp.StatusCode) - } +// if resp.StatusCode != http.StatusOK { +// return []byte{}, fmt.Errorf("status error: %v", resp.StatusCode) +// } - data, err := io.ReadAll(resp.Body) - if err != nil { - return []byte{}, fmt.Errorf("read body: %v", err) - } +// data, err := io.ReadAll(resp.Body) +// if err != nil { +// return []byte{}, fmt.Errorf("read body: %v", err) +// } - return data, nil -} +// return data, nil +// } func GetXMLFromFile[T any](filePath string) (T, error) { var result T @@ -144,11 +144,3 @@ func GetStringValue(strPtr *string) string { func RemoveWhitespace(str string) string { return strings.ReplaceAll(str, " ", "") } - -func CommaToDot(str *string) *string { - if str != nil { - result := strings.ReplaceAll(*str, ",", ".") - return &result - } - return nil -} diff --git a/apps/backend/video.go b/apps/backend/video.go index b70a619..c826f41 100644 --- a/apps/backend/video.go +++ b/apps/backend/video.go @@ -17,7 +17,7 @@ import ( "sync" "time" - "github.com/labstack/echo/v4" + "github.com/google/uuid" "github.com/nfnt/resize" "github.com/nickalie/go-webpbin" ) @@ -111,12 +111,12 @@ func ProcessVideoPool(toProcess *[]typesdefs.MediaToProcess) ([]typesdefs.Upload defer uploadWg.Done() 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 { log.Printf("failed to upload video: %v", err) return } - thumbId, err := UploadFile(video.ThumnailPath, "image/webp", "videos") + thumbId, err := queries.UploadFile(video.ThumnailPath, "image/webp", "videos") if err != nil { log.Printf("failed to upload thumbnail: %v", err) return @@ -324,29 +324,20 @@ func extractThumbnail(input, output string) error { 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 { Og_url string - Video string - Thumb string + Video uuid.UUID + Thumb uuid.UUID } func CodiciToProcessVideos(annunci *typesdefs.AnnunciXML) ([]string, error) { - currentBucketState, err := ListBucket("videos") + currentBucketState, err := queries.GetStorageByTag("videos") if err != nil { return nil, fmt.Errorf("failed to list bucket: %w", err) } //make a list of bucket ids - bucketIdsMap := make(map[string]bool) + bucketIdsMap := make(map[uuid.UUID]bool) for _, item := range currentBucketState { 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 for _, ref := range db_videos { dbVideosMap[ref.Codice] = append(dbVideosMap[ref.Codice], videRef{ - Og_url: ref.Og_url, + Og_url: ref.OgURL, Video: ref.Video, Thumb: ref.Thumb, }) @@ -422,7 +413,7 @@ func CodiciToProcessVideos(annunci *typesdefs.AnnunciXML) ([]string, error) { } func ForceClearVideos(codici []string) error { - var idsToDelete []string + var idsToDelete uuid.UUIDs for _, codice := range codici { refs, err := queries.GetCodiceVideosFromDB(codice) if err != nil { @@ -434,7 +425,7 @@ func ForceClearVideos(codici []string) error { } } - err := DeleteIdsFromStorage(idsToDelete) + err := queries.DeleteMultFile(idsToDelete) if err != nil { 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) func ReconcileVideos() error { - currentBucketState, err := ListBucket("videos") + currentBucketState, err := queries.GetStorageByTag("videos") if err != nil { return fmt.Errorf("failed to list bucket: %w", err) } //make a list of bucket ids - bucketIdsMap := make(map[string]bool) + bucketIdsMap := make(map[uuid.UUID]bool) for _, item := range currentBucketState { bucketIdsMap[item.ID] = true } @@ -462,7 +453,7 @@ func ReconcileVideos() error { if err != nil { return fmt.Errorf("failed to get videos from DB: %w", err) } - var strandedIds []string + var strandedIds uuid.UUIDs for _, ref := range db_videos { if !bucketIdsMap[ref.Video] { strandedIds = append(strandedIds, ref.Video) @@ -472,7 +463,7 @@ func ReconcileVideos() error { } } if len(strandedIds) > 0 { - err := DeleteIdsFromStorage(strandedIds) + err := queries.DeleteMultFile(strandedIds) if err != nil { return fmt.Errorf("failed to delete stranded videos from storage: %w", err) } diff --git a/apps/db/migrations/56_storage.up.sql b/apps/db/migrations/56_storage.up.sql new file mode 100644 index 0000000..f9d0a00 --- /dev/null +++ b/apps/db/migrations/56_storage.up.sql @@ -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 $$; + diff --git a/apps/infoalloggi/Dockerfile b/apps/infoalloggi/Dockerfile index ebb7089..1372ddc 100644 --- a/apps/infoalloggi/Dockerfile +++ b/apps/infoalloggi/Dockerfile @@ -75,11 +75,6 @@ ENV SKEBBY_USER=$SKEBBY_USER ARG SKEBBY_PASS ENV SKEBBY_PASS=$SKEBBY_PASS 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 diff --git a/apps/infoalloggi/TODO b/apps/infoalloggi/TODO index 746fc04..4fdc288 100644 --- a/apps/infoalloggi/TODO +++ b/apps/infoalloggi/TODO @@ -12,6 +12,7 @@ TODOS: - pre attivazione se admin mostrare tastino contatti in preview - onboarding submit (toast e email che invitano a pagare) - migliorare onboarding e pag guida + - rendere opzionale email e numero? AFTER MVP: - TODO migrazione app router https://nextjs.org/docs/pages/building-your-application/upgrading/app-router-migration#migrating-from-pages-to-app diff --git a/apps/infoalloggi/dev-docker-compose.yml b/apps/infoalloggi/dev-docker-compose.yml deleted file mode 100644 index 22baa4c..0000000 --- a/apps/infoalloggi/dev-docker-compose.yml +++ /dev/null @@ -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: diff --git a/apps/infoalloggi/dev_utils_compose.sh b/apps/infoalloggi/dev_utils_compose.sh index daddb45..97eae26 100644 --- a/apps/infoalloggi/dev_utils_compose.sh +++ b/apps/infoalloggi/dev_utils_compose.sh @@ -3,5 +3,3 @@ # Start the pgAdmin service using its Docker Compose file 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 diff --git a/apps/infoalloggi/knip.json b/apps/infoalloggi/knip.json index cc97b90..0376c16 100644 --- a/apps/infoalloggi/knip.json +++ b/apps/infoalloggi/knip.json @@ -23,7 +23,6 @@ "ignoreDependencies": [ "pdfjs-dist", "kysely-plugin-serialize", - "sharp", "@react-email/components", "kanel-kysely", "kanel-zod", diff --git a/apps/infoalloggi/next.config.ts b/apps/infoalloggi/next.config.ts index e48cc25..238d8ee 100644 --- a/apps/infoalloggi/next.config.ts +++ b/apps/infoalloggi/next.config.ts @@ -17,51 +17,31 @@ async function createNextConfig(): Promise { await jiti.import("./src/env.ts"); - const buildId = `${Date.now().toString(36)}`; - const apiVersion = `v.${buildId}`; - const configs: NextConfig = { compiler: { removeConsole: false, }, experimental: { scrollRestoration: true, + proxyClientMaxBodySize: "300mb", // esmExternals: "loose", // This if react-pdf gives compilation issues }, allowedDevOrigins: ["host.docker.internal"], - generateBuildId: async () => buildId, devIndicators: false, - headers: async () => { - return [ - { - source: "/:path*", - headers: [ - { - key: "X-Frame-Options", - value: "", - }, - ], - }, - { - 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, - }, - ], - }, - ]; - }, + // headers: async () => { + // return [ + // { + // source: "/api/trpc/:path*", + // headers: [ + // // { + // // key: "Cache-Control", + // // value: + // // "no-cache, no-store, must-revalidate, max-age=0, proxy-revalidate", + // // }, + // ], + // }, + // ]; + // }, i18n: { defaultLocale: "it", locales: ["it", "en"], @@ -70,17 +50,6 @@ async function createNextConfig(): Promise { images: { unoptimized: true, 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 transpilePackages: ["@t3-oss/env-nextjs", "@t3-oss/env-core"], diff --git a/apps/infoalloggi/package-lock.json b/apps/infoalloggi/package-lock.json index dbacfb3..fec7aa1 100644 --- a/apps/infoalloggi/package-lock.json +++ b/apps/infoalloggi/package-lock.json @@ -81,7 +81,6 @@ "react-reverse-portal": "^2.3.0", "react-select": "^5.10.2", "react-use": "^17.6.0", - "sharp": "^0.34.5", "stripe": "^22.1.1", "superjson": "^2.2.6", "tailwind-merge": "^3.6.0", @@ -1376,6 +1375,7 @@ "node_modules/@img/colour": { "version": "1.0.0", "license": "MIT", + "optional": true, "engines": { "node": ">=18" } @@ -1387,6 +1387,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1409,6 +1410,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1431,6 +1433,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1447,6 +1450,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1463,6 +1467,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1479,6 +1484,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1495,6 +1501,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1511,6 +1518,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1527,6 +1535,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1543,6 +1552,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1559,6 +1569,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1575,6 +1586,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1591,6 +1603,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1613,6 +1626,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1635,6 +1649,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1657,6 +1672,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1679,6 +1695,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1701,6 +1718,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1723,6 +1741,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1745,6 +1764,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1767,6 +1787,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { @@ -1786,6 +1807,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -1805,6 +1827,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -1822,6 +1845,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -5776,6 +5800,29 @@ "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": { "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", @@ -12459,6 +12506,7 @@ }, "node_modules/semver": { "version": "7.7.4", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -12521,6 +12569,7 @@ "version": "0.34.5", "hasInstallScript": true, "license": "Apache-2.0", + "optional": true, "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", diff --git a/apps/infoalloggi/package.json b/apps/infoalloggi/package.json index f294e4e..328d75a 100644 --- a/apps/infoalloggi/package.json +++ b/apps/infoalloggi/package.json @@ -96,7 +96,6 @@ "react-reverse-portal": "^2.3.0", "react-select": "^5.10.2", "react-use": "^17.6.0", - "sharp": "^0.34.5", "stripe": "^22.1.1", "superjson": "^2.2.6", "tailwind-merge": "^3.6.0", diff --git a/apps/infoalloggi/src/components/allegato-iframe.tsx b/apps/infoalloggi/src/components/allegato-iframe.tsx index e46ce0f..45e124f 100644 --- a/apps/infoalloggi/src/components/allegato-iframe.tsx +++ b/apps/infoalloggi/src/components/allegato-iframe.tsx @@ -36,7 +36,7 @@ export const AllegatoIframe = ({ setIsIOS(/iphone|ipad|ipod/i.test(userAgent)); // Try to detect if file is PDF from extension - setIsPDF(allegato.mimeType === "application/pdf"); + setIsPDF(allegato.mime_type === "application/pdf"); }, [allegato]); const handleIframeLoad = () => { @@ -52,7 +52,7 @@ export const AllegatoIframe = ({ }; const requestUrl = getStorageUrl({ - storageId: allegato.id, + id: allegato.id, params: { mode: "inline" }, }); // For PDF on iOS, provide download link instead diff --git a/apps/infoalloggi/src/components/annunci_map.tsx b/apps/infoalloggi/src/components/annunci_map.tsx index f65250f..89aea05 100644 --- a/apps/infoalloggi/src/components/annunci_map.tsx +++ b/apps/infoalloggi/src/components/annunci_map.tsx @@ -134,7 +134,7 @@ const SelectedComp = memo( className="size-24 rounded-md object-cover sm:size-40" height={500} src={getStorageUrl({ - storageId: selected.images[0].img, + id: selected.images[0].img, params: { media: "image", cacheKey: selected.media_updated_at?.toISOString(), diff --git a/apps/infoalloggi/src/components/annuncio_card.tsx b/apps/infoalloggi/src/components/annuncio_card.tsx index 449fcb0..e3e5199 100644 --- a/apps/infoalloggi/src/components/annuncio_card.tsx +++ b/apps/infoalloggi/src/components/annuncio_card.tsx @@ -151,7 +151,6 @@ export const CardAnnuncio = ({ ); }; - return (
handleOpenModal(idx)} priority={idx === 0} src={getStorageUrl({ - storageId: img.img, + id: img.img, params: { cacheKey: updated_at?.toISOString(), media: "image", }, })} - width={800} + width={500} />
- +
- {file.originalName} + {file.filename}
- {format(new Date(file.uploadedAt), "d MMM yyyy")} + {format(new Date(file.created_at), "d MMM yyyy")}
@@ -352,7 +352,7 @@ function FileList({ - selectHandler(file.id, file.originalName || "") + selectHandler(file.id, file.filename || "") } > diff --git a/apps/infoalloggi/src/components/area-riservata/documenti_personali.tsx b/apps/infoalloggi/src/components/area-riservata/documenti_personali.tsx index ff2a961..c133692 100644 --- a/apps/infoalloggi/src/components/area-riservata/documenti_personali.tsx +++ b/apps/infoalloggi/src/components/area-riservata/documenti_personali.tsx @@ -6,6 +6,7 @@ import toast from "react-hot-toast"; import { getStorageUrl } from "~/lib/storage_utils"; import { useTranslation } from "~/providers/I18nProvider"; import { useSession } from "~/providers/SessionProvider"; +import type { StorageId } from "~/schemas/public/Storage"; import type { UsersId } from "~/schemas/public/Users"; import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage"; import type { CompleteUserData } from "~/server/services/user.service"; @@ -133,7 +134,7 @@ export const DocumentiUploadSection = ({ }: { title: string; document: { - storageId: string; + storage_id: StorageId | null; ref: UsersStorageUserStorageId | null; } | null; onUpload: (id: UsersStorageUserStorageId) => void; @@ -145,7 +146,7 @@ export const DocumentiUploadSection = ({ }) => { const { locale } = useTranslation(); const [imageError, setImageError] = useState(false); - if (document?.storageId) { + if (document?.storage_id) { return (

{title}

@@ -167,7 +168,7 @@ export const DocumentiUploadSection = ({ height={96} onError={() => setImageError(true)} src={getStorageUrl({ - storageId: document.storageId, + id: document.storage_id, })} width={96} /> @@ -175,7 +176,7 @@ export const DocumentiUploadSection = ({