diff --git a/app/panel/shield/classifications/page.tsx b/app/panel/shield/classifications/page.tsx index ac6210b..d115974 100644 --- a/app/panel/shield/classifications/page.tsx +++ b/app/panel/shield/classifications/page.tsx @@ -1,8 +1,54 @@ -import EmailClassificationRow from "@/components/pages/shield/EmailClassificationRow"; +"use client"; + +import EmailClassificationRow, { + CLASSIFICATIONS, +} from "@/components/pages/shield/EmailClassificationRow"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; -import { Table, TableBody, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Command, CommandItem } from "@/components/ui/command"; +import { Input } from "@/components/ui/input"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { + Table, + TableBody, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { toast } from "@/components/ui/use-toast"; +import { createEmailClassification, fetchEmailClassifications } from "@/lib/actions"; +import { EmailClassification } from "@/lib/db"; +import { useEffect, useState } from "react"; export default function Classifications() { + const [loaded, setLoaded] = useState(false); + const [domains, setDomains] = useState([] as EmailClassification[]); + const [domainDraft, setDomainDraft] = useState(""); + const [classificationDraft, setClassificationDraft] = useState(""); + const [classificationOpen, setClassificationOpen] = useState(false); + + useEffect(() => { + fetchEmailClassifications().then((domains) => { + setDomains(domains); + setLoaded(true); + }); + }, []); + return ( @@ -11,10 +57,87 @@ export default function Classifications() { DomainClassificationAction + + + + + + + + Create classification + + setDomainDraft(e.currentTarget.value)} + placeholder="reddit.com" + /> + + + + + + + {CLASSIFICATIONS.map((c) => ( + { + setClassificationDraft(c); + setClassificationOpen(false); + }}> + {c} + + ))} + + + + + + + Cancel + { + try { + await createEmailClassification(domainDraft, classificationDraft); + setDomains([...domains, { _id: domainDraft, classification: classificationDraft }]); + setDomainDraft(""); + setClassificationDraft(""); + setClassificationOpen(false); + toast({ + title: "Classification created", + }); + } catch (e) { + toast({ + title: "Failed to create classification", + description: String(e), + variant: "destructive", + }); + } + }} + > + Create + + + + + - + {domains.map((domain) => ( + + ))}
diff --git a/components/pages/shield/EmailClassificationRow.tsx b/components/pages/shield/EmailClassificationRow.tsx index 0014e4b..f1d89ee 100644 --- a/components/pages/shield/EmailClassificationRow.tsx +++ b/components/pages/shield/EmailClassificationRow.tsx @@ -1,68 +1,137 @@ -"use client" +"use client"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTrigger, + AlertDialogCancel, + AlertDialogDescription, +} from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { Command, CommandItem } from "@/components/ui/command"; -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; import { TableCell, TableRow } from "@/components/ui/table"; +import { toast } from "@/components/ui/use-toast"; +import { deleteEmailClassification, updateEmailClassification } from "@/lib/actions"; import { cn } from "@/lib/utils"; import { Check } from "lucide-react"; import { useState } from "react"; -const CLASSIFICATIONS = [ - "DISPOSABLE", - "GAYSEX", - "HOMOSEXUAL", - "FDGIUHDIFUIOFZH", - ]; +export const CLASSIFICATIONS = [ + "DISPOSABLE", + "GAYSEX", + "HOMOSEXUAL", + "FDGIUHDIFUIOFZH", +]; -export default function EmailClassificationRow({ domain, ...props }: { domain: string, classification: string }) { - const [selectClassification, setSelectClassification] = useState(false); - const [classification, setClassification] = useState(props.classification); - const [deleted, setDeleted] = useState(false); - - return deleted ? null : ( - - {domain} - - - - - - - - {CLASSIFICATIONS.map((c) => ( - + {domain} + + + + + + + + {CLASSIFICATIONS.map((c) => ( + { - setSelectClassification(false); - setClassification(c); + onSelect={async () => { + try { + await updateEmailClassification(domain, c); + + setSelectClassification(false); + setClassification(c); + toast({ + title: "Classification updated", + description: `${domain} is now classified as ${c}`, + }); + } catch (e) { + toast({ + title: "Failed to update classification", + description: String(e), + variant: "destructive", + }); + } }} > - - {c} - - ))} - - - - - - - - - ); - } \ No newline at end of file + + {c} + + ))} + + + + + + + + + + + + + Delete classification for {domain}? + + + + Cancel + { + try { + await deleteEmailClassification(domain); + setDeleted(true); + toast({ + title: "Classification deleted", + }); + } catch(e) { + toast({ + title: "Failed to delete classification", + description: String(e), + variant: "destructive", + }); + } + }} + > + Remove + + + + + + + ); +} diff --git a/lib/accessPermissions.ts b/lib/accessPermissions.ts index 6b92f73..f9e18e7 100644 --- a/lib/accessPermissions.ts +++ b/lib/accessPermissions.ts @@ -2,7 +2,16 @@ import { getServerSession } from "next-auth"; import { SafetyNotes, insertAuditLog } from "./db"; type Permission = - | "authifier" + | `authifier${ + | "" + | `/classification${ + | "" + | "/fetch" + | "/create" + | "/update" + | "/delete" + }` + }` | "publish_message" | "chat_message" | `accounts${ @@ -189,6 +198,10 @@ const PermissionSets = { "safety_notes/fetch", "safety_notes/update", ] as Permission[], + + "authifier": [ + "authifier/classification", + ] as Permission[], }; const Roles = { @@ -196,6 +209,7 @@ const Roles = { ...PermissionSets["view-open-reports"], ...PermissionSets["edit-reports"], ...PermissionSets["moderate-users"], + ...PermissionSets["authifier"], ], "user-support": [...PermissionSets["user-support"]], "revolt-discover": [...PermissionSets["revolt-discover"]], diff --git a/lib/actions.ts b/lib/actions.ts index cca188f..e138e0a 100644 --- a/lib/actions.ts +++ b/lib/actions.ts @@ -5,6 +5,7 @@ import { PLATFORM_MOD_ID, RESTRICT_ACCESS_LIST } from "./constants"; import mongo, { Account, ChannelInvite, + EmailClassification, createDM, fetchAccountById, findDM, @@ -903,3 +904,47 @@ export async function fetchBackup(name: string) { return JSON.parse((await readFile(`./exports/${name}`)).toString("utf-8")); } + +export async function fetchEmailClassifications(): Promise { + await checkPermission("authifier/classification/fetch", null); + + return await mongo() + .db("authifier") + .collection("email_classification") + .find({}) + .toArray(); +} + +export async function createEmailClassification(domain: string, classification: string) { + await checkPermission("authifier/classification/create", { domain, classification }); + + await mongo() + .db("authifier") + .collection("email_classification") + .insertOne( + { _id: domain, classification }, + ); +} + +export async function updateEmailClassification(domain: string, classification: string) { + await checkPermission("authifier/classification/update", { domain, classification }); + + await mongo() + .db("authifier") + .collection("email_classification") + .updateOne( + { _id: domain }, + { $set: { classification } }, + ); +} + +export async function deleteEmailClassification(domain: string) { + await checkPermission("authifier/classification/delete", domain); + + await mongo() + .db("authifier") + .collection("email_classification") + .deleteOne( + { _id: domain }, + ); +}