diff --git a/apps/backend/images.go b/apps/backend/images.go
index d41c109..faa6c0c 100644
--- a/apps/backend/images.go
+++ b/apps/backend/images.go
@@ -305,7 +305,7 @@ func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) {
return nil, err
}
- dbImagesMap := make(map[string][]imgRef) // Convert slice to map for faster lookup
+ dbImagesMap := make(map[string][]imgRef)
for _, ref := range db_images {
dbImagesMap[ref.Codice] = append(dbImagesMap[ref.Codice], imgRef{
Og_url: ref.Og_url,
@@ -318,7 +318,8 @@ func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) {
for _, annuncio := range annunci.Annuncio {
if annuncio.Codice != nil && annuncio.Foto != nil {
- refFoto, existsInDb := dbImagesMap[*annuncio.Codice]
+ // Check if codice exists in DB
+ refFotos, existsInDb := dbImagesMap[*annuncio.Codice]
if !existsInDb {
codiciToProcess = append(codiciToProcess, *annuncio.Codice)
continue
@@ -326,8 +327,10 @@ func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) {
needProcess := false
for _, fotoUrl := range *annuncio.Foto {
found := false
- for _, ref := range refFoto {
+ // Check if the fotoUrl exists in the DB refs
+ for _, ref := range refFotos {
if fotoUrl == ref.Og_url {
+ // check if the corresponding img and thumb are in the bucket
if bucketIdsMap[ref.Img] && bucketIdsMap[ref.Thumb] {
found = true
break
@@ -374,3 +377,42 @@ func ForceClearImages(codici []string) error {
}
return nil
}
+
+// Removes stranded images (in storage but not in DB)
+func ReconcileImages() error {
+ currentBucketState, err := ListBucket("images")
+ if err != nil {
+ return fmt.Errorf("failed to list bucket: %w", err)
+ }
+ //make a list of bucket ids
+ bucketIdsMap := make(map[string]bool)
+ for _, item := range currentBucketState {
+ bucketIdsMap[item.ID] = true
+ }
+ db_images, err := queries.GetImagesFromDB()
+ if err != nil {
+ return fmt.Errorf("failed to get images from DB: %w", err)
+ }
+ knownStorageIds := make(map[string]bool)
+ for _, ref := range db_images {
+ knownStorageIds[ref.Img] = true
+ knownStorageIds[ref.Thumb] = true
+ }
+ // reconcile: delete storage files not referenced in DB
+ var strandedIds []string
+ for _, item := range currentBucketState {
+ if !knownStorageIds[item.ID] {
+ strandedIds = append(strandedIds, item.ID)
+ }
+ }
+ if len(strandedIds) > 0 {
+ log.Printf("Reconciliation: found %d stranded files, deleting...", len(strandedIds))
+ if err := DeleteIdsFromStorage(strandedIds); err != nil {
+ log.Printf("Warning: failed to delete stranded files: %v", err) // non-fatal
+ }
+ } else {
+ log.Println("Reconciliation: no stranded files found.")
+ }
+ return nil
+
+}
diff --git a/apps/backend/miogest_handler.go b/apps/backend/miogest_handler.go
index 1fdf19e..818bde0 100644
--- a/apps/backend/miogest_handler.go
+++ b/apps/backend/miogest_handler.go
@@ -204,6 +204,7 @@ func (m *MiogestHandler) ParseAnnunci(codFilter string, imgPool *[]typesdefs.Med
image_codes_to_process = append(image_codes_to_process, codFilter)
}
}
+ err = ReconcileImages()
err = ForceClearImages(image_codes_to_process)
if err != nil {
log.Fatalf("Failed to clear images for codice %s: %v", codFilter, err.Error())
@@ -237,6 +238,7 @@ func (m *MiogestHandler) ParseAnnunci(codFilter string, imgPool *[]typesdefs.Med
video_codes_to_process = append(video_codes_to_process, codFilter)
}
}
+ err = ReconcileVideos()
err = ForceClearVideos(video_codes_to_process)
if err != nil {
log.Fatalf("Failed to clear videos for codice %s: %v", codFilter, err.Error())
diff --git a/apps/backend/typesdefs/models.go b/apps/backend/typesdefs/models.go
index 01b10fc..9df0d6e 100644
--- a/apps/backend/typesdefs/models.go
+++ b/apps/backend/typesdefs/models.go
@@ -201,8 +201,8 @@ type AnnuncioXML struct {
} `xml:"Clienti"`
Cap *string `xml:"Cap"`
Video *[]string `xml:"Video"`
- AMMedias *[]struct {
- AMVideo *struct {
+ AMMedias *struct {
+ AMVideo []struct {
Url *string `xml:"Url"`
} `xml:"AMVideo"`
} `xml:"AMMedias"`
diff --git a/apps/backend/video.go b/apps/backend/video.go
index 2a2f792..942a6da 100644
--- a/apps/backend/video.go
+++ b/apps/backend/video.go
@@ -27,10 +27,10 @@ func AddToVideoProcessing(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.Ann
miogestVideos := []string{}
if annuncio.AMMedias != nil {
- for _, media := range *annuncio.AMMedias {
- if media.AMVideo != nil && media.AMVideo.Url != nil {
- if strings.Contains(*media.AMVideo.Url, "video.miogest") {
- miogestVideos = append(miogestVideos, *media.AMVideo.Url)
+ for _, media := range annuncio.AMMedias.AMVideo {
+ if media.Url != nil {
+ if strings.Contains(*media.Url, "video.miogest") {
+ miogestVideos = append(miogestVideos, *media.Url)
}
}
@@ -347,12 +347,12 @@ func CodiciToProcessVideos(annunci *typesdefs.AnnunciXML) ([]string, error) {
var codiciToProcess []string
for _, annuncio := range annunci.Annuncio {
- if annuncio.Codice != nil && annuncio.AMMedias != nil && len(*annuncio.AMMedias) > 0 {
+ if annuncio.Codice != nil && annuncio.AMMedias != nil {
mediaUrls := []string{}
- for _, media := range *annuncio.AMMedias {
- if media.AMVideo != nil && media.AMVideo.Url != nil {
- if strings.Contains(*media.AMVideo.Url, "video.miogest") {
- mediaUrls = append(mediaUrls, *media.AMVideo.Url)
+ for _, media := range annuncio.AMMedias.AMVideo {
+ if media.Url != nil {
+ if strings.Contains(*media.Url, "video.miogest") {
+ mediaUrls = append(mediaUrls, *media.Url)
}
}
}
@@ -367,17 +367,17 @@ func CodiciToProcessVideos(annunci *typesdefs.AnnunciXML) ([]string, error) {
}
needProcess := false
- for _, media := range *annuncio.AMMedias {
- if media.AMVideo == nil || media.AMVideo.Url == nil {
+ for _, media := range annuncio.AMMedias.AMVideo {
+ if media.Url == nil {
continue
}
- if !strings.Contains(*media.AMVideo.Url, "video.miogest") {
+ if !strings.Contains(*media.Url, "video.miogest") {
continue
}
found := false
for _, ref := range refVideos {
- if *media.AMVideo.Url == ref.Og_url {
+ if *media.Url == ref.Og_url {
if bucketIdsMap[ref.Video] && bucketIdsMap[ref.Thumb] {
found = true
break
@@ -425,3 +425,36 @@ func ForceClearVideos(codici []string) error {
}
return nil
}
+
+// ReconcileVideos removes stranded videos (in storage but not in DB)
+func ReconcileVideos() error {
+ currentBucketState, err := ListBucket("videos")
+ if err != nil {
+ return fmt.Errorf("failed to list bucket: %w", err)
+ }
+ //make a list of bucket ids
+ bucketIdsMap := make(map[string]bool)
+ for _, item := range currentBucketState {
+ bucketIdsMap[item.ID] = true
+ }
+ db_videos, err := queries.GetVideosFromDB()
+ if err != nil {
+ return fmt.Errorf("failed to get videos from DB: %w", err)
+ }
+ var strandedIds []string
+ for _, ref := range db_videos {
+ if !bucketIdsMap[ref.Video] {
+ strandedIds = append(strandedIds, ref.Video)
+ }
+ if !bucketIdsMap[ref.Thumb] {
+ strandedIds = append(strandedIds, ref.Thumb)
+ }
+ }
+ if len(strandedIds) > 0 {
+ err := DeleteIdsFromStorage(strandedIds)
+ if err != nil {
+ return fmt.Errorf("failed to delete stranded videos from storage: %w", err)
+ }
+ }
+ return nil
+}
diff --git a/apps/infoalloggi/src/components/prezzi.tsx b/apps/infoalloggi/src/components/prezzi.tsx
index 6e83670..cd01d29 100644
--- a/apps/infoalloggi/src/components/prezzi.tsx
+++ b/apps/infoalloggi/src/components/prezzi.tsx
@@ -33,7 +33,6 @@ export const PricingChoice = () => {
downPayment: prezziario.stabile.acconto.prezzo_cent / 100,
secondPayment: s.prezzo_cent / 100,
}));
-
return (
Scegli il tuo servizio
@@ -75,6 +74,7 @@ export const PricingChoice = () => {
opzioni={[
t.pricing_cmp.stabile_option1,
t.pricing_cmp.stabile_option2,
+ t.pricing_cmp.stabile_option3,
]}
pricingData={pricingStabile}
tipo={TipologiaPosizioneEnum.Stabile}
@@ -103,6 +103,7 @@ export const PricingComponent = ({
className,
title,
tipo,
+ optionsBtnClassName,
}: {
pricingData: PricingData;
opzioni: string[];
@@ -111,6 +112,7 @@ export const PricingComponent = ({
statico?: boolean;
className?: string;
tipo: TipologiaPosizioneEnum;
+ optionsBtnClassName?: string;
}) => {
const { t } = useTranslation();
const [Value, setValue] = useState(0);
@@ -213,6 +215,7 @@ export const PricingComponent = ({
className={cn(
"h-9 xs:h-10 rounded-md border-none px-3 xs:px-4 xs:py-2",
idx === Value ? "fade-in-0 animate-in" : "",
+ optionsBtnClassName,
)}
key={opzione}
onClick={() => {
diff --git a/apps/infoalloggi/src/forms/FormEditAnnuncio.tsx b/apps/infoalloggi/src/forms/FormEditAnnuncio.tsx
index acf4adb..429a616 100644
--- a/apps/infoalloggi/src/forms/FormEditAnnuncio.tsx
+++ b/apps/infoalloggi/src/forms/FormEditAnnuncio.tsx
@@ -8,11 +8,12 @@ import {
RefreshCcw,
RotateCw,
TriangleAlert,
+ X,
} from "lucide-react";
+import Image from "next/image";
import Link from "next/link";
import toast from "react-hot-toast";
import z from "zod";
-import { CarouselAnnuncio } from "~/components/annuncio_card";
import { Confirm } from "~/components/confirm";
import {
Form,
@@ -41,8 +42,10 @@ import {
} from "~/components/ui/popover";
import { Switch } from "~/components/ui/switch";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
+import { VideoPlayer } from "~/components/videoPlayer";
import { filteredCaratteristiche } from "~/hooks/schedaAnnuncioUtils";
import { NullableStringOnChange } from "~/lib/form_utils";
+import { getStorageUrl } from "~/lib/storage_utils";
import { cn } from "~/lib/utils";
import { useZodForm } from "~/lib/zodForm";
import { StatusBadge } from "~/pages/area-riservata/admin/edit-annuncio/[id]";
@@ -109,14 +112,6 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => {
titolo_it: z.string().nullable(),
unita_condominio: z.number().nullable(),
web: z.boolean().nullable(),
-
- // MEDIA
- //url_immagini: z.string().array().nullable(),
- //url_video: z.string().array().nullable(),
- //og_url: z.string().nullable(),
-
- //creato_il: z.date().nullable(),
- //modificato_il: z.date().nullable(),
});
type FormValues = z.infer;
@@ -160,6 +155,14 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => {
},
});
+ const { mutateAsync: removeMedia } =
+ api.annunci.removeAnnuncioMedia.useMutation({
+ onSettled: async () => {
+ await revalidate({ cod: data.codice });
+ toast.success("Media rimosso con successo");
+ },
+ });
+
const defaultValues: FormValues = {
...data,
mq: Number(data.mq) || null,
@@ -535,19 +538,6 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => {
/>
-
-
- Imagini Annuncio
-
-
-
-
-
@@ -1843,7 +1833,80 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => {
Media
-
+
+ {data.images.map((img) => (
+
+
+
+ removeMedia({ type: "img", id: img.img })
+ }
+ title="Rimuovi media"
+ >
+
+
+
+ ))}
+ {data.videos.map((video) => (
+
+
+
+ removeMedia({ type: "video", id: video.video })
+ }
+ title="Rimuovi media"
+ >
+
+
+
+ ))}
+
diff --git a/apps/infoalloggi/src/forms/FormPrezzo.tsx b/apps/infoalloggi/src/forms/FormPrezzo.tsx
index 7a04105..9b99ee7 100644
--- a/apps/infoalloggi/src/forms/FormPrezzo.tsx
+++ b/apps/infoalloggi/src/forms/FormPrezzo.tsx
@@ -133,12 +133,7 @@ export const FormPrezzo = ({
-
+
)}
diff --git a/apps/infoalloggi/src/i18n/en.ts b/apps/infoalloggi/src/i18n/en.ts
index 9c4d14b..313275f 100644
--- a/apps/infoalloggi/src/i18n/en.ts
+++ b/apps/infoalloggi/src/i18n/en.ts
@@ -1187,7 +1187,8 @@ The Stable Rent service, ideal for those with demonstrable work references and f
stabile_footer:
"Types of stable rent: Libero (4+4 years), Subsidized (3+2 years) or commercial (6+6 years)",
stabile_option1: "Up to 500 €/month",
- stabile_option2: "Over 500 €/month",
+ stabile_option2: "Up to 700 €/month",
+ stabile_option3: "Over 700 €/month",
stabile_select: "Select the rent:",
titolo: "Pay based on your search",
txt_tipo_acquisto: "For the purchase of any type of property",
diff --git a/apps/infoalloggi/src/i18n/it.ts b/apps/infoalloggi/src/i18n/it.ts
index 830b765..c74cbf8 100644
--- a/apps/infoalloggi/src/i18n/it.ts
+++ b/apps/infoalloggi/src/i18n/it.ts
@@ -1183,8 +1183,9 @@ export const it: LangDict = {
saldi_stabile: ["Fino a 500 €/mese", "Superiore a 500 €/mese"],
stabile_footer:
"Tipologie di affitto stabile: libero (4+4 anni), agevolato (3+2 anni) o commerciale (6+6 anni)",
- stabile_option1: "Fino a 500 €/mese",
- stabile_option2: "Superiore a 500 €/mese",
+ stabile_option1: "Fino a 500€/mese",
+ stabile_option2: "Fino a 700€/mese",
+ stabile_option3: "Oltre 700€/mese",
stabile_select: "Seleziona il canone:",
titolo: "Paga in base alla tua ricerca",
txt_tipo_acquisto: "Per l'acquisto di qualsiasi tipologia di immobile",
diff --git a/apps/infoalloggi/src/i18n/locales.ts b/apps/infoalloggi/src/i18n/locales.ts
index 25bacf9..191c3e9 100644
--- a/apps/infoalloggi/src/i18n/locales.ts
+++ b/apps/infoalloggi/src/i18n/locales.ts
@@ -185,6 +185,7 @@ export type LangDict = {
stabile_select: string;
stabile_option1: string;
stabile_option2: string;
+ stabile_option3: string;
stabile_footer: string;
breve_select: string;
breve_option1: string;
diff --git a/apps/infoalloggi/src/pages/annuncio/[cod].tsx b/apps/infoalloggi/src/pages/annuncio/[cod].tsx
index 3ca12ba..593a0d2 100644
--- a/apps/infoalloggi/src/pages/annuncio/[cod].tsx
+++ b/apps/infoalloggi/src/pages/annuncio/[cod].tsx
@@ -307,7 +307,11 @@ const Pricing = ({
t.pricing_cmp.breve_option4,
t.pricing_cmp.breve_option5,
]
- : [t.pricing_cmp.stabile_option1, t.pricing_cmp.stabile_option2];
+ : [
+ t.pricing_cmp.stabile_option1,
+ t.pricing_cmp.stabile_option2,
+ t.pricing_cmp.stabile_option3,
+ ];
const style =
tipologia === TipologiaPosizioneEnum.Transitorio
@@ -456,7 +460,7 @@ const CardInfos = ({ data }: { data: Annunci }) => {
-
+
{data.prezzo && (data.prezzo / 1e2).toFixed(2).replace(".", ",")}{" "}
{t.annunci.euro_mese}
@@ -480,7 +484,7 @@ const CardInfos = ({ data }: { data: Annunci }) => {
{
- {(() => {
- if (!data.numero_camere) return "N/A";
- if (data.numero_camere > 1)
- return `${data.numero_camere} ${t.card.camere2}`;
- if (data.numero_camere === 1)
- return `${data.numero_camere} ${t.card.camere1}`;
- })()}
+
+ {(() => {
+ if (!data.numero_camere) return "N/A";
+ if (data.numero_camere > 1)
+ return `${data.numero_camere} ${t.card.camere2}`;
+ if (data.numero_camere === 1)
+ return `${data.numero_camere} ${t.card.camere1}`;
+ })()}
+
-
+
{data.numero_bagni || 0} {t.annunci.bagni}
- {data.caratteristiche?.Scheda_Arredi || ""}
+
+ {data.caratteristiche?.Scheda_Arredi || ""}
+
-
+
{data.numero_postiauto || 0} {t.annunci.posti_auto}
@@ -532,7 +540,7 @@ const CardInfos = ({ data }: { data: Annunci }) => {
-
+
{data.mq ? Number(data.mq).toLocaleString("it-IT") : "--"} m
2
@@ -542,7 +550,7 @@ const CardInfos = ({ data }: { data: Annunci }) => {
{data.piano !== null && data.piano !== "" && (
<>
-
+
{(() => {
if (data.piano === "0") return t.annunci.p_terra;
if (data.piano === "0.5") return t.annunci.p_rialzato;
diff --git a/apps/infoalloggi/src/pages/prezzi.tsx b/apps/infoalloggi/src/pages/prezzi.tsx
index e77e53b..a4e9e46 100644
--- a/apps/infoalloggi/src/pages/prezzi.tsx
+++ b/apps/infoalloggi/src/pages/prezzi.tsx
@@ -102,6 +102,7 @@ const Prezzi: NextPage = () => {
opzioni={[
t.pricing_cmp.stabile_option1,
t.pricing_cmp.stabile_option2,
+ t.pricing_cmp.stabile_option3,
]}
pricingData={pricingStabile}
statico
diff --git a/apps/infoalloggi/src/server/api/routers/annunci.ts b/apps/infoalloggi/src/server/api/routers/annunci.ts
index 7d0e3c3..d07e596 100644
--- a/apps/infoalloggi/src/server/api/routers/annunci.ts
+++ b/apps/infoalloggi/src/server/api/routers/annunci.ts
@@ -19,6 +19,9 @@ import {
getOnline_AnnuncioIdCodice_AnnunciHandler,
getOptions_AnnunciHandler,
getProprietarioDataHandler,
+ type ImgToRemove,
+ removeAnnuncioMedia,
+ type VideoToRemove,
} from "~/server/controllers/annunci.controller";
import { db } from "~/server/db";
import { NewMail } from "~/server/services/mailer";
@@ -142,4 +145,11 @@ export const annunciRouter = createTRPCRouter({
}
return { status: "ok", timestamp: Date.now() };
}),
+ removeAnnuncioMedia: adminProcedure
+ .input(z.custom())
+ .mutation(async ({ input }) => {
+ return await removeAnnuncioMedia({
+ ...input,
+ });
+ }),
});
diff --git a/apps/infoalloggi/src/server/controllers/annunci.controller.ts b/apps/infoalloggi/src/server/controllers/annunci.controller.ts
index 2037742..f06fd36 100644
--- a/apps/infoalloggi/src/server/controllers/annunci.controller.ts
+++ b/apps/infoalloggi/src/server/controllers/annunci.controller.ts
@@ -495,3 +495,30 @@ export const getProprietarioDataHandler = async ({
});
}
};
+
+export type ImgToRemove = {
+ type: "img";
+ id: ImagesRefs["img"];
+};
+export type VideoToRemove = {
+ type: "video";
+ id: VideosRefs["video"];
+};
+export const removeAnnuncioMedia = async ({
+ id,
+ type,
+}: ImgToRemove | VideoToRemove) => {
+ try {
+ if (type === "img") {
+ await db.deleteFrom("images_refs").where("img", "=", id).execute();
+ }
+ if (type === "video") {
+ await db.deleteFrom("videos_refs").where("video", "=", id).execute();
+ }
+ } catch (e) {
+ throw new TRPCError({
+ code: "INTERNAL_SERVER_ERROR",
+ message: `Errore rimozione media: ${(e as Error).message}`,
+ });
+ }
+};
diff --git a/apps/infoalloggi/src/server/controllers/servizio.controller.ts b/apps/infoalloggi/src/server/controllers/servizio.controller.ts
index aca3328..ccadbb6 100644
--- a/apps/infoalloggi/src/server/controllers/servizio.controller.ts
+++ b/apps/infoalloggi/src/server/controllers/servizio.controller.ts
@@ -461,7 +461,11 @@ export const setupSaldoServizio = async ({
}
saldo = await qry.where("idprezziario", "=", mpp).executeTakeFirst();
} else if (servizio.tipologia === TipologiaPosizioneEnum.Stabile) {
- if (servizio.budget >= 500) {
+ if (servizio.budget >= 700) {
+ saldo = await qry
+ .where("idprezziario", "=", "SALDOCA700+" as PrezziarioIdprezziario)
+ .executeTakeFirst();
+ } else if (servizio.budget >= 500) {
saldo = await qry
.where("idprezziario", "=", "SALDOCA500+" as PrezziarioIdprezziario)
@@ -588,9 +592,11 @@ export const getPacksPerServizio = async (
}
case TipologiaPosizioneEnum.Stabile: {
const pack = await getPrezziarioByIdHandler({
- id: (servizio.budget >= 500
- ? "SALDOCA500+"
- : "SALDOCA500") as PrezziarioIdprezziario,
+ id: (servizio.budget >= 700
+ ? "SALDOCA700+"
+ : servizio.budget >= 500
+ ? "SALDOCA500+"
+ : "SALDOCA500") as PrezziarioIdprezziario,
});
if (!pack) {
return { success: false, message: "Pack non trovato" };