Add new inbox endpoint

This commit is contained in:
Jesse Wierzbinski 2023-09-12 17:06:47 -10:00
parent e618996936
commit 1027eada7c
No known key found for this signature in database
GPG key ID: F9A1E418934E40B0
5 changed files with 110 additions and 16 deletions

View file

@ -0,0 +1,27 @@
import {
BaseEntity,
Column,
Entity,
ManyToMany,
PrimaryGeneratedColumn,
} from "typeorm";
import { APActivity } from "activitypub-types";
import { RawObject } from "./RawObject";
/**
* Stores an ActivityPub activity as raw JSON-LD data
*/
@Entity({
name: "activities",
})
export class RawActivity extends BaseEntity {
@PrimaryGeneratedColumn("uuid")
id!: string;
@Column("json")
data!: APActivity;
// Any associated objects (there is typically only one)
@ManyToMany(() => RawObject, object => object.id)
objects!: RawObject[];
}

View file

@ -0,0 +1,69 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { errorResponse, jsonResponse } from "@response";
import { APActivity, APCreate, APObject } from "activitypub-types";
import { MatchedRoute } from "bun";
import { RawActivity } from "~database/entities/RawActivity";
import { RawObject } from "~database/entities/RawObject";
/**
* ActivityPub user inbox endpoint
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
// Check if POST request
if (req.method !== "POST") {
return errorResponse("Method not allowed", 405);
}
// Process request body
const body: APActivity = await req.json();
// Get the object's ActivityPub type
const type = body.type;
switch (type) {
case "Create" as APCreate: {
// Body is an APCreate object
// Store the Create object in database
// Check is Activity already exists
const exists = await RawActivity.findOneBy({
data: {
id: body.id,
},
});
if (exists) return errorResponse("Activity already exists", 409);
// Check if object already exists
const objectExists = await RawObject.findOneBy({
data: {
id: (body.object as APObject).id,
},
});
if (objectExists)
return errorResponse("Object already exists", 409);
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;
}
}
return jsonResponse({});
};

View file

@ -3,20 +3,15 @@ import { MatchedRoute } from "bun";
import { User } from "~database/entities/User";
import { getHost } from "@config";
import { NodeObject, compact } from "jsonld";
import { RawObject } from "~database/entities/RawObject";
import { RawActivity } from "~database/entities/RawActivity";
/**
* ActivityPub user inbox endpoint
* ActivityPub user outbox endpoint
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
// Check if POST request
if (req.method !== "POST") {
return errorResponse("Method not allowed", 405);
}
const username = matchedRoute.params.username;
const page = Boolean(matchedRoute.query.page || "false");
const min_id = matchedRoute.query.min_id || false;
@ -29,7 +24,7 @@ export default async (
}
// Get the user's corresponding ActivityPub notes
const count = await RawObject.count({
const count = await RawActivity.count({
where: {
data: {
attributedTo: `${getHost()}/@${user.username}`,
@ -43,7 +38,7 @@ export default async (
});
const lastPost = (
await RawObject.find({
await RawActivity.find({
where: {
data: {
attributedTo: `${getHost()}/@${user.username}`,
@ -75,10 +70,10 @@ export default async (
})
);
else {
let posts: RawObject[] = [];
let posts: RawActivity[] = [];
if (min_id) {
posts = await RawObject.find({
posts = await RawActivity.find({
where: {
data: {
attributedTo: `${getHost()}/@${user.username}`,
@ -93,7 +88,7 @@ export default async (
take: 11, // Take one extra to have the ID of the next post
});
} else if (max_id) {
posts = await RawObject.find({
posts = await RawActivity.find({
where: {
data: {
attributedTo: `${getHost()}/@${user.username}`,

View file

@ -1,6 +1,6 @@
import { errorResponse, jsonLdResponse } from "@response";
import { MatchedRoute } from "bun";
import { RawObject } from "~database/entities/RawObject";
import { RawActivity } from "~database/entities/RawActivity";
/**
* Fetch a user
@ -9,7 +9,7 @@ export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
const object = await RawObject.findOneBy({
const object = await RawActivity.findOneBy({
id: matchedRoute.params.id,
});

View file

@ -1,4 +1,4 @@
import { APObject } from "activitypub-types";
import { APActivity, APObject } from "activitypub-types";
import { NodeObject } from "jsonld";
export const jsonResponse = (data: object, status = 200) => {
@ -10,7 +10,10 @@ export const jsonResponse = (data: object, status = 200) => {
});
};
export const jsonLdResponse = (data: NodeObject | APObject, status = 200) => {
export const jsonLdResponse = (
data: NodeObject | APActivity | APObject,
status = 200
) => {
return new Response(JSON.stringify(data), {
headers: {
"Content-Type": "application/activity+json",