infoalloggi-monorepo/apps/infoalloggi/src/server/controllers/annunci.controller.ts

476 lines
12 KiB
TypeScript
Raw Normal View History

2025-08-04 17:45:44 +02:00
import type { Annunci, AnnunciId } from "~/schemas/public/Annunci";
import { TRPCError } from "@trpc/server";
import { createSrc, createSrcset } from "~/server/services/imageServer";
import { AnnuncioObjectWithImages } from "~/server/services/annunci.service";
import { zAnnuncioId } from "~/server/utils/zod_types";
import { z } from "zod/v4";
2025-08-04 17:45:44 +02:00
import { db } from "~/server/db";
import type { ServizioServizioId } from "~/schemas/public/Servizio";
// const ratelimit = new RateLimiterHandler({
// windowSize: 10,
// maxRequests: 10,
// analytics: true,
// });
export const getAnnunciListHandler = async (): Promise<
Pick<
Annunci,
"id" | "codice" | "titolo_it" | "comune" | "tipo" | "locatore" | "stato"
>[]
> => {
try {
return await db
.selectFrom("annunci")
.select([
"id",
"codice",
"titolo_it",
"comune",
"tipo",
"locatore",
"stato",
])
.execute();
} catch {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Errore query getAllRaw",
});
}
};
export const getCodici_AnnunciHandler = async (): Promise<string[]> => {
try {
const codici = await db
.selectFrom("annunci")
.select("codice")
.where("annunci.web", "=", true)
.where("stato", "!=", "Sospeso")
.execute();
if (!codici) {
return [];
}
return codici.map((c) => c.codice);
} catch {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Errore query getCodici",
});
}
};
export type AnnunciWithCoverImage = Annunci & { coverImage: string | null };
export const getAnnunciByCod = async ({
cod,
}: {
cod: string;
}): Promise<AnnunciWithCoverImage | null> => {
try {
const annuncio = await db
.selectFrom("annunci")
.selectAll()
.where("annunci.web", "=", true)
.where("codice", "=", cod)
.executeTakeFirst();
if (!annuncio) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Annuncio non trovato",
});
}
const coverImage = annuncio.url_immagini
? annuncio.url_immagini[0]
? createSrc(annuncio.url_immagini[0])
: null
: null;
const item = { ...annuncio, coverImage: coverImage };
return AnnuncioObjectWithImages<typeof item>(item);
} catch {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Errore query getAnnuncio",
});
}
};
export const getAnnunciById = async ({
id,
}: {
id: AnnunciId;
}): Promise<AnnunciWithCoverImage | null> => {
try {
const annuncio = await db
.selectFrom("annunci")
.selectAll()
.where("id", "=", id)
.executeTakeFirst();
if (!annuncio) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Annuncio non trovato",
});
}
const coverImage = annuncio.url_immagini
? annuncio.url_immagini[0]
? createSrc(annuncio.url_immagini[0])
: null
: null;
const item = { ...annuncio, coverImage: coverImage };
return AnnuncioObjectWithImages<typeof item>(item);
} catch {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Errore query getAnnuncioById",
});
}
};
export type AnnuncioRicercaWPosition = AnnuncioRicerca & {
lat_secondario: string | null;
lon_secondario: string | null;
};
export const get_AnnunciPositionsHandler = async ({
tipologia,
comune,
consegna,
}: {
tipologia?: string;
comune?: string;
consegna?: number;
}): Promise<AnnuncioRicercaWPosition[]> => {
try {
let query = db
.selectFrom("annunci")
.select([
"id",
"codice",
"comune",
"provincia",
"prezzo",
"consegna",
"numero_camere",
"mq",
"tipo",
"titolo_it",
"titolo_en",
"url_immagini",
"modificato_il",
"stato",
"lon_secondario",
"lat_secondario",
])
.where("web", "=", true)
.where("stato", "!=", "Sospeso")
.orderBy("id", "asc");
if (tipologia && tipologia !== "") {
query = query.where("tipo", "=", tipologia);
}
if (comune && comune !== "") {
query = query.where("comune", "like", comune);
}
if (consegna) {
query = query.where("consegna", "=", consegna);
}
const annunci = await query.execute();
if (!annunci) {
return [];
}
for (const annuncio of annunci) {
annuncio.url_immagini = annuncio.url_immagini
? createSrcset(annuncio.url_immagini)
: [];
}
return annunci;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Errore query getAnnunciPositions: " + (e as Error).message,
});
}
};
export type AnnuncioRicerca = Pick<
Annunci,
| "id"
| "codice"
| "comune"
| "provincia"
| "prezzo"
| "consegna"
| "numero_camere"
| "mq"
| "tipo"
| "titolo_it"
| "titolo_en"
| "url_immagini"
| "modificato_il"
| "stato"
>;
export const getCursor_AnnunciHandler = async ({
input,
}: {
input: {
limit?: number;
cursor?: string;
tipologia?: string;
comune?: string;
consegna?: number;
sort?: "Recenti" | "Prezzo" | "Consegna" | "Mq";
};
}): Promise<{
annunci: AnnuncioRicerca[];
nextCursor: string | undefined;
}> => {
const limit = input.limit ?? 50;
const [cursor, sortCursor] = input.cursor?.split("_") ?? [0, undefined];
function sortType(sort: "Recenti" | "Prezzo" | "Consegna" | "Mq") {
switch (sort) {
case "Recenti":
return { col: "modificato_il", opr: "desc" };
case "Prezzo":
return { col: "prezzo", opr: "asc" };
case "Consegna":
return { col: "consegna", opr: "asc" };
case "Mq":
return { col: "mq", opr: "asc" };
}
}
try {
let query = db
.selectFrom("annunci")
.select([
"id",
"codice",
"comune",
"provincia",
"prezzo",
"consegna",
"numero_camere",
"mq",
"tipo",
"titolo_it",
"titolo_en",
"url_immagini",
"modificato_il",
"stato",
])
.where("web", "=", true)
.where("stato", "!=", "Sospeso");
if (sortCursor && input.sort !== undefined) {
const { col: sortColumn, opr } = sortType(input.sort) as {
col: keyof Annunci;
opr: string;
};
query = query.where((eb) =>
eb.or([
eb.and([
eb("id", ">=", (cursor as AnnunciId) ?? 0),
eb(sortColumn, "=", sortCursor.toString()),
]),
eb(sortColumn, opr === "desc" ? "<" : ">", sortCursor.toString()),
]),
);
} else {
query = query.where("id", ">=", (cursor as AnnunciId) ?? 0);
}
if (input.tipologia && input.tipologia !== "") {
query = query.where("tipo", "=", input.tipologia);
}
if (input.comune && input.comune !== "") {
query = query.where("comune", "like", input.comune);
}
if (input.consegna) {
query = query.where("consegna", "=", input.consegna);
}
if (input.sort) {
switch (input.sort) {
case "Recenti":
query = query.orderBy("modificato_il", "desc");
break;
case "Prezzo":
query = query.orderBy("prezzo", "asc");
break;
case "Consegna":
query = query.orderBy("consegna", "asc");
break;
case "Mq":
query = query.orderBy("mq", "asc");
break;
}
}
query = query.orderBy("id", "asc");
const itemsraw = await query.limit(limit + 1).execute();
const annunci = itemsraw;
let nextCursor: string | undefined = undefined;
if (annunci.length > limit) {
const nextItem = annunci.pop(); // return the last item from the array
if (!nextItem) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Errore interno",
});
}
if (!input.sort) {
nextCursor = nextItem.id.toString();
} else {
const { col: sortColumn } = sortType(input.sort);
if (sortColumn === "modificato_il") {
if (!nextItem.modificato_il) {
nextCursor = `${nextItem.id.toString()}_1990-01-01`;
} else {
nextCursor = `${nextItem.id.toString()}_${nextItem.modificato_il.toISOString()}`;
}
} else {
nextCursor = `${nextItem.id.toString()}_${nextItem[sortColumn as keyof typeof nextItem]?.toString()}`;
}
}
}
for (const annuncio of annunci) {
annuncio.url_immagini = annuncio.url_immagini
? createSrcset(annuncio.url_immagini)
: [];
}
return {
annunci,
nextCursor,
};
} catch {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Errore query getWithCursor",
});
}
};
export const getOptions_AnnunciHandler = async () => {
try {
const comuni = await db
.selectFrom("annunci")
.select(["comune", "tipo", "consegna"])
.where("web", "=", true)
.where("stato", "!=", "Sospeso")
.distinct()
.execute();
const results: { comune: string; tipo: string; consegna: number }[] = [];
for (const comune of comuni) {
if (comune.comune && comune.tipo && comune.consegna) {
results.push({
comune: comune.comune,
tipo: comune.tipo,
consegna: comune.consegna,
});
}
}
return results;
} catch {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Errore query getComuni",
});
}
};
export const EdiAnnuncioSchema = z.object({
annuncioId: zAnnuncioId,
titolo_it: z.string().nonempty("Inserisci un titolo valido"),
desc_it: z.string().nonempty("Inserisci una descrizione valida"),
titolo_en: z.string().nonempty("Inserisci un titolo valido"),
desc_en: z.string().nonempty("Inserisci una descrizione valida"),
status: z.string().nonempty("Seleziona uno stato"),
});
type EditAnnuncioFormValues = z.infer<typeof EdiAnnuncioSchema>;
export const editAnnuncioHandler = async ({
annuncioId,
titolo_it,
desc_it,
titolo_en,
desc_en,
status,
}: EditAnnuncioFormValues) => {
try {
await db
.updateTable("annunci")
.set({
titolo_it: titolo_it,
desc_it: desc_it,
titolo_en: titolo_en,
desc_en: desc_en,
stato: status,
})
.where("id", "=", annuncioId)
.execute();
return true;
} catch {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Errore interno",
});
}
};
export const getProprietarioDataHandler = async ({
annuncioId,
servizioId,
}: {
annuncioId: AnnunciId;
servizioId: ServizioServizioId;
}) => {
try {
const opened_at = await db
.selectFrom("servizio_annunci")
.select("open_contatti_at")
.where("servizio_id", "=", servizioId)
.where("annunci_id", "=", annuncioId)
.executeTakeFirst();
if (!opened_at || !opened_at.open_contatti_at) {
return { propData: null, opened_at: null };
}
const propData = await db
.selectFrom("annunci")
.select([
"codice",
"comune",
"indirizzo",
"civico",
"provincia",
"regione",
"cap",
"lat",
"lon",
"numero",
"locatore",
])
.where("id", "=", annuncioId)
.executeTakeFirstOrThrow();
return { propData, opened_at: opened_at.open_contatti_at };
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Errore recupero dati proprietario: " + (e as Error).message,
});
}
};