init
35
.dockerignore
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
.gitignore
|
||||
.git
|
||||
readme.md
|
||||
build_logs.txt
|
||||
|
||||
|
||||
apps/backend/.git.bkp
|
||||
apps/backend/images
|
||||
apps/backend/storage
|
||||
apps/backend/tmp
|
||||
apps/backend/vscode
|
||||
apps/backend/.air.toml
|
||||
apps/backend/.git
|
||||
apps/backend/.gitignore
|
||||
apps/backend/bkp.xml
|
||||
apps/backend/.bin
|
||||
apps/backend/test.webp
|
||||
apps/backend/TODO
|
||||
|
||||
apps/db/.git.bkp
|
||||
apps/db/.git
|
||||
apps/db/.gitignore
|
||||
|
||||
apps/infoalloggi/.git.bkp
|
||||
apps/infoalloggi/.git
|
||||
apps/infoalloggi/.gitignore
|
||||
apps/infoalloggi/TODO
|
||||
apps/infoalloggi/node_modules
|
||||
apps/infoalloggi/.vscode
|
||||
apps/infoalloggi/.next
|
||||
apps/infoalloggi/Dockerfile
|
||||
apps/infoalloggi/.env
|
||||
apps/infoalloggi/.docker_env
|
||||
|
||||
apps/infoalloggi_old
|
||||
37
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
.env
|
||||
build_logs.txt
|
||||
|
||||
infoalloggi_old/
|
||||
|
||||
apps/db/.git.bkp
|
||||
apps/db/.env
|
||||
apps/db/.gitignore
|
||||
|
||||
apps/backend/.git.bkp
|
||||
apps/backend/.gitignore
|
||||
apps/backend/tmp
|
||||
apps/backend/images
|
||||
apps/backend/bkp.xml
|
||||
apps/backend/storage
|
||||
apps/backend/.bin
|
||||
apps/backend/caller.bat
|
||||
apps/backend/*.exe
|
||||
apps/backend/web.config
|
||||
apps/backend/.env
|
||||
apps/backend/test.webp
|
||||
apps/backend/thumbnail-test.webp
|
||||
|
||||
apps/infoalloggi/.git.bkp
|
||||
apps/infoalloggi/.gitignore
|
||||
apps/infoalloggi/node_modules
|
||||
apps/infoalloggi/.next/
|
||||
apps/infoalloggi/out/
|
||||
apps/infoalloggi/next-env.d.ts
|
||||
apps/infoalloggi/build
|
||||
apps/infoalloggi/.DS_Store
|
||||
apps/infoalloggi/*.pem
|
||||
apps/infoalloggi/npm-debug.log*
|
||||
apps/infoalloggi/.env
|
||||
apps/infoalloggi/.env*.local
|
||||
apps/infoalloggi/*.tsbuildinfo
|
||||
apps/infoalloggi/certificates
|
||||
46
apps/backend/.air.toml
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
root = "."
|
||||
testdata_dir = "testdata"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "tmp\\main.exe"
|
||||
cmd = "go build -o ./tmp/main.exe ."
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata", "images", ".bin", "storage"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
post_cmd = []
|
||||
pre_cmd = []
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
stop_on_error = false
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
||||
11
apps/backend/.dockerignore
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
images
|
||||
storage
|
||||
tmp
|
||||
vscode
|
||||
.air.toml
|
||||
.git
|
||||
.gitignore
|
||||
bkp.xml
|
||||
.bin
|
||||
test.webp
|
||||
TODO
|
||||
69
apps/backend/Dockerfile
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# Start from the latest golang base image
|
||||
FROM golang:1.24 AS builder
|
||||
|
||||
# Set the Current Working Directory inside the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy go mod and sum files
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed
|
||||
RUN go mod download
|
||||
|
||||
# Copy the source from the current directory to the Working Directory inside the container
|
||||
COPY . .
|
||||
|
||||
# Build the Go app
|
||||
RUN go build -o main .
|
||||
|
||||
# Start a new stage from alpine:latest
|
||||
FROM alpine:latest AS runner
|
||||
|
||||
ARG POSTGRES_USER
|
||||
ARG POSTGRES_PASSWORD
|
||||
ARG POSTGRES_DB
|
||||
ARG PGHOST
|
||||
ARG PGPORT
|
||||
ARG IMAGEOPTION
|
||||
ARG CONCURRENT_IMAGES
|
||||
ARG STORAGE_STRATEGY
|
||||
ARG MINIO_ENDPOINT
|
||||
ARG MINIO_PORT
|
||||
ARG MINIO_ROOT_USER
|
||||
ARG MINIO_ROOT_PASSWORD
|
||||
|
||||
RUN apk --no-cache add ca-certificates libc6-compat
|
||||
ENV GOMEMLIMIT=2750MiB
|
||||
ENV GOGC=100
|
||||
ENV PGHOST=$PGHOST
|
||||
ENV POSTGRES_DB=$POSTGRES_DB
|
||||
ENV PGPORT=$PGPORT
|
||||
ENV POSTGRES_USER=$POSTGRES_USER
|
||||
ENV POSTGRES_PASSWORD=$POSTGRES_PASSWORD
|
||||
ENV IMAGEOPTION=$IMAGEOPTION
|
||||
ENV CONCURRENT_IMAGES=$CONCURRENT_IMAGES
|
||||
ENV STORAGE_STRATEGY=$STORAGE_STRATEGY
|
||||
ENV MINIO_ENDPOINT=$MINIO_ENDPOINT
|
||||
ENV MINIO_PORT=$MINIO_PORT
|
||||
ENV MINIO_ROOT_USER=$MINIO_ROOT_USER
|
||||
ENV MINIO_ROOT_PASSWORD=$MINIO_ROOT_PASSWORD
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the Pre-built binary file from the previous stage
|
||||
COPY --from=builder /app/main /app/.
|
||||
COPY --from=builder /app/public /app/public
|
||||
COPY --from=builder /app/test.jpg /app/test.jpg
|
||||
#COPY --from=builder /app/.env /app/.env
|
||||
# create images and storage folder
|
||||
RUN mkdir images
|
||||
RUN mkdir storage
|
||||
RUN mkdir storage/tmp
|
||||
|
||||
RUN apk add curl
|
||||
|
||||
# Expose port 1323 to the outside world
|
||||
EXPOSE 1323
|
||||
|
||||
# Command to run the executable
|
||||
CMD ["./main"]
|
||||
4
apps/backend/TODO
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
Todo:
|
||||
☐ https://medium.com/@felipedutratine/pass-environment-variables-from-docker-to-my-golang-2a967c5905fe
|
||||
dsdsd
|
||||
317
apps/backend/db_handler.go
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"backend/dbhelpers"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/georgysavva/scany/v2/pgxscan"
|
||||
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
|
||||
}
|
||||
|
||||
func NewDbHandler() *DbHandler {
|
||||
fmt.Println("Connecting to db")
|
||||
ctx := context.Background()
|
||||
|
||||
host := os.Getenv("PGHOST")
|
||||
dbname := os.Getenv("POSTGRES_DB")
|
||||
dbport := os.Getenv("PGPORT")
|
||||
pguser := os.Getenv("POSTGRES_USER")
|
||||
pgpassword := os.Getenv("POSTGRES_PASSWORD")
|
||||
|
||||
connStr := fmt.Sprintf("postgresql://%s:%s@%s:%s/%s?sslmode=disable", pguser, pgpassword, host, dbport, dbname)
|
||||
fmt.Printf("Connecting to db with connection string: %s", connStr)
|
||||
config, err := pgxpool.ParseConfig(connStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
config.ConnConfig.Tracer = dbhelpers.NewMultiQueryTracer(dbhelpers.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}
|
||||
}
|
||||
|
||||
func (h *DbHandler) GetActiveAnnunci() []string {
|
||||
var annunci []string
|
||||
pgxscan.Select(h.Ctx, h.Dbpool, &annunci, "SELECT codice FROM public.annunci WHERE web = true and stato = 'Attivo' ")
|
||||
/*for i, annuncio := range annunci {
|
||||
//if first letter is V then set annuncio as the last 4 digits
|
||||
if string(annuncio[0]) != "V" {
|
||||
annunci[i] = annuncio[:4]
|
||||
}
|
||||
}*/
|
||||
return annunci
|
||||
}
|
||||
|
||||
func (h *DbHandler) GetAllAnnunci() []AnnunciDB {
|
||||
//var annuncio AnnunciDB
|
||||
var annunci []AnnunciDB
|
||||
|
||||
pgxscan.Select(h.Ctx, h.Dbpool, &annunci, "SELECT * FROM public.annunci")
|
||||
return annunci
|
||||
|
||||
}
|
||||
|
||||
func (h *DbHandler) InsertAnnunciInDB(annunci AnnunciParsed) {
|
||||
|
||||
//SET ALL WEB TO FALSE
|
||||
_, err := h.Dbpool.Exec(h.Ctx, "UPDATE public.annunci SET web = false")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
}
|
||||
//GET ALL CODICI
|
||||
var codici []string
|
||||
err = pgxscan.Select(h.Ctx, h.Dbpool, &codici, "SELECT DISTINCT codice FROM public.annunci")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
batch := &pgx.Batch{}
|
||||
for _, annuncio := range annunci.Annuncio {
|
||||
|
||||
if 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,
|
||||
url_immagini = $45,
|
||||
url_video = $46
|
||||
WHERE codice = $47
|
||||
`, 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.Url_foto,
|
||||
annuncio.Url_video, annuncio.Codice)
|
||||
|
||||
} 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,
|
||||
url_immagini,
|
||||
url_video,
|
||||
codice
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44, $45, $46, $47)
|
||||
`, 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.Url_foto,
|
||||
annuncio.Url_video, annuncio.Codice)
|
||||
|
||||
}
|
||||
}
|
||||
results := h.Dbpool.SendBatch(h.Ctx, batch).Close()
|
||||
fmt.Println(results)
|
||||
|
||||
}
|
||||
|
||||
func contains(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *DbHandler) ClearRecords() {
|
||||
|
||||
//SET ALL WEB TO FALSE
|
||||
_, err := h.Dbpool.Exec(h.Ctx, "DELETE FROM public.annunci")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (h *DbHandler) SetFromBkp(annunci AnnunciParsed) {
|
||||
h.ClearRecords()
|
||||
|
||||
batch := &pgx.Batch{}
|
||||
for _, annuncio := range annunci.Annuncio {
|
||||
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,
|
||||
url_immagini,
|
||||
url_video,
|
||||
codice
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44, $45, $46, $47)
|
||||
`, 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.Url_foto,
|
||||
annuncio.Url_video, annuncio.Codice)
|
||||
|
||||
}
|
||||
results := h.Dbpool.SendBatch(h.Ctx, batch).Close()
|
||||
fmt.Println(results)
|
||||
|
||||
}
|
||||
146
apps/backend/dbhelpers/dbhelpers.go
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
package dbhelpers
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
102
apps/backend/docker-compose.yml
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
services:
|
||||
minio:
|
||||
image: minio/minio
|
||||
expose:
|
||||
- "9000"
|
||||
- "9001"
|
||||
volumes:
|
||||
- minio_storage:/data
|
||||
networks:
|
||||
- dokploy-network
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
command: server --console-address ":9001" /data
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "-f", "http://minio:9000/minio/health/live" ]
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
|
||||
tiles:
|
||||
image: eqalpha/keydb:latest
|
||||
volumes:
|
||||
- tiles-data:/data
|
||||
networks:
|
||||
- dokploy-network
|
||||
expose:
|
||||
- "6379"
|
||||
command: keydb-server /etc/keydb/keydb.conf --maxmemory 100mb --maxmemory-policy volatile-lru --maxmemory-samples 5
|
||||
healthcheck:
|
||||
test: ["CMD", "keydb-cli", "-h", "localhost", "ping"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.5"
|
||||
sysctls:
|
||||
vm.overcommit_memory: "1"
|
||||
|
||||
keydb:
|
||||
image: eqalpha/keydb:latest
|
||||
volumes:
|
||||
- keydb-data:/data
|
||||
networks:
|
||||
- dokploy-network
|
||||
expose:
|
||||
- "6379"
|
||||
healthcheck:
|
||||
test: ["CMD", "keydb-cli", "-h", "localhost", "ping"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.5"
|
||||
sysctls:
|
||||
vm.overcommit_memory: "1"
|
||||
|
||||
backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
networks:
|
||||
- dokploy-network
|
||||
expose:
|
||||
- 1323
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- images_volume:/app/images
|
||||
- storage_volume:/app/storage
|
||||
environment:
|
||||
STORAGE_STRATEGY: "minio"
|
||||
MINIO_URL: minio:9000
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
PGHOST: db
|
||||
PGPORT: "5432"
|
||||
IMAGEOPTION: "true"
|
||||
CONCURRENT_IMAGES: ${CONCURRENT_IMAGES}
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
minio_storage:
|
||||
images_volume:
|
||||
storage_volume:
|
||||
tiles-data:
|
||||
keydb-data:
|
||||
|
||||
|
||||
networks:
|
||||
dokploy-network:
|
||||
external: true
|
||||
BIN
apps/backend/fallback.webp
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
266
apps/backend/from_bkp.go
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func From_Backup() XmlBkp {
|
||||
//open the bkp.xml file
|
||||
xmlFile, err := os.Open("bkp.xml")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
defer xmlFile.Close()
|
||||
|
||||
byteValue, err := io.ReadAll(xmlFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
var miogest XmlBkp
|
||||
xml.Unmarshal(byteValue, &miogest)
|
||||
return miogest
|
||||
|
||||
}
|
||||
|
||||
func ParseUpdateBkp() AnnunciParsed {
|
||||
|
||||
miogest := From_Backup()
|
||||
|
||||
var AnnunciArray AnnunciParsed
|
||||
extractedData := make(chan AnnuncioParsed)
|
||||
defaultCaratteristiche := Miogest.GetCaratteristiche()
|
||||
defaultCategorie := Miogest.GetCategorie()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(miogest.Annunci.Record))
|
||||
|
||||
for i := 0; i < len(miogest.Annunci.Record); i++ {
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
extractData_bkp(miogest.Annunci.Record[i], extractedData, defaultCaratteristiche, defaultCategorie)
|
||||
}(i)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(extractedData)
|
||||
}()
|
||||
|
||||
for result := range extractedData {
|
||||
AnnunciArray.Annuncio = append(AnnunciArray.Annuncio, result)
|
||||
}
|
||||
|
||||
return AnnunciArray
|
||||
}
|
||||
|
||||
func extractData_bkp(annuncio AnnuncioBKP, extractedData chan AnnuncioParsed, defaultCaratteristiche ListaCaratteristiche, defaultCategorie []Categoria) {
|
||||
|
||||
var tmp AnnuncioParsed = AnnuncioParsed{}
|
||||
|
||||
codice := RemoveWhitespace(GetStringValue(annuncio.Codice))
|
||||
tmp.Codice = codice
|
||||
tmp.Email = nil
|
||||
if annuncio.Proprietario != nil && annuncio.Proprietario.Cliente != nil && annuncio.Proprietario.Cliente.Nome != nil {
|
||||
tmp.Locatore = annuncio.Proprietario.Cliente.Nome
|
||||
}
|
||||
tmp.Numero = nil
|
||||
if annuncio.Proprietario != nil && annuncio.Proprietario.Cliente != nil && annuncio.Proprietario.Cliente.Nome != nil {
|
||||
tmp.Idlocatore = annuncio.Proprietario.Cliente.ID
|
||||
}
|
||||
|
||||
upperIndirizzo := MakeUppercase(GetStringValue(annuncio.Indirizzo))
|
||||
tmp.Indirizzo = &upperIndirizzo
|
||||
tmp.Civico = annuncio.Civico
|
||||
upperComune := MakeUppercase(GetStringValue(annuncio.Comune))
|
||||
tmp.Comune = &upperComune
|
||||
tmp.Cap = annuncio.Cap
|
||||
tmp.Provincia = annuncio.Provincia
|
||||
tmp.Regione = annuncio.Regione
|
||||
tmp.Lat = CommaToDot(annuncio.Lat)
|
||||
tmp.Lon = CommaToDot(annuncio.Lon)
|
||||
tmp.Indirizzo_secondario = nil
|
||||
tmp.Civico_secondario = nil
|
||||
tmp.Lat_secondario = nil
|
||||
tmp.Lon_secondario = nil
|
||||
|
||||
var tipo = tipologiaParser(*annuncio.Tipo)
|
||||
tmp.Tipo = &tipo
|
||||
|
||||
tmpanno := GetStringValue(annuncio.Anno)
|
||||
if tmpanno != "0" && tmpanno != "" {
|
||||
tmp.Anno = &tmpanno
|
||||
}
|
||||
tmp.Prezzo = prezzoParse(annuncio.Prezzo)
|
||||
tmp.Consegna = parseConsegna(annuncio.Consegna)
|
||||
tmp.Classe = annuncio.Classe
|
||||
|
||||
if annuncio.Categorie != nil && annuncio.Categorie.Categoria != nil {
|
||||
resultArray := make([]string, 0)
|
||||
cats := *annuncio.Categorie.Categoria
|
||||
for _, cat := range cats {
|
||||
for _, catdef := range defaultCategorie {
|
||||
if cat.ID == catdef.Id {
|
||||
resultArray = append(resultArray, catdef.Nome)
|
||||
}
|
||||
}
|
||||
}
|
||||
if tmp.Categorie == nil {
|
||||
tmp.Categorie = &[]string{}
|
||||
}
|
||||
*tmp.Categorie = resultArray
|
||||
}
|
||||
|
||||
tmp.Mq = CommaToDot(annuncio.Mq)
|
||||
tmp.Piano = annuncio.Piano
|
||||
tmp.Piano_palazzo = annuncio.PianoPalazzo
|
||||
tmp.Unita_condominio = annuncio.UnitaImmobiliari
|
||||
tmp.Numero_vani = annuncio.NumeroVani
|
||||
tmp.Numero_camere = annuncio.NumeroCamere
|
||||
tmp.Numero_bagni = annuncio.NumeroBagni
|
||||
tmp.Numero_balconi = annuncio.NumeroBalconi
|
||||
tmp.Numero_terrazzi = annuncio.NumeroTerrazzi
|
||||
tmp.Numero_box = annuncio.NumeroBox
|
||||
tmp.Numero_postiauto = annuncio.NumeroPostiauto
|
||||
tmpstato := GetStringValue(annuncio.Stato)
|
||||
var tmpstatoString string
|
||||
if tmpstato == "1" {
|
||||
tmpstatoString = "Attivo"
|
||||
}
|
||||
if tmpstato == "2" {
|
||||
tmpstatoString = "Trattativa"
|
||||
} else {
|
||||
tmpstatoString = "Sospeso"
|
||||
}
|
||||
tmp.Stato = &tmpstatoString
|
||||
home := false
|
||||
tmp.Homepage = &home
|
||||
|
||||
layout := "20060102150405"
|
||||
if annuncio.Inserito != nil {
|
||||
t, err := time.Parse(layout, *annuncio.Inserito)
|
||||
if err != nil {
|
||||
tmp.Creato_il = nil
|
||||
} else {
|
||||
tmp.Creato_il = &t
|
||||
}
|
||||
}
|
||||
|
||||
tmp.Modificato_il = nil
|
||||
|
||||
var itlingua string = "it"
|
||||
var enlingua string = "en"
|
||||
var tmpTitoloIt *string = nil
|
||||
var tmpTitoloEn *string = nil
|
||||
var tmpDescIt *string = nil
|
||||
var tmpDescEn *string = nil
|
||||
|
||||
if annuncio.Descrizioni != nil {
|
||||
for _, desc := range *annuncio.Descrizioni.Descrizione {
|
||||
|
||||
if desc.Lingua != nil {
|
||||
if *desc.Lingua == itlingua {
|
||||
tmpTitoloIt = desc.Titolo
|
||||
tmpDescIt = desc.Testo
|
||||
}
|
||||
if *desc.Lingua == enlingua {
|
||||
tmpTitoloEn = desc.Titolo
|
||||
tmpDescEn = desc.Testo
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
tmp.Titolo_it = tmpTitoloIt
|
||||
tmp.Titolo_en = tmpTitoloEn
|
||||
tmp.Desc_it = tmpDescIt
|
||||
tmp.Desc_en = tmpDescEn
|
||||
w := false
|
||||
tmp.Web = &w
|
||||
|
||||
tmp.Url_video = nil
|
||||
|
||||
if annuncio.Accessori != nil && annuncio.Accessori.Accessorio != nil {
|
||||
tmpAcc := make([]string, 0)
|
||||
for _, acc := range *annuncio.Accessori.Accessorio {
|
||||
if acc.Nome != nil {
|
||||
tmpAcc = append(tmpAcc, *acc.Nome)
|
||||
}
|
||||
}
|
||||
if tmp.Accessori == nil {
|
||||
tmp.Accessori = &[]string{}
|
||||
}
|
||||
*tmp.Accessori = tmpAcc
|
||||
}
|
||||
|
||||
tmpCaratteristiche := make(map[string]interface{})
|
||||
|
||||
if annuncio.Schede != nil && annuncio.Schede.Scheda != nil {
|
||||
for _, scheda := range *annuncio.Schede.Scheda {
|
||||
if scheda.Valore != nil {
|
||||
var cossriponding_name *string = nil
|
||||
for _, def := range defaultCaratteristiche.Caratteristiche {
|
||||
if scheda.ID == def.Id {
|
||||
cossriponding_name = &def.Tagxml
|
||||
}
|
||||
}
|
||||
if cossriponding_name != nil {
|
||||
tmpCaratteristiche[*cossriponding_name] = *scheda.Valore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, def := range defaultCaratteristiche.Caratteristiche {
|
||||
|
||||
if _, ok := tmpCaratteristiche[def.Tagxml]; !ok {
|
||||
tmpCaratteristiche[def.Tagxml] = nil
|
||||
}
|
||||
|
||||
}
|
||||
tmp.Caratteristiche = &tmpCaratteristiche
|
||||
|
||||
tmp.Url_foto = nil
|
||||
tmp.Url_video = nil
|
||||
|
||||
extractedData <- tmp
|
||||
|
||||
}
|
||||
|
||||
func TestTipologieBkp() {
|
||||
|
||||
bkp := From_Backup()
|
||||
var tipologie []string
|
||||
for _, r := range bkp.Annunci.Record {
|
||||
if r.Tipo != nil {
|
||||
if !contains(tipologie, *r.Tipo) {
|
||||
tipologie = append(tipologie, *r.Tipo)
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println(strings.Join(tipologie, ","))
|
||||
|
||||
}
|
||||
|
||||
func tipologiaParser(tipo string) string {
|
||||
var result string
|
||||
switch tipo {
|
||||
case "Affitto Residenziale", "Affitto Turistico":
|
||||
result = "Transitorio"
|
||||
case "Vendita Residenziale", "Vendita Commerciale":
|
||||
result = "Vendita"
|
||||
case "Affitto Commerciale", "Affitto":
|
||||
result = "Stabile"
|
||||
case "Cessione Commerciale", "Cessione":
|
||||
result = "Cessione"
|
||||
default:
|
||||
result = "Errore"
|
||||
}
|
||||
return result
|
||||
}
|
||||
55
apps/backend/go.mod
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
module backend
|
||||
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/georgysavva/scany/v2 v2.1.3
|
||||
github.com/jackc/pgx/v5 v5.5.5
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/labstack/echo/v4 v4.11.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/frankban/quicktest v1.14.6 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/gofrs/uuid/v5 v5.0.0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/mholt/archiver v3.1.1+incompatible // indirect
|
||||
github.com/minio/crc64nvme v1.0.0 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/nickalie/go-binwrapper v0.0.0-20190114141239-525121d43c84 // indirect
|
||||
github.com/nwaples/rardecode v1.1.3 // indirect
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
golang.org/x/image v0.14.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/jackc/pgx-gofrs-uuid v0.0.0-20230224015001-1d428863c2e2
|
||||
github.com/labstack/gommon v0.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/minio/minio-go/v7 v7.0.86
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
github.com/nickalie/go-webpbin v0.0.0-20220110095747-f10016bf2dc1
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.33.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
)
|
||||
137
apps/backend/go.sum
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
github.com/cockroachdb/cockroach-go/v2 v2.2.0 h1:/5znzg5n373N/3ESjHF5SMLxiW4RKB05Ql//KWfeTFs=
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.0/go.mod h1:u3MiKYGupPPjkn3ozknpMUpxPaNLTFWAya419/zv6eI=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/georgysavva/scany/v2 v2.1.3 h1:Zd4zm/ej79Den7tBSU2kaTDPAH64suq4qlQdhiBeGds=
|
||||
github.com/georgysavva/scany/v2 v2.1.3/go.mod h1:fqp9yHZzM/PFVa3/rYEC57VmDx+KDch0LoqrJzkvtos=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
||||
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx-gofrs-uuid v0.0.0-20230224015001-1d428863c2e2 h1:QWdhlQz98hUe1xmjADOl2mr8ERLrOqj0KWLdkrnNsRQ=
|
||||
github.com/jackc/pgx-gofrs-uuid v0.0.0-20230224015001-1d428863c2e2/go.mod h1:Ti7pyNDU/UpXKmBTeFgxTvzYDM9xHLiYKMsLdt4b9cg=
|
||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/labstack/echo/v4 v4.11.2 h1:T+cTLQxWCDfqDEoydYm5kCobjmHwOwcv4OJAPHilmdE=
|
||||
github.com/labstack/echo/v4 v4.11.2/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws=
|
||||
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
|
||||
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
|
||||
github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
|
||||
github.com/minio/crc64nvme v1.0.0 h1:MeLcBkCTD4pAoU7TciAfwsfxgkhM2u5hCe48hSEVFr0=
|
||||
github.com/minio/crc64nvme v1.0.0/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.86 h1:DcgQ0AUjLJzRH6y/HrxiZ8CXarA70PAIufXHodP4s+k=
|
||||
github.com/minio/minio-go/v7 v7.0.86/go.mod h1:VbfO4hYwUu3Of9WqGLBZ8vl3Hxnxo4ngxK4hzQDf4x4=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/nickalie/go-binwrapper v0.0.0-20190114141239-525121d43c84 h1:/6MoQlTdk1eAi0J9O89ypO8umkp+H7mpnSF2ggSL62Q=
|
||||
github.com/nickalie/go-binwrapper v0.0.0-20190114141239-525121d43c84/go.mod h1:Eeech2fhQ/E4bS8cdc3+SGABQ+weQYGyWBvZ/mNr5uY=
|
||||
github.com/nickalie/go-webpbin v0.0.0-20220110095747-f10016bf2dc1 h1:9awJsNP+gYOGCr3pQu9i217bCNsVwoQCmD3h7CYwxOw=
|
||||
github.com/nickalie/go-webpbin v0.0.0-20220110095747-f10016bf2dc1/go.mod h1:m5oz0fmp+uyRBxxFkvciIpe1wd2JZ3pDVJ3x/D8/EGw=
|
||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
|
||||
github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
|
||||
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
127
apps/backend/image_handler.go
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/georgysavva/scany/v2/pgxscan"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
type ImageRefManager struct {
|
||||
}
|
||||
|
||||
func NewImageRefManager() *ImageRefManager {
|
||||
return &ImageRefManager{}
|
||||
}
|
||||
|
||||
func (i *ImageRefManager) GetImagesRef() ([]MiogestImagesRef_Table, error) {
|
||||
|
||||
var images []MiogestImagesRef_Table
|
||||
|
||||
err := pgxscan.Select(Db.Ctx, Db.Dbpool, &images, "SELECT * FROM public.miogest_images_ref")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return images, nil
|
||||
}
|
||||
|
||||
func (i *ImageRefManager) InsertImagesRef(annunci *AnnunciXML) error {
|
||||
|
||||
batch := &pgx.Batch{}
|
||||
|
||||
for _, annuncio := range annunci.Annuncio {
|
||||
batch.Queue(`
|
||||
INSERT INTO public.miogest_images_ref (codice, foto)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT (codice) DO UPDATE SET foto = $2
|
||||
`, &annuncio.Codice, &annuncio.Foto)
|
||||
}
|
||||
|
||||
if err := Db.Dbpool.SendBatch(Db.Ctx, batch).Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ImageRefManager) FindIsUpdate(annunci *AnnunciXML) ([]string, error) {
|
||||
r, err := i.GetImagesRef()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refMap := make(map[string][]string) // Convert slice to map for faster lookup
|
||||
for _, ref := range r {
|
||||
if ref.Foto != nil {
|
||||
refMap[ref.Codice] = *ref.Foto
|
||||
}
|
||||
}
|
||||
|
||||
var result []string
|
||||
for _, annuncio := range annunci.Annuncio {
|
||||
if annuncio.Codice != nil && annuncio.Foto != nil {
|
||||
refFoto, exists := refMap[*annuncio.Codice]
|
||||
if !exists {
|
||||
fmt.Println("new codice", *annuncio.Codice)
|
||||
result = append(result, *annuncio.Codice)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(*annuncio.Foto) != len(refFoto) {
|
||||
fmt.Println("Different length", *annuncio.Foto, refFoto)
|
||||
result = append(result, *annuncio.Codice)
|
||||
continue
|
||||
}
|
||||
|
||||
for i, foto := range *annuncio.Foto {
|
||||
if foto != refFoto[i] {
|
||||
fmt.Println("Different content", *annuncio.Foto, refFoto)
|
||||
result = append(result, *annuncio.Codice)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (i *ImageRefManager) ClearImages(to_clear []string) error {
|
||||
|
||||
for _, codice := range to_clear {
|
||||
|
||||
folder := filepath.Join(imgbasepath, codice)
|
||||
err := os.RemoveAll(folder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ImageRefManager) Reset() error {
|
||||
|
||||
_, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.miogest_images_ref")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = Db.Dbpool.Exec(Db.Ctx, "UPDATE public.annunci SET url_immagini = NULL, url_video = NULL")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.RemoveAll(imgbasepath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = os.Mkdir(imgbasepath, 0755)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
206
apps/backend/images.go
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/nfnt/resize"
|
||||
"github.com/nickalie/go-webpbin"
|
||||
)
|
||||
|
||||
func Conversion(input string) error {
|
||||
var width uint = 1920
|
||||
output := filepath.Base(input)
|
||||
outputdir := filepath.Dir(input)
|
||||
ext := filepath.Ext(output)
|
||||
output = output[0 : len(output)-len(ext)] // Remove file extension // Add .webp extension
|
||||
log.Println("Converting", input, "to", output+".webp")
|
||||
f, err := os.Open(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
var img image.Image
|
||||
|
||||
switch ext {
|
||||
case ".webp":
|
||||
log.Println("Image already in WebP format")
|
||||
img, err = webpbin.Decode(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case ".png", ".PNG":
|
||||
// Convert from PNG
|
||||
img, err = png.Decode(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case ".jpeg", ".jpg", ".JPG", ".JPEG":
|
||||
// Convert from JPEG
|
||||
img, err = jpeg.Decode(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("error: Unsupported file type: %s", ext)
|
||||
}
|
||||
|
||||
err = image_creation(img, width, output, outputdir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = thumbnail_creation(img, output, outputdir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Conversion completed successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func image_creation(img image.Image, width uint, output string, outputdir string) error {
|
||||
// Resize the image
|
||||
resizedImg := resize.Resize(width, 0, img, resize.Lanczos3)
|
||||
|
||||
// Create the output file
|
||||
outputPath := fmt.Sprintf("%s/%s.webp", outputdir, output)
|
||||
log.Println("Creating", outputPath)
|
||||
out, err := os.Create(outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Encode the resized image to the output file
|
||||
err = webpbin.Encode(out, resizedImg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func thumbnail_creation(img image.Image, output string, outputdir string) error {
|
||||
// Resize the image
|
||||
resizedImg := resize.Resize(32, 0, img, resize.Lanczos3)
|
||||
|
||||
// Create the output file
|
||||
outputPath := fmt.Sprintf("%s/thumbnail-%s.webp", outputdir, output)
|
||||
log.Println("Creating", outputPath)
|
||||
out, err := os.Create(outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Encode the resized image to the output file
|
||||
err = webpbin.Encode(out, resizedImg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ImageRoutes(e *echo.Echo) {
|
||||
|
||||
e.GET("/initcwebp", func(c echo.Context) error {
|
||||
Conversion("test.jpg")
|
||||
return c.String(http.StatusOK, "Cwebp initialized")
|
||||
})
|
||||
|
||||
e.GET("/images/list", func(c echo.Context) error {
|
||||
annunci := Db.GetActiveAnnunci()
|
||||
return c.JSON(http.StatusOK, annunci)
|
||||
})
|
||||
|
||||
e.GET("/images/get/:cod/:imgNum", func(c echo.Context) error {
|
||||
cod := c.Param("cod")
|
||||
imgNum := c.Param("imgNum")
|
||||
thumb := c.QueryParam("thumbnail") == "true"
|
||||
widthStr := c.QueryParam("w")
|
||||
//quality := c.QueryParam("q")
|
||||
fmt.Printf("request for %s/%s, thumbnail=%t, w=%s\n", cod, imgNum, thumb, widthStr)
|
||||
prefix := ""
|
||||
if thumb {
|
||||
prefix = "thumbnail-"
|
||||
}
|
||||
|
||||
Path := filepath.Join(imgbasepath, cod, imgNum, fmt.Sprintf("%s%s_%s.webp", prefix, cod, imgNum))
|
||||
fmt.Println(Path)
|
||||
if _, err := os.Stat(Path); os.IsNotExist(err) {
|
||||
|
||||
fmt.Println("Image does not exist:", Path)
|
||||
// Serve a fallback image if the requested image is not found
|
||||
|
||||
return FallbackImage(c)
|
||||
}
|
||||
if widthStr != "" {
|
||||
width, err := strconv.Atoi(widthStr)
|
||||
if err != nil {
|
||||
fmt.Println("Invalid width parameter:", widthStr)
|
||||
|
||||
return FallbackImage(c)
|
||||
}
|
||||
f, err := os.Open(Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
img, err := webpbin.Decode(f)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to decode image:", err)
|
||||
return FallbackImage(c)
|
||||
}
|
||||
resizedImg := resize.Resize(uint(width), 0, img, resize.Lanczos3)
|
||||
buffer := new(bytes.Buffer)
|
||||
err = webpbin.Encode(buffer, resizedImg)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to encode image:", err)
|
||||
return FallbackImage(c)
|
||||
}
|
||||
return c.Blob(http.StatusOK, "image/webp", buffer.Bytes())
|
||||
}
|
||||
|
||||
return c.File(Path)
|
||||
|
||||
})
|
||||
|
||||
e.GET("/images/resetimages", func(c echo.Context) error {
|
||||
|
||||
err := ImageManager.Reset()
|
||||
if err != nil {
|
||||
return c.String(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.String(http.StatusOK, "OK")
|
||||
})
|
||||
|
||||
e.POST("/image-option-toggle", func(c echo.Context) error {
|
||||
// Toggle the imageOption value
|
||||
imageOption = !imageOption
|
||||
|
||||
// Convert the boolean value to a string and set the environment variable
|
||||
os.Setenv("IMAGEOPTION", strconv.FormatBool(imageOption))
|
||||
|
||||
return c.String(http.StatusOK, "OK")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
516
apps/backend/miogest_handler.go
Normal file
|
|
@ -0,0 +1,516 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MiogestHandler struct {
|
||||
Caratterisiche ListaCaratteristiche
|
||||
Categorie []Categoria
|
||||
AnnunciXML AnnunciXML
|
||||
AnnunciParsed AnnunciParsed
|
||||
annunci_cache_expires time.Time
|
||||
vars_cache_expires time.Time
|
||||
}
|
||||
|
||||
func NewMiogestHandler() *MiogestHandler {
|
||||
return &MiogestHandler{
|
||||
Caratterisiche: ListaCaratteristiche{},
|
||||
Categorie: nil,
|
||||
AnnunciXML: AnnunciXML{},
|
||||
AnnunciParsed: AnnunciParsed{},
|
||||
annunci_cache_expires: time.Now(),
|
||||
vars_cache_expires: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// ANNUNCI FROM MIOGEST
|
||||
func (m *MiogestHandler) GetAnnunci() AnnunciXML {
|
||||
if len(m.AnnunciXML.Annuncio) == 0 || time.Now().After(m.annunci_cache_expires) {
|
||||
m.GetAnnunciFromMiogest()
|
||||
}
|
||||
return m.AnnunciXML
|
||||
}
|
||||
|
||||
func (m *MiogestHandler) GetAnnunciFromMiogest() {
|
||||
m.AnnunciXML = GetDataXML[AnnunciXML]("http://partner.miogest.com/agenzie/infoalloggi.xml")
|
||||
m.annunci_cache_expires = time.Now().Add(1 * time.Hour)
|
||||
}
|
||||
|
||||
// CATEGORIE AND CARATTERISTICHE FROM MIOGEST
|
||||
func (m *MiogestHandler) InitDefaults() {
|
||||
m.GenerateCaratteristiche()
|
||||
m.GenerateCategorie()
|
||||
}
|
||||
|
||||
func (m *MiogestHandler) GetCaratteristiche() ListaCaratteristiche {
|
||||
if len(m.Caratterisiche.Caratteristiche) == 0 || time.Now().After(m.vars_cache_expires) {
|
||||
m.GenerateCaratteristiche()
|
||||
}
|
||||
return m.Caratterisiche
|
||||
}
|
||||
|
||||
func (m *MiogestHandler) GetCategorie() []Categoria {
|
||||
if len(m.Categorie) == 0 || time.Now().After(m.vars_cache_expires) {
|
||||
m.GenerateCategorie()
|
||||
}
|
||||
return m.Categorie
|
||||
}
|
||||
|
||||
func (m *MiogestHandler) GenerateCaratteristiche() {
|
||||
var result = GetDataXML[DefaultCaratteristiche]("https://www.miogest.com/apps/revo.aspx?tipo=schede")
|
||||
var array ListaCaratteristiche
|
||||
for _, tag := range result.Scheda {
|
||||
var c = Caratteristica{Id: tag.Id, Tagxml: tag.Tagxml}
|
||||
array.Caratteristiche = append(array.Caratteristiche, c)
|
||||
}
|
||||
m.Caratterisiche = array
|
||||
m.vars_cache_expires = time.Now().Add(1 * time.Hour)
|
||||
}
|
||||
|
||||
func (m *MiogestHandler) GenerateCategorie() {
|
||||
var result = GetDataXML[DefaultCategories]("https://www.miogest.com/apps/revo.aspx?tipo=categorie")
|
||||
var listaCategorie []Categoria
|
||||
for _, elem := range result.Cat {
|
||||
var c = Categoria{Id: elem.ID, Nome: elem.Nome}
|
||||
listaCategorie = append(listaCategorie, c)
|
||||
}
|
||||
m.Categorie = listaCategorie
|
||||
m.vars_cache_expires = time.Now().Add(1 * time.Hour)
|
||||
}
|
||||
|
||||
// ANNUNCI PARSED
|
||||
func (m *MiogestHandler) GetAnnunciParsed() AnnunciParsed {
|
||||
m.ParseAnnunci()
|
||||
return m.AnnunciParsed
|
||||
}
|
||||
|
||||
func (m *MiogestHandler) ParseAnnunci() {
|
||||
annunci := m.GetAnnunci()
|
||||
cod_image_to_update := []string{}
|
||||
if imageOption {
|
||||
var err error
|
||||
cod_image_to_update, err = ImageManager.FindIsUpdate(&annunci)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to find images to update: %v", err.Error())
|
||||
}
|
||||
if err := ImageManager.ClearImages(cod_image_to_update); err != nil {
|
||||
log.Fatalf("Failed to clear images: %v", err.Error())
|
||||
}
|
||||
ImageManager.InsertImagesRef(&annunci)
|
||||
}
|
||||
|
||||
var AnnunciArray AnnunciParsed
|
||||
AnnunciArray.Annuncio = make([]AnnuncioParsed, 0, len(annunci.Annuncio))
|
||||
Miogest.InitDefaults()
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(annunci.Annuncio))
|
||||
sem := make(chan struct{}, concurrentImages) //Limit concurrent goroutines
|
||||
lista_codici := []string{}
|
||||
for i := range annunci.Annuncio {
|
||||
|
||||
lista_codici = append(lista_codici, RemoveWhitespace(*annunci.Annuncio[i].Codice))
|
||||
sem <- struct{}{}
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
defer func() { <-sem }()
|
||||
result := extractData_update(&annunci.Annuncio[i], contains(cod_image_to_update, *annunci.Annuncio[i].Codice))
|
||||
AnnunciArray.Annuncio = append(AnnunciArray.Annuncio, result)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
if imageOption {
|
||||
for _, codice := range lista_codici {
|
||||
err := folderCleanup(codice)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to cleanup folder: %v", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
m.AnnunciParsed = AnnunciArray
|
||||
}
|
||||
|
||||
var re = regexp.MustCompile(`[^+\d]`)
|
||||
|
||||
func boolUpd(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
func strUpd(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func extractData_update(annuncio *AnnuncioXML, updateImages bool) AnnuncioParsed {
|
||||
var tmp AnnuncioParsed = AnnuncioParsed{}
|
||||
tmp.Codice = RemoveWhitespace(GetStringValue(annuncio.Codice))
|
||||
processLocatore(&tmp, annuncio)
|
||||
processIndirizzo(&tmp, annuncio)
|
||||
processTipologia(&tmp, annuncio)
|
||||
tmp.Consegna = parseConsegna(annuncio.Consegna)
|
||||
processCategorie(&tmp, annuncio)
|
||||
processDatiImmobile(&tmp, annuncio)
|
||||
tmp.Stato = annuncio.Stato
|
||||
|
||||
tmp.Homepage = boolUpd(false)
|
||||
if annuncio.HomePage != nil {
|
||||
if *annuncio.HomePage == "si" {
|
||||
|
||||
tmp.Homepage = boolUpd(true)
|
||||
}
|
||||
}
|
||||
layout := "20060102150405"
|
||||
if annuncio.Creato != nil {
|
||||
t, err := time.Parse(layout, *annuncio.Creato)
|
||||
if err != nil {
|
||||
tmp.Creato_il = nil
|
||||
} else {
|
||||
tmp.Creato_il = &t
|
||||
}
|
||||
}
|
||||
if annuncio.Modifica != nil {
|
||||
t, err := time.Parse(layout, *annuncio.Modifica)
|
||||
if err != nil {
|
||||
tmp.Modificato_il = nil
|
||||
} else {
|
||||
tmp.Modificato_il = &t
|
||||
}
|
||||
}
|
||||
tmp.Titolo_it = annuncio.Titolo
|
||||
tmp.Titolo_en = annuncio.TitoloEn
|
||||
tmp.Desc_it = annuncio.Descrizione
|
||||
tmp.Desc_en = annuncio.DescrizioneEn
|
||||
|
||||
tmp.Web = boolUpd(true)
|
||||
|
||||
videos := []string{}
|
||||
if annuncio.Video != nil {
|
||||
videos = *annuncio.Video
|
||||
}
|
||||
miogestVideos := []string{}
|
||||
if annuncio.AMMedias != nil {
|
||||
for _, media := range *annuncio.AMMedias {
|
||||
if media.AMVideo != nil && media.AMVideo.Url != nil {
|
||||
miogestVideos = append(miogestVideos, *media.AMVideo.Url)
|
||||
}
|
||||
}
|
||||
}
|
||||
concatenatedVideos := slices.Concat(videos, miogestVideos)
|
||||
tmp.Url_video = &concatenatedVideos
|
||||
|
||||
if annuncio.Accessori != nil {
|
||||
tmp.Accessori = annuncio.Accessori
|
||||
}
|
||||
tmpcaratt := parseCaratteristiche(annuncio)
|
||||
tmp.Caratteristiche = &tmpcaratt
|
||||
if updateImages {
|
||||
processImages(&tmp, annuncio)
|
||||
}
|
||||
folders := GetPathDirsArray(filepath.Join(imgbasepath, tmp.Codice))
|
||||
for _, folder := range folders {
|
||||
folderpath := filepath.Join(filepath.Join(imgbasepath, tmp.Codice), folder)
|
||||
files, err := os.ReadDir(folderpath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, file := range files {
|
||||
if strings.Contains(file.Name(), "webp") && !strings.HasPrefix(file.Name(), "thumbnail") {
|
||||
tmpImgUrl := tmp.Codice + "/" + folder
|
||||
if tmp.Url_foto == nil {
|
||||
tmp.Url_foto = &[]string{}
|
||||
}
|
||||
*tmp.Url_foto = append(*tmp.Url_foto, tmpImgUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
|
||||
func processLocatore(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
|
||||
if annuncio.Clienti == nil || annuncio.Clienti.Cliente == nil {
|
||||
return
|
||||
}
|
||||
tmp.Email = annuncio.Clienti.Cliente.Email1
|
||||
|
||||
var number string
|
||||
for _, tel := range []*string{annuncio.Clienti.Cliente.Tel1, annuncio.Clienti.Cliente.Tel2, annuncio.Clienti.Cliente.Tel3} {
|
||||
if tel != nil {
|
||||
cleanedTel := re.ReplaceAllString(*tel, "")
|
||||
if cleanedTel != "" {
|
||||
number = cleanedTel
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
tmp.Numero = &number
|
||||
if annuncio.Clienti.Cliente.Cognome != nil && annuncio.Clienti.Cliente.Nome != nil {
|
||||
tmp.Locatore = strUpd(fmt.Sprintf("%s %s", MakeUppercase(*annuncio.Clienti.Cliente.Cognome), MakeUppercase(*annuncio.Clienti.Cliente.Nome)))
|
||||
}
|
||||
tmp.Idlocatore = annuncio.Clienti.Cliente.Id
|
||||
}
|
||||
|
||||
func processIndirizzo(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
|
||||
tmp.Indirizzo = strUpd(MakeUppercase(GetStringValue(annuncio.Indirizzo)))
|
||||
tmp.Civico = annuncio.Civico
|
||||
tmp.Comune = strUpd(MakeUppercase(GetStringValue(annuncio.Comune)))
|
||||
tmp.Cap = annuncio.Cap
|
||||
tmp.Provincia = annuncio.Provincia
|
||||
tmp.Regione = annuncio.Regione
|
||||
tmp.Lat = annuncio.Latitudine
|
||||
tmp.Lon = annuncio.Longitudine
|
||||
tmp.Indirizzo_secondario = strUpd(MakeUppercase(GetStringValue(annuncio.IndirizzoSecondario)))
|
||||
tmp.Civico_secondario = annuncio.CivicoSecondario
|
||||
tmp.Lat_secondario = annuncio.LatitudineSecondario
|
||||
tmp.Lon_secondario = annuncio.LongitudineSecondario
|
||||
}
|
||||
|
||||
func processTipologia(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
|
||||
if annuncio.Tipologia == nil {
|
||||
tmp.Tipo = strUpd("Errore")
|
||||
return
|
||||
}
|
||||
if *annuncio.Tipologia == "V" {
|
||||
tmp.Tipo = strUpd("Vendita")
|
||||
return
|
||||
}
|
||||
if *annuncio.Tipologia == "I" {
|
||||
tmp.Tipo = strUpd("Cessione")
|
||||
return
|
||||
}
|
||||
if *annuncio.Tipologia == "A" {
|
||||
if *annuncio.Tipologia2 == "Residenziale" || *annuncio.Tipologia2 == "Turistico" || *annuncio.Tipologia2 == "Stanza" {
|
||||
tmp.Tipo = strUpd("Transitorio")
|
||||
} else {
|
||||
tmp.Tipo = strUpd("Stabile")
|
||||
}
|
||||
return
|
||||
}
|
||||
tmp.Tipo = strUpd("Errore")
|
||||
|
||||
}
|
||||
|
||||
func processCategorie(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
|
||||
if annuncio.Categoria == nil {
|
||||
return
|
||||
}
|
||||
resultArray := make([]string, 0)
|
||||
cats := *annuncio.Categoria
|
||||
defaultCategorie := Miogest.GetCategorie()
|
||||
for _, cat := range cats {
|
||||
for _, catdef := range defaultCategorie {
|
||||
if cat == catdef.Id {
|
||||
resultArray = append(resultArray, catdef.Nome)
|
||||
}
|
||||
}
|
||||
}
|
||||
if tmp.Categorie == nil {
|
||||
tmp.Categorie = &[]string{}
|
||||
}
|
||||
*tmp.Categorie = resultArray
|
||||
}
|
||||
|
||||
func prezzoParse(prezzo *string) int {
|
||||
zero := 0
|
||||
if prezzo == nil {
|
||||
return zero
|
||||
}
|
||||
f, err := strconv.ParseFloat(strings.TrimSpace(strings.ReplaceAll(*prezzo, ",", ".")), 64)
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse price: %v", err)
|
||||
return zero
|
||||
}
|
||||
prezzoInt := int(f * 100) // Convert to cents
|
||||
return prezzoInt
|
||||
}
|
||||
|
||||
func processDatiImmobile(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
|
||||
tmpanno := GetStringValue(annuncio.Anno)
|
||||
if tmpanno != "0" && tmpanno != "" {
|
||||
tmp.Anno = &tmpanno
|
||||
}
|
||||
|
||||
tmp.Prezzo = prezzoParse(annuncio.Prezzo)
|
||||
tmp.Classe = annuncio.Classe
|
||||
if annuncio.Mq != nil {
|
||||
replaced := strings.ReplaceAll(*annuncio.Mq, ",", "")
|
||||
tmp.Mq = &replaced
|
||||
}
|
||||
tmp.Piano = annuncio.Piano
|
||||
tmp.Piano_palazzo = annuncio.PianiCondominio
|
||||
tmp.Unita_condominio = annuncio.UnitaCondominio
|
||||
tmp.Numero_vani = annuncio.Vani
|
||||
tmp.Numero_camere = annuncio.Camere
|
||||
tmp.Numero_bagni = annuncio.Bagni
|
||||
tmp.Numero_balconi = annuncio.Balconi
|
||||
tmp.Numero_terrazzi = annuncio.Terrazzi
|
||||
tmp.Numero_box = annuncio.Box
|
||||
tmp.Numero_postiauto = annuncio.PostiAuto
|
||||
}
|
||||
|
||||
func processImages(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
|
||||
path := filepath.Join(imgbasepath, tmp.Codice)
|
||||
err := os.MkdirAll(path, 0755)
|
||||
if err != nil {
|
||||
log.Printf("Failed to create directory: %v", err)
|
||||
panic(err)
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
sem := make(chan struct{}, concurrentImages) // Limit concurrent goroutines
|
||||
nfoto := 0
|
||||
var fotos []string
|
||||
if annuncio.Foto != nil {
|
||||
fotos = *annuncio.Foto
|
||||
}
|
||||
for _, url := range fotos {
|
||||
fmt.Println("Processing image", url)
|
||||
imgpath := filepath.Join(path, fmt.Sprintf("%d", nfoto))
|
||||
if _, err := os.Stat(imgpath); os.IsNotExist(err) {
|
||||
os.Mkdir(imgpath, 0755)
|
||||
}
|
||||
filename := filepath.Join(imgpath, fmt.Sprintf("%s_%d%s", tmp.Codice, nfoto, filepath.Ext(url)))
|
||||
wg.Add(1)
|
||||
sem <- struct{}{}
|
||||
go func(url, filename string) {
|
||||
defer wg.Done()
|
||||
defer func() { <-sem }()
|
||||
downloadImage(url, filename)
|
||||
}(url, filename)
|
||||
nfoto++
|
||||
}
|
||||
wg.Wait()
|
||||
processWebpImages(path)
|
||||
}
|
||||
|
||||
func downloadImage(url, filename string) {
|
||||
response, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to download image: %v", err)
|
||||
return
|
||||
}
|
||||
defer response.Body.Close()
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create file: %v", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
_, err = io.Copy(file, response.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to save image: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func processWebpImages(path string) {
|
||||
array, err := GetPathsArray(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
sem := make(chan struct{}, concurrentImages) // Limit concurrent goroutines
|
||||
for _, img := range array {
|
||||
wg.Add(1)
|
||||
sem <- struct{}{}
|
||||
go func(img string) {
|
||||
defer wg.Done()
|
||||
defer func() { <-sem }()
|
||||
err := Conversion(img)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}(img)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func folderCleanup(codice string) error {
|
||||
folders := GetPathDirsArray(filepath.Join(imgbasepath, codice))
|
||||
non_webp := []string{}
|
||||
for _, folder := range folders {
|
||||
folderpath := filepath.Join(imgbasepath, codice, folder)
|
||||
files, err := os.ReadDir(folderpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, file := range files {
|
||||
if !strings.Contains(file.Name(), "webp") {
|
||||
non_webp = append(non_webp, filepath.Join(folderpath, file.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, file := range non_webp {
|
||||
err := os.Remove(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseCaratteristiche(wrkrecord *AnnuncioXML) map[string]interface{} {
|
||||
if reflect.TypeOf(wrkrecord).Kind() != reflect.Ptr || reflect.ValueOf(wrkrecord).Elem().Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
tmpCaratteristiche := make(map[string]interface{})
|
||||
val := reflect.ValueOf(wrkrecord).Elem()
|
||||
numField := val.NumField()
|
||||
for i := 0; i < numField; i++ {
|
||||
if strings.HasPrefix(val.Type().Field(i).Name, "Scheda_") {
|
||||
nameField := val.Type().Field(i).Name
|
||||
valueField := val.Field(i)
|
||||
if valueField.IsValid() && !valueField.IsNil() {
|
||||
tmpCaratteristiche[nameField] = valueField.Interface()
|
||||
} else {
|
||||
tmpCaratteristiche[nameField] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, scheda := range Miogest.GetCaratteristiche().Caratteristiche {
|
||||
if _, ok := tmpCaratteristiche[scheda.Tagxml]; !ok {
|
||||
tmpCaratteristiche[scheda.Tagxml] = nil
|
||||
}
|
||||
}
|
||||
return tmpCaratteristiche
|
||||
}
|
||||
|
||||
var mesiMapping = map[int]string{
|
||||
1: "Gennaio",
|
||||
2: "Febbraio",
|
||||
3: "Marzo",
|
||||
4: "Aprile",
|
||||
5: "Maggio",
|
||||
6: "Giugno",
|
||||
7: "Luglio",
|
||||
8: "Agosto",
|
||||
9: "Settembre",
|
||||
10: "Ottobre",
|
||||
11: "Novembre",
|
||||
12: "Dicembre",
|
||||
}
|
||||
|
||||
func parseConsegna(original *string) *int {
|
||||
currentMonth := int(time.Now().Month())
|
||||
if original == nil {
|
||||
return nil
|
||||
}
|
||||
if *original == "Libero" {
|
||||
return ¤tMonth
|
||||
} else {
|
||||
for k, v := range mesiMapping {
|
||||
if *original == v {
|
||||
return &k
|
||||
}
|
||||
}
|
||||
}
|
||||
return ¤tMonth
|
||||
}
|
||||
365
apps/backend/models.go
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type XmlBkp struct {
|
||||
Annunci struct {
|
||||
Record []AnnuncioBKP `xml:"record"`
|
||||
} `xml:"annunci"`
|
||||
}
|
||||
|
||||
type AnnuncioBKP struct {
|
||||
Codice *string `xml:"codice"`
|
||||
Proprietario *struct {
|
||||
Cliente *struct {
|
||||
ID *string `xml:"id"`
|
||||
Nome *string `xml:"nome"`
|
||||
} `xml:"cliente"`
|
||||
} `xml:"proprietario"`
|
||||
Nazione *string `xml:"nazione"`
|
||||
Regione *string `xml:"regione"`
|
||||
Provincia *string `xml:"provincia"`
|
||||
Comune *string `xml:"comune"`
|
||||
Cap *string `xml:"cap"`
|
||||
Indirizzo *string `xml:"indirizzo"`
|
||||
Civico *string `xml:"civico"`
|
||||
Lat *string `xml:"lat"`
|
||||
Lon *string `xml:"lon"`
|
||||
Tipo *string `xml:"tipo"`
|
||||
Categorie *struct {
|
||||
Categoria *[]struct {
|
||||
ID string `xml:"id"`
|
||||
Nome string `xml:"nome"`
|
||||
} `xml:"categoria"`
|
||||
} `xml:"categorie"`
|
||||
Prezzo *string `xml:"prezzo"`
|
||||
Piani *string `xml:"piani"`
|
||||
Anno *string `xml:"anno"`
|
||||
Consegna *string `xml:"consegna"`
|
||||
Classe *string `xml:"classe"`
|
||||
Mq *string `xml:"mq"`
|
||||
Piano *string `xml:"piano"`
|
||||
PianoPalazzo *string `xml:"piano_palazzo"`
|
||||
UnitaImmobiliari *string `xml:"unita_immobiliari"`
|
||||
NumeroVani *string `xml:"numero_vani"`
|
||||
NumeroCamere *string `xml:"numero_camere"`
|
||||
NumeroLetti *string `xml:"numero_letti"`
|
||||
NumeroBagni *string `xml:"numero_bagni"`
|
||||
NumeroBalconi *string `xml:"numero_balconi"`
|
||||
NumeroTerrazzi *string `xml:"numero_terrazzi"`
|
||||
NumeroBox *string `xml:"numero_box"`
|
||||
NumeroPostiauto *string `xml:"numero_postiauto"`
|
||||
Schede *struct {
|
||||
Scheda *[]struct {
|
||||
ID string `xml:"id"`
|
||||
Nome string `xml:"nome"`
|
||||
Valore *string `xml:"valore"`
|
||||
} `xml:"scheda"`
|
||||
} `xml:"schede"`
|
||||
Accessori *struct {
|
||||
Accessorio *[]struct {
|
||||
ID string `xml:"id"`
|
||||
Nome *string `xml:"nome"`
|
||||
} `xml:"accessorio"`
|
||||
} `xml:"accessori"`
|
||||
Web *string `xml:"web"`
|
||||
Descrizioni *struct {
|
||||
Descrizione *[]struct {
|
||||
Lingua *string `xml:"lingua"`
|
||||
Titolo *string `xml:"titolo"`
|
||||
Testo *string `xml:"testo"`
|
||||
} `xml:"descrizione"`
|
||||
} `xml:"descrizioni"`
|
||||
Allegati *struct {
|
||||
Foto *[]struct {
|
||||
Nome *string `xml:"nome"`
|
||||
URL *string `xml:"url"`
|
||||
} `xml:"foto"`
|
||||
} `xml:"allegati"`
|
||||
Inserito *string `xml:"inserito"`
|
||||
Stato *string `xml:"stato"`
|
||||
}
|
||||
|
||||
type AnnunciXML struct {
|
||||
Annuncio []AnnuncioXML `xml:"Annuncio"`
|
||||
}
|
||||
|
||||
type AnnuncioXML struct {
|
||||
Codice *string `xml:"Codice"`
|
||||
Stato *string `xml:"Stato"`
|
||||
Creato *string `xml:"Creato"`
|
||||
Modifica *string `xml:"Modifica"`
|
||||
Regione *string `xml:"Regione"`
|
||||
Provincia *string `xml:"Provincia"`
|
||||
Comune *string `xml:"Comune"`
|
||||
Indirizzo *string `xml:"Indirizzo"`
|
||||
Civico *string `xml:"Civico"`
|
||||
Latitudine *string `xml:"Latitudine"`
|
||||
Longitudine *string `xml:"Longitudine"`
|
||||
IndirizzoSecondario *string `xml:"IndirizzoSecondario"`
|
||||
CivicoSecondario *string `xml:"CivicoSecondario"`
|
||||
LatitudineSecondario *string `xml:"LatitudineSecondario"`
|
||||
LongitudineSecondario *string `xml:"LongitudineSecondario"`
|
||||
Tipologia *string `xml:"Tipologia"`
|
||||
Tipologia2 *string `xml:"Tipologia2"`
|
||||
Categoria *[]string `xml:"Categoria"`
|
||||
HomePage *string `xml:"HomePage"`
|
||||
Prezzo *string `xml:"Prezzo"`
|
||||
Anno *string `xml:"Anno"`
|
||||
Mq *string `xml:"Mq"`
|
||||
Vani *string `xml:"Vani"`
|
||||
Camere *string `xml:"Camere"`
|
||||
Bagni *string `xml:"Bagni"`
|
||||
Balconi *string `xml:"Balconi"`
|
||||
Terrazzi *string `xml:"Terrazzi"`
|
||||
Piano *string `xml:"Piano"`
|
||||
PianiCondominio *string `xml:"PianiCondominio"`
|
||||
UnitaCondominio *string `xml:"UnitaCondominio"`
|
||||
Box *string `xml:"Box"`
|
||||
PostiAuto *string `xml:"PostiAuto"`
|
||||
Classe *string `xml:"Classe"`
|
||||
Scheda_AccessoDisabili *string `xml:"Scheda_AccessoDisabili"`
|
||||
Scheda_AccoglienzaBimbi *string `xml:"Scheda_AccoglienzaBimbi"`
|
||||
Scheda_AcquaCalda *string `xml:"Scheda_AcquaCalda"`
|
||||
Scheda_AffittoMuriAnno *string `xml:"Scheda_AffittoMuriAnno"`
|
||||
Scheda_Appartamenti *string `xml:"Scheda_Appartamenti"`
|
||||
Scheda_Arredi *string `xml:"Scheda_Arredi"`
|
||||
Scheda_Balconi *string `xml:"Scheda_Balconi"`
|
||||
Scheda_CambioBiancheria *string `xml:"Scheda_CambioBiancheria"`
|
||||
Scheda_CanoniAnticipati *string `xml:"Scheda_CanoniAnticipati"`
|
||||
Scheda_CantinaSolaio *string `xml:"Scheda_CantinaSolaio"`
|
||||
Scheda_Categoria *string `xml:"Scheda_Categoria"`
|
||||
Scheda_Cauzione *string `xml:"Scheda_Cauzione"`
|
||||
Scheda_Destinazione *string `xml:"Scheda_Destinazione"`
|
||||
Scheda_ElettricitaGas *string `xml:"Scheda_ElettricitaGas"`
|
||||
Scheda_FonteEnergetica *string `xml:"Scheda_FonteEnergetica"`
|
||||
Scheda_Giardino *string `xml:"Scheda_Giardino"`
|
||||
Scheda_Grado *string `xml:"Scheda_Grado"`
|
||||
Scheda_IncassoAnnuo *string `xml:"Scheda_IncassoAnnuo"`
|
||||
Scheda_IndiceEdificabilita *string `xml:"Scheda_IndiceEdificabilita"`
|
||||
Scheda_Infissi *string `xml:"Scheda_Infissi"`
|
||||
Scheda_Internet *string `xml:"Scheda_Internet"`
|
||||
Scheda_LatiLiberi *string `xml:"Scheda_LatiLiberi"`
|
||||
Scheda_Magazzino *string `xml:"Scheda_Magazzino"`
|
||||
Scheda_NumeroCamere *string `xml:"Scheda_NumeroCamere"`
|
||||
Scheda_NumeroDipendenti *string `xml:"Scheda_NumeroDipendenti"`
|
||||
Scheda_NumeroVetrine *string `xml:"Scheda_NumeroVetrine"`
|
||||
Scheda_OneriUrbanizzazione *string `xml:"Scheda_OneriUrbanizzazione"`
|
||||
Scheda_OrariApertura *string `xml:"Scheda_OrariApertura"`
|
||||
Scheda_Orientamento *string `xml:"Scheda_Orientamento"`
|
||||
Scheda_Panorama *string `xml:"Scheda_Panorama"`
|
||||
Scheda_PavimentoBagno *string `xml:"Scheda_PavimentoBagno"`
|
||||
Scheda_PavimentoCucina *string `xml:"Scheda_PavimentoCucina"`
|
||||
Scheda_PavimentoGiorno *string `xml:"Scheda_PavimentoGiorno"`
|
||||
Scheda_PavimentoNotte *string `xml:"Scheda_PavimentoNotte"`
|
||||
Scheda_Pavimento *string `xml:"Scheda_Pavimento"`
|
||||
Scheda_Persiana *string `xml:"Scheda_Persiana"`
|
||||
Scheda_Piano *string `xml:"Scheda_Piano"`
|
||||
Scheda_Posizione *string `xml:"Scheda_Posizione"`
|
||||
Scheda_PossibilitàAcquistoMuri *string `xml:"Scheda_PossibilitàAcquistoMuri"`
|
||||
Scheda_PostiSedere *string `xml:"Scheda_PostiSedere"`
|
||||
Scheda_Pulizie *string `xml:"Scheda_Pulizie"`
|
||||
Scheda_Raffrescamento *string `xml:"Scheda_Raffrescamento"`
|
||||
Scheda_Riscaldamento *string `xml:"Scheda_Riscaldamento"`
|
||||
Scheda_ScadenzaContratto *string `xml:"Scheda_ScadenzaContratto"`
|
||||
Scheda_Serramenti *string `xml:"Scheda_Serramenti"`
|
||||
Scheda_SpCondominialiAnno *string `xml:"Scheda_SpCondominialiAnno"`
|
||||
Scheda_SpazioParcheggio *string `xml:"Scheda_SpazioParcheggio"`
|
||||
Scheda_StatoImmobile *string `xml:"Scheda_StatoImmobile"`
|
||||
Scheda_Taverna *string `xml:"Scheda_Taverna"`
|
||||
Scheda_Terrazzi *string `xml:"Scheda_Terrazzi"`
|
||||
Scheda_TipoContratto *string `xml:"Scheda_TipoContratto"`
|
||||
Scheda_TipoCucina *string `xml:"Scheda_TipoCucina"`
|
||||
Scheda_TipoGestione *string `xml:"Scheda_TipoGestione"`
|
||||
Scheda_TipoImpianto *string `xml:"Scheda_TipoImpianto"`
|
||||
Scheda_TipoRegistr *string `xml:"Scheda_TipoRegistr"`
|
||||
Scheda_TipoRiscaldamento *string `xml:"Scheda_TipoRiscaldamento"`
|
||||
Scheda_TipoSaracinesca *string `xml:"Scheda_TipoSaracinesca"`
|
||||
Scheda_TipoSoggiorno *string `xml:"Scheda_TipoSoggiorno"`
|
||||
Scheda_TotalePiani *string `xml:"Scheda_TotalePiani"`
|
||||
Scheda_Tv *string `xml:"Scheda_Tv"`
|
||||
Accessori *[]string `xml:"Accessori"`
|
||||
Titolo *string `xml:"Titolo"`
|
||||
TitoloEn *string `xml:"Titolo_En"`
|
||||
Descrizione *string `xml:"Descrizione"`
|
||||
DescrizioneEn *string `xml:"Descrizione_En"`
|
||||
Foto *[]string `xml:"Foto"`
|
||||
Consegna *string `xml:"Consegna"`
|
||||
Clienti *struct {
|
||||
Cliente *struct {
|
||||
Id *string `xml:"Id"`
|
||||
Cognome *string `xml:"Cognome"`
|
||||
Nome *string `xml:"Nome"`
|
||||
Tel1 *string `xml:"Tel1"`
|
||||
Tel2 *string `xml:"Tel2"`
|
||||
Tel3 *string `xml:"Tel3"`
|
||||
Email1 *string `xml:"Email1"`
|
||||
} `xml:"Cliente"`
|
||||
} `xml:"Clienti"`
|
||||
Cap *string `xml:"Cap"`
|
||||
Video *[]string `xml:"Video"`
|
||||
AMMedias *[]struct {
|
||||
AMVideo *struct {
|
||||
Url *string `xml:"Url"`
|
||||
} `xml:"AMVideo"`
|
||||
} `xml:"AMMedias"`
|
||||
}
|
||||
|
||||
type AnnunciParsed struct {
|
||||
Annuncio []AnnuncioParsed
|
||||
}
|
||||
type AnnuncioParsed struct {
|
||||
Accessori *[]string
|
||||
Anno *string
|
||||
Numero_bagni *string
|
||||
Numero_balconi *string
|
||||
Numero_box *string
|
||||
Numero_camere *string
|
||||
Cap *string
|
||||
Categorie *[]string
|
||||
Civico *string
|
||||
Civico_secondario *string
|
||||
Classe *string
|
||||
Codice string
|
||||
Comune *string
|
||||
Consegna *int
|
||||
Creato_il *time.Time
|
||||
Desc_it *string
|
||||
Desc_en *string
|
||||
Email *string
|
||||
Homepage *bool
|
||||
Indirizzo *string
|
||||
Indirizzo_secondario *string
|
||||
Lat *string
|
||||
Lat_secondario *string
|
||||
Lon *string
|
||||
Lon_secondario *string
|
||||
Modificato_il *time.Time
|
||||
Mq *string
|
||||
Piano_palazzo *string
|
||||
Piano *string
|
||||
Numero_postiauto *string
|
||||
Prezzo int
|
||||
Provincia *string
|
||||
Regione *string
|
||||
Caratteristiche *map[string]interface{}
|
||||
Locatore *string
|
||||
Numero *string
|
||||
Idlocatore *string
|
||||
Stato *string
|
||||
Numero_terrazzi *string
|
||||
Tipo *string
|
||||
Titolo_it *string
|
||||
Titolo_en *string
|
||||
Unita_condominio *string
|
||||
Numero_vani *string
|
||||
Url_foto *[]string
|
||||
Url_video *[]string
|
||||
Web *bool
|
||||
}
|
||||
|
||||
type AnnunciDB struct {
|
||||
Id int
|
||||
Codice string
|
||||
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 *string
|
||||
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{}
|
||||
Url_immagini *[]string
|
||||
Homepage *bool
|
||||
Url_video *[]string
|
||||
Email *string
|
||||
Creato_il *time.Time
|
||||
Modificato_il *time.Time
|
||||
}
|
||||
|
||||
type FilebrowserData struct {
|
||||
Data []File
|
||||
Folder *string
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Name string
|
||||
Size int64
|
||||
Mode os.FileMode
|
||||
ModTime string
|
||||
IsDir bool
|
||||
}
|
||||
type DefaultCaratteristiche struct {
|
||||
Scheda []struct {
|
||||
Id string `xml:"id"`
|
||||
Tagxml string `xml:"tagxml"`
|
||||
} `xml:"scheda"`
|
||||
}
|
||||
type Caratteristica struct {
|
||||
Id string
|
||||
Tagxml string
|
||||
}
|
||||
type ListaCaratteristiche struct {
|
||||
Caratteristiche []Caratteristica
|
||||
}
|
||||
|
||||
type DefaultCategories struct {
|
||||
Cat []struct {
|
||||
ID string `xml:"id"`
|
||||
Nome string `xml:"nome"`
|
||||
} `xml:"cat"`
|
||||
}
|
||||
|
||||
type Categoria struct {
|
||||
Id string
|
||||
Nome string
|
||||
}
|
||||
|
||||
type Storageindex struct {
|
||||
Id string
|
||||
Created_at time.Time
|
||||
Filename string
|
||||
Ext string
|
||||
From_admin bool
|
||||
Expires_at *time.Time
|
||||
}
|
||||
|
||||
type MiogestImagesRef_Table struct {
|
||||
Codice string
|
||||
Foto *[]string
|
||||
}
|
||||
23
apps/backend/pg_dumper.sh
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Set the necessary environment variables
|
||||
export PGHOST="localhost"
|
||||
export PGPORT="5432"
|
||||
export PGDATABASE="postgres"
|
||||
export PGUSER="postgres"
|
||||
export PGPASSWORD="rootpost"
|
||||
|
||||
# Call pg_dump.exe to generate the SQL file
|
||||
"C:/Program Files/PostgreSQL/16/bin/pg_dump.exe" -f "./db-docker/initial/1_init.sql" --schema-only
|
||||
|
||||
"C:/Program Files/PostgreSQL/16/bin/pg_dump.exe" -f "./db-docker/initial/2_data.sql" --data-only -t prezziario -t banners -t etichette -t flags -t banlist -t testi_e_stringhe -t temp_tokens
|
||||
|
||||
# Check if the pg_dump command was successful
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "SQL file generated successfully."
|
||||
else
|
||||
echo "Failed to generate SQL file."
|
||||
fi
|
||||
|
||||
# Wait for user keypress
|
||||
read -n 1 -s -r -p "Press any key to exit..."
|
||||
1
apps/backend/public/static/js/htmx.js
Normal file
59
apps/backend/public/views/backup-upload.html
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
{{define "backup-upload"}}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Backup Upload</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: #1f1f1f;
|
||||
/* Dark background */
|
||||
font-family: Arial, sans-serif;
|
||||
color: #fff;
|
||||
/* White text */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subgroup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
background-color: #303030;
|
||||
border-radius: 15px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #97c1ff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Backup Upload</h1>
|
||||
<div class="subgroup">
|
||||
<form action="/upload-backup" method="post" enctype="multipart/form-data">
|
||||
<input type="file" name="file" id="file">
|
||||
|
||||
<button type="submit">Upload</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
{{end}}
|
||||
163
apps/backend/public/views/main.html
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
{{define "main"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dashboard</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: #1f1f1f;
|
||||
/* Dark background */
|
||||
font-family: Arial, sans-serif;
|
||||
color: #fff;
|
||||
/* White text */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
margin: 10px;
|
||||
background-color: #007bff;
|
||||
/* Blue button */
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #0056b3;
|
||||
/* Darker blue on hover */
|
||||
}
|
||||
|
||||
.element {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.subgroup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
background-color: #303030;
|
||||
border-radius: 15px;
|
||||
padding: 1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Dashboard Api:</h1>
|
||||
<div class="subgroup" style="gap:0.5rem;">
|
||||
|
||||
<h3>Enviroment Variables:</h3>
|
||||
|
||||
<div class="element">
|
||||
<button onclick="toggleImageOption()" class="button">Toggle</button>
|
||||
<h5>Image Processing: {{.imageOption}}</h5>
|
||||
|
||||
</div>
|
||||
<h5>Concurrent Images: {{.concurrentImages}}</h5>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<div class="subgroup">
|
||||
|
||||
<div class="element">
|
||||
<a href="/initcwebp" class="button" style="background-color: orange;">Inizializza cwebp</a>
|
||||
|
||||
</div>
|
||||
<div class="element">
|
||||
<a href="/health" class="button" style="background-color: green;">Health DB</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="subgroup">
|
||||
<div class="element">
|
||||
<a href="/upload-backup" class="button">Upload Bkp</a>
|
||||
<h4>Carica il file di backup</h4>
|
||||
</div>
|
||||
<div class="element">
|
||||
<a href="/bkp" class="button">Open Bkp</a>
|
||||
<h4>Legge il file di backup</h4>
|
||||
</div>
|
||||
<div class="element">
|
||||
<a href="/parsebkp" class="button">Parse Bkp</a>
|
||||
<h4>Prende i dati dal file di backup e li eabora</h4>
|
||||
</div>
|
||||
<div class="element">
|
||||
<a href="/setbkp" class="button">Set Bkp in db</a>
|
||||
<h4>Aggiorna i dati nel database con i dati provenienti dal backup</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="subgroup">
|
||||
<div class="element">
|
||||
<a href="/miogest" class="button">Fetch MioGest</a>
|
||||
<h4>Riceve dati dal xml giornaliero</h4>
|
||||
</div>
|
||||
<div class="element">
|
||||
<a href="/miogest-sample" class="button">MioGest Sample</a>
|
||||
<h4>Sample del file xml giornaliero</h4>
|
||||
</div>
|
||||
<div class="element">
|
||||
<a href="/parse" class="button">Parse Update</a>
|
||||
<h4>Prende i dati da miogest e li eabora, scarica e converte le foto</h4>
|
||||
</div>
|
||||
<div class="element">
|
||||
<a href="/update" class="button">Set Update in db</a>
|
||||
<h4>Aggiorna i dati nel database con i dati provenienti da miogest</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="subgroup">
|
||||
<div class="element">
|
||||
<a href="/cats" class="button">Sample Caratteristiche</a>
|
||||
</div>
|
||||
<div class="element">
|
||||
<a href="/categ" class="button">Sample Categorie</a>
|
||||
</div>
|
||||
<div class="element">
|
||||
<a href="/images/resetimages" class="button" style="background-color: red;">Reset Images</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
function toggleImageOption() {
|
||||
fetch("/image-option-toggle", {
|
||||
method: "POST",
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</html>
|
||||
|
||||
{{end}}
|
||||
260
apps/backend/server.go
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
storagehandlers "backend/storage_handlers"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
var (
|
||||
imgbasepath string
|
||||
imageOption bool
|
||||
concurrentImages int = 1
|
||||
storagepath string
|
||||
Db *DbHandler
|
||||
Miogest *MiogestHandler
|
||||
ImageManager *ImageRefManager
|
||||
Storage storagehandlers.StorageHandler
|
||||
tempStorageLimit int = 5
|
||||
)
|
||||
|
||||
func main() {
|
||||
// SETUP WORK DIRECTORY
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// join paths indipedently of the OS
|
||||
imgbasepath = filepath.Join(pwd, "images")
|
||||
fmt.Println("Image base path: ", imgbasepath)
|
||||
storagepath = filepath.Join(pwd, "storage")
|
||||
|
||||
// SETUP ENV VARIABLES
|
||||
err = godotenv.Load(".env")
|
||||
if err != nil {
|
||||
fmt.Println("Error loading .env file")
|
||||
}
|
||||
if os.Getenv("IMAGEOPTION") != "" {
|
||||
if os.Getenv("IMAGEOPTION") == "true" {
|
||||
imageOption = true
|
||||
|
||||
} else {
|
||||
imageOption = false
|
||||
}
|
||||
}
|
||||
if os.Getenv("CONCURRENT_IMAGES") != "" {
|
||||
concurrentImages, err = strconv.Atoi(os.Getenv("CONCURRENT_IMAGES"))
|
||||
if err != nil {
|
||||
fmt.Println("Error parsing CONCURRENT_IMAGES:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// SETUP DATABASE HANDLER
|
||||
Db = NewDbHandler()
|
||||
if Db == nil {
|
||||
log.Fatal("Error connecting to db")
|
||||
}
|
||||
defer Db.Dbpool.Close()
|
||||
|
||||
// SETUP MIOGEST HANDLER
|
||||
Miogest = NewMiogestHandler()
|
||||
if Miogest == nil {
|
||||
log.Fatal("Error connecting to miogest")
|
||||
}
|
||||
|
||||
// SETUP IMAGE MANAGER
|
||||
ImageManager = NewImageRefManager()
|
||||
if ImageManager == nil {
|
||||
log.Fatal("Error creating image manager")
|
||||
}
|
||||
|
||||
// SETUP STORAGE HANDLER
|
||||
storagehandlers.Storagepath = storagepath
|
||||
var strategy storagehandlers.StorageStrategy = storagehandlers.FS
|
||||
STORAGE_STRATEGY := os.Getenv("STORAGE_STRATEGY")
|
||||
|
||||
if STORAGE_STRATEGY == "s3" || STORAGE_STRATEGY == "minio" {
|
||||
MINIO_URL := os.Getenv("MINIO_URL")
|
||||
MINIO_ROOT_USER := os.Getenv("MINIO_ROOT_USER")
|
||||
MINIO_ROOT_PASSWORD := os.Getenv("MINIO_ROOT_PASSWORD")
|
||||
|
||||
if MINIO_URL == "" || MINIO_ROOT_USER == "" || MINIO_ROOT_PASSWORD == "" {
|
||||
fmt.Println("Error: MINIO_ROOT_USER, MINIO_ROOT_PASSWORD and MINIO_URL must be set")
|
||||
strategy = storagehandlers.FS
|
||||
} else {
|
||||
strategy = storagehandlers.S3
|
||||
}
|
||||
}
|
||||
|
||||
h, err := storagehandlers.NewStorageHandler(strategy)
|
||||
if err != nil {
|
||||
log.Fatal("Error creating storage handler: " + err.Error())
|
||||
}
|
||||
Storage = h
|
||||
fmt.Printf("Storage Strategy: %s\n", strategy)
|
||||
|
||||
// SETUP ECHO
|
||||
e := SetupRoutes()
|
||||
|
||||
// SETUP PORT
|
||||
port := "1323"
|
||||
if os.Getenv("ASPNETCORE_PORT") != "" {
|
||||
port = os.Getenv("ASPNETCORE_PORT")
|
||||
}
|
||||
|
||||
fmt.Println("Image Option: ", imageOption)
|
||||
fmt.Println("Concurrent Images: ", concurrentImages)
|
||||
fmt.Println("Server url: http://localhost:" + port)
|
||||
|
||||
Conversion("test.jpg")
|
||||
|
||||
// START ECHO
|
||||
e.Logger.Info(e.Start(":" + port))
|
||||
}
|
||||
|
||||
type Template struct {
|
||||
templates *template.Template
|
||||
}
|
||||
|
||||
func (t *Template) Render(w io.Writer, name string, data any, c echo.Context) error {
|
||||
return t.templates.ExecuteTemplate(w, name, data)
|
||||
}
|
||||
|
||||
func SetupRoutes() *echo.Echo {
|
||||
funcs := map[string]any{
|
||||
"contains": strings.Contains,
|
||||
"hasPrefix": strings.HasPrefix,
|
||||
"hasSuffix": strings.HasSuffix,
|
||||
"getImage": func(txt string) string {
|
||||
return txt[7:]
|
||||
},
|
||||
"isWebp": func(txt string) bool {
|
||||
return strings.Contains(txt, "webp")
|
||||
},
|
||||
}
|
||||
t := &Template{
|
||||
templates: template.Must(template.New("").Funcs(funcs).ParseGlob("public/views/*.html")),
|
||||
}
|
||||
e := echo.New()
|
||||
e.Renderer = t
|
||||
|
||||
// MISC
|
||||
e.Static("/static", "public/static")
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
return c.Render(http.StatusOK, "main", map[string]any{
|
||||
"imageOption": imageOption,
|
||||
"concurrentImages": concurrentImages,
|
||||
})
|
||||
})
|
||||
e.GET("/test", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "OK")
|
||||
})
|
||||
e.POST("/testpost", func(c echo.Context) error {
|
||||
// Get the form value
|
||||
test := c.FormValue("test")
|
||||
fmt.Println("test:", test)
|
||||
return c.String(http.StatusOK, "OK")
|
||||
})
|
||||
e.GET("/health", func(c echo.Context) error {
|
||||
_, err := Db.Dbpool.Exec(Db.Ctx, "SELECT 1")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c.String(http.StatusOK, "success")
|
||||
})
|
||||
|
||||
MiogestRoutes(e)
|
||||
BkpRoutes(e)
|
||||
StorageRoutes(e)
|
||||
ImageRoutes(e)
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func BkpRoutes(e *echo.Echo) {
|
||||
e.GET("/upload-backup", func(c echo.Context) error {
|
||||
return c.Render(http.StatusOK, "backup-upload", nil)
|
||||
})
|
||||
e.POST("/upload-backup", func(c echo.Context) error {
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
dst, err := os.Create("bkp.xml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
if _, err = io.Copy(dst, src); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.HTML(http.StatusOK, fmt.Sprintf("<p>File %s uploaded successfully.</p>", file.Filename))
|
||||
})
|
||||
e.GET("/bkp", func(c echo.Context) error {
|
||||
data := From_Backup()
|
||||
return c.JSONPretty(http.StatusOK, data, " ")
|
||||
})
|
||||
e.GET("/parsebkp", func(c echo.Context) error {
|
||||
var data = ParseUpdateBkp()
|
||||
return c.JSONPretty(http.StatusOK, data, " ")
|
||||
})
|
||||
e.GET("/setbkp", func(c echo.Context) error {
|
||||
var data = ParseUpdateBkp()
|
||||
Db.SetFromBkp(data)
|
||||
return c.String(http.StatusOK, "Setted")
|
||||
})
|
||||
}
|
||||
|
||||
func MiogestRoutes(e *echo.Echo) {
|
||||
// MIOGEST DEFAULTS
|
||||
e.GET("/cats", func(c echo.Context) error {
|
||||
var data = Miogest.GetCaratteristiche()
|
||||
return c.JSONPretty(http.StatusOK, data, " ")
|
||||
})
|
||||
e.GET("/categ", func(c echo.Context) error {
|
||||
var data = Miogest.GetCategorie()
|
||||
return c.JSONPretty(http.StatusOK, data, " ")
|
||||
})
|
||||
|
||||
// MIOGEST UPDATE
|
||||
e.GET("/miogest", func(c echo.Context) error {
|
||||
data := Miogest.GetAnnunci()
|
||||
return c.JSONPretty(http.StatusOK, data, " ")
|
||||
})
|
||||
e.GET("/miogest-sample", func(c echo.Context) error {
|
||||
xml := Miogest.GetAnnunci()
|
||||
rand := rand.Intn(len(xml.Annuncio))
|
||||
data := xml.Annuncio[rand]
|
||||
return c.JSONPretty(http.StatusOK, data, " ")
|
||||
})
|
||||
e.GET("/parse", func(c echo.Context) error {
|
||||
start := time.Now()
|
||||
var data = Miogest.GetAnnunciParsed()
|
||||
end := time.Now()
|
||||
fmt.Println("time taken:", end.Sub(start).String())
|
||||
return c.JSONPretty(http.StatusOK, data, " ")
|
||||
})
|
||||
e.GET("/update", func(c echo.Context) error {
|
||||
var data = Miogest.GetAnnunciParsed()
|
||||
Db.InsertAnnunciInDB(data)
|
||||
return c.String(http.StatusOK, "Updated")
|
||||
})
|
||||
}
|
||||
263
apps/backend/storage.go
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/georgysavva/scany/v2/pgxscan"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// Upload saves a file to the storage directory.
|
||||
func Upload(file *multipart.FileHeader, from_admin bool) (string, error) {
|
||||
log.Printf("Uploading file: %s", file.Filename)
|
||||
log.Printf("DB instance: %v", Db.Dbpool)
|
||||
var newId string
|
||||
err := Db.Dbpool.QueryRow(Db.Ctx, "INSERT INTO public.storageindex(filename)VALUES (null) RETURNING id;").Scan(&newId)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if newId == "" {
|
||||
return "", errors.New("failed to create new id")
|
||||
}
|
||||
err = Storage.Add(file, newId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
//if !from_admin , expires in 60 days
|
||||
var expiresAt *time.Time
|
||||
if !from_admin {
|
||||
exp := time.Now().AddDate(0, 2, 0) // 60 days
|
||||
expiresAt = &exp
|
||||
} else {
|
||||
expiresAt = nil
|
||||
}
|
||||
|
||||
_, err = Db.Dbpool.Exec(Db.Ctx, "UPDATE public.storageindex set filename= $2, ext = $3, from_admin = $4, expires_at = $5 where id = $1", newId, file.Filename, filepath.Ext(file.Filename), from_admin, expiresAt)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.storageindex WHERE filename IS NULL")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return newId, nil
|
||||
|
||||
}
|
||||
|
||||
func StorageAuth(c echo.Context) error {
|
||||
token := c.Request().URL.Query().Get("token")
|
||||
if len(token) < 1 {
|
||||
return c.String(http.StatusUnauthorized, "Unauthorized")
|
||||
}
|
||||
var tk []string
|
||||
err := pgxscan.Select(Db.Ctx, Db.Dbpool, &tk, "SELECT token FROM public.temp_tokens WHERE token = $1 and elapse > $2", token, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tk == nil {
|
||||
return errors.New("invalid token")
|
||||
}
|
||||
|
||||
_, err = Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.temp_tokens WHERE elapse < $1", time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func RemoveFromDb(ref string) error {
|
||||
|
||||
_, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.storageindex WHERE id = $1", ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func RemoveExpired() error {
|
||||
//get all expired files
|
||||
var items_db []Storageindex
|
||||
err := pgxscan.Select(Db.Ctx, Db.Dbpool, &items_db, "SELECT * FROM public.storageindex WHERE expires_at is not null and expires_at < NOW()")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get expired items: %w", err)
|
||||
}
|
||||
for _, item := range items_db {
|
||||
formattedName := item.Id + item.Ext
|
||||
err = Storage.Delete(formattedName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete file %s: %w", formattedName, err)
|
||||
}
|
||||
err = RemoveFromDb(item.Id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove item from db: %w", err)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckConcruency() error {
|
||||
|
||||
var items_db []struct {
|
||||
Id string
|
||||
Ext string
|
||||
}
|
||||
|
||||
err := pgxscan.Select(Db.Ctx, Db.Dbpool, &items_db, "SELECT id, ext FROM public.storageindex")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
files, err := Storage.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(items_db) != len(files) {
|
||||
var founds []string
|
||||
for _, item := range items_db {
|
||||
formattedName := item.Id + item.Ext
|
||||
found := slices.Contains(files, formattedName)
|
||||
if found {
|
||||
founds = append(founds, formattedName)
|
||||
} else {
|
||||
|
||||
_, err := Db.Dbpool.Exec(Db.Ctx, "DELETE FROM public.storageindex WHERE id = $1", item.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
for _, file := range files {
|
||||
found := slices.Contains(founds, file)
|
||||
if !found {
|
||||
fmt.Printf("File %v not found in db\n", file)
|
||||
//TODO decidere se eliminarlo
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TempFolderCleaner() error {
|
||||
files, err := os.ReadDir(filepath.Join(storagepath, "/tmp/"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Temp files: ", len(files))
|
||||
if len(files) >= tempStorageLimit {
|
||||
|
||||
var oldest_Time time.Time
|
||||
var oldest_File string
|
||||
for _, file := range files {
|
||||
file_infos, err := file.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if oldest_Time.IsZero() || file_infos.ModTime().Before(oldest_Time) {
|
||||
oldest_Time = file_infos.ModTime()
|
||||
oldest_File = file.Name()
|
||||
}
|
||||
}
|
||||
err = os.Remove(filepath.Join(storagepath, "/tmp/", oldest_File))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// ROUTES
|
||||
func StorageRoutes(e *echo.Echo) {
|
||||
|
||||
e.GET("/storage/list", func(c echo.Context) error {
|
||||
filenames, err := Storage.List()
|
||||
if err != nil {
|
||||
return c.String(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return c.JSON(http.StatusOK, filenames)
|
||||
})
|
||||
|
||||
e.GET("/storage/get/:filename", func(c echo.Context) error {
|
||||
err := TempFolderCleaner()
|
||||
if err != nil {
|
||||
return c.String(http.StatusInternalServerError, "Failed to clean temp folder: "+err.Error())
|
||||
}
|
||||
err = StorageAuth(c)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusUnauthorized, "Unauthorized: "+err.Error())
|
||||
}
|
||||
|
||||
filename := c.Param("filename")
|
||||
path, err := Storage.Get(filename)
|
||||
if err != nil {
|
||||
return c.String(http.StatusInternalServerError, "Failed to get file: "+err.Error())
|
||||
}
|
||||
|
||||
return c.File(path)
|
||||
})
|
||||
e.POST("/storage/upload", func(c echo.Context) error {
|
||||
fmt.Println("Uploading file")
|
||||
|
||||
err := StorageAuth(c)
|
||||
if err != nil {
|
||||
return c.String(http.StatusInternalServerError, "Failed to verify Auth: "+err.Error())
|
||||
}
|
||||
|
||||
from_admin := c.QueryParam("from_admin") == "true"
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileId, err := Upload(file, from_admin)
|
||||
if err != nil {
|
||||
return c.String(http.StatusInternalServerError, "Failed to upload file: "+err.Error())
|
||||
}
|
||||
|
||||
return c.String(http.StatusOK, fileId)
|
||||
})
|
||||
e.DELETE("/storage/delete/:filename", func(c echo.Context) error {
|
||||
err := StorageAuth(c)
|
||||
if err != nil {
|
||||
return c.String(http.StatusInternalServerError, "Failed to verify Auth: "+err.Error())
|
||||
}
|
||||
filename := c.Param("filename")
|
||||
|
||||
err = Storage.Delete(filename)
|
||||
if err != nil {
|
||||
return c.String(http.StatusInternalServerError, "Failed to remove file / file not found: "+err.Error())
|
||||
}
|
||||
return c.String(http.StatusOK, "File removed")
|
||||
|
||||
})
|
||||
e.GET("/storage/remove-expired", func(c echo.Context) error {
|
||||
|
||||
err := RemoveExpired()
|
||||
if err != nil {
|
||||
return c.String(http.StatusInternalServerError, "Failed to remove files / files not found: "+err.Error())
|
||||
}
|
||||
return c.String(http.StatusOK, "File removed")
|
||||
|
||||
})
|
||||
e.GET("/storage/test", func(c echo.Context) error {
|
||||
err := CheckConcruency()
|
||||
if err != nil {
|
||||
return c.String(http.StatusInternalServerError, "Failed to check concruency: "+err.Error())
|
||||
}
|
||||
return c.String(http.StatusOK, "OK")
|
||||
})
|
||||
}
|
||||
74
apps/backend/storage_handlers/fs.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package storagehandlers
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Fs_Handler struct {
|
||||
}
|
||||
|
||||
func NewFsHandler() *Fs_Handler {
|
||||
return &Fs_Handler{}
|
||||
}
|
||||
func (f *Fs_Handler) Init() error {
|
||||
if _, err := os.Stat(Storagepath); os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Fs_Handler) Add(file *multipart.FileHeader, fileId string) error {
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
log.Printf("Error opening file: %v\n", err)
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
extension := filepath.Ext(file.Filename)
|
||||
destPath := filepath.Join(Storagepath, fileId+extension)
|
||||
dst, err := os.Create(destPath)
|
||||
if err != nil {
|
||||
log.Println("Error creating file")
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
if _, err = io.Copy(dst, src); err != nil {
|
||||
log.Println("Error copying file")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Fs_Handler) Delete(filename string) error {
|
||||
filePath := filepath.Join(Storagepath, filename)
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return os.Remove(filePath)
|
||||
}
|
||||
|
||||
func (f *Fs_Handler) Get(filename string) (string, error) {
|
||||
path := filepath.Join(Storagepath, filename)
|
||||
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (f *Fs_Handler) List() ([]string, error) {
|
||||
files, err := os.ReadDir(Storagepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var filenames []string
|
||||
for _, file := range files {
|
||||
filenames = append(filenames, file.Name())
|
||||
}
|
||||
return filenames, nil
|
||||
}
|
||||
137
apps/backend/storage_handlers/minio.go
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
package storagehandlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
)
|
||||
|
||||
var (
|
||||
useSSL = false
|
||||
bucketName = "storage"
|
||||
)
|
||||
|
||||
type S3_Handler struct {
|
||||
minioClient *minio.Client
|
||||
}
|
||||
|
||||
func (s *S3_Handler) Init() error {
|
||||
found, err := s.minioClient.BucketExists(context.Background(), bucketName)
|
||||
if err != nil {
|
||||
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
err = s.minioClient.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
log.Println("Bucket created successfully")
|
||||
}
|
||||
log.Println("Bucket found")
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func NewS3Handler() (*S3_Handler, error) {
|
||||
MINIO_URL := os.Getenv("MINIO_URL")
|
||||
MINIO_ROOT_USER := os.Getenv("MINIO_ROOT_USER")
|
||||
MINIO_ROOT_PASSWORD := os.Getenv("MINIO_ROOT_PASSWORD")
|
||||
|
||||
minioClient, err := minio.New(MINIO_URL, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(MINIO_ROOT_USER, MINIO_ROOT_PASSWORD, ""),
|
||||
Secure: useSSL,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
return &S3_Handler{
|
||||
minioClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *S3_Handler) Add(file *multipart.FileHeader, fileId string) error {
|
||||
ctx := context.Background()
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
log.Printf("Error opening file: %v\n", err)
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
extension := filepath.Ext(file.Filename)
|
||||
objectName := fileId + extension
|
||||
|
||||
info, err := s.minioClient.PutObject(ctx, bucketName, objectName, src, file.Size, minio.PutObjectOptions{ContentType: "application/octet-stream"})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
return err
|
||||
}
|
||||
log.Printf("Successfully uploaded %s of size %d\n", objectName, info.Size)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *S3_Handler) Delete(filename string) error {
|
||||
opts := minio.RemoveObjectOptions{
|
||||
GovernanceBypass: true,
|
||||
}
|
||||
err := s.minioClient.RemoveObject(context.Background(), bucketName, filename, opts)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// func (s *S3_Handler) DeleteM(filename string) error {
|
||||
// opts := minio.RemoveObjectOptions{
|
||||
// GovernanceBypass: true,
|
||||
// }
|
||||
// err := s.minioClient.RemoveObjects(context.Background(), bucketName, filename, opts)
|
||||
// if err != nil {
|
||||
// log.Fatalln(err)
|
||||
// return err
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (s *S3_Handler) Get(filename string) (string, error) {
|
||||
localPath := filepath.Join(Storagepath, "/tmp/", filename)
|
||||
|
||||
err := s.minioClient.FGetObject(context.Background(), bucketName, filename, localPath, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return "", fmt.Errorf("error downloading file %s: %w", filename, err)
|
||||
}
|
||||
log.Printf("Successfully downloaded %s\n", filename)
|
||||
return localPath, nil
|
||||
|
||||
}
|
||||
|
||||
func (s *S3_Handler) List() ([]string, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
defer cancel()
|
||||
|
||||
objectCh := s.minioClient.ListObjects(ctx, bucketName, minio.ListObjectsOptions{
|
||||
Prefix: "",
|
||||
Recursive: true,
|
||||
})
|
||||
var list []string
|
||||
for object := range objectCh {
|
||||
if object.Err != nil {
|
||||
log.Println(object.Err)
|
||||
return nil, object.Err
|
||||
}
|
||||
list = append(list, object.Key)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
53
apps/backend/storage_handlers/storage_handler.go
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package storagehandlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"mime/multipart"
|
||||
)
|
||||
|
||||
var (
|
||||
Storagepath string
|
||||
)
|
||||
|
||||
type StorageHandler interface {
|
||||
Init() error
|
||||
Add(file *multipart.FileHeader, fileId string) error
|
||||
Delete(filename string) error
|
||||
Get(filename string) (string, error)
|
||||
List() ([]string, error)
|
||||
}
|
||||
|
||||
type StorageStrategy string
|
||||
|
||||
const (
|
||||
FS StorageStrategy = "fs"
|
||||
S3 StorageStrategy = "s3"
|
||||
)
|
||||
|
||||
func NewStorageHandler(strategy StorageStrategy) (StorageHandler, error) {
|
||||
if Storagepath == "" {
|
||||
return nil, errors.New("storage path not set")
|
||||
}
|
||||
switch strategy {
|
||||
case FS:
|
||||
h := NewFsHandler()
|
||||
err := h.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h, nil
|
||||
|
||||
case S3:
|
||||
h, err := NewS3Handler()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = h.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h, nil
|
||||
default:
|
||||
return nil, errors.New("invalid storage strategy")
|
||||
}
|
||||
}
|
||||
BIN
apps/backend/test.jpg
Normal file
|
After Width: | Height: | Size: 76 KiB |
122
apps/backend/utils.go
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func MakeUppercase(s string) string {
|
||||
if s != "" && reflect.TypeOf(s).Kind() == reflect.String && len(s) > 0 {
|
||||
return strings.ToUpper(string(s[0])) + s[1:]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func CheckStringIfPresent(value *string) string {
|
||||
if value != nil {
|
||||
return strings.TrimSpace(*value)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func CheckStringIfPresentBool(value string) bool {
|
||||
if value != "" {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func fetchXML(url string) ([]byte, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("read body: %v", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func GetDataXML[T any](url string) T {
|
||||
xmlBytes, err := fetchXML(url)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get XML: %v", err)
|
||||
panic(err)
|
||||
}
|
||||
var result T
|
||||
err = xml.Unmarshal(xmlBytes, &result)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to unmarshal XML: %v", err)
|
||||
panic(err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func GetPathsArray(path string) ([]string, error) {
|
||||
var paths []string
|
||||
|
||||
err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
paths = append(paths, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func GetPathDirsArray(path string) []string {
|
||||
var folderNames []string
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
folderNames = append(folderNames, file.Name())
|
||||
}
|
||||
}
|
||||
return folderNames
|
||||
}
|
||||
|
||||
func GetStringValue(strPtr *string) string {
|
||||
if strPtr != nil {
|
||||
return *strPtr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
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
|
||||
}
|
||||
19
apps/db/Dockerfile
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Start a new stage for PostgreSQL
|
||||
FROM postgres:16 AS postgres
|
||||
|
||||
# Set environment variables for PostgreSQL
|
||||
|
||||
ARG POSTGRES_USER
|
||||
ARG POSTGRES_PASSWORD
|
||||
ARG POSTGRES_DB
|
||||
|
||||
ENV POSTGRES_USER=$POSTGRES_USER
|
||||
ENV POSTGRES_PASSWORD=$POSTGRES_PASSWORD
|
||||
ENV POSTGRES_DB=$POSTGRES_DB
|
||||
|
||||
# Copy the schema.sql file to the docker-entrypoint-initdb.d directory
|
||||
COPY ./initial/*.sql /docker-entrypoint-initdb.d/
|
||||
|
||||
# Expose the PostgreSQL port
|
||||
EXPOSE 5432
|
||||
CMD ["-p", "5432"]
|
||||
34
apps/db/docker-compose.yml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
services:
|
||||
db:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
restart: always
|
||||
networks:
|
||||
- dokploy-network
|
||||
expose:
|
||||
- "5432"
|
||||
volumes:
|
||||
- dbdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
|
||||
interval: 1s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
|
||||
networks:
|
||||
dokploy-network:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
dbdata:
|
||||
72
apps/db/filter_sql.awk
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# filter_sql.awk
|
||||
|
||||
BEGIN {
|
||||
in_sql_block = 0;
|
||||
sql_command = "";
|
||||
sql_start_pattern = "ISTRUZIONE:|LOG: *istruzione:";
|
||||
target_commands_regex = "(CREATE TABLE|ALTER TABLE|DROP TABLE)";
|
||||
|
||||
# If git_cutoff_date_str is empty (no Git repo or commit), set a dummy value to avoid error
|
||||
# and set perform_cutoff to false. Otherwise, it's true.
|
||||
perform_cutoff = (git_cutoff_date_str != "");
|
||||
if (!perform_cutoff) {
|
||||
git_cutoff_date_str = ""; # Ensure it's empty if not performing cutoff
|
||||
}
|
||||
|
||||
current_log_timestamp_full = ""; # Initialize for each log file
|
||||
}
|
||||
|
||||
# --- NEW AWK LOGIC FOR CUTOFF ---
|
||||
# Rule 1: Always check for a new log entry header and extract its timestamp.
|
||||
# This must be the first pattern rule in awk.
|
||||
$0 ~ new_log_regex {
|
||||
# Always update the timestamp if it's a new log line
|
||||
current_log_timestamp_full = substr($0, 1, 23); # YYYY-MM-DD HH:MM:SS.ms (23 chars)
|
||||
|
||||
# Rule 1a: If a cutoff is active AND the current log entry is OLDER than the cutoff date.
|
||||
# String comparison works for YYYY-MM-DD HH:MM:SS.ms format.
|
||||
if (perform_cutoff && current_log_timestamp_full < git_cutoff_date_str) {
|
||||
# If the current log entry is too old, discard any partially accumulated SQL command
|
||||
# that might have started before this old log line was encountered.
|
||||
in_sql_block = 0;
|
||||
sql_command = "";
|
||||
next; # Skip to the next line immediately.
|
||||
}
|
||||
|
||||
# Rule 1b: If we were previously in an SQL block and this is a new, valid (not-cut-off) log entry,
|
||||
# then the previous command is complete and should be processed.
|
||||
if (in_sql_block) {
|
||||
gsub(/^[ \t\n]+|[ \t\n]+$/, "", sql_command);
|
||||
if (tolower(sql_command) ~ tolower(target_commands_regex)) {
|
||||
print sql_command "\n";
|
||||
}
|
||||
sql_command = ""; # Reset for the next command
|
||||
in_sql_block = 0; # Reset the flag
|
||||
}
|
||||
}
|
||||
|
||||
# Rule 2: If the line starts an SQL statement (and it passed the cutoff check or no cutoff is applied).
|
||||
# This rule will only run for lines that were *not* skipped by Rule 1a.
|
||||
$0 ~ sql_start_pattern {
|
||||
line_content = $0;
|
||||
sub(/.*(ISTRUZIONE:|LOG: *istruzione:)[ \t]*/, "", line_content);
|
||||
sql_command = line_content;
|
||||
in_sql_block = 1;
|
||||
next; # Skip to the next line.
|
||||
}
|
||||
|
||||
# Rule 3: If we are in an SQL block (meaning it's a continuation line for an SQL command).
|
||||
# This rule will only run for lines that were *not* skipped by Rule 1a or handled by Rule 2.
|
||||
in_sql_block {
|
||||
sql_command = sql_command "\n" $0;
|
||||
}
|
||||
|
||||
END {
|
||||
# This block processes the very last command in the file if it was still accumulating
|
||||
if (in_sql_block) {
|
||||
gsub(/^[ \t\n]+|[ \t\n]+$/, "", sql_command);
|
||||
if (tolower(sql_command) ~ tolower(target_commands_regex)) {
|
||||
print sql_command "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
1135
apps/db/initial/1_init.sql
Normal file
133
apps/db/initial/2_data.sql
Normal file
21
apps/db/initial/3_seed_user.sql
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
INSERT INTO
|
||||
PUBLIC.USERS (
|
||||
USERNAME,
|
||||
EMAIL,
|
||||
"isAdmin",
|
||||
PASSWORD,
|
||||
NOME,
|
||||
COGNOME,
|
||||
"isVerified",
|
||||
SALT
|
||||
)
|
||||
VALUES (
|
||||
'Marco Pedone'::TEXT,
|
||||
'm.pedone98@gmail.com'::TEXT,
|
||||
TRUE::BOOLEAN,
|
||||
'changeme'::TEXT,
|
||||
'Marco'::TEXT,
|
||||
'Pedone'::TEXT,
|
||||
TRUE::BOOLEAN,
|
||||
'a'::TEXT
|
||||
);
|
||||
61
apps/db/log_parser.sh
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Define the directory where PostgreSQL logs are stored
|
||||
LOG_DIR="/c/Program Files/PostgreSQL/16/data/log"
|
||||
|
||||
# Define the output file for extracted SQL commands
|
||||
OUTPUT_FILE="migration.sql"
|
||||
|
||||
# Define the AWK script file path
|
||||
AWK_SCRIPT_FILE="filter_sql.awk" # Make sure this file is in the same directory as your bash script
|
||||
|
||||
# Clear the output file if it exists, or create a new one
|
||||
> "$OUTPUT_FILE"
|
||||
|
||||
echo "Starting SQL command extraction from logs in: $LOG_DIR"
|
||||
echo "Extracted commands will be saved to: $OUTPUT_FILE"
|
||||
echo "----------------------------------------------------"
|
||||
|
||||
# --- NEW PART: GET LAST GIT COMMIT DATE ---
|
||||
GIT_LAST_COMMIT_DATE_MS="" # This will store the Git commit date in YYYY-MM-DD HH:MM:SS.000 format
|
||||
|
||||
# Check if we are inside a Git repository
|
||||
if git rev-parse --is-inside-work-tree &>/dev/null; then
|
||||
# Get the last commit date in ISO 8601 strict format (e.g., 2025-07-31T16:21:44+02:00)
|
||||
GIT_LAST_COMMIT_ISO=$(git log -1 --format=%cd --date=iso-strict)
|
||||
|
||||
if [ -n "$GIT_LAST_COMMIT_ISO" ]; then
|
||||
# Convert to YYYY-MM-DD HH:MM:SS.000 for comparison with log timestamps
|
||||
# cut -d'.' -f1 gets "YYYY-MM-DDTHH:MM:SS"
|
||||
# tr 'T' ' ' changes 'T' to space
|
||||
# "\.000" appends milliseconds for consistent string comparison
|
||||
GIT_LAST_COMMIT_DATE_MS=$(echo "$GIT_LAST_COMMIT_ISO" | cut -d'.' -f1 | tr 'T' ' ')"\.000"
|
||||
echo "Git last commit date detected (for log cutoff): $GIT_LAST_COMMIT_DATE_MS"
|
||||
else
|
||||
echo "Warning: Could not get last Git commit date. Processing all log entries."
|
||||
fi
|
||||
else
|
||||
echo "Not inside a Git repository. Processing all log entries."
|
||||
fi
|
||||
|
||||
# Regex to detect the start of a new log entry (used by awk)
|
||||
NEW_LOG_ENTRY_REGEX="^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}"
|
||||
|
||||
# --- Check if the AWK script file exists ---
|
||||
if [ ! -f "$AWK_SCRIPT_FILE" ]; then
|
||||
echo "ERROR: AWK script file '$AWK_SCRIPT_FILE' not found!"
|
||||
echo "Please create '$AWK_SCRIPT_FILE' with the AWK code from Step 1."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find all .log files in the specified directory and process them
|
||||
find "$LOG_DIR" -type f -name "*.log" | while read -r logfile; do
|
||||
echo "Processing log file: $logfile"
|
||||
LC_ALL=C awk -v new_log_regex="$NEW_LOG_ENTRY_REGEX" \
|
||||
-v git_cutoff_date_str="$GIT_LAST_COMMIT_DATE_MS" \
|
||||
-f "$AWK_SCRIPT_FILE" "$logfile" >> "$OUTPUT_FILE"
|
||||
done
|
||||
|
||||
echo "SQL extraction complete"
|
||||
echo "----------------------------------------------------"
|
||||
echo "Final SQL output is in: '$OUTPUT_FILE'."
|
||||
0
apps/db/migration.sql
Normal file
23
apps/db/pg_dumper.sh
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Set the necessary environment variables
|
||||
export PGHOST="localhost"
|
||||
export PGPORT="5432"
|
||||
export PGDATABASE="postgres"
|
||||
export PGUSER="postgres"
|
||||
export PGPASSWORD="rootpost"
|
||||
|
||||
# Call pg_dump.exe to generate the SQL file
|
||||
"C:/Program Files/PostgreSQL/16/bin/pg_dump.exe" -f "./initial/1_init.sql" --schema-only
|
||||
|
||||
"C:/Program Files/PostgreSQL/16/bin/pg_dump.exe" -f "./initial/2_data.sql" --data-only -t prezziario -t banners -t etichette -t flags -t banlist -t testi_e_stringhe -t temp_tokens
|
||||
|
||||
# Check if the pg_dump command was successful
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "SQL file generated successfully."
|
||||
else
|
||||
echo "Failed to generate SQL file."
|
||||
fi
|
||||
|
||||
# Wait for user keypress
|
||||
read -n 1 -s -r -p "Press any key to exit..."
|
||||
25
apps/infoalloggi/.docker_env
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#Api key for the mail service
|
||||
SENDGRID_API_KEY='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'
|
||||
|
||||
#Stripe keys
|
||||
STRIPE_SECRET_KEY = "sk_test_51P4IY3ILe4KoQRqXJJLyn33Dvb4z3ajTrMsGD4y8g4DWXMbPQcoqOAcvedemB83xn0FIdVAMGX1hk8gXYog1BXl700cRUngUET"
|
||||
NEXT_PUBLIC_STRIPE_PUBLIC_KEY = "pk_test_51P4IY3ILe4KoQRqXXUVDyiVEy9oiKdzDIB9xgDY054xVRyVZ5dfWoJlo1F5dXlQ6sEATwgylPp708PQEf2uhagUx00qcL0rRgq"
|
||||
STRIPE_LIMITED_KEY = "rk_test_"
|
||||
STRIPE_WEBHOOK_SECRET = "whsec_a1a62f5c31b2ade9237af6032e30392dda47f945e2eddd7e2f69579f1fb47118"
|
||||
|
||||
#Fatture in Cloud keys
|
||||
FIC_CLIENT_ID = "ggZlcim78qkRYrl5n2d8DpNoKGiqwRHa"
|
||||
FIC_ACCESS_TOKEN = "a/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyZWYiOiJ2Z2VOWFhST0JyZUliN3dNMFFpM0NpdXFhb1VHTzBlciJ9.ewEBe0Qq4ATNyx03rTTynWHhhxYvjuCB-ra8XBJV_B0"
|
||||
FIC_COMPANY_ID = "120838"
|
||||
|
||||
|
||||
#ARUBA EMAIL
|
||||
ARUBA_USER = "web@infoalloggi.it"
|
||||
ARUBA_PASS = "we2023Info.1"
|
||||
|
||||
#EXPOSED API AUTH
|
||||
EXP_API_USER = "infoalloggi-admin"
|
||||
EXP_API_PASS = "cyYk24u9fRn5DsqjFWpTV8GKBNwhQvEtbMmHxJeU73ZSaXrdCLAPg6NtDkC43ZQa9dVKHbxMv7fWpy65gFmAsEReXwuPYTL82jJnSUhrzcqGkqvsHPyxmEAFzYVSntGeQTr25dJb3UNCMuRpgwaDW69Zhc7Kj8LfB4ZCHR9kYfWVsGrx7tygqdvc82SFXj54Nhnpm6PEBLbKUuaMTQJeA3wDgPSCXwDVmhned6J8ZQ3qzNsr5bBuLtHxvFTAKGy74W2fY9pckjMRUEJYRkVPZwbvBtTKy2F8ULAseunzWj7G5d6Q3ExfmNCXqg4MDahH9cSruXyWxLNAsGQTPSFhD9HYVzB3Meg2rk5Cvacfq8mdZ6tUE4KRpwbn7jJAev7WRbVgPdncMLKxwm8yT9pQZ4DNUkaYj6ztXfhsBHqF5ErCu32Gcjb3H6KxvWsu9p5fkPrh7ZARXMLDdS82enFU4CqwztTayEmGNVgYJB"
|
||||
|
||||
|
||||
JWT_SECRET = "335922ae76f8334dd11fa2614434624017ee21bffea95e355c0ac0a3b4e717193cdefbe4fadb5dac98939d6c4cd8b3158d530ebc8bb670281300efe5d5642fec"
|
||||
9
apps/infoalloggi/.dockerignore
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
TODO
|
||||
node_modules
|
||||
.vscode
|
||||
.next
|
||||
.git
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
.env
|
||||
.docker_env
|
||||
21
apps/infoalloggi/.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
||||
#ignore:
|
||||
# - dependency-name: "react"
|
||||
# - dependency-name: "react-dom"
|
||||
# - dependency-name: "@next/eslint-plugin-next"
|
||||
# versions: [">=14.2.23"]
|
||||
# - dependency-name: "next"
|
||||
# versions: [">=14.2.23"]
|
||||
# - dependency-name: "eslint-config-next"
|
||||
# versions: [">=14.2.23"]
|
||||
open-pull-requests-limit: 50
|
||||
56
apps/infoalloggi/.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
INTERNAL_BASE_URL: "http://web:3000"
|
||||
PGHOST: "localhost"
|
||||
POSTGRES_USER: "test"
|
||||
POSTGRES_DB: "test"
|
||||
POSTGRES_PASSWORD: "test"
|
||||
PGPORT: 123
|
||||
BACKENDSERVER_URL: "https://fake.com"
|
||||
NEXT_PUBLIC_BASE_URL: "https://fake.com"
|
||||
STRIPE_SECRET_KEY: "string"
|
||||
STRIPE_WEBHOOK_SECRET: "string"
|
||||
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: "string"
|
||||
STRIPE_LIMITED_KEY: "string"
|
||||
FIC_CLIENT_ID: "string"
|
||||
FIC_ACCESS_TOKEN: "string"
|
||||
FIC_COMPANY_ID: "string"
|
||||
NEXT_TELEMETRY_DISABLED: 1
|
||||
ARUBA_USER: "string"
|
||||
ARUBA_PASS: "string"
|
||||
EXP_API_USER: "string"
|
||||
EXP_API_PASS: "string"
|
||||
TILES_URL: "keydb://string"
|
||||
KEYDB_URL: "keydb://string"
|
||||
JWT_SECRET: "335922ae"
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "npm"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Typecheck
|
||||
run: npm run types
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Print Environment Variable
|
||||
run: echo $MY_ENV_VAR
|
||||
|
||||
#- name: Run knip
|
||||
# run: npx knip --no-exit-code
|
||||
24
apps/infoalloggi/.kanelrc.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
const { makeKyselyHook } = require("kanel-kysely");
|
||||
|
||||
const ignoredTables = [];
|
||||
|
||||
/** @type {import('kanel').Config} */
|
||||
module.exports = {
|
||||
connection: {
|
||||
host: "localhost",
|
||||
user: "postgres",
|
||||
database: "postgres",
|
||||
password: "rootpost",
|
||||
port: 5432,
|
||||
},
|
||||
typeFilter: (pgType) => !ignoredTables.includes(pgType.name),
|
||||
preDeleteOutputFolder: true,
|
||||
outputPath: "./src/schemas",
|
||||
|
||||
customTypeMap: {
|
||||
"pg_catalog.tsvector": "string",
|
||||
"pg_catalog.bpchar": "string",
|
||||
//"pg_catalog.json": "string",
|
||||
},
|
||||
preRenderHooks: [makeKyselyHook()],
|
||||
};
|
||||
28
apps/infoalloggi/.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Next.js: debug server-side",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "npm run dev"
|
||||
},
|
||||
{
|
||||
"name": "Next.js: debug client-side",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:3000"
|
||||
},
|
||||
{
|
||||
"name": "Next.js: debug full stack",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "npm run dev",
|
||||
"serverReadyAction": {
|
||||
"pattern": "- Local:.+(https?://.+)",
|
||||
"uriFormat": "%s",
|
||||
"action": "debugWithChrome"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
29
apps/infoalloggi/.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||
"sqltools.connections": [
|
||||
{
|
||||
"previewLimit": 50,
|
||||
"server": "localhost",
|
||||
"port": 5432,
|
||||
"driver": "PostgreSQL",
|
||||
"name": "DB",
|
||||
"database": "postgres",
|
||||
"username": "postgres",
|
||||
"password": "rootpost"
|
||||
}
|
||||
],
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
}
|
||||
}
|
||||
135
apps/infoalloggi/Dockerfile
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
FROM node:lts-alpine AS base
|
||||
|
||||
# Dependencies stage
|
||||
# This stage is used to install dependencies and prepare the environment
|
||||
FROM base AS deps
|
||||
RUN apk add --no-cache libc6-compat openssl
|
||||
WORKDIR /app
|
||||
COPY package*.json package-lock.json* ./
|
||||
RUN npm ci
|
||||
|
||||
# Builder stage
|
||||
# This stage is used to build the application
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
ARG BASE_URL
|
||||
ENV NEXT_PUBLIC_BASE_URL=$BASE_URL
|
||||
ARG INTERNAL_BASE_URL
|
||||
ENV INTERNAL_BASE_URL=$INTERNAL_BASE_URL
|
||||
ARG POSTGRES_USER
|
||||
ARG POSTGRES_PASSWORD
|
||||
ARG POSTGRES_DB
|
||||
ARG PGHOST
|
||||
ARG PGPORT
|
||||
ENV PGHOST=$PGHOST
|
||||
ENV POSTGRES_DB=$POSTGRES_DB
|
||||
ENV PGPORT=$PGPORT
|
||||
ENV POSTGRES_USER=$POSTGRES_USER
|
||||
ENV POSTGRES_PASSWORD=$POSTGRES_PASSWORD
|
||||
ARG JWT_SECRET
|
||||
ENV JWT_SECRET=$JWT_SECRET
|
||||
ARG BACKENDSERVER_URL
|
||||
ENV BACKENDSERVER_URL=$BACKENDSERVER_URL
|
||||
ARG SENDGRID_API_KEY
|
||||
ENV SENDGRID_API_KEY=$SENDGRID_API_KEY
|
||||
ARG STRIPE_SECRET_KEY
|
||||
ENV STRIPE_SECRET_KEY=$STRIPE_SECRET_KEY
|
||||
ARG NEXT_PUBLIC_STRIPE_PUBLIC_KEY
|
||||
ENV NEXT_PUBLIC_STRIPE_PUBLIC_KEY=$NEXT_PUBLIC_STRIPE_PUBLIC_KEY
|
||||
ARG STRIPE_LIMITED_KEY
|
||||
ENV STRIPE_LIMITED_KEY=$STRIPE_LIMITED_KEY
|
||||
ARG STRIPE_WEBHOOK_SECRET
|
||||
ENV STRIPE_WEBHOOK_SECRET=$STRIPE_WEBHOOK_SECRET
|
||||
ARG FIC_CLIENT_ID
|
||||
ENV FIC_CLIENT_ID=$FIC_CLIENT_ID
|
||||
ARG FIC_ACCESS_TOKEN
|
||||
ENV FIC_ACCESS_TOKEN=$FIC_ACCESS_TOKEN
|
||||
ARG FIC_COMPANY_ID
|
||||
ENV FIC_COMPANY_ID=$FIC_COMPANY_ID
|
||||
ARG ARUBA_USER
|
||||
ENV ARUBA_USER=$ARUBA_USER
|
||||
ARG ARUBA_PASS
|
||||
ENV ARUBA_PASS=$ARUBA_PASS
|
||||
ARG EXP_API_USER
|
||||
ENV EXP_API_USER=$EXP_API_USER
|
||||
ARG EXP_API_PASS
|
||||
ENV EXP_API_PASS=$EXP_API_PASS
|
||||
ARG KEYDB_URL
|
||||
ENV KEYDB_URL=$KEYDB_URL
|
||||
ARG TILES_URL
|
||||
ENV TILES_URL=$TILES_URL
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN SKIP_ENV_VALIDATION=1 npm run build
|
||||
|
||||
# Runner stage
|
||||
# This stage is used to run the built application
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
ARG BASE_URL
|
||||
ENV NEXT_PUBLIC_BASE_URL=$BASE_URL
|
||||
ARG INTERNAL_BASE_URL
|
||||
ENV INTERNAL_BASE_URL=$INTERNAL_BASE_URL
|
||||
ARG POSTGRES_USER
|
||||
ARG POSTGRES_PASSWORD
|
||||
ARG POSTGRES_DB
|
||||
ARG PGHOST
|
||||
ARG PGPORT
|
||||
ENV PGHOST=$PGHOST
|
||||
ENV POSTGRES_DB=$POSTGRES_DB
|
||||
ENV PGPORT=$PGPORT
|
||||
ENV POSTGRES_USER=$POSTGRES_USER
|
||||
ENV POSTGRES_PASSWORD=$POSTGRES_PASSWORD
|
||||
ARG JWT_SECRET
|
||||
ENV JWT_SECRET=$JWT_SECRET
|
||||
ARG BACKENDSERVER_URL
|
||||
ENV BACKENDSERVER_URL=$BACKENDSERVER_URL
|
||||
ARG SENDGRID_API_KEY
|
||||
ENV SENDGRID_API_KEY=$SENDGRID_API_KEY
|
||||
ARG STRIPE_SECRET_KEY
|
||||
ENV STRIPE_SECRET_KEY=$STRIPE_SECRET_KEY
|
||||
ARG NEXT_PUBLIC_STRIPE_PUBLIC_KEY
|
||||
ENV NEXT_PUBLIC_STRIPE_PUBLIC_KEY=$NEXT_PUBLIC_STRIPE_PUBLIC_KEY
|
||||
ARG STRIPE_LIMITED_KEY
|
||||
ENV STRIPE_LIMITED_KEY=$STRIPE_LIMITED_KEY
|
||||
ARG STRIPE_WEBHOOK_SECRET
|
||||
ENV STRIPE_WEBHOOK_SECRET=$STRIPE_WEBHOOK_SECRET
|
||||
ARG FIC_CLIENT_ID
|
||||
ENV FIC_CLIENT_ID=$FIC_CLIENT_ID
|
||||
ARG FIC_ACCESS_TOKEN
|
||||
ENV FIC_ACCESS_TOKEN=$FIC_ACCESS_TOKEN
|
||||
ARG FIC_COMPANY_ID
|
||||
ENV FIC_COMPANY_ID=$FIC_COMPANY_ID
|
||||
ARG ARUBA_USER
|
||||
ENV ARUBA_USER=$ARUBA_USER
|
||||
ARG ARUBA_PASS
|
||||
ENV ARUBA_PASS=$ARUBA_PASS
|
||||
ARG EXP_API_USER
|
||||
ENV EXP_API_USER=$EXP_API_USER
|
||||
ARG EXP_API_PASS
|
||||
ENV EXP_API_PASS=$EXP_API_PASS
|
||||
ARG KEYDB_URL
|
||||
ENV KEYDB_URL=$KEYDB_URL
|
||||
ARG TILES_URL
|
||||
ENV TILES_URL=$TILES_URL
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN addgroup -g 1001 -S nodejs
|
||||
RUN adduser -S nextjs -u 1001
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
ENV PORT=3000
|
||||
|
||||
CMD HOSTNAME="0.0.0.0" node server.js
|
||||
|
||||
59
apps/infoalloggi/TODO
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
NEW IDEA TODOS:
|
||||
//TODO predere privacy policy sito vecchio e adattarla
|
||||
|
||||
- trigger pag sucecss event from ordini tab and gen payment button in order detail
|
||||
|
||||
Minimal Viable Product:
|
||||
|
||||
- Msg and email queue. se è fuori orario il prop verrà notificato domani mattina
|
||||
|
||||
- tab locazioni
|
||||
|
||||
- GDPR Iubenda
|
||||
|
||||
- Translations
|
||||
|
||||
MVP Extras:
|
||||
|
||||
- admin annunci salvati: Da admin vedere indirizzo e interno nella visualizzazione annunci salvati di immobile
|
||||
- Admin annuncio link: se admin, in annuncio, collegamento rapido per copiare risposta x email con dati di tipologia, persone e decorrenza annuncio
|
||||
- info annunci/pack icon: Info Annunci/pack su navbar, icona con casetta con forse numeretti tipocontatore icone con dropdown con n annunci salvati n annunci sbloccati
|
||||
- Extensions: integrare estensioni copiare dati x miogest e packs ecc
|
||||
- Provincie options in maiuscolo
|
||||
|
||||
AFTER MVP:
|
||||
|
||||
- TODO migrazione app router https://nextjs.org/docs/pages/building-your-application/upgrading/app-router-migration#migrating-from-pages-to-app
|
||||
- Proprietari: Pagina proprietari e submit annuncio e Sezione "I miei annunci" con lista degli annunci inseriti dall'utente
|
||||
- Sezione "La mia locazione" con i dati della locazione attuale dell'utente
|
||||
- prenotazione visita, con data e ora, e conferma da parte del proprietario, con notifica a entrambi, tab con propietari, giorni e orari di disponibilità per annunci che offrono il servizio
|
||||
- Tutorials
|
||||
- lettura contatori
|
||||
- Ricezione e Rilascio immobili
|
||||
- consigli presa e rilascio appartamento
|
||||
- Third party apps: Etra, add to calendario, scarica calendario pdf
|
||||
- Report andamento props: Generate a Report with selected user review, analytics and other data for the apartment owner
|
||||
- Infoalloggi.AI: Pagina area riservata "Infoalloggi.it AI" o "la mia ricerca Infoalloggi" robe cosí in cui il sistema fa una ricerca personalizzata, elemento marketizzabile
|
||||
- Document scan: Document scan for upload
|
||||
|
||||
- indirizzo autocomplete nomatim:
|
||||
|
||||
```
|
||||
https://github.com/tomickigrzegorz/autocomplete
|
||||
function nominatim(currentValue) {
|
||||
const api = `https://nominatim.openstreetmap.org/search?format=geojson&limit=5&q=${encodeURI(
|
||||
currentValue
|
||||
)}`;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
fetch(api)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
resolve(data.features);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
45
apps/infoalloggi/TypeHelpers.type.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* interface BaseType = {
|
||||
* a: string; b: number
|
||||
* };
|
||||
*
|
||||
* interface BiggerType extends BaseType = {
|
||||
* c: string[]
|
||||
* }
|
||||
* type Bar = BiggerType;
|
||||
* // Bar = BiggerType
|
||||
*
|
||||
* type Bar = Prettify<BiggerType>;
|
||||
* // Bar = { a: string; b: number; c: string[]; }
|
||||
* ```
|
||||
*/
|
||||
export type Prettify<T> = {
|
||||
[K in keyof T]: T[K];
|
||||
} & {};
|
||||
//
|
||||
|
||||
export type PrettifyR<T> = T extends object
|
||||
? T extends infer O
|
||||
? { [K in keyof O]: PrettifyR<O[K]> }
|
||||
: never
|
||||
: T;
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* interface BaseType = {
|
||||
* a: string; b: number
|
||||
* };
|
||||
*
|
||||
* interface NewType = {
|
||||
* a: number
|
||||
* }
|
||||
*
|
||||
* type Bar = Override<BaseType, NewType>;
|
||||
* // Bar = { a: number; b: number; }
|
||||
*
|
||||
* ```
|
||||
* */
|
||||
export type Override<Type, NewType> = Omit<Type, keyof NewType> & NewType;
|
||||
50
apps/infoalloggi/check-env-vars.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
const fs = require("node:fs");
|
||||
const YAML = require("yaml");
|
||||
|
||||
const ciFilepath = ".github/workflows/ci.yml";
|
||||
const envFilepath = "src/env.mjs";
|
||||
|
||||
const ignoreEnvs = ["NODE_ENV", "SKIP_ENV_VALIDATION"];
|
||||
|
||||
// ANSI escape codes for colors
|
||||
const colors = {
|
||||
red: "\x1b[31m%s\x1b[0m",
|
||||
green: "\x1b[32m%s\x1b[0m",
|
||||
yellow: "\x1b[33m%s\x1b[0m",
|
||||
};
|
||||
|
||||
console.log();
|
||||
console.log(colors.yellow, "Environment variables checker:");
|
||||
// Read CI file content
|
||||
try {
|
||||
const ciContent = fs.readFileSync(ciFilepath, "utf8");
|
||||
const ciData = YAML.parse(ciContent);
|
||||
|
||||
// Read env file content
|
||||
const envContent = fs.readFileSync(envFilepath, "utf8");
|
||||
const envRegex = /process\.env\.([A-Z_0-9]+)/g;
|
||||
const declaredEnvs = [];
|
||||
|
||||
let match = envRegex.exec(envContent);
|
||||
while (match !== null) {
|
||||
declaredEnvs.push(match[1]);
|
||||
match = envRegex.exec(envContent); // Re-assign at the end of the loop
|
||||
}
|
||||
|
||||
// Remove ignored environment variables from the list of declared environment variables
|
||||
const filteredEnvs = declaredEnvs.filter((env) => !ignoreEnvs.includes(env));
|
||||
|
||||
// Check for missing env variables in CI file
|
||||
const missingEnvs = filteredEnvs.filter((env) => !ciData.env[env]);
|
||||
|
||||
if (missingEnvs.length > 0) {
|
||||
console.error(
|
||||
colors.red,
|
||||
`Missing environment variables in ${ciFilepath}: ${missingEnvs.join(", ")}`,
|
||||
);
|
||||
} else {
|
||||
console.log(colors.green, "No missing environment variables found.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(colors.red, `Error processing files: ${error.message}`);
|
||||
}
|
||||
21
apps/infoalloggi/components.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/styles/global.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "~/components",
|
||||
"utils": "~/lib/utils",
|
||||
"ui": "~/components/ui",
|
||||
"lib": "~/lib",
|
||||
"hooks": "~/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
7
apps/infoalloggi/dev_utils_compose.sh
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
# 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 keydb -f keydb-docker-compose.yml up -d
|
||||
63
apps/infoalloggi/docker-compose.yml
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
services:
|
||||
web:
|
||||
platform: "linux/amd64"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
INTERNAL_BASE_URL: http://web:3000
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
PGHOST: db
|
||||
PGPORT: "5432"
|
||||
BACKENDSERVER_URL: http://backend:1323
|
||||
TILES_URL: tiles:6379
|
||||
KEYDB_URL: keydb:6379
|
||||
BASE_URL: ${BASE_URL}
|
||||
SENDGRID_API_KEY: ${SENDGRID_API_KEY}
|
||||
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
|
||||
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${NEXT_PUBLIC_STRIPE_PUBLIC_KEY}
|
||||
STRIPE_LIMITED_KEY: ${STRIPE_LIMITED_KEY}
|
||||
STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET}
|
||||
FIC_CLIENT_ID: ${FIC_CLIENT_ID}
|
||||
FIC_ACCESS_TOKEN: ${FIC_ACCESS_TOKEN}
|
||||
FIC_COMPANY_ID: ${FIC_COMPANY_ID}
|
||||
ARUBA_USER: ${ARUBA_USER}
|
||||
ARUBA_PASS: ${ARUBA_PASS}
|
||||
EXP_API_USER: ${EXP_API_USER}
|
||||
EXP_API_PASS: ${EXP_API_PASS}
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
working_dir: /app
|
||||
environment:
|
||||
INTERNAL_BASE_URL: http://web:3000
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
PGHOST: db
|
||||
PGPORT: "5432"
|
||||
BACKENDSERVER_URL: http://backend:1323
|
||||
KEYDB_URL: keydb:6379
|
||||
TILES_URL: tiles:6379
|
||||
BASE_URL: ${BASE_URL}
|
||||
SENDGRID_API_KEY: ${SENDGRID_API_KEY}
|
||||
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
|
||||
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${NEXT_PUBLIC_STRIPE_PUBLIC_KEY}
|
||||
STRIPE_LIMITED_KEY: ${STRIPE_LIMITED_KEY}
|
||||
STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET}
|
||||
FIC_CLIENT_ID: ${FIC_CLIENT_ID}
|
||||
FIC_ACCESS_TOKEN: ${FIC_ACCESS_TOKEN}
|
||||
FIC_COMPANY_ID: ${FIC_COMPANY_ID}
|
||||
ARUBA_USER: ${ARUBA_USER}
|
||||
ARUBA_PASS: ${ARUBA_PASS}
|
||||
EXP_API_USER: ${EXP_API_USER}
|
||||
EXP_API_PASS: ${EXP_API_PASS}
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
networks:
|
||||
- dokploy-network
|
||||
ports:
|
||||
- "3000"
|
||||
|
||||
networks:
|
||||
dokploy-network:
|
||||
external: true
|
||||
40
apps/infoalloggi/emails/VerificaEmail.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { Button, Heading, Row, Section, Text } from "@react-email/components";
|
||||
import Base from "./base";
|
||||
export type VerificaEmail_NewMail = {
|
||||
mailType: "verificaEmail";
|
||||
props: VerificaEmailProps;
|
||||
};
|
||||
|
||||
type VerificaEmailProps = {
|
||||
token: string;
|
||||
userId: string;
|
||||
redirect?: string;
|
||||
};
|
||||
|
||||
const VerificaEmail = ({ token, userId, redirect }: VerificaEmailProps) => {
|
||||
return (
|
||||
<Base preview="Verifica la tua email" noreply>
|
||||
<>
|
||||
<Heading className="text-center text-3xl leading-8">
|
||||
Verifica la tua email
|
||||
</Heading>
|
||||
<Section className="px-4 text-center text-base md:px-12">
|
||||
<Row>
|
||||
<Text>
|
||||
Clicca sul pulsante qui sotto per verificare la tua email.
|
||||
</Text>
|
||||
</Row>
|
||||
|
||||
<Button
|
||||
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
|
||||
href={`${process.env.NEXT_PUBLIC_BASE_URL}/auth/verifica-email-action?t=${token}&u=${userId}${redirect ? `&r=${redirect}` : ""}`}
|
||||
>
|
||||
Clicca qui per verificare
|
||||
</Button>
|
||||
</Section>
|
||||
</>
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerificaEmail;
|
||||
81
apps/infoalloggi/emails/base.tsx
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import {
|
||||
Body,
|
||||
Container,
|
||||
Head,
|
||||
Html,
|
||||
Preview,
|
||||
Section,
|
||||
Text,
|
||||
Tailwind,
|
||||
Img,
|
||||
Hr,
|
||||
} from "@react-email/components";
|
||||
import type { JSX } from "react";
|
||||
|
||||
const Base = ({
|
||||
children,
|
||||
preview,
|
||||
noreply,
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
preview: string;
|
||||
noreply?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Html>
|
||||
<Head />
|
||||
|
||||
<Preview>{preview}</Preview>
|
||||
<Body className="bg-neutral-50 py-2 text-center font-sans text-base text-neutral-700">
|
||||
<Container className="max-w-xl bg-white pt-2 pb-10">
|
||||
<Img
|
||||
referrerPolicy="no-referrer"
|
||||
width={230.5}
|
||||
height={44.25}
|
||||
src={
|
||||
"https://lh3.google.com/u/0/d/1nEurjVWPiz0F8QRLhKcT4ZSujeFRChBs" //TEMP FIX
|
||||
//getMediaUrl("/Infoalloggi.png")
|
||||
}
|
||||
alt="Infoalloggi.it"
|
||||
className="mx-auto my-2 scale-80"
|
||||
/>
|
||||
|
||||
<Section className="max-w-lg px-8">
|
||||
<Hr />
|
||||
<Section className="max-w-md">{children}</Section>
|
||||
|
||||
<Section className="mt-4">
|
||||
{noreply && (
|
||||
<Text className="text-xs text-neutral-500">
|
||||
Questo è un messaggio automatico, per favore non rispondere
|
||||
a questa email.
|
||||
</Text>
|
||||
)}
|
||||
</Section>
|
||||
<Section className="mt-3">
|
||||
<Text className="text-left text-sm">
|
||||
Arcenia S.r.l. Sede legale: Via Beata Giovanna 1, Bassano del
|
||||
Grappa (VI)
|
||||
</Text>
|
||||
</Section>
|
||||
</Section>
|
||||
</Container>
|
||||
</Body>
|
||||
</Html>
|
||||
</Tailwind>
|
||||
);
|
||||
};
|
||||
export default Base;
|
||||
44
apps/infoalloggi/emails/contatto.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { Heading, Row, Section, Text } from "@react-email/components";
|
||||
import Base from "./base";
|
||||
|
||||
export type ContattoAdmin_NewMail = {
|
||||
mailType: "contattoAdminEmail";
|
||||
props: ContattoAdminEmailProps;
|
||||
};
|
||||
|
||||
type ContattoAdminEmailProps = {
|
||||
nome: string;
|
||||
cognome: string;
|
||||
email: string;
|
||||
telefono: string;
|
||||
messaggio: string;
|
||||
};
|
||||
const ContattoAdminEmail = ({
|
||||
nome,
|
||||
cognome,
|
||||
email,
|
||||
telefono,
|
||||
messaggio,
|
||||
}: ContattoAdminEmailProps) => {
|
||||
return (
|
||||
<Base preview="Contatto Email">
|
||||
<>
|
||||
<Heading className="text-center text-3xl leading-8">
|
||||
Richiesta di contatto
|
||||
</Heading>
|
||||
|
||||
<Section className="px-5 text-left text-base md:px-12">
|
||||
<Row>
|
||||
<Text>Data: {new Date().toLocaleString()}</Text>
|
||||
<Text>Nome: {nome}</Text>
|
||||
<Text>Cognome: {cognome}</Text>
|
||||
<Text>Email: {email}</Text>
|
||||
<Text>Telefono: {telefono}</Text>
|
||||
<Text>Messaggio: {messaggio}</Text>
|
||||
</Row>
|
||||
</Section>
|
||||
</>
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
export default ContattoAdminEmail;
|
||||
52
apps/infoalloggi/emails/contatto_annuncio.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { Button, Heading, Row, Section, Text } from "@react-email/components";
|
||||
import Base from "./base";
|
||||
|
||||
export type ContattoAnnuncio_NewMail = {
|
||||
mailType: "contattoAnnuncioEmail";
|
||||
props: ContattoAnnuncioEmailProps;
|
||||
};
|
||||
|
||||
type ContattoAnnuncioEmailProps = {
|
||||
nome: string;
|
||||
codice: string;
|
||||
email: string;
|
||||
telefono: string;
|
||||
messaggio: string;
|
||||
};
|
||||
const ContattoAnnuncioEmail = ({
|
||||
nome,
|
||||
codice,
|
||||
email,
|
||||
telefono,
|
||||
messaggio,
|
||||
}: ContattoAnnuncioEmailProps) => {
|
||||
return (
|
||||
<Base preview="Contatto Email">
|
||||
<>
|
||||
<Heading className="text-center text-3xl leading-8">
|
||||
Richiesta di contatto
|
||||
</Heading>
|
||||
|
||||
<Section className="px-5 text-left text-base md:px-12">
|
||||
<Row>
|
||||
<Text>Data: {new Date().toLocaleString()}</Text>
|
||||
<Text>Nome: {nome}</Text>
|
||||
<Text>Annuncio: {codice}</Text>
|
||||
<Text>Telefono: {telefono}</Text>
|
||||
<Text>Email: {email}</Text>
|
||||
<Text>Messaggio: {messaggio}</Text>
|
||||
</Row>
|
||||
</Section>
|
||||
<Section className="mb-5">
|
||||
<Button
|
||||
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
|
||||
href={`${process.env.NEXT_PUBLIC_BASE_URL}/annuncio/${codice}`}
|
||||
>
|
||||
Vai all'annuncio
|
||||
</Button>
|
||||
</Section>
|
||||
</>
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
export default ContattoAnnuncioEmail;
|
||||
71
apps/infoalloggi/emails/email-interessato.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { Heading, Row, Section, Text } from "@react-email/components";
|
||||
import Base from "./base";
|
||||
export type ContattoInteressato_NewMail = {
|
||||
mailType: "emailContattoInteressato";
|
||||
props: EmailContattoInteressatoProps;
|
||||
};
|
||||
type EmailContattoInteressatoProps = {
|
||||
nome_cognome: string;
|
||||
nome_cognome_utente: string;
|
||||
telefono: string;
|
||||
indirizzo: string | null;
|
||||
sesso: string | null;
|
||||
};
|
||||
|
||||
const EmailContattoInteressato = ({
|
||||
nome_cognome,
|
||||
nome_cognome_utente,
|
||||
telefono,
|
||||
indirizzo,
|
||||
sesso,
|
||||
}: EmailContattoInteressatoProps) => {
|
||||
const sig = sesso
|
||||
? sesso == "M"
|
||||
? "Il Sig."
|
||||
: "La Sig.ra"
|
||||
: "Il/La Sig./Sig.ra";
|
||||
return (
|
||||
<Base preview="Contatto Email">
|
||||
<>
|
||||
<Heading className="text-center text-3xl leading-8">
|
||||
Richiesta di contatto
|
||||
</Heading>
|
||||
<Section className="px-5 text-left md:px-12">
|
||||
<Row>
|
||||
<Text className="text-base">
|
||||
Gentile proprietario {nome_cognome}, le comunichiamo che a breve
|
||||
la contatterà {sig} {nome_cognome_utente} (tel: {telefono}),
|
||||
cliente Arca abbonato tramite il nostro servizio online
|
||||
Infoalloggi.it interessato a vedere il Suo immobile{" "}
|
||||
{indirizzo ? "di " + indirizzo : ""}.
|
||||
</Text>
|
||||
<Text className="text-base">
|
||||
Per ulteriori informazioni e chiarimenti non esiti a contattarci
|
||||
all'indirizzo{" "}
|
||||
<a href="mailto:web@infoalloggi.it" rel="noopener noreferrer">
|
||||
web@infoalloggi.it
|
||||
</a>{" "}
|
||||
oppure tramite tel. 04241760915.
|
||||
</Text>
|
||||
<Text className="text-base">Cordiali saluti</Text>
|
||||
<br />
|
||||
<Text className="text-xs">
|
||||
{" "}
|
||||
Le informazioni contenute in questo messaggio di posta elettronica
|
||||
sono riservate confidenziali e ne è vietata la diffusione in
|
||||
qualunque modo eseguita. Qualora Lei non fosse la persona a cui il
|
||||
presente messaggio è destinato, La invitiamo gentilmente ad
|
||||
eliminarlo dopo averne dato tempestiva comunicazione al mittente e
|
||||
a non utilizzare in alcun caso il suo contenuto. Qualsiasi
|
||||
utilizzo non autorizzato di questo messaggio e dei suoi eventuali
|
||||
allegati espone il responsabile alle relative conseguenze civili e
|
||||
penali.
|
||||
</Text>
|
||||
</Row>
|
||||
</Section>
|
||||
</>
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmailContattoInteressato;
|
||||
48
apps/infoalloggi/emails/gereric-email.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { Button, Heading, Row, Section, Text } from "@react-email/components";
|
||||
import Base from "./base";
|
||||
export type Generic_NewMail = {
|
||||
mailType: "generic";
|
||||
props: GenericProps;
|
||||
};
|
||||
|
||||
type GenericProps = {
|
||||
title: string;
|
||||
noreply?: boolean;
|
||||
testo: string;
|
||||
link?: {
|
||||
href: string;
|
||||
label?: string;
|
||||
};
|
||||
};
|
||||
|
||||
const Generic = ({
|
||||
title,
|
||||
noreply = false,
|
||||
testo,
|
||||
link = undefined,
|
||||
}: GenericProps) => {
|
||||
return (
|
||||
<Base preview={title} noreply={noreply}>
|
||||
<>
|
||||
<Heading className="text-center text-3xl leading-8">{title}</Heading>
|
||||
<Section className="px-5 text-left text-base md:px-12">
|
||||
<Row>
|
||||
<Text>{testo}</Text>
|
||||
</Row>
|
||||
{link && (
|
||||
<Section className="mb-5">
|
||||
<Button
|
||||
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
|
||||
href={`${link.href}`}
|
||||
>
|
||||
{link.label}
|
||||
</Button>
|
||||
</Section>
|
||||
)}
|
||||
</Section>
|
||||
</>
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
|
||||
export default Generic;
|
||||
102
apps/infoalloggi/emails/onboarding-servizio.tsx
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import {
|
||||
Button,
|
||||
Heading,
|
||||
Img,
|
||||
Link,
|
||||
Row,
|
||||
Section,
|
||||
Text,
|
||||
} from "@react-email/components";
|
||||
import Base from "./base";
|
||||
export type OnboardingServizio_NewMail = {
|
||||
mailType: "onboardingServizio";
|
||||
props: OnboardingServizioProps;
|
||||
};
|
||||
|
||||
type OnboardingServizioProps = {
|
||||
token: string;
|
||||
nome: string;
|
||||
annunci?: {
|
||||
titolo: string | null;
|
||||
immagine: string | null;
|
||||
codice: string;
|
||||
prezzo: number;
|
||||
}[];
|
||||
};
|
||||
|
||||
const OnboardingServizio = ({
|
||||
token,
|
||||
nome,
|
||||
annunci,
|
||||
}: OnboardingServizioProps) => {
|
||||
return (
|
||||
<Base preview="Procedi ora su Infoalloggi.it">
|
||||
<>
|
||||
<Heading className="text-center text-3xl leading-8">
|
||||
Procedi ora su Infoalloggi.it
|
||||
</Heading>
|
||||
<Section className="px-5 text-left text-base md:px-12">
|
||||
<Row>
|
||||
<Text>
|
||||
Ciao {nome},<br />
|
||||
Accedi ora al servizio di ricerca Infoalloggi, dove potrai andare
|
||||
a contattare direttamente i proprietari degli immobili
|
||||
disponibili.
|
||||
</Text>
|
||||
</Row>
|
||||
<Section className="mb-5">
|
||||
<Button
|
||||
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
|
||||
href={`${process.env.NEXT_PUBLIC_BASE_URL}/servizio/pre-onboard/${token}`}
|
||||
>
|
||||
Vai al servizio
|
||||
</Button>
|
||||
</Section>
|
||||
<Section>
|
||||
{annunci?.map((annuncio, index) => (
|
||||
<>
|
||||
<Text className="font-semibold">
|
||||
Annunci selezionati per te:
|
||||
</Text>
|
||||
<Section className="mb-[30px]" key={index}>
|
||||
<Row className="mb-[12px]">
|
||||
<Img
|
||||
src={`${process.env.NEXT_PUBLIC_BASE_URL}/go-api/images/get/${annuncio.immagine}`}
|
||||
width="100%"
|
||||
height="140px"
|
||||
alt={`Annuncio image - ${annuncio.codice}`}
|
||||
className="block w-[240px] rounded-[4px] object-cover object-center"
|
||||
/>
|
||||
</Row>
|
||||
<Row className="block w-full sm:hidden">
|
||||
<div className="mb-[5px] flex h-[24px] w-fit items-center justify-center gap-1 rounded-full bg-indigo-600 px-2 text-[12px] leading-none font-semibold text-white">
|
||||
<strong>Codice: </strong> {annuncio.codice}
|
||||
</div>
|
||||
<Heading
|
||||
as="h2"
|
||||
className="mt-0 mb-[6px] text-[16px] leading-none font-bold"
|
||||
>
|
||||
{annuncio.titolo || "N/A"}
|
||||
</Heading>
|
||||
<Text className="m-0 text-[14px] leading-[24px] text-gray-500">
|
||||
<strong>Prezzo:</strong> €
|
||||
{(annuncio.prezzo / 1e2).toFixed(2)}
|
||||
</Text>
|
||||
<Link
|
||||
href={`${process.env.NEXT_PUBLIC_BASE_URL}/annuncio/${annuncio.codice}`}
|
||||
className="block text-[14px] font-semibold text-indigo-600 no-underline"
|
||||
>
|
||||
Scheda dell'annuncio →
|
||||
</Link>
|
||||
</Row>
|
||||
</Section>
|
||||
</>
|
||||
))}
|
||||
</Section>
|
||||
</Section>
|
||||
</>
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
|
||||
export default OnboardingServizio;
|
||||
42
apps/infoalloggi/emails/pagamento-conferma.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { Button, Heading, Row, Section, Text } from "@react-email/components";
|
||||
import Base from "./base";
|
||||
export type PagamentoConferma_NewMail = {
|
||||
mailType: "pagamentoConferma";
|
||||
props: PagamentoConfermaProps;
|
||||
};
|
||||
|
||||
type PagamentoConfermaProps = {
|
||||
receiptHref: string;
|
||||
};
|
||||
|
||||
const PagamentoConferma = ({ receiptHref }: PagamentoConfermaProps) => {
|
||||
return (
|
||||
<Base preview="Conferma Pagamento">
|
||||
<>
|
||||
<Heading className="text-center text-3xl leading-8">
|
||||
Conferma Pagamento
|
||||
</Heading>
|
||||
<Section className="px-5 text-left text-base md:px-12">
|
||||
<Row>
|
||||
<Text>
|
||||
Gentile Cliente, la informiamo che il pagamento è stato confermato
|
||||
con successo. Procederemo ad attivare i servizi acquistati se non
|
||||
lo fossero già. Può controllare lo stato del suo account nella
|
||||
sezione dedicata.
|
||||
</Text>
|
||||
</Row>
|
||||
<Section className="mb-5">
|
||||
<Button
|
||||
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
|
||||
href={receiptHref}
|
||||
>
|
||||
Vai al tuo account
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
</>
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
|
||||
export default PagamentoConferma;
|
||||
36
apps/infoalloggi/emails/pagamento-errore.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { Button, Heading, Row, Section, Text } from "@react-email/components";
|
||||
import Base from "./base";
|
||||
export type PagamentoErrore_NewMail = {
|
||||
mailType: "pagamentoErrore";
|
||||
};
|
||||
|
||||
const PagamentoErrore = () => {
|
||||
return (
|
||||
<Base preview="Errore Pagamento">
|
||||
<>
|
||||
<Heading className="text-center text-3xl leading-8">
|
||||
Errore Pagamento
|
||||
</Heading>
|
||||
<Section className="px-5 text-left text-base md:px-12">
|
||||
<Row>
|
||||
<Text>
|
||||
Gentile Cliente, la informiamo che il pagamento non è andato a
|
||||
buon fine. Si prega di riprovare o contattare il nostro servizio
|
||||
clienti per ulteriori informazioni.
|
||||
</Text>
|
||||
</Row>
|
||||
<Section className="mb-5">
|
||||
<Button
|
||||
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
|
||||
href={`${process.env.NEXT_PUBLIC_BASE_URL}/area-riservata/dashboard`}
|
||||
>
|
||||
Vai al tuo account
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
</>
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
|
||||
export default PagamentoErrore;
|
||||
36
apps/infoalloggi/emails/pagamento-ricezione.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { Button, Heading, Row, Section, Text } from "@react-email/components";
|
||||
import Base from "./base";
|
||||
export type PagamentoRicezione_NewMail = {
|
||||
mailType: "pagamentoRicezione";
|
||||
};
|
||||
|
||||
const PagamentoRicezione = () => {
|
||||
return (
|
||||
<Base preview="Ricezione Pagamento">
|
||||
<>
|
||||
<Heading className="text-center text-3xl leading-8">
|
||||
Ricezione Pagamento
|
||||
</Heading>
|
||||
<Section className="px-5 text-left text-base md:px-12">
|
||||
<Row>
|
||||
<Text>
|
||||
Gentile Cliente, le confermiamo la ricezione del pagamento. Le
|
||||
invieremo una mail di conferma quando il pagamento sarà stato
|
||||
processato.
|
||||
</Text>
|
||||
</Row>
|
||||
<Section className="mb-5">
|
||||
<Button
|
||||
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
|
||||
href={`${process.env.NEXT_PUBLIC_BASE_URL}/area-riservata/dashboard`}
|
||||
>
|
||||
Vai al tuo account
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
</>
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
|
||||
export default PagamentoRicezione;
|
||||
39
apps/infoalloggi/emails/pw-reset-link.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { Heading, Row, Section, Text, Button } from "@react-email/components";
|
||||
import Base from "./base";
|
||||
export type PwResetLink_NewMail = {
|
||||
mailType: "pwResetLink";
|
||||
props: PwResetLinkProps;
|
||||
};
|
||||
|
||||
type PwResetLinkProps = {
|
||||
resetlink: string;
|
||||
};
|
||||
const PwResetLink = ({ resetlink }: PwResetLinkProps) => {
|
||||
return (
|
||||
<Base preview="Link Reset Password">
|
||||
<>
|
||||
<Heading className="text-center text-3xl leading-8">
|
||||
Link Reset Password
|
||||
</Heading>
|
||||
<Section className="px-5 text-left text-base md:px-12">
|
||||
<Row>
|
||||
<Text>
|
||||
Abbiamo ricevuto la tua richiesta di reset password, clicca sul
|
||||
link sottostante per procedere.
|
||||
</Text>
|
||||
</Row>
|
||||
<Section className="mb-5">
|
||||
<Button
|
||||
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
|
||||
href={resetlink}
|
||||
>
|
||||
Clicca qui per il reset della password
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
</>
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
|
||||
export default PwResetLink;
|
||||
39
apps/infoalloggi/emails/recesso.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { Heading, Row, Section, Text } from "@react-email/components";
|
||||
import Base from "./base";
|
||||
export type Recesso_NewMail = {
|
||||
mailType: "recesso";
|
||||
};
|
||||
const Recesso = () => {
|
||||
return (
|
||||
<Base preview="Richiesta di recesso">
|
||||
<>
|
||||
<Heading className="text-center text-3xl leading-8">
|
||||
Richiesta di recesso
|
||||
</Heading>
|
||||
<Section className="px-5 text-left text-base md:px-12">
|
||||
<Row>
|
||||
<Text>
|
||||
Gentile cliente, abbiamo ricevuto la tua richiesta di recesso.
|
||||
Procederemo ad elaborare la richiesta e termineremo i servizi
|
||||
attualemte attivi nel tuo account.
|
||||
</Text>
|
||||
<Text>
|
||||
Per ulteriori informazioni e chiarimenti non esiti a contattarci
|
||||
all'indirizzo{" "}
|
||||
<a
|
||||
href="mailto:amministrazione@infoalloggi.it"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
amministrazione@infoalloggi.it{" "}
|
||||
</a>
|
||||
</Text>
|
||||
<Text>Cordiali saluti</Text>
|
||||
<br />
|
||||
</Row>
|
||||
</Section>
|
||||
</>
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
|
||||
export default Recesso;
|
||||
35
apps/infoalloggi/emails/registrazione-avvenuta.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { Button, Heading, Row, Section, Text } from "@react-email/components";
|
||||
import Base from "./base";
|
||||
export type RegistrazioneAvvenuta_NewMail = {
|
||||
mailType: "registrazioneAvvenuta";
|
||||
};
|
||||
|
||||
const RegistrazioneAvvenuta = () => {
|
||||
return (
|
||||
<Base preview="Registrazione Avvenuta">
|
||||
<>
|
||||
<Heading className="text-center text-3xl leading-8">
|
||||
Registrazione avvenuta
|
||||
</Heading>
|
||||
<Section className="px-5 text-left text-base md:px-12">
|
||||
<Row>
|
||||
<Text>
|
||||
La tua registrazione è avvenuta con successo! Ora puoi accedere al
|
||||
nostro sito con le tue credenziali.
|
||||
</Text>
|
||||
</Row>
|
||||
<Section className="mb-5">
|
||||
<Button
|
||||
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
|
||||
href={`${process.env.NEXT_PUBLIC_BASE_URL}/area-riservata/dashboard`}
|
||||
>
|
||||
Vai al tuo account
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
</>
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegistrazioneAvvenuta;
|
||||
BIN
apps/infoalloggi/emails/static/Infoalloggi.png
Normal file
|
After Width: | Height: | Size: 6 KiB |
152
apps/infoalloggi/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import react from "eslint-plugin-react";
|
||||
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
||||
import pluginQuery from "@tanstack/eslint-plugin-query";
|
||||
import kyselyRules from "./kyselyRules/plugin.js";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import js from "@eslint/js";
|
||||
import noRelativeImportPaths from "eslint-plugin-no-relative-import-paths";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
import jsxA11y from "eslint-plugin-jsx-a11y";
|
||||
import eslintPluginBetterTailwindcss from "eslint-plugin-better-tailwindcss";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all,
|
||||
});
|
||||
|
||||
export default [
|
||||
...pluginQuery.configs["flat/recommended"],
|
||||
|
||||
{
|
||||
ignores: [
|
||||
"**/.next",
|
||||
"**/out",
|
||||
"**/public",
|
||||
"**/node_modules/",
|
||||
"**/check-env-vars.js",
|
||||
"**/headers/",
|
||||
"**/schemas/",
|
||||
"**/kyselyRules/",
|
||||
"prettier.config.cjs",
|
||||
"postcss.config.cjs",
|
||||
".kanelrc.js",
|
||||
],
|
||||
},
|
||||
...compat.extends(
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@next/next/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/recommended-type-checked",
|
||||
"plugin:@typescript-eslint/stylistic-type-checked",
|
||||
"plugin:jsx-a11y/recommended",
|
||||
),
|
||||
{
|
||||
plugins: {
|
||||
react,
|
||||
"@typescript-eslint": typescriptEslint,
|
||||
"better-tailwindcss": eslintPluginBetterTailwindcss,
|
||||
"jsx-a11y": jsxA11y,
|
||||
kyselyRules: kyselyRules,
|
||||
//"no-relative-import-paths": noRelativeImportPaths,
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
ecmaVersion: 5,
|
||||
sourceType: "script",
|
||||
|
||||
parserOptions: {
|
||||
project: true,
|
||||
tsconfigRootDir: "./",
|
||||
},
|
||||
},
|
||||
linterOptions: {
|
||||
reportUnusedDisableDirectives: true,
|
||||
},
|
||||
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
|
||||
"import/resolver": {
|
||||
typescript: {
|
||||
alwaysTryTypes: true,
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
},
|
||||
"better-tailwindcss": {
|
||||
entryPoint: "src/styles/globals.css",
|
||||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
"react/prop-types": "off",
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/array-type": "off",
|
||||
"@typescript-eslint/no-redundant-type-constituents": "warn",
|
||||
"@typescript-eslint/consistent-type-definitions": "off",
|
||||
"@typescript-eslint/prefer-optional-chain": "off",
|
||||
"@typescript-eslint/prefer-nullish-coalescing": "off",
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"@typescript-eslint/unbound-method": "off",
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
"warn",
|
||||
{
|
||||
prefer: "type-imports",
|
||||
fixStyle: "inline-type-imports",
|
||||
},
|
||||
],
|
||||
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
|
||||
"@typescript-eslint/require-await": "off",
|
||||
"@typescript-eslint/switch-exhaustiveness-check": "warn",
|
||||
|
||||
"@typescript-eslint/no-misused-promises": [
|
||||
"error",
|
||||
{
|
||||
checksVoidReturn: {
|
||||
attributes: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
"@next/next/no-img-element": "warn",
|
||||
"kyselyRules/enforce-select": "error",
|
||||
"kyselyRules/enforce-where": "error",
|
||||
"kyselyRules/enforce-null": "error",
|
||||
...eslintPluginBetterTailwindcss.configs["recommended-warn"]?.rules,
|
||||
...eslintPluginBetterTailwindcss.configs["recommended-error"]?.rules,
|
||||
"better-tailwindcss/enforce-consistent-line-wrapping": "off",
|
||||
"better-tailwindcss/enforce-consistent-class-order": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
//ignores: ["**/emails/**/*.tsx", "next.config.js"],
|
||||
files: ["src/**/*.tsx", "src/**/*.ts"],
|
||||
plugins: {
|
||||
"no-relative-import-paths": noRelativeImportPaths,
|
||||
},
|
||||
rules: {
|
||||
"no-relative-import-paths/no-relative-import-paths": [
|
||||
"warn",
|
||||
{ rootDir: "src", prefix: "~" },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
13
apps/infoalloggi/headers/dev-https.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
console.log(
|
||||
"\x1b[34m%s\x1b[0m",
|
||||
`
|
||||
____ _______ __ _ _ _____ _____ ____ ____
|
||||
| _ \\| ____\\ \\ / / | | | |_ _|_ _| _ \\/ ___|
|
||||
| | | | _| \\ \\ / / | |_| | | | | | | |_) \\___ \\
|
||||
| |_| | |___ \\ V / | _ | | | | | | __/ ___) |
|
||||
|____/|_____| \\_/ |_| |_| |_| |_| |_| |____/
|
||||
|
||||
|
||||
🩺 Running Development...
|
||||
`,
|
||||
);
|
||||
13
apps/infoalloggi/headers/dev.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
console.log(
|
||||
"\x1b[32m%s\x1b[0m",
|
||||
`
|
||||
____ _______ __
|
||||
| _ \\| ____\\ \\ / /
|
||||
| | | | _| \\ \\ / /
|
||||
| |_| | |___ \\ V /
|
||||
|____/|_____| \\_/
|
||||
|
||||
|
||||
🩺 Running Development...
|
||||
`,
|
||||
);
|
||||
13
apps/infoalloggi/headers/lint.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
console.log(
|
||||
"\x1b[33m%s\x1b[0m",
|
||||
`
|
||||
_ _ _
|
||||
| | (_) | |
|
||||
| | _ _ __ | |_
|
||||
| | | | '_ \\| __|
|
||||
| |____| | | | | |_
|
||||
\\_____/|_|_| |_|\\__|
|
||||
|
||||
🔍 Linting and Env Checks...
|
||||
`,
|
||||
);
|
||||
13
apps/infoalloggi/headers/pg-typegen.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
console.log(
|
||||
"\x1b[36m%s\x1b[0m",
|
||||
`
|
||||
____ ____ _____
|
||||
| _ \\ / ___| |_ _| _ _ __ ___ __ _ ___ _ __
|
||||
| |_) | | _ | || | | | \'_ \\ / _ \\/ _\` |/ _ \\ \'_ \\
|
||||
| __/| |_| | | || |_| | |_) | __/ (_| | __/ | | |
|
||||
|_| \\____| |_| \\__, | .__/ \\___|\\__, |\\___|_| |_|
|
||||
|___/|_| |___/
|
||||
|
||||
📢 Importing Postgres schema...
|
||||
`,
|
||||
);
|
||||
13
apps/infoalloggi/headers/type.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
console.log(
|
||||
"\x1b[31m%s\x1b[0m",
|
||||
`
|
||||
_____ ____ _ _
|
||||
|_ _| _ _ __ ___ / ___| |__ ___ ___| | __
|
||||
| || | | | \'_ \\ / _ \\ | | | \'_ \\ / _ \\/ __| |/ /
|
||||
| || |_| | |_) | __/ | |___| | | | __/ (__| <
|
||||
|_| \\__, | .__/ \\___| \\____|_| |_|\\___|\\___|_|\\_\\
|
||||
|___/|_|
|
||||
|
||||
🌶️ Typescript type check...
|
||||
`,
|
||||
);
|
||||
21
apps/infoalloggi/keydb-docker-compose.yml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
services:
|
||||
tiles:
|
||||
image: eqalpha/keydb:latest
|
||||
volumes:
|
||||
- tiles-data:/data
|
||||
ports:
|
||||
- "6379:6379"
|
||||
command: keydb-server /etc/keydb/keydb.conf --maxmemory 100mb --maxmemory-policy volatile-lru --maxmemory-samples 5
|
||||
restart: unless-stopped
|
||||
keydb:
|
||||
image: eqalpha/keydb:latest
|
||||
volumes:
|
||||
- keydb-data:/data
|
||||
ports:
|
||||
- "6380:6379"
|
||||
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
tiles-data:
|
||||
keydb-data:
|
||||
57
apps/infoalloggi/knip.json
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"$schema": "https://unpkg.com/knip@5/schema.json",
|
||||
"project": [
|
||||
"src/**",
|
||||
"**",
|
||||
"!src/schemas/public/*.ts",
|
||||
"!emails/**"
|
||||
],
|
||||
"ignore": [
|
||||
".kanelrc.js",
|
||||
".eslintrc.json",
|
||||
"TypeHelpers.type.ts",
|
||||
"src/utils/api.ts",
|
||||
"src/server/api/trpc.ts",
|
||||
"check-env-vars.js",
|
||||
"src/components/ui/*",
|
||||
"src/hooks/coordDistance.ts",
|
||||
"src/hooks/useMobile.tsx",
|
||||
"src/lib/nossr.tsx",
|
||||
"src/lib/useIsTyping.ts",
|
||||
"src/middlewares/apis_middleware.ts",
|
||||
"src/server/auth.ts",
|
||||
"src/utils/get-media-url.ts",
|
||||
"src/utils/utils.ts"
|
||||
],
|
||||
"ignoreBinaries": [
|
||||
"stripe"
|
||||
],
|
||||
"ignoreDependencies": [
|
||||
"kysely-plugin-serialize",
|
||||
"sharp",
|
||||
"uuid",
|
||||
"@react-email/components",
|
||||
"@eslint/compat",
|
||||
"@next/eslint-plugin-next",
|
||||
"@prettier/sync",
|
||||
"@types/eslint",
|
||||
"@types/uuid",
|
||||
"eslint-config-next",
|
||||
"eslint-import-resolver-typescript",
|
||||
"eslint-plugin-import",
|
||||
"eslint-plugin-react-hooks",
|
||||
"kanel-kysely",
|
||||
"npm-run-all",
|
||||
"typescript-eslint",
|
||||
"@react-email/preview-server",
|
||||
"@tailwindcss/forms",
|
||||
"@tailwindcss/typography",
|
||||
"tailwind-scrollbar",
|
||||
"tw-animate-css",
|
||||
"@radix-ui/react-dialog",
|
||||
"@radix-ui/react-label",
|
||||
"@radix-ui/react-slot",
|
||||
"@radix-ui/react-tooltip",
|
||||
"@radix-ui/react-popover"
|
||||
]
|
||||
}
|
||||
15
apps/infoalloggi/kyselyRules/plugin.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
const plugin = {
|
||||
meta: {
|
||||
name: "kyselyRules",
|
||||
version: "1.0.0",
|
||||
},
|
||||
configs: {},
|
||||
rules: {
|
||||
"enforce-select": require("./rules/select"),
|
||||
"enforce-where": require("./rules/where"),
|
||||
"enforce-null": require("./rules/null"),
|
||||
},
|
||||
processors: {},
|
||||
};
|
||||
|
||||
module.exports = plugin;
|
||||
107
apps/infoalloggi/kyselyRules/rules/null.js
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
//@ts-nocheck
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description:
|
||||
"Ensure that all where condition that thest for null values utilize the correct operators.",
|
||||
},
|
||||
fixable: "code",
|
||||
schema: [], // no options
|
||||
},
|
||||
create(context) {
|
||||
const sourceCode = context.sourceCode;
|
||||
const reportedNodes = new Set();
|
||||
if (!/\.where\(/.test(sourceCode.text)) {
|
||||
// Skip
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
ExpressionStatement(node) {
|
||||
evaluate(node.expression, sourceCode.getTokens(node));
|
||||
},
|
||||
AwaitExpression(node) {
|
||||
evaluate(node.argument, sourceCode.getTokens(node));
|
||||
},
|
||||
};
|
||||
|
||||
function Check(expression, operator) {
|
||||
const forbiddenOperators = [
|
||||
"'='",
|
||||
"'=='",
|
||||
"'!='",
|
||||
"'!=='",
|
||||
'"="',
|
||||
'"=="',
|
||||
'"!="',
|
||||
'"!=="',
|
||||
];
|
||||
|
||||
if (forbiddenOperators.includes(operator.value)) {
|
||||
const message = `The where condition is testing for null using the ${operator.value} operator. Ensure this is intentional and uses the correct operator.`;
|
||||
|
||||
context.report({
|
||||
node: expression,
|
||||
message,
|
||||
fix: (fixer) => {
|
||||
const correctOperator =
|
||||
operator.value === "'='" ||
|
||||
operator.value === "'=='" ||
|
||||
operator.value === '"="' ||
|
||||
operator.value === '"=="'
|
||||
? "'is'"
|
||||
: "'is not'";
|
||||
return fixer.replaceText(operator, correctOperator);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
function findNodes(expression, node, nodes) {
|
||||
const nullNodes = nodes.filter((token) => token.value === "null");
|
||||
if (nullNodes.length === 0) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < nullNodes.length; i++) {
|
||||
const nullNode = nullNodes[i];
|
||||
|
||||
// Avoid duplicate reports
|
||||
if (reportedNodes.has(nullNode)) {
|
||||
continue;
|
||||
}
|
||||
reportedNodes.add(nullNode);
|
||||
|
||||
const operator = sourceCode.getTokensBefore(nullNode, 2)[0];
|
||||
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
Check(expression, operator);
|
||||
}
|
||||
}
|
||||
|
||||
function evaluate(expression, nodes) {
|
||||
if (!expression || expression.type !== "CallExpression") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const whereNodes = nodes.filter((token) => token.value === "where");
|
||||
|
||||
if (whereNodes.length === 0 || !whereNodes) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (whereNodes.length === 1) {
|
||||
findNodes(expression, whereNodes[0], nodes);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < whereNodes.length; i++) {
|
||||
const whereNode = whereNodes[i];
|
||||
findNodes(expression, whereNode, nodes);
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
};
|
||||
55
apps/infoalloggi/kyselyRules/rules/null.test.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
const { RuleTester } = require("eslint");
|
||||
const rule = require("./null");
|
||||
|
||||
const ruleTester = new RuleTester({
|
||||
languageOptions: { ecmaVersion: 2015 },
|
||||
});
|
||||
|
||||
ruleTester.run(
|
||||
"enforce-null", // rule name
|
||||
rule,
|
||||
{
|
||||
valid: [
|
||||
{
|
||||
code: "trx.selectFrom('name').select(['field']).where('something', 'is', null).execute()",
|
||||
},
|
||||
{
|
||||
code: "trx.selectFrom('name').select(['field']).where('something', 'is not', null).execute()",
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: "trx.selectFrom('name').select(['field']).where('something', '=', null).execute()",
|
||||
errors: 1,
|
||||
output:
|
||||
"trx.selectFrom('name').select(['field']).where('something', 'is', null).execute()",
|
||||
},
|
||||
{
|
||||
code: "trx.selectFrom('name').select(['field']).where('something', '==', null).execute()",
|
||||
errors: 1,
|
||||
output:
|
||||
"trx.selectFrom('name').select(['field']).where('something', 'is', null).execute()",
|
||||
},
|
||||
{
|
||||
code: "trx.selectFrom('name').select(['field']).where('something', '!=', null).execute()",
|
||||
errors: 1,
|
||||
output:
|
||||
"trx.selectFrom('name').select(['field']).where('something', 'is not', null).execute()",
|
||||
},
|
||||
{
|
||||
code: "trx.selectFrom('name').select(['field']).where('something', '!==', null).execute()",
|
||||
errors: 1,
|
||||
output:
|
||||
"trx.selectFrom('name').select(['field']).where('something', 'is not', null).execute()",
|
||||
},
|
||||
{
|
||||
code: "trx.selectFrom('name').select(['field']).where('something', 'is', null).where('something2', '=', null).execute()",
|
||||
errors: 1,
|
||||
output:
|
||||
"trx.selectFrom('name').select(['field']).where('something', 'is', null).where('something2', 'is', null).execute()",
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
console.log("EnforceNull: All tests passed!");
|
||||
61
apps/infoalloggi/kyselyRules/rules/select.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
//@ts-nocheck
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
docs: {
|
||||
description: "Description of the rule",
|
||||
},
|
||||
fixable: "code",
|
||||
schema: [], // no options
|
||||
},
|
||||
create(context) {
|
||||
const sourceCode = context.sourceCode;
|
||||
if (!/\.selectFrom\(/.test(sourceCode.text)) {
|
||||
// Skip
|
||||
return {};
|
||||
}
|
||||
function evaluate(expression, nodes) {
|
||||
if (!expression || expression.type !== "CallExpression") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const hasSelectFrom = nodes.some((token) => token.value === "selectFrom");
|
||||
|
||||
if (hasSelectFrom) {
|
||||
const hasSelect = nodes.some(
|
||||
(token) => token.value === "select" || token.value === "selectAll",
|
||||
);
|
||||
|
||||
if (!hasSelect) {
|
||||
let closingParenthesisNode = null;
|
||||
const selectNodeIndex = nodes.findIndex(
|
||||
(token) => token.value === "selectFrom",
|
||||
);
|
||||
if (selectNodeIndex !== -1) {
|
||||
closingParenthesisNode = nodes
|
||||
.slice(selectNodeIndex)
|
||||
.find((token) => token.value === ")");
|
||||
}
|
||||
context.report({
|
||||
node: expression,
|
||||
message: `Call chains containing "selectFrom" must also include a "select" or "selectAll" clause.`,
|
||||
fix: (fixer) => {
|
||||
return fixer.insertTextAfter(
|
||||
closingParenthesisNode || nodes[6],
|
||||
".selectAll()",
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
ExpressionStatement(node) {
|
||||
evaluate(node.expression, sourceCode.getTokens(node));
|
||||
},
|
||||
AwaitExpression(node) {
|
||||
evaluate(node.argument, sourceCode.getTokens(node));
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
31
apps/infoalloggi/kyselyRules/rules/select.test.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
const { RuleTester } = require("eslint");
|
||||
const rule = require("./select");
|
||||
|
||||
const ruleTester = new RuleTester({
|
||||
languageOptions: { ecmaVersion: 2015 },
|
||||
});
|
||||
|
||||
ruleTester.run(
|
||||
"enforce-select", // rule name
|
||||
rule,
|
||||
{
|
||||
valid: [
|
||||
{
|
||||
code: "trx.selectFrom('name').select(['field']).where('something', '=', something).execute()",
|
||||
},
|
||||
{
|
||||
code: "trx.selectFrom('name').selectAll().where('something', '=', something).execute()",
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: "trx.selectFrom('name').where('something', '=', something).execute()",
|
||||
errors: 1,
|
||||
output:
|
||||
"trx.selectFrom('name').selectAll().where('something', '=', something).execute()",
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
console.log("EnforceSelect: All tests passed!");
|
||||
52
apps/infoalloggi/kyselyRules/rules/where.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
//@ts-nocheck
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description:
|
||||
"Ensures that any call chain which affects a table also includes a 'where' clause.",
|
||||
},
|
||||
fixable: "code",
|
||||
schema: [], // no options
|
||||
},
|
||||
create(context) {
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
if (!/\.updateTable\(|\.deleteFrom\(/.test(sourceCode.text)) {
|
||||
// Skip
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
ExpressionStatement(node) {
|
||||
evaluate(node.expression, sourceCode.getTokens(node));
|
||||
},
|
||||
AwaitExpression(node) {
|
||||
evaluate(node.argument, sourceCode.getTokens(node));
|
||||
},
|
||||
};
|
||||
|
||||
function evaluate(expression, nodes) {
|
||||
if (!expression || expression.type !== "CallExpression") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const hasUpdateOrDelete = nodes.some(
|
||||
(token) =>
|
||||
token.value === "updateTable" || token.value === "deleteFrom",
|
||||
);
|
||||
|
||||
if (hasUpdateOrDelete) {
|
||||
const hasWhere = nodes.some((token) => token.value === "where");
|
||||
|
||||
if (!hasWhere) {
|
||||
const message = `Call chains containing "updateTable" or "deleteFrom" must also include a "where" clause.`;
|
||||
context.report({
|
||||
node: expression,
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
26
apps/infoalloggi/kyselyRules/rules/where.test.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
const { RuleTester } = require("eslint");
|
||||
const rule = require("./where");
|
||||
|
||||
const ruleTester = new RuleTester({
|
||||
languageOptions: { ecmaVersion: 2015 },
|
||||
});
|
||||
|
||||
ruleTester.run(
|
||||
"enforce-where", // rule name
|
||||
rule,
|
||||
{
|
||||
valid: [
|
||||
{
|
||||
code: "trx.updateTable('name').set({foo:bar}).where('something', '=', something).execute()",
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: "trx.updateTable('name').set({foo:bar}).execute()",
|
||||
errors: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
console.log("EnforceWhere: All tests passed!");
|
||||
88
apps/infoalloggi/next.config.mjs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation.
|
||||
* This is especially useful for Docker builds.
|
||||
*/
|
||||
|
||||
import { env } from "node:process";
|
||||
|
||||
/** @type {import("next").NextConfig} */
|
||||
const nextConfig = {
|
||||
output: env.NODE_ENV === "production" ? "standalone" : undefined, // This is for docker builds
|
||||
reactStrictMode: true,
|
||||
i18n: {
|
||||
locales: ["it", "en"],
|
||||
defaultLocale: "it",
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "*.googleusercontent.com",
|
||||
port: "",
|
||||
pathname: "**",
|
||||
},
|
||||
{
|
||||
hostname: env.BACKENDSERVER_URL || "localhost:1323",
|
||||
},
|
||||
],
|
||||
minimumCacheTTL: 86400, // 1 day in seconds
|
||||
},
|
||||
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
transpilePackages: ["jotai-devtools"],
|
||||
devIndicators: false,
|
||||
rewrites: async () => {
|
||||
return [
|
||||
{
|
||||
source: "/go-api/images/get/:slug*",
|
||||
destination: env.BACKENDSERVER_URL + "/images/get/:slug*",
|
||||
},
|
||||
{
|
||||
source: "/go-api/storage/get/:slug*",
|
||||
destination: env.BACKENDSERVER_URL + "/storage/get/:slug*",
|
||||
has: [
|
||||
{ type: "cookie", key: "access_token" },
|
||||
{
|
||||
type: "query",
|
||||
key: "token",
|
||||
value:
|
||||
"(?<token>^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
source: "/go-api/storage/upload:slug*",
|
||||
destination: env.BACKENDSERVER_URL + "/storage/upload:slug*",
|
||||
has: [
|
||||
{ type: "cookie", key: "access_token" },
|
||||
{
|
||||
type: "query",
|
||||
key: "token",
|
||||
value:
|
||||
"(?<token>^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$)",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
headers: async () => {
|
||||
return [
|
||||
{
|
||||
source: "/:path*",
|
||||
headers: [
|
||||
{
|
||||
key: "X-Frame-Options",
|
||||
value: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
19903
apps/infoalloggi/package-lock.json
generated
Normal file
146
apps/infoalloggi/package.json
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
{
|
||||
"name": "infoalloggi2",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"dev": "node ./headers/dev.js && next dev",
|
||||
"dev-https": "node ./headers/dev-https.js && cross-env NEXT_PUBLIC_BASE_URL='https://localhost:3000' INTERNAL_BASE_URL='https://localhost:3000' next dev --experimental-https",
|
||||
"idev": "cross-env NODE_OPTIONS='--inspect' next dev",
|
||||
"lint": "node ./headers/lint.js && next lint && node check-env-vars.js",
|
||||
"start": "next start",
|
||||
"types": "node ./headers/type.js && tsc --noEmit",
|
||||
"pg-typegen": "node ./headers/pg-typegen.js && npx kanel",
|
||||
"email": "cross-env NEXT_PUBLIC_BASE_URL='https://localhost:3000' email dev --port 3500",
|
||||
"test": "npm run lint && npm run types",
|
||||
"knip": "knip --no-exit-code",
|
||||
"stripe-dev": "stripe listen --forward-to http://localhost:3000/api/stripe-webhook",
|
||||
"d": "npm run-p dev stripe-dev -cl",
|
||||
"test-kr": "node ./kyselyRules/rules/select.test.js && node ./kyselyRules/rules/where.test.js&& node ./kyselyRules/rules/null.test.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fattureincloud/fattureincloud-ts-sdk": "^2.1.1",
|
||||
"@hookform/resolvers": "^5.1.1",
|
||||
"@radix-ui/react-popover": "^1.1.14",
|
||||
"@radix-ui/react-scroll-area": "^1.2.9",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-toggle-group": "^1.1.10",
|
||||
"@react-email/components": "^0.3.2",
|
||||
"@react-email/render": "^1.1.3",
|
||||
"@stripe/react-stripe-js": "^3.7.0",
|
||||
"@stripe/stripe-js": "^7.5.0",
|
||||
"@tanstack/react-query": "^5.74.4",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@tiptap/extension-underline": "^2.23.0",
|
||||
"@tiptap/pm": "^2.25.0",
|
||||
"@tiptap/react": "^2.23.0",
|
||||
"@tiptap/starter-kit": "^2.25.0",
|
||||
"@trpc/client": "^11.1.0",
|
||||
"@trpc/next": "^11.4.3",
|
||||
"@trpc/react-query": "^11.1.0",
|
||||
"@trpc/server": "^11.1.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"cookies-next": "^5.1.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"framer-motion": "^12.8.0",
|
||||
"frimousse": "^0.3.0",
|
||||
"ioredis": "^5.6.1",
|
||||
"jose": "^6.0.12",
|
||||
"js-cookie": "^3.0.5",
|
||||
"kysely": "^0.28.2",
|
||||
"kysely-plugin-serialize": "^0.8.2",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet-defaulticon-compatibility": "^0.1.2",
|
||||
"lucide-react": "^0.525.0",
|
||||
"next": "^15.3.4",
|
||||
"next-themes": "^0.4.6",
|
||||
"nodemailer": "^7.0.5",
|
||||
"nodemailer-html-to-text": "^3.2.0",
|
||||
"nuqs": "^2.4.3",
|
||||
"pg": "^8.16.3",
|
||||
"radix-ui": "^1.4.2",
|
||||
"react": "^19.1.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-day-picker": "^9.8.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-email": "^4.2.3",
|
||||
"react-hook-form": "^7.60.0",
|
||||
"react-hot-toast": "^2.5.2",
|
||||
"react-is": "^19.1.0",
|
||||
"react-leaflet": "^5.0.0",
|
||||
"react-phone-number-input": "^3.4.12",
|
||||
"react-reverse-portal": "^2.3.0",
|
||||
"react-select": "^5.10.2",
|
||||
"react-to-print": "^3.1.1",
|
||||
"react-use": "^17.6.0",
|
||||
"recharts": "^2.15.4",
|
||||
"sharp": "^0.34.3",
|
||||
"stripe": "^18.3.0",
|
||||
"superjson": "2.2.2",
|
||||
"tailwind-merge": "2.4",
|
||||
"trpc-ui": "^1.0.15",
|
||||
"type-fest": "^4.41.0",
|
||||
"uuid": "^11.1.0",
|
||||
"vaul": "^1.1.2",
|
||||
"yaml": "^2.8.0",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.3.1",
|
||||
"@eslint/eslintrc": "^3.3.0",
|
||||
"@eslint/js": "^9.30.0",
|
||||
"@hookform/devtools": "^4.4.0",
|
||||
"@next/eslint-plugin-next": "^15.3.4",
|
||||
"@prettier/sync": "^0.6.1",
|
||||
"@react-email/preview-server": "^4.2.3",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tanstack/eslint-plugin-query": "^5.81.2",
|
||||
"@total-typescript/ts-reset": "^0.6.1",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@types/eslint-plugin-jsx-a11y": "^6.10.0",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/leaflet": "^1.9.20",
|
||||
"@types/node": "^24.0.7",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/nodemailer-html-to-text": "^3.1.3",
|
||||
"@types/pg": "^8.15.4",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@types/react-is": "^19.0.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
||||
"@typescript-eslint/parser": "^8.31.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^9.30.1",
|
||||
"eslint-config-next": "^15.3.4",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-better-tailwindcss": "^3.5.1",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-no-relative-import-paths": "^1.6.1",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"jiti": "^2.4.2",
|
||||
"kanel": "^3.14.1",
|
||||
"kanel-kysely": "^0.7.1",
|
||||
"knip": "^5.62.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-classnames": "^0.8.1",
|
||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||
"tailwind-scrollbar": "^4.0.2",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"tw-animate-css": "^1.3.5",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.35.0"
|
||||
},
|
||||
"ct3aMetadata": {
|
||||
"initVersion": "7.9.0"
|
||||
}
|
||||
}
|
||||
15
apps/infoalloggi/pgadmin-docker-compose.yml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
services:
|
||||
pgadmin:
|
||||
image: dpage/pgadmin4:latest
|
||||
container_name: pgadmin
|
||||
environment:
|
||||
PGADMIN_DEFAULT_EMAIL: m.pedone98@gmail.com
|
||||
PGADMIN_DEFAULT_PASSWORD: rootpost
|
||||
ports:
|
||||
- "5050:80"
|
||||
volumes:
|
||||
- pgadmin_data:/var/lib/pgadmin
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
pgadmin_data:
|
||||
9
apps/infoalloggi/postcss.config.cjs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/* eslint-env node */
|
||||
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
10
apps/infoalloggi/prettier.config.cjs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/* eslint-env node */
|
||||
/** @type {import("prettier").Config & import('prettier-plugin-tailwindcss').PluginOptions} */
|
||||
const config = {
|
||||
printWidth: 80,
|
||||
plugins: ["prettier-plugin-classnames", "prettier-plugin-tailwindcss"],
|
||||
customAttributes: ["className"],
|
||||
customFunctions: ["classNames"],
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
100
apps/infoalloggi/public/Hero.svg
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg viewBox="0 0 491.85 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style>.cls-1{fill:#b1b4c4;}.cls-2{fill:#e4e7f2;}.cls-15,.cls-3,.cls-6{fill:#d6d8e5;}.cls-4{fill:#fff;}.cls-5{opacity:0.5;fill:url(#Безымянный_градиент_15);}.cls-6{opacity:0.4;}.cls-7{fill:#2b303f;}.cls-8{fill:#c3c6d1;}.cls-9{fill:#e9eaf2;}.cls-10{fill:#8c50ff;}.cls-11{fill:#f5f6ff;}.cls-12,.cls-13{fill:none;stroke:#e9eaf2;stroke-miterlimit:10;}.cls-12{stroke-width:0.22px;}.cls-13{stroke-width:1.09px;}.cls-14{fill:#fafbff;}.cls-15{opacity:0.3;}.cls-16,.cls-30{opacity:0.1;}.cls-17{opacity:0.15;}.cls-18{fill:#2b2a30;}.cls-19{fill:#ffcea9;}.cls-20{fill:#f4f4f4;}.cls-21{fill:#bf4b4b;}.cls-22{fill:#e26161;}.cls-23{fill:#ededed;}.cls-24{fill:#3a2c6d;}.cls-25{fill:#bfbfbf;}.cls-26{fill:#e5e5e5;}.cls-27{fill:#38226d;}.cls-28{fill:#9c73ff;}.cls-29{fill:#a1a5b5;}.cls-30{fill:#3e7eff;}</style>
|
||||
</defs>
|
||||
<title>Business_SVG</title>
|
||||
<g transform="matrix(1, 0, 0, 1, 0, -62.849998)">
|
||||
<path d="M 240.441 116.863 L 103.331 253.961 L 103.331 425.15 L 388.519 425.15 L 388.519 264.93 L 240.441 116.863 Z" fill="#EAEAEA"/>
|
||||
<path d="M 158.175 304.495 L 234.956 304.495 L 234.956 425.15 L 158.175 425.15 L 158.175 304.495 Z" fill="#434854"/>
|
||||
<path d="M 256.894 293.525 L 289.8 293.525 L 289.8 326.432 L 256.894 326.432 L 256.894 293.525 Z M 300.769 293.525 L 333.675 293.525 L 333.675 326.432 L 300.769 326.432 L 300.769 293.525 Z M 300.769 249.65 L 333.675 249.65 L 333.675 282.557 L 300.769 282.557 L 300.769 249.65 Z M 256.894 249.65 L 289.8 249.65 L 289.8 282.557 L 256.894 282.557 L 256.894 249.65 Z" fill="#469FCC"/>
|
||||
<path d="M 256.894 249.65 L 289.8 249.65 L 289.8 260.62 L 256.894 260.62 L 256.894 249.65 Z M 300.769 249.65 L 333.675 249.65 L 333.675 260.62 L 300.769 260.62 L 300.769 249.65 Z M 256.894 293.525 L 289.8 293.525 L 289.8 304.495 L 256.894 304.495 L 256.894 293.525 Z M 300.769 293.525 L 333.675 293.525 L 333.675 304.495 L 300.769 304.495 L 300.769 293.525 Z" fill=""/>
|
||||
<path d="M 240.441 96.088 L 103.331 238.682 L 103.331 271.588 L 240.441 134.479 L 388.519 282.557 L 388.519 249.65 L 240.441 96.088 Z" fill=""/>
|
||||
<path d="M 417.509 247.38 L 255.95 85.822 L 248.195 78.066 C 243.915 73.778 236.967 73.778 232.686 78.066 L 224.931 85.822 L 74.341 236.411 C 70.053 240.692 70.053 247.641 74.341 251.921 L 82.096 259.676 C 86.377 263.964 93.325 263.964 97.606 259.676 L 240.441 116.863 L 394.234 270.656 C 398.514 274.944 405.462 274.944 409.743 270.656 L 417.498 262.901 C 421.784 258.617 421.79 251.67 417.509 247.38 Z" fill="#EF4D4D"/>
|
||||
<path d="M 152.088 425.15 C 156.032 418.51 158.134 410.937 158.175 403.213 C 158.175 369.438 121.612 348.329 92.362 365.216 C 78.788 373.054 70.425 387.538 70.425 403.213 C 70.425 411.242 72.74 418.668 76.512 425.15 L 152.088 425.15 Z M 377.55 359.338 C 360.375 359.357 344.797 369.415 337.711 385.06 C 333.087 382.609 327.94 381.311 322.706 381.275 C 304.533 381.275 289.8 396.008 289.8 414.182 C 289.8 418.054 290.59 421.706 291.818 425.15 L 415.348 425.15 C 419.285 418.507 421.382 410.936 421.425 403.213 C 421.425 378.982 401.781 359.338 377.55 359.338 Z" fill="#3AAD73"/>
|
||||
<path d="M 337.711 385.06 C 333.087 382.609 327.94 381.311 322.706 381.275 C 304.533 381.275 289.8 396.008 289.8 414.182 C 289.8 418.054 290.59 421.706 291.818 425.15 L 339.762 425.15 C 335.818 418.51 333.716 410.937 333.675 403.213 C 333.675 396.719 335.178 390.61 337.711 385.06 Z" fill=""/>
|
||||
</g>
|
||||
<g id="Girl_8" transform="matrix(1, 0, 0, 1, -9.090909, -35.950413)">
|
||||
<ellipse class="cls-6" cx="455.85" cy="360.19" rx="22.69" ry="13.1"/>
|
||||
<path id="_Контур_27" data-name="<Контур>" class="cls-24" d="M454.85,237.69c.91-1.46,7.86-2.42,10.15,3.47.86,2.23.73,7.94,1.46,11.52s2.43,5.62,3,8.53-.38,7.78-7.39,8.65-12.4-1.6-14.06-3.84-1.69-7,0-9.53,3-4.63,3-8Z"/>
|
||||
<path class="cls-19" d="M446.53,262.33S444.88,274,448,282.21s2.25-2.13,2.25-2.13l-.75-2.46s-.62-9.5-.36-11.83.2-4.33.36-4.52-.61-.56-.94-.61S446.53,262.33,446.53,262.33Z"/>
|
||||
<path id="_Контур_28" data-name="<Контур>" class="cls-24" d="M459.44,236.68s-5.23-1.51-8.4,1.32a7.77,7.77,0,0,0-2.24,7.75c.42,1.65,1.27,3.91,2.58,4S459.44,236.68,459.44,236.68Z"/>
|
||||
<path id="_Контур_29" data-name="<Контур>" class="cls-19" d="M461.44,251s-.34,5.83-.23,6.26,2.28.84,2.9,1.35-3.17,6.42-5.1,7.3-7.88-1.36-8.29-4.33c-.38-2.72,1.7-4.84,2.51-5.58a12.41,12.41,0,0,1,1.64,0l.17-2.82C457.65,252.25,461.44,251,461.44,251Z"/>
|
||||
<path id="_Контур_30" data-name="<Контур>" class="cls-20" d="M462.24,257.77c1.64.26,3.51,1.13,3.77,2a19.33,19.33,0,0,1-.47,9.73c-1.42,3.75-2.37,6.14-3.19,7.76,0,0-7.09,3-12.8-.51,0,0-.4-4-.42-6.17-5.28-4.6-.77-10.28,3.7-14.69a10.85,10.85,0,0,1,1.61,0s-4,4.72-1.21,7.4C459.16,261.5,460.34,258.84,462.24,257.77Z"/>
|
||||
<path id="_Контур_31" data-name="<Контур>" class="cls-19" d="M450.55,243.72h0c.59-3.75,3.29-6.58,7.16-6.35A7.43,7.43,0,0,1,461.44,251a7.54,7.54,0,0,1-.59,1.63c-1.37,1.6-5,2.61-6.21,2.62-1,0-1.93-.95-2.9-2.56C449.71,249.29,450.23,245.57,450.55,243.72Z"/>
|
||||
<path class="cls-24" d="M458.9,237.44c2.71.53,4.94,1.29,5.68,3.95.5,1.84,1.16,5.33.53,6.8l-.38,1.07L461.44,251s-1.53-1.28-1.05-5a2.8,2.8,0,0,0-.46-1.42,4.12,4.12,0,0,1-.46-1.13,4.25,4.25,0,0,0-2.12-2.3c-2.45-1.37-6-.19-6.52.31A7.11,7.11,0,0,1,458.9,237.44Z"/>
|
||||
<path id="_Контур_32" data-name="<Контур>" class="cls-19" d="M466.2,285c.61-3.8,1.75-6.26,1.22-8.35a75.85,75.85,0,0,0-4.62-12.3c-1.2-2.36-.83-4,.41-4.94,1.4-1,2.73-.59,4,2,1.59,3.29,3.37,6.67,4.24,11.57a25.78,25.78,0,0,1-.12,9.57c-.54,3.41-1.62,5.33-2.91,8.53C468.06,292,465.68,288.24,466.2,285Z"/>
|
||||
<path class="cls-27" d="M463.83,355.83c-1.37,1.11-3.51.34-3.61.44a55.48,55.48,0,0,1-5,3.84c-.86.67-2.19,1.57-1.95,2.87.43,2.31,4.13,1.6,5.53.89s2.53-1.88,3.85-2.7c.95-.59,1.87-.89,2-2.08C464.74,358.37,464.07,355.8,463.83,355.83Z"/>
|
||||
<path class="cls-28" d="M464.63,358.61c-.18,1.14-1.09,1.46-2,2-1.34.83-2.52,2-3.92,2.74s-4.51,1.3-5.41-.35c.49,2.23,4.13,1.53,5.51.83s2.53-1.88,3.85-2.7c.95-.59,1.87-.89,2-2.08A2.88,2.88,0,0,0,464.63,358.61Z"/>
|
||||
<path class="cls-27" d="M454.2,353.9c-1.25,1-3.15.45-3.24.55a52.4,52.4,0,0,1-4.59,3.5c-.79.61-2,1.44-1.79,2.62.4,2.12,3.79,1.47,5.07.82s2.32-1.72,3.53-2.47c.86-.54,1.71-.82,1.85-1.91C455.11,356.35,454.43,353.88,454.2,353.9Z"/>
|
||||
<path class="cls-28" d="M455,356.57c-.17,1.05-1,1.33-1.86,1.87-1.23.76-2.31,1.86-3.59,2.51s-4.13,1.19-5-.32c.45,2,3.78,1.4,5,.76s2.32-1.72,3.53-2.47c.86-.54,1.71-.82,1.85-1.91A2.41,2.41,0,0,0,455,356.57Z"/>
|
||||
<path class="cls-19" d="M446.81,290.1c.61-4.73,2.35-11.6,2.35-11.6a14,14,0,0,0,5.25,1.31,43.54,43.54,0,0,0,7.93-.78,47.67,47.67,0,0,0,2.8,4.33c1.36,1.89,4,6,3.37,13.29-.41,4.73-3.07,24.84-3.07,24.84a37.47,37.47,0,0,1,1.63,10.43,84,84,0,0,1-1.66,12.49l-1.58,11.42s-2.11,1.52-3.61.44l.19-11.17c-.18-2.73-.45-6.76-.63-9.34-.28-4.19-1-10.85-1.25-12.52s-.75-4.57-1-7.83-1.48-19.8-1.48-19.8l-.17,3.1s-.17,5-.89,11.24-1,9-1,9a13.34,13.34,0,0,1,1.42,4c.13,1.34,1.66,12,.74,17.36L454.29,354a3.61,3.61,0,0,1-3.25.43l-1.11-13.84c-.74-5.27-2.31-16.43-2.55-17.88a59.48,59.48,0,0,1-.94-8.85C446.07,307.87,446.21,294.83,446.81,290.1Z"/>
|
||||
<path class="cls-10" d="M462.35,277.29l4.07,8.18s6.22,9.1-.22,32.48l1.21,7.21s-12.19,7.83-20.35,0c0,0-2.83-21.61-1.32-31.57a113.26,113.26,0,0,1,3.8-16.93s2,2.21,12.27.75"/>
|
||||
<path class="cls-23" d="M469.66,266.05s-2.83,2.82-6.19,2.47L461.86,263s-1-2.87,1.4-4.13S468.25,260.43,469.66,266.05Z"/>
|
||||
<path class="cls-23" d="M452.83,255.92s-3.24-1-6.67,6.19l1.28.77A54.66,54.66,0,0,1,452.83,255.92Z"/>
|
||||
</g>
|
||||
<g id="Girl_4" transform="matrix(1, 0, 0, 1, 40.495865, -45.041321)">
|
||||
<ellipse class="cls-6" cx="85.39" cy="486.9" rx="22.69" ry="13.1"/>
|
||||
<path id="_Контур_33" data-name="<Контур>" class="cls-19" d="M98.48,386.23c2,.57,3.12,2.76,4.86,7.45a39.25,39.25,0,0,1,2.09,13.22c-.56,4.42-2.39,7.86-6.81,11.44l-2.11-3.87s4.33-3.82,4.77-8.24c.25-2.48-2.66-10.84-2.66-11.07S98.48,386.23,98.48,386.23Z"/>
|
||||
<path class="cls-27" d="M81.17,488.09a4.85,4.85,0,0,1-4-.54c-1.08,0-7.43-2.49-8.23-.38-.7,1.85,1.85,3.57,3.2,3.9,3,.71,5.16,2.19,6.36,2.44a3,3,0,0,0,2.74-.38C82.07,492.29,82,489.19,81.17,488.09Z"/>
|
||||
<path class="cls-28" d="M78.48,493c-1.2-.25-3.39-1.73-6.36-2.45-1.13-.27-3.1-1.52-3.29-3-.27,1.7,2,3.21,3.29,3.52,3,.71,5.16,2.19,6.36,2.44a3,3,0,0,0,2.74-.38,1.91,1.91,0,0,0,.39-.71A3.56,3.56,0,0,1,78.48,493Z"/>
|
||||
<path class="cls-27" d="M97.29,479.71a3.81,3.81,0,0,1-3.49-.53c-1,0-7.53-2.38-8.3-.35-.67,1.78,1.78,3.42,3.08,3.74,2.86.69,5,2.12,6.12,2.36a2.9,2.9,0,0,0,2.64-.37C98.16,483.75,98.09,480.77,97.29,479.71Z"/>
|
||||
<path class="cls-28" d="M94.7,484.44c-1.15-.25-3.26-1.67-6.12-2.36-1.09-.27-3-1.47-3.17-2.9-.25,1.64,2,3.1,3.17,3.39,2.86.69,5,2.12,6.12,2.36a2.9,2.9,0,0,0,2.64-.37,1.67,1.67,0,0,0,.37-.69A3.4,3.4,0,0,1,94.7,484.44Z"/>
|
||||
<path id="_Контур_34" data-name="<Контур>" class="cls-19" d="M99.42,463.6a21.09,21.09,0,0,0-1.24-7.15s.82-9.79,1.45-16.22c.92-9.48,2.38-10.42,2-16.18-.38-5.34-3.42-8.8-4.21-12.42l-15.47,1.22s-1.35,4.39-3.09,13.13S77,445.11,77.14,458.87c0,3.55-.6,6.63-.4,15.47.14,5.94.48,13.22.48,13.22,2.26,1.94,3.83.51,3.83.51s4-17.6,4.55-21.64a12.73,12.73,0,0,0-.54-6.68s1.16-5.53,2-9.88c1-5.31,3.65-16.77,3.65-16.77s-.45,17.61.32,21.08.9,6.49,1.56,11.21c.79,5.7,1.15,14.05,1.15,14.05,2.36,1.5,3.41.35,3.41.35S99.42,468.59,99.42,463.6Z"/>
|
||||
<path class="cls-10" d="M97.38,411.63c1.62,1.66,4.3,6.71,4.3,14.35,0,8.6-1.25,6.86-1.85,16.33l-.6,9.47s-12.93,6.17-22.15.45c-.82-1.79.31-21.71,1.93-28.79s2.73-10.55,2.73-10.55S93.63,416.1,97.38,411.63Z"/>
|
||||
<path id="_Контур_35" data-name="<Контур>" class="cls-19" d="M97.57,386.14c-3.3-.2-3.62.35-3.88-.82-.09-.42-.28-3.32-.28-3.32a10.61,10.61,0,0,0,.83-1.22,8.37,8.37,0,0,0,4-6.29,8.47,8.47,0,0,0-7.54-9.3c-4.37-.45-7.59,2.62-8.45,6.84-.46,2.08-.94,6.33.36,9.74.73,1.93,1.41,3.28,3.15,3.21a10.47,10.47,0,0,0,1.72-.3,16.18,16.18,0,0,1,.1,2.41c0,.85.1,1.27-1.56,2.29s2.5,3.1,5.21,2.9,5.75-1.84,6.59-3.59C98.74,386.73,99.12,386.23,97.57,386.14Z"/>
|
||||
<path class="cls-23" d="M100,400.75s3.73-.84,5.44-2.5c0,0-2.22-7-3.64-8.74S99,399,99,399Z"/>
|
||||
<path id="_Контур_36" data-name="<Контур>" class="cls-20" d="M94.29,386.07c.61.76.68,1.71-1.67,2.31a7.44,7.44,0,0,1-5.15-.4,34.52,34.52,0,0,0-5.32,2.82c-2,6.6-.39,7.45-1.23,12.18.33,2.38.72,8.52.82,9.91,4.49,3.14,13.6,2.07,15.64-1.26a27.55,27.55,0,0,1,.31-3.31c2.14-7.94,3.81-10,3.22-16-.34-3.42-.61-5.14-2.43-6.06A16.1,16.1,0,0,0,94.29,386.07Z"/>
|
||||
<path id="_Контур_37" data-name="<Контур>" class="cls-19" d="M53.07,411.89a23.07,23.07,0,0,1,3,.86,10,10,0,0,0,2.93,1c1.1.17,2.82-.4,5.79-1.8a32,32,0,0,0,8.7-6.54,75.44,75.44,0,0,0,7.6-10.85c1.29-2.35,2.92-2.94,4.34-2.48,1.7.56,1.91,2.25,0,5.41a60.5,60.5,0,0,1-8.7,11.39,32.63,32.63,0,0,1-9.07,6.4,27.87,27.87,0,0,1-7.63,2.65c-.93.2-1.56.59-3.28,1.19a11.36,11.36,0,0,1-5.35.59c-1.49-.29-2-.63-2.06-1s.24-.63.94-.58a9.18,9.18,0,0,0,3.32-.08s-1.54-.05-2.45-.16a8.86,8.86,0,0,1-2.38-.6c-.73-.31-1-1.46-.26-1.42s1.24.26,2.6.4a9,9,0,0,0,2.16,0,16.91,16.91,0,0,1-2.65-.66,3.76,3.76,0,0,1-1.85-1.14c-.25-.29-.18-1,.69-.82a13.89,13.89,0,0,0,3.24.71c1.11,0,1.9-.09,1.76-.26s-.8-.15-1.64-.62-1.34-1.38-1-1.82S52.16,411.55,53.07,411.89Z"/>
|
||||
<path class="cls-23" d="M83.64,401.31a12,12,0,0,1-6.84-3.47l4.14-5.74s2.33-2.94,5.41-1.28c0,0,2.32.25,0,5.11A47.84,47.84,0,0,1,83.64,401.31Z"/>
|
||||
<path class="cls-24" d="M81.45,369.54a3.83,3.83,0,0,1,4.32-3.16,7.31,7.31,0,0,1,4.92-1.19c3.93.41,6.5,3,7.69,6.9a30.94,30.94,0,0,1,1.16,6c1.18,8.75,4.36,15.69,1.39,18.29S89.82,400.63,86.42,399s2.8-16.71-3.93-25.09C80.91,371.92,80.76,370.53,81.45,369.54Z"/>
|
||||
</g>
|
||||
<g id="Men_13" transform="matrix(1, 0, 0, 1, 40.495865, -45.041321)">
|
||||
<ellipse class="cls-6" cx="22.69" cy="454.91" rx="22.69" ry="13.1"/>
|
||||
<path class="cls-18" d="M12,451.62c1.55,1.26,3.83.11,3.94.23a72.22,72.22,0,0,0,5.71,4.75c1,.76,2.47,1.77,2.19,3.23-.48,2.6-4.65,1.8-6.23,1s-2.86-2.11-4.35-3c-1.06-.66-2.1-1-2.27-2.34C10.85,454.64,11.69,451.59,12,451.62Z"/>
|
||||
<path class="cls-21" d="M11,454.91c.21,1.28,1.24,1.64,2.29,2.29,1.52.94,2.85,2.3,4.42,3.09s5.08,1.47,6.1-.39c-.55,2.51-4.65,1.72-6.21.93s-2.86-2.11-4.35-3c-1.06-.66-2.1-1-2.27-2.34A3.6,3.6,0,0,1,11,454.91Z"/>
|
||||
<path class="cls-18" d="M25.38,446.63c1.55,1.26,4.25-.11,4.37,0a60,60,0,0,0,5.09,4.61c1,.75,2.47,1.77,2.2,3.23-.49,2.6-4.66,1.8-6.23,1s-2.86-2.12-4.35-3c-1.06-.66-2.11-1-2.27-2.34C24.09,449.28,25.11,446.6,25.38,446.63Z"/>
|
||||
<path class="cls-21" d="M24.23,449.73c.21,1.28,1.24,1.64,2.29,2.29,1.52.94,2.85,2.3,4.42,3.1s5.08,1.46,6.1-.4c-.55,2.51-4.65,1.73-6.21.94s-2.85-2.12-4.34-3.05c-1.07-.66-2.11-1-2.28-2.34A3.6,3.6,0,0,1,24.23,449.73Z"/>
|
||||
<path id="_Контур_38" data-name="<Контур>" class="cls-22" d="M11.87,394.54c.08,2.34.8,12.5,1.13,17.87s.4,12,.4,12-.93,3.91-1.53,8.52,0,19.13,0,19.13a3.67,3.67,0,0,0,4,0s2.48-11.63,3.62-16.87,1.35-8.26,1.94-11.5c.72-3.93,2.27-22.26,2.27-22.26l.62,0,1.66,20.69a23.16,23.16,0,0,0-1.05,4.7c-.45,3.37.48,20.33.48,20.33a4.1,4.1,0,0,0,4.28-.18s4.13-22.46,4.13-25.87c0-2.27-.38-32.07-.38-32.07Z"/>
|
||||
<path id="_Контур_39" data-name="<Контур>" class="cls-19" d="M17.48,349.35s.26,5.1.15,5.55-2.34,1.94-3,2.48S18.16,362,20.19,363s8.46-1.52,8.57-4.68-1.1-5.81-2.08-5.95S17.48,349.35,17.48,349.35Z"/>
|
||||
<path class="cls-19" d="M31.44,358.81c.3-2.25-1.18-4.39.25-6.18,5,4.94,4.4,18,5.54,26.35,3.74,3.9,11.25,7.75,13.36,8.7.38.18.72.37.64.78a9.26,9.26,0,0,1-1.75,3.82c-5-2.14-8.55-3.37-14.76-9.27a13.59,13.59,0,0,1-1.57-2.07C31.38,375.62,30.93,362.69,31.44,358.81Z"/>
|
||||
<path id="_Контур_40" data-name="<Контур>" class="cls-20" d="M16.91,355.78s3.7,5.51,8.86,6c1.63-1.6,1.77-7.31-.48-9.14,0,0,2.07-1.67,3.49-1.35A7.35,7.35,0,0,1,32,355.39a33.52,33.52,0,0,1,1.82,11.8c-.09,5.41,0,21.94,0,21.94s-.72,3.49-5.39,6.09-8.14,2.86-11.31,2.12c-2.61-.61-4.77-1.53-5.67-3.42.24-3.8,1.93-13.69,1.3-18.46s-1.64-8.32-1.9-12.18.5-3.78,2.44-5.15A29.62,29.62,0,0,1,16.91,355.78Z"/>
|
||||
<path id="_Контур_41" data-name="<Контур>" class="cls-19" d="M29,341.75h0c-.61-4-3.46-7-7.55-6.73a7.85,7.85,0,0,0-4,14.33,7.82,7.82,0,0,0,.63,1.73c1.44,1.69,5.81,2.73,7.06,2.55A3.74,3.74,0,0,0,28.42,351C29.78,347.86,29.35,343.7,29,341.75Z"/>
|
||||
<path class="cls-18" d="M17.48,349.35h0l.37-3.46S15,338.59,21.7,340c4.4.93,5.5,1.33,6.84-1.62s-3.38-5.61-9-4.87a7.75,7.75,0,0,0-6.94,7.86C12.76,343.53,13.34,347.93,17.48,349.35Z"/>
|
||||
<path class="cls-19" d="M9.67,361c1.93-.55.46-.1,1.93-.55,5,4.93,4.41,13.39,5.55,21.74C19.93,386.8,30.24,393,30.24,393c.13.89-.18,1.15-1.3,3a44.43,44.43,0,0,1-15.33-9.89c-.25-.23-.61-.3-.71-.61C11.13,380.14,9.16,364.89,9.67,361Z"/>
|
||||
<polygon class="cls-20" points="55.55 371.31 48.06 396.93 31.61 403.5 37.61 378.54 55.55 371.31"/>
|
||||
<path class="cls-19" d="M27.65,393.21c.24-.88.87-.66,1.59-.44a4.13,4.13,0,0,0,2.43.21,7.52,7.52,0,0,0,1.74-1.18,2.15,2.15,0,0,1,1.1-.31c0,.22-.38,1.52-.38,1.52s3.61,0,4,0a.61.61,0,0,1,.57,1l.25,0c.73-.16,1,1,.31,1.14l-.6.09c.48.19.6,1,0,1.1l-.19,0a.78.78,0,0,1-.26,0H38a.63.63,0,0,1-.54.76c-1.76.23-4.35.93-5.94-.52a9.88,9.88,0,0,1-3.19-.86C27.38,395.24,27.35,394.25,27.65,393.21Z"/>
|
||||
<path class="cls-19" d="M45.63,391.11l.25,0a.6.6,0,0,1,.35-1.09c.37,0,4.08-.78,4.08-.78l.45-1.52c0-.09.64.3.66.32a2.46,2.46,0,0,1,1.12,1.85,4,4,0,0,1-1.07,2.87A3.66,3.66,0,0,1,49,393.82c-.45,0-.88.05-1.27.09a.65.65,0,0,1-.69-.64l-.12,0a.47.47,0,0,1-.26,0h-.19c-.63,0-.68-.78-.25-1.06l-.61,0C44.81,392.28,44.88,391.1,45.63,391.11Z"/>
|
||||
<path class="cls-23" d="M33.54,362.15a4,4,0,0,0,2.68-1.2c-.14-1.9-2.56-9.09-7.44-9.63a9.09,9.09,0,0,1,3,3.68A48.52,48.52,0,0,1,33.54,362.15Z"/>
|
||||
<path class="cls-23" d="M8.79,371.65s4.85,1.68,8.27-1.14c.38-.64-2-8.9-2-8.9s-2-4.22-5.22-2.54S8.79,371.65,8.79,371.65Z"/>
|
||||
</g>
|
||||
<g transform="matrix(1, 0, 0, 1, -94.214874, 11.983471)">
|
||||
<path class="cls-1" d="M 425.06 388.31 C 423.503 386.888 421.792 385.644 419.96 384.6 C 406.11 376.6 383.59 376.6 369.75 384.6 C 367.612 385.809 365.647 387.303 363.91 389.04 C 356.3 396.85 358.25 406.95 369.75 413.59 C 383.59 421.59 406.12 421.59 419.96 413.59 C 431.82 406.74 433.52 396.21 425.06 388.31 Z M 415.55 411 C 404.14 417.59 385.55 417.59 374.17 411 C 366.17 406.38 363.77 399.72 367 393.88 C 368.739 391.021 371.216 388.682 374.17 387.11 C 385.58 380.53 404.17 380.53 415.55 387.11 C 418.501 388.679 420.972 391.019 422.7 393.88 C 425.94 399.76 423.55 406.41 415.55 411 Z"/>
|
||||
<path class="cls-29" d="M 422.7 393.92 C 420.972 391.059 418.501 388.719 415.55 387.15 C 404.14 380.57 385.55 380.57 374.17 387.15 C 371.216 388.722 368.739 391.061 367 393.92 L 363.91 389 L 363.35 388.11 L 367.35 378.56 L 381.24 370.84 L 407.1 371.29 L 424.67 381.71 C 424.67 381.71 424.99 385.01 425.03 388.27 C 425.1 392.35 424.7 396.32 422.7 393.92 Z"/>
|
||||
<polygon class="cls-1" points="359.37 399 359.38 388.15 364.76 397.26 359.37 399"/>
|
||||
<polygon class="cls-1" points="430.34 399 430.34 388.21 425.6 393.58 430.34 399"/>
|
||||
<ellipse class="cls-30" cx="395.18" cy="393.58" rx="29.54" ry="17.05"/>
|
||||
<path class="cls-1" d="M 362.07 400.83 C 362.07 400.83 366.62 418.75 400.55 417.83 C 424.1 417.83 427.11 397.59 426.55 394.71 C 421.66 401.449 414.045 405.687 405.74 406.29 C 391.41 407.54 380.83 407.38 375.17 404.64 C 369.51 401.9 362.08 394.71 362.08 394.71 L 362.07 400.83 Z"/>
|
||||
<path class="cls-3" d="M 369.75 403.25 C 355.91 395.25 355.91 382.25 369.75 374.25 C 383.59 366.25 406.12 366.25 419.96 374.25 C 433.8 382.25 433.8 395.25 419.96 403.25 C 406.12 411.25 383.59 411.24 369.75 403.25 Z M 415.54 376.81 C 404.13 370.23 385.54 370.22 374.17 376.81 C 362.8 383.4 362.76 394.11 374.17 400.7 C 385.58 407.29 404.17 407.29 415.54 400.7 C 426.91 394.11 427 383.4 415.54 376.81 Z"/>
|
||||
<ellipse class="cls-16" cx="319.57" cy="438.88" rx="3.9" ry="6.75" transform="matrix(0.866025, -0.5, 0.5, 0.866025, -176.630124, 218.599962)"/>
|
||||
<polygon class="cls-1" points="373.39 407.78 358.45 416.41 353.84 413.74 368.78 405.12 373.39 407.78"/>
|
||||
<polygon class="cls-29" points="373.39 413.1 358.45 421.72 358.45 416.4 373.39 407.77 373.39 413.1"/>
|
||||
<path class="cls-10" d="M 361.5 420.18 C 361.318 416.821 359.546 413.75 356.73 411.91 C 355.762 411.21 354.496 411.073 353.4 411.55 L 316.22 433 L 323 444.64 L 359.72 423.44 C 360.81 423.05 361.5 421.91 361.5 420.18 Z"/>
|
||||
<ellipse class="cls-10" cx="319.57" cy="438.88" rx="3.9" ry="6.75" transform="matrix(0.866025, -0.5, 0.5, 0.866025, -176.630124, 218.599962)"/>
|
||||
</g>
|
||||
<g id="Men_1" transform="matrix(1, 0, 0, 1, -22.314049, -27.685949)">
|
||||
<ellipse class="cls-6" cx="423.93" cy="486.82" rx="22.69" ry="13.1"/>
|
||||
<path id="_Контур_54" data-name="<Контур>" class="cls-19" d="M439.85,392.73c.24,2.11,2.76,11.21,2.22,14.32s-9.14,8.33-9.14,8.33l-1.19-5.33,4.76-4.67-1.29-8.83Z"/>
|
||||
<path id="_Контур_55" data-name="<Контур>" class="cls-23" d="M433.2,383.76a3.77,3.77,0,0,1,4.79,1.45c1.26,2.09,2.57,9.28,2.69,10.5,0,0-1.77,2.46-4.43,2.05Z"/>
|
||||
<path class="cls-18" d="M423.38,485.22a7.4,7.4,0,0,1-5.56-.68c-1.34,0-8.62-3.07-9.61-.46-.86,2.3,2.29,4.42,4,4.82,3.68.89,6.39,2.73,7.87,3,1.1.23,2.52.38,3.4-.48C424.5,490.42,424.41,486.58,423.38,485.22Z"/>
|
||||
<path class="cls-21" d="M420.05,491.3c-1.48-.31-4.19-2.14-7.87-3-1.41-.34-3.84-1.89-4.09-3.73-.32,2.11,2.52,4,4.09,4.36,3.68.89,6.39,2.73,7.87,3,1.1.23,2.52.38,3.4-.48a2.22,2.22,0,0,0,.48-.88A4.41,4.41,0,0,1,420.05,491.3Z"/>
|
||||
<path class="cls-18" d="M435.18,479.92a7.35,7.35,0,0,1-5.56-.67c-1.34,0-8.62-3.08-9.61-.46-.86,2.29,2.29,4.41,4,4.82,3.67.89,6.39,2.72,7.87,3,1.1.23,2.52.38,3.4-.48C436.3,485.12,436.21,481.28,435.18,479.92Z"/>
|
||||
<path class="cls-21" d="M431.85,486c-1.48-.31-4.2-2.14-7.87-3-1.41-.34-3.84-1.88-4.09-3.72-.32,2.11,2.52,4,4.09,4.36,3.67.89,6.39,2.72,7.87,3,1.1.23,2.52.38,3.4-.48a2.22,2.22,0,0,0,.48-.88A4.38,4.38,0,0,1,431.85,486Z"/>
|
||||
<path id="_Контур_56" data-name="<Контур>" class="cls-22" d="M435.32,460.15a44.45,44.45,0,0,0-1.67-8s.5-6.88.75-13.4c.28-7.4,2.85-13.91.82-19.81l-22.82,5.07s1.38,29.5,1.68,33.07a66.94,66.94,0,0,0,1,8.28c1,5.73,2.73,19.35,2.73,19.35a8.52,8.52,0,0,0,5.58.62s-.17-17.22-.33-21.35c-.19-5-.34-4.65-.34-4.65l.76-12.55.49-6.24s.63,4.58,1,8.67c.36,3.57,1.22,6.8,2.18,13.44.84,5.76,2.19,17.34,2.19,17.34,2.42,1.4,5.69.24,5.69.24S436,465.15,435.32,460.15Z"/>
|
||||
<path id="_Контур_57" data-name="<Контур>" class="cls-19" d="M432,383.88c-1.74.09-3,.54-3.25,0a24.73,24.73,0,0,1-.4-2.71c.2-.51.38-1.05.38-1.05,2.18-1.34,2.87-3.7,3.16-6.45.49-4.7-2.05-8.81-6.75-9.3-4.41-.46-7.66,2.65-8.53,6.91-.46,2.1-1.39,6.07-.14,9.53.71,1.94,1.61,3.47,2.52,3.77a20.09,20.09,0,0,0,2.61-.23h0s.23,1.3.39,2.14.1,1.28-1.57,2.3,2.52,3.14,5.26,2.93,5.81-1.86,6.66-3.62C433.27,386.15,433.18,383.83,432,383.88Z"/>
|
||||
<path id="_Контур_58" data-name="<Контур>" class="cls-20" d="M429.48,384.14c.48,1.2-.75,2.67-4,3.7s-3.56-.3-3.56-.3a66.49,66.49,0,0,0-6.35,3.27c-2,1.37-2.82,5.34-3.09,12-.3,7.74-.33,19.23-.07,21.21,0,0,3.66,3.65,8.35,3.27s12.57-4.53,14.48-7.74c-.05-6.79-.85-7.89.25-11.8,2.52-9,3.84-12.62,2.62-18.57-1-4.91-2.42-5.52-4.36-5.47A41.07,41.07,0,0,0,429.48,384.14Z"/>
|
||||
<path id="_Контур_59" data-name="<Контур>" class="cls-19" d="M405.1,409.24c3.85,1,4.75-1.45,5.26-3.46,1.22-4.86,1.88-9.25,2.79-12,1.08-3.26,2.2-3.78,3.79-4.41,1.85-.75,3.45.82,2.83,5a82.82,82.82,0,0,1-3.52,14.13c-.48,1.52-1.55,4.21-3.16,5.51-2,1.6-5,1.53-9.5.41-1.92-.48-4-1.63-7.69-3.33-1-.46-1.74-.76-3.62-1.64a12.94,12.94,0,0,1-4.82-3.76c-1-1.43-1.11-2.1-.91-2.42s.7-.32,1.22.28a10.72,10.72,0,0,0,2.77,2.56s-1.22-1.26-1.87-2.07a10.55,10.55,0,0,1-1.46-2.38c-.35-.83.37-2,.91-1.37s.81,1.2,1.8,2.39a10.35,10.35,0,0,0,1.78,1.7,19.53,19.53,0,0,1-1.64-2.64,4.45,4.45,0,0,1-.6-2.39c0-.43.65-.95,1.22-.12a16.2,16.2,0,0,0,2.07,3.15c.88.91,1.62,1.44,1.64,1.18s-.53-.75-.84-1.81,0-2.18.59-2.31.47-.09.94.91a28.49,28.49,0,0,1,1.75,3.17,6.56,6.56,0,0,0,1.58,2.73C399.36,407.21,401.45,408.32,405.1,409.24Z"/>
|
||||
<path id="_Контур_60" data-name="<Контур>" class="cls-23" d="M418.36,388.69c-2.57-.37-4.38.78-5.52,4.67s-1.92,7.09-1.92,7.09a7,7,0,0,0,4.3,2.86c3.07.74,4.18-.73,4.18-.73s.85-4.08,1.38-6.85S421.54,389.16,418.36,388.69Z"/>
|
||||
<path class="cls-18" d="M416.75,370.67s-4.05-6.16,4.74-7.42c6.34-.91,10.41,3.17,10.88,7.59.45,4.22-2.15,8.61-4,10.35a7.68,7.68,0,0,1-5.48-.67,33.14,33.14,0,0,1-.12-3.45S425.83,370.56,416.75,370.67Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 22 KiB |
BIN
apps/infoalloggi/public/Infoalloggi.png
Normal file
|
After Width: | Height: | Size: 6 KiB |
BIN
apps/infoalloggi/public/aptm.webp
Normal file
|
After Width: | Height: | Size: 193 KiB |
BIN
apps/infoalloggi/public/bgpattern.webp
Normal file
|
After Width: | Height: | Size: 656 KiB |
BIN
apps/infoalloggi/public/come_funziona_aggiungi.jpg
Normal file
|
After Width: | Height: | Size: 193 KiB |
BIN
apps/infoalloggi/public/come_funziona_confirm.jpg
Normal file
|
After Width: | Height: | Size: 210 KiB |
BIN
apps/infoalloggi/public/come_funziona_contratto.jpg
Normal file
|
After Width: | Height: | Size: 256 KiB |
BIN
apps/infoalloggi/public/come_funziona_meet.jpg
Normal file
|
After Width: | Height: | Size: 554 KiB |
BIN
apps/infoalloggi/public/come_funziona_ricerca.jpg
Normal file
|
After Width: | Height: | Size: 278 KiB |
BIN
apps/infoalloggi/public/fallback-image.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
apps/infoalloggi/public/fallback-video.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
apps/infoalloggi/public/favicon/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
apps/infoalloggi/public/favicon/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
apps/infoalloggi/public/favicon/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |