mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
Refactor code, add more filtering
This commit is contained in:
parent
768d1858dc
commit
4d0283caf0
|
|
@ -6,8 +6,10 @@ import {
|
||||||
ManyToMany,
|
ManyToMany,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
} from "typeorm";
|
} from "typeorm";
|
||||||
import { APActivity } from "activitypub-types";
|
import { APActivity, APActor, APObject, APTombstone } from "activitypub-types";
|
||||||
import { RawObject } from "./RawObject";
|
import { RawObject } from "./RawObject";
|
||||||
|
import { RawActor } from "./RawActor";
|
||||||
|
import { getConfig } from "@config";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores an ActivityPub activity as raw JSON-LD data
|
* Stores an ActivityPub activity as raw JSON-LD data
|
||||||
|
|
@ -26,4 +28,189 @@ export class RawActivity extends BaseEntity {
|
||||||
@ManyToMany(() => RawObject, object => object.id)
|
@ManyToMany(() => RawObject, object => object.id)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
objects!: RawObject[];
|
objects!: RawObject[];
|
||||||
|
|
||||||
|
@ManyToMany(() => RawActor, actor => actor.id)
|
||||||
|
@JoinTable()
|
||||||
|
actors!: RawActor[];
|
||||||
|
|
||||||
|
static async getByObjectId(id: string) {
|
||||||
|
return await RawActivity.createQueryBuilder("activity")
|
||||||
|
// Objects is a many-to-many relationship
|
||||||
|
.leftJoinAndSelect("activity.objects", "objects")
|
||||||
|
.leftJoinAndSelect("activity.actors", "actors")
|
||||||
|
.where("objects.data @> :data", {
|
||||||
|
data: JSON.stringify({
|
||||||
|
id,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getById(id: string) {
|
||||||
|
return await RawActivity.createQueryBuilder("activity")
|
||||||
|
.leftJoinAndSelect("activity.objects", "objects")
|
||||||
|
.leftJoinAndSelect("activity.actors", "actors")
|
||||||
|
.where("activity.data->>'id' = :id", {
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
.getOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getLatestById(id: string) {
|
||||||
|
return await RawActivity.createQueryBuilder("activity")
|
||||||
|
// Where id is part of the jsonb column 'data'
|
||||||
|
.where("activity.data->>'id' = :id", {
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
.leftJoinAndSelect("activity.objects", "objects")
|
||||||
|
.leftJoinAndSelect("activity.actors", "actors")
|
||||||
|
// Sort by most recent
|
||||||
|
.orderBy("activity.data->>'published'", "DESC")
|
||||||
|
.getOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async exists(id: string) {
|
||||||
|
return !!(await RawActivity.getById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateObjectIfExists(object: APObject) {
|
||||||
|
const rawObject = await RawObject.getById(object.id ?? "");
|
||||||
|
|
||||||
|
if (rawObject) {
|
||||||
|
rawObject.data = object;
|
||||||
|
|
||||||
|
// Check if object body contains any filtered terms
|
||||||
|
if (await rawObject.isObjectFiltered())
|
||||||
|
return new Error("Object filtered");
|
||||||
|
|
||||||
|
await rawObject.save();
|
||||||
|
return rawObject;
|
||||||
|
} else {
|
||||||
|
return new Error("Object does not exist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async deleteObjectIfExists(object: APObject) {
|
||||||
|
const dbObject = await RawObject.getById(object.id ?? "");
|
||||||
|
|
||||||
|
if (!dbObject) return new Error("Object not found");
|
||||||
|
|
||||||
|
const config = getConfig();
|
||||||
|
|
||||||
|
if (config.activitypub.use_tombstones) {
|
||||||
|
dbObject.data = {
|
||||||
|
...dbObject.data,
|
||||||
|
type: "Tombstone",
|
||||||
|
deleted: new Date(),
|
||||||
|
formerType: dbObject.data.type,
|
||||||
|
} as APTombstone;
|
||||||
|
|
||||||
|
await dbObject.save();
|
||||||
|
} else {
|
||||||
|
const activities = await RawActivity.getByObjectId(object.id ?? "");
|
||||||
|
|
||||||
|
activities.forEach(
|
||||||
|
activity =>
|
||||||
|
(activity.objects = activity.objects.filter(
|
||||||
|
o => o.id !== object.id
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
activities.map(async activity => await activity.save())
|
||||||
|
);
|
||||||
|
|
||||||
|
await dbObject.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async addIfNotExists(activity: APActivity) {
|
||||||
|
if (!(await RawActivity.exists(activity.id ?? ""))) {
|
||||||
|
const rawActivity = new RawActivity();
|
||||||
|
rawActivity.data = {
|
||||||
|
...activity,
|
||||||
|
object: undefined,
|
||||||
|
actor: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const actor = await rawActivity.addActorIfNotExists(
|
||||||
|
activity.actor as APActor
|
||||||
|
);
|
||||||
|
|
||||||
|
if (actor instanceof Error) {
|
||||||
|
return actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const object = await rawActivity.addObjectIfNotExists(
|
||||||
|
activity.object as APObject
|
||||||
|
);
|
||||||
|
|
||||||
|
if (object instanceof Error) {
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
await rawActivity.save();
|
||||||
|
return rawActivity;
|
||||||
|
} else {
|
||||||
|
return new Error("Activity already exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addObjectIfNotExists(object: APObject) {
|
||||||
|
if (!this.objects.some(o => o.data.id === object.id)) {
|
||||||
|
const rawObject = new RawObject();
|
||||||
|
rawObject.data = object;
|
||||||
|
|
||||||
|
// Check if object body contains any filtered terms
|
||||||
|
if (await rawObject.isObjectFiltered())
|
||||||
|
return new Error("Object filtered");
|
||||||
|
|
||||||
|
await rawObject.save();
|
||||||
|
|
||||||
|
this.objects.push(rawObject);
|
||||||
|
|
||||||
|
return rawObject;
|
||||||
|
} else {
|
||||||
|
return new Error("Object already exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addActorIfNotExists(actor: APActor) {
|
||||||
|
if (!this.actors.some(a => a.data.id === actor.id)) {
|
||||||
|
const rawActor = new RawActor();
|
||||||
|
rawActor.data = actor;
|
||||||
|
|
||||||
|
const config = getConfig();
|
||||||
|
|
||||||
|
if (
|
||||||
|
config.activitypub.discard_avatars.find(
|
||||||
|
instance => actor.id?.includes(instance)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
rawActor.data.icon = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
config.activitypub.discard_banners.find(
|
||||||
|
instance => actor.id?.includes(instance)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
rawActor.data.image = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await rawActor.isObjectFiltered()) {
|
||||||
|
return new Error("Actor filtered");
|
||||||
|
}
|
||||||
|
|
||||||
|
await rawActor.save();
|
||||||
|
|
||||||
|
this.actors.push(rawActor);
|
||||||
|
|
||||||
|
return rawActor;
|
||||||
|
} else {
|
||||||
|
return new Error("Actor already exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
79
database/entities/RawActor.ts
Normal file
79
database/entities/RawActor.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||||
|
import { APActor } from "activitypub-types";
|
||||||
|
import { getConfig } from "@config";
|
||||||
|
import { appendFile } from "fs/promises";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores an ActivityPub actor as raw JSON-LD data
|
||||||
|
*/
|
||||||
|
@Entity({
|
||||||
|
name: "actors",
|
||||||
|
})
|
||||||
|
export class RawActor extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn("uuid")
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Column("jsonb")
|
||||||
|
data!: APActor;
|
||||||
|
|
||||||
|
static async getById(id: string) {
|
||||||
|
return await RawActor.createQueryBuilder("actor")
|
||||||
|
.where("actor.data->>'id' = :id", {
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
.getOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
async isObjectFiltered() {
|
||||||
|
const config = getConfig();
|
||||||
|
|
||||||
|
const usernameFilterResult = await Promise.all(
|
||||||
|
config.filters.username_filters.map(async filter => {
|
||||||
|
if (
|
||||||
|
this.data.type === "Person" &&
|
||||||
|
this.data.preferredUsername?.match(filter)
|
||||||
|
) {
|
||||||
|
// Log filter
|
||||||
|
|
||||||
|
if (config.logging.log_filters)
|
||||||
|
await appendFile(
|
||||||
|
process.cwd() + "/logs/filters.log",
|
||||||
|
`${new Date().toISOString()} Filtered actor username: "${
|
||||||
|
this.data.preferredUsername
|
||||||
|
}" (ID: ${this.data.id}) based on rule: ${filter}\n`
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const displayNameFilterResult = await Promise.all(
|
||||||
|
config.filters.displayname_filters.map(async filter => {
|
||||||
|
if (
|
||||||
|
this.data.type === "Person" &&
|
||||||
|
this.data.name?.match(filter)
|
||||||
|
) {
|
||||||
|
// Log filter
|
||||||
|
|
||||||
|
if (config.logging.log_filters)
|
||||||
|
await appendFile(
|
||||||
|
process.cwd() + "/logs/filters.log",
|
||||||
|
`${new Date().toISOString()} Filtered actor username: "${
|
||||||
|
this.data.preferredUsername
|
||||||
|
}" (ID: ${this.data.id}) based on rule: ${filter}\n`
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
usernameFilterResult.includes(true) ||
|
||||||
|
displayNameFilterResult.includes(true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async exists(id: string) {
|
||||||
|
return !!(await RawActor.getById(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||||
import { APObject } from "activitypub-types";
|
import { APObject } from "activitypub-types";
|
||||||
|
import { getConfig } from "@config";
|
||||||
|
import { appendFile } from "fs/promises";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores an ActivityPub object as raw JSON-LD data
|
* Stores an ActivityPub object as raw JSON-LD data
|
||||||
|
|
@ -13,4 +15,45 @@ export class RawObject extends BaseEntity {
|
||||||
|
|
||||||
@Column("jsonb")
|
@Column("jsonb")
|
||||||
data!: APObject;
|
data!: APObject;
|
||||||
|
|
||||||
|
static async getById(id: string) {
|
||||||
|
return await RawObject.createQueryBuilder("object")
|
||||||
|
.where("object.data->>'id' = :id", {
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
.getOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
async isObjectFiltered() {
|
||||||
|
const config = getConfig();
|
||||||
|
|
||||||
|
const filter_result = await Promise.all(
|
||||||
|
config.filters.note_filters.map(async filter => {
|
||||||
|
if (
|
||||||
|
this.data.type === "Note" &&
|
||||||
|
this.data.content?.match(filter)
|
||||||
|
) {
|
||||||
|
// Log filter
|
||||||
|
|
||||||
|
if (config.logging.log_filters)
|
||||||
|
await appendFile(
|
||||||
|
process.cwd() + "/logs/filters.log",
|
||||||
|
`${new Date().toISOString()} Filtered note content: "${this.data.content.replaceAll(
|
||||||
|
"\n",
|
||||||
|
" "
|
||||||
|
)}" (ID: ${
|
||||||
|
this.data.id
|
||||||
|
}) based on rule: ${filter}\n`
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return filter_result.includes(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async exists(id: string) {
|
||||||
|
return !!(await RawObject.getById(id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,48 @@ export class User extends BaseEntity {
|
||||||
@UpdateDateColumn()
|
@UpdateDateColumn()
|
||||||
updated_at!: Date;
|
updated_at!: Date;
|
||||||
|
|
||||||
|
@Column("varchar")
|
||||||
|
public_key!: string;
|
||||||
|
|
||||||
|
@Column("varchar")
|
||||||
|
private_key!: string;
|
||||||
|
|
||||||
|
async generateKeys(): Promise<void> {
|
||||||
|
// openssl genrsa -out private.pem 2048
|
||||||
|
// openssl rsa -in private.pem -outform PEM -pubout -out public.pem
|
||||||
|
|
||||||
|
const keys = await crypto.subtle.generateKey(
|
||||||
|
{
|
||||||
|
name: "RSASSA-PKCS1-v1_5",
|
||||||
|
hash: "SHA-256",
|
||||||
|
modulusLength: 4096,
|
||||||
|
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
["sign", "verify"]
|
||||||
|
);
|
||||||
|
|
||||||
|
const privateKey = btoa(
|
||||||
|
String.fromCharCode.apply(null, [
|
||||||
|
...new Uint8Array(
|
||||||
|
await crypto.subtle.exportKey("pkcs8", keys.privateKey)
|
||||||
|
),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
const publicKey = btoa(
|
||||||
|
String.fromCharCode(
|
||||||
|
...new Uint8Array(
|
||||||
|
await crypto.subtle.exportKey("spki", keys.publicKey)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add header, footer and newlines later on
|
||||||
|
// These keys are PEM encrypted
|
||||||
|
this.private_key = privateKey;
|
||||||
|
this.public_key = publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/require-await
|
// eslint-disable-next-line @typescript-eslint/require-await
|
||||||
async toAPI(): Promise<APIAccount> {
|
async toAPI(): Promise<APIAccount> {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,7 @@ import {
|
||||||
APUpdate,
|
APUpdate,
|
||||||
} from "activitypub-types";
|
} from "activitypub-types";
|
||||||
import { MatchedRoute } from "bun";
|
import { MatchedRoute } from "bun";
|
||||||
import { appendFile } from "fs/promises";
|
|
||||||
import { RawActivity } from "~database/entities/RawActivity";
|
import { RawActivity } from "~database/entities/RawActivity";
|
||||||
import { RawObject } from "~database/entities/RawObject";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ActivityPub user inbox endpoint
|
* ActivityPub user inbox endpoint
|
||||||
|
|
@ -44,63 +42,11 @@ export default async (
|
||||||
// TODO: Add authentication
|
// TODO: Add authentication
|
||||||
|
|
||||||
// Check is Activity already exists
|
// Check is Activity already exists
|
||||||
const exists = await RawActivity.createQueryBuilder("activity")
|
const activity = await RawActivity.addIfNotExists(body);
|
||||||
.where("activity.data->>'id' = :id", {
|
|
||||||
id: body.id,
|
|
||||||
})
|
|
||||||
.getOne();
|
|
||||||
|
|
||||||
if (exists) return errorResponse("Activity already exists", 409);
|
if (activity instanceof Error) {
|
||||||
|
return errorResponse(activity.message, 409);
|
||||||
// Check if object already exists
|
}
|
||||||
const objectExists = await RawObject.createQueryBuilder("object")
|
|
||||||
.where("object.data->>'id' = :id", {
|
|
||||||
id: (body.object as APObject).id,
|
|
||||||
})
|
|
||||||
.getOne();
|
|
||||||
|
|
||||||
if (objectExists)
|
|
||||||
return errorResponse("Object already exists", 409);
|
|
||||||
|
|
||||||
// Check if object body contains any filtered terms
|
|
||||||
const filter_result = await Promise.all(
|
|
||||||
config.filters.note_filters.map(async filter => {
|
|
||||||
if (
|
|
||||||
(body.object as APObject).type === "Note" &&
|
|
||||||
(body.object as APObject).content?.match(filter)
|
|
||||||
) {
|
|
||||||
// Log filter
|
|
||||||
|
|
||||||
if (config.logging.log_filters)
|
|
||||||
await appendFile(
|
|
||||||
process.cwd() + "/logs/filters.log",
|
|
||||||
`${new Date().toISOString()} Filtered note content: "${(
|
|
||||||
body.object as APObject
|
|
||||||
).content?.replaceAll("\n", " ")}" (ID: ${
|
|
||||||
(body.object as APObject).id
|
|
||||||
}) based on rule: ${filter}\n`
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (filter_result.includes(true)) return jsonResponse({});
|
|
||||||
|
|
||||||
const activity = new RawActivity();
|
|
||||||
const object = new RawObject();
|
|
||||||
|
|
||||||
activity.data = {
|
|
||||||
...body,
|
|
||||||
object: undefined,
|
|
||||||
};
|
|
||||||
object.data = body.object as APObject;
|
|
||||||
|
|
||||||
activity.objects = [object];
|
|
||||||
|
|
||||||
// Save the new object and activity
|
|
||||||
await object.save();
|
|
||||||
await activity.save();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Update" as APUpdate: {
|
case "Update" as APUpdate: {
|
||||||
|
|
@ -108,26 +54,20 @@ export default async (
|
||||||
// Replace the object in database with the new provided object
|
// Replace the object in database with the new provided object
|
||||||
// TODO: Add authentication
|
// TODO: Add authentication
|
||||||
|
|
||||||
const object = await RawObject.createQueryBuilder("object")
|
const object = await RawActivity.updateObjectIfExists(
|
||||||
.where("object.data->>'id' = :id", {
|
body.object as APObject
|
||||||
id: (body.object as APObject).id,
|
);
|
||||||
})
|
|
||||||
.getOne();
|
|
||||||
|
|
||||||
if (!object) return errorResponse("Object not found", 404);
|
if (object instanceof Error) {
|
||||||
|
return errorResponse(object.message, 409);
|
||||||
|
}
|
||||||
|
|
||||||
object.data = body.object as APObject;
|
const activity = await RawActivity.addIfNotExists(body);
|
||||||
|
|
||||||
// Store the Update event in database
|
if (activity instanceof Error) {
|
||||||
const activity = new RawActivity();
|
return errorResponse(activity.message, 409);
|
||||||
activity.data = {
|
}
|
||||||
...body,
|
|
||||||
object: undefined,
|
|
||||||
};
|
|
||||||
activity.objects = [object];
|
|
||||||
|
|
||||||
await object.save();
|
|
||||||
await activity.save();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Delete" as APDelete: {
|
case "Delete" as APDelete: {
|
||||||
|
|
@ -135,59 +75,10 @@ export default async (
|
||||||
// Delete the object from database
|
// Delete the object from database
|
||||||
// TODO: Add authentication
|
// TODO: Add authentication
|
||||||
|
|
||||||
const object = await RawObject.createQueryBuilder("object")
|
await RawActivity.deleteObjectIfExists(body.object as APObject);
|
||||||
.where("object.data->>'id' = :id", {
|
|
||||||
id: (body.object as APObject).id,
|
|
||||||
})
|
|
||||||
.getOne();
|
|
||||||
|
|
||||||
if (!object) return errorResponse("Object not found", 404);
|
|
||||||
|
|
||||||
const activities = await RawActivity.createQueryBuilder("activity")
|
|
||||||
// Objects is a many-to-many relationship
|
|
||||||
.leftJoinAndSelect("activity.objects", "objects")
|
|
||||||
.where("objects.data @> :data", {
|
|
||||||
data: JSON.stringify({
|
|
||||||
id: object.id,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.getMany();
|
|
||||||
|
|
||||||
if (config.activitypub.use_tombstones) {
|
|
||||||
object.data = {
|
|
||||||
...object.data,
|
|
||||||
type: "Tombstone",
|
|
||||||
deleted: new Date(),
|
|
||||||
formerType: object.data.type,
|
|
||||||
} as APTombstone;
|
|
||||||
|
|
||||||
await object.save();
|
|
||||||
} else {
|
|
||||||
activities.forEach(
|
|
||||||
activity =>
|
|
||||||
(activity.objects = activity.objects.filter(
|
|
||||||
o => o.id !== object.id
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
activities.map(async activity => await activity.save())
|
|
||||||
);
|
|
||||||
|
|
||||||
await object.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the Delete event in the database
|
// Store the Delete event in the database
|
||||||
const activity = new RawActivity();
|
const activity = RawActivity.addIfNotExists(body);
|
||||||
activity.data = {
|
|
||||||
...body,
|
|
||||||
object: undefined,
|
|
||||||
};
|
|
||||||
activity.objects = config.activitypub.use_tombstones
|
|
||||||
? [object]
|
|
||||||
: [];
|
|
||||||
|
|
||||||
await activity.save();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Accept" as APAccept: {
|
case "Accept" as APAccept: {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ beforeAll(async () => {
|
||||||
user.display_name = "";
|
user.display_name = "";
|
||||||
user.bio = "";
|
user.bio = "";
|
||||||
|
|
||||||
|
await user.generateKeys();
|
||||||
|
|
||||||
await user.save();
|
await user.save();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -55,13 +57,9 @@ describe("POST /@test/inbox", () => {
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
expect(response.headers.get("content-type")).toBe("application/json");
|
expect(response.headers.get("content-type")).toBe("application/json");
|
||||||
|
|
||||||
const activity = await RawActivity.createQueryBuilder("activity")
|
const activity = await RawActivity.getLatestById(
|
||||||
// id is part of the jsonb column 'data'
|
"https://example.com/notes/1/activity"
|
||||||
.where("activity.data->>'id' = :id", {
|
);
|
||||||
id: "https://example.com/notes/1/activity",
|
|
||||||
})
|
|
||||||
.leftJoinAndSelect("activity.objects", "objects")
|
|
||||||
.getOne();
|
|
||||||
|
|
||||||
expect(activity).not.toBeUndefined();
|
expect(activity).not.toBeUndefined();
|
||||||
expect(activity?.data).toEqual({
|
expect(activity?.data).toEqual({
|
||||||
|
|
@ -118,15 +116,9 @@ describe("POST /@test/inbox", () => {
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
expect(response.headers.get("content-type")).toBe("application/json");
|
expect(response.headers.get("content-type")).toBe("application/json");
|
||||||
|
|
||||||
const activity = await RawActivity.createQueryBuilder("activity")
|
const activity = await RawActivity.getLatestById(
|
||||||
// Where id is part of the jsonb column 'data'
|
"https://example.com/notes/1/activity"
|
||||||
.where("activity.data->>'id' = :id", {
|
);
|
||||||
id: "https://example.com/notes/1/activity",
|
|
||||||
})
|
|
||||||
.leftJoinAndSelect("activity.objects", "objects")
|
|
||||||
// Sort by most recent
|
|
||||||
.orderBy("activity.data->>'published'", "DESC")
|
|
||||||
.getOne();
|
|
||||||
|
|
||||||
expect(activity).not.toBeUndefined();
|
expect(activity).not.toBeUndefined();
|
||||||
expect(activity?.data).toEqual({
|
expect(activity?.data).toEqual({
|
||||||
|
|
@ -183,15 +175,9 @@ describe("POST /@test/inbox", () => {
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
expect(response.headers.get("content-type")).toBe("application/json");
|
expect(response.headers.get("content-type")).toBe("application/json");
|
||||||
|
|
||||||
const activity = await RawActivity.createQueryBuilder("activity")
|
const activity = await RawActivity.getLatestById(
|
||||||
// Where id is part of the jsonb column 'data'
|
"https://example.com/notes/1/activity"
|
||||||
.where("activity.data->>'id' = :id", {
|
);
|
||||||
id: "https://example.com/notes/1/activity",
|
|
||||||
})
|
|
||||||
.leftJoinAndSelect("activity.objects", "objects")
|
|
||||||
// Sort by most recent
|
|
||||||
.orderBy("activity.data->>'published'", "DESC")
|
|
||||||
.getOne();
|
|
||||||
|
|
||||||
expect(activity).not.toBeUndefined();
|
expect(activity).not.toBeUndefined();
|
||||||
expect(activity?.data).toEqual({
|
expect(activity?.data).toEqual({
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ beforeAll(async () => {
|
||||||
user.display_name = "";
|
user.display_name = "";
|
||||||
user.bio = "";
|
user.bio = "";
|
||||||
|
|
||||||
|
await user.generateKeys();
|
||||||
|
|
||||||
await user.save();
|
await user.save();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,14 @@ export interface ConfigType {
|
||||||
|
|
||||||
activitypub: {
|
activitypub: {
|
||||||
use_tombstones: boolean;
|
use_tombstones: boolean;
|
||||||
|
reject_activities: string[];
|
||||||
|
force_followers_only: string[];
|
||||||
|
discard_reports: string[];
|
||||||
|
discard_deletes: string[];
|
||||||
|
discard_banners: string[];
|
||||||
|
discard_avatars: string[];
|
||||||
|
force_sensitive: string[];
|
||||||
|
remove_media: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
filters: {
|
filters: {
|
||||||
|
|
@ -128,6 +136,14 @@ export const configDefaults: ConfigType = {
|
||||||
},
|
},
|
||||||
activitypub: {
|
activitypub: {
|
||||||
use_tombstones: true,
|
use_tombstones: true,
|
||||||
|
reject_activities: [],
|
||||||
|
force_followers_only: [],
|
||||||
|
discard_reports: [],
|
||||||
|
discard_deletes: [],
|
||||||
|
discard_banners: [],
|
||||||
|
discard_avatars: [],
|
||||||
|
force_sensitive: [],
|
||||||
|
remove_media: [],
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
note_filters: [],
|
note_filters: [],
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue