1
0
Fork 0

feat: object notes

user-stream
Lea 2023-08-11 19:38:46 +02:00 committed by insert
parent 6cfe0a2ffa
commit 03fffc9849
9 changed files with 162 additions and 3 deletions

View File

@ -24,6 +24,7 @@ import { User } from "revolt-api";
import { decodeTime } from "ulid";
import relativeTime from "dayjs/plugin/relativeTime";
import SafetyNotesCard from "@/components/cards/SafetyNotesCard";
dayjs.extend(relativeTime);
export default async function User({
@ -43,6 +44,7 @@ export default async function User({
{user && <UserCard user={user} subtitle={account.email} withLink />}
<AccountActions account={account} user={user as User} />
<EmailClassificationCard email={account.email} />
<SafetyNotesCard objectId={account._id} type="account" />
<Separator />
<Card>

View File

@ -1,5 +1,6 @@
import { ChannelCard } from "@/components/cards/ChannelCard";
import { JsonCard } from "@/components/cards/JsonCard";
import SafetyNotesCard from "@/components/cards/SafetyNotesCard";
import { ServerCard } from "@/components/cards/ServerCard";
import { UserCard } from "@/components/cards/UserCard";
import { NavigationToolbar } from "@/components/common/NavigationToolbar";
@ -34,6 +35,8 @@ export default async function Message({ params }: { params: { id: string } }) {
</Link>
)}
<SafetyNotesCard objectId={channel._id} type="channel" />
{participants.length ? (
<>
<Separator />

View File

@ -1,5 +1,6 @@
import { ChannelCard } from "@/components/cards/ChannelCard";
import { JsonCard } from "@/components/cards/JsonCard";
import SafetyNotesCard from "@/components/cards/SafetyNotesCard";
import { UserCard } from "@/components/cards/UserCard";
import { NavigationToolbar } from "@/components/common/NavigationToolbar";
import { Card, CardHeader } from "@/components/ui/card";
@ -28,6 +29,8 @@ export default async function Message({
</CardHeader>
</Card>
<SafetyNotesCard objectId={message._id} type="message" />
{author && (
<Link href={`/panel/inspect/user/${author!._id}`}>
<UserCard user={author!} subtitle="Message Author" />

View File

@ -1,4 +1,5 @@
import { JsonCard } from "@/components/cards/JsonCard";
import SafetyNotesCard from "@/components/cards/SafetyNotesCard";
import { ServerCard } from "@/components/cards/ServerCard";
import { UserCard } from "@/components/cards/UserCard";
import { NavigationToolbar } from "@/components/common/NavigationToolbar";
@ -21,6 +22,7 @@ export default async function Server({ params }: { params: { id: string } }) {
<NavigationToolbar>Inspecting Server</NavigationToolbar>
<ServerCard server={server} subtitle="Server" />
<ServerActions server={server} />
<SafetyNotesCard objectId={server._id} type="server" />
{server.description && (
<Card>
<CardHeader>

View File

@ -1,4 +1,5 @@
import { JsonCard } from "@/components/cards/JsonCard";
import SafetyNotesCard from "@/components/cards/SafetyNotesCard";
import { UserCard } from "@/components/cards/UserCard";
import { NavigationToolbar } from "@/components/common/NavigationToolbar";
import { RecentMessages } from "@/components/pages/inspector/RecentMessages";
@ -76,6 +77,7 @@ export default async function User({
<UserCard user={user} subtitle={user.status?.text ?? "No status set"} />
<UserActions user={user} bot={bot as Bot} />
<SafetyNotesCard objectId={user._id} type="user" />
{user.profile?.content && (
<Card>

View File

@ -1,3 +1,7 @@
import SafetyNotesCard from "@/components/cards/SafetyNotesCard";
export default function Home() {
return <main>this is the admin panel ever</main>;
return <main>
<SafetyNotesCard objectId="home" type="global" title="Bulletin board" />
</main>;
}

View File

@ -0,0 +1,89 @@
"use client"
import { useEffect, useState } from "react";
import { Textarea } from "../ui/textarea";
import { toast } from "../ui/use-toast";
import { SafetyNotes, fetchSafetyNote, updateSafetyNote } from "@/lib/db";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../ui/card";
import { useSession } from "next-auth/react";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
dayjs.extend(relativeTime);
export default function SafetyNotesCard({ objectId, type, title }: {
objectId: string,
type: SafetyNotes["_id"]["type"],
title?: string
}) {
const session = useSession();
const [draft, setDraft] = useState("");
const [value, setValue] = useState<SafetyNotes | null>(null);
const [ready, setReady] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchSafetyNote(objectId, type)
.then((note) => {
setDraft(note?.text || "");
setValue(note);
setReady(true);
})
.catch((e) => {
setError(String(e));
});
}, [objectId, type]);
return (
<Card>
<CardHeader>
<CardTitle>{title ?? type.charAt(0).toUpperCase() + type.slice(1) + " notes"}</CardTitle>
</CardHeader>
<CardContent>
<Textarea
placeholder={
error
? error
: ready
? "Enter notes here... (save on unfocus)"
: "Fetching notes..."
}
className="!min-h-[80px] max-h-[50vh]"
disabled={!ready || error != null}
value={ready ? draft : undefined} // not defaulting to undefined causes next to complain
onChange={(e) => setDraft(e.currentTarget.value)}
onBlur={async () => {
if (draft === value?.text ?? "") return;
try {
await updateSafetyNote(objectId, type, draft);
setValue({
_id: { id: objectId, type: type },
edited_at: new Date(Date.now()),
edited_by: session.data?.user?.email || "",
text: draft,
});
toast({
title: "Updated notes",
});
} catch (err) {
toast({
title: "Failed to update notes",
description: String(err),
variant: "destructive",
});
}
}}
/>
</CardContent>
<CardFooter className="-my-2">
<CardDescription>
{
value
? <>Last edited {dayjs(value.edited_at).fromNow()} by {value.edited_by}</>
: <>No object note set</>
}
</CardDescription>
</CardFooter>
</Card>
)
}

View File

@ -1,5 +1,5 @@
import { getServerSession } from "next-auth";
import { insertAuditLog } from "./db";
import { SafetyNotes, insertAuditLog } from "./db";
type Permission =
| "authifier"
@ -49,7 +49,11 @@ type Permission =
| "/relations"}`
| `/create${"" | "/alert" | "/strike"}`
| `/update${"" | "/badges"}`
| `/action${"" | "/unsuspend" | "/suspend" | "/wipe" | "/ban" | "/wipe-profile"}`}`;
| `/action${"" | "/unsuspend" | "/suspend" | "/wipe" | "/ban" | "/wipe-profile"}`}`
| `safety_notes${
| ""
| `/fetch${"" | `/${SafetyNotes["_id"]["type"]}`}`
| `/update${"" | `/${SafetyNotes["_id"]["type"]}`}`}`;
const PermissionSets = {
// Admin
@ -65,6 +69,7 @@ const PermissionSets = {
"sessions",
"servers",
"users",
"safety_notes",
] as Permission[],
// View open reports
@ -92,6 +97,12 @@ const PermissionSets = {
"bots/fetch/by-id",
"bots/update/discoverability",
"safety_notes/fetch/global",
"safety_notes/fetch/server",
"safety_notes/fetch/user",
"safety_notes/update/server",
"safety_notes/update/user",
] as Permission[],
// User support
@ -112,6 +123,9 @@ const PermissionSets = {
"channels/update/invites",
"channels/fetch/invites",
"safety_notes/fetch",
"safety_notes/update",
] as Permission[],
// Moderate users
@ -147,6 +161,9 @@ const PermissionSets = {
"publish_message",
"chat_message",
"safety_notes/fetch",
"safety_notes/update",
] as Permission[],
};

View File

@ -560,3 +560,40 @@ export async function fetchAuthifierEmailClassification(provider: string) {
.collection<EmailClassification>("email_classification")
.findOne({ _id: provider });
}
export type SafetyNotes = {
_id: { id: string, type: "message" | "channel" | "server" | "user" | "account" | "global" };
text: string;
edited_by: string;
edited_at: Date;
}
export async function fetchSafetyNote(objectId: string, type: SafetyNotes["_id"]["type"]) {
await checkPermission(`safety_notes/fetch/${type}`, objectId);
return mongo()
.db("revolt")
.collection<SafetyNotes>("safety_notes")
.findOne({ _id: { id: objectId, type: type } });
}
export async function updateSafetyNote(objectId: string, type: SafetyNotes["_id"]["type"], note: string) {
await checkPermission(`safety_notes/update/${type}`, objectId);
const session = await getServerSession();
return mongo()
.db("revolt")
.collection<SafetyNotes>("safety_notes")
.updateOne(
{ _id: { id: objectId, type: type } },
{
$set: {
text: note,
edited_at: new Date(Date.now()),
edited_by: session?.user?.email ?? "",
},
},
{ upsert: true },
);
}