feat: enhance email management features and improve flag handling

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Marco Pedone 2026-04-29 16:31:57 +02:00
parent 06c93024f7
commit e33ee7a8cd
18 changed files with 451 additions and 142 deletions

View file

@ -1,15 +1,266 @@
import { ExternalLink, Maximize2, Trash2 } from "lucide-react";
import Link from "next/link";
import { type IframeHTMLAttributes, useState } from "react";
import toast from "react-hot-toast";
import { Confirm } from "~/components/confirm";
import { LoadingPage } from "~/components/loading";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "~/components/ui/accordion";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog";
import { Label } from "~/components/ui/label";
import { Switch } from "~/components/ui/switch";
import { FormEditQueueMail } from "~/forms/FormEditQueueMail";
import { useMediaQuery } from "~/hooks/use-media-query";
import type { UsersId } from "~/schemas/public/Users";
import type { Email_EventData } from "~/server/controllers/event_queue.controller";
import { api } from "~/utils/api";
import { Button } from "../ui/button";
export const AdminComunicazioni = () => {
const { data: emails, isLoading: isEmailsLoading } =
api.comunicazioni.getEmails.useQuery();
const { data: queue, isLoading: isQueueLoading } =
api.comunicazioni.getQueuedEmails.useQuery();
const { data: flag, isLoading: isFlagLoading } =
api.flags.GetFlagValue.useQuery("EMAIL_ENABLED", {
refetchOnWindowFocus: true,
});
const utils = api.useUtils();
const { mutate: deleteEmail } = api.comunicazioni.deleteEmail.useMutation({
onSuccess: async () => {
toast.success("Email eliminata con successo");
setOpenEmail("unset");
await utils.comunicazioni.getEmails.invalidate();
},
});
const { mutate: clearEmailQueue } =
api.comunicazioni.clearEmailQueue.useMutation({
onSuccess: async () => {
toast.success("Coda email pulita");
await utils.comunicazioni.getQueuedEmails.invalidate();
},
onError: (error) => {
console.error(error);
toast.error("Errore durante la pulizia della coda email");
},
});
const { mutate: removeFromQueue } =
api.comunicazioni.removeFromEmailQueue.useMutation({
onSuccess: async () => {
toast.success("Email rimossa dalla coda");
await utils.comunicazioni.getQueuedEmails.invalidate();
},
onError: (error) => {
console.error(error);
toast.error("Errore durante la rimozione dell'email dalla coda");
},
});
const { mutate: updateEvent } = api.comunicazioni.updateEvent.useMutation({
onSuccess: async () => {
toast.success("Email aggiornata nella coda");
await utils.comunicazioni.getQueuedEmails.invalidate();
},
onError: (error) => {
console.error(error);
toast.error("Errore durante l'aggiornamento dell'email nella coda");
},
});
const { mutate: editFlag } = api.flags.EditFlag.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => {
toast.success("Successo");
await utils.flags.GetFlagValue.invalidate();
},
});
const [openEmail, setOpenEmail] = useState<string | "unset">("unset");
const mobile = useMediaQuery("(max-width: 768px)");
const iframeHeight = mobile ? 600 : 500;
if (isEmailsLoading || isQueueLoading || isFlagLoading) {
return <LoadingPage />;
}
return (
<div className="mx-auto flex w-full flex-col gap-4 py-4">
<div className="flex items-center gap-2">
<Label htmlFor="comms">Abilita comunicazioni Globale</Label>
<Switch
className="data-[state=checked]:bg-neutral-700"
defaultChecked={flag ?? false}
id="comms"
onCheckedChange={(checked) => {
editFlag({ id: "EMAIL_ENABLED", value: checked });
}}
/>
</div>
<hr />
<div className="flex w-full flex-col gap-2">
<div className="flex items-center gap-2">
<h3>Email in coda: {queue?.length || 0}</h3>{" "}
<Confirm
description="Sei sicuro di voler svuotare la coda email?"
onConfirm={() => {
clearEmailQueue();
}}
title="Elimina"
>
<Button
aria-label="Delete"
disabled={queue?.length === 0}
size="sm"
variant="destructive"
>
<Trash2 />
Pulisci coda email
</Button>
</Confirm>
</div>
{queue?.length === 0 && <div>Nessuna email in coda</div>}
{queue?.map((email) => {
const mailData = email.data as Email_EventData;
return (
<div
className="flex items-center gap-2 rounded-md border p-1.5"
key={email.event_id}
>
<Dialog>
<DialogTrigger asChild>
<Button size="sm" variant="outline">
<Maximize2 />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-xl">
<DialogHeader>
<DialogTitle>Dettagli</DialogTitle>
<DialogDescription className="sr-only">
dettagli dell'email in coda
</DialogDescription>
</DialogHeader>
<FormEditQueueMail
data={email}
del={() => removeFromQueue({ eventId: email.event_id })}
submitMutation={(values) => {
updateEvent({ eventId: email.event_id, data: values });
}}
/>
</DialogContent>
</Dialog>
<span className="font-semibold">{email.username}</span>
<span>
{mailData.data.mail.subject ||
mailData.data.template?.mailType ||
"Nessun oggetto"}
</span>
<span className="text-muted-foreground text-sm">
data invio:{" "}
{new Date(email.lock_expires).toLocaleDateString("it-IT", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
})}
</span>
</div>
);
})}
</div>
<hr />
<div className="flex w-full flex-col gap-2">
<h3>Email inviate:</h3>
{emails?.length === 0 && <div>Nessuna email inviata</div>}
<Accordion
className="space-y-4"
collapsible
onValueChange={setOpenEmail}
type="single"
value={openEmail}
>
{emails?.map((email) => {
const isOpen = openEmail === email.id_email.toString();
return (
<AccordionItem
className="space-y-2 border-none"
key={email.id_email}
value={email.id_email.toString()}
>
<AccordionTrigger className="rounded-md bg-muted px-4 py-2">
<div className="flex items-center gap-2">
<span>{email.user}</span>
<span className="text-muted-foreground text-sm">
&ldquo;{email.title}&rdquo;
</span>
-
<span>
{new Date(email.created_at).toLocaleDateString("it-IT", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
})}
</span>
</div>
</AccordionTrigger>
<AccordionContent className="overflow-clip rounded-md border">
<div className="flex h-7 w-full items-center justify-end gap-4 bg-secondary px-4">
<Link
href={`/area-riservata/admin/user-view/comunicazioni/${email.userId}`}
>
<Button className="h-5">
<span>Vai alle email dell'utente</span>
<ExternalLink className="size-3" />
</Button>
</Link>
<Button
className="h-5"
onClick={() =>
confirm(
"Sei sicuro di voler eliminare questa email?",
) &&
deleteEmail({
id_email: email.id_email,
})
}
variant="destructive"
>
Rimuovi
</Button>
</div>
{isOpen && (
<LoadingEmailIframe
height={iframeHeight}
srcDoc={email.html}
title="Email Content"
width="100%"
/>
)}
</AccordionContent>
</AccordionItem>
);
})}
</Accordion>
</div>
</div>
);
};
export const EmailAccordion = ({
userId,
isAdmin,
@ -17,7 +268,7 @@ export const EmailAccordion = ({
userId: UsersId;
isAdmin: boolean;
}) => {
const { data } = api.comunicazioni.getEmails.useQuery({
const { data } = api.comunicazioni.getUserEmails.useQuery({
userId,
});
const utils = api.useUtils();
@ -25,7 +276,7 @@ export const EmailAccordion = ({
onSuccess: async () => {
toast.success("Email eliminata con successo");
setOpenEmail("unset");
await utils.comunicazioni.getEmails.invalidate({ userId });
await utils.comunicazioni.getUserEmails.invalidate({ userId });
},
});

View file

@ -54,7 +54,7 @@ export const UserViewHeader = () => {
},
onSuccess: async () => {
toast.success("Email inviata con successo");
await utils.comunicazioni.getEmails.invalidate({
await utils.comunicazioni.getUserEmails.invalidate({
userId: data?.id || "",
});
},

View file

@ -327,7 +327,7 @@ const PostConfermaSection = () => {
<div className="flex flex-col gap-1 rounded-md border border-card bg-card p-4">
<h3 className="font-semibold text-green-500">
{locale === "it"
? "Ci siamo quasi! Stiamo preparando il contratto per te."
? "Immobile Confermato! Stiamo preparando il contratto per te."
: "Almost there! We are preparing the contract for you."}
</h3>
<h3 className="font-semibold">

View file

@ -42,7 +42,13 @@ import {
} from "../ui/collapsible";
import { AnnuncioDettaglio, TipoBadge } from "./annuncio_dettaglio";
export const AnnuncioCard = ({ className }: { className?: string }) => {
export const AnnuncioCard = ({
className,
userInteractionDisabled = false,
}: {
className?: string;
userInteractionDisabled?: boolean;
}) => {
const { isAdmin } = useServizio();
const { data } = useServizioAnnuncio();
@ -67,6 +73,15 @@ export const AnnuncioCard = ({ className }: { className?: string }) => {
<AdminActions />
</div>
) : userInteractionDisabled ? (
<div>
<p>
Contatti:{" "}
{data.open_contatti_at
? `Ottenuti il ${format(data.open_contatti_at, "dd/MM/yyyy HH:mm")}`
: "Non ancora ottenuti"}
</p>
</div>
) : (
<UserActions />
)
@ -149,10 +164,10 @@ export const AnnuncioCardRichieste = ({
<div className="flex w-full flex-col gap-4">
<div>
<p>
Visto contatti:{" "}
Contatti:{" "}
{data.open_contatti_at
? format(data.open_contatti_at, "dd/MM/yyyy HH:mm")
: "Non ancora visto"}
? `Ottenuti il ${format(data.open_contatti_at, "dd/MM/yyyy HH:mm")}`
: "Non ancora ottenuti"}
</p>
</div>
</div>
@ -164,7 +179,7 @@ export const AnnuncioCardRichieste = ({
</Link>
<IncrociDialog
annuncioId={data.id}
props={{ className: "w-fit" , size: "sm"}}
props={{ className: "w-fit", size: "sm" }}
/>
<div>
<AnnuncioContatti data={data} />

View file

@ -568,8 +568,11 @@ const Main = () => {
}
const InConferma = servizio.annunci.filter((a) => a.status_conferma !== null);
const NonInConferma = servizio.annunci.filter(
(a) => a.status_conferma === null,
);
// in conferma, mostra solo annunci confermati
// in conferma
if (servizio.isOkSaldo && InConferma.length > 0) {
return (
<>
@ -584,20 +587,18 @@ const Main = () => {
</ServizioAnnuncioProvider>
);
})}
{isAdmin && (
<div className="w-full space-y-2">
<h4 className="text-lg">Altri annunci:</h4>
<div className="@container grid grid-flow-row @sm:grid-cols-2 grid-cols-1 gap-4">
{servizio.annunci
.filter((a) => a.status_conferma === null)
.map((data) => {
{NonInConferma.map((data) => {
return (
<ServizioAnnuncioProvider
data={data}
key={data.id}
servizioId={servizio.servizio_id}
>
<AnnuncioCard />
<AnnuncioCard userInteractionDisabled />
</ServizioAnnuncioProvider>
);
})}
@ -607,7 +608,6 @@ const Main = () => {
)}
</div>
</div>
)}
</>
);
}
@ -653,7 +653,7 @@ const Main = () => {
</div>
)}
<div className="@container grid grid-flow-row @sm:grid-cols-2 grid-cols-1 gap-4">
{servizio.annunci.map((data) => {
{NonInConferma.map((data) => {
return (
<ServizioAnnuncioProvider
data={data}

View file

@ -26,7 +26,13 @@ export const DocInfo = ({
const { data: file_info } = api.storage.getFileInfos.useQuery({
storageId,
});
if (!file_info) return null;
if (!file_info)
return (
<div className="flex items-center gap-2">
<p>File non trovato</p>
{children}
</div>
);
return (
<div className="flex w-full flex-col gap-2">
<Label htmlFor="selected-file">File Selezionato: </Label>

View file

@ -62,9 +62,7 @@ import { cn, redirectTo500 } from "~/lib/utils";
import { AnnuncioContext, useAnnuncio } from "~/providers/AnnuncioProvider";
import { useTranslation } from "~/providers/I18nProvider";
import { buildRicercaUrl } from "~/providers/RicercaProvider";
import {
tipologiaPosizioneEnum,
} from "~/schemas/public/TipologiaPosizioneEnum";
import { tipologiaPosizioneEnum } from "~/schemas/public/TipologiaPosizioneEnum";
import type { AnnuncioData } from "~/server/controllers/annunci.controller";
import { generateSSGHelper } from "~/server/utils/ssgHelper";
import { api } from "~/utils/api";
@ -201,9 +199,9 @@ export async function getStaticProps(
};
}
const flag = await ssg.flags.GetFlagValue.fetch({
id: "ANNUNCIO_INTERACTIONS_DISABLED",
});
const flag = await ssg.flags.GetFlagValue.fetch(
"ANNUNCIO_INTERACTIONS_DISABLED",
);
return {
props: {
data: result.value,

View file

@ -167,11 +167,7 @@ const EditModal = ({
</DialogHeader>
<div className="py-4">
<FormFlags
handleRemove={(id) =>
remove({
id,
})
}
handleRemove={(id) => remove(id)}
initialValues={initial}
submitMutation={(fields) => {
if (initial) {

View file

@ -25,7 +25,7 @@ import { FormEditQueueMail } from "~/forms/FormEditQueueMail";
import { FormAddCodaEmail } from "~/forms/FormNewMail";
import type { NextPageWithLayout } from "~/pages/_app";
import { useEnforcedSession } from "~/providers/SessionProvider";
import { usersId, type UsersId } from "~/schemas/public/Users";
import { type UsersId, usersId } from "~/schemas/public/Users";
import type { Email_EventData } from "~/server/controllers/event_queue.controller";
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
@ -50,7 +50,7 @@ const ComunicazioniUser: NextPageWithLayout<ComunicazioniUserProps> = ({
},
onSuccess: async () => {
toast.success("Comunicazioni eliminate");
await utils.comunicazioni.getEmails.invalidate({ userId });
await utils.comunicazioni.getUserEmails.invalidate({ userId });
},
});
@ -116,7 +116,7 @@ export const getServerSideProps = (async (context) => {
const helpers = await TrpcAuthedFetchingIstance({ access_token });
if (helpers) {
await helpers.trpc.comunicazioni.getEmails.prefetch({
await helpers.trpc.comunicazioni.getUserEmails.prefetch({
userId: parsed.data,
});
await helpers.trpc.comunicazioni.getUserQueuedEmails.prefetch({
@ -187,7 +187,7 @@ const Coda = ({ userId }: { userId: UsersId }) => {
},
});
const { mutate: removeFromQueue } =
api.comunicazioni.removeFromUserEmailQueue.useMutation({
api.comunicazioni.removeFromEmailQueue.useMutation({
onSuccess: async () => {
toast.success("Email rimossa dalla coda");
await utils.comunicazioni.getUserQueuedEmails.invalidate({ userId });

View file

@ -1,5 +1,8 @@
import Head from "next/head";
import { EmailAccordion } from "~/components/area-riservata/comunicazioni";
import {
AdminComunicazioni,
EmailAccordion,
} from "~/components/area-riservata/comunicazioni";
import { AreaRiservataLayout } from "~/components/Layout";
import type { NextPageWithLayout } from "~/pages/_app";
import { useEnforcedSession } from "~/providers/SessionProvider";
@ -9,7 +12,7 @@ import { useEnforcedSession } from "~/providers/SessionProvider";
* Mostra le comunicazioni email inviate all'utente.
*/
const Comunicazioni: NextPageWithLayout = () => {
const session = useEnforcedSession();
const { user } = useEnforcedSession();
return (
<>
@ -21,11 +24,11 @@ const Comunicazioni: NextPageWithLayout = () => {
<div className="p-1">
<h1 className="font-bold text-2xl">Comunicazioni</h1>
</div>
<EmailAccordion
isAdmin={session.user.isAdmin}
userId={session.user.id}
/>
{user.isAdmin ? (
<AdminComunicazioni />
) : (
<EmailAccordion isAdmin={user.isAdmin} userId={user.id} />
)}
</div>
</>
);

View file

@ -120,9 +120,7 @@ export const getServerSideProps = (async (context) => {
return redirectTo500;
}
const flag = await helper.trpc.flags.GetFlagValue.fetch({
id: "STRIPE_DISABLED",
});
const flag = await helper.trpc.flags.GetFlagValue.fetch("STRIPE_DISABLED");
const payment_type = context.query?.type;
if (typeof payment_type !== "string") {
console.error("Error: payment type is not a string");

View file

@ -17,7 +17,9 @@ import {
} from "~/server/api/trpc";
import {
addEventToQueue,
clearEmailQueue,
clearUserEmailQueue,
getEmailQueue,
removeEventFromQueue,
updateEvent,
userEmailQueue,
@ -28,16 +30,20 @@ import {
deleteAllUserEmails,
deleteEmail,
getEmails,
getUserEmails,
} from "~/server/services/email.service";
import type { MailsTemplates } from "~/server/services/mail-templates";
import { NewMail } from "~/server/services/mailer";
import { CONDIZIONI_DEFAULT } from "~/server/services/prezziario.service";
export const comunicazioniRouter = createTRPCRouter({
getEmails: protectedProcedure
getEmails: adminProcedure.query(async () => {
return await getEmails();
}),
getUserEmails: protectedProcedure
.input(z.object({ userId: usersId }))
.query(async ({ input }) => {
return await getEmails({
return await getUserEmails({
userId: input.userId,
});
}),
@ -220,6 +226,9 @@ export const comunicazioniRouter = createTRPCRouter({
.query(async ({ input }) => {
return await userEmailQueue(input.userId);
}),
getQueuedEmails: adminProcedure.query(async () => {
return await getEmailQueue();
}),
addToUserEmailQueue: adminProcedure
.input(
z.object({
@ -264,7 +273,7 @@ export const comunicazioniRouter = createTRPCRouter({
}
}),
removeFromUserEmailQueue: adminProcedure
removeFromEmailQueue: adminProcedure
.input(
z.object({
eventId: eventQueueEventId,
@ -273,7 +282,9 @@ export const comunicazioniRouter = createTRPCRouter({
.mutation(async ({ input }) => {
return await removeEventFromQueue(input.eventId);
}),
clearEmailQueue: adminProcedure.mutation(async () => {
return await clearEmailQueue();
}),
clearUserEmailQueue: adminProcedure
.input(z.object({ userId: usersId }))
.mutation(async ({ input }) => {

View file

@ -1,5 +1,5 @@
import { z } from "zod/v4";
import { flagsId } from "~/schemas/public/Flags";
import { FlagsSchema, flagsId } from "~/schemas/public/Flags";
import {
adminProcedure,
createTRPCRouter,
@ -12,47 +12,26 @@ import {
GetFlagValueHandler,
RemoveFlagHandler,
} from "~/server/controllers/flags.controller";
import { db } from "~/server/db";
export const flagsRouter = createTRPCRouter({
AddFlag: adminProcedure
.input(
z.object({
id: flagsId,
value: z.boolean(),
AddFlag: adminProcedure.input(FlagsSchema).mutation(async ({ input }) => {
return await AddFlagHandler(input);
}),
)
.mutation(async ({ input }) => {
return await AddFlagHandler({ db, input });
}),
EditFlag: adminProcedure
.input(
z.object({
id: flagsId,
value: z.boolean(),
}),
)
.mutation(async ({ input }) => {
return await EditFlagHandler({ db, input });
EditFlag: adminProcedure.input(FlagsSchema).mutation(async ({ input }) => {
return await EditFlagHandler(input);
}),
GetFlags: adminProcedure.query(async () => {
return await GetFlagHandler({ db });
return await GetFlagHandler();
}),
GetFlagValue: publicProcedure
.input(z.object({ id: z.string() }))
.input(z.string())
.query(async ({ input }) => {
return await GetFlagValueHandler({ db, id: input.id });
return await GetFlagValueHandler(input);
}),
RemoveFlag: adminProcedure
.input(
z.object({
id: flagsId,
}),
)
.mutation(async ({ input }) => {
return await RemoveFlagHandler({ db, id: input.id });
RemoveFlag: adminProcedure.input(flagsId).mutation(async ({ input }) => {
return await RemoveFlagHandler(input);
}),
});

View file

@ -223,7 +223,32 @@ export const processEvents = async () => {
}
return { message: `${events.length} events processed` };
};
export const getEmailQueue = async () => {
try {
const events = await db
.selectFrom("event_queue")
.selectAll("event_queue")
.select((eb) => [
eb
.selectFrom("users")
.select("username")
.whereRef(
"users.id",
"=",
sql`(event_queue.data->'data'->>'userId')::uuid`,
)
.as("username"),
])
.where(sql`data->>'tipo'`, "=", "email")
.execute();
return events;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error while getting email events from queue, ${(e as Error).message}`,
});
}
};
export const userEmailQueue = async (userId: UsersId) => {
try {
const events = await db
@ -256,6 +281,21 @@ export const removeEventFromQueue = async (eventId: EventQueueEventId) => {
});
}
};
export const clearEmailQueue = async () => {
try {
await db
.deleteFrom("event_queue")
.where(sql`data->>'tipo'`, "=", "email")
.execute();
return true;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error while clearing email events from queue, ${(e as Error).message}`,
cause: e,
});
}
};
export const clearUserEmailQueue = async (userId: UsersId) => {
try {
await db

View file

@ -1,14 +1,8 @@
import { TRPCError } from "@trpc/server";
import type { Flags, FlagsId } from "~/schemas/public/Flags";
import type { Querier } from "~/server/db";
import { db } from "~/server/db";
export const EditFlagHandler = async ({
db,
input,
}: {
db: Querier;
input: Flags;
}) => {
export const EditFlagHandler = async (input: Flags) => {
try {
return await db
.updateTable("flags")
@ -25,7 +19,7 @@ export const EditFlagHandler = async ({
});
}
};
export const GetFlagHandler = async ({ db }: { db: Querier }) => {
export const GetFlagHandler = async () => {
try {
return await db.selectFrom("flags").selectAll().execute();
} catch (e) {
@ -36,13 +30,7 @@ export const GetFlagHandler = async ({ db }: { db: Querier }) => {
}
};
export const GetFlagValueHandler = async ({
db,
id,
}: {
db: Querier;
id: string;
}) => {
export const GetFlagValueHandler = async (id: string) => {
try {
const flag = await db
.selectFrom("flags")
@ -58,13 +46,7 @@ export const GetFlagValueHandler = async ({
}
};
export const RemoveFlagHandler = async ({
db,
id,
}: {
db: Querier;
id: FlagsId;
}) => {
export const RemoveFlagHandler = async (id: FlagsId) => {
try {
await db.deleteFrom("flags").where("id", "=", id).execute();
return true;
@ -75,13 +57,7 @@ export const RemoveFlagHandler = async ({
});
}
};
export const AddFlagHandler = async ({
db,
input,
}: {
db: Querier;
input: Flags;
}) => {
export const AddFlagHandler = async (input: Flags) => {
try {
await db
.insertInto("flags")

View file

@ -1,6 +1,5 @@
import { env } from "~/env";
import { GetFlagValueHandler } from "~/server/controllers/flags.controller";
import { db } from "~/server/db";
import { NewMail } from "~/server/services/mailer";
const BASEURL = "https://api.skebby.it/API/v1.0/REST/";
@ -131,10 +130,7 @@ async function sendSMS(auth: Auth, smsData: SMSData) {
};*/
export const SendMessage = async (data: MinimalSMSData) => {
const smsFlag = await GetFlagValueHandler({
db: db,
id: "SMS_ENABLED",
});
const smsFlag = await GetFlagValueHandler("SMS_ENABLED");
if (smsFlag) {
try {
const auth = await login(env.SKEBBY_USER, env.SKEBBY_PASS);

View file

@ -29,7 +29,51 @@ export const storeEmail = async ({
}
};
export const getEmails = async ({ userId }: { userId: UsersId }) => {
export const getEmails = async () => {
try {
const mails = await db
.selectFrom("emails")
.innerJoin("users", "users.id", "emails.user_id")
.selectAll("emails")
.select("users.username")
.orderBy("emails.created_at", "desc")
.execute();
const gen = await Promise.all(
mails.map(async (mail) => {
return {
...(await genMail(mail.data as MailsTemplates)),
created_at: mail.created_at,
id_email: mail.id_email,
user: mail.username,
userId: mail.user_id,
};
}),
);
const obsoleteMails = gen.filter((mail) => mail.html === "");
if (obsoleteMails.length > 0) {
await db
.deleteFrom("emails")
.where(
"id_email",
"in",
obsoleteMails.map((mail) => mail.id_email),
)
.execute();
}
return gen;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Errore durante il recupero delle email: ${(e as Error).message}`,
});
}
};
export const getUserEmails = async ({ userId }: { userId: UsersId }) => {
try {
const mails = await db
.selectFrom("emails")
@ -65,7 +109,7 @@ export const getEmails = async ({ userId }: { userId: UsersId }) => {
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Errore durante il recupero delle email: ${(e as Error).message}`,
message: `Errore durante il recupero delle email utente: ${(e as Error).message}`,
});
}
};

View file

@ -23,7 +23,6 @@ import type { JSX } from "react";
import { env } from "~/env";
import type { UsersId } from "~/schemas/public/Users";
import { GetFlagValueHandler } from "~/server/controllers/flags.controller";
import { db } from "~/server/db";
import { storeEmail } from "~/server/services/email.service";
import {
@ -147,10 +146,7 @@ export const genMail = async ({ ...mail }: MailsTemplates) => {
};
export const NewMail = async ({ template, userId, mail }: NewMailProps) => {
const emailFlag = await GetFlagValueHandler({
db: db,
id: "EMAIL_ENABLED",
});
const emailFlag = await GetFlagValueHandler("EMAIL_ENABLED");
if (emailFlag) {
try {
if (template) {