mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
Add new inbox endpoint
This commit is contained in:
parent
e618996936
commit
1027eada7c
27
database/entities/RawActivity.ts
Normal file
27
database/entities/RawActivity.ts
Normal 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[];
|
||||||
|
}
|
||||||
69
server/api/@[username]/inbox/index.ts
Normal file
69
server/api/@[username]/inbox/index.ts
Normal 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({});
|
||||||
|
};
|
||||||
|
|
@ -3,20 +3,15 @@ import { MatchedRoute } from "bun";
|
||||||
import { User } from "~database/entities/User";
|
import { User } from "~database/entities/User";
|
||||||
import { getHost } from "@config";
|
import { getHost } from "@config";
|
||||||
import { NodeObject, compact } from "jsonld";
|
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 (
|
export default async (
|
||||||
req: Request,
|
req: Request,
|
||||||
matchedRoute: MatchedRoute
|
matchedRoute: MatchedRoute
|
||||||
): Promise<Response> => {
|
): Promise<Response> => {
|
||||||
// Check if POST request
|
|
||||||
if (req.method !== "POST") {
|
|
||||||
return errorResponse("Method not allowed", 405);
|
|
||||||
}
|
|
||||||
|
|
||||||
const username = matchedRoute.params.username;
|
const username = matchedRoute.params.username;
|
||||||
const page = Boolean(matchedRoute.query.page || "false");
|
const page = Boolean(matchedRoute.query.page || "false");
|
||||||
const min_id = matchedRoute.query.min_id || false;
|
const min_id = matchedRoute.query.min_id || false;
|
||||||
|
|
@ -29,7 +24,7 @@ export default async (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the user's corresponding ActivityPub notes
|
// Get the user's corresponding ActivityPub notes
|
||||||
const count = await RawObject.count({
|
const count = await RawActivity.count({
|
||||||
where: {
|
where: {
|
||||||
data: {
|
data: {
|
||||||
attributedTo: `${getHost()}/@${user.username}`,
|
attributedTo: `${getHost()}/@${user.username}`,
|
||||||
|
|
@ -43,7 +38,7 @@ export default async (
|
||||||
});
|
});
|
||||||
|
|
||||||
const lastPost = (
|
const lastPost = (
|
||||||
await RawObject.find({
|
await RawActivity.find({
|
||||||
where: {
|
where: {
|
||||||
data: {
|
data: {
|
||||||
attributedTo: `${getHost()}/@${user.username}`,
|
attributedTo: `${getHost()}/@${user.username}`,
|
||||||
|
|
@ -75,10 +70,10 @@ export default async (
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
else {
|
else {
|
||||||
let posts: RawObject[] = [];
|
let posts: RawActivity[] = [];
|
||||||
|
|
||||||
if (min_id) {
|
if (min_id) {
|
||||||
posts = await RawObject.find({
|
posts = await RawActivity.find({
|
||||||
where: {
|
where: {
|
||||||
data: {
|
data: {
|
||||||
attributedTo: `${getHost()}/@${user.username}`,
|
attributedTo: `${getHost()}/@${user.username}`,
|
||||||
|
|
@ -93,7 +88,7 @@ export default async (
|
||||||
take: 11, // Take one extra to have the ID of the next post
|
take: 11, // Take one extra to have the ID of the next post
|
||||||
});
|
});
|
||||||
} else if (max_id) {
|
} else if (max_id) {
|
||||||
posts = await RawObject.find({
|
posts = await RawActivity.find({
|
||||||
where: {
|
where: {
|
||||||
data: {
|
data: {
|
||||||
attributedTo: `${getHost()}/@${user.username}`,
|
attributedTo: `${getHost()}/@${user.username}`,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { errorResponse, jsonLdResponse } from "@response";
|
import { errorResponse, jsonLdResponse } from "@response";
|
||||||
import { MatchedRoute } from "bun";
|
import { MatchedRoute } from "bun";
|
||||||
import { RawObject } from "~database/entities/RawObject";
|
import { RawActivity } from "~database/entities/RawActivity";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch a user
|
* Fetch a user
|
||||||
|
|
@ -9,7 +9,7 @@ export default async (
|
||||||
req: Request,
|
req: Request,
|
||||||
matchedRoute: MatchedRoute
|
matchedRoute: MatchedRoute
|
||||||
): Promise<Response> => {
|
): Promise<Response> => {
|
||||||
const object = await RawObject.findOneBy({
|
const object = await RawActivity.findOneBy({
|
||||||
id: matchedRoute.params.id,
|
id: matchedRoute.params.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { APObject } from "activitypub-types";
|
import { APActivity, APObject } from "activitypub-types";
|
||||||
import { NodeObject } from "jsonld";
|
import { NodeObject } from "jsonld";
|
||||||
|
|
||||||
export const jsonResponse = (data: object, status = 200) => {
|
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), {
|
return new Response(JSON.stringify(data), {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/activity+json",
|
"Content-Type": "application/activity+json",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue