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

@ -94,62 +94,76 @@ model Relationship {
}
model Status {
id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid
uri String @unique
author User @relation("UserStatuses", fields: [authorId], references: [id], onDelete: Cascade)
authorId String @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
reblog Status? @relation("StatusToStatus", fields: [reblogId], references: [id], onDelete: Cascade)
reblogId String? @db.Uuid
id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid
uri String @unique
author User @relation("UserStatuses", fields: [authorId], references: [id], onDelete: Cascade)
authorId String @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
reblog Status? @relation("StatusToStatus", fields: [reblogId], references: [id], onDelete: Cascade)
reblogId String? @db.Uuid
isReblog Boolean
content String @default("")
contentType String @default("text/plain")
contentSource String @default("")
content String @default("")
contentType String @default("text/plain")
contentSource String @default("")
visibility String
inReplyToPost Status? @relation("StatusToStatusReply", fields: [inReplyToPostId], references: [id], onDelete: SetNull)
inReplyToPostId String? @db.Uuid
quotingPost Status? @relation("StatusToStatusQuote", fields: [quotingPostId], references: [id], onDelete: SetNull)
quotingPostId String? @db.Uuid
instance Instance? @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String? @db.Uuid
inReplyToPost Status? @relation("StatusToStatusReply", fields: [inReplyToPostId], references: [id], onDelete: SetNull)
inReplyToPostId String? @db.Uuid
quotingPost Status? @relation("StatusToStatusQuote", fields: [quotingPostId], references: [id], onDelete: SetNull)
quotingPostId String? @db.Uuid
instance Instance? @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String? @db.Uuid
sensitive Boolean
spoilerText String @default("")
application Application? @relation(fields: [applicationId], references: [id], onDelete: SetNull)
applicationId String? @db.Uuid
emojis Emoji[] @relation
spoilerText String @default("")
application Application? @relation(fields: [applicationId], references: [id], onDelete: SetNull)
applicationId String? @db.Uuid
emojis Emoji[] @relation
mentions User[]
likes Like[] @relation("LikedToStatus")
reblogs Status[] @relation("StatusToStatus")
replies Status[] @relation("StatusToStatusReply")
quotes Status[] @relation("StatusToStatusQuote")
pinnedBy User[] @relation("UserPinnedNotes")
likes Like[] @relation("LikedToStatus")
reblogs Status[] @relation("StatusToStatus")
replies Status[] @relation("StatusToStatusReply")
quotes Status[] @relation("StatusToStatusQuote")
pinnedBy User[] @relation("UserPinnedNotes")
attachments Attachment[]
relatedNotifications Notification[]
flags Flag[]
moderationData ModerationData[]
modNotes ModNote[]
modTags ModTag[]
}
model ModerationData {
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
note String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
model ModNote {
id String @id @default(dbgenerated("uuid_generate_v7()")) @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())
}
// 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
flagType String @default("other")
createdAt DateTime @default(now())
id String @id @default(dbgenerated("uuid_generate_v7()")) @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())
}
model Token {
@ -204,42 +218,45 @@ model Notification {
}
model User {
id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid
uri String @unique
username String @unique
id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid
uri String @unique
username String @unique
displayName String
password String? // Nullable
email String? @unique // Nullable
note String @default("")
isAdmin Boolean @default(false)
email String? @unique // Nullable
note String @default("")
isAdmin Boolean @default(false)
endpoints Json? // Nullable
source Json
avatar String
header String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
isBot Boolean @default(false)
isLocked Boolean @default(false)
isDiscoverable Boolean @default(false)
sanctions String[] @default([])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
isBot Boolean @default(false)
isLocked Boolean @default(false)
isDiscoverable Boolean @default(false)
sanctions String[] @default([])
publicKey String
privateKey String? // Nullable
relationships Relationship[] @relation("OwnerToRelationship") // One to many relation with Relationship
relationshipSubjects Relationship[] @relation("SubjectToRelationship") // One to many relation with Relationship
instance Instance? @relation(fields: [instanceId], references: [id], onDelete: Cascade) // Many to one relation with Instance
instanceId String? @db.Uuid
pinnedNotes Status[] @relation("UserPinnedNotes") // Many to many relation with Status
relationships Relationship[] @relation("OwnerToRelationship") // One to many relation with Relationship
relationshipSubjects Relationship[] @relation("SubjectToRelationship") // One to many relation with Relationship
instance Instance? @relation(fields: [instanceId], references: [id], onDelete: Cascade) // Many to one relation with Instance
instanceId String? @db.Uuid
pinnedNotes Status[] @relation("UserPinnedNotes") // Many to many relation with Status
emojis Emoji[] // Many to many relation with Emoji
statuses Status[] @relation("UserStatuses") // One to many relation with Status
statuses Status[] @relation("UserStatuses") // One to many relation with Status
tokens Token[] // One to many relation with Token
likes Like[] @relation("UserLiked") // One to many relation with Like
likes Like[] @relation("UserLiked") // One to many relation with Like
statusesMentioned Status[] // Many to many relation with Status
notifications Notification[] // One to many relation with Notification
notified Notification[] @relation("NotificationToNotified") // 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[]
disableAutomoderation Boolean @default(false)
flags Flag[]
modNotes ModNote[] @relation("ModNoteToUser")
modTags ModTag[] @relation("ModNoteToTaggedUser")
disableAutomoderation Boolean @default(false)
createdModTags ModTag[] @relation("ModTagToMod")
createdModNotes ModNote[] @relation("ModNoteToMod")
}
model OpenIdAccount {