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"; 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 => { 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 => { 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] ? annuncio.url_immagini[0] : null : null; const item = { ...annuncio, coverImage: coverImage }; return AnnuncioObjectWithImages(item); } catch { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Errore query getAnnuncio", }); } }; export const getAnnunciById = async ({ id, }: { id: AnnunciId; }): Promise => { 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(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 => { 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; 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, }); } };