forked from administration/panel
feat: further work on more narrow permissions
parent
70aa95577a
commit
ea39b68703
|
@ -8,13 +8,14 @@ import { RelevantReports } from "@/components/pages/inspector/RelevantReports";
|
||||||
import { UserActions } from "@/components/pages/inspector/UserActions";
|
import { UserActions } from "@/components/pages/inspector/UserActions";
|
||||||
import { Card, CardHeader } from "@/components/ui/card";
|
import { Card, CardHeader } from "@/components/ui/card";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { PLATFORM_MOD_ID } from "@/lib/constants";
|
|
||||||
import {
|
import {
|
||||||
fetchBotById,
|
fetchBotById,
|
||||||
fetchBotsByUser,
|
fetchBotsByUser,
|
||||||
fetchMembershipsByUser,
|
fetchMembershipsByUser,
|
||||||
fetchMessages,
|
fetchNoticesByUser,
|
||||||
fetchReports,
|
fetchReports,
|
||||||
|
fetchReportsAgainstUser,
|
||||||
|
fetchReportsByUser,
|
||||||
fetchServers,
|
fetchServers,
|
||||||
fetchSnapshots,
|
fetchSnapshots,
|
||||||
fetchStrikesByUser,
|
fetchStrikesByUser,
|
||||||
|
@ -37,17 +38,14 @@ export default async function User({
|
||||||
const bot = user.bot ? await fetchBotById(user._id) : undefined;
|
const bot = user.bot ? await fetchBotById(user._id) : undefined;
|
||||||
const botOwner = user.bot ? await fetchUserById(user.bot.owner) : undefined;
|
const botOwner = user.bot ? await fetchUserById(user.bot.owner) : undefined;
|
||||||
|
|
||||||
// Fetch strikes
|
// Fetch strikes and moderation alerts
|
||||||
const strikes = await fetchStrikesByUser(user._id);
|
const strikes = await fetchStrikesByUser(user._id);
|
||||||
|
const notices = await fetchNoticesByUser(user._id);
|
||||||
// Fetch moderation alerts
|
|
||||||
const dm = await findDM(PLATFORM_MOD_ID, user._id);
|
|
||||||
const notices = dm ? await fetchMessages({ channel: dm._id }) : [];
|
|
||||||
|
|
||||||
// Fetch friends and bots
|
// Fetch friends and bots
|
||||||
const botIds = await fetchBotsByUser(user._id).then((bots) =>
|
const botIds = await fetchBotsByUser(user._id)
|
||||||
bots.map((bot) => bot._id)
|
.then((bots) => bots.map((bot) => bot._id))
|
||||||
);
|
.catch(() => []);
|
||||||
|
|
||||||
const relevantUsers = await fetchUsersById([
|
const relevantUsers = await fetchUsersById([
|
||||||
...botIds,
|
...botIds,
|
||||||
|
@ -59,30 +57,18 @@ export default async function User({
|
||||||
relevantUsers.sort((a) => (a.bot ? -1 : 0));
|
relevantUsers.sort((a) => (a.bot ? -1 : 0));
|
||||||
|
|
||||||
// Fetch server memberships
|
// Fetch server memberships
|
||||||
const serverMemberships = await fetchMembershipsByUser(user._id);
|
const serverMemberships = await fetchMembershipsByUser(user._id).catch(
|
||||||
|
() => []
|
||||||
|
);
|
||||||
const servers = await fetchServers({
|
const servers = await fetchServers({
|
||||||
_id: {
|
_id: {
|
||||||
$in: serverMemberships.map((member) => member._id.server),
|
$in: serverMemberships.map((member) => member._id.server),
|
||||||
},
|
},
|
||||||
});
|
}).catch(() => []);
|
||||||
|
|
||||||
// Fetch reports
|
// Fetch reports
|
||||||
const reportsByUser = await fetchReports({
|
const reportsByUser = await fetchReportsByUser(user._id);
|
||||||
author_id: user._id,
|
const reportsAgainstUser = await fetchReportsAgainstUser(user._id);
|
||||||
});
|
|
||||||
|
|
||||||
const reportIdsInvolvingUser = await fetchSnapshots({
|
|
||||||
// TODO: slow query
|
|
||||||
$or: [{ "content._id": user._id }, { "content.author": user._id }],
|
|
||||||
}).then((snapshots) => [
|
|
||||||
...new Set(snapshots.map((snapshot) => snapshot.report_id)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const reportsAgainstUser = await fetchReports({
|
|
||||||
_id: {
|
|
||||||
$in: reportIdsInvolvingUser,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
|
@ -123,7 +109,7 @@ export default async function User({
|
||||||
<RelevantReports byUser={reportsByUser} forUser={reportsAgainstUser} />
|
<RelevantReports byUser={reportsByUser} forUser={reportsAgainstUser} />
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
<RecentMessages query={{ author: user._id }} />
|
<RecentMessages userId={user._id} />
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
<JsonCard obj={user} />
|
<JsonCard obj={user} />
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { NavigationToolbar } from "@/components/common/NavigationToolbar";
|
||||||
import { ReportActions } from "@/components/pages/inspector/ReportActions";
|
import { ReportActions } from "@/components/pages/inspector/ReportActions";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import {
|
import {
|
||||||
fetchRelatedReports,
|
fetchRelatedReportsByContent,
|
||||||
fetchReportById,
|
fetchReportById,
|
||||||
fetchSnapshotsByReport,
|
fetchSnapshotsByReport,
|
||||||
fetchUserById,
|
fetchUserById,
|
||||||
|
@ -28,7 +28,7 @@ export default async function Reports({ params }: { params: { id: string } }) {
|
||||||
snapshots
|
snapshots
|
||||||
.map((snapshot) => snapshot._id)
|
.map((snapshot) => snapshot._id)
|
||||||
.map((contentId) =>
|
.map((contentId) =>
|
||||||
fetchRelatedReports(contentId).then((reports) =>
|
fetchRelatedReportsByContent(contentId).then((reports) =>
|
||||||
reports.filter((entry) => entry._id !== report._id)
|
reports.filter((entry) => entry._id !== report._id)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,7 +15,12 @@ export function ListCompactor<T>({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{data.length === 0 && (
|
{data.length === 0 && (
|
||||||
<h2 className="text-sm text-center pb-2 text-gray-400">Empty List</h2>
|
<>
|
||||||
|
<h2 className="text-sm text-center pb-2 text-gray-400">Empty List</h2>
|
||||||
|
<h3 className="text-xs text-center pb-2 text-gray-400">
|
||||||
|
You may be lacking permissions.
|
||||||
|
</h3>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{data.slice(0, limit).map((item, index) => (
|
{data.slice(0, limit).map((item, index) => (
|
||||||
<Component key={index} item={item} />
|
<Component key={index} item={item} />
|
||||||
|
|
|
@ -7,23 +7,32 @@ import {
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "../../ui/card";
|
} from "../../ui/card";
|
||||||
import { fetchMessages, fetchUsersById } from "@/lib/db";
|
import { fetchMessages, fetchMessagesByUser, fetchUsersById } from "@/lib/db";
|
||||||
import { CompactMessage } from "../../cards/CompactMessage";
|
import { CompactMessage } from "../../cards/CompactMessage";
|
||||||
|
|
||||||
export async function RecentMessages({
|
type Props = { users?: boolean | User[] } & (
|
||||||
query,
|
| {
|
||||||
users,
|
query: Filter<Message>;
|
||||||
}: {
|
}
|
||||||
query: Filter<Message>;
|
| {
|
||||||
users?: boolean | User[];
|
userId: string;
|
||||||
}) {
|
}
|
||||||
const recentMessages = (await fetchMessages(query)).reverse();
|
);
|
||||||
|
|
||||||
|
export async function RecentMessages(props: Props) {
|
||||||
|
const recentMessages = (
|
||||||
|
(props as { query: {} }).query
|
||||||
|
? await fetchMessages((props as { query: Filter<Message> }).query)
|
||||||
|
: await fetchMessagesByUser((props as { userId: string }).userId).catch(
|
||||||
|
() => []
|
||||||
|
)
|
||||||
|
).reverse();
|
||||||
|
|
||||||
const userList = (
|
const userList = (
|
||||||
users === true
|
props.users === true
|
||||||
? await fetchUsersById([...new Set(recentMessages.map((x) => x.author))])
|
? await fetchUsersById([...new Set(recentMessages.map((x) => x.author))])
|
||||||
: Array.isArray(users)
|
: Array.isArray(props.users)
|
||||||
? users
|
? props.users
|
||||||
: []
|
: []
|
||||||
).reduce((prev, next) => {
|
).reduce((prev, next) => {
|
||||||
prev[next._id] = next;
|
prev[next._id] = next;
|
||||||
|
@ -38,6 +47,16 @@ export async function RecentMessages({
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{/* enter reason for fetching */}
|
{/* enter reason for fetching */}
|
||||||
|
{!recentMessages.length && (
|
||||||
|
<>
|
||||||
|
<h2 className="text-sm text-center pb-2 text-gray-400">
|
||||||
|
No messages found
|
||||||
|
</h2>
|
||||||
|
<h3 className="text-xs text-center pb-2 text-gray-400">
|
||||||
|
You may be lacking permissions.
|
||||||
|
</h3>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{recentMessages.map((message) => (
|
{recentMessages.map((message) => (
|
||||||
<CompactMessage
|
<CompactMessage
|
||||||
key={message._id}
|
key={message._id}
|
||||||
|
|
|
@ -29,14 +29,8 @@ import {
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { decodeTime } from "ulid";
|
import { decodeTime } from "ulid";
|
||||||
|
|
||||||
/**
|
import relativeTime from "dayjs/plugin/relativeTime";
|
||||||
*
|
dayjs.extend(relativeTime);
|
||||||
* @param param0 You have received an account strike, for one or more reasons:
|
|
||||||
- REASON_HERE
|
|
||||||
|
|
||||||
Further violations will result in suspension or a permanent ban depending on severity, please abide by the [Acceptable Usage Policy](https://revolt.chat/aup).
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function RelevantModerationNotices({
|
export function RelevantModerationNotices({
|
||||||
userId,
|
userId,
|
||||||
|
@ -68,9 +62,7 @@ export function RelevantModerationNotices({
|
||||||
{strikesDraft.map((strike) => (
|
{strikesDraft.map((strike) => (
|
||||||
<TableRow key={strike._id}>
|
<TableRow key={strike._id}>
|
||||||
<TableCell>{strike.reason}</TableCell>
|
<TableCell>{strike.reason}</TableCell>
|
||||||
<TableCell>
|
<TableCell>{dayjs(decodeTime(strike._id)).fromNow()}</TableCell>
|
||||||
{dayjs(decodeTime(strike._id))?.fromNow()}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|
|
@ -13,14 +13,14 @@ type Permission =
|
||||||
| `/fetch${"" | "/by-id" | "/by-user"}`
|
| `/fetch${"" | "/by-id" | "/by-user"}`
|
||||||
| `/update${"" | "/discoverability"}`}`
|
| `/update${"" | "/discoverability"}`}`
|
||||||
| `channels${"" | `/fetch${"" | "/by-id" | "/dm"}` | `/create${"" | "/dm"}`}`
|
| `channels${"" | `/fetch${"" | "/by-id" | "/dm"}` | `/create${"" | "/dm"}`}`
|
||||||
| `messages${"" | `/fetch${"" | "/by-id"}`}`
|
| `messages${"" | `/fetch${"" | "/by-id" | "/by-user"}`}`
|
||||||
| `reports${
|
| `reports${
|
||||||
| ""
|
| ""
|
||||||
| `/fetch${
|
| `/fetch${
|
||||||
| ""
|
| ""
|
||||||
| "/by-id"
|
| "/by-id"
|
||||||
| "/open"
|
| "/open"
|
||||||
| `/related${"" | "/by-content" | "/by-user"}`
|
| `/related${"" | "/by-content" | "/by-user" | "/against-user"}`
|
||||||
| `/snapshots${"" | "/by-report" | "/by-user"}`}`
|
| `/snapshots${"" | "/by-report" | "/by-user"}`}`
|
||||||
| `/update${
|
| `/update${
|
||||||
| ""
|
| ""
|
||||||
|
@ -36,24 +36,55 @@ type Permission =
|
||||||
| `/update${"" | "/flags" | "/discoverability"}`}`
|
| `/update${"" | "/flags" | "/discoverability"}`}`
|
||||||
| `users${
|
| `users${
|
||||||
| ""
|
| ""
|
||||||
| `/fetch${"" | "/by-id" | "/memberships"}`
|
| `/fetch${
|
||||||
|
| ""
|
||||||
|
| "/by-id"
|
||||||
|
| "/memberships"
|
||||||
|
| "/strikes"
|
||||||
|
| "/notices"
|
||||||
|
| "/relations"}`
|
||||||
| `/create${"" | "/alert" | "/strike"}`
|
| `/create${"" | "/alert" | "/strike"}`
|
||||||
| `/update${"" | "/badges"}`
|
| `/update${"" | "/badges"}`
|
||||||
| `/action${"" | "/unsuspend" | "/suspend" | "/wipe" | "/ban"}`}`;
|
| `/action${"" | "/unsuspend" | "/suspend" | "/wipe" | "/ban"}`}`;
|
||||||
|
|
||||||
const PermissionSets = {
|
const PermissionSets = {
|
||||||
|
// View open reports
|
||||||
"view-open-reports": [
|
"view-open-reports": [
|
||||||
// Required for viewing open reports
|
|
||||||
"users/fetch/by-id",
|
"users/fetch/by-id",
|
||||||
"reports/fetch/open",
|
"reports/fetch/open",
|
||||||
"reports/fetch/by-id",
|
"reports/fetch/by-id",
|
||||||
"reports/fetch/related",
|
"reports/fetch/related",
|
||||||
"reports/fetch/snapshots/by-report",
|
"reports/fetch/snapshots/by-report",
|
||||||
] as Permission[],
|
] as Permission[],
|
||||||
|
|
||||||
|
// Edit reports
|
||||||
|
"edit-reports": [
|
||||||
|
"reports/update/notes",
|
||||||
|
"reports/update/resolve",
|
||||||
|
"reports/update/reject",
|
||||||
|
"reports/update/reopen",
|
||||||
|
] as Permission[],
|
||||||
|
|
||||||
|
// Moderate users
|
||||||
|
"moderate-users": [
|
||||||
|
"users/fetch/by-id",
|
||||||
|
"users/fetch/strikes",
|
||||||
|
"users/fetch/notices",
|
||||||
|
// "bots/fetch/by-user",
|
||||||
|
// "messages/fetch/by-user",
|
||||||
|
// "users/fetch/memberships",
|
||||||
|
// "servers/fetch",
|
||||||
|
"reports/fetch/related/by-user",
|
||||||
|
"reports/fetch/related/against-user",
|
||||||
|
] as Permission[],
|
||||||
};
|
};
|
||||||
|
|
||||||
const Roles = {
|
const Roles = {
|
||||||
moderator: [...PermissionSets["view-open-reports"]],
|
moderator: [
|
||||||
|
...PermissionSets["view-open-reports"],
|
||||||
|
...PermissionSets["edit-reports"],
|
||||||
|
...PermissionSets["moderate-users"],
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const ACL: Record<string, Set<Permission>> = {
|
const ACL: Record<string, Set<Permission>> = {
|
||||||
|
|
122
lib/db.ts
122
lib/db.ts
|
@ -14,7 +14,8 @@ import type {
|
||||||
} from "revolt-api";
|
} from "revolt-api";
|
||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
import { publishMessage } from "./redis";
|
import { publishMessage } from "./redis";
|
||||||
import { checkPermission } from "./accessPermissions";
|
import { checkPermission, hasPermissionFromSession } from "./accessPermissions";
|
||||||
|
import { PLATFORM_MOD_ID } from "./constants";
|
||||||
|
|
||||||
let client: MongoClient;
|
let client: MongoClient;
|
||||||
|
|
||||||
|
@ -121,7 +122,16 @@ export async function fetchUserById(id: string) {
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
.collection<User>("users")
|
.collection<User>("users")
|
||||||
.findOne({ _id: id });
|
.findOne(
|
||||||
|
{ _id: id },
|
||||||
|
{
|
||||||
|
projection: {
|
||||||
|
relations: (await hasPermissionFromSession("users/fetch/relations"))
|
||||||
|
? 1
|
||||||
|
: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchUsersById(ids: string[]) {
|
export async function fetchUsersById(ids: string[]) {
|
||||||
|
@ -130,7 +140,16 @@ export async function fetchUsersById(ids: string[]) {
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
.collection<User>("users")
|
.collection<User>("users")
|
||||||
.find({ _id: { $in: ids } })
|
.find(
|
||||||
|
{ _id: { $in: ids } },
|
||||||
|
{
|
||||||
|
projection: {
|
||||||
|
relations: (await hasPermissionFromSession("users/fetch/relations"))
|
||||||
|
? 1
|
||||||
|
: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
.toArray();
|
.toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,6 +246,26 @@ export async function fetchMessageById(id: string) {
|
||||||
.findOne({ _id: id });
|
.findOne({ _id: id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchMessagesByUser(userId: string) {
|
||||||
|
await checkPermission("messages/fetch/by-user");
|
||||||
|
|
||||||
|
return await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Message>("messages")
|
||||||
|
.find({ author: userId }, { sort: { _id: -1 }, limit: 50 })
|
||||||
|
.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchMessagesByChannel(channelId: string) {
|
||||||
|
await checkPermission("messages/fetch/by-user");
|
||||||
|
|
||||||
|
return await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Message>("messages")
|
||||||
|
.find({ channel: channelId }, { sort: { _id: -1 }, limit: 50 })
|
||||||
|
.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchMessages(query: Filter<Message>, limit = 50) {
|
export async function fetchMessages(query: Filter<Message>, limit = 50) {
|
||||||
await checkPermission("messages/fetch");
|
await checkPermission("messages/fetch");
|
||||||
|
|
||||||
|
@ -254,7 +293,7 @@ export async function fetchOpenReports() {
|
||||||
.toArray();
|
.toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchRelatedReports(contentId: string) {
|
export async function fetchRelatedReportsByContent(contentId: string) {
|
||||||
await checkPermission("reports/fetch/related/by-content");
|
await checkPermission("reports/fetch/related/by-content");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
|
@ -271,6 +310,57 @@ export async function fetchRelatedReports(contentId: string) {
|
||||||
.toArray();
|
.toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchReportsByUser(userId: string) {
|
||||||
|
await checkPermission("reports/fetch/related/by-user");
|
||||||
|
|
||||||
|
return await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Report>("safety_reports")
|
||||||
|
.find(
|
||||||
|
{ status: "Created", authorId: userId },
|
||||||
|
{
|
||||||
|
sort: {
|
||||||
|
_id: -1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchReportsAgainstUser(userId: string) {
|
||||||
|
await checkPermission("reports/fetch/related/against-user");
|
||||||
|
|
||||||
|
const reportIdsInvolvingUser = await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<{ _id: string; report_id: string; content: SnapshotContent }>(
|
||||||
|
"safety_snapshots"
|
||||||
|
)
|
||||||
|
.find({
|
||||||
|
$or: [{ "content._id": userId }, { "content.author": userId }],
|
||||||
|
})
|
||||||
|
.toArray()
|
||||||
|
.then((snapshots) => [
|
||||||
|
...new Set(snapshots.map((snapshot) => snapshot.report_id)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Report>("safety_reports")
|
||||||
|
.find(
|
||||||
|
{
|
||||||
|
_id: {
|
||||||
|
$in: reportIdsInvolvingUser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sort: {
|
||||||
|
_id: -1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchReports(
|
export async function fetchReports(
|
||||||
query: Filter<Report> = { status: "Created" }
|
query: Filter<Report> = { status: "Created" }
|
||||||
) {
|
) {
|
||||||
|
@ -332,7 +422,7 @@ export async function fetchSnapshotsByReport(reportId: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchStrikesByUser(userId: string) {
|
export async function fetchStrikesByUser(userId: string) {
|
||||||
await checkPermission("reports/fetch/snapshots/by-user");
|
await checkPermission("users/fetch/strikes");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -348,6 +438,28 @@ export async function fetchStrikesByUser(userId: string) {
|
||||||
.toArray();
|
.toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchNoticesByUser(userId: string) {
|
||||||
|
await checkPermission("users/fetch/notices");
|
||||||
|
|
||||||
|
const dm = await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Channel & { channel_type: "DirectMessage" }>("channels")
|
||||||
|
.findOne({
|
||||||
|
channel_type: "DirectMessage",
|
||||||
|
recipients: {
|
||||||
|
$all: [userId, PLATFORM_MOD_ID],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!dm) return [];
|
||||||
|
|
||||||
|
return await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Message>("messages")
|
||||||
|
.find({ channel: dm!._id }, { sort: { _id: -1 } })
|
||||||
|
.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchBotsByUser(userId: string) {
|
export async function fetchBotsByUser(userId: string) {
|
||||||
await checkPermission("bots/fetch/by-user");
|
await checkPermission("bots/fetch/by-user");
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue