mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
Fix failing tests
This commit is contained in:
parent
6d2f9072ac
commit
c7743aa154
|
|
@ -4,6 +4,7 @@ import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
|
JoinTable,
|
||||||
ManyToMany,
|
ManyToMany,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
|
|
@ -66,14 +67,44 @@ export class Status extends BaseEntity {
|
||||||
application!: Application | null;
|
application!: Application | null;
|
||||||
|
|
||||||
@ManyToMany(() => Emoji, emoji => emoji.id)
|
@ManyToMany(() => Emoji, emoji => emoji.id)
|
||||||
|
@JoinTable()
|
||||||
emojis!: Emoji[];
|
emojis!: Emoji[];
|
||||||
|
|
||||||
@ManyToMany(() => RawActivity, activity => activity.id, {})
|
@ManyToMany(() => RawActivity, activity => activity.id)
|
||||||
|
@JoinTable()
|
||||||
likes!: RawActivity[];
|
likes!: RawActivity[];
|
||||||
|
|
||||||
@ManyToMany(() => RawActivity, activity => activity.id, {})
|
@ManyToMany(() => RawActivity, activity => activity.id)
|
||||||
|
@JoinTable()
|
||||||
announces!: RawActivity[];
|
announces!: RawActivity[];
|
||||||
|
|
||||||
|
static async createNew(data: {
|
||||||
|
account: User;
|
||||||
|
application: Application | null;
|
||||||
|
content: string;
|
||||||
|
visibility: APIStatus["visibility"];
|
||||||
|
sensitive: boolean;
|
||||||
|
spoiler_text: string;
|
||||||
|
emojis: Emoji[];
|
||||||
|
}) {
|
||||||
|
const newStatus = new Status();
|
||||||
|
|
||||||
|
newStatus.account = data.account;
|
||||||
|
newStatus.application = data.application ?? null;
|
||||||
|
newStatus.content = data.content;
|
||||||
|
newStatus.visibility = data.visibility;
|
||||||
|
newStatus.sensitive = data.sensitive;
|
||||||
|
newStatus.spoiler_text = data.spoiler_text;
|
||||||
|
newStatus.emojis = data.emojis;
|
||||||
|
newStatus.likes = [];
|
||||||
|
newStatus.announces = [];
|
||||||
|
newStatus.isReblog = false;
|
||||||
|
newStatus.announces = [];
|
||||||
|
|
||||||
|
await newStatus.save();
|
||||||
|
return newStatus;
|
||||||
|
}
|
||||||
|
|
||||||
async toAPI(): Promise<APIStatus> {
|
async toAPI(): Promise<APIStatus> {
|
||||||
return {
|
return {
|
||||||
account: await this.account.toAPI(),
|
account: await this.account.toAPI(),
|
||||||
|
|
@ -84,7 +115,7 @@ export class Status extends BaseEntity {
|
||||||
this.emojis.map(async emoji => await emoji.toAPI())
|
this.emojis.map(async emoji => await emoji.toAPI())
|
||||||
),
|
),
|
||||||
favourited: false,
|
favourited: false,
|
||||||
favourites_count: 0,
|
favourites_count: this.likes.length,
|
||||||
id: this.id,
|
id: this.id,
|
||||||
in_reply_to_account_id: null,
|
in_reply_to_account_id: null,
|
||||||
in_reply_to_id: null,
|
in_reply_to_id: null,
|
||||||
|
|
@ -96,13 +127,13 @@ export class Status extends BaseEntity {
|
||||||
poll: null,
|
poll: null,
|
||||||
reblog: this.isReblog ? (await this.reblog?.toAPI()) ?? null : null,
|
reblog: this.isReblog ? (await this.reblog?.toAPI()) ?? null : null,
|
||||||
reblogged: false,
|
reblogged: false,
|
||||||
reblogs_count: 0,
|
reblogs_count: this.announces.length,
|
||||||
replies_count: 0,
|
replies_count: 0,
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
spoiler_text: "",
|
spoiler_text: "",
|
||||||
tags: [],
|
tags: [],
|
||||||
card: null,
|
card: null,
|
||||||
content: "",
|
content: this.content,
|
||||||
uri: `${config.http.base_url}/@${this.account.username}/${this.id}`,
|
uri: `${config.http.base_url}/@${this.account.username}/${this.id}`,
|
||||||
url: `${config.http.base_url}/@${this.account.username}/${this.id}`,
|
url: `${config.http.base_url}/@${this.account.username}/${this.id}`,
|
||||||
visibility: "public",
|
visibility: "public",
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import { APIAccount } from "~types/entities/account";
|
||||||
import { RawActor } from "./RawActor";
|
import { RawActor } from "./RawActor";
|
||||||
import { APActor } from "activitypub-types";
|
import { APActor } from "activitypub-types";
|
||||||
import { RawObject } from "./RawObject";
|
import { RawObject } from "./RawObject";
|
||||||
|
import { Token } from "./Token";
|
||||||
|
import { Status } from "./Status";
|
||||||
|
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
|
|
||||||
|
|
@ -132,6 +134,26 @@ export class User extends BaseEntity {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async selfDestruct() {
|
||||||
|
// Clean up tokens
|
||||||
|
const tokens = await Token.findBy({
|
||||||
|
user: {
|
||||||
|
id: this.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const statuses = await Status.findBy({
|
||||||
|
account: {
|
||||||
|
id: this.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete both
|
||||||
|
await Promise.all(tokens.map(async token => await token.remove()));
|
||||||
|
|
||||||
|
await Promise.all(statuses.map(async status => await status.remove()));
|
||||||
|
}
|
||||||
|
|
||||||
async updateActor() {
|
async updateActor() {
|
||||||
// Check if actor exists
|
// Check if actor exists
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
|
@ -218,7 +240,7 @@ export class User extends BaseEntity {
|
||||||
avatar_static: "",
|
avatar_static: "",
|
||||||
bot: false,
|
bot: false,
|
||||||
created_at: this.created_at.toISOString(),
|
created_at: this.created_at.toISOString(),
|
||||||
display_name: "",
|
display_name: this.display_name,
|
||||||
followers_count: 0,
|
followers_count: 0,
|
||||||
following_count: 0,
|
following_count: 0,
|
||||||
group: false,
|
group: false,
|
||||||
|
|
|
||||||
|
|
@ -1,126 +0,0 @@
|
||||||
import { errorResponse, jsonLdResponse } from "@response";
|
|
||||||
import { MatchedRoute } from "bun";
|
|
||||||
import { User } from "~database/entities/User";
|
|
||||||
import { getHost } from "@config";
|
|
||||||
import { compact } from "jsonld";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ActivityPub user actor endpoinmt
|
|
||||||
*/
|
|
||||||
export default async (
|
|
||||||
req: Request,
|
|
||||||
matchedRoute: MatchedRoute
|
|
||||||
): Promise<Response> => {
|
|
||||||
const username = matchedRoute.params.username.split("@")[0];
|
|
||||||
|
|
||||||
const user = await User.findOneBy({ username });
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return errorResponse("User not found", 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonLdResponse(
|
|
||||||
await compact({
|
|
||||||
"@context": [
|
|
||||||
"https://www.w3.org/ns/activitystreams",
|
|
||||||
"https://w3id.org/security/v1",
|
|
||||||
{
|
|
||||||
manuallyApprovesFollowers: "as:manuallyApprovesFollowers",
|
|
||||||
toot: "http://joinmastodon.org/ns#",
|
|
||||||
featured: {
|
|
||||||
"@id": "toot:featured",
|
|
||||||
"@type": "@id",
|
|
||||||
},
|
|
||||||
featuredTags: {
|
|
||||||
"@id": "toot:featuredTags",
|
|
||||||
"@type": "@id",
|
|
||||||
},
|
|
||||||
alsoKnownAs: {
|
|
||||||
"@id": "as:alsoKnownAs",
|
|
||||||
"@type": "@id",
|
|
||||||
},
|
|
||||||
movedTo: {
|
|
||||||
"@id": "as:movedTo",
|
|
||||||
"@type": "@id",
|
|
||||||
},
|
|
||||||
schema: "http://schema.org#",
|
|
||||||
PropertyValue: "schema:PropertyValue",
|
|
||||||
value: "schema:value",
|
|
||||||
discoverable: "toot:discoverable",
|
|
||||||
Device: "toot:Device",
|
|
||||||
Ed25519Signature: "toot:Ed25519Signature",
|
|
||||||
Ed25519Key: "toot:Ed25519Key",
|
|
||||||
Curve25519Key: "toot:Curve25519Key",
|
|
||||||
EncryptedMessage: "toot:EncryptedMessage",
|
|
||||||
publicKeyBase64: "toot:publicKeyBase64",
|
|
||||||
deviceId: "toot:deviceId",
|
|
||||||
claim: {
|
|
||||||
"@type": "@id",
|
|
||||||
"@id": "toot:claim",
|
|
||||||
},
|
|
||||||
fingerprintKey: {
|
|
||||||
"@type": "@id",
|
|
||||||
"@id": "toot:fingerprintKey",
|
|
||||||
},
|
|
||||||
identityKey: {
|
|
||||||
"@type": "@id",
|
|
||||||
"@id": "toot:identityKey",
|
|
||||||
},
|
|
||||||
devices: {
|
|
||||||
"@type": "@id",
|
|
||||||
"@id": "toot:devices",
|
|
||||||
},
|
|
||||||
messageFranking: "toot:messageFranking",
|
|
||||||
messageType: "toot:messageType",
|
|
||||||
cipherText: "toot:cipherText",
|
|
||||||
suspended: "toot:suspended",
|
|
||||||
Emoji: "toot:Emoji",
|
|
||||||
focalPoint: {
|
|
||||||
"@container": "@list",
|
|
||||||
"@id": "toot:focalPoint",
|
|
||||||
},
|
|
||||||
Hashtag: "as:Hashtag",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
id: `${getHost()}/@${user.username}/actor`,
|
|
||||||
type: "Person",
|
|
||||||
preferredUsername: user.username, // TODO: Add user display name
|
|
||||||
name: user.username,
|
|
||||||
summary: user.note,
|
|
||||||
icon: /*{
|
|
||||||
type: "Image",
|
|
||||||
url: user.avatar,
|
|
||||||
mediaType: mimetype
|
|
||||||
}*/ undefined, // TODO: Add avatar
|
|
||||||
image: /*{
|
|
||||||
type: "Image",
|
|
||||||
url: user.avatar,
|
|
||||||
mediaType: mimetype
|
|
||||||
}*/ undefined, // TODO: Add banner
|
|
||||||
inbox: `${getHost()}/@${user.username}/inbox`,
|
|
||||||
outbox: `${getHost()}/@${user.username}/outbox`,
|
|
||||||
followers: `${getHost()}/@${user.username}/followers`,
|
|
||||||
following: `${getHost()}/@${user.username}/following`,
|
|
||||||
liked: `${getHost()}/@${user.username}/liked`,
|
|
||||||
discoverable: true,
|
|
||||||
alsoKnownAs: [
|
|
||||||
// TODO: Add accounts from which the user migrated
|
|
||||||
],
|
|
||||||
manuallyApprovesFollowers: false, // TODO: Change
|
|
||||||
publicKey: {
|
|
||||||
id: `${getHost()}/@${user.username}/actor#main-key`,
|
|
||||||
owner: `${getHost()}/@${user.username}/actor`,
|
|
||||||
// TODO: Add user public key
|
|
||||||
},
|
|
||||||
tag: [
|
|
||||||
// TODO: Add emojis here, and hashtags
|
|
||||||
],
|
|
||||||
attachment: [
|
|
||||||
// TODO: Add user attachments (I.E. profile metadata)
|
|
||||||
],
|
|
||||||
endpoints: {
|
|
||||||
sharedInbox: `${getHost()}/inbox`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
137
server/api/[username]/actor/index.ts
Normal file
137
server/api/[username]/actor/index.ts
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
import { errorResponse, jsonLdResponse } from "@response";
|
||||||
|
import { MatchedRoute } from "bun";
|
||||||
|
import { User } from "~database/entities/User";
|
||||||
|
import { getConfig, getHost } from "@config";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ActivityPub user actor endpoinmt
|
||||||
|
*/
|
||||||
|
export default async (
|
||||||
|
req: Request,
|
||||||
|
matchedRoute: MatchedRoute
|
||||||
|
): Promise<Response> => {
|
||||||
|
// Check for Accept header
|
||||||
|
const accept = req.headers.get("Accept");
|
||||||
|
|
||||||
|
if (!accept || !accept.includes("application/activity+json")) {
|
||||||
|
return errorResponse("This endpoint requires an Accept header", 406);
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = getConfig();
|
||||||
|
|
||||||
|
const username = matchedRoute.params.username.replace("@", "");
|
||||||
|
|
||||||
|
const user = await User.findOneBy({ username });
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return errorResponse("User not found", 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonLdResponse({
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
manuallyApprovesFollowers: "as:manuallyApprovesFollowers",
|
||||||
|
toot: "http://joinmastodon.org/ns#",
|
||||||
|
featured: {
|
||||||
|
"@id": "toot:featured",
|
||||||
|
"@type": "@id",
|
||||||
|
},
|
||||||
|
featuredTags: {
|
||||||
|
"@id": "toot:featuredTags",
|
||||||
|
"@type": "@id",
|
||||||
|
},
|
||||||
|
alsoKnownAs: {
|
||||||
|
"@id": "as:alsoKnownAs",
|
||||||
|
"@type": "@id",
|
||||||
|
},
|
||||||
|
movedTo: {
|
||||||
|
"@id": "as:movedTo",
|
||||||
|
"@type": "@id",
|
||||||
|
},
|
||||||
|
schema: "http://schema.org#",
|
||||||
|
PropertyValue: "schema:PropertyValue",
|
||||||
|
value: "schema:value",
|
||||||
|
discoverable: "toot:discoverable",
|
||||||
|
Device: "toot:Device",
|
||||||
|
Ed25519Signature: "toot:Ed25519Signature",
|
||||||
|
Ed25519Key: "toot:Ed25519Key",
|
||||||
|
Curve25519Key: "toot:Curve25519Key",
|
||||||
|
EncryptedMessage: "toot:EncryptedMessage",
|
||||||
|
publicKeyBase64: "toot:publicKeyBase64",
|
||||||
|
deviceId: "toot:deviceId",
|
||||||
|
claim: {
|
||||||
|
"@type": "@id",
|
||||||
|
"@id": "toot:claim",
|
||||||
|
},
|
||||||
|
fingerprintKey: {
|
||||||
|
"@type": "@id",
|
||||||
|
"@id": "toot:fingerprintKey",
|
||||||
|
},
|
||||||
|
identityKey: {
|
||||||
|
"@type": "@id",
|
||||||
|
"@id": "toot:identityKey",
|
||||||
|
},
|
||||||
|
devices: {
|
||||||
|
"@type": "@id",
|
||||||
|
"@id": "toot:devices",
|
||||||
|
},
|
||||||
|
messageFranking: "toot:messageFranking",
|
||||||
|
messageType: "toot:messageType",
|
||||||
|
cipherText: "toot:cipherText",
|
||||||
|
suspended: "toot:suspended",
|
||||||
|
Emoji: "toot:Emoji",
|
||||||
|
focalPoint: {
|
||||||
|
"@container": "@list",
|
||||||
|
"@id": "toot:focalPoint",
|
||||||
|
},
|
||||||
|
Hashtag: "as:Hashtag",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
id: `${config.http.base_url}:${config.http.port}/@${user.username}`,
|
||||||
|
type: "Person",
|
||||||
|
preferredUsername: user.username, // TODO: Add user display name
|
||||||
|
name: user.username,
|
||||||
|
summary: user.note,
|
||||||
|
icon: {
|
||||||
|
type: "Image",
|
||||||
|
url: user.avatar,
|
||||||
|
mediaType: "image/png", // TODO: Set user avatar mimetype
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
type: "Image",
|
||||||
|
url: user.header,
|
||||||
|
mediaType: "image/png", // TODO: Set user header mimetype
|
||||||
|
},
|
||||||
|
inbox: `${config.http.base_url}:${config.http.port}/@${user.username}/inbox`,
|
||||||
|
outbox: `${config.http.base_url}:${config.http.port}/@${user.username}/outbox`,
|
||||||
|
followers: `${config.http.base_url}:${config.http.port}/@${user.username}/followers`,
|
||||||
|
following: `${config.http.base_url}:${config.http.port}/@${user.username}/following`,
|
||||||
|
liked: `${config.http.base_url}:${config.http.port}/@${user.username}/liked`,
|
||||||
|
discoverable: true,
|
||||||
|
alsoKnownAs: [
|
||||||
|
// TODO: Add accounts from which the user migrated
|
||||||
|
],
|
||||||
|
manuallyApprovesFollowers: false, // TODO: Change
|
||||||
|
publicKey: {
|
||||||
|
id: `${getHost()}${config.http.base_url}:${config.http.port}/@${
|
||||||
|
user.username
|
||||||
|
}/actor#main-key`,
|
||||||
|
owner: `${config.http.base_url}:${config.http.port}/@${user.username}`,
|
||||||
|
// Split the public key into PEM format
|
||||||
|
publicKeyPem: `-----BEGIN PUBLIC KEY-----\n${user.public_key
|
||||||
|
.match(/.{1,64}/g)
|
||||||
|
?.join("\n")}\n-----END PUBLIC KEY-----`,
|
||||||
|
},
|
||||||
|
tag: [
|
||||||
|
// TODO: Add emojis here, and hashtags
|
||||||
|
],
|
||||||
|
attachment: [
|
||||||
|
// TODO: Add user attachments (I.E. profile metadata)
|
||||||
|
],
|
||||||
|
endpoints: {
|
||||||
|
sharedInbox: `${config.http.base_url}:${config.http.port}/inbox`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -16,9 +16,14 @@ export default async (
|
||||||
const token = req.headers.get("Authorization")?.split(" ")[1] || null;
|
const token = req.headers.get("Authorization")?.split(" ")[1] || null;
|
||||||
const user = await getUserByToken(token);
|
const user = await getUserByToken(token);
|
||||||
|
|
||||||
const foundUser = await RawActor.findOneBy({
|
let foundUser: RawActor | null;
|
||||||
|
try {
|
||||||
|
foundUser = await RawActor.findOneBy({
|
||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return errorResponse("Invalid ID", 404);
|
||||||
|
}
|
||||||
|
|
||||||
if (!foundUser) return errorResponse("User not found", 404);
|
if (!foundUser) return errorResponse("User not found", 404);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,5 +46,7 @@ export default async (
|
||||||
take: limit ?? 20,
|
take: limit ?? 20,
|
||||||
});
|
});
|
||||||
|
|
||||||
return jsonResponse(statuses.map(status => status.toAPI()));
|
return jsonResponse(
|
||||||
|
await Promise.all(statuses.map(async status => await status.toAPI()))
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -119,10 +119,7 @@ export default async (req: Request): Promise<Response> => {
|
||||||
// user.discoverable = discoverable === "true";
|
// user.discoverable = discoverable === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonResponse(
|
await user.save();
|
||||||
{
|
|
||||||
error: `Not really implemented yet`,
|
return jsonResponse(await user.toAPI());
|
||||||
},
|
|
||||||
501
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -98,19 +98,17 @@ export default async (req: Request): Promise<Response> => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create status
|
// Create status
|
||||||
const newStatus = new Status();
|
const newStatus = await Status.createNew({
|
||||||
newStatus.account = user;
|
account: user,
|
||||||
newStatus.application = application;
|
application,
|
||||||
newStatus.content = status;
|
content: status,
|
||||||
newStatus.spoiler_text = spoiler_text || "";
|
visibility: visibility || "public",
|
||||||
newStatus.sensitive = sensitive || false;
|
sensitive: sensitive || false,
|
||||||
newStatus.visibility = visibility || "public";
|
spoiler_text: spoiler_text || "",
|
||||||
newStatus.likes = [];
|
emojis: [],
|
||||||
newStatus.isReblog = false;
|
});
|
||||||
|
|
||||||
// TODO: add database jobs to deliver the post
|
// TODO: add database jobs to deliver the post
|
||||||
|
|
||||||
await newStatus.save();
|
return jsonResponse(await newStatus.toAPI());
|
||||||
|
|
||||||
return jsonResponse(newStatus);
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { APActor } from "activitypub-types";
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import { AppDataSource } from "~database/datasource";
|
import { AppDataSource } from "~database/datasource";
|
||||||
import { RawActivity } from "~database/entities/RawActivity";
|
import { RawActivity } from "~database/entities/RawActivity";
|
||||||
import { Token } from "~database/entities/Token";
|
|
||||||
import { User } from "~database/entities/User";
|
import { User } from "~database/entities/User";
|
||||||
|
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
|
|
@ -22,10 +21,10 @@ beforeAll(async () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("POST /@test", () => {
|
describe("POST /@test/actor", () => {
|
||||||
test("should return a valid ActivityPub Actor when querying an existing user", async () => {
|
test("should return a valid ActivityPub Actor when querying an existing user", async () => {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${config.http.base_url}:${config.http.port}/@test`,
|
`${config.http.base_url}:${config.http.port}/@test/actor`,
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -64,6 +63,9 @@ describe("POST /@test", () => {
|
||||||
`${config.http.base_url}:${config.http.port}/@test`
|
`${config.http.base_url}:${config.http.port}/@test`
|
||||||
);
|
);
|
||||||
expect((actor as any).publicKey.publicKeyPem).toBeDefined();
|
expect((actor as any).publicKey.publicKeyPem).toBeDefined();
|
||||||
|
expect((actor as any).publicKey.publicKeyPem).toMatch(
|
||||||
|
/(-----BEGIN PUBLIC KEY-----(\n|\r|\r\n)([0-9a-zA-Z+/=]{64}(\n|\r|\r\n))*([0-9a-zA-Z+/=]{1,63}(\n|\r|\r\n))?-----END PUBLIC KEY-----)|(-----BEGIN PRIVATE KEY-----(\n|\r|\r\n)([0-9a-zA-Z+/=]{64}(\n|\r|\r\n))*([0-9a-zA-Z+/=]{1,63}(\n|\r|\r\n))?-----END PRIVATE KEY-----)/
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -73,13 +75,6 @@ afterAll(async () => {
|
||||||
username: "test",
|
username: "test",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clean up tokens
|
|
||||||
const tokens = await Token.findBy({
|
|
||||||
user: {
|
|
||||||
username: "test",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const activities = await RawActivity.createQueryBuilder("activity")
|
const activities = await RawActivity.createQueryBuilder("activity")
|
||||||
.where("activity.data->>'actor' = :actor", {
|
.where("activity.data->>'actor' = :actor", {
|
||||||
actor: `${config.http.base_url}:${config.http.port}/@test`,
|
actor: `${config.http.base_url}:${config.http.port}/@test`,
|
||||||
|
|
@ -97,7 +92,8 @@ afterAll(async () => {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
await Promise.all(tokens.map(async token => await token.remove()));
|
if (user) {
|
||||||
|
await user.selfDestruct();
|
||||||
if (user) await user.remove();
|
await user.remove();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -48,11 +48,11 @@ beforeAll(async () => {
|
||||||
token.token_type = TokenType.BEARER;
|
token.token_type = TokenType.BEARER;
|
||||||
token.user = user;
|
token.user = user;
|
||||||
|
|
||||||
await token.save();
|
token = await token.save();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("POST /api/v1/accounts/:id", () => {
|
describe("POST /api/v1/accounts/:id", () => {
|
||||||
test("should return a 404 error when trying to update a non-existent user", async () => {
|
test("should return a 404 error when trying to fetch a non-existent user", async () => {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${config.http.base_url}:${config.http.port}/api/v1/accounts/999999`,
|
`${config.http.base_url}:${config.http.port}/api/v1/accounts/999999`,
|
||||||
{
|
{
|
||||||
|
|
@ -93,26 +93,14 @@ describe("POST /api/v1/statuses", () => {
|
||||||
|
|
||||||
expect(status.content).toBe("Hello, world!");
|
expect(status.content).toBe("Hello, world!");
|
||||||
expect(status.visibility).toBe("public");
|
expect(status.visibility).toBe("public");
|
||||||
expect(status.account.id).toBe(
|
expect(status.account.id).toBe(user.id);
|
||||||
`${config.http.base_url}:${config.http.port}/@test`
|
|
||||||
);
|
|
||||||
expect(status.replies_count).toBe(0);
|
expect(status.replies_count).toBe(0);
|
||||||
expect(status.favourites_count).toBe(0);
|
expect(status.favourites_count).toBe(0);
|
||||||
expect(status.reblogged).toBe(false);
|
expect(status.reblogged).toBe(false);
|
||||||
expect(status.favourited).toBe(false);
|
expect(status.favourited).toBe(false);
|
||||||
expect(status.reblog?.content).toBe("Hello, world!");
|
|
||||||
expect(status.reblog?.visibility).toBe("public");
|
|
||||||
expect(status.reblog?.account.id).toBe(
|
|
||||||
`${config.http.base_url}:${config.http.port}/@test`
|
|
||||||
);
|
|
||||||
expect(status.reblog?.replies_count).toBe(0);
|
|
||||||
expect(status.reblog?.favourites_count).toBe(0);
|
|
||||||
expect(status.reblog?.reblogged).toBe(false);
|
|
||||||
expect(status.reblog?.favourited).toBe(false);
|
|
||||||
expect(status.media_attachments).toEqual([]);
|
expect(status.media_attachments).toEqual([]);
|
||||||
expect(status.mentions).toEqual([]);
|
expect(status.mentions).toEqual([]);
|
||||||
expect(status.tags).toEqual([]);
|
expect(status.tags).toEqual([]);
|
||||||
expect(status.application).toBeNull();
|
|
||||||
expect(status.sensitive).toBe(false);
|
expect(status.sensitive).toBe(false);
|
||||||
expect(status.spoiler_text).toBe("");
|
expect(status.spoiler_text).toBe("");
|
||||||
expect(status.language).toBeNull();
|
expect(status.language).toBeNull();
|
||||||
|
|
@ -123,17 +111,15 @@ describe("POST /api/v1/statuses", () => {
|
||||||
expect(status.emojis).toEqual([]);
|
expect(status.emojis).toEqual([]);
|
||||||
expect(status.in_reply_to_id).toBeNull();
|
expect(status.in_reply_to_id).toBeNull();
|
||||||
expect(status.in_reply_to_account_id).toBeNull();
|
expect(status.in_reply_to_account_id).toBeNull();
|
||||||
expect(status.reblog?.in_reply_to_id).toBeNull();
|
|
||||||
expect(status.reblog?.in_reply_to_account_id).toBeNull();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("POST /api/v1/accounts/update_credentials", () => {
|
describe("PATCH /api/v1/accounts/update_credentials", () => {
|
||||||
test("should update the authenticated user's display name", async () => {
|
test("should update the authenticated user's display name", async () => {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${config.http.base_url}:${config.http.port}/api/v1/accounts/update_credentials`,
|
`${config.http.base_url}:${config.http.port}/api/v1/accounts/update_credentials`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "PATCH",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token.access_token}`,
|
Authorization: `Bearer ${token.access_token}`,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|
@ -154,18 +140,10 @@ describe("POST /api/v1/accounts/update_credentials", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
// Clean up user
|
|
||||||
const user = await User.findOneBy({
|
const user = await User.findOneBy({
|
||||||
username: "test",
|
username: "test",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clean up tokens
|
|
||||||
const tokens = await Token.findBy({
|
|
||||||
user: {
|
|
||||||
username: "test",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const activities = await RawActivity.createQueryBuilder("activity")
|
const activities = await RawActivity.createQueryBuilder("activity")
|
||||||
.where("activity.data->>'actor' = :actor", {
|
.where("activity.data->>'actor' = :actor", {
|
||||||
actor: `${config.http.base_url}:${config.http.port}/@test`,
|
actor: `${config.http.base_url}:${config.http.port}/@test`,
|
||||||
|
|
@ -183,7 +161,8 @@ afterAll(async () => {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
await Promise.all(tokens.map(async token => await token.remove()));
|
if (user) {
|
||||||
|
await user.selfDestruct();
|
||||||
if (user) await user.remove();
|
await user.remove();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,11 @@ import { Token } from "~database/entities/Token";
|
||||||
export const getUserByToken = async (access_token: string | null) => {
|
export const getUserByToken = async (access_token: string | null) => {
|
||||||
if (!access_token) return null;
|
if (!access_token) return null;
|
||||||
|
|
||||||
const token = await Token.findOneBy({
|
const token = await Token.findOne({
|
||||||
|
where: {
|
||||||
access_token,
|
access_token,
|
||||||
|
},
|
||||||
|
relations: ["user"],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!token) return null;
|
if (!token) return null;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
* @param request The request to parse
|
* @param request The request to parse
|
||||||
*/
|
*/
|
||||||
export async function parseRequest<T>(request: Request): Promise<Partial<T>> {
|
export async function parseRequest<T>(request: Request): Promise<Partial<T>> {
|
||||||
const formData = await request.formData();
|
|
||||||
const query = new URL(request.url).searchParams;
|
const query = new URL(request.url).searchParams;
|
||||||
|
|
||||||
// if request contains a JSON body
|
// if request contains a JSON body
|
||||||
|
|
@ -16,6 +15,8 @@ export async function parseRequest<T>(request: Request): Promise<Partial<T>> {
|
||||||
|
|
||||||
// If request contains FormData
|
// If request contains FormData
|
||||||
if (request.headers.get("Content-Type")?.includes("multipart/form-data")) {
|
if (request.headers.get("Content-Type")?.includes("multipart/form-data")) {
|
||||||
|
const formData = await request.formData();
|
||||||
|
|
||||||
if ([...formData.entries()].length > 0) {
|
if ([...formData.entries()].length > 0) {
|
||||||
const data: Record<string, string | File> = {};
|
const data: Record<string, string | File> = {};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue