Add more moderation systems, document new APIs

This commit is contained in:
Jesse Wierzbinski 2024-03-03 18:33:25 -10:00
parent a87c8b6cc5
commit 847e679a10
No known key found for this signature in database
3 changed files with 428 additions and 67 deletions

268
API.md Normal file
View file

@ -0,0 +1,268 @@
# API
The Lysand project uses the Mastodon API to interact with clients. However, the moderation API is custom-made for Lysand Server, as it allows for more fine-grained control over the server's behavior.
## Flags, ModTags and ModNotes
Flags are used by Lysand Server to automatically attribute tags to a status or account based on rules. ModTags and ModNotes are used by moderators to manually tag and take notes on statuses and accounts.
The difference between flags and modtags is that flags are automatically attributed by the server, while modtags are manually attributed by moderators.
### Flag Types
- `content_filter`: (Statuses only) The status contains content that was filtered by the server's content filter.
- `bio_filter`: (Accounts only) The account's bio contains content that was filtered by the server's content filter.
- `emoji_filter`: The status or account contains an emoji that was filtered by the server's content filter.
- `reported`: The status or account was previously reported by a user.
- `suspended`: The status or account was previously suspended by a moderator.
- `silenced`: The status or account was previously silenced by a moderator.
### ModTag Types
ModTag do not have set types and can be anything. Lysand Server autosuggest previously used tags when a moderator is adding a new tag to avoid duplicates.
### Data Format
```ts
type Flag = {
id: string,
// One of the following two fields will be present
flaggedStatus?: Status,
flaggedUser?: User,
flagType: string,
createdAt: string,
}
type ModTag = {
id: string,
// One of the following two fields will be present
taggedStatus?: Status,
taggedUser?: User,
mod: User,
tag: string,
createdAt: string,
}
type ModNote = {
id: string,
// One of the following two fields will be present
notedStatus?: Status,
notedUser?: User,
mod: User,
note: string,
createdAt: string,
}
```
The `User` and `Status` types are the same as the ones in the Mastodon API.
## Moderation API Routes
### `GET /api/v1/moderation/accounts/:id`
Returns full moderation data and flags for the account with the given ID.
Output format:
```ts
{
id: string, // Same ID as in account field
flags: Flag[],
modtags: ModTag[],
modnotes: ModNote[],
account: User,
}
```
### `GET /api/v1/moderation/statuses/:id`
Returns full moderation data and flags for the status with the given ID.
Output format:
```ts
{
id: string, // Same ID as in status field
flags: Flag[],
modtags: ModTag[],
modnotes: ModNote[],
status: Status,
}
```
### `POST /api/v1/moderation/accounts/:id/modtags`
Params:
- `tag`: string
Adds a modtag to the account with the given ID
### `POST /api/v1/moderation/statuses/:id/modtags`
Params:
- `tag`: string
Adds a modtag to the status with the given ID
### `POST /api/v1/moderation/accounts/:id/modnotes`
Params:
- `note`: string
Adds a modnote to the account with the given ID
### `POST /api/v1/moderation/statuses/:id/modnotes`
Params:
- `note`: string
Adds a modnote to the status with the given ID
### `DELETE /api/v1/moderation/accounts/:id/modtags/:modtag_id`
Deletes the modtag with the given ID from the account with the given ID
### `DELETE /api/v1/moderation/statuses/:id/modtags/:modtag_id`
Deletes the modtag with the given ID from the status with the given ID
### `DELETE /api/v1/moderation/accounts/:id/modnotes/:modnote_id`
Deletes the modnote with the given ID from the account with the given ID
### `DELETE /api/v1/moderation/statuses/:id/modnotes/:modnote_id`
Deletes the modnote with the given ID from the status with the given ID
### `GET /api/v1/moderation/modtags`
Returns a list of all modtags previously used by moderators
Output format:
```ts
{
tags: string[],
}
```
### `GET /api/v1/moderation/accounts/flags/search`
Allows moderators to search for accounts based on their flags, this can also include status flags
Params:
- `limit`: Number
- `min_id`: String. Returns results immediately newer than this ID. In effect, sets a cursor at this ID and paginates forward.
- `max_id`: String. All results returned will be lesser than this ID. In effect, sets an upper bound on results.
- `since_id`: String. All results returned will be greater than this ID. In effect, sets a lower bound on results.
- `flags`: String (optional). Comma-separated list of flag types to filter by. Can be left out to return accounts with at least one flag
- `flag_count`: Number (optional). Minimum number of flags to filter by
- `include_statuses`: Boolean (optional). If true, includes status flags in the search results
- `account_id`: Array of strings (optional). Filters accounts by account ID
This method returns a `Link` header the same way Mastodon does, to allow for pagination.
Output format:
```ts
{
accounts: {
account: User,
modnotes: ModNote[],
flags: Flag[],
statuses?: {
status: Status,
modnotes: ModNote[],
flags: Flag[],
}[],
}[],
}
```
### `GET /api/v1/moderation/statuses/flags/search`
Allows moderators to search for statuses based on their flags
Params:
- `limit`: Number
- `min_id`: String. Returns results immediately newer than this ID. In effect, sets a cursor at this ID and paginates forward.
- `max_id`: String. All results returned will be lesser than this ID. In effect, sets an upper bound on results.
- `since_id`: String. All results returned will be greater than this ID. In effect, sets a lower bound on results.
- `flags`: String (optional). Comma-separated list of flag types to filter by. Can be left out to return statuses with at least one flag
- `flag_count`: Number (optional). Minimum number of flags to filter by
- `account_id`: Array of strings (optional). Filters statuses by account ID
This method returns a `Link` header the same way Mastodon does, to allow for pagination.
Output format:
```ts
{
statuses: {
status: Status,
modnotes: ModNote[],
flags: Flag[],
}[],
}
```
### `GET /api/v1/moderation/accounts/modtags/search`
Allows moderators to search for accounts based on their modtags
Params:
- `limit`: Number
- `min_id`: String. Returns results immediately newer than this ID. In effect, sets a cursor at this ID and paginates forward.
- `max_id`: String. All results returned will be lesser than this ID. In effect, sets an upper bound on results.
- `since_id`: String. All results returned will be greater than this ID. In effect, sets a lower bound on results.
- `tags`: String (optional). Comma-separated list of tags to filter by. Can be left out to return accounts with at least one tag
- `tag_count`: Number (optional). Minimum number of tags to filter by
- `include_statuses`: Boolean (optional). If true, includes status tags in the search results
- `account_id`: Array of strings (optional). Filters accounts by account ID
This method returns a `Link` header the same way Mastodon does, to allow for pagination.
Output format:
```ts
{
accounts: {
account: User,
modnotes: ModNote[],
modtags: ModTag[],
statuses?: {
status: Status,
modnotes: ModNote[],
modtags: ModTag[],
}[],
}[],
}
```
### `GET /api/v1/moderation/statuses/modtags/search`
Allows moderators to search for statuses based on their modtags
Params:
- `limit`: Number
- `min_id`: String. Returns results immediately newer than this ID. In effect, sets a cursor at this ID and paginates forward.
- `max_id`: String. All results returned will be lesser than this ID. In effect, sets an upper bound on results.
- `since_id`: String. All results returned will be greater than this ID. In effect, sets a lower bound on results.
- `tags`: String (optional). Comma-separated list of tags to filter by. Can be left out to return statuses with at least one tag
- `tag_count`: Number (optional). Minimum number of tags to filter by
- `account_id`: Array of strings (optional). Filters statuses by account ID
- `include_statuses`: Boolean (optional). If true, includes status tags in the search results
This method returns a `Link` header the same way Mastodon does, to allow for pagination.
Output format:
```ts
{
statuses: {
status: Status,
modnotes: ModNote[],
modtags: ModTag[],
}[],
}
```

View file

@ -0,0 +1,76 @@
/*
Warnings:
- You are about to drop the column `statusId` on the `Flag` table. All the data in the column will be lost.
- You are about to drop the column `userId` on the `Flag` table. All the data in the column will be lost.
- You are about to drop the `ModerationData` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropForeignKey
ALTER TABLE "Flag" DROP CONSTRAINT "Flag_statusId_fkey";
-- DropForeignKey
ALTER TABLE "Flag" DROP CONSTRAINT "Flag_userId_fkey";
-- DropForeignKey
ALTER TABLE "ModerationData" DROP CONSTRAINT "ModerationData_creatorId_fkey";
-- DropForeignKey
ALTER TABLE "ModerationData" DROP CONSTRAINT "ModerationData_statusId_fkey";
-- AlterTable
ALTER TABLE "Flag" DROP COLUMN "statusId",
DROP COLUMN "userId",
ADD COLUMN "flaggeStatusId" UUID,
ADD COLUMN "flaggedUserId" UUID;
-- DropTable
DROP TABLE "ModerationData";
-- CreateTable
CREATE TABLE "ModNote" (
"id" UUID NOT NULL DEFAULT uuid_generate_v7(),
"notedStatusId" UUID,
"notedUserId" UUID,
"modId" UUID NOT NULL,
"note" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ModNote_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ModTag" (
"id" UUID NOT NULL DEFAULT uuid_generate_v7(),
"taggedStatusId" UUID,
"taggedUserId" UUID,
"modId" UUID NOT NULL,
"tag" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ModTag_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "ModNote" ADD CONSTRAINT "ModNote_notedStatusId_fkey" FOREIGN KEY ("notedStatusId") REFERENCES "Status"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ModNote" ADD CONSTRAINT "ModNote_notedUserId_fkey" FOREIGN KEY ("notedUserId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ModNote" ADD CONSTRAINT "ModNote_modId_fkey" FOREIGN KEY ("modId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ModTag" ADD CONSTRAINT "ModTag_taggedStatusId_fkey" FOREIGN KEY ("taggedStatusId") REFERENCES "Status"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ModTag" ADD CONSTRAINT "ModTag_taggedUserId_fkey" FOREIGN KEY ("taggedUserId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ModTag" ADD CONSTRAINT "ModTag_modId_fkey" FOREIGN KEY ("modId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Flag" ADD CONSTRAINT "Flag_flaggeStatusId_fkey" FOREIGN KEY ("flaggeStatusId") REFERENCES "Status"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Flag" ADD CONSTRAINT "Flag_flaggedUserId_fkey" FOREIGN KEY ("flaggedUserId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View file

@ -127,27 +127,41 @@ model Status {
attachments Attachment[]
relatedNotifications Notification[]
flags Flag[]
moderationData ModerationData[]
modNotes ModNote[]
modTags ModTag[]
}
model ModerationData {
model ModNote {
id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid
status Status @relation(fields: [statusId], references: [id], onDelete: Cascade)
statusId String @db.Uuid
creator User @relation(fields: [creatorId], references: [id], onDelete: Cascade)
creatorId String @db.Uuid
notedStatus Status? @relation(fields: [notedStatusId], references: [id], onDelete: Cascade)
notedStatusId String? @db.Uuid
notedUser User? @relation("ModNoteToUser", fields: [notedUserId], references: [id], onDelete: Cascade)
notedUserId String? @db.Uuid
mod User @relation("ModNoteToMod", fields: [modId], references: [id], onDelete: Cascade)
modId String @db.Uuid
note String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// Used for moderation purposes
model ModTag {
id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid
taggedStatus Status? @relation(fields: [taggedStatusId], references: [id], onDelete: Cascade)
taggedStatusId String? @db.Uuid
taggedUser User? @relation("ModNoteToTaggedUser", fields: [taggedUserId], references: [id], onDelete: Cascade)
taggedUserId String? @db.Uuid
mod User @relation("ModTagToMod", fields: [modId], references: [id], onDelete: Cascade)
modId String @db.Uuid
tag String
createdAt DateTime @default(now())
}
// Used to tag notes and accounts with automatic moderation infractions
model Flag {
id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid
status Status @relation(fields: [statusId], references: [id], onDelete: Cascade)
statusId String @db.Uuid
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String @db.Uuid
flaggedStatus Status? @relation(fields: [flaggeStatusId], references: [id], onDelete: Cascade)
flaggeStatusId String? @db.Uuid
flaggedUser User? @relation(fields: [flaggedUserId], references: [id], onDelete: Cascade)
flaggedUserId String? @db.Uuid
flagType String @default("other")
createdAt DateTime @default(now())
}
@ -237,9 +251,12 @@ model User {
notifications Notification[] // One to many relation with Notification
notified Notification[] @relation("NotificationToNotified") // One to many relation with Notification
linkedOpenIdAccounts OpenIdAccount[] // One to many relation with OpenIdAccount
flags Flag[] @relation
moderationData ModerationData[]
flags Flag[]
modNotes ModNote[] @relation("ModNoteToUser")
modTags ModTag[] @relation("ModNoteToTaggedUser")
disableAutomoderation Boolean @default(false)
createdModTags ModTag[] @relation("ModTagToMod")
createdModNotes ModNote[] @relation("ModNoteToMod")
}
model OpenIdAccount {