diff --git a/database/entities/Status.ts b/database/entities/Status.ts index 42c2318f..d793e3ee 100644 --- a/database/entities/Status.ts +++ b/database/entities/Status.ts @@ -14,6 +14,7 @@ import { User } from "./User"; import { Application } from "./Application"; import { Emoji } from "./Emoji"; import { Favourite } from "./Favourite"; +import { RawActivity } from "./RawActivity"; const config = getConfig(); @@ -68,6 +69,12 @@ export class Status extends BaseEntity { @ManyToMany(() => Emoji, emoji => emoji.id) emojis!: Emoji[]; + @ManyToMany(() => RawActivity, activity => activity.id, {}) + likes: RawActivity[] = []; + + @ManyToMany(() => RawActivity, activity => activity.id, {}) + announces: RawActivity[] = []; + async getFavourites(): Promise { return Favourite.find({ where: { diff --git a/database/entities/User.ts b/database/entities/User.ts index 2e0e6cf9..4c8d6063 100644 --- a/database/entities/User.ts +++ b/database/entities/User.ts @@ -27,9 +27,12 @@ export class User extends BaseEntity { username!: string; @Column("varchar", { - nullable: true, + unique: true, }) - password!: string | null; + display_name!: string; + + @Column("varchar") + password!: string; @Column("varchar", { unique: true, @@ -47,9 +50,6 @@ export class User extends BaseEntity { @UpdateDateColumn() updated_at!: Date; - @Column("boolean") - isRemote!: boolean; - // eslint-disable-next-line @typescript-eslint/require-await async toAPI(): Promise { return { diff --git a/server/api/.well-known/nodeinfo/index.ts b/server/api/.well-known/nodeinfo/index.ts new file mode 100644 index 00000000..e9270922 --- /dev/null +++ b/server/api/.well-known/nodeinfo/index.ts @@ -0,0 +1,17 @@ +import { MatchedRoute } from "bun"; +import { getHost } from "@config"; + +/** + * ARedirect to /nodeinfo/2.0 + */ +export default async ( + req: Request, + matchedRoute: MatchedRoute +): Promise => { + return new Response("", { + status: 301, + headers: { + Location: `https://${getHost()}/.well-known/nodeinfo/2.0`, + }, + }); +}; diff --git a/server/api/@[username]/inbox/index.ts b/server/api/@[username]/inbox/index.ts index dcc4e9e0..b4968442 100644 --- a/server/api/@[username]/inbox/index.ts +++ b/server/api/@[username]/inbox/index.ts @@ -1,6 +1,15 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { errorResponse, jsonResponse } from "@response"; -import { APActivity, APCreate, APObject } from "activitypub-types"; +import { + APAccept, + APActivity, + APCreate, + APDelete, + APFollow, + APObject, + APReject, + APUpdate, +} from "activitypub-types"; import { MatchedRoute } from "bun"; import { RawActivity } from "~database/entities/RawActivity"; import { RawObject } from "~database/entities/RawObject"; @@ -27,6 +36,7 @@ export default async ( case "Create" as APCreate: { // Body is an APCreate object // Store the Create object in database + // TODO: Add authentication // Check is Activity already exists const exists = await RawActivity.findOneBy({ @@ -63,6 +73,52 @@ export default async ( await activity.save(); break; } + case "Update" as APUpdate: { + // Body is an APUpdate object + // Replace the object in database with the new provided object + // TODO: Add authentication + + const object = await RawObject.findOneBy({ + data: { + id: (body.object as RawObject).id, + }, + }); + + if (!object) return errorResponse("Object not found", 404); + + object.data = body.object as APObject; + + await object.save(); + break; + } + case "Delete" as APDelete: { + // Body is an APDelete object + // Delete the object from database + // TODO: Add authentication + + const object = await RawObject.findOneBy({ + data: { + id: (body.object as RawObject).id, + }, + }); + + if (!object) return errorResponse("Object not found", 404); + + await object.remove(); + break; + } + case "Accept" as APAccept: { + // Body is an APAccept object + // Add the actor to the object actor's followers list + // TODO: Add actor to object actor's followers list + break; + } + case "Reject" as APReject: { + // Body is an APReject object + // Mark the follow request as not pending + // TODO: Implement + break; + } } return jsonResponse({}); diff --git a/server/api/nodeinfo/2.0/index.ts b/server/api/nodeinfo/2.0/index.ts new file mode 100644 index 00000000..d57bccc1 --- /dev/null +++ b/server/api/nodeinfo/2.0/index.ts @@ -0,0 +1,21 @@ +import { jsonResponse } from "@response"; + +/** + * ActivityPub nodeinfo 2.0 endpoint + */ +// eslint-disable-next-line @typescript-eslint/require-await +export default async (): Promise => { + // TODO: Implement this + return jsonResponse({ + version: "2.0", + software: { name: "fediproject", version: "0.0.1" }, + protocols: ["activitypub"], + services: { outbound: [], inbound: [] }, + usage: { + users: { total: 0, activeMonth: 0, activeHalfyear: 0 }, + localPosts: 0, + }, + openRegistrations: false, + metadata: {}, + }); +}; diff --git a/utils/config.ts b/utils/config.ts index 67a33205..d9b441bc 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -12,6 +12,20 @@ export interface ConfigType { port: number; base_url: string; }; + validation: { + max_displayname_size: number; + max_bio_size: number; + max_username_size: number; + max_note_size: number; + max_media_size: number; + max_media_attachments: number; + max_media_description_size: number; + + username_blacklist: string[]; + blacklist_tempmail: boolean; + email_blacklist: string[]; + url_scheme_whitelist: string[]; + }; [key: string]: unknown; }