feat: enhance email management features and improve flag handling
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
06c93024f7
commit
e33ee7a8cd
18 changed files with 451 additions and 142 deletions
|
|
@ -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">
|
||||
“{email.title}”
|
||||
</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 });
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 || "",
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue