mirror of
https://github.com/versia-pub/server.git
synced 2026-03-13 05:49:16 +01:00
Add more utilities, implement Accept and Reject objects
This commit is contained in:
parent
4d0283caf0
commit
1a1bee83a7
9 changed files with 230 additions and 25 deletions
|
|
@ -10,6 +10,7 @@ import { APActivity, APActor, APObject, APTombstone } from "activitypub-types";
|
|||
import { RawObject } from "./RawObject";
|
||||
import { RawActor } from "./RawActor";
|
||||
import { getConfig } from "@config";
|
||||
import { errorResponse } from "@response";
|
||||
|
||||
/**
|
||||
* Stores an ActivityPub activity as raw JSON-LD data
|
||||
|
|
@ -81,19 +82,19 @@ export class RawActivity extends BaseEntity {
|
|||
|
||||
// Check if object body contains any filtered terms
|
||||
if (await rawObject.isObjectFiltered())
|
||||
return new Error("Object filtered");
|
||||
return errorResponse("Object filtered", 409);
|
||||
|
||||
await rawObject.save();
|
||||
return rawObject;
|
||||
} else {
|
||||
return new Error("Object does not exist");
|
||||
return errorResponse("Object does not exist", 404);
|
||||
}
|
||||
}
|
||||
|
||||
static async deleteObjectIfExists(object: APObject) {
|
||||
const dbObject = await RawObject.getById(object.id ?? "");
|
||||
|
||||
if (!dbObject) return new Error("Object not found");
|
||||
if (!dbObject) return errorResponse("Object does not exist", 404);
|
||||
|
||||
const config = getConfig();
|
||||
|
||||
|
|
@ -139,7 +140,7 @@ export class RawActivity extends BaseEntity {
|
|||
activity.actor as APActor
|
||||
);
|
||||
|
||||
if (actor instanceof Error) {
|
||||
if (actor instanceof Response) {
|
||||
return actor;
|
||||
}
|
||||
|
||||
|
|
@ -147,14 +148,14 @@ export class RawActivity extends BaseEntity {
|
|||
activity.object as APObject
|
||||
);
|
||||
|
||||
if (object instanceof Error) {
|
||||
if (object instanceof Response) {
|
||||
return object;
|
||||
}
|
||||
|
||||
await rawActivity.save();
|
||||
return rawActivity;
|
||||
} else {
|
||||
return new Error("Activity already exists");
|
||||
return errorResponse("Activity already exists", 409);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +166,7 @@ export class RawActivity extends BaseEntity {
|
|||
|
||||
// Check if object body contains any filtered terms
|
||||
if (await rawObject.isObjectFiltered())
|
||||
return new Error("Object filtered");
|
||||
return errorResponse("Object filtered", 409);
|
||||
|
||||
await rawObject.save();
|
||||
|
||||
|
|
@ -173,7 +174,7 @@ export class RawActivity extends BaseEntity {
|
|||
|
||||
return rawObject;
|
||||
} else {
|
||||
return new Error("Object already exists");
|
||||
return errorResponse("Object already exists", 409);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -201,7 +202,7 @@ export class RawActivity extends BaseEntity {
|
|||
}
|
||||
|
||||
if (await rawActor.isObjectFiltered()) {
|
||||
return new Error("Actor filtered");
|
||||
return errorResponse("Actor filtered", 409);
|
||||
}
|
||||
|
||||
await rawActor.save();
|
||||
|
|
@ -210,7 +211,7 @@ export class RawActivity extends BaseEntity {
|
|||
|
||||
return rawActor;
|
||||
} else {
|
||||
return new Error("Actor already exists");
|
||||
return errorResponse("Actor already exists", 409);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
|||
import { APActor } from "activitypub-types";
|
||||
import { getConfig } from "@config";
|
||||
import { appendFile } from "fs/promises";
|
||||
import { errorResponse } from "@response";
|
||||
|
||||
/**
|
||||
* Stores an ActivityPub actor as raw JSON-LD data
|
||||
|
|
@ -24,6 +25,40 @@ export class RawActor extends BaseEntity {
|
|||
.getOne();
|
||||
}
|
||||
|
||||
static async addIfNotExists(data: APActor) {
|
||||
if (!(await RawActor.exists(data.id ?? ""))) {
|
||||
const actor = new RawActor();
|
||||
actor.data = data;
|
||||
|
||||
const config = getConfig();
|
||||
|
||||
if (
|
||||
config.activitypub.discard_avatars.find(instance =>
|
||||
actor.id.includes(instance)
|
||||
)
|
||||
) {
|
||||
actor.data.icon = undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
config.activitypub.discard_banners.find(instance =>
|
||||
actor.id.includes(instance)
|
||||
)
|
||||
) {
|
||||
actor.data.image = undefined;
|
||||
}
|
||||
|
||||
if (await actor.isObjectFiltered()) {
|
||||
return errorResponse("Actor filtered", 409);
|
||||
}
|
||||
|
||||
await actor.save();
|
||||
|
||||
return actor;
|
||||
}
|
||||
return errorResponse("Actor already exists", 409);
|
||||
}
|
||||
|
||||
async isObjectFiltered() {
|
||||
const config = getConfig();
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,15 @@ import {
|
|||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from "typeorm";
|
||||
import { APIAccount } from "~types/entities/account";
|
||||
import { RawActor } from "./RawActor";
|
||||
import { APActor } from "activitypub-types";
|
||||
|
||||
const config = getConfig();
|
||||
|
||||
|
|
@ -42,7 +47,18 @@ export class User extends BaseEntity {
|
|||
@Column("varchar", {
|
||||
default: "",
|
||||
})
|
||||
bio!: string;
|
||||
note!: string;
|
||||
|
||||
@Column("boolean", {
|
||||
default: false,
|
||||
})
|
||||
is_admin!: boolean;
|
||||
|
||||
@Column("varchar")
|
||||
avatar!: string;
|
||||
|
||||
@Column("varchar")
|
||||
header!: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at!: Date;
|
||||
|
|
@ -56,6 +72,95 @@ export class User extends BaseEntity {
|
|||
@Column("varchar")
|
||||
private_key!: string;
|
||||
|
||||
@ManyToOne(() => RawActor, actor => actor.id)
|
||||
actor!: RawActor;
|
||||
|
||||
@ManyToMany(() => RawActor, actor => actor.id)
|
||||
@JoinTable()
|
||||
following!: RawActor[];
|
||||
|
||||
static async getByActorId(id: string) {
|
||||
return await User.createQueryBuilder("user")
|
||||
// Objects is a many-to-many relationship
|
||||
.leftJoinAndSelect("user.actor", "actor")
|
||||
.leftJoinAndSelect("user.following", "following")
|
||||
.where("actor.data @> :data", {
|
||||
data: JSON.stringify({
|
||||
id,
|
||||
}),
|
||||
})
|
||||
.getOne();
|
||||
}
|
||||
|
||||
static async createNew(data: {
|
||||
username: string;
|
||||
display_name?: string;
|
||||
password: string;
|
||||
email: string;
|
||||
bio?: string;
|
||||
avatar?: string;
|
||||
header?: string;
|
||||
}) {
|
||||
const config = getConfig();
|
||||
const user = new User();
|
||||
|
||||
user.username = data.username;
|
||||
user.display_name = data.display_name ?? data.username;
|
||||
user.password = await Bun.password.hash(data.password);
|
||||
user.email = data.email;
|
||||
user.note = data.bio ?? "";
|
||||
user.avatar = data.avatar ?? config.defaults.avatar;
|
||||
user.header = data.header ?? config.defaults.avatar;
|
||||
|
||||
await user.generateKeys();
|
||||
await user.updateActor();
|
||||
|
||||
await user.save();
|
||||
return user;
|
||||
}
|
||||
|
||||
async updateActor() {
|
||||
// Check if actor exists
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
const actor = this.actor ? this.actor : new RawActor();
|
||||
|
||||
actor.data = {
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
],
|
||||
id: `${config.http.base_url}/@${this.username}`,
|
||||
type: "Person",
|
||||
preferredUsername: this.username,
|
||||
name: this.display_name,
|
||||
inbox: `${config.http.base_url}/@${this.username}/inbox`,
|
||||
outbox: `${config.http.base_url}/@${this.username}/outbox`,
|
||||
followers: `${config.http.base_url}/@${this.username}/followers`,
|
||||
following: `${config.http.base_url}/@${this.username}/following`,
|
||||
manuallyApprovesFollowers: false,
|
||||
summary: this.note,
|
||||
icon: {
|
||||
type: "Image",
|
||||
url: this.avatar,
|
||||
},
|
||||
image: {
|
||||
type: "Image",
|
||||
url: this.header,
|
||||
},
|
||||
publicKey: {
|
||||
id: `${config.http.base_url}/@${this.username}/actor#main-key`,
|
||||
owner: `${config.http.base_url}/@${this.username}/actor`,
|
||||
publicKeyPem: this.public_key,
|
||||
},
|
||||
} as APActor;
|
||||
|
||||
await actor.save();
|
||||
|
||||
this.actor = actor;
|
||||
await this.save();
|
||||
return actor;
|
||||
}
|
||||
|
||||
async generateKeys(): Promise<void> {
|
||||
// openssl genrsa -out private.pem 2048
|
||||
// openssl rsa -in private.pem -outform PEM -pubout -out public.pem
|
||||
|
|
@ -110,7 +215,7 @@ export class User extends BaseEntity {
|
|||
locked: false,
|
||||
moved: null,
|
||||
noindex: false,
|
||||
note: this.bio,
|
||||
note: this.note,
|
||||
suspended: false,
|
||||
url: `${config.http.base_url}/@${this.username}`,
|
||||
username: this.username,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue